name: macOS Package on: push: branches: - master - maint - next tags: - "v*" pull_request: workflow_dispatch: env: CARGO_TERM_COLOR: always jobs: determine-versioning: runs-on: macos-latest outputs: macos_package_version: ${{ steps.macos_version.outputs.macos_package_version }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Derive macOS package version id: macos_version shell: bash run: | : ${GITHUB_OUTPUT:=/tmp/github_output} BASE_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') if [[ "$GITHUB_REF" == refs/tags/* ]]; then VERSION="${GITHUB_REF_NAME#v}" else BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|[^A-Za-z0-9]|.|g; s/\.\.+/./g; s/^\.//; s/\.$//') HEIGHT=$(git rev-list --count HEAD) HASH=$(git rev-parse --short HEAD) if [[ -z "$BRANCH" ]]; then BRANCH="ref" fi VERSION="${BASE_VERSION}+${BRANCH}.${HEIGHT}.${HASH}" fi echo "macos_package_version=${VERSION}" >> "$GITHUB_OUTPUT" build: name: Build macOS package (${{ matrix.arch }}) runs-on: ${{ matrix.os }} needs: determine-versioning strategy: fail-fast: false matrix: include: - os: macos-latest arch: arm64 target: aarch64-apple-darwin - os: macos-latest arch: x86_64 target: x86_64-apple-darwin steps: - uses: actions/checkout@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: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: target: ${{ matrix.target }} cache: false rustflags: '' - name: Cache Cargo registry + build uses: actions/cache@v5 with: path: | ~/.cargo/registry ~/.cargo/git target key: macos-release-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: | macos-release-${{ matrix.arch }}- - name: Build release binaries run: cargo build --release --target ${{ matrix.target }} - name: Build macOS package run: | packaging/macos/build-pkg.sh \ --version "${{ needs.determine-versioning.outputs.macos_package_version }}" \ --target ${{ matrix.target }} \ --no-build - name: Resolve macOS asset path id: macos-assets shell: bash run: | : ${GITHUB_OUTPUT:=/tmp/github_output} set -euo pipefail # 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=$EXPECTED" >> "$GITHUB_OUTPUT" - name: Verify .pkg structural correctness shell: bash run: | set -euo pipefail PKG="${{ steps.macos-assets.outputs.pkg }}" EXPAND_DIR="$(mktemp -d)/expanded" PAYLOAD_DIR="$(mktemp -d)/payload" fail=0 echo "==> Verifying $PKG" # 1) Flat-package expansion if pkgutil --expand "$PKG" "$EXPAND_DIR"; then echo "PASS: pkgutil --expand" else echo "FAIL: pkgutil --expand" fail=1 fi # Extract the cpio.gz Payload so we can inspect installed file layout PAYLOAD_FILE="$(find "$EXPAND_DIR" -name Payload -type f | head -n 1)" if [[ -z "$PAYLOAD_FILE" ]]; then echo "FAIL: no Payload file inside expanded pkg" fail=1 else mkdir -p "$PAYLOAD_DIR" (cd "$PAYLOAD_DIR" && gzip -dc "$PAYLOAD_FILE" | cpio -i --quiet) echo "PASS: extracted Payload to $PAYLOAD_DIR" fi # 2) Binary at canonical install path (./usr/local/bin/fips inside payload) BIN_PATH="$PAYLOAD_DIR/usr/local/bin/fips" if [[ -f "$BIN_PATH" ]]; then echo "PASS: binary present at usr/local/bin/fips" else echo "FAIL: binary missing at usr/local/bin/fips" echo " fallback search:" find "$PAYLOAD_DIR" -name fips -type f -print || true fail=1 fi for extra in fipsctl fipstop; do if [[ -f "$PAYLOAD_DIR/usr/local/bin/$extra" ]]; then echo "PASS: binary present at usr/local/bin/$extra" else echo "FAIL: binary missing at usr/local/bin/$extra" fail=1 fi done # 3) LaunchDaemon plist at canonical location PLIST_PATH="$PAYLOAD_DIR/Library/LaunchDaemons/com.fips.daemon.plist" if [[ -f "$PLIST_PATH" ]]; then echo "PASS: plist present at Library/LaunchDaemons/com.fips.daemon.plist" else echo "FAIL: plist missing at Library/LaunchDaemons/com.fips.daemon.plist" echo " fallback search:" find "$PAYLOAD_DIR" -name '*.plist' -print || true fail=1 fi # 4) plutil -lint on the plist if [[ -f "$PLIST_PATH" ]]; then if plutil -lint "$PLIST_PATH"; then echo "PASS: plutil -lint" else echo "FAIL: plutil -lint" fail=1 fi fi if [[ "$fail" -ne 0 ]]; then echo "==> .pkg verification FAILED" exit 1 fi echo "==> .pkg verification PASSED" - name: SHA-256 hash and sidecar shell: bash run: | set -euo pipefail PKG="${{ steps.macos-assets.outputs.pkg }}" echo "==> macOS release asset:" # 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@v7 with: name: fips_${{ needs.determine-versioning.outputs.macos_package_version }}_${{ matrix.arch }}_macos path: | ${{ steps.macos-assets.outputs.pkg }} ${{ steps.macos-assets.outputs.pkg }}.sha256 retention-days: 30 - name: Build summary run: | 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@v8 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, verify-handoff] if: startsWith(github.ref, 'refs/tags/') permissions: contents: write steps: - name: Download macOS artifacts uses: actions/download-artifact@v8 with: 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 find . -maxdepth 1 -type f -name '*.pkg' -printf '%P\n' \ | LC_ALL=C sort \ | xargs sha256sum \ > checksums-macos.txt - name: Wait for tag release env: GH_TOKEN: ${{ github.token }} run: | for attempt in $(seq 1 20); do if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then exit 0 fi echo "Release ${GITHUB_REF_NAME} not available yet; waiting..." sleep 15 done echo "Timed out waiting for release ${GITHUB_REF_NAME}" >&2 exit 1 - name: Upload macOS assets env: GH_TOKEN: ${{ github.token }} run: | gh release upload "${GITHUB_REF_NAME}" \ dist/*.pkg \ dist/checksums-macos.txt \ --clobber \ --repo "${GITHUB_REPOSITORY}"