From 3e0d9f572636defdf02b00e303083918f1e1bc79 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sun, 14 Jun 2026 03:39:32 +0000 Subject: [PATCH] ci: build and lint the AUR package on every CI trigger Run the AUR package through makepkg + namcap on the same triggers as the other package workflows (pushes to master/maint/next, pull requests, tags, and manual dispatch), so a broken PKGBUILD is caught continuously rather than only at release time. The build runs in an Arch container (makepkg/namcap are not on ubuntu-latest) and packages the checked-out tree from a local git-archive tarball, so it works for branch/PR builds and unreleased rc tags that have no published GitHub source archive yet. makepkg runs with --nocheck since the test suite is already covered by ci.yml; this job validates packaging. Publishing to the AUR is unchanged in intent but now gated to a real (non-prerelease) release tag push, plus the existing manual-dispatch republish path for packaging-only pkgrel bumps; it depends on the build job so a package that fails to build or lint is never published. Branch pushes and pull requests build and lint but never publish. The PKGBUILD-patching logic is factored into a shared packaging/aur/patch-pkgbuild.sh used by both jobs; the build path sanitizes the version for makepkg (which forbids '-' in pkgver). --- .github/workflows/aur-publish.yml | 167 +++++++++++++++++++++++------- packaging/aur/patch-pkgbuild.sh | 76 ++++++++++++++ 2 files changed, 208 insertions(+), 35 deletions(-) create mode 100755 packaging/aur/patch-pkgbuild.sh diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml index ff8803c..7a259e5 100644 --- a/.github/workflows/aur-publish.yml +++ b/.github/workflows/aur-publish.yml @@ -1,25 +1,151 @@ name: AUR Publish on: + push: + branches: + - master + - maint + - next + tags: + - 'v*' + pull_request: workflow_dispatch: inputs: tag: - description: 'Release tag to publish (e.g. v0.3.0). Defaults to the tag the workflow was dispatched from.' + description: 'Release tag to publish (e.g. v0.4.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*' jobs: + # ─────────────────────────────────────────────────────────────────────────── + # Build + lint the AUR package on every trigger, matching the coverage the + # other package workflows (linux/macos/windows/openwrt) give their artifacts: + # branch pushes, pull requests, tags, and manual dispatch. Uses makepkg + + # namcap in an Arch container (neither tool exists on ubuntu-latest) and builds + # the *checked-out tree* from a local git-archive tarball, so it works for + # branch/PR builds and unreleased rc tags whose GitHub source archive does not + # exist yet. This job never publishes. + # ─────────────────────────────────────────────────────────────────────────── + aur-build: + name: Build and lint fips AUR package + runs-on: ubuntu-latest + container: archlinux:base-devel + + steps: + - name: Install build and lint tooling + run: | + set -euo pipefail + pacman -Sy --noconfirm --needed base-devel namcap git curl + + - uses: actions/checkout@v4 + + - name: Resolve package version + id: ver + env: + INPUT_TAG: ${{ inputs.tag }} + INPUT_PKGREL: ${{ inputs.pkgrel }} + run: | + set -euo pipefail + if [ -n "${INPUT_TAG:-}" ]; then + RAW="${INPUT_TAG#v}" + elif [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then + RAW="${GITHUB_REF_NAME#v}" + else + # Branch push / PR: derive the version from the crate manifest. + RAW=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/') + fi + # makepkg forbids '-' in pkgver; map e.g. 0.4.0-rc1 -> 0.4.0rc1, + # 0.4.0-dev -> 0.4.0dev. The build only needs an internally consistent + # pkgver (it matches the git-archive prefix below); this is not the + # value the real publish uses. + VERSION="${RAW//-/}" + PKGREL="${INPUT_PKGREL:-1}" + case "$PKGREL" in + ''|*[!0-9]*|0) echo "pkgrel '$PKGREL' must be a positive integer"; exit 1 ;; + esac + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "pkgrel=${PKGREL}" >> "$GITHUB_OUTPUT" + echo "Resolved AUR pkgver=${VERSION} pkgrel=${PKGREL}" + + - name: Create non-root build user and fix ownership + run: | + set -euo pipefail + # makepkg refuses to run as root; create an unprivileged build user + # with passwordless sudo (needed for pacman dep installs during -s). + useradd -m -s /bin/bash builder + echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder + chmod 0440 /etc/sudoers.d/builder + # The checkout is owned by root; hand it to the build user. + chown -R builder:builder "$GITHUB_WORKSPACE" + + - name: Build a local source tarball of the checkout + env: + VERSION: ${{ steps.ver.outputs.version }} + run: | + set -euo pipefail + # The PKGBUILD source= points at GitHub archive/.tar.gz, which does + # not exist for a branch push, a PR, or an unreleased rc tag and would + # 404. Instead build the checked-out tree by packing it into a local + # tarball whose top-level directory matches what the PKGBUILD expects + # ("fips-/"); patch-pkgbuild.sh repoints source= at it. + TARBALL="packaging/aur/fips-${VERSION}.tar.gz" + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git -C "$GITHUB_WORKSPACE" archive --format=tar.gz \ + --prefix="fips-${VERSION}/" -o "$TARBALL" HEAD + chown builder:builder "$TARBALL" + ls -l "$TARBALL" + + - name: Patch PKGBUILD + env: + TAG: v${{ steps.ver.outputs.version }} + VERSION: ${{ steps.ver.outputs.version }} + PKGREL: ${{ steps.ver.outputs.pkgrel }} + run: | + set -euo pipefail + LOCAL_TARBALL="packaging/aur/fips-${VERSION}.tar.gz" \ + bash packaging/aur/patch-pkgbuild.sh + chown builder:builder packaging/aur/PKGBUILD + + - name: makepkg build and namcap lint (as build user) + run: | + set -euo pipefail + sudo -u builder bash -euo pipefail -c ' + cd packaging/aur + echo "::group::namcap PKGBUILD" + namcap PKGBUILD + echo "::endgroup::" + echo "::group::makepkg build" + # --nocheck: skip the PKGBUILD check() (cargo test --lib); the test + # suite is already covered by ci.yml. This job validates packaging. + makepkg -s --noconfirm --nocheck + echo "::endgroup::" + echo "::group::namcap built package" + for pkg in *.pkg.tar.*; do + echo "namcap $pkg" + namcap "$pkg" + done + echo "::endgroup::" + ' + + # ─────────────────────────────────────────────────────────────────────────── + # Publish to the AUR. Runs only on a real (non-prerelease) release tag push, + # or a manual dispatch (packaging-only republish with explicit tag + pkgrel). + # Branch pushes and pull requests build+lint above but never reach this job. + # Gated on aur-build so a package that fails to build/lint is never published. + # ─────────────────────────────────────────────────────────────────────────── aur-publish-fips: name: Publish fips to AUR + needs: aur-build runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || !contains(github.ref_name, '-') + if: >- + github.event_name == 'workflow_dispatch' + || (github.event_name == 'push' + && startsWith(github.ref, 'refs/tags/v') + && !contains(github.ref_name, '-')) steps: - name: Resolve release tag @@ -59,36 +185,7 @@ jobs: 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" - echo "Fetching $URL" - SOURCE_SUM=$(curl -fsSL --retry 3 "$URL" | b2sum | awk '{print $1}') - SYSUSERS_SUM=$(b2sum packaging/aur/fips.sysusers | awk '{print $1}') - TMPFILES_SUM=$(b2sum packaging/aur/fips.tmpfiles | awk '{print $1}') - for v in SOURCE_SUM SYSUSERS_SUM TMPFILES_SUM; do - eval val=\$$v - 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 } - in_block { - count++ - if (count == 2) sub(/[a-f0-9]{128}/, s1) - if (count == 3) sub(/[a-f0-9]{128}/, s2) - if ($0 ~ /\)/) in_block=0 - } - { print } - ' packaging/aur/PKGBUILD > packaging/aur/PKGBUILD.new - mv packaging/aur/PKGBUILD.new packaging/aur/PKGBUILD - echo "Patched PKGBUILD:" - grep -E "^(pkgver|pkgrel|conflicts|options)=" packaging/aur/PKGBUILD - awk '/^b2sums=\(/,/\)$/' packaging/aur/PKGBUILD + run: bash packaging/aur/patch-pkgbuild.sh - name: Publish to AUR uses: KSXGitHub/github-actions-deploy-aur@v4.1.2 diff --git a/packaging/aur/patch-pkgbuild.sh b/packaging/aur/patch-pkgbuild.sh new file mode 100755 index 0000000..55b8fe3 --- /dev/null +++ b/packaging/aur/patch-pkgbuild.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Patch packaging/aur/PKGBUILD in place with the release pkgver, pkgrel, +# conflicts, options, and b2sums. +# +# Shared by the AUR publish workflow's real-publish job and its dry-run job +# so the patching logic lives in exactly one place. +# +# Required environment variables: +# TAG - release tag (e.g. v0.4.0 or v0.4.0-rc1) +# VERSION - tag without the leading 'v' (e.g. 0.4.0) +# PKGREL - AUR pkgrel (positive integer) +# +# Source of the tarball b2sum: +# Default: fetch the GitHub source archive for $TAG and hash it. +# Dry-run: set LOCAL_TARBALL to a path; its b2sum is used instead and the +# PKGBUILD source= line is rewritten to point at that local file. +# This lets a dry-run validate an rc tag that has no published +# GitHub release archive yet (the archive URL would 404). +# +# Uses $GITHUB_REPOSITORY for the source URL (owner/repo). + +set -euo pipefail + +: "${TAG:?TAG must be set}" +: "${VERSION:?VERSION must be set}" +: "${PKGREL:?PKGREL must be set}" + +PKGBUILD="packaging/aur/PKGBUILD" + +SYSUSERS_SUM=$(b2sum packaging/aur/fips.sysusers | awk '{print $1}') +TMPFILES_SUM=$(b2sum packaging/aur/fips.tmpfiles | awk '{print $1}') + +if [ -n "${LOCAL_TARBALL:-}" ]; then + # Dry-run path: hash the locally-created tarball of the checkout. + echo "Using local tarball $LOCAL_TARBALL for b2sum (dry-run)" + SOURCE_SUM=$(b2sum "$LOCAL_TARBALL" | awk '{print $1}') +else + URL="https://github.com/${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}/archive/${TAG}.tar.gz" + echo "Fetching $URL" + SOURCE_SUM=$(curl -fsSL --retry 3 "$URL" | b2sum | awk '{print $1}') +fi + +for v in SOURCE_SUM SYSUSERS_SUM TMPFILES_SUM; do + eval val=\$$v + if [ -z "$val" ]; then echo "$v is empty"; exit 1; fi +done + +sed -i "s/^pkgver=.*/pkgver=${VERSION}/" "$PKGBUILD" +sed -i "s/^pkgrel=.*/pkgrel=${PKGREL}/" "$PKGBUILD" +sed -i "s/^conflicts=.*/conflicts=('fips-git' 'fips-git-debug')/" "$PKGBUILD" +sed -i "s/^options=.*/options=('!lto' '!debug')/" "$PKGBUILD" + +if [ -n "${LOCAL_TARBALL:-}" ]; then + # Repoint the first source entry at the local tarball so makepkg builds the + # checked-out tree instead of fetching the (possibly unpublished) GitHub + # archive. makepkg resolves a bare filename source against $startdir. + LOCAL_BASE=$(basename "$LOCAL_TARBALL") + sed -i "s|^source=(\"\$pkgname-\$pkgver.tar.gz::[^\"]*\"|source=(\"\$pkgname-\$pkgver.tar.gz::${LOCAL_BASE}\"|" "$PKGBUILD" +fi + +sed -i "s|^b2sums=('SKIP'.*|b2sums=('${SOURCE_SUM}'|" "$PKGBUILD" +awk -v s1="$SYSUSERS_SUM" -v s2="$TMPFILES_SUM" ' + /^b2sums=\(/ { in_block=1; count=0 } + in_block { + count++ + if (count == 2) sub(/[a-f0-9]{128}/, s1) + if (count == 3) sub(/[a-f0-9]{128}/, s2) + if ($0 ~ /\)/) in_block=0 + } + { print } +' "$PKGBUILD" > "$PKGBUILD.new" +mv "$PKGBUILD.new" "$PKGBUILD" + +echo "Patched PKGBUILD:" +grep -E "^(pkgver|pkgrel|conflicts|options|source)=" "$PKGBUILD" +awk '/^b2sums=\(/,/\)$/' "$PKGBUILD"