From 429d77731b517a9bad7601334e1782e27f86f9d8 Mon Sep 17 00:00:00 2001 From: fr34aky <162515565+fr34aky@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:15:47 +0000 Subject: [PATCH] add a pfSense package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pfSense is FreeBSD underneath, but the FreeBSD package does not work there, failing in three silent ways. pfSense runs only /usr/local/etc/rc.d/*.sh at boot and re-runs them when WAN gets a new address, so a suffixless rc script never starts; unbound.conf is generated from config.xml with no conf.d, so a drop-in is never read; and on a firewall where the default-on "Allow IPv6" has been turned off, unbound is then generated with do-ip6: no and a responder on ::1 is unreachable. So this ships fips.sh, wires the fips. zone into the DNS Resolver through config.xml, and binds the responder on 127.0.0.1 for robustness against that last case. The rc script is plain sh: what pfSense imposes is the .sh name and that a re-run leave a running daemon alone and exit 0. It identifies the daemon by process name and recovers an orphaned daemon(8) supervisor found via fstat, since a locked empty pidfile makes daemon(8) report pid -1. The DNS setup is a manual step, never run from post-install, and validates the merged options with unbound-checkconf (pfSense's test_unbound_config) before touching config.xml, so a bad merge cannot take DNS from every client behind the firewall. The daemon runs under daemon(8) -H so newsyslog can rotate its log by signalling a reopen. Packages link statically by default: pfSense runs a FreeBSD base that cannot be obtained to link against. A firmware upgrade keeps the package (pfSense-upgrade removes only pfSense-pkg-*; confirmed on a live Plus 26.03.1 -> 26.07 upgrade, aarch64 — the package survived and the daemon restarted at boot. That is a minor, FreeBSD 16 -> 16 change; the cross-major compat case is still only source-reasoned). aarch64 is refused, where a static binary faults at posix_spawn. The mechanics the two builders share — version derivation, the stage layout, the manifest fields, the @sample scripts and pkg create — live in packaging/common/pkg-lib.sh, which both source; the FreeBSD package is byte-identical before and after that extraction. One ABI can serve more than one product: CE 2.9 and Plus 26.x on Intel are both FreeBSD:16:amd64 with a byte-identical artifact, named ...-ce2.9-plus26-amd64.pkg. The pfSense package is built and checked in its own CI job — separate from the FreeBSD package, and not a dependency of the release job, so a pfSense-only failure reds that job alone and is never a release asset. It is kept as a workflow artifact until it has been installed on a real pfSense box. CI produces the CE 2.8.1 (FreeBSD:15:amd64) package; CE 2.9, Plus 26.x Intel and ARM need a FreeBSD 16 build host the CI does not have, and ARM stays build-it-yourself because rustup ships no toolchain for it. testing/check-pfsense-pkg.sh validates a built package on any FreeBSD host and runs in that CI job: contents, modes, a positive boot-script lifecycle against a stub daemon, php -l and a fips_strip_block unit test of the config.xml helper. Installing on a real pfSense box, and the firmware-upgrade behaviour, are covered only by an aarch64 hardware run and pfSense-upgrade's source; the README records what is and is not tested. Co-authored-by: Johnathan Corgan --- .github/workflows/package-freebsd.yml | 107 ++++ CHANGELOG.md | 25 + README.md | 3 +- docs/getting-started.md | 6 + packaging/Makefile | 9 +- packaging/README.md | 57 ++- packaging/common/pkg-lib.sh | 174 +++++++ packaging/freebsd/README.md | 5 + packaging/freebsd/build-pkg.sh | 73 +-- packaging/pfsense/README.md | 525 ++++++++++++++++++++ packaging/pfsense/build-pkg.sh | 572 ++++++++++++++++++++++ packaging/pfsense/fips-dns-setup | 223 +++++++++ packaging/pfsense/fips-dns-teardown | 29 ++ packaging/pfsense/fips-unbound-custom.php | 226 +++++++++ packaging/pfsense/fips.conf | 22 + packaging/pfsense/fips.newsyslog | 15 + packaging/pfsense/fips.sh | 302 ++++++++++++ packaging/pfsense/fips.yaml.dns | 19 + packaging/pfsense/pkg-descr | 17 + testing/check-pfsense-pkg.sh | 571 +++++++++++++++++++++ 20 files changed, 2921 insertions(+), 59 deletions(-) create mode 100644 packaging/common/pkg-lib.sh create mode 100644 packaging/pfsense/README.md create mode 100755 packaging/pfsense/build-pkg.sh create mode 100755 packaging/pfsense/fips-dns-setup create mode 100755 packaging/pfsense/fips-dns-teardown create mode 100644 packaging/pfsense/fips-unbound-custom.php create mode 100644 packaging/pfsense/fips.conf create mode 100644 packaging/pfsense/fips.newsyslog create mode 100755 packaging/pfsense/fips.sh create mode 100644 packaging/pfsense/fips.yaml.dns create mode 100644 packaging/pfsense/pkg-descr create mode 100755 testing/check-pfsense-pkg.sh diff --git a/.github/workflows/package-freebsd.yml b/.github/workflows/package-freebsd.yml index cec6276c..4541657c 100644 --- a/.github/workflows/package-freebsd.yml +++ b/.github/workflows/package-freebsd.yml @@ -227,6 +227,99 @@ jobs: echo "Build Summary for freebsd/x86_64:" echo " Package: ${{ steps.freebsd-assets.outputs.pkg }}" + pfsense: + name: Build and check the pfSense package (x86_64) + # A job of its own, and deliberately NOT a dependency of `release`. A + # pfSense-only failure — a new key in the common dns: block, a + # dependency that stops linking statically, a checker regression — reds + # this check and nothing else; it can never hide behind the FreeBSD + # job's result, and it cannot block the FreeBSD release asset. The + # pfSense package is therefore never a release asset. It is published + # here as a workflow artifact (30-day retention) for anyone to test, + # until someone has installed it on a real pfSense box; see + # packaging/pfsense/README.md. + # + # This VM is FreeBSD 15.1, so the package it produces is FreeBSD:15:amd64 + # (pfSense CE 2.8.1). CE 2.9 and Plus 26.x are FreeBSD 16 and need a + # FreeBSD 16 host this workflow does not have. No aarch64 package is built + # here or published anywhere: rustup ships no toolchain for + # aarch64-unknown-freebsd, so such a build cannot honour the + # rust-toolchain.toml pin every published artifact is built with. ARM is + # build-it-yourself, per the README. + runs-on: ubuntu-latest + needs: determine-versioning + # Its own full release build in an emulated FreeBSD VM, like the build + # job; the same generous bound applies. + timeout-minutes: 45 + + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + + - name: Set SOURCE_DATE_EPOCH from git + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV" + + - name: Build and check the pfSense package in a FreeBSD VM + uses: vmactions/freebsd-vm@f0552d3b69211736abd97f02ff3d4674c56b73b1 # v1 + env: + FREEBSD_PACKAGE_VERSION: ${{ needs.determine-versioning.outputs.freebsd_package_version }} + with: + release: "15.1" + usesh: true + sync: rsync + copyback: true + mem: 6144 + envs: "SOURCE_DATE_EPOCH CARGO_TERM_COLOR FREEBSD_PACKAGE_VERSION" + prepare: | + # curl for rustup; bash and php for testing/check-pfsense-pkg.sh + # (the checker is bash, and it runs php -l plus a fips_strip_block + # unit test on the config.xml helper). + pkg install -y curl bash php85 + run: | + set -e + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none --profile minimal + . "$HOME/.cargo/env" + + # Builds the release binaries (static by default) and packages + # them; on a FreeBSD 15.1 VM this yields the FreeBSD:15:amd64 + # package for pfSense CE 2.8.1. + packaging/pfsense/build-pkg.sh --version "$FREEBSD_PACKAGE_VERSION" + + PKG=$(ls deploy/fips-*-pfsense-*.pkg) + testing/check-pfsense-pkg.sh "$PKG" + php -l packaging/pfsense/fips-unbound-custom.php + + ( cd deploy && sha256 -q "$(basename "$PKG")" \ + | { read -r h; printf '%s %s\n' "$h" "$(basename "$PKG")"; } \ + > "$(basename "$PKG").sha256" ) + + rm -rf target + + - name: Resolve pfSense asset path + id: pfsense-asset + shell: bash + run: | + : ${GITHUB_OUTPUT:=/tmp/github_output} + set -euo pipefail + VER="${{ needs.determine-versioning.outputs.freebsd_pkg_file_version }}" + PKG=$(ls deploy/fips-${VER}-pfsense-*.pkg) + if [[ ! -f "$PKG" ]]; then + echo "No pfSense package was produced" >&2 + ls -la deploy >&2 || true + exit 1 + fi + echo "pkg=$PKG" >> "$GITHUB_OUTPUT" + + - name: Upload pfSense artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: fips_${{ needs.determine-versioning.outputs.freebsd_package_version }}_x86_64_pfsense + path: | + ${{ steps.pfsense-asset.outputs.pkg }} + ${{ steps.pfsense-asset.outputs.pkg }}.sha256 + retention-days: 30 + release: name: Publish FreeBSD assets to GitHub Release runs-on: ubuntu-latest @@ -239,6 +332,11 @@ jobs: - name: Download FreeBSD artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: + # Only the FreeBSD artifact. Without a pattern this action downloads + # every artifact in the run — including the pfSense package from the + # `pfsense` job, which is a workflow artifact by design and must never + # reach a release. `needs` only orders jobs; it does not scope this. + pattern: fips_*_x86_64_freebsd path: dist merge-multiple: true @@ -248,6 +346,15 @@ jobs: set -euo pipefail cd dist + # The pfSense package is never a release asset (see the `pfsense` + # job). The download step is scoped to the FreeBSD artifact; this + # keeps the rule if that artifact is ever renamed or a new one added. + if compgen -G '*-pfsense-*' >/dev/null; then + echo "FAIL: a pfSense package reached the release stage; it must stay a workflow artifact:" >&2 + ls -la ./*-pfsense-* >&2 || true + exit 1 + fi + pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort) if [[ -z "$pkgs" ]]; then echo "FAIL: no .pkg artifacts were downloaded" >&2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5811e50d..c216473f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -137,6 +137,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 a typo'd interface name were previously the same flat `StartFailed(String)`; nothing downstream could branch on absence. +#### Packaging + +- A pfSense package (`packaging/pfsense/`, `gmake pfsense`). pfSense is + FreeBSD underneath, but the FreeBSD package fails there in three + silent ways: pfSense runs only `/usr/local/etc/rc.d/*.sh` at boot and + re-runs them on WAN IP changes, so the suffixless rc script never + starts; `unbound.conf` is generated from `config.xml` with no `conf.d`, + so the DNS drop-in is never read; and — on a firewall where the + default-on "Allow IPv6" has been turned off — the responder's `::1` + default is unreachable when unbound is then generated with + `do-ip6: no`, so it binds `127.0.0.1` for robustness. The package + ships `fips.sh`, wires the `fips.` zone into the DNS Resolver through + `config.xml`, and binds the responder on `127.0.0.1`. It links + statically by default, since pfSense runs a FreeBSD base that cannot + be obtained to link against; aarch64 is refused, where static binaries + fault at `posix_spawn`. Mechanics shared with the FreeBSD builder live + in `packaging/common/pkg-lib.sh`, which both source; the FreeBSD + package is byte-identical before and after. The pfSense package is + built and checked in its own CI job and published as a workflow + artifact, not attached to a release, until it has been installed on a + real pfSense box; CI produces the CE 2.8.1 (`FreeBSD:15:amd64`) + package, while CE 2.9 and Plus 26.x on Intel need a FreeBSD 16 build + host the CI does not have, and ARM stays build-it-yourself because + rustup ships no toolchain for it. See `packaging/pfsense/README.md`. + ### Changed - `Degraded` is now a level rather than a latch. The supervisor's reason set diff --git a/README.md b/README.md index ff3e0ba4..4d76056d 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,8 @@ default `/etc/fips/fips.yaml` you can edit before starting. The package enables `fips` and `fips-dns` but starts neither, which is why the second command is there. -For macOS, Windows, FreeBSD, OpenWrt, the systemd tarball or a Nix +For macOS, Windows, FreeBSD (including a pfSense build under +`packaging/pfsense/`), OpenWrt, the systemd tarball or a Nix flake, see [docs/getting-started.md](docs/getting-started.md) for the full multi-platform installation guide. diff --git a/docs/getting-started.md b/docs/getting-started.md index ee3280f9..879754c1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -88,6 +88,12 @@ included: its NAT backend is nftables, which is Linux-only. The Ethernet and BLE transports are unavailable on FreeBSD; UDP, TCP, Tor, and Nym are. +On **pfSense** (CE or Plus) use `packaging/pfsense/` rather than this +package: pfSense diverges from stock FreeBSD in how it boots packages, +generates the DNS resolver config, and applies upgrades, and the pfSense +package handles each. See +[packaging/pfsense/README.md](../packaging/pfsense/README.md). + **One architecture.** The published artifact is `fips--freebsd-amd64.pkg`. There is no aarch64 FreeBSD build, so on any other architecture use the from-source path below. diff --git a/packaging/Makefile b/packaging/Makefile index 69bb106f..b0cfd75a 100644 --- a/packaging/Makefile +++ b/packaging/Makefile @@ -12,6 +12,7 @@ # make aur Build fips-git AUR package and validate with namcap # make pkg Build a macOS .pkg installer # make freebsd Build a FreeBSD .pkg package (on FreeBSD; use gmake) +# make pfsense Build a pfSense .pkg package (on FreeBSD; use gmake) # make zip Build a Windows .zip package # make all Build deb and tarball (default) # make clean Remove deploy/ directory @@ -20,7 +21,7 @@ SHELL := /bin/bash PACKAGING_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) PROJECT_ROOT := $(abspath $(PACKAGING_DIR)/..) -.PHONY: all deb deb-host tarball ipk apk aur pkg freebsd zip clean +.PHONY: all deb deb-host tarball ipk apk aur pkg freebsd pfsense zip clean all: deb tarball @@ -57,6 +58,12 @@ pkg: freebsd: @sh $(PACKAGING_DIR)/freebsd/build-pkg.sh +# pfSense is FreeBSD underneath but boots, resolves and is upgraded +# differently enough that the FreeBSD package does not work there; see +# packaging/pfsense/README.md for the three divergences. +pfsense: + @sh $(PACKAGING_DIR)/pfsense/build-pkg.sh + zip: @powershell -File $(PACKAGING_DIR)/windows/build-zip.ps1 diff --git a/packaging/README.md b/packaging/README.md index 3859942e..87f4f675 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -14,6 +14,7 @@ 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) ``` @@ -70,9 +71,11 @@ runtime dependency and is not needed to build. ```text packaging/ aur/ Arch Linux AUR packaging (PKGBUILD, supporting files) - common/ Shared assets (default config, hosts file) + 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 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 @@ -212,6 +215,58 @@ service fips_dns start See [freebsd/README.md](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 and FreeBSD major must still match 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), and `pkg` refuses a +mismatched ABI. 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. + +```sh +# 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--pfsense-ce2.8-amd64.pkg + +# Install (on the firewall, as root) +pkg add ./fips--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, and a pfSense firmware upgrade removes +it. See [pfsense/README.md](pfsense/README.md) for the "Allow IPv6" +prerequisite the mesh depends on, firewall-rule notes, and removal +behaviour. + ### Windows (`.zip`) A ZIP archive containing binaries, default config, and PowerShell diff --git a/packaging/common/pkg-lib.sh b/packaging/common/pkg-lib.sh new file mode 100644 index 00000000..fe674cef --- /dev/null +++ b/packaging/common/pkg-lib.sh @@ -0,0 +1,174 @@ +#!/bin/sh +# Shared helpers for the FreeBSD-family package builders. +# +# packaging/freebsd/build-pkg.sh and packaging/pfsense/build-pkg.sh +# produce different packages for different systems, but the mechanics of +# getting there — deriving a pkg-legal version, locating the binaries, +# laying out the stage, and the manifest scripts that give the config +# files @sample semantics — are the same work, and were duplicated +# verbatim. They live here so a fix lands in both. +# +# What deliberately does NOT live here is anything the two packages +# disagree about: the boot script, the DNS integration, linkage, the ABI +# and product naming. Those differences are the reason there are two +# builders at all, and folding them into a shared file with flags would +# hide them. +# +# POSIX sh, sourced with `.` — no bashisms, no `local`. + +# Print the version to stamp on the package, given the project root and +# an optional override (CI passes a derived version on branch builds). +# +# '-' is the pkg name/version separator and neither '-' nor '+' is legal +# inside a pkg version, so both map to '.': 0.6.0-dev -> 0.6.0.dev. +pkg_resolve_version() { + pkg_rv_root="$1" + pkg_rv_version="${2:-}" + [ -n "$pkg_rv_version" ] \ + || pkg_rv_version="$(sed -n 's/^version = "\(.*\)"/\1/p' "${pkg_rv_root}/Cargo.toml" | head -1)" + if [ -z "$pkg_rv_version" ]; then + echo "error: could not read version from Cargo.toml" >&2 + return 1 + fi + printf '%s\n' "$pkg_rv_version" | tr -- '+-' '..' +} + +# The build host's pkg ABI, or a sane default where pkg cannot say. +pkg_host_abi() { + pkg config abi 2>/dev/null || echo "FreeBSD:15:amd64" +} + +# Fail unless every named binary is present and executable in $1. +pkg_require_binaries() { + pkg_rb_dir="$1" + shift + for pkg_rb_bin in "$@"; do + if [ ! -x "${pkg_rb_dir}/${pkg_rb_bin}" ]; then + echo "error: ${pkg_rb_dir}/${pkg_rb_bin} missing (run without --no-build)" >&2 + return 1 + fi + done + return 0 +} + +# The directory skeleton both packages install into. +pkg_stage_tree() { + install -d "$1/usr/local/bin" \ + "$1/usr/local/etc/fips" \ + "$1/usr/local/etc/rc.d" \ + "$1/usr/local/libexec/fips" +} + +# Copy the named binaries from $1 into the stage at $2. +pkg_stage_binaries() { + pkg_sb_dir="$1" + pkg_sb_stage="$2" + shift 2 + for pkg_sb_bin in "$@"; do + install -m 0755 "${pkg_sb_dir}/${pkg_sb_bin}" "${pkg_sb_stage}/usr/local/bin/" || return 1 + done + return 0 +} + +# Emit the post-install lines that copy a sample into place if the real +# file is absent. Arguments are ":" pairs, e.g. fips.yaml:0600. +# +# This is the @sample plist keyword spelled out by hand: that keyword +# lives in the ports tree (/usr/ports/Keywords/sample.ucl), which neither +# a plain pkg-create host nor pfSense has. +# +# FreeBSD has no "root" group; wheel is gid 0. +pkg_sample_seed_script() { + for pkg_ss_entry in "$@"; do + pkg_ss_name="${pkg_ss_entry%%:*}" + pkg_ss_mode="${pkg_ss_entry##*:}" + cat <-.pkg; every caller +# wants something more specific, so the rename is part of the helper +# rather than repeated after it. +pkg_create_package() { + pkg_cp_stage="$1" + pkg_cp_deploy="$2" + pkg_cp_version="$3" + pkg_cp_out="$4" + + mkdir -p "$pkg_cp_deploy" + echo "==> pkg create" + pkg create -M "${pkg_cp_stage}/+MANIFEST" -p "${pkg_cp_stage}/pkg-plist" \ + -r "$pkg_cp_stage" -o "$pkg_cp_deploy" || return $? + mv "${pkg_cp_deploy}/fips-${pkg_cp_version}.pkg" "$pkg_cp_out" || return $? + + echo "==> built:" + ls -l "$pkg_cp_out" +} + +# Emit the manifest fields both packages agree on, in the order +# pkg-create(8) expects: $1 version, $2 ABI, $3 comment, $4 description. +# +# Shared because they are shared *policy*, not merely duplicated text — +# origin, maintainer, licence and prefix describe one project, and two +# copies is two things to forget to update. Anything a package decides +# for itself (annotations, the scripts block) is appended by the caller +# after this. +pkg_manifest_header() { + cat </dev/null 2>&1 || pw groupadd fips +EOS +} + +# Build the release binaries. $1 project root, $2 optional Rust target +# triple — passing one keeps RUSTFLAGS off build scripts and proc-macros, +# and puts the output under target//release. +pkg_cargo_build() { + if [ -n "${2:-}" ]; then + echo "==> cargo build --release --target $2" + (cd "$1" && cargo build --release --target "$2") + else + echo "==> cargo build --release" + (cd "$1" && cargo build --release) + fi +} diff --git a/packaging/freebsd/README.md b/packaging/freebsd/README.md index 197c6239..5b289472 100644 --- a/packaging/freebsd/README.md +++ b/packaging/freebsd/README.md @@ -4,6 +4,11 @@ Builds a native FreeBSD `.pkg` shipping `fips`, `fipsctl`, `fipstop`, rc.d services, and `.fips` DNS integration. `fips-gateway` is excluded (its NAT backend is nftables, Linux-only). +**On pfSense?** Use [`packaging/pfsense/`](../pfsense/README.md) +instead. pfSense is FreeBSD underneath, but it boots packages, wires up +DNS and handles upgrades differently enough that this package does not +work there; the pfSense one addresses each difference. + Platform notes: the Ethernet and BLE transports are not available on FreeBSD (UDP, TCP, Tor, and Nym are). The UDP datapath deliberately uses the portable single-packet receive loop — FreeBSD's `recvmmsg(2)` diff --git a/packaging/freebsd/build-pkg.sh b/packaging/freebsd/build-pkg.sh index 27f78e83..aa0f4c2f 100755 --- a/packaging/freebsd/build-pkg.sh +++ b/packaging/freebsd/build-pkg.sh @@ -14,6 +14,10 @@ set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +# Mechanics shared with the pfSense builder: version derivation, the +# stage layout, the @sample manifest scripts, and pkg create. +. "${PROJECT_ROOT}/packaging/common/pkg-lib.sh" + NO_BUILD=0 VERSION="" while [ $# -gt 0 ]; do @@ -25,41 +29,25 @@ while [ $# -gt 0 ]; do shift done -# Default to the Cargo.toml version; CI passes a derived version that -# appends +.. on branch builds. Either way, map -# '-' and '+' to '.' — '-' is the pkg name/version separator and -# neither is allowed inside a pkg version (0.5.0-dev -> 0.5.0.dev). -[ -n "$VERSION" ] \ - || VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' "${PROJECT_ROOT}/Cargo.toml" | head -1)" -[ -n "$VERSION" ] || { echo "error: could not read version from Cargo.toml" >&2; exit 1; } -VERSION="$(printf '%s' "$VERSION" | tr -- '+-' '..')" +# CI passes a derived version that appends +.. on +# branch builds; otherwise the version comes from Cargo.toml. +VERSION="$(pkg_resolve_version "$PROJECT_ROOT" "$VERSION")" -ABI="$(pkg config abi 2>/dev/null || echo "FreeBSD:15:amd64")" +ABI="$(pkg_host_abi)" ARCH="${ABI##*:}" if [ "$NO_BUILD" -eq 0 ]; then - echo "==> cargo build --release" - (cd "$PROJECT_ROOT" && cargo build --release) + pkg_cargo_build "$PROJECT_ROOT" fi -for bin in fips fipsctl fipstop; do - [ -x "${PROJECT_ROOT}/target/release/${bin}" ] \ - || { echo "error: target/release/${bin} missing (run without --no-build)" >&2; exit 1; } -done +pkg_require_binaries "${PROJECT_ROOT}/target/release" fips fipsctl fipstop STAGE="$(mktemp -d "${TMPDIR:-/tmp}/fips-pkg.XXXXXX")" trap 'rm -rf "$STAGE"' EXIT echo "==> staging into ${STAGE}" -install -d "${STAGE}/usr/local/bin" \ - "${STAGE}/usr/local/etc/fips" \ - "${STAGE}/usr/local/etc/rc.d" \ - "${STAGE}/usr/local/libexec/fips" - -install -m 0755 "${PROJECT_ROOT}/target/release/fips" \ - "${PROJECT_ROOT}/target/release/fipsctl" \ - "${PROJECT_ROOT}/target/release/fipstop" \ - "${STAGE}/usr/local/bin/" +pkg_stage_tree "$STAGE" +pkg_stage_binaries "${PROJECT_ROOT}/target/release" "$STAGE" fips fipsctl fipstop # Config ships sample-style: copied into place on install if absent, # removed on deinstall only if unmodified (see the manifest scripts). @@ -85,31 +73,15 @@ DESC="$(cat "${SCRIPT_DIR}/pkg-descr")" # (/usr/ports/Keywords/sample.ucl), which a plain pkg-create host (e.g. # a CI VM) does not have. cat > "${STAGE}/+MANIFEST" </dev/null 2>&1 || pw groupadd fips +$(pkg_group_script) # Install-if-absent config. fips.yaml may hold a node private key # (nsec:), so it is 0600; FreeBSD has no "root" group, wheel is gid 0. -[ -f /usr/local/etc/fips/fips.yaml ] || install -m 0600 -o root -g wheel \\ - /usr/local/etc/fips/fips.yaml.sample /usr/local/etc/fips/fips.yaml -[ -f /usr/local/etc/fips/hosts ] || install -m 0644 -o root -g wheel \\ - /usr/local/etc/fips/hosts.sample /usr/local/etc/fips/hosts +$(pkg_sample_seed_script fips.yaml:0600 hosts:0644) # pkg upgrade runs the old package's pre-deinstall (which stops the # services); bring them back up on the new binaries if enabled. if [ "\${PKG_UPGRADE:-}" = "true" ]; then @@ -132,11 +104,7 @@ if [ "\${PKG_UPGRADE:-}" != "true" ]; then # Removal: clear the resolver drop-in even if the service was never # started through rc. /usr/local/libexec/fips/fips-dns-teardown 2>/dev/null || true - for f in fips.yaml hosts; do - s="/usr/local/etc/fips/\${f}.sample" - t="/usr/local/etc/fips/\${f}" - if [ -f "\$t" ] && cmp -s "\$t" "\$s"; then rm -f "\$t"; fi - done +$(pkg_sample_purge_script fips.yaml hosts) fi EOD } @@ -155,15 +123,8 @@ libexec/fips/fips-dns-teardown @dir etc/fips EOF -mkdir -p "${PROJECT_ROOT}/deploy" -echo "==> pkg create" -pkg create -M "${STAGE}/+MANIFEST" -p "${STAGE}/pkg-plist" \ - -r "$STAGE" -o "${PROJECT_ROOT}/deploy" - # pkg create always names the file -.pkg; add the OS and # arch so release assets stay distinct from the macOS .pkg files. OUT="${PROJECT_ROOT}/deploy/fips-${VERSION}-freebsd-${ARCH}.pkg" -mv "${PROJECT_ROOT}/deploy/fips-${VERSION}.pkg" "$OUT" -echo "==> built:" -ls -l "$OUT" +pkg_create_package "$STAGE" "${PROJECT_ROOT}/deploy" "$VERSION" "$OUT" diff --git a/packaging/pfsense/README.md b/packaging/pfsense/README.md new file mode 100644 index 00000000..f8835c6e --- /dev/null +++ b/packaging/pfsense/README.md @@ -0,0 +1,525 @@ +# FIPS pfSense packaging + +Builds a `.pkg` that installs FIPS on pfSense: `fips`, `fipsctl`, +`fipstop`, a boot script pfSense actually runs, and helpers that wire +the `.fips` zone into the DNS Resolver. `fips-gateway` is excluded (its +NAT backend is nftables, Linux-only; pfSense has pf for that). + +This is **not a Netgate-supported package** and has no GUI. Netgate +[documents third-party packages as unsupported][netgate-freebsd-pkg] +and warns they can break upgrades; treat it accordingly. + +[netgate-freebsd-pkg]: https://docs.netgate.com/pfsense/en/latest/recipes/freebsd-pkg-repo.html + +## Maintenance and reports + +This package is maintained by **fr34aky** (via the project's issue +tracker). pfSense-specific problems — a boot script that does not start, +DNS wiring, an upgrade that misbehaves — are best reported there; the +package manifest's maintainer field points at the project, so reports +reach it either way. The ABI-to-product table below tracks Netgate's +releases and needs updating when a new pfSense version ships or an old +one goes end-of-life; that is part of maintaining this package. + +## Why this is separate from `packaging/freebsd/` + +pfSense is FreeBSD underneath, but the FreeBSD package does not work +here — not "works worse", does not work — in three ways that all fail +silently: + +| | FreeBSD package | pfSense | +|---|---|---| +| Boot | `rc.d/fips`, an `rc.conf`-gated rc.subr service | pfSense's `rc.start_packages` globs `/usr/local/etc/rc.d/*.sh` and runs each as `