packaging/README.md and the pfSense builder's header said a firmware upgrade removes the package, and the fips-dns-setup comment said the same of everything under /usr/local. pfSense-upgrade reinstalls only pfSense-pkg-* packages, and a live Plus 26.03.1 to 26.07 upgrade kept this one, as the pfSense README, the post-install banner and pkg-descr already say. A major base change still calls for the package built for the new base.
20 KiB
FIPS Packaging
This directory contains packaging for all supported target platforms.
Most build outputs go to deploy/ at the project root; make ipk
and make apk write to dist/ instead.
Quick Start
make deb # Debian/Ubuntu .deb (built in the pinned container)
make rpm # Fedora/RHEL .rpm, named fips-mesh (built in the pinned container)
make tarball # systemd install tarball
make ipk # OpenWrt .ipk (opkg, OpenWrt 24.x and earlier)
make apk # OpenWrt .apk (apk-tools, mandatory on OpenWrt 25+)
make aur # Arch Linux AUR package (fips-git, local build + namcap)
make pkg # macOS .pkg installer
make freebsd # FreeBSD .pkg package (on FreeBSD; use gmake)
make pfsense # pfSense .pkg package (on FreeBSD; use gmake)
make zip # Windows .zip package
make all # deb + tarball (default)
The two Debian build paths
make deb builds in a container pinned to the oldest supported
Debian-family distribution, named with the glibc floor in
build-floor.env, and checks the package it produced
against that floor before handing it back. The floor itself is lower than that
image's glibc: RHEL 9 is the lowest supported distribution project-wide, and
both families ship these same binaries. Its only host prerequisite is
docker: the toolchain and the build dependencies live in the image. This
is the path the release workflow, the integration suite and the internal
builder all take, so a package that passes locally is built the way the
shipped one is.
make deb-host is the old path. It builds on the host, at whatever glibc
the host has, and it is checked against nothing. Use it for local
iteration only. A package built on a current distribution records a
version dependency that the loader refuses on Debian 12 and Ubuntu 22.04,
which is what shipped in every Linux artifact from v0.3.0 through v0.5.0,
so it must not produce anything anyone else installs.
Build Prerequisites
The prerequisites below apply to the host-build targets. make deb needs
docker and nothing else.
These targets build FIPS from source, so the host needs a build
environment in addition to a Rust toolchain (the version pinned in
rust-toolchain.toml is auto-installed by rustup).
On Linux, libclang is required: the LAN gateway's nftables
bindings are generated by bindgen at build time, which needs
libclang.so on the build host. Without it the build fails inside the
rustables crate with an "Unable to find libclang" error.
sudo apt install libclang-dev # Debian / Ubuntu
This is a build-time prerequisite only — it is not a runtime
dependency, so hosts installing a pre-built .deb do not need it.
BLE is not optional, and it is not universal either. build.rs sets
ble_available for glibc Linux or Android, which is the set of
platforms with a concrete backend: the transport is absent from musl
Linux, macOS, FreeBSD and Windows builds entirely. On glibc Linux
libdbus-1-dev and pkg-config are hard build prerequisites: there is
no probe that skips BLE when they are missing. The BlueZ daemon is a
runtime dependency and is not needed to build.
Directory Structure
packaging/
aur/ Arch Linux AUR packaging (PKGBUILD, supporting files)
common/ Shared assets (default config, hosts file) and pkg-lib.sh,
the helpers the FreeBSD and pfSense builders share
debian/ Debian/Ubuntu .deb packaging via cargo-deb
rpm/ Fedora/RHEL .rpm packaging via rpmbuild, over the binaries
the Debian container build produces
freebsd/ FreeBSD .pkg packaging via pkg-create(8)
pfsense/ pfSense .pkg packaging (FreeBSD-based, but not the same)
macos/ macOS .pkg installer via pkgbuild
nixos/ NixOS flake module (services.fips.*)
systemd/ Generic Linux systemd tarball packaging
openwrt-ipk/ OpenWrt .ipk packaging via cargo-zigbuild (opkg)
openwrt-apk/ OpenWrt .apk packaging via cargo-zigbuild + apk mkpkg
windows/ Windows .zip package with service scripts
Formats
Debian/Ubuntu (.deb)
Built with cargo-deb. Installs
fips, fipsctl, fipstop, and fips-gateway to /usr/bin/, ships
the fips, fips-dns, fips-firewall, and fips-gateway systemd
units, and enables the fips and fips-dns services.
The default configuration ships as an example at
/usr/share/fips/fips.yaml.example and is not a dpkg conf-file.
(It is deliberately not under /usr/share/doc, which minimal and
container installs path-exclude, since the postinst reads it at install
time.)
On install, postinst seeds /etc/fips/fips.yaml (mode 600) from the
example only if it does not already exist, so a configuration that
was rendered by configuration management or edited by an operator is
never prompted for or clobbered on upgrade. To reset to defaults, remove
/etc/fips/fips.yaml and reinstall, or copy the example back manually.
# Build
make deb
# Install
sudo apt install ./deploy/fips_<version>_<arch>.deb
# Remove (preserves config and keys)
sudo dpkg -r fips
# Purge (removes config and identity keys)
sudo dpkg -P fips
RPM (.rpm)
Built with rpmbuild from rpm/fips.spec. The same files land
in the same places as the .deb, the same fips system group is created, the
same /etc/fips/fips.yaml seeding happens, and the same two units are enabled;
fips-firewall and fips-gateway stay opt-in.
The package is named fips-mesh, not fips. Fedora's namespace already
has a fips — an unrelated OpenGL FITS image viewer, currently 3.4.0 — which
owns /usr/bin/fips. Ours at 0.6.0 would be an older fips to every RPM
tool, so a routine dnf upgrade replaces a running mesh node with an image
viewer and takes the units with it; that is not hypothetical, it happened
within the hour on a test machine. The two cannot coexist either, since both
ship /usr/bin/fips, so the spec declares Conflicts: fips and dnf refuses
with both names on screen instead of a bare path.
Like the Debian package, it has two build paths, and for the same reason.
make rpm compiles nothing on the host: it builds the binaries in the image
declared in build-floor.env, packages those, and checks the
glibc requirement of the finished package against the declared floor. So the
RPM carries the same objects as the .deb and the tarball, and a package built
above the floor fails there rather than at a user's dnf install. rpmbuild
runs in FIPS_RPM_BUILD_IMAGE (AlmaLinux 9, pinned by digest), which supplies
two things a build host may lack: rpmbuild itself, and systemd-rpm-macros,
without which the spec's %systemd_post would not expand and the package would
ship scriptlets that quietly do nothing. Docker is the only host prerequisite.
make rpm-host packages whatever the host toolchain built. Like deb-host it
is for local iteration and not for anything anyone else installs; nothing
checks its floor.
The release workflow calls the same container script both matrix legs, with
--no-build, over the binaries it has already recovered from the .deb — one
build, three artifacts — and attaches the result to the GitHub Release next to
the .deb and the tarball.
# Build (requires docker)
make rpm
# Install
sudo dnf install ./deploy/fips-mesh-<version>-<release>.<arch>.rpm
# Remove (keeps /etc/fips, including identity keys)
sudo dnf remove fips-mesh
Two firewalls, on the distributions where firewalld owns nftables. They do not
conflict — firewalld manages its own tables and fips-firewall.service adds
table inet fips, which returns immediately for anything not arriving on
fips0 — but firewalld is filtering the node whether or not that unit ever
runs, and in two places worth knowing:
-
Inbound peers arrive on your ordinary interface, on the transport ports (
2121/udpand8443/tcpin the shipped config), and those are in whatever zone that interface belongs to. Fedora Workstation's default zone opens1025-65535for both protocols, so it works there untouched; RHEL, CentOS Stream and Fedora Server default topublic, which allowsssh,dhcpv6-clientandmdnsand nothing else, so a node there accepts no inbound peers until the ports are opened:sudo firewall-cmd --permanent --add-port=2121/udp --add-port=8443/tcp sudo firewall-cmd --reload -
fips0itself lands in the default zone, since nothing assigns it one —firewall-cmd --get-zone-of-interface=fips0saysno zone, which means the default. Mesh traffic to local services is then subject to that zone as well as to the fips baseline. Giving the interface its own zone keeps the two decisions apart, andtrustedleaves the filtering to/etc/fips/fips.nftand its drop-ins, which is where it is meant to be:sudo firewall-cmd --permanent --zone=trusted --change-interface=fips0 sudo firewall-cmd --reload
Either way the fips table stays invisible to firewalld: firewall-cmd --list-all will not show it, and opening a port with firewall-cmd does not
open it in the fips table. That is what /etc/fips/fips.d/ is for.
Note also that a default RHEL, CentOS Stream or AlmaLinux install has no
resolver backend fips-dns-setup can use: systemd is older than the
dns-delegate drop-in, systemd-resolved is installed but not enabled, and
dnsmasq is not installed. The script falls through to its last branch and
prints manual instructions, so .fips names do not resolve until a backend is
in place. Fedora, which enables systemd-resolved, is configured automatically.
Three things differ from the Debian package, because the package managers do:
- The floor is checked on the package, not against it.
cargo-debwrites a dependency floor that can disagree with the binaries, sotesting/check-deb-depends.shcompares the two. rpm derives the requirement from the ELF files and cannot disagree with them, which moves the risk one step back — to binaries built above the floor in the first place.testing/check-rpm-floor.shreadslibc.so.6(GLIBC_x.y)out of the finished package, the same tablednfenforces at install time, and fails the build above the floor. - No purge. dpkg distinguishes remove from purge, and
postrm purgedeletes/etc/fipsand thefipsgroup. rpm has no such distinction, so the equivalent would run on an ordinary erase — and during a distribution upgrade that erases and reinstalls — taking the node's identity keys with it. Configuration and keys therefore survivednf remove; delete/etc/fipsyourself if you mean it. - Version vs Release. A dev build is
0.6.0-0.dev.git<date>.<sha>rather than the.deb's0.6.0~dev+git<date>.<sha>-1. rpm has understood~since 4.10, so this is a choice rather than a limitation: a Release beginning with0.is the convention for pre-release packages in this ecosystem, and it sorts below the1a tagged release carries. The Release carries no%{dist}tag either: there is one build, the glibc one, and a dist tag would name whichever image happened to run rpmbuild in an artifact that installs on all of them.
No install-test suite covers the RPM. The deb-install suite exercises the
.deb across five distributions on every push; the RPM is built on every push
and installed by nobody but you.
systemd Tarball
A self-contained tarball with binaries and an install.sh script for
any systemd-based Linux distribution.
# Build
make tarball
# Install (on target host)
tar -xzf deploy/fips-<version>-linux-<arch>.tar.gz
sudo ./fips-<version>-linux-<arch>/install.sh
See systemd/README.install.md for full installation and configuration instructions.
OpenWrt (.ipk, opkg — OpenWrt 24.x and earlier)
Cross-compiled with cargo-zigbuild and assembled as a standard .ipk
archive. The build script accepts aarch64, mipsel, mips, arm and
x86_64; releases publish aarch64 and x86_64. The MIPS targets are
not built: 32-bit MIPS has no 64-bit atomics, which fips and
nostr-relay-pool both use (see the comment in
.github/workflows/package-openwrt.yml).
# Build (default: aarch64)
make ipk
# Build for a specific architecture
bash packaging/openwrt-ipk/build-ipk.sh --arch x86_64
See openwrt-ipk/README.md for router-specific installation instructions.
OpenWrt (.apk, apk-tools — mandatory on OpenWrt 25+)
OpenWrt 25 makes apk-tools the mandatory package manager (it is opt-in on
24.10). Same SDK-free approach
(cargo-zigbuild), but the .apk container is assembled by apk mkpkg
rather than hand-rolled, so the build additionally needs an apk-tools v3
apk binary built from source. The installed-filesystem payload is shared
with the .ipk package.
# Build (default: aarch64; also x86_64)
make apk
# Build for a specific architecture
bash packaging/openwrt-apk/build-apk.sh --arch x86_64
Packages are unsigned; install with apk add --allow-untrusted. See
openwrt-apk/README.md for building apk-tools and
router-specific installation.
macOS (.pkg)
Built with pkgbuild (included with Xcode command-line tools). Installs
binaries to /usr/local/bin/, config to /usr/local/etc/fips/, sets up
the /etc/resolver/fips DNS resolver for .fips domains, and loads a
launchd daemon. The TUN device is named utun<N> (kernel-assigned)
rather than fips0.
# Build
make pkg
# Install
sudo installer -pkg deploy/fips-<version>-macos-<arch>.pkg -target /
# Remove
sudo packaging/macos/uninstall.sh
FreeBSD (.pkg)
Built natively on a FreeBSD host with pkg create. Ships fips,
fipsctl, and fipstop (fips-gateway is excluded — its NAT backend
is nftables, Linux-only), rc.d services, and .fips DNS integration
for local_unbound, unbound, or dnsmasq. Config installs
sample-style under /usr/local/etc/fips/ (edits survive upgrades).
# Build (on FreeBSD; this Makefile needs GNU make — pkg install gmake)
gmake freebsd
# or directly, no gmake needed:
./packaging/freebsd/build-pkg.sh
# Install
pkg add ./deploy/fips-<version>-freebsd-<arch>.pkg
sysrc fips_enable=YES fips_dns_enable=YES
service fips start
service fips_dns start
See freebsd/README.md for host resolver setup and field-tested caveats.
pfSense (.pkg)
pfSense is FreeBSD underneath, but the FreeBSD package does not work
there, and fails silently in three ways: pfSense boots packages by
globbing /usr/local/etc/rc.d/*.sh (a suffixless rc script is never
run), it generates unbound.conf from config.xml and reads no
conf.d directory (the DNS drop-in is never read), and it writes
do-ip6: no unless "Allow IPv6" is enabled (so a responder on [::1]
is unreachable). This package ships fips.sh, integrates DNS through
the DNS Resolver custom options in config.xml, and binds the
responder on 127.0.0.1.
Unlike the other packages, this one links statically by default
(--dynamic opts out). pfSense runs a FreeBSD base you cannot
obtain — Netgate builds Plus from its own 16.0-CURRENT snapshot — so
a dynamically linked binary can reference a libc symbol the appliance
does not export, install cleanly, and then refuse to start. A static
package declares no shared libraries at all.
On aarch64 this is refused, not applied. A statically linked
aarch64 FreeBSD binary faults where posix_spawn should be, so the
daemon dies the first time it shells out. ARM builds must pass
--dynamic, and then ldd on the appliance is the check that the
base drift is not real.
The build host's architecture must match the target's, and pkg
refuses a mismatched ABI major, so the package carries the target's:
pfSense CE 2.8.1 is FreeBSD 15 amd64; CE 2.9.0 and Plus 26.x are
FreeBSD 16 (amd64, plus aarch64 for Plus on ARM appliances). The
FreeBSD 16 amd64 package is the FreeBSD 15.1 build relabelled
(--no-build --abi FreeBSD:16:amd64): static binaries from an older
release on a newer kernel is the direction FreeBSD supports, and it has
been run on Plus 26.03.1 and 26.07. No aarch64 package is published:
rustup ships no toolchain for aarch64 FreeBSD, so such a build cannot
honour the rust-toolchain.toml pin. It is build-it-yourself.
# Build (on FreeBSD; this Makefile needs GNU make — pkg install gmake)
gmake pfsense
# or directly, no gmake needed:
./packaging/pfsense/build-pkg.sh
# Validate the package before shipping it
./testing/check-pfsense-pkg.sh deploy/fips-<version>-pfsense-ce2.8-amd64.pkg
# Install (on the firewall, as root)
pkg add ./fips-<version>-pfsense-ce2.8-amd64.pkg
/usr/local/etc/rc.d/fips.sh start
/usr/local/libexec/fips/fips-dns-setup # edits config.xml; run deliberately
Not a Netgate-supported package. A pfSense firmware upgrade keeps it (it
is a plain pkg, not a pfSense-pkg-*); after a major base change,
reinstall the package built for the new base. See
pfsense/README.md for the "Allow IPv6" prerequisite
the mesh depends on, firewall-rule notes, and upgrade and removal
behaviour.
Windows (.zip)
A ZIP archive containing binaries, default config, and PowerShell service helper scripts. Requires the wintun driver for TUN support.
# Build
make zip
# Or directly
powershell -File packaging/windows/build-zip.ps1
# Extract and install as service (requires Administrator)
Expand-Archive deploy\fips-<version>-windows-x86_64.zip -DestinationPath fips
cd fips
powershell -ExecutionPolicy Bypass -File install-service.ps1
# Uninstall (preserves config)
powershell -ExecutionPolicy Bypass -File uninstall-service.ps1
# Uninstall and remove config
powershell -ExecutionPolicy Bypass -File uninstall-service.ps1 -RemoveAll
Arch Linux (AUR)
Two AUR packages are maintained: fips (release, builds from tagged
tarball) and fips-git (development, builds from latest git master).
# Build and validate locally (git variant)
make aur
# Install from AUR
yay -S fips-git # development build from master
yay -S fips # release build from latest tag
See aur/README.md for AUR publication instructions and maintainer guide.
Nix / NixOS (flake)
A flake at the project root builds all four binaries
(fips, fipsctl, fips-gateway, fipstop) from source. It pins the
exact toolchain from rust-toolchain.toml via
fenix and wires up the
build-time native dependencies (libclang for bindgen, plus dbus
and pkg-config for BLE), so it needs no system setup beyond Nix with
flakes enabled.
nix build .#fips # build the package (all four binaries)
nix run .#fips -- --help # run a binary directly
nix run .#fipsctl -- show status
nix develop # dev shell with the pinned toolchain + cargo-edit
nix flake check # build + validate the flake
The flake also exposes:
overlays.default— addspkgs.fipsto nixpkgsnixosModules.default— a NixOS module (packaging/nixos/) that providesservices.fips.enableand runs the daemon as a systemd service
As a package only (no service management):
environment.systemPackages = [ fips.packages.${system}.default ];
As a managed NixOS service (recommended — starts on boot, journalctl logs):
# flake.nix
{
inputs.fips = {
url = "github:jmcorgan/fips";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, fips, ... }@inputs: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
fips.nixosModules.default
{ services.fips.enable = true; }
];
};
};
}
See packaging/nixos/README.md for the full option
reference (services.fips.enable, .package, .configFile,
.openFirewall).
Shared Assets
common/ contains assets used across packaging formats:
fips.yaml— default configuration (ephemeral identity, UDP/TCP/TUN/DNS)hosts— static hostname-to-npub mappings for.fipsDNS resolution