Files
fips/packaging/README.md
T
Johnathan Corgan 867f5f81b4 build: produce every Linux artifact in a pinned container and check its floor
The released .deb installs cleanly on Debian 12 and Ubuntu 22.04 and the
daemon then cannot start, with the loader reporting GLIBC_2.39 not found.
Three binaries are affected, fips, fipstop and fips-gateway; fipsctl runs,
which is why it stayed quiet, since an install checked by running fipsctl
gets a clean answer while the daemon is dead. It is not a v0.5.0 regression:
every release artifact from v0.3.0 onward carries the same floor and the same
unversioned dependency.

The cause is the build machine. Rust's standard library references
pidfd_spawnp and pidfd_getpid as weak undefined symbols behind a runtime
check, so a binary should fall back where the C library lacks them. Linking
against a C library that has them records a hard version dependency instead,
and the loader refuses the image on that entry alone. No Rust changed here.

One script now produces the Linux artifacts. It builds in a container pinned
to the oldest distribution still supported for free by its distributor, named
with the floor in packaging/build-floor.env, and runs the floor check on the
package it produced, so every producer is gated rather than one workflow. The
floor guard reads readelf's Version needs section: the obvious objdump
formulation returns 2.2.5 for the shipped fips and would have passed every
affected release.

The script prints the package path as the only thing on its stdout, which is
what lets a caller take it without parsing, and it takes --features. Both
required care. The container's own stdout reaches the caller, so the build
runs with its output on stderr; without that, a caller using a plain command
substitution captures four lines of build chatter along with the path. And a
feature build must keep the +<features> marker that distinguishes it from the
default build of the same commit, or dpkg sees two packages at one version and
a revert silently no-ops. The version is derived on the host, because the
image has no git and the source is mounted read-only, so build-deb.sh now
applies that marker to an explicit version as well as to one it derives.

Both runners now build once through that script and install the artifact.
The five deb-install legs previously built their own package each, so one CI
run performed five complete release builds and four were waste; they now live
in a job of their own that downloads one built package, which also stops the
rest of the integration matrix waiting on it. The parity guard read one
hardcoded job and now sweeps every job's matrix. The release workflow builds
both architectures through the same script, and the systemd tarball takes its
binaries out of that package instead of from a second, unchecked set on the
runner, then is floor-checked after the strip.

Cargo.toml derives the dependency with $auto rather than stating a bare libc6
that nothing can fail. Note the ordering this implies for any pipeline that
builds on a current distribution: until it builds through this script, its
packages will declare libc6 (>= 2.39).

Measured: the container build produces four binaries at 2.34, and one artifact
passes all five distributions, 95 checks, in about two minutes. The floor
check fails the released 0.5.0 package on three binaries and passes this one.
A profiling build produces fips_0.5.1~dev+git<date>.<sha>+profiling-1_amd64.deb.
2026-09-05 23:34:05 +00:00

321 lines
10 KiB
Markdown

# 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
```sh
make deb # Debian/Ubuntu .deb (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 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
distribution, named with the glibc floor in
[build-floor.env](build-floor.env), and checks the package it produced
against that floor before handing it back. 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.
```sh
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
```text
packaging/
aur/ Arch Linux AUR packaging (PKGBUILD, supporting files)
common/ Shared assets (default config, hosts file)
debian/ Debian/Ubuntu .deb packaging via cargo-deb
freebsd/ FreeBSD .pkg packaging via pkg-create(8)
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](https://github.com/kornelski/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.
```sh
# Build
make deb
# Install
sudo dpkg -i deploy/fips_<version>_<arch>.deb
# Remove (preserves config and keys)
sudo dpkg -r fips
# Purge (removes config and identity keys)
sudo dpkg -P fips
```
### systemd Tarball
A self-contained tarball with binaries and an `install.sh` script for
any systemd-based Linux distribution.
```sh
# 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](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. Supports aarch64, mipsel, mips, arm, and x86\_64 targets.
```sh
# Build (default: aarch64)
make ipk
# Build for a specific architecture
bash packaging/openwrt-ipk/build-ipk.sh --arch mipsel
```
See [openwrt-ipk/README.md](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.
```sh
# 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](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`.
```sh
# 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).
```sh
# 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](freebsd/README.md) for host resolver setup and
field-tested caveats.
### Windows (`.zip`)
A ZIP archive containing binaries, default config, and PowerShell
service helper scripts. Requires the [wintun](https://www.wintun.net/)
driver for TUN support.
```powershell
# 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 -File install-service.ps1
# Uninstall (preserves config)
powershell -File uninstall-service.ps1
# Uninstall and remove config
powershell -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).
```sh
# 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](aur/README.md) for AUR publication instructions
and maintainer guide.
### Nix / NixOS (flake)
A [flake](../flake.nix) 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](https://github.com/nix-community/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.
```sh
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` — adds `pkgs.fips` to nixpkgs
- `nixosModules.default` — a NixOS module (`packaging/nixos/`) that provides
`services.fips.enable` and runs the daemon as a systemd service
**As a package only** (no service management):
```nix
environment.systemPackages = [ fips.packages.${system}.default ];
```
**As a managed NixOS service** (recommended — starts on boot, journalctl logs):
```nix
# 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`](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 `.fips` DNS resolution