graylog-deploy/setup-forgejo-runner.sh
byrsapty 3aaa1c96a8 Fix bash syntax error: apostrophe inside \${VAR:?message} broke brace matching
"the repo's Actions" inside FORGEJO_RUNNER_TOKEN's :? error message threw
off bash's parser even though the whole expression sits inside double
quotes - confirmed live via bisection (bash -n on truncated line ranges
pinpointed line 20 exactly, "unexpected EOF while looking for matching
`''`"). Single quotes inside \${VAR:?message} aren't neutralized by the
outer double quotes the way they would be in a plain string. Reworded to
avoid the apostrophe entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 22:51:51 +03:00

131 lines
5 KiB
Bash

#!/usr/bin/env bash
# Runs as root - either INSIDE the Graylog LXC container (via
# `pct exec <vmid> -- bash setup-forgejo-runner.sh`, for redeploying
# Graylog config) or directly ON the Proxmox host (for a runner that can
# do host-level work like create-graylog-lxc.sh - the container can't run
# that against itself). Idempotent: safe to re-run after a partial failure.
#
# Registers a Forgejo Actions self-hosted runner in "host" mode (no Docker -
# it runs shell steps directly on the machine it's installed on) and wires
# it up as a systemd service so it survives reboots.
#
# The registration token is a one-time credential from Forgejo itself, not
# something this script invents - get it from:
# Repo -> Settings -> Actions -> Runners -> "Create new Runner", or
# GET /api/v1/repos/<owner>/<repo>/actions/runners/registration-token
# (requires a personal access token with repo admin rights)
set -euo pipefail
FORGEJO_URL="${FORGEJO_URL:?Set FORGEJO_URL, e.g. https://git.zotac.keenetic.link}"
FORGEJO_RUNNER_TOKEN="${FORGEJO_RUNNER_TOKEN:?Set FORGEJO_RUNNER_TOKEN (registration token from the repo Settings -> Actions -> Runners page)}"
RUNNER_NAME="${RUNNER_NAME:-graylog-container-$(hostname)}"
RUNNER_LABEL="${RUNNER_LABEL:-self-hosted:host}"
RUNNER_VERSION="12.13.1"
RUNNER_DIR="${RUNNER_DIR:-/opt/forgejo-runner}"
SERVICE_NAME="${SERVICE_NAME:-forgejo-runner}"
# Empty (default) = service runs as root, appropriate INSIDE the container
# (its root is already scoped to just that container). On the Proxmox HOST
# this must be set to an unprivileged account (e.g. claude-deploy) - a
# root-owned systemd service with no User= would give every CI job full,
# unrestricted root on the host, defeating the whole point of claude-deploy's
# narrowly-scoped sudoers rules.
RUNNER_USER="${RUNNER_USER:-}"
if [ -t 2 ]; then
C_RESET=$'\033[0m'; C_CYAN=$'\033[36m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RED=$'\033[1;31m'
else
C_RESET=''; C_CYAN=''; C_GREEN=''; C_YELLOW=''; C_RED=''
fi
log() { echo "${C_CYAN}[setup-forgejo-runner]${C_RESET} $*" >&2; }
ok() { echo "${C_GREEN}[setup-forgejo-runner] ✓${C_RESET} $*" >&2; }
skip() { echo "${C_YELLOW}[setup-forgejo-runner] ⏭${C_RESET} $*" >&2; }
die() { echo "${C_RED}[setup-forgejo-runner] ✗ ERROR:${C_RESET} $*" >&2; exit 1; }
[ "$(id -u)" -eq 0 ] || die "must run as root inside the container (use pct exec)"
step_install_binary() {
if [ -x /usr/local/bin/forgejo-runner ] && /usr/local/bin/forgejo-runner --version 2>/dev/null | grep -q "$RUNNER_VERSION"; then
skip "forgejo-runner $RUNNER_VERSION already installed"
return
fi
log "Downloading and verifying forgejo-runner $RUNNER_VERSION..."
local base="https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}"
local tmp; tmp="$(mktemp -d)"
( cd "$tmp" \
&& curl -fsSL -o "forgejo-runner-${RUNNER_VERSION}-linux-amd64" "$base/forgejo-runner-${RUNNER_VERSION}-linux-amd64" \
&& curl -fsSL -o "forgejo-runner-${RUNNER_VERSION}-linux-amd64.sha256" "$base/forgejo-runner-${RUNNER_VERSION}-linux-amd64.sha256" \
&& sha256sum -c "forgejo-runner-${RUNNER_VERSION}-linux-amd64.sha256" )
chmod +x "$tmp/forgejo-runner-${RUNNER_VERSION}-linux-amd64"
mv "$tmp/forgejo-runner-${RUNNER_VERSION}-linux-amd64" /usr/local/bin/forgejo-runner
rm -rf "$tmp"
ok "installed forgejo-runner $RUNNER_VERSION"
}
step_register() {
mkdir -p "$RUNNER_DIR"
if [ -f "$RUNNER_DIR/.runner" ]; then
skip "runner already registered ($RUNNER_DIR/.runner exists)"
return
fi
log "Registering runner '$RUNNER_NAME' with $FORGEJO_URL (host mode, label $RUNNER_LABEL)..."
( cd "$RUNNER_DIR" && /usr/local/bin/forgejo-runner register --no-interactive \
--instance "$FORGEJO_URL" \
--token "$FORGEJO_RUNNER_TOKEN" \
--name "$RUNNER_NAME" \
--labels "$RUNNER_LABEL" )
ok "runner registered"
}
step_config() {
if [ -f "$RUNNER_DIR/config.yaml" ]; then
skip "config.yaml already exists"
return
fi
( cd "$RUNNER_DIR" && /usr/local/bin/forgejo-runner generate-config > config.yaml )
ok "generated config.yaml"
}
step_ownership() {
[ -n "$RUNNER_USER" ] || return
chown -R "$RUNNER_USER" "$RUNNER_DIR"
ok "chowned $RUNNER_DIR to $RUNNER_USER"
}
step_service() {
if systemctl is-active --quiet "$SERVICE_NAME"; then
skip "$SERVICE_NAME service already running"
return
fi
local user_line=""
[ -n "$RUNNER_USER" ] && user_line="User=$RUNNER_USER"
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=Forgejo Actions Runner ($RUNNER_NAME)
After=network.target docker.service
[Service]
Type=simple
$user_line
WorkingDirectory=$RUNNER_DIR
ExecStart=/usr/local/bin/forgejo-runner daemon --config $RUNNER_DIR/config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now "$SERVICE_NAME"
ok "$SERVICE_NAME service started and enabled${RUNNER_USER:+ (as $RUNNER_USER)}"
}
main() {
step_install_binary
step_register
step_config
step_ownership
step_service
}
main