Files
fips/.github/workflows/aur-publish.yml
Johnathan Corgan a5d782e6b5 Lint the tag a manual AUR republish publishes
A manual dispatch of the AUR workflow that names a tag built and linted
the branch it was dispatched from, then published the tag. The two trees
were never compared, so a republish could push a PKGBUILD that namcap and
makepkg had never seen.

The build-and-lint job now checks out the tag a dispatch names, and the
event's own ref otherwise, which is what the publish job resolves. The
gate scripts and their fixture tests come from a second, sparse checkout
of the workflow's own revision, so republishing a tag cut before they
existed still runs them. The job reports the commit it linted, and the
publish job refuses to continue, before its package wait, unless the tag
resolves to that commit.
2026-10-07 01:30:06 +00:00

311 lines
14 KiB
YAML

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.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'
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. The lint fails the job on namcap error-level (E:) findings;
# warnings (W:) are advisory. This job never publishes.
#
# The tree it builds and lints is the tree the publish job below publishes:
# a dispatch naming a tag checks out that tag here too, and every other
# trigger checks out the event's own ref, which is what the publish job
# resolves when no tag is given. The gate scripts and their fixture tests
# come from this workflow's own revision instead (gate-src), so republishing
# a tag cut before they existed still runs them. The job reports the commit
# it linted, and the publish job refuses any other.
# ───────────────────────────────────────────────────────────────────────────
aur-build:
name: Build and lint fips AUR package
runs-on: ubuntu-latest
container: archlinux:base-devel
outputs:
linted: ${{ steps.linted.outputs.sha }}
steps:
- name: Install build and lint tooling
run: |
set -euo pipefail
pacman -Sy --noconfirm --needed base-devel namcap git curl jq
# The tree to build, lint and (in the publish job) publish. inputs.tag
# is empty unless a dispatch names a tag, and an empty ref checks out
# the event's ref.
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ inputs.tag }}
# The gate scripts, from this workflow's own revision. The second path
# is for the publish gate's test, which discovers the real workflows
# relative to the script.
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
path: gate-src
sparse-checkout: |
packaging/aur
.github/workflows
- name: Record the commit being linted
id: linted
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
git config --global --add safe.directory "$GITHUB_WORKSPACE"
SHA=$(git -C "$GITHUB_WORKSPACE" rev-parse 'HEAD^{commit}')
echo "sha=${SHA}" >> "$GITHUB_OUTPUT"
echo "Linting ${SHA} (${INPUT_TAG:-$GITHUB_REF})"
- name: Test the publish gate
# Fixture tests for the script the publish job below waits with. They
# run here, on every trigger, so a change to the gate is exercised
# before a release tag depends on it.
run: bash gate-src/packaging/aur/test-await-package-runs.sh
- name: Test the namcap gate
# Fixture tests, with canned namcap output, for the script the lint
# below runs namcap through.
run: bash gate-src/packaging/aur/test-namcap-gate.sh
- 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: Prove the namcap gate against real namcap
# Builds toy packages, one declared correctly and two missing a
# dependency, and checks the gate passes the first and fails the others
# with this run's namcap. Runs as the build user because makepkg
# refuses root and because that is how the lint below runs.
run: sudo -u builder bash gate-src/packaging/aur/test-namcap-gate.sh --live
- 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/<tag>.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-<pkgver>/"); 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
# The gate comes from gate-src (see the checkouts above). sudo resets
# the environment, so the path is relative to packaging/aur.
sudo -u builder bash -euo pipefail -c '
cd packaging/aur
gate=../../gate-src/packaging/aur/namcap-gate.sh
echo "::group::namcap PKGBUILD"
bash "$gate" PKGBUILD
echo "::endgroup::"
echo "::group::makepkg build"
# No --nocheck. makepkg runs check() by default, so every AUR user
# runs it and this job must too. ci.yml runs the tests, but not this
# way: frozen and offline against what prepare fetched, with the
# Arch toolchain, in this container.
makepkg -s --noconfirm
echo "::endgroup::"
echo "::group::namcap built package"
bash "$gate" ./*.pkg.tar.*
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.
# It also waits for every package-*.yml run on the tag to succeed before it
# pushes: the AUR must not point at a tag whose release assets are still
# uploading or failed, and once it does, withdrawing the tag breaks the AUR
# package (its b2sum pins the tag's source archive).
#
# Before anything else it checks that the tag resolves to the commit
# aur-build built and linted, so a tag moved between the two jobs, or an
# edit that makes the two checkouts diverge, stops the publish.
# ───────────────────────────────────────────────────────────────────────────
aur-publish-fips:
name: Publish fips to AUR
needs: aur-build
runs-on: ubuntu-latest
# Above the gate's own 60-minute budget, so the gate reports a timeout
# rather than the runner killing it.
timeout-minutes: 90
permissions:
contents: read
actions: read
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
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
echo "Pre-release tag '$TAG' — skipping AUR publish"
exit 1
fi
;;
esac
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
echo "pkgrel=${PKGREL}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ steps.tag.outputs.tag }}
- name: Refuse a commit aur-build did not lint
env:
LINTED: ${{ needs.aur-build.outputs.linted }}
run: |
set -euo pipefail
SHA=$(git rev-parse 'HEAD^{commit}')
if [ -z "${LINTED:-}" ]; then
echo "::error::aur-build reported no linted commit; refusing to publish ${SHA}"
exit 1
fi
if [ "$SHA" != "$LINTED" ]; then
echo "::error::aur-build linted ${LINTED}, but the tag resolves to ${SHA}; refusing to publish"
exit 1
fi
echo "Publishing ${SHA}, the commit aur-build linted"
# The gate script comes from this workflow's own revision, not the tag:
# a dispatch republishing a tag cut before the gate existed would not
# find it in the tag's tree. The workflows it waits on still come from
# the tag's tree, which is what the tag push triggered.
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
path: gate-src
sparse-checkout: packaging/aur
- name: Wait for the tag's package workflows
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
set -euo pipefail
SHA=$(git rev-parse 'HEAD^{commit}')
echo "Tag $TAG is at $SHA"
SHA="$SHA" WORKFLOW_DIR=.github/workflows \
bash gate-src/packaging/aur/await-package-runs.sh
- 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: bash packaging/aur/patch-pkgbuild.sh
- name: Publish to AUR
uses: KSXGitHub/github-actions-deploy-aur@abe8ac26b51011c88be58c8809fd2ac674068ea5 # v4.1.2
with:
pkgname: fips
pkgbuild: packaging/aur/PKGBUILD
updpkgsums: false
assets: |
packaging/aur/fips.sysusers
packaging/aur/fips.tmpfiles
packaging/aur/fips.install
commit_username: ${{ github.repository_owner }}
commit_email: ${{ secrets.AUR_EMAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: "Update to ${{ steps.tag.outputs.tag }}"