From 57a089f6c3685721129a79775435256da3c14a30 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Wed, 20 May 2026 22:40:32 +0000 Subject: [PATCH 1/3] macos package: derive package arch from the build target The published v0.3.0 macOS installer is a structurally corrupt xar archive: pkgutil and xar reject it even though its SHA-256 matches the published checksum. build-pkg.sh derived the architecture suffix in the .pkg filename from `uname -m`. On the Apple-silicon macOS runner that always reports arm64, so the cross-compiled x86_64 build also named its output fips--macos-arm64.pkg. The release job downloads both build artifacts with merge-multiple into one directory, where the two identically named files collide and tear into a malformed result. The x86_64 package never reaches the release at all. Derive the package architecture from the Rust target triple, which is authoritative for cross-compiles, instead of from the build host. Each matrix leg now produces a distinctly named, arch-correct package, so the two artifacts no longer collide. Add a SHA-256 integrity chain so a corrupt or mismatched asset cannot be published again: - Capture the .pkg SHA-256 on the macOS runner, after the on-runner structural verification, into a sidecar file carried in the artifact. - Add a verify-handoff job that runs on every trigger and asserts each downloaded .pkg still matches its macOS-runner SHA-256. - Gate the release job on verify-handoff and repeat the check on the exact bytes about to be published. The build step now asserts it produced the expected arch-named package so a regression in the naming fails loudly rather than as a silent collision. Relates to #102. The published v0.3.0 macOS assets still need to be rebuilt and reuploaded separately. --- .github/workflows/package-macos.yml | 125 ++++++++++++++++++++++++++-- packaging/macos/build-pkg.sh | 18 +++- 2 files changed, 134 insertions(+), 9 deletions(-) diff --git a/.github/workflows/package-macos.yml b/.github/workflows/package-macos.yml index 60ad1ef..f5bb05f 100644 --- a/.github/workflows/package-macos.yml +++ b/.github/workflows/package-macos.yml @@ -100,14 +100,22 @@ jobs: shell: bash run: | : ${GITHUB_OUTPUT:=/tmp/github_output} + set -euo pipefail - PKG_FILE=$(find deploy -maxdepth 1 -type f -name "fips-*-macos-*.pkg" | sort | head -n 1) - if [[ -z "$PKG_FILE" ]]; then - echo "Missing macOS package" >&2 + # build-pkg.sh names the package from the build target, so each + # matrix leg produces a distinctly named, arch-correct asset. + # Assert that here: a regression in that naming then fails loudly + # at the build stage instead of as a silent collision when the + # release job merges both artifacts into one directory. + EXPECTED="deploy/fips-${{ needs.determine-versioning.outputs.macos_package_version }}-macos-${{ matrix.arch }}.pkg" + if [[ ! -f "$EXPECTED" ]]; then + echo "Expected package $EXPECTED was not produced" >&2 + echo "deploy/ contains:" >&2 + ls -la deploy >&2 || true exit 1 fi - echo "pkg=$PKG_FILE" >> "$GITHUB_OUTPUT" + echo "pkg=$EXPECTED" >> "$GITHUB_OUTPUT" - name: Verify .pkg structural correctness shell: bash @@ -185,16 +193,27 @@ jobs: fi echo "==> .pkg verification PASSED" - - name: SHA-256 hash + - name: SHA-256 hash and sidecar + shell: bash run: | + set -euo pipefail + PKG="${{ steps.macos-assets.outputs.pkg }}" echo "==> macOS release asset:" - shasum -a 256 "${{ steps.macos-assets.outputs.pkg }}" + # Capture the SHA-256 of the verified .pkg on the macOS runner and + # write it to a sidecar file next to the .pkg, in the standard + # ` ` shasum format. The verify-handoff and release + # jobs re-check the downloaded bytes against this value, so any + # corruption introduced after this point is detected before + # publication. + ( cd "$(dirname "$PKG")" && shasum -a 256 "$(basename "$PKG")" | tee "$(basename "$PKG").sha256" ) - name: Upload artifact uses: actions/upload-artifact@v4 with: name: fips_${{ needs.determine-versioning.outputs.macos_package_version }}_${{ matrix.arch }}_macos - path: ${{ steps.macos-assets.outputs.pkg }} + path: | + ${{ steps.macos-assets.outputs.pkg }} + ${{ steps.macos-assets.outputs.pkg }}.sha256 retention-days: 30 - name: Build summary @@ -202,10 +221,61 @@ jobs: echo "Build Summary for macOS/${{ matrix.arch }}:" echo " Package: ${{ steps.macos-assets.outputs.pkg }}" + verify-handoff: + name: Verify macOS package handoff integrity + runs-on: ubuntu-latest + needs: build + + steps: + - name: Download macOS artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Verify .pkg integrity across the handoff + shell: bash + run: | + set -euo pipefail + cd dist + + pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort) + if [[ -z "$pkgs" ]]; then + echo "FAIL: no .pkg artifacts were downloaded" >&2 + exit 1 + fi + + fail=0 + while IFS= read -r pkg; do + base=$(basename "$pkg") + sidecar="${pkg}.sha256" + if [[ ! -f "$sidecar" ]]; then + echo "FAIL: missing SHA-256 sidecar for $base" >&2 + fail=1 + continue + fi + expected=$(awk '{print $1}' "$sidecar") + actual=$(sha256sum "$pkg" | awk '{print $1}') + if [[ "$expected" != "$actual" ]]; then + echo "FAIL: $base SHA-256 mismatch across the artifact handoff" >&2 + echo " expected (macOS runner): $expected" >&2 + echo " actual (downloaded): $actual" >&2 + fail=1 + continue + fi + echo "PASS: $base matches the macOS-runner SHA-256 ($actual)" + done <<<"$pkgs" + + if [[ "$fail" -ne 0 ]]; then + echo "==> macOS package handoff verification FAILED" >&2 + exit 1 + fi + echo "==> macOS package handoff verification PASSED" + release: name: Publish macOS assets to GitHub Release runs-on: ubuntu-latest - needs: build + needs: [build, verify-handoff] if: startsWith(github.ref, 'refs/tags/') permissions: contents: write @@ -217,6 +287,45 @@ jobs: path: dist merge-multiple: true + - name: Validate .pkg bytes before publishing + shell: bash + run: | + set -euo pipefail + cd dist + + pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort) + if [[ -z "$pkgs" ]]; then + echo "FAIL: no .pkg artifacts were downloaded" >&2 + exit 1 + fi + + fail=0 + while IFS= read -r pkg; do + base=$(basename "$pkg") + sidecar="${pkg}.sha256" + if [[ ! -f "$sidecar" ]]; then + echo "FAIL: missing SHA-256 sidecar for $base" >&2 + fail=1 + continue + fi + expected=$(awk '{print $1}' "$sidecar") + actual=$(sha256sum "$pkg" | awk '{print $1}') + if [[ "$expected" != "$actual" ]]; then + echo "FAIL: $base SHA-256 mismatch on the bytes about to be published" >&2 + echo " expected (macOS runner): $expected" >&2 + echo " actual (downloaded): $actual" >&2 + fail=1 + continue + fi + echo "PASS: $base matches the macOS-runner SHA-256 ($actual)" + done <<<"$pkgs" + + if [[ "$fail" -ne 0 ]]; then + echo "==> pre-publish .pkg verification FAILED; not publishing" >&2 + exit 1 + fi + echo "==> pre-publish .pkg verification PASSED" + - name: Generate macOS release checksums run: | cd dist diff --git a/packaging/macos/build-pkg.sh b/packaging/macos/build-pkg.sh index 0974342..37b4531 100755 --- a/packaging/macos/build-pkg.sh +++ b/packaging/macos/build-pkg.sh @@ -55,7 +55,23 @@ while [[ $# -gt 0 ]]; do done VERSION="${VERSION_OVERRIDE:-$(grep '^version' "${PROJECT_ROOT}/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')}" -ARCH="$(uname -m)" + +# Derive the package architecture from the build target, not the build +# host. When cross-compiling (for example building the x86_64 package on +# an Apple-silicon machine) `uname -m` reports the host architecture and +# would mislabel the package; the Rust target triple is authoritative. +if [[ -n "${TARGET_TRIPLE}" ]]; then + case "${TARGET_TRIPLE}" in + aarch64-*) ARCH="arm64" ;; + x86_64-*) ARCH="x86_64" ;; + *) + echo "Unsupported target triple: ${TARGET_TRIPLE}" >&2 + exit 1 + ;; + esac +else + ARCH="$(uname -m)" +fi PKG_NAME="fips-${VERSION}-macos-${ARCH}" DEPLOY_DIR="${PROJECT_ROOT}/deploy" STAGING_DIR="$(mktemp -d)" From 7a1365fb9eea66a416c41ecc4fbe11d3b98df0fc Mon Sep 17 00:00:00 2001 From: sandwich Date: Wed, 20 May 2026 17:59:27 +0200 Subject: [PATCH 2/3] aur: install fips-dns helpers, fix fips/fips-git package transition The AUR `fips` and `fips-git` packages did not install the `fips-dns-setup` and `fips-dns-teardown` helper scripts that `fips-dns.service` runs. The Debian package ships them to `/usr/lib/fips/` through the `[package.metadata.deb]` assets, but the AUR `package()` functions never replicated those install steps, so `fips-dns.service` failed to start on Arch with "Unable to locate executable /usr/lib/fips/fips-dns-setup". Add the two `install -Dm0755` lines to both PKGBUILDs so the AUR packages match the Debian layout. Also harden the transition between the `fips` and `fips-git` packages: each PKGBUILD now declares the other variant's `-debug` split package as a conflict and opts out of the debug split, so a stale debug build cannot retain ownership of installed files when switching between the release and VCS packages. The `aur-publish` workflow gains a validated `pkgrel` dispatch input so corrected packaging can be republished against an existing release tag without retagging. Fixes #98 (cherry picked from commit 4cf550e23d85b6d2460733edc7c2c288422ce543) --- .github/workflows/aur-publish.yml | 18 ++++++++++++++++-- CHANGELOG.md | 9 +++++++++ packaging/aur/PKGBUILD | 8 ++++++-- packaging/aur/PKGBUILD-git | 6 +++++- packaging/aur/README.md | 12 ++++++++++++ 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml index d91dceb..ff8803c 100644 --- a/.github/workflows/aur-publish.yml +++ b/.github/workflows/aur-publish.yml @@ -7,6 +7,10 @@ on: description: 'Release tag to publish (e.g. v0.3.0). Defaults to the tag the workflow was dispatched from.' required: false default: '' + pkgrel: + description: 'AUR pkgrel to publish. Use 2+ for packaging-only republishes of an existing tag.' + required: false + default: '1' push: tags: - 'v*' @@ -22,13 +26,18 @@ jobs: id: tag env: INPUT_TAG: ${{ inputs.tag }} + INPUT_PKGREL: ${{ inputs.pkgrel }} run: | set -euo pipefail TAG="${INPUT_TAG:-$GITHUB_REF_NAME}" + PKGREL="${INPUT_PKGREL:-1}" case "$TAG" in v*) ;; *) echo "Tag '$TAG' does not look like a release tag (vX.Y.Z)"; exit 1 ;; esac + case "$PKGREL" in + ''|*[!0-9]*|0) echo "pkgrel '$PKGREL' must be a positive integer"; exit 1 ;; + esac case "$TAG" in *-*) if [ "$GITHUB_EVENT_NAME" != "workflow_dispatch" ]; then @@ -39,15 +48,17 @@ jobs: esac echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + echo "pkgrel=${PKGREL}" >> "$GITHUB_OUTPUT" - uses: actions/checkout@v4 with: ref: ${{ steps.tag.outputs.tag }} - - name: Patch PKGBUILD with pkgver and b2sums + - name: Patch PKGBUILD with pkgver, pkgrel, conflicts, and b2sums env: TAG: ${{ steps.tag.outputs.tag }} VERSION: ${{ steps.tag.outputs.version }} + PKGREL: ${{ steps.tag.outputs.pkgrel }} run: | set -euo pipefail URL="https://github.com/${GITHUB_REPOSITORY}/archive/${TAG}.tar.gz" @@ -60,6 +71,9 @@ jobs: if [ -z "$val" ]; then echo "$v is empty"; exit 1; fi done sed -i "s/^pkgver=.*/pkgver=${VERSION}/" packaging/aur/PKGBUILD + sed -i "s/^pkgrel=.*/pkgrel=${PKGREL}/" packaging/aur/PKGBUILD + sed -i "s/^conflicts=.*/conflicts=('fips-git' 'fips-git-debug')/" packaging/aur/PKGBUILD + sed -i "s/^options=.*/options=('!lto' '!debug')/" packaging/aur/PKGBUILD sed -i "s|^b2sums=('SKIP'.*|b2sums=('${SOURCE_SUM}'|" packaging/aur/PKGBUILD awk -v s1="$SYSUSERS_SUM" -v s2="$TMPFILES_SUM" ' /^b2sums=\(/ { in_block=1; count=0 } @@ -73,7 +87,7 @@ jobs: ' packaging/aur/PKGBUILD > packaging/aur/PKGBUILD.new mv packaging/aur/PKGBUILD.new packaging/aur/PKGBUILD echo "Patched PKGBUILD:" - grep -E "^pkgver=" packaging/aur/PKGBUILD + grep -E "^(pkgver|pkgrel|conflicts|options)=" packaging/aur/PKGBUILD awk '/^b2sums=\(/,/\)$/' packaging/aur/PKGBUILD - name: Publish to AUR diff --git a/CHANGELOG.md b/CHANGELOG.md index cccc88e..a1e9bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- AUR packaging: the `fips` and `fips-git` PKGBUILDs now install the + `fips-dns-setup` and `fips-dns-teardown` helpers into + `/usr/lib/fips/`, matching the Debian package. The AUR `package()` + step previously omitted them, so `fips-dns.service` failed to + start on Arch installs ("Unable to locate executable + `/usr/lib/fips/fips-dns-setup`", #98). The PKGBUILDs additionally + opt out of the debug split package and declare the `*-debug` + variant as a conflict, so a stale debug build cannot own installed + files across a package switch. - Nostr discovery: filter unroutable direct UDP/TCP advert endpoints. Publisher and validator now retain only endpoints that parse as concrete socket addresses with routable IPs and nonzero ports. diff --git a/packaging/aur/PKGBUILD b/packaging/aur/PKGBUILD index 2f9e810..f7ba3b4 100644 --- a/packaging/aur/PKGBUILD +++ b/packaging/aur/PKGBUILD @@ -9,7 +9,7 @@ arch=('x86_64') depends=('gcc-libs' 'glibc') makedepends=('cargo') optdepends=('systemd-resolved: .fips DNS resolution') -conflicts=('fips-git') +conflicts=('fips-git' 'fips-git-debug') backup=('etc/fips/fips.yaml' 'etc/fips/hosts' 'etc/fips/fips.nft') install=fips.install source=("$pkgname-$pkgver.tar.gz::https://github.com/jmcorgan/fips/archive/v$pkgver.tar.gz" @@ -18,7 +18,7 @@ source=("$pkgname-$pkgver.tar.gz::https://github.com/jmcorgan/fips/archive/v$pkg b2sums=('SKIP' # tarball hash computed by CI via updpkgsums on release '25a0552f3d67d12f48dfd40fe4776ad7c46afeeab76bd2674b48e234db3c145810a24569a8c1a7f4c186eb546f0fae2ebe1550080c0e91d8eb72ba9934c752a6' '844257cb8e09cd935d0d6345922d0f3ec777411daca20e24175b346a7b3cb95ebce12631a9466c4d94f1588ed8d62d92514ff24025ccfd0efb358e542b454b00') -options=('!lto') +options=('!lto' '!debug') prepare() { cd "$pkgname-$pkgver" @@ -53,6 +53,10 @@ package() { install -Dm0644 packaging/debian/fips-gateway.service "$pkgdir/usr/lib/systemd/system/fips-gateway.service" install -Dm0644 packaging/debian/fips-firewall.service "$pkgdir/usr/lib/systemd/system/fips-firewall.service" + # DNS helper scripts referenced by fips-dns.service + install -Dm0755 packaging/common/fips-dns-setup "$pkgdir/usr/lib/fips/fips-dns-setup" + install -Dm0755 packaging/common/fips-dns-teardown "$pkgdir/usr/lib/fips/fips-dns-teardown" + # Config files (from packaging/common/) install -Dm0600 packaging/common/fips.yaml "$pkgdir/etc/fips/fips.yaml" install -Dm0644 packaging/common/hosts "$pkgdir/etc/fips/hosts" diff --git a/packaging/aur/PKGBUILD-git b/packaging/aur/PKGBUILD-git index 39dad6c..89c4ce3 100644 --- a/packaging/aur/PKGBUILD-git +++ b/packaging/aur/PKGBUILD-git @@ -10,7 +10,7 @@ depends=('dbus' 'gcc-libs' 'glibc') makedepends=('cargo' 'git') optdepends=('systemd-resolved: .fips DNS resolution') provides=('fips') -conflicts=('fips') +conflicts=('fips' 'fips-debug') backup=('etc/fips/fips.yaml' 'etc/fips/hosts' 'etc/fips/fips.nft') install=fips.install source=("fips::git+https://github.com/jmcorgan/fips.git" @@ -60,6 +60,10 @@ package() { install -Dm0644 packaging/debian/fips-gateway.service "$pkgdir/usr/lib/systemd/system/fips-gateway.service" install -Dm0644 packaging/debian/fips-firewall.service "$pkgdir/usr/lib/systemd/system/fips-firewall.service" + # DNS helper scripts referenced by fips-dns.service + install -Dm0755 packaging/common/fips-dns-setup "$pkgdir/usr/lib/fips/fips-dns-setup" + install -Dm0755 packaging/common/fips-dns-teardown "$pkgdir/usr/lib/fips/fips-dns-teardown" + # Config files (from packaging/common/) install -Dm0600 packaging/common/fips.yaml "$pkgdir/etc/fips/fips.yaml" install -Dm0644 packaging/common/hosts "$pkgdir/etc/fips/hosts" diff --git a/packaging/aur/README.md b/packaging/aur/README.md index 4d51357..9373379 100644 --- a/packaging/aur/README.md +++ b/packaging/aur/README.md @@ -31,6 +31,8 @@ package: - Binaries: `fips`, `fipsctl`, `fipstop`, `fips-gateway` - Systemd units: `fips.service`, `fips-dns.service`, `fips-gateway.service`, `fips-firewall.service` +- DNS helpers: `/usr/lib/fips/fips-dns-setup`, + `/usr/lib/fips/fips-dns-teardown` - Config: `/etc/fips/fips.yaml`, `/etc/fips/hosts`, `/etc/fips/fips.nft` - sysusers/tmpfiles fragments for the `fips` group and `/run/fips/` @@ -39,6 +41,11 @@ operator edits to the nftables ruleset survive package upgrades. `fips-firewall.service` is shipped disabled by default, matching the Debian package: operators opt in by enabling it explicitly. +Both PKGBUILDs opt out of makepkg's automatic `*-debug` split packages. The +package metadata still conflicts with stale peer debug package names +(`fips-debug` / `fips-git-debug`) so switching between release and development +variants removes old debug-file owners cleanly. + ## Local Build and Validation Build and validate the `-git` package locally using the Makefile target: @@ -321,3 +328,8 @@ Push an update when a new version is tagged. The steps are: Phase 4 CI automation will handle this workflow automatically on new GitHub releases. + +For a packaging-only republish of an existing release tag, run the AUR Publish +workflow manually with the existing tag and incremented `pkgrel` (for example, +`tag=v0.3.0`, `pkgrel=2`). This keeps the upstream source tarball unchanged +while forcing AUR helpers to rebuild with the corrected package metadata. From 66020bc318df9c074757b853a3845bd1c83ea061 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 21 May 2026 00:50:11 +0000 Subject: [PATCH 3/3] changelog: document the macOS package-integrity fix Commit 57a089f6 (the GitHub #102 fix) landed without a CHANGELOG entry. Add the `[Unreleased]` / `### Fixed` line so the macOS package-integrity fix is on record before the v0.3.1 cut. --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e9bc4..3402e90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 opt out of the debug split package and declare the `*-debug` variant as a conflict, so a stale debug build cannot own installed files across a package switch. +- macOS package build: the `.pkg` architecture is now derived from + the Cargo `--target` triple instead of the build host's + `uname -m`. The arm64 and x86_64 release legs build on the same + Apple-silicon runner, so `uname -m` named both outputs + `fips-0.3.0-macos-arm64.pkg`; the release job's `merge-multiple` + artifact download then interleaved the two identically named + files into a single corrupt xar archive, and no x86_64 package + reached the release at all. (This shipped as the broken v0.3.0 + macOS `.pkg`, GitHub #102.) The release workflow now also asserts + the arch-named file is present and carries a SHA-256 integrity + chain from the build runner through to `gh release upload`, so a + recurrence fails CI instead of publishing. - Nostr discovery: filter unroutable direct UDP/TCP advert endpoints. Publisher and validator now retain only endpoints that parse as concrete socket addresses with routable IPs and nonzero ports.