diff --git a/.gitea/workflows/update.yaml b/.gitea/workflows/update.yaml index ded5264..2dcd21d 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 # rpmbuild runs as the unprivileged `builder` user inside a Fedora image; # the spec is built with `dist` undefined, so the noarch package is the @@ -49,8 +128,9 @@ jobs: # One broken package must not cancel the rest of the matrix. fail-fast: false matrix: - package: - - skeptical-updater + # 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, jq, curl and the packaging toolchain all @@ -106,12 +186,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 80f7628..4e5c444 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ in step. | `latest-version.sh` | resolves the latest upstream version (verbatim from `aur/.autoupdate`) | | `common.sh` | logging, guards, version compare, tarball fetch, registry API | | `dockerfile` | the Fedora toolchain image the nightly job runs in | -| `.gitea/workflows/update.yaml` | nightly matrix job | +| `.gitea/workflows/update.yaml` | nightly job: `plan` reads `UPDATE_PACKAGES`, `update` runs one matrix entry per package | | `.gitea/workflows/build-image.yaml` | monthly image rebuild | Nothing here is package-specific. Everything that differs between packages @@ -45,6 +45,33 @@ gui="false" # ignored here; AUR AppImage extraction only Start a new package at a placeholder such as `0.0.1`: the first nightly run bumps it to the newest tag. +## 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 `rpm` org +(Settings -> Actions -> Variables), which is the recommended place; a variable +of the same name on `rpm/.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: + +``` +- skeptical-updater +``` + +The workflow's `plan` job turns the variable into the `update` matrix, one job +per package. A manual run's `package` input (Actions -> Update RPM 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 `https://git.bjphoster.com/rpm/` and read `.autoupdate`. @@ -89,7 +116,7 @@ that points at the registry. | workflow input | variable | effect | |---|---|---| -| `package` | | run one matrix entry only (blank = all) | +| `package` | | run only this package instead of `UPDATE_PACKAGES` (blank = every listed package) | | `force_rebuild` | `FORCE_REBUILD=1` | bump `Release` even with no new upstream version; replaces a registry copy of that exact version-release if one exists | | `force_refresh` | `FORCE_REFRESH=1` | re-download the source and rebuild even when nothing changed (a check; uploads only what is missing) | | `no_git_push` | `NO_GIT_PUSH=1` | dry run: build, but neither upload nor push to origin | @@ -116,6 +143,7 @@ the `.autoupdate` repo itself: | variable | `GIT_NAME` | display name for commits and `%changelog`, e.g. `Bryan Joshua Pedini` | | variable | `GIT_EMAIL` | email for commits and `%changelog` | | variable | `REGISTRY_USER` | your Gitea **username** for the container registry login (not `GIT_NAME`, which has spaces) | +| variable | `UPDATE_PACKAGES` | the packages to update, one per line (see [Package list](#package-list)); currently `skeptical-updater` | | variable | `GIT_HOST` | optional; defaults to `git.bjphoster.com` | Also: @@ -129,7 +157,7 @@ Also: the registry entry to the repo; - build the image once by hand (Actions -> Build Autoupdater Image -> Run workflow) before the first nightly run, since `update.yaml` pulls it; -- list every package in the `matrix.package` of `.gitea/workflows/update.yaml`. +- list every package in `UPDATE_PACKAGES` ([Package list](#package-list)). A package repo that is not listed there is never processed. The token is read back by `autoupdate.sh` through `git credential fill`, from the @@ -139,7 +167,8 @@ never on a command line or in a log. ## Running by hand Actions -> Update RPM Packages -> Run workflow. Inputs: `package` (blank for -the whole matrix), `force_rebuild`, `force_refresh`, `no_git_push`. +every package in `UPDATE_PACKAGES`), `force_rebuild`, `force_refresh`, +`no_git_push`. ## Installing packages