#!/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 — 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 — the runner's name, empty when unregistered. forgejo_runner_name() { [ -e "$1/.runner" ] || return 0 json_field "$1/.runner" name } # forgejo_runner_secure — 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" } # assert_runner_instance # # Returns 0 when the box has no runner, or has one already registered to # : 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 }