packaging: bring systemd tarball to feature parity with .deb / AUR

The generic systemd install tarball is the catch-all install path
for systemd Linux distros that don't have a per-format package
(Fedora, RHEL/CentOS, openSUSE, Alpine, etc.). It had drifted
behind the .deb and AUR packages and was missing fips-gateway, the
mesh-interface firewall baseline, and the multi-backend DNS helper
in the shipped tarball. Bring it to parity:

- New `packaging/systemd/fips-gateway.service` (clone of the .deb
  unit; ExecStart pointed at `/usr/local/bin/fips-gateway`). Not
  enabled at install time; operator opt-in.
- New `packaging/systemd/fips-firewall.service` (clone of the .deb
  unit; nft path unchanged at `/usr/sbin/nft`). Not enabled at
  install time; operator opt-in.
- `build-tarball.sh` now bundles the `fips-gateway` binary, the two
  new units, the `fips.nft` baseline conffile, and the
  `fips-dns-setup` / `fips-dns-teardown` multi-backend helpers from
  `packaging/common/`.
- `install.sh` now installs `fips-gateway` to `/usr/local/bin/`,
  installs both new units to `/etc/systemd/system/` (without
  enabling them), preserves `/etc/fips/fips.nft` on upgrade like
  `fips.yaml`, and creates the `/etc/fips/fips.d/` operator drop-in
  directory. Post-install messaging mentions both opt-in services.
- `uninstall.sh` stops and disables the optional services in
  dependency order (firewall, gateway, dns, daemon), removes the
  new unit files, and removes the gateway binary. `--purge` already
  handles `/etc/fips/` removal which covers `fips.nft` and
  `fips.d/`.
- `README.install.md` documents all of the above: expanded
  "What Gets Installed" table, new sections covering the firewall
  baseline and the LAN gateway, refreshed DNS section reflecting
  the multi-backend setup helper (systemd dns-delegate /
  systemd-resolved drop-in / per-link resolvectl / dnsmasq /
  NetworkManager-dnsmasq), and updated Service Management.

Also fixes a latent packaging bug: `install.sh` previously
referenced `${SCRIPT_DIR}/../common/fips-dns-setup`, a path that
exists only in the source-repo layout and not in the extracted
tarball. The script now resolves the helper from the staging
directory first (the tarball case), falling back to the source-repo
relative path. Bug latent since the multi-backend DNS helpers
landed.

CHANGELOG `[Unreleased]` documents the parity bump under Changed
and the path-resolution fix under Fixed.

Closes the longest-standing parity gap for non-Debian / non-Arch
systemd Linux distros installing from the release-distribution
tarball.
This commit is contained in:
Johnathan Corgan
2026-05-10 21:52:47 +00:00
parent d52d7debb7
commit eaba693b18
7 changed files with 240 additions and 40 deletions
+24
View File
@@ -388,9 +388,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
bind by setting `dns.listen: "[::]:53"` explicitly. The new
default binds IPv6 loopback only — forwarders that reach the
gateway over IPv4 loopback need an explicit IPv4 listen address.
- Generic systemd install tarball brought to feature parity with
the `.deb` and AUR packages. The tarball now ships the
`fips-gateway` binary with its (operator-opt-in)
`fips-gateway.service`, a `fips-firewall.service` unit with the
`/etc/fips/fips.nft` mesh-interface nftables baseline (also
opt-in), an `/etc/fips/fips.d/` operator drop-in directory for
per-service nft rules, and the multi-backend `fips-dns-setup` /
`fips-dns-teardown` helpers. `install.sh` and `uninstall.sh`
handle the new units and conffile (preserve-on-upgrade for
`fips.nft`, like `fips.yaml`). `README.install.md` documents
the gateway, firewall, and DNS-routing services. Closes the
longest-standing parity gap for non-Debian / non-Arch systemd
Linux distros (Fedora, RHEL/CentOS, openSUSE, etc.) installing
from the release-distribution tarball.
### Fixed
- Generic systemd install tarball: `install.sh` now correctly
resolves the `fips-dns-setup` and `fips-dns-teardown` helpers
from the tarball staging directory. Previously the script
referenced them at `${SCRIPT_DIR}/../common/`, a path that
exists only in the source-repo layout, not in the extracted
tarball. Bug latent since the multi-backend DNS helpers
landed in `7260ad2`; only manifested when operators ran
`install.sh` from an extracted tarball rather than from a
source checkout.
- Adopted NAT-traversed UDP transports inherit the primary listener's
MTU and buffer config. `Node::adopt_established_traversal`
constructed the adopted UDP transport with `UdpConfig::default()`
+83 -14
View File
@@ -15,12 +15,24 @@ sudo ./install.sh
| fips (daemon) | /usr/local/bin/fips |
| fipsctl (CLI) | /usr/local/bin/fipsctl |
| fipstop (TUI) | /usr/local/bin/fipstop |
| fips-gateway (LAN bridge) | /usr/local/bin/fips-gateway |
| Configuration | /etc/fips/fips.yaml |
| Identity key | /etc/fips/fips.key (auto-generated) |
| Public key | /etc/fips/fips.pub (auto-generated) |
| systemd unit | /etc/systemd/system/fips.service |
| Hosts file | /etc/fips/hosts |
| Firewall baseline | /etc/fips/fips.nft |
| Firewall drop-in directory | /etc/fips/fips.d/ |
| Daemon unit | /etc/systemd/system/fips.service (enabled) |
| DNS routing unit | /etc/systemd/system/fips-dns.service (enabled) |
| Gateway unit | /etc/systemd/system/fips-gateway.service (NOT enabled) |
| Firewall unit | /etc/systemd/system/fips-firewall.service (NOT enabled) |
| DNS helpers | /usr/lib/fips/fips-dns-{setup,teardown} |
A system group `fips` is created for control socket access.
A system group `fips` is created for control socket access. By
default, only `fips.service` and `fips-dns.service` are enabled at
install time. `fips-gateway.service` and `fips-firewall.service`
are installed but require explicit operator opt-in (see the
sections below).
## Post-Install Configuration
@@ -101,26 +113,69 @@ peers:
connect_policy: auto_connect
```
### 5. DNS Resolver (optional, requires systemd-resolved)
### 5. DNS Resolver
FIPS includes a DNS responder for `.fips` domain names (port 5354).
On systems running `systemd-resolved`, the installer automatically enables
`fips-dns.service` to route `.fips` queries to the FIPS resolver.
FIPS includes a DNS responder for `.fips` domain names that listens on
`fips0` and on `[::1]:5354`. The `fips-dns.service` helper detects the
host's DNS routing system and configures it to forward `.fips` queries
to the responder. Backends tried in order:
If `systemd-resolved` is not running at install time, DNS integration is
skipped. To enable it later (after starting `systemd-resolved`):
1. systemd `dns-delegate` drop-in (systemd >= 258, declarative)
2. `systemd-resolved` global drop-in via `/etc/systemd/resolved.conf.d/`
3. `systemd-resolved` per-link `resolvectl` (legacy fallback)
4. `dnsmasq` (standalone, drops a config in `/etc/dnsmasq.d/`)
5. NetworkManager with the `dnsmasq` plugin
If none of the supported backends is detected, `fips-dns-setup` logs a
warning with manual instructions and exits cleanly. The daemon itself
keeps working; only the host's `.fips` resolution is left unwired.
The installer enables `fips-dns.service` automatically. To disable
or re-enable later:
```bash
sudo systemctl enable --now fips-dns.service
sudo systemctl disable --now fips-dns.service # disable
sudo systemctl enable --now fips-dns.service # re-enable
```
For manual configuration without `fips-dns.service`:
### 6. Mesh-interface firewall baseline (optional)
`fips.nft` is a default-deny baseline for inbound traffic on the
`fips0` mesh interface. It is shipped as `/etc/fips/fips.nft` (not
loaded by default) along with a disabled `fips-firewall.service`
unit. Enable it explicitly:
```bash
sudo resolvectl dns fips0 127.0.0.1:5354
sudo resolvectl domain fips0 ~fips
sudo systemctl enable --now fips-firewall.service
```
The baseline polices only `fips0`, leaving Docker, Tor, the host
firewall, and other interfaces untouched. Outbound from `fips0` is
unrestricted; inbound is dropped except for replies to outbound
flows, ICMPv6 echo-request, and any operator drop-ins under
`/etc/fips/fips.d/*.nft`. Read the comments at the top of
`/etc/fips/fips.nft` for the full policy and how to add per-service
allow rules.
### 7. Outbound LAN gateway (optional)
`fips-gateway` bridges unmodified LAN hosts to `.fips` destinations
through a DNS-allocated virtual IPv6 pool and kernel nftables NAT.
The binary is installed at `/usr/local/bin/fips-gateway` and a
`fips-gateway.service` unit ships disabled by default.
To enable it, configure the gateway block in `/etc/fips/fips.yaml`,
then:
```bash
sudo systemctl enable --now fips-gateway.service
```
The unit `Requires=fips.service`, waits up to 30 seconds for `fips0`
to come up, and runs `fips-gateway --config /etc/fips/fips.yaml`.
Inbound port-forward rules can be added in the same `gateway:`
block.
## Firewall Ports
| Port | Protocol | Purpose |
@@ -130,14 +185,28 @@ sudo resolvectl domain fips0 ~fips
## Service Management
The install ships four units. `fips.service` and `fips-dns.service`
are enabled at install time. `fips-gateway.service` and
`fips-firewall.service` are installed but disabled until the
operator opts in.
```bash
# Start / stop / restart
# Daemon
sudo systemctl start fips
sudo systemctl stop fips
sudo systemctl restart fips
# View logs
# DNS routing helper
sudo systemctl restart fips-dns
# Optional services (opt-in)
sudo systemctl enable --now fips-firewall # mesh-interface nftables baseline
sudo systemctl enable --now fips-gateway # outbound LAN gateway
# View logs (any of the units above)
sudo journalctl -u fips -f
sudo journalctl -u fips-gateway -f
sudo journalctl -u fips-firewall -f
# Switch to debug logging
sudo systemctl set-environment RUST_LOG=debug
+11 -2
View File
@@ -98,7 +98,7 @@ rm -rf "${STAGING_DIR}"
mkdir -p "${STAGING_DIR}"
# Copy binaries
for bin in fips fipsctl fipstop; do
for bin in fips fipsctl fipstop fips-gateway; do
if [[ ! -f "${BINARY_DIR}/${bin}" ]]; then
echo "Missing binary: ${BINARY_DIR}/${bin}" >&2
exit 1
@@ -111,15 +111,24 @@ if ! command -v "${STRIP_BIN}" &>/dev/null; then
echo "Strip tool not found: ${STRIP_BIN}" >&2
exit 1
fi
"${STRIP_BIN}" "${STAGING_DIR}/fips" "${STAGING_DIR}/fipsctl" "${STAGING_DIR}/fipstop"
"${STRIP_BIN}" \
"${STAGING_DIR}/fips" \
"${STAGING_DIR}/fipsctl" \
"${STAGING_DIR}/fipstop" \
"${STAGING_DIR}/fips-gateway"
# Copy packaging files
cp "${SCRIPT_DIR}/install.sh" "${STAGING_DIR}/"
cp "${SCRIPT_DIR}/uninstall.sh" "${STAGING_DIR}/"
cp "${SCRIPT_DIR}/fips.service" "${STAGING_DIR}/"
cp "${SCRIPT_DIR}/fips-dns.service" "${STAGING_DIR}/"
cp "${SCRIPT_DIR}/fips-gateway.service" "${STAGING_DIR}/"
cp "${SCRIPT_DIR}/fips-firewall.service" "${STAGING_DIR}/"
cp "${PACKAGING_DIR}/common/fips.yaml" "${STAGING_DIR}/"
cp "${PACKAGING_DIR}/common/hosts" "${STAGING_DIR}/"
cp "${PACKAGING_DIR}/common/fips.nft" "${STAGING_DIR}/"
cp "${PACKAGING_DIR}/common/fips-dns-setup" "${STAGING_DIR}/"
cp "${PACKAGING_DIR}/common/fips-dns-teardown" "${STAGING_DIR}/"
cp "${SCRIPT_DIR}/README.install.md" "${STAGING_DIR}/"
chmod +x "${STAGING_DIR}/install.sh" "${STAGING_DIR}/uninstall.sh"
+17
View File
@@ -0,0 +1,17 @@
# Firewall is not enabled by default. Enable with: systemctl enable --now fips-firewall
[Unit]
Description=FIPS mesh-interface nftables baseline
Documentation=file:///etc/fips/fips.nft
Before=fips.service
ConditionPathExists=/etc/fips/fips.nft
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nft -f /etc/fips/fips.nft
ExecStop=-/usr/sbin/nft delete table inet fips
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
+33
View File
@@ -0,0 +1,33 @@
# Gateway is not enabled by default. Enable with: systemctl enable --now fips-gateway
[Unit]
Description=FIPS Outbound LAN Gateway
After=fips.service
Requires=fips.service
[Service]
Type=simple
# Wait up to 30s for the fips daemon's TUN to come up before launching
# the gateway. fips-gateway hard-fails on a missing fips0 interface; with
# Requires=fips.service the daemon unit is started, but the TUN device
# itself is brought up some time after the daemon process starts. Without
# this wait the gateway's first ExecStart tends to lose a race on fresh
# boot, exit, and rely on Restart=on-failure for ~5s of recovery. Bounded
# wait avoids the noisy restart cycle while still surfacing real failures
# (if fips0 never appears, the wait expires and the gateway's existing
# error path runs).
ExecStartPre=/bin/sh -c 'for i in $(seq 1 30); do ip link show fips0 >/dev/null 2>&1 && exit 0; sleep 1; done; echo "fips0 did not appear within 30s" >&2; exit 1'
ExecStart=/usr/local/bin/fips-gateway --config /etc/fips/fips.yaml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
TimeoutStopSec=15
# Security hardening (needs CAP_NET_ADMIN for nftables and proxy NDP)
ProtectHome=yes
PrivateTmp=yes
ProtectKernelModules=yes
ProtectKernelTunables=no
[Install]
WantedBy=multi-user.target
+55 -3
View File
@@ -9,10 +9,15 @@
# /usr/local/bin/fips Daemon binary
# /usr/local/bin/fipsctl CLI query tool
# /usr/local/bin/fipstop TUI monitor
# /usr/local/bin/fips-gateway Outbound LAN gateway binary (opt-in)
# /etc/fips/fips.yaml Configuration (preserved if exists)
# /etc/fips/hosts Host-to-npub mappings (preserved if exists)
# /etc/systemd/system/fips.service systemd unit
# /etc/systemd/system/fips-dns.service DNS routing for .fips domain
# /etc/fips/fips.nft Mesh-interface nftables baseline (preserved if exists)
# /etc/fips/fips.d/ Operator drop-in directory for nft rules
# /etc/systemd/system/fips.service Daemon unit (enabled)
# /etc/systemd/system/fips-dns.service DNS routing for .fips domain (enabled)
# /etc/systemd/system/fips-gateway.service Gateway unit (NOT enabled; opt-in)
# /etc/systemd/system/fips-firewall.service Firewall baseline unit (NOT enabled; opt-in)
set -euo pipefail
@@ -60,6 +65,9 @@ install -m 0755 "${SCRIPT_DIR}/fipsctl" "${INSTALL_PREFIX}/bin/fipsctl"
if [ -f "${SCRIPT_DIR}/fipstop" ]; then
install -m 0755 "${SCRIPT_DIR}/fipstop" "${INSTALL_PREFIX}/bin/fipstop"
fi
if [ -f "${SCRIPT_DIR}/fips-gateway" ]; then
install -m 0755 "${SCRIPT_DIR}/fips-gateway" "${INSTALL_PREFIX}/bin/fips-gateway"
fi
# --- Install configuration ---
@@ -82,7 +90,27 @@ else
echo "Hosts file installed to ${HOSTS_FILE}"
fi
# --- Install systemd unit ---
# Mesh-interface nftables baseline. Preserved on upgrade like fips.yaml
# so operator edits aren't clobbered.
NFT_FILE="${CONFIG_DIR}/fips.nft"
if [ -f "${NFT_FILE}" ]; then
echo "Firewall baseline exists at ${NFT_FILE}, not overwriting."
install -m 0644 "${SCRIPT_DIR}/fips.nft" "${CONFIG_DIR}/fips.nft.template"
echo " New template installed as ${CONFIG_DIR}/fips.nft.template"
elif [ -f "${SCRIPT_DIR}/fips.nft" ]; then
install -m 0644 "${SCRIPT_DIR}/fips.nft" "${NFT_FILE}"
echo "Firewall baseline installed to ${NFT_FILE}"
fi
# Drop-in directory for operator nftables rules included by
# /etc/fips/fips.nft. Empty by default; the include glob matches
# nothing cleanly out of the box.
if [ ! -d "${CONFIG_DIR}/fips.d" ]; then
install -d -m 0755 "${CONFIG_DIR}/fips.d"
echo "Drop-in directory created at ${CONFIG_DIR}/fips.d/"
fi
# --- Install systemd units ---
was_active=false
if systemctl is-active --quiet fips.service 2>/dev/null; then
@@ -100,9 +128,23 @@ fi
install -m 0644 "${SCRIPT_DIR}/fips.service" "${SYSTEMD_DIR}/fips.service"
install -m 0644 "${SCRIPT_DIR}/fips-dns.service" "${SYSTEMD_DIR}/fips-dns.service"
if [ -f "${SCRIPT_DIR}/fips-gateway.service" ]; then
install -m 0644 "${SCRIPT_DIR}/fips-gateway.service" "${SYSTEMD_DIR}/fips-gateway.service"
fi
if [ -f "${SCRIPT_DIR}/fips-firewall.service" ]; then
install -m 0644 "${SCRIPT_DIR}/fips-firewall.service" "${SYSTEMD_DIR}/fips-firewall.service"
fi
# DNS helpers ship flat in the tarball alongside install.sh; from a
# source checkout they live under packaging/common/. Resolve from
# either layout.
install -d -m 0755 /usr/lib/fips
if [ -f "${SCRIPT_DIR}/fips-dns-setup" ]; then
install -m 0755 "${SCRIPT_DIR}/fips-dns-setup" /usr/lib/fips/fips-dns-setup
install -m 0755 "${SCRIPT_DIR}/fips-dns-teardown" /usr/lib/fips/fips-dns-teardown
else
install -m 0755 "${SCRIPT_DIR}/../common/fips-dns-setup" /usr/lib/fips/fips-dns-setup
install -m 0755 "${SCRIPT_DIR}/../common/fips-dns-teardown" /usr/lib/fips/fips-dns-teardown
fi
systemctl daemon-reload
echo "systemd units and DNS scripts installed."
@@ -149,6 +191,16 @@ echo ""
echo "Start the service:"
echo " sudo systemctl start fips"
echo ""
echo "Optional services (NOT enabled by default):"
echo ""
echo " Mesh-interface firewall baseline (default-deny on fips0):"
echo " sudo systemctl enable --now fips-firewall.service"
echo " Operator drop-ins under /etc/fips/fips.d/*.nft"
echo ""
echo " Outbound LAN gateway (bridge unmodified LAN hosts to .fips):"
echo " sudo systemctl enable --now fips-gateway.service"
echo " Configure under the gateway: section of ${CONFIG_FILE}"
echo ""
echo "Monitor:"
echo " sudo journalctl -u fips -f"
echo " fipsctl show status"
+13 -17
View File
@@ -18,30 +18,26 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
# --- Stop and disable service ---
# --- Stop and disable services ---
# Stop dependents (firewall, gateway, dns) before the daemon to avoid
# noisy "fips0 disappeared" cascades during the teardown.
if systemctl is-active --quiet fips-dns.service 2>/dev/null; then
echo "Stopping fips-dns service..."
systemctl stop fips-dns.service
for unit in fips-gateway.service fips-firewall.service fips-dns.service fips.service; do
if systemctl is-active --quiet "${unit}" 2>/dev/null; then
echo "Stopping ${unit}..."
systemctl stop "${unit}"
fi
if systemctl is-enabled --quiet fips-dns.service 2>/dev/null; then
systemctl disable fips-dns.service
fi
if systemctl is-active --quiet fips.service 2>/dev/null; then
echo "Stopping fips service..."
systemctl stop fips.service
fi
if systemctl is-enabled --quiet fips.service 2>/dev/null; then
systemctl disable fips.service
if systemctl is-enabled --quiet "${unit}" 2>/dev/null; then
systemctl disable "${unit}"
fi
done
# --- Remove systemd units ---
rm -f /etc/systemd/system/fips.service
rm -f /etc/systemd/system/fips-dns.service
rm -f /etc/systemd/system/fips-gateway.service
rm -f /etc/systemd/system/fips-firewall.service
rm -rf /usr/lib/fips/
systemctl daemon-reload
echo "systemd units and DNS scripts removed."
@@ -57,7 +53,7 @@ rm -f /etc/tmpfiles.d/fips.conf
# --- Remove binaries ---
rm -f /usr/local/bin/fips /usr/local/bin/fipsctl /usr/local/bin/fipstop
rm -f /usr/local/bin/fips /usr/local/bin/fipsctl /usr/local/bin/fipstop /usr/local/bin/fips-gateway
echo "Binaries removed."
# --- Optionally remove configuration and group ---