From cc5f478e7713d2d7036e2cbc3c043e5ed90f710f Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Sat, 1 Aug 2026 21:22:01 +0000 Subject: [PATCH 1/2] fix(ci): the check job installs the tools its image does not ship MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rig's own `ci / check` failed 9 times out of 27 ci.yml tasks on the first Forgejo runner, every one on `shellcheck: command not found`. `ubuntu-latest` maps to catthehacker's SLIM act image, and workflows written for GitHub reasonably assume GitHub's tool surface. Measured before choosing (2026-08-01, streaming ghcr layer blobs rather than pulling): the parity image is 18.67 GB on the wire and 54.52 GB extracted, against a box-class ci tenant with ~34-40 GB free. There is no cheap middle — runner-22.04 is the same slim class, tool for tool, and ships no shellcheck either. `apt-get install -y shellcheck` costs 7s and yields the same ShellCheck 0.8.0 that full-22.04 carries. So the slim default stays and the workflow equips itself. The `command -v` short-circuit keeps either forge from paying for the other; the sudo is a no-op on the act path and load-bearing on GitHub's. `ubuntu-latest-full` ships in the default map beside it: a mapping pulls nothing until a job matches it, and Forgejo freezes labels at registration, so a label absent then cannot be added without re-registering. A plain converge now warns when a runner carries a SUPERSEDED default — matched against the exact strings rig has shipped, so a map the operator chose stays silent. The message says plainly that nothing is broken. Refs #144 --- .github/workflows/ci.yml | 21 ++++++++ changelog.d/144.md | 7 +++ commands/forgejo-runner-install.sh | 86 +++++++++++++++++++++++++++--- test/cli.sh | 69 ++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 changelog.d/144.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96d98ba..a49021d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,27 @@ jobs: # a skip (a guard that can quietly stop guarding is the failure # shape these checks exist to refuse). fetch-depth: 0 + # GitHub's `ubuntu-latest` ships shellcheck. The image this forge maps + # that label to does not, and no cheap image does: measured 2026-08-01, + # catthehacker's slim `act-22.04` and `runner-22.04` both lack it, and + # the parity image that carries it is 54.5 GB extracted — more than a + # box-class ci tenant has (#144, the numbers live beside DEFAULT_LABELS + # in commands/forgejo-runner-install.sh). So the job equips itself + # rather than the label promising a tool surface it cannot deliver. + # + # The `command -v` short-circuit is what keeps either forge from paying + # for the other: on GitHub the tool is already there and no apt runs. + # Keep the sudo — it is a no-op on the Forgejo path (act jobs run as + # uid 0, and that image has no `runner` account at all) and load-bearing + # on the GitHub path, where the job runs as `runner` with passwordless + # sudo. It reads like dead weight and is not: it is what keeps this + # green on GitHub the day that image stops preinstalling shellcheck. + - name: shellcheck — preinstalled on GitHub, installed here + run: | + command -v shellcheck >/dev/null || { + sudo apt-get update && sudo apt-get install -y shellcheck + } + shellcheck --version - name: shellcheck # -x follows the `source=SCRIPTDIR/...` directives into commands/lib/. # globstar so a script in a new subdirectory is linted without anyone diff --git a/changelog.d/144.md b/changelog.d/144.md new file mode 100644 index 0000000..fedc1e0 --- /dev/null +++ b/changelog.d/144.md @@ -0,0 +1,7 @@ +### Fixed + +- CI's `check` job installs `shellcheck` where the image lacks it, so a runner on rig's defaults runs rig's own workflows (#144) + +### Added + +- `ubuntu-latest-full` in the default runner label map — GitHub's full tool surface, pulled only when a job asks for it (#144) diff --git a/commands/forgejo-runner-install.sh b/commands/forgejo-runner-install.sh index 902bff6..e7d5cf9 100755 --- a/commands/forgejo-runner-install.sh +++ b/commands/forgejo-runner-install.sh @@ -27,11 +27,65 @@ die() { printf 'rig-forgejo-runner: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; } # editing to migrate; catthehacker's image is the act/Forgejo ecosystem's # stand-in for GitHub's runner image. `docker` is the lean second option. # -# Both are `docker://` — jobs run in CONTAINERS on the box's own dockerd, not -# on the box itself. No docker-in-docker: the guide this came from stacks a +# Every entry is `docker://` — jobs run in CONTAINERS on the box's own dockerd, +# not on the box itself. No docker-in-docker: the guide this came from stacks a # privileged dind sidecar with a plaintext tcp://…:2375 daemon to isolate jobs # from a shared CI server, and inside a box that boundary is already paid for. -DEFAULT_LABELS='ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-22.04,docker:docker://node:22-bookworm' +# +# `ubuntu-latest` is the SLIM act image and does not carry GitHub's tool +# surface. That is a deliberate choice, ruled on 2026-08-01 (#144) after rig's +# own `ci / check` failed 9 times on `shellcheck: command not found`. The +# measurement, so nobody re-litigates it from the tag names: +# +# image on the wire extracted shellcheck +# act-22.04 (this one) 0.55 GB 2.2 GB no +# runner-22.04 0.55 GB 2.2 GB no <- not a middle +# full-22.04 18.67 GB 54.52 GB yes +# +# There is no cheap parity image: `runner-22.04` sounds like one and is the +# same slim class, tool for tool. Parity is 54.52 GB or nothing, and a +# box-class ci tenant does not have it — so mapping `ubuntu-latest` to +# `full-22.04` would be a default that cannot land on the host class it is +# for. `apt-get install -y shellcheck` takes 7s and yields the SAME +# ShellCheck 0.8.0 that `full-22.04` ships, so parity buys no newer tool +# either. +# +# The rule that follows, and the one to state when a workflow surprises +# someone: on this forge a workflow must not assume tools from the image. +# `ubuntu-latest` means "a Linux container that runs GitHub-shaped +# workflows", not "GitHub's runner image" — rig's own .github/workflows/ci.yml +# installs what it uses, and that is the pattern to copy. +# +# `ubuntu-latest-full` is the escape hatch for anyone who does want the whole +# GitHub tool surface. A label mapping pulls NOTHING until a job matches it, +# so shipping it by default costs a box that never says `runs-on: +# ubuntu-latest-full` exactly nothing — and it must ship by default, because +# Forgejo freezes labels at registration and a label absent at that moment +# cannot be added later without re-registering the runner. Budget ~120 GB of +# disk on any box that intends to use it. +DEFAULT_LABELS='ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-22.04,ubuntu-latest-full:docker://ghcr.io/catthehacker/ubuntu:full-22.04,docker:docker://node:22-bookworm' + +# Every label map rig has ever shipped as its DEFAULT, oldest first. Append the +# outgoing string here whenever DEFAULT_LABELS changes; never edit or remove a +# row, because the whole point is to recognise a runner registered long ago. +# +# This exists so a plain converge can tell "registered under an older rig" from +# "the operator chose these labels" — the second must stay silent, and nothing +# on disk distinguishes them except the string itself. +SUPERSEDED_DEFAULTS=( + # pre-#144: no ubuntu-latest-full escape hatch + 'ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-22.04,docker:docker://node:22-bookworm' +) + +# labels_are_a_superseded_default — true when the recorded map is one +# rig itself used to ship. Exact match only: a near-miss is an operator's map. +labels_are_a_superseded_default() { + local recorded="$1" past + for past in "${SUPERSEDED_DEFAULTS[@]}"; do + [ "$recorded" = "$past" ] && return 0 + done + return 1 +} # fetch_and_verify_sha256