Files
infrastructure/servers/gcp/runners/startup.sh
T
Will Anderson b3609d6401 fold in dev-env + ci-runner provisioning fixups
cloud-run.tf + cloud-run-stage.tf: small alignment edits from the
dev-env agent's work to match the actual deployed Cloud Run shape.

runners/startup.sh: 10-line additions from the gitea-runner agent
during initial provisioning - environment setup adjustments
discovered when the runner came up the first time.

stripe-billing.tf: prior-session work that hadn't been committed,
folding it in now to clean the working tree before further changes.
2026-05-02 13:21:32 -05:00

107 lines
4.0 KiB
Bash

#!/usr/bin/env bash
# servers/gcp/runners/startup.sh
#
# Boot script for the Gitea Actions runner GCE VM.
# - Installs docker + git + jq + the Forgejo/Gitea act_runner binary.
# - Pulls the runner registration token from GCP Secret Manager
# (the VM's attached service account has secretmanager.secretAccessor on it).
# - Registers against https://git.neuralplatform.ai with labels matching
# the workflows that target ubuntu-latest hosts.
# - Starts act_runner under systemd so it auto-recovers on reboot.
#
# Logs land at /var/log/runner-bootstrap.log for debugging from IAP SSH.
set -euxo pipefail
exec > >(tee /var/log/runner-bootstrap.log) 2>&1
apt-get update
# nodejs/npm needed for JavaScript actions like actions/checkout and
# google-github-actions/auth. python3 is for our inline label-rewrite below.
apt-get install -y curl ca-certificates docker.io git jq nodejs npm python3
# Make docker usable by the unprivileged runner user
systemctl enable --now docker
useradd -m -s /bin/bash runner || true
usermod -aG docker runner
# act_runner — pinned to a known-good release. Bump RUNNER_VERSION when
# upgrading. The project moved from gitea/act_runner to gitea/runner around
# the 0.6.x series; the binary inside the asset is still called act_runner.
# Latest releases at: https://gitea.com/gitea/runner/releases
RUNNER_VERSION="0.6.1"
curl -fsSL \
"https://gitea.com/gitea/runner/releases/download/v${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-amd64" \
-o /usr/local/bin/act_runner
chmod +x /usr/local/bin/act_runner
mkdir -p /opt/runner
chown -R runner:runner /opt/runner
# Pull registration token. Fail fast if the secret is missing — the runner
# is useless without it, and a silent fallback would leave us debugging an
# unregistered VM later.
GITEA_RUNNER_TOKEN="$(gcloud secrets versions access latest \
--secret=gitea-runner-token \
--project=neuron-785695)"
# Generate a baseline config so the daemon has a sane starting point. The
# register step writes the .runner state file into /opt/runner.
sudo -u runner /usr/local/bin/act_runner generate-config > /opt/runner/config.yaml
chown runner:runner /opt/runner/config.yaml
cd /opt/runner
sudo -u runner /usr/local/bin/act_runner register \
--no-interactive \
--instance https://git.neuralplatform.ai \
--token "${GITEA_RUNNER_TOKEN}" \
--name "gcp-us-central1-runner-1" \
--labels "ubuntu-latest,ubuntu-22.04,ubuntu-24.04"
# Belt-and-braces: act_runner v0.6 has a habit of wrapping bare labels with
# the default `:docker://docker.gitea.com/runner-images:<label>` shape on
# first daemon start. We want host execution (build-stage.sh uses the
# host's Docker socket), so re-write the labels in the persisted state
# file before we start the daemon.
python3 - <<'PY'
import json
p = "/opt/runner/.runner"
with open(p) as f: d = json.load(f)
d["labels"] = ["ubuntu-latest", "ubuntu-22.04", "ubuntu-24.04"]
with open(p, "w") as f: json.dump(d, f, indent=2)
PY
chown runner:runner /opt/runner/.runner
cat > /etc/systemd/system/act_runner.service <<'EOF'
[Unit]
Description=Gitea Actions runner (act_runner)
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=simple
User=runner
WorkingDirectory=/opt/runner
# Wipe the act cache on each daemon start. Without this, host-mode
# execution leaves stale action source trees with .git/objects/pack/*.idx
# files whose permissions trip the next run's `cp -a` step
# ("open ...idx: permission denied"). The cache is not load-bearing
# (act re-downloads actions on demand), so a clean start each restart
# is the simplest fix.
ExecStartPre=/bin/sh -c "rm -rf /home/runner/.cache/act/* /home/runner/.cache/act/.* 2>/dev/null || true"
ExecStart=/usr/local/bin/act_runner daemon --config /opt/runner/config.yaml
Restart=always
RestartSec=5
# Pass through GCP metadata-server access so workflows can call gcloud
Environment=HOME=/home/runner
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now act_runner
echo "act_runner registered and running"