feat: Forgejo-native CI — a ci-box tenant and a forgejo-runner family
rig's CI story was GitHub-shaped end to end. This makes it work against a
self-hosted Forgejo, in three pieces.
The registry fetch becomes forge-aware. templates_resolve hardcoded three
github.com archive URLs; RIG_TEMPLATES_HOST now selects the grammar, because
the forges genuinely differ — GitHub serves refs/tags, refs/heads and bare
paths, Forgejo serves exactly one, and emitting the other two there would mean
two guaranteed 404s per fetch and a failure message listing URLs that never
could have worked. Measured against forgejo.heavyduty.builders, not inferred.
The default stays GitHub, so every existing caller is unchanged. install.sh's
snapshot reads the same variable through a byte-identical copy of the builder,
diffed by the tests: a snapshot cached from a forge converge would never fetch
from is worse than no snapshot, and the pin-in-the-name staleness guard cannot
catch a wrong-ORIGIN snapshot, only an old one.
ci-box is a tenant, not a machine role. The topology is a fleet machine
hosting boxes, one of which runs CI — a '-box' guest by rig's own family rule.
That also deletes the docker-in-docker layer the usual setup needs:
bootstrap-tenant.sh already installs Docker and adds the tenant user to the
group, and the isolation a privileged dind sidecar buys is already paid for by
a box that is network-isolated, inbound-less and disposable. rig runner
install refuses Docker for good reason — it converges a MACHINE, where the
blast radius is the machine. Here it is a guest that gets thrown away.
rig forgejo-runner is a new family beside rig runner, which is untouched.
Forgejo registers against an INSTANCE and the token carries the scope, so
there is no --repo to converge toward and nothing to compare; folding that
into one command would make every guard bimodal to share a flag name while the
contract underneath differs. assert_runner_instance asks the same
trust-boundary question about the axis Forgejo actually has. There is no
repoint and no --local, and both absences are explained where an operator
arriving from the GitHub sibling will hit them.
Forgejo's .runner holds the runner's own long-lived token, unlike GitHub's, so
it is installed 0600 and the mode is re-asserted on every converge — a mode
that drifted leaks the secret silently, since nothing fails and the runner
keeps working. status reports it and never prints the token.
Both downloads verify the published .sha256 before installing: this binary
lands as root and is executed by a systemd unit.
bootstrap --undo learns the guard for the same hazard on the other forge, and
it matters more here — Forgejo has no deregistration endpoint, so the ghost it
would strand has to be deleted by hand.
Known prerequisite, documented rather than assumed: the fetch is
unauthenticated by contract, and a Forgejo with REQUIRE_SIGNIN_VIEW=true
answers 404 for repos it reports as public. Hosting a registry there needs
FORGEJO__service__REQUIRE_SIGNIN_VIEW=false. The refusal names that case,
because it is indistinguishable from a wrong ref.
forgejo#109
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 20:40:54 +00:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Shared reader for the Forgejo runner's own on-disk config ($RUNNER_DIR/.runner).
|
|
|
|
|
# Sourced by the forgejo-runner-* commands; never executed on its own.
|
|
|
|
|
#
|
|
|
|
|
# WHY A SECOND LIB, not an arm inside lib/runner-config.sh: the two files are
|
|
|
|
|
# different documents making different claims, and the sibling's helpers answer
|
|
|
|
|
# questions this one cannot ask. GitHub's .runner names a REPOSITORY
|
|
|
|
|
# (gitHubUrl), so `runner install` converges toward --repo. Forgejo's names an
|
|
|
|
|
# INSTANCE (address) and nothing else about scope — whether a registration is
|
|
|
|
|
# instance-wide, org, or single-repo is a property of the TOKEN, decided in
|
|
|
|
|
# Forgejo's UI before rig ever sees it. There is no repo here to converge
|
|
|
|
|
# toward, and no way to read one back. Sharing a reader would mean a
|
|
|
|
|
# gitHubUrl accessor that returns empty forever on one of the two forges.
|
|
|
|
|
#
|
|
|
|
|
# json_field is deliberately re-used FROM the sibling rather than copied: a
|
|
|
|
|
# rig-bootstrapped box has no jq, both files are flat JSON, and one grep/sed
|
|
|
|
|
# reader for both is the same trade lib/runner-config.sh already argued.
|
|
|
|
|
|
|
|
|
|
HERE_FJ="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
# shellcheck source=SCRIPTDIR/runner-config.sh
|
|
|
|
|
. "$HERE_FJ/runner-config.sh" # json_field
|
|
|
|
|
|
|
|
|
|
# THE CREDENTIAL FACT that shapes this whole family: Forgejo's .runner holds
|
|
|
|
|
# the runner's own long-lived token — the secret it authenticates every poll
|
|
|
|
|
# with — alongside address/name/labels. GitHub's holds no such thing.
|
|
|
|
|
#
|
|
|
|
|
# So the mode is part of the contract, not hygiene: a registration secret
|
|
|
|
|
# readable by every account on the box is a quiet, permanent credential leak,
|
|
|
|
|
# and it leaks silently — nothing fails, the runner keeps working. Converge is
|
|
|
|
|
# the only moment rig can notice a mode that drifted (an operator's editor, a
|
|
|
|
|
# restore from a tarball that lost modes, a hand-edit to add a label).
|
|
|
|
|
FORGEJO_RUNNER_FILE_MODE=600
|
|
|
|
|
|
|
|
|
|
# forgejo_runner_instance <runner_dir> — the Forgejo instance this box's runner
|
|
|
|
|
# is registered to, empty when nothing is registered there.
|
|
|
|
|
forgejo_runner_instance() {
|
|
|
|
|
[ -e "$1/.runner" ] || return 0
|
|
|
|
|
json_field "$1/.runner" address
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# forgejo_runner_name <runner_dir> — the runner's name, empty when unregistered.
|
|
|
|
|
forgejo_runner_name() {
|
|
|
|
|
[ -e "$1/.runner" ] || return 0
|
|
|
|
|
json_field "$1/.runner" name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# forgejo_runner_secure <runner_dir> <user> <group> — converge .runner to 0600
|
|
|
|
|
# owned by the runner user. Called on every install, not only at registration.
|
|
|
|
|
# Silent on success: this is a mode that should always already be right, and a
|
|
|
|
|
# line saying so on every converge would train the reader to skip it.
|
|
|
|
|
forgejo_runner_secure() {
|
|
|
|
|
local dir="$1" user="$2" group="$3"
|
|
|
|
|
[ -e "$dir/.runner" ] || return 0
|
|
|
|
|
chmod "$FORGEJO_RUNNER_FILE_MODE" "$dir/.runner"
|
|
|
|
|
chown "$user:$group" "$dir/.runner"
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
# runner_version_of <bin> — the bare version number ("12.13.2") the binary
|
|
|
|
|
# reports, empty when it cannot answer. `forgejo-runner --version` prints
|
|
|
|
|
# "forgejo-runner version v12.13.2"; the leading v is stripped so this compares
|
|
|
|
|
# against a --version argument, which has its own v stripped at parse.
|
fix: the --version read cannot die ahead of its own refusal
Self-review of the whole head after the panel converged. One real defect,
reproduced verbatim before it was touched.
runner_version_of is a pipeline, and every forgejo-runner-* command runs under
set -euo pipefail. When the binary exits non-zero the pipeline fails, and
`PRESENT_VER="$(runner_version_of "$BIN")"` is an ASSIGNMENT — so install died
at that line, exit 1, printing nothing at all. The lib documented the opposite
("empty when it cannot answer"), and the refusal written for exactly this case
twenty lines later — "the download landed but cannot run" — was unreachable
code that could never have fired.
The shape is not hypothetical. `[ -x ]` yes, runnable no is what a truncated or
wrong-arch download leaves behind, and a ci-box's template preinstalls the
binary at mint — so the silent death sat on the one path this command family
exists to serve.
json_field, two files away, already carries `|| true` with this reasoning
written above it: "callers run under set -e with pipefail, where a grep that
matches nothing would otherwise kill the script with no message". The same
guard, for the same reason, at both sites: the lib reader and install's own
read of --version.
The test could not have caught it. vparse drove the lib through `bash -c` with
NO shell options, and its "bad" stub exits 0 — it proved garbage-in-empty-out
in a permissive shell, which is not where the function is used. vparse now runs
under set -euo pipefail, and a stub that exits non-zero is a third row.
Mutation-checked: that row exits 1 against the unpatched lib and 0 against the
fixed one. Install's site is grep-pinned, since reaching it for real needs root
and a downloaded binary.
748 passed (was 746), release 31, drill 43, shellcheck clean.
2026-07-28 09:26:17 +00:00
|
|
|
#
|
|
|
|
|
# `|| true` for json_field's reason, which bites harder here. Callers run under
|
|
|
|
|
# `set -euo pipefail`, where a pipeline whose FIRST stage exits non-zero fails
|
|
|
|
|
# the whole pipeline — and `PRESENT_VER="$(runner_version_of "$BIN")"` is an
|
|
|
|
|
# assignment, so the script dies AT THAT LINE, with no message. "Empty when it
|
|
|
|
|
# cannot answer" is only true if this says so out loud.
|
|
|
|
|
#
|
|
|
|
|
# That is not a hypothetical shape: the binary is `[ -x ]` but unrunnable
|
|
|
|
|
# exactly when a ci-box's template preinstall landed a truncated or wrong-arch
|
|
|
|
|
# download — the one path this command family exists for. Without this, install
|
|
|
|
|
# exits 1 in silence and the refusal written for that case ("the download
|
|
|
|
|
# landed but cannot run") is unreachable code.
|
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
|
|
|
runner_version_of() {
|
|
|
|
|
"$1" --version 2>/dev/null | head -n1 \
|
fix: the --version read cannot die ahead of its own refusal
Self-review of the whole head after the panel converged. One real defect,
reproduced verbatim before it was touched.
runner_version_of is a pipeline, and every forgejo-runner-* command runs under
set -euo pipefail. When the binary exits non-zero the pipeline fails, and
`PRESENT_VER="$(runner_version_of "$BIN")"` is an ASSIGNMENT — so install died
at that line, exit 1, printing nothing at all. The lib documented the opposite
("empty when it cannot answer"), and the refusal written for exactly this case
twenty lines later — "the download landed but cannot run" — was unreachable
code that could never have fired.
The shape is not hypothetical. `[ -x ]` yes, runnable no is what a truncated or
wrong-arch download leaves behind, and a ci-box's template preinstalls the
binary at mint — so the silent death sat on the one path this command family
exists to serve.
json_field, two files away, already carries `|| true` with this reasoning
written above it: "callers run under set -e with pipefail, where a grep that
matches nothing would otherwise kill the script with no message". The same
guard, for the same reason, at both sites: the lib reader and install's own
read of --version.
The test could not have caught it. vparse drove the lib through `bash -c` with
NO shell options, and its "bad" stub exits 0 — it proved garbage-in-empty-out
in a permissive shell, which is not where the function is used. vparse now runs
under set -euo pipefail, and a stub that exits non-zero is a third row.
Mutation-checked: that row exits 1 against the unpatched lib and 0 against the
fixed one. Install's site is grep-pinned, since reaching it for real needs root
and a downloaded binary.
748 passed (was 746), release 31, drill 43, shellcheck clean.
2026-07-28 09:26:17 +00:00
|
|
|
| sed -nE 's/.*[Vv]ersion[[:space:]]+v?([0-9][0-9A-Za-z.+-]*).*/\1/p' || true
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# runner_download_decision <have-binary yes|no> <present-ver> <wanted-ver>
|
|
|
|
|
# -> "install" | "skip" | "converge"
|
|
|
|
|
#
|
|
|
|
|
# A PURE function, and pure on purpose: this is the decision review !110 caught
|
|
|
|
|
# being wrong, and it was wrong in a way no grep could see. Lifting it out of
|
|
|
|
|
# the root-only install path is what makes "a pre-existing binary plus
|
|
|
|
|
# --version" a real test rather than a string match.
|
|
|
|
|
#
|
|
|
|
|
# The rule, and why it is not the GitHub sibling's:
|
|
|
|
|
#
|
|
|
|
|
# no binary -> install. Nothing to reason about.
|
|
|
|
|
# binary, no --version -> skip. Chasing "latest" on every converge would make
|
|
|
|
|
# a plain re-run an unrequested upgrade, and a
|
|
|
|
|
# convergent verb must not be a moving target.
|
|
|
|
|
# binary, pin matches -> skip.
|
|
|
|
|
# binary, pin differs -> CONVERGE, including downward. A pin is an
|
|
|
|
|
# instruction, not a floor.
|
|
|
|
|
#
|
|
|
|
|
# runner-install.sh skips on mere presence because actions/runner SELF-UPDATES,
|
|
|
|
|
# so its version moves regardless and freezing it would only make GitHub refuse
|
|
|
|
|
# the runner's jobs. forgejo-runner does not self-update: nothing else ever
|
|
|
|
|
# moves this version, and a ci-box's template preinstalls the binary at mint —
|
|
|
|
|
# so mere-presence here would leave --version dead on the one path this whole
|
|
|
|
|
# command exists to serve.
|
|
|
|
|
#
|
|
|
|
|
# An unreadable present version (empty) with a pin asked for falls to
|
|
|
|
|
# "converge", which is the right direction: a binary that cannot say what it is
|
|
|
|
|
# should be replaced by one that can.
|
|
|
|
|
runner_download_decision() {
|
|
|
|
|
local have="$1" present="$2" want="$3"
|
|
|
|
|
[ "$have" = yes ] || { printf 'install\n'; return 0; }
|
|
|
|
|
[ -n "$want" ] || { printf 'skip\n'; return 0; }
|
|
|
|
|
[ "$present" = "$want" ] && { printf 'skip\n'; return 0; }
|
|
|
|
|
printf 'converge\n'
|
|
|
|
|
}
|
|
|
|
|
|
feat: Forgejo-native CI — a ci-box tenant and a forgejo-runner family
rig's CI story was GitHub-shaped end to end. This makes it work against a
self-hosted Forgejo, in three pieces.
The registry fetch becomes forge-aware. templates_resolve hardcoded three
github.com archive URLs; RIG_TEMPLATES_HOST now selects the grammar, because
the forges genuinely differ — GitHub serves refs/tags, refs/heads and bare
paths, Forgejo serves exactly one, and emitting the other two there would mean
two guaranteed 404s per fetch and a failure message listing URLs that never
could have worked. Measured against forgejo.heavyduty.builders, not inferred.
The default stays GitHub, so every existing caller is unchanged. install.sh's
snapshot reads the same variable through a byte-identical copy of the builder,
diffed by the tests: a snapshot cached from a forge converge would never fetch
from is worse than no snapshot, and the pin-in-the-name staleness guard cannot
catch a wrong-ORIGIN snapshot, only an old one.
ci-box is a tenant, not a machine role. The topology is a fleet machine
hosting boxes, one of which runs CI — a '-box' guest by rig's own family rule.
That also deletes the docker-in-docker layer the usual setup needs:
bootstrap-tenant.sh already installs Docker and adds the tenant user to the
group, and the isolation a privileged dind sidecar buys is already paid for by
a box that is network-isolated, inbound-less and disposable. rig runner
install refuses Docker for good reason — it converges a MACHINE, where the
blast radius is the machine. Here it is a guest that gets thrown away.
rig forgejo-runner is a new family beside rig runner, which is untouched.
Forgejo registers against an INSTANCE and the token carries the scope, so
there is no --repo to converge toward and nothing to compare; folding that
into one command would make every guard bimodal to share a flag name while the
contract underneath differs. assert_runner_instance asks the same
trust-boundary question about the axis Forgejo actually has. There is no
repoint and no --local, and both absences are explained where an operator
arriving from the GitHub sibling will hit them.
Forgejo's .runner holds the runner's own long-lived token, unlike GitHub's, so
it is installed 0600 and the mode is re-asserted on every converge — a mode
that drifted leaks the secret silently, since nothing fails and the runner
keeps working. status reports it and never prints the token.
Both downloads verify the published .sha256 before installing: this binary
lands as root and is executed by a systemd unit.
bootstrap --undo learns the guard for the same hazard on the other forge, and
it matters more here — Forgejo has no deregistration endpoint, so the ghost it
would strand has to be deleted by hand.
Known prerequisite, documented rather than assumed: the fetch is
unauthenticated by contract, and a Forgejo with REQUIRE_SIGNIN_VIEW=true
answers 404 for repos it reports as public. Hosting a registry there needs
FORGEJO__service__REQUIRE_SIGNIN_VIEW=false. The refusal names that case,
because it is indistinguishable from a wrong ref.
forgejo#109
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 20:40:54 +00:00
|
|
|
# assert_runner_instance <runner_dir> <instance-url>
|
|
|
|
|
#
|
|
|
|
|
# Returns 0 when the box has no runner, or has one already registered to
|
|
|
|
|
# <instance-url>: re-running `install` against the instance the box is already
|
|
|
|
|
# on is real convergence — it re-uses the binary, skips registration, exits 0.
|
|
|
|
|
#
|
|
|
|
|
# Returns 1, explaining itself on stderr, when the runner is registered to a
|
|
|
|
|
# DIFFERENT instance. Skipping *that* is not convergence, it is ignoring the
|
|
|
|
|
# argument: `install` would skip its registration step, restart the service
|
|
|
|
|
# against the OLD instance, and report success — leaving the instance you asked
|
|
|
|
|
# for with no runner and its jobs queued against one that will never come.
|
|
|
|
|
#
|
|
|
|
|
# This is assert_runner_repo's reasoning, asked about the axis Forgejo actually
|
|
|
|
|
# has. There is deliberately no `repoint` sibling: Forgejo has no
|
|
|
|
|
# deregistration handshake to perform against the old instance, so moving a
|
|
|
|
|
# runner is `remove` then `install` — two acts that are already honest about
|
|
|
|
|
# leaving a stale entry behind, rather than one verb pretending to be atomic.
|
|
|
|
|
assert_runner_instance() {
|
|
|
|
|
local dir="$1" wanted="$2" current
|
|
|
|
|
[ -e "$dir/.runner" ] || return 0
|
|
|
|
|
|
|
|
|
|
current="$(forgejo_runner_instance "$dir")"
|
|
|
|
|
|
|
|
|
|
if [ -z "$current" ]; then
|
|
|
|
|
printf 'rig-forgejo-runner: ERROR: %s\n' \
|
|
|
|
|
"${dir}/.runner exists but names no instance — this box's registration cannot
|
|
|
|
|
be read, so rig cannot tell whether it is already on ${wanted}.
|
|
|
|
|
Wipe the local registration and install again:
|
|
|
|
|
rig forgejo-runner remove" >&2
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Trailing slashes are a spelling difference, not a different instance:
|
|
|
|
|
# forgejo-runner records the URL as given, so `--instance https://f.example/`
|
|
|
|
|
# and `--instance https://f.example` would otherwise read as a move.
|
|
|
|
|
if [ "${current%/}" = "${wanted%/}" ]; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
printf 'rig-forgejo-runner: ERROR: %s\n' \
|
|
|
|
|
"this box's runner is already registered to ${current}, not ${wanted}.
|
|
|
|
|
install will not move a runner between instances: it would leave the service
|
|
|
|
|
running against the OLD instance and report success. To move it, take it off
|
|
|
|
|
the old instance first:
|
|
|
|
|
rig forgejo-runner remove
|
|
|
|
|
then install against the new one. Forgejo has no deregistration handshake, so
|
|
|
|
|
the old entry stays listed until you delete it in that instance's admin UI." >&2
|
|
|
|
|
return 1
|
|
|
|
|
}
|