magicciv/scripts/dev-setup/lib/runner.sh

147 lines
6.3 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# Shared forgejo-runner install/register/persist helpers for the
# per-OS dev-setup scripts. Sourced, never executed directly.
#
# Callers provide:
# - RUNNER_OS (darwin | linux)
# - RUNNER_ARCH (arm64 | amd64)
# - RUNNER_LABELS (comma-separated, e.g. "self-hosted,macos,arm64")
# - RUNNER_NAME (display name, e.g. $(hostname -s))
# Env required (load via scripts/run/common.sh cascade):
# - FORGEJO_HOST e.g. http://forge.black.lan
# - FORGEJO_ORG org the runner registers against
# - FORGEJO_RUNNER_TOKEN org-scoped registration token
#
# Callers (osx.sh / bluefin.sh / linux.sh) also decide the persistence
# layer — these helpers only handle the binary + registration step;
# each OS file installs its own launchd plist or systemd user unit.
set -euo pipefail
RUNNER_DIR="$HOME/.local/share/forgejo-runner"
# Binary path discovered at install time (brew on macOS, direct binary on Linux).
RUNNER_BIN=""
runner_require_env() {
local missing=()
for v in FORGEJO_HOST FORGEJO_ORG FORGEJO_RUNNER_TOKEN; do
[[ -z "${!v:-}" ]] && missing+=("$v")
done
if (( ${#missing[@]} > 0 )); then
echo " runner: missing required env: ${missing[*]}" >&2
echo " runner: set in .env.local — see .env.example" >&2
return 2
fi
}
# Install the runner binary and set RUNNER_BIN to its path.
# - macOS: Homebrew `act_runner` (the forgejo-compatible runner).
# (Forgejo's own release tarballs don't ship a darwin binary.)
# - Linux: direct download from Forgejo's latest tagged release.
runner_install_binary() {
mkdir -p "$RUNNER_DIR"
case "$RUNNER_OS" in
darwin)
if command -v act_runner >/dev/null 2>&1; then
RUNNER_BIN="$(command -v act_runner)"
echo " runner: using $RUNNER_BIN (already installed)"
return 0
fi
if ! command -v brew >/dev/null 2>&1; then
echo " runner: Homebrew required on macOS — install from https://brew.sh" >&2
return 1
fi
if ! command -v act_runner >/dev/null 2>&1; then
echo " runner: installing via Homebrew (act_runner)"
brew install act_runner
fi
RUNNER_BIN="$(command -v act_runner)"
# macOS Sequoia TCC Local Network requires a stable code-signing
# identifier. Homebrew ships `Identifier=a.out` (ad-hoc, generic)
# which TCC can't anchor → launchd-spawned runs get "no route to
# host" on port 3000 even when the same binary works in Terminal.
# Re-sign ad-hoc with a project identifier to make TCC's Local
# Network permission stick. Idempotent; re-run after brew upgrade.
if codesign -d --verbose "$RUNNER_BIN" 2>&1 | grep -q "Identifier=a.out"; then
echo " runner: re-signing with stable TCC identifier (com.forgejo.runner)"
codesign --force --sign - --identifier com.forgejo.runner "$RUNNER_BIN"
fi
;;
linux)
RUNNER_BIN="$HOME/.local/bin/forgejo-runner"
mkdir -p "$(dirname "$RUNNER_BIN")"
if [[ -x "$RUNNER_BIN" ]]; then
echo " runner: binary already present at $RUNNER_BIN"
return 0
fi
# Latest tagged release (not "nightly" — that download path doesn't exist).
local latest url
latest=$(curl -fsSL "https://code.forgejo.org/api/v1/repos/forgejo/runner/releases/latest" \
| python3 -c "import json,sys; print(json.load(sys.stdin)['tag_name'])")
latest="${latest#v}"
url="https://code.forgejo.org/forgejo/runner/releases/download/v${latest}/forgejo-runner-${latest}-${RUNNER_OS}-${RUNNER_ARCH}"
echo " runner: downloading $url"
curl -fsSL -o "$RUNNER_BIN.tmp" "$url"
chmod +x "$RUNNER_BIN.tmp"
mv "$RUNNER_BIN.tmp" "$RUNNER_BIN"
;;
*)
echo " runner: unknown RUNNER_OS=$RUNNER_OS" >&2
return 1
;;
esac
echo " runner: installed → $RUNNER_BIN"
}
# Register (or re-register) at org scope. Removes .runner file if the
# existing registration has different labels or a different name — this
# rebinds the runner to the org without manual DB intervention.
runner_register() {
runner_require_env || return $?
cd "$RUNNER_DIR"
if [[ -f .runner ]]; then
local existing_name existing_labels
existing_name=$(jq -r '.name // ""' .runner 2>/dev/null || echo "")
existing_labels=$(jq -c '.labels // []' .runner 2>/dev/null || echo "[]")
if [[ "$existing_name" == "$RUNNER_NAME" ]] && \
[[ "$existing_labels" == "[\"${RUNNER_LABELS//,/\",\"}\"]" ]]; then
echo " runner: already registered as '$RUNNER_NAME' with labels [$RUNNER_LABELS]"
return 0
fi
echo " runner: existing registration ($existing_name, labels=$existing_labels) differs — re-registering"
rm -f .runner
fi
"$RUNNER_BIN" register \
--no-interactive \
--instance "$FORGEJO_HOST" \
--token "$FORGEJO_RUNNER_TOKEN" \
--name "$RUNNER_NAME" \
--labels "$RUNNER_LABELS" >/dev/null
echo " runner: registered as '$RUNNER_NAME' at $FORGEJO_HOST/$FORGEJO_ORG (labels: $RUNNER_LABELS)"
}
# Verify the runner appears online via the Forgejo API within a short
# polling window. Uses FORGEJO_RUNNER_TOKEN only if also passed as a
# personal access token; otherwise requires FORGEJO_ADMIN_TOKEN to read
# /api/v1/user/actions/runners.
runner_verify_online() {
local tok="${FORGEJO_ADMIN_TOKEN:-${FORGEJO_TOKEN:-}}"
if [[ -z "$tok" ]]; then
echo " runner: skipping online-verify (no admin/personal token — set FORGEJO_ADMIN_TOKEN to enable)"
return 0
fi
local tries=0 online=""
while (( tries < 12 )); do
online=$(curl -fsS -H "Authorization: token $tok" \
"$FORGEJO_HOST/api/v1/orgs/$FORGEJO_ORG/actions/runners" 2>/dev/null \
| jq -r ".runners[] | select(.name==\"$RUNNER_NAME\") | .status" 2>/dev/null || echo "")
[[ "$online" == "online" ]] && { echo " runner: online"; return 0; }
tries=$((tries + 1))
sleep 2
done
echo " runner: NOT online after 24s; check service logs" >&2
return 1
}