diff --git a/.gitea/workflows/update.yaml b/.gitea/workflows/update.yaml index 785c46f..fb16569 100644 --- a/.gitea/workflows/update.yaml +++ b/.gitea/workflows/update.yaml @@ -31,7 +31,86 @@ defaults: shell: bash jobs: + # Resolves the package list for the update matrix below, so adding or + # dropping a package is a variable edit on the org, not a commit here. + # See README "Package list". + plan: + runs-on: ubuntu-latest + outputs: + packages: ${{ steps.resolve.outputs.packages }} + steps: + - name: Resolve the package list + id: resolve + # Both go through the environment, never pasted into the script: the + # variable is free-form multi-line text. + env: + UPDATE_PACKAGES: ${{ vars.UPDATE_PACKAGES }} + PACKAGE_INPUT: ${{ inputs.package }} + run: | + set -euo pipefail + export LC_ALL=C + : "${UPDATE_PACKAGES:=}" "${PACKAGE_INPUT:=}" + re='^[A-Za-z0-9][A-Za-z0-9._+-]*$' + pkgs=() + declare -A seen=() + + # add NAME: validate, then append unless already listed. + add() { + if [[ ! $1 =~ $re ]]; then + echo "::error::${from}: invalid package name '$1' (letters, digits and . _ + - only, starting with a letter or digit)" >&2 + exit 1 + fi + if [ -z "${seen[$1]:-}" ]; then + seen[$1]=1 + pkgs+=("$1") + fi + } + + if [ -n "${PACKAGE_INPUT//[[:space:]]/}" ]; then + # workflow_dispatch with a package name runs only that one, + # whatever UPDATE_PACKAGES says. Surrounding whitespace is + # trimmed; the rest must be a single valid name. + from="workflow_dispatch input 'package'" + want=${PACKAGE_INPUT#"${PACKAGE_INPUT%%[![:space:]]*}"} + want=${want%"${want##*[![:space:]]}"} + add "${want}" + else + # "- a\n- b", "a\nb", "- a - b" and "a b" are the same list: + # comments go first, then every whitespace run (CR included, in + # case the value was saved with CRLF line ends) becomes a line + # break. Each line is then a name, a lone "-" list marker, or a + # "-" glued to a name. + from="UPDATE_PACKAGES" + while IFS= read -r tok; do + case ${tok} in + "" | -) continue ;; + -*) tok=${tok#-} ;; + esac + add "${tok}" + done <<< "$(printf '%s\n' "${UPDATE_PACKAGES}" | sed -e 's/#.*$//' | tr -s '[:space:]' '\n')" + fi + + # Fail rather than run nothing: an empty matrix would pass and look + # exactly like a night with nothing to update. + if [ "${#pkgs[@]}" -eq 0 ]; then + echo '::error::UPDATE_PACKAGES is not set: add an org or repo variable with one package per line (leading "- " optional)' >&2 + exit 1 + fi + + # Names are validated above, so none needs JSON escaping. + json= + for p in "${pkgs[@]}"; do + json+="${json:+,}\"${p}\"" + done + json="[${json}]" + + echo "${#pkgs[@]} package(s) from ${from}:" + printf ' - %s\n' "${pkgs[@]}" + echo "matrix: ${json}" + echo "packages=${json}" >> "${GITHUB_OUTPUT}" + update: + needs: plan runs-on: ubuntu-latest # A real Arch makepkg is required. Ubuntu's build environment silently # emits an empty .SRCINFO, which the AUR then rejects -- that is what broke @@ -50,13 +129,9 @@ jobs: # One broken package must not cancel the rest of the matrix. fail-fast: false matrix: - package: - - nethlink-appimage - - open-video-downloader-appimage - - open-video-downloader-bin - - pman-helper - - reflex-appimage - - tsparams + # One entry per package, resolved by the plan job above from + # vars.UPDATE_PACKAGES or the dispatch input. + package: ${{ fromJSON(needs.plan.outputs.packages) }} steps: # No bootstrap step: node, git, openssh, jq, curl and the makepkg @@ -127,12 +202,6 @@ jobs: NO_GIT_PUSH: ${{ (inputs.no_git_push == true || inputs.no_git_push == 'true') && '1' || '' }} run: | set -euo pipefail - # workflow_dispatch with a package name runs only that one. - want="${{ inputs.package }}" - if [ -n "${want}" ] && [ "${want}" != "${{ matrix.package }}" ]; then - echo "skipping ${{ matrix.package }} (dispatch asked for ${want})" - exit 0 - fi runuser -u builder -- env \ GIT_HOST="${GIT_HOST}" \ FORCE_REBUILD="${FORCE_REBUILD}" \ diff --git a/README.md b/README.md index 1d1fa22..f7ea164 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ three take the same path, and the AUR reconcile runs on every pass. | `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 matrix job | +| `.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. @@ -40,6 +40,42 @@ 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](#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. @@ -116,8 +152,8 @@ 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, all six -matrix jobs would run a full `pacman` transaction every night. +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