Files
.autoupdate/README.md
T

9.0 KiB

AUR Auto Update

Nightly reconciler that keeps the aur/* package repos and their AUR counterparts in sync.

This is a reconciler, not a version-bump script. A new upstream release is only one of the things that can make a package diverge from the AUR; a manual push to origin is another, and a push that failed last night is a third. All three take the same path, and the AUR reconcile runs on every pass.

Layout

file role
autoupdate.sh entry point; orchestrates one or more packages
latest-version.sh resolves the latest upstream version (GitHub / Gitea)
update-checksums.sh downloads sources, computes sha256, rewrites the arrays
sync-aur.sh reconciles the repo against the AUR
common.sh logging, guards, PKGBUILD parsing and array splicing
test.sh hermetic test suite
.gitea/workflows/update.yaml nightly job: plan reads UPDATE_PACKAGES, update runs one matrix entry per package

Nothing here is package-specific. Everything that differs between packages lives in a .autoupdate manifest committed to the package repo itself.

Per-package manifest

Each package repo carries a .autoupdate file:

srchost="github"           # github | gitea
srcmntr="Sunhaiy"          # upstream owner/org
srcname="Reflex"           # upstream repo
verstrip="v"               # tag prefix to strip ("app-v" for open-video-downloader-*)
gui="true"                 # extract .desktop + icon from the AppImage
desktop="reflex.desktop"   # name *inside* the AppImage
icon="reflex.png"

It rides along to the AUR on push, as README.md and LICENSE already do. Harmless: the AUR reads only PKGBUILD and .SRCINFO.

Package list

The nightly job updates the packages named in the Gitea Actions variable UPDATE_PACKAGES; nothing in this repo lists them. Set it on the aur org (Settings -> Actions -> Variables), which is the recommended place; a variable of the same name on aur/.autoupdate itself overrides the org one.

One package per line. A leading - is optional, so YAML-list lines and bare names both work and can be mixed; blank lines and # comments (whole-line or trailing) are ignored, and duplicates are dropped. A name must match ^[A-Za-z0-9][A-Za-z0-9._+-]*$, otherwise the run fails naming it. The current value:

- nethlink-appimage
- open-video-downloader-appimage
- open-video-downloader-bin
- pman-helper
- reflex-appimage
- tsparams

Add - skeptical-updater once source/skeptical-updater has its first tag. Until then its version lookup finds no tag, which is a failed lookup (see Exit codes) and would turn that matrix job red every night.

The workflow's plan job turns the variable into the update matrix, one job per package. A manual run's package input (Actions -> Update AUR Packages -> Run workflow) overrides the variable and runs that one package only, listed or not. An unset or empty variable fails the run on purpose: an empty matrix would pass and look exactly like a night with nothing to update.

This needs Gitea 28.0 or newer, the first release that expands a matrix from another job's outputs. Older servers expand it before plan has run, start a single update job with an empty package name, and that job fails.

What a run does

  1. Clone the package repo, read its manifest.
  2. Fetch the AUR over HTTPS as the reference state. This happens before anything else, because pkgrel is derived from it. Read-only, so no SSH key is required for a dry run.
  3. Resolve the upstream version. A failed lookup exits 1; "no new version" is not a failure.
  4. If the version moved, re-download and re-hash the remote sources.
  5. Always re-hash local sources (.desktop, icons) and regenerate .SRCINFO. Local hashing needs no network, so this is free, and it is what lets a hand-edited repo self-correct.
  6. Derive pkgrel (below).
  7. Validate .SRCINFO; abort before committing if it is empty or disagrees with the PKGBUILD.
  8. Commit only if something actually changed.
  9. Push to the AUR first, then origin, so a failed AUR push leaves origin clean.

pkgrel

X.Y.Z-N — everything before the dash is upstream, -N is packaging-only revisions. You should never need to touch it by hand.

pkgrel is derived from the AUR, because that is what -N is numbered against:

  • version differs from the published one → pkgrel = 1
  • same version, packaging content differs → pkgrel = AUR's + 1
  • same version, packaging identical → unchanged
  • you already bumped it yourself → respected, not bumped again

Using the AUR as the reference rather than local history is what makes this idempotent: once the bump is published, the next night sees no difference and does nothing. There is a test for exactly that (pkgrel stable on re-run).

An edit to a local source such as an icon counts as a packaging change, because its sha256sums entry is regenerated first. A README-only edit does not.

Environment

variable effect
NO_GIT_PUSH=1 dry run: do everything except push
FORCE_REBUILD=1 bump pkgrel even with no detected change
FORCE_REFRESH=1 re-download remote sources without a version change
GIT_HOST git host holding the package repos
GITHUB_TOKEN optional; avoids GitHub's 60/hour anonymous rate limit

There is deliberately no force-push escape hatch. The AUR accepts fast-forwards only and refuses force pushes from anyone who is not an AUR administrator, so such a flag could never do anything except recommend a remedy that always fails. sync-aur.sh checks fast-forward status before pushing and, on failure, prints git's actual error and names a cause that fits it — a rejected SSH key, an unreachable host, or a genuine divergence — rather than assuming.

Exit codes

"No new upstream version" is the overwhelmingly common outcome and exits 0. The trap the previous implementation fell into was giving that same silent exit 0 to a lookup that had broken — which is how pman-helper shipped pkgver="" unnoticed. A failed lookup now exits 1.

Requirements

Needs a genuine Arch makepkg. Ubuntu's build environment emits an empty .SRCINFO, which the AUR rejects — this silently broke three packages. The CI job therefore runs in an Arch container as a non-root user (makepkg refuses to run as root), and regen_srcinfo in common.sh fails loudly if the output is ever empty again.

autoupdate.sh calls require_tools on startup, so a missing binary fails immediately instead of taking a silently wrong branch further in.

Container image

dockerfile builds the environment the nightly job runs in, published to the Gitea registry as git.bjphoster.com/aur/autoupdate:latest. Without it, every matrix job would run a full pacman transaction every night.

.gitea/workflows/build-image.yaml rebuilds and overwrites :latest monthly (Arch is rolling, so a month-old image is a month behind), on any push touching the dockerfile, and on demand via workflow_dispatch. That job runs on plain ubuntu-latest — it only drives docker; the image is the Arch part.

Based on archlinux:base, not base-devel: the autoupdater only ever parses PKGBUILDs and never compiles, so the toolchain is dead weight — 822 MB against 1.48 GB. Add it back if a real build or verify step is ever introduced.

Building locally:

docker build -t git.bjphoster.com/aur/autoupdate:latest .
docker run --rm -v "$PWD:/src:ro" git.bjphoster.com/aur/autoupdate:latest \
  bash -c 'cp -r /src /w && chown -R builder:builder /w && cd /w &&
           runuser -u builder -- ./test.sh'

Running the suite inside the image is worth doing after any dockerfile change. Trimming to archlinux:base originally dropped diffutils, and the missing diff made every run look like a packaging change and ratchet pkgrel upward — the host tests passed throughout. pkgrel no longer depends on diff at all, but the lesson stands.

Registry credentials

Needs vars.REGISTRY_USER — your Gitea username, not vars.GIT_NAME, which is the display name used for commit authorship and contains spaces. The token is the existing secrets.GIT_TOKEN, which needs write:package scope.

Notes on checksums

sha256 only; sha1sums/md5sums are actively removed.

Checksums are computed directly rather than via makepkg -g / updpkgsums. Those generate every arch array in one pass and download all arches into a single directory, so when two arches rename to the same target (foo.AppImage::…-amd64, foo.AppImage::…-arm64) the second finds the first one's file already there and emits an identical, silently wrong checksum. Overriding CARCH does not help. Downloads here go to content-addressed cache paths instead, never to the :: rename target. Multi-arch is supported for when a package needs it; single-arch is the degenerate case of the same loop.

Tests

./test.sh            # all
./test.sh pkgrel     # filter by name

Runs against local bare repos and a local HTTP server — no network, and it never touches the real AUR. Requires a real makepkg.