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>
86 lines
3.9 KiB
Bash
Executable file
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}"
|