#!/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 }