The 15min timeout was too short for Rust builds. Also checks for act_runner child processes (image pull, git clone phases) not just Docker containers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
178 lines
4.9 KiB
Smarty
178 lines
4.9 KiB
Smarty
#cloud-config
|
|
#
|
|
# Foxhunt CI Runner (act_runner for Gitea Actions)
|
|
# Tailscale-only access, native Docker (no DinD).
|
|
#
|
|
package_update: true
|
|
package_upgrade: true
|
|
|
|
packages:
|
|
- ca-certificates
|
|
- curl
|
|
- gnupg
|
|
|
|
write_files:
|
|
- path: /opt/act_runner/config.yaml
|
|
permissions: '0644'
|
|
content: |
|
|
log:
|
|
level: info
|
|
|
|
runner:
|
|
file: /opt/act_runner/.runner
|
|
capacity: ${runner_capacity}
|
|
timeout: 3h
|
|
insecure: false
|
|
fetch_timeout: 5s
|
|
fetch_interval: 2s
|
|
|
|
cache:
|
|
enabled: true
|
|
dir: /opt/act_runner/cache
|
|
|
|
container:
|
|
network: host
|
|
privileged: false
|
|
options:
|
|
valid_volumes: []
|
|
|
|
- path: /etc/systemd/system/act-runner.service
|
|
permissions: '0644'
|
|
content: |
|
|
[Unit]
|
|
Description=Gitea Actions Runner
|
|
After=docker.service tailscaled.service
|
|
Requires=docker.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=/opt/act_runner
|
|
ExecStart=/usr/local/bin/act_runner daemon --config /opt/act_runner/config.yaml
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
|
|
- path: /opt/act_runner/idle-shutdown.sh
|
|
permissions: '0755'
|
|
content: |
|
|
#!/bin/bash
|
|
# Shutdown instance after ${idle_timeout_minutes} minutes with no running CI jobs.
|
|
# Runs via systemd timer every 5 minutes.
|
|
IDLE_FILE="/tmp/ci-idle-since"
|
|
TIMEOUT_SEC=$((${idle_timeout_minutes} * 60))
|
|
|
|
# Check for running Docker containers
|
|
CONTAINERS=$(docker ps -q 2>/dev/null | wc -l)
|
|
if [ "$CONTAINERS" -gt 0 ]; then
|
|
rm -f "$IDLE_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
# Check if act_runner has child processes (actively running a job)
|
|
RUNNER_PID=$(pgrep -f 'act_runner daemon' 2>/dev/null)
|
|
if [ -n "$RUNNER_PID" ]; then
|
|
CHILDREN=$(pgrep -P "$RUNNER_PID" 2>/dev/null | wc -l)
|
|
if [ "$CHILDREN" -gt 0 ]; then
|
|
rm -f "$IDLE_FILE"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# No jobs running — record when idle started
|
|
if [ ! -f "$IDLE_FILE" ]; then
|
|
date +%s > "$IDLE_FILE"
|
|
logger -t idle-shutdown "CI runner idle, starting countdown"
|
|
exit 0
|
|
fi
|
|
|
|
IDLE_SINCE=$(cat "$IDLE_FILE")
|
|
NOW=$(date +%s)
|
|
IDLE_SECS=$((NOW - IDLE_SINCE))
|
|
|
|
if [ "$IDLE_SECS" -ge "$TIMEOUT_SEC" ]; then
|
|
logger -t idle-shutdown "Idle for $${IDLE_SECS}s (threshold $${TIMEOUT_SEC}s), shutting down"
|
|
# Clean up Docker to free disk space on next boot
|
|
docker system prune -af --volumes 2>/dev/null || true
|
|
rm -f "$IDLE_FILE"
|
|
poweroff
|
|
fi
|
|
|
|
- path: /etc/systemd/system/ci-idle-shutdown.service
|
|
permissions: '0644'
|
|
content: |
|
|
[Unit]
|
|
Description=Check CI runner idle state and shutdown if unused
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/opt/act_runner/idle-shutdown.sh
|
|
|
|
- path: /etc/systemd/system/ci-idle-shutdown.timer
|
|
permissions: '0644'
|
|
content: |
|
|
[Unit]
|
|
Description=Periodic idle check for CI runner auto-shutdown
|
|
|
|
[Timer]
|
|
OnBootSec=10min
|
|
OnUnitActiveSec=5min
|
|
AccuracySec=1min
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
|
|
runcmd:
|
|
# --- Docker CE ---
|
|
- install -m 0755 -d /etc/apt/keyrings
|
|
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
- chmod a+r /etc/apt/keyrings/docker.gpg
|
|
- |
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list
|
|
- apt-get update -qq
|
|
- apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin
|
|
- systemctl enable docker
|
|
- systemctl start docker
|
|
|
|
# --- Tailscale ---
|
|
- curl -fsSL https://tailscale.com/install.sh | sh
|
|
- tailscale up --authkey=${tailscale_auth_key} --hostname=${instance_name} --ssh
|
|
|
|
# Wait for Tailscale connectivity
|
|
- |
|
|
for i in $(seq 1 30); do
|
|
TS_IP=$(tailscale ip -4 2>/dev/null)
|
|
if [ -n "$TS_IP" ]; then
|
|
echo "Tailscale IP: $TS_IP"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# --- act_runner ---
|
|
- |
|
|
ACT_RUNNER_VERSION="0.3.0"
|
|
curl -fsSL "https://dl.gitea.com/act_runner/$${ACT_RUNNER_VERSION}/act_runner-$${ACT_RUNNER_VERSION}-linux-amd64" \
|
|
-o /usr/local/bin/act_runner
|
|
chmod +x /usr/local/bin/act_runner
|
|
|
|
# Register runner with Gitea (via Tailscale)
|
|
- |
|
|
cd /opt/act_runner
|
|
act_runner register --no-interactive \
|
|
--instance "${gitea_url}" \
|
|
--token "${runner_token}" \
|
|
--name "${instance_name}" \
|
|
--labels "${runner_labels}"
|
|
|
|
# Enable and start runner service
|
|
- systemctl daemon-reload
|
|
- systemctl enable act-runner
|
|
- systemctl start act-runner
|
|
|
|
# Enable idle auto-shutdown timer
|
|
- systemctl enable --now ci-idle-shutdown.timer
|
|
|
|
- echo "PROVISIONING_COMPLETE" > /var/log/cloud-init-done
|