2026-04-17 12:20:43 -07:00
|
|
|
#!/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.local
|
|
|
|
|
# - 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"
|
2026-04-17 12:35:54 -07:00
|
|
|
# Binary path discovered at install time (brew on macOS, direct binary on Linux).
|
|
|
|
|
RUNNER_BIN=""
|
2026-04-17 12:20:43 -07:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 12:35:54 -07:00
|
|
|
# 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.
|
2026-04-17 12:20:43 -07:00
|
|
|
runner_install_binary() {
|
2026-04-17 12:35:54 -07:00
|
|
|
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
|
2026-04-17 12:56:07 -07:00
|
|
|
if ! command -v act_runner >/dev/null 2>&1; then
|
|
|
|
|
echo " runner: installing via Homebrew (act_runner)"
|
|
|
|
|
brew install act_runner
|
|
|
|
|
fi
|
2026-04-17 12:35:54 -07:00
|
|
|
RUNNER_BIN="$(command -v act_runner)"
|
2026-04-17 12:56:07 -07:00
|
|
|
# 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
|
2026-04-17 12:35:54 -07:00
|
|
|
;;
|
|
|
|
|
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
|
2026-04-17 12:20:43 -07:00
|
|
|
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
|
|
|
|
|
}
|