rig/docs/templates/ci-box/install.sh
cluade-reviewer-andresmgsl 1933b07fd4
Some checks failed
ci / check (pull_request) Has been cancelled
ci / install (pull_request) Has been cancelled
ci / db-integration (pull_request) Has been cancelled
labels / labels (pull_request) Has been cancelled
fix: honour --version, scope .rig-labels, close the checksum gate
Three defects from review !110, all the same family — a stated contract the
code did not keep.

--version was swallowed on the path this command exists for. The download
block skipped on mere presence, copying runner-install.sh's shape without its
justification: actions/runner SELF-UPDATES, so freezing it would only make
GitHub refuse its jobs. forgejo-runner does not self-update, so nothing else
ever moves the version — and a ci-box's template preinstalls the binary at
mint, which meant the documented deterministic-pin lever could never fire on a
ci-box. It now converges toward the pin, downward included, because a pin is
an instruction and not a floor; absent a pin an existing binary is left alone,
since chasing latest would make a re-run an unrequested upgrade. The decision
moved to runner_download_decision in the lib as a pure function: the first
attempt at a test here grepped for a log string and survived the logic being
disabled, which is exactly the weak test the review warned about. The binary
is now renamed into place rather than written over — the converge path runs
while the daemon is live, and in-place is ETXTBSY.

.rig-labels outlived the registration it described. The write had escaped the
registration branch, where runner-install.sh correctly keeps its copy, so a
plain re-run stamped this invocation's labels over a registration made with
different ones and status then reported confidently wrong labels while Forgejo
still held the originals. Scoped again, and an EXPLICIT --labels on a re-run
now warns that Forgejo owns labels from registration time rather than letting
the request evaporate silently.

The checksum gate failed open. A missing .sha256 warned and installed anyway,
contradicting both the README and the template's own comment about unverified
root downloads. The original reasoning — do not let an upstream layout change
break installs — reasons about the wrong failure: a layout change breaks the
BINARY url too, so "binary yes, checksum no" is the shape of an interfered
fetch, which is precisely what the checksum exists to catch. Both paths refuse
now, with no bypass flag; if upstream really moves its assets that is a rig PR
editing the URL, not an operator improvising past a security gate.

Tests: the checksum paths are now DRIVEN against a stub curl through the real
template install.sh — matching, missing, mismatched and empty — instead of
grepped, and all three fixes were mutation-checked by reverting each and
confirming the suite goes red.

739/31/43 pass, shellcheck clean.

forgejo#109

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 21:08:15 +00:00

86 lines
3.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# ci-box — the forgejo-runner binary. Run BY THE MECHANISM as root, with
# TENANT_USER/TENANT_HOME/TENANT_GROUP/ROLE exported.
#
# This lands the BINARY ONLY. Registration is deliberately not here: it needs a
# short-lived token from the Forgejo instance, and a tenant install is
# creds-free by contract — box auto-runs it at mint, holding nothing. The
# operator registers afterwards, out loud:
#
# box shell ci-box
# sudo rig forgejo-runner install --instance https://forgejo.example.com
#
# Same split as staging-box's tailnet join, for the same reason.
#
# Root-owned under /usr/local/bin rather than the tenant's home: unlike an
# agent CLI, this binary is run by a systemd unit as the tenant user, and a
# tenant-writable binary that root's unit executes is a trivial path to root
# inside the box.
set -euo pipefail
BIN=/usr/local/bin/forgejo-runner
if [ -x "$BIN" ]; then
exit 0
fi
case "$(uname -m)" in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
*) echo "ci-box install: unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac
# The latest release, resolved by following the releases/latest redirect — no
# API call, no token, no JSON to parse on a dependency-free guest. A pinned
# version belongs to `rig forgejo-runner install --version`, which is where an
# operator who needs a deterministic install already is; a pin baked into the
# registry would go stale in a repo nobody watches.
LATEST_URL="$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
https://code.forgejo.org/forgejo/runner/releases/latest)" \
|| { echo "ci-box install: could not resolve the latest forgejo-runner release" >&2; exit 1; }
VERSION="${LATEST_URL##*/}"
VERSION="${VERSION#v}"
case "$VERSION" in
""|*[!0-9.]*) echo "ci-box install: could not parse a version from ${LATEST_URL}" >&2; exit 1 ;;
esac
ASSET="forgejo-runner-${VERSION}-linux-${ARCH}"
URL="https://code.forgejo.org/forgejo/runner/releases/download/v${VERSION}/${ASSET}"
WORKDIR="$(mktemp -d)"
cleanup() { rm -rf "$WORKDIR"; }
trap cleanup EXIT
echo "ci-box install: downloading forgejo-runner ${VERSION} (${ARCH})"
curl -fsSL "$URL" -o "$WORKDIR/forgejo-runner" \
|| { echo "ci-box install: could not download ${URL}" >&2; exit 1; }
# Forgejo publishes a .sha256 beside each binary. Verifying it costs one
# request and makes the install auditable; this file executes as root inside
# every future mint, so an unverified download is the last thing it should do.
# An unfetchable checksum REFUSES — it does not warn and continue. The comment
# above says an unverified root download is the last thing this file should do,
# and an earlier draft then did exactly that whenever the .sha256 404'd. The
# binary downloading while its checksum does not is not an upstream layout
# change (that would break both URLs); it is the shape of an interfered fetch,
# which is the case the checksum exists to catch. Failing open here would hand
# an unverified root install, inside every future mint, to anyone who can block
# one URL.
if ! curl -fsSL "${URL}.sha256" -o "$WORKDIR/forgejo-runner.sha256" 2>/dev/null; then
echo "ci-box install: no published .sha256 for ${ASSET} — refusing to install an unverified binary that runs as root. The binary itself downloaded, so this is not an upstream layout change; check what is intercepting the fetch." >&2
exit 1
fi
WANT="$(tr -d '\r' < "$WORKDIR/forgejo-runner.sha256" | awk '{print $1}' | head -n1)"
GOT="$(sha256sum "$WORKDIR/forgejo-runner" | awk '{print $1}')"
if [ -z "$WANT" ]; then
echo "ci-box install: the published checksum for ${ASSET} is unreadable — refusing to install an unverified binary" >&2
exit 1
fi
if [ "$WANT" != "$GOT" ]; then
echo "ci-box install: checksum mismatch for ${ASSET}: published ${WANT}, downloaded ${GOT} — refusing to install" >&2
exit 1
fi
echo "ci-box install: checksum verified (${GOT})"
install -m 0755 -o root -g root "$WORKDIR/forgejo-runner" "$BIN"
echo "ci-box install: installed ${BIN}"