ci: prebuild the Arch environment as a container image

All six nightly jobs ran the same pacman transaction before doing any work.
The toolchain now lives in an image published to the Gitea registry as
git.bjphoster.com/aur/autoupdate:latest, rebuilt monthly since Arch is rolling
and a month-old image is a month behind.

The build job itself runs on plain ubuntu-latest -- it only drives docker. Only
the image is Arch.

Based on archlinux:base rather than base-devel: the autoupdater parses
PKGBUILDs and never compiles, so the toolchain is dead weight, 822 MB against
1.48 GB. nodejs is included because actions/checkout is a JavaScript action the
runner executes with node from inside the image.

Slimming to base initially dropped diffutils, and apply_pkgrel_rule used
diff -q. A missing binary makes the `if` fail, which reads as "changed", so
pkgrel ratcheted upward on every run -- the same silent-wrong-default class as
the empty .SRCINFO this rewrite was about, and invisible to the host test run.
The comparison is now a shell string equality with no external dependency, and
is verified against an image with no diff at all. require_tools() additionally
fails on startup for any missing binary rather than midway through.
This commit is contained in:
2026-07-26 23:17:37 +02:00
parent 4fecc4bd2c
commit 4defd478dc
7 changed files with 199 additions and 18 deletions

View File

@@ -0,0 +1,67 @@
---
name: Build Autoupdater Image
concurrency:
group: build-image
cancel-in-progress: false
on:
schedule:
# Monthly. Arch is a rolling release, so a month-old image is a month
# behind on packages; :latest is overwritten each time.
- cron: "0 3 1 * *"
push:
branches:
- main
paths:
- dockerfile
- .dockerignore
- .gitea/workflows/build-image.yaml
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
build:
# No Arch needed here -- this only drives docker. The *image* is Arch.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to the Gitea container registry
env:
GIT_HOST: ${{ vars.GIT_HOST }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: |
set -euo pipefail
# NB: this is the Gitea *username*, not vars.GIT_NAME -- that one is
# the display name used for git commit authorship and contains spaces.
printf '%s' "${GIT_TOKEN}" \
| docker login "${GIT_HOST}" \
--username "${REGISTRY_USER}" \
--password-stdin
- name: Build and push
env:
GIT_HOST: ${{ vars.GIT_HOST }}
run: |
set -euo pipefail
# The repo is `.autoupdate`, but OCI names cannot start with a dot.
image="${GIT_HOST}/aur/autoupdate"
# --pull so a monthly rebuild actually picks up a new archlinux base
# rather than reusing a cached one.
docker build --pull --tag "${image}:latest" .
docker push "${image}:latest"
echo "pushed ${image}:latest"
- name: Log out
if: always()
env:
GIT_HOST: ${{ vars.GIT_HOST }}
run: docker logout "${GIT_HOST}" || true

View File

@@ -37,8 +37,14 @@ jobs:
# emits an empty .SRCINFO, which the AUR then rejects -- that is what broke
# reflex-appimage, nethlink-appimage and pman-helper. regen_srcinfo() in
# common.sh fails loudly if this ever regresses.
#
# Prebuilt by build-image.yaml (see dockerfile) so the toolchain is not
# reinstalled in all six jobs every night.
container:
image: archlinux:base-devel
image: git.bjphoster.com/aur/autoupdate:latest
credentials:
username: ${{ vars.REGISTRY_USER }}
password: ${{ secrets.GIT_TOKEN }}
strategy:
# One broken package must not cancel the rest of the matrix.
@@ -53,15 +59,10 @@ jobs:
- tsparams
steps:
# Must come BEFORE actions/checkout. That action is JavaScript, and the
# archlinux image ships no node, so checkout dies with
# `exec: "node": executable file not found in $PATH`. A `run:` step needs
# no checkout, so the toolchain can be installed first.
- name: Bootstrap container
run: |
pacman -Syu --noconfirm --needed \
nodejs git openssh jq curl pacman-contrib namcap fuse2 python
# No bootstrap step: node, git, openssh, jq, curl and the makepkg
# toolchain all come from the prebuilt image above. node in particular
# has to be present before this action runs, since actions/checkout is
# JavaScript and the runner executes it with `node` from the image.
- uses: actions/checkout@v4
- name: Configure git and SSH
@@ -91,11 +92,11 @@ jobs:
EOF
chmod 600 ~/.ssh/config
# makepkg refuses to run as root, and CI containers are root by default.
- name: Create build user
# The `builder` user is baked into the image; this only hands it the
# workspace and the credentials written by the previous step.
- name: Hand over to the build user
run: |
set -euo pipefail
useradd --create-home --shell /bin/bash builder
chown -R builder:builder "${GITHUB_WORKSPACE}"
cp -r /root/.ssh /root/.gitconfig /root/.git-credentials /home/builder/ 2>/dev/null || true
chown -R builder:builder /home/builder