Repository navigation
Expand file tree
/
Copy pathentrypoint.sh
More file actions
executable file
·135 lines (118 loc) · 5.31 KB
/
Copy pathentrypoint.sh
File metadata and controls
executable file
·135 lines (118 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env bash
# ============================================================================
# ActionForge — Container Entrypoint
# A FlutterPlaza Open-Source Product | https://flutterplaza.com
# - Auto-registers with GitHub on startup
# - Runs in ephemeral mode (clean slate per job)
# - Deregisters on graceful shutdown
# ============================================================================
set -euo pipefail
# ── Resolve API endpoint ─────────────────────────────────────────────────────
if [[ -n "${GH_REPO:-}" ]]; then
TOKEN_URL="https://api.github.com/repos/${GH_ORG}/${GH_REPO}/actions/runners/registration-token"
REMOVE_URL="https://api.github.com/repos/${GH_ORG}/${GH_REPO}/actions/runners/remove-token"
CONFIG_URL="https://github.com/${GH_ORG}/${GH_REPO}"
else
TOKEN_URL="https://api.github.com/orgs/${GH_ORG}/actions/runners/registration-token"
REMOVE_URL="https://api.github.com/orgs/${GH_ORG}/actions/runners/remove-token"
CONFIG_URL="https://github.com/${GH_ORG}"
fi
LABELS="${RUNNER_LABELS:-ubuntu-latest,ubuntu-22.04,ubuntu-24.04,self-hosted,linux,x64}"
RUNNER_NAME="${RUNNER_NAME:-$(hostname)}"
# ── Get registration token ──────────────────────────────────────────────────
# Stores the full API response in GET_TOKEN_RESPONSE for error reporting.
GET_TOKEN_RESPONSE=""
get_token() {
local url="$1"
GET_TOKEN_RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer ${GH_PAT}" \
-H "Accept: application/vnd.github+json" \
"$url")
echo "$GET_TOKEN_RESPONSE" | jq -r '.token'
}
# ── Graceful shutdown — wait for job, then deregister ────────────────────────
RUNNER_PID=""
cleanup() {
echo "Caught signal — waiting for current job to finish..."
if [[ -n "$RUNNER_PID" ]] && kill -0 "$RUNNER_PID" 2>/dev/null; then
# Forward SIGTERM to the runner process — it will finish the current
# job step before exiting. Do NOT skip this: killing run.sh immediately
# would abort a user's CI job mid-run.
kill -TERM "$RUNNER_PID" 2>/dev/null || true
wait "$RUNNER_PID" 2>/dev/null || true
fi
echo "Runner process exited — deregistering..."
local remove_token
remove_token=$(get_token "$REMOVE_URL")
./config.sh remove --token "$remove_token" 2>/dev/null || true
exit 0
}
trap cleanup SIGTERM SIGINT SIGQUIT
# ── Remove stale config from previous run (container restart) ────────────────
if [[ -f .runner ]]; then
echo "Removing stale runner configuration..."
REMOVE_TOKEN=$(get_token "$REMOVE_URL")
./config.sh remove --token "$REMOVE_TOKEN" 2>/dev/null || true
fi
# ── Clean the work directory (ephemeral clean-slate) ─────────────────────────
# The runner registers `--ephemeral` (one job per container start), but Docker's
# `restart: unless-stopped` RESTARTS the same container rather than recreating
# it — so _work (checkouts, the tool cache, build output — often GBs per job)
# would otherwise accumulate across restarts and eventually fill the host's
# Docker disk (ENOSPC, e.g. a mid-job "no space left on device" while unpacking
# an SDK). Wiping it on every start makes each job the clean slate the ephemeral
# model already promises. Set ACTIONFORGE_KEEP_WORK=1 to opt out (e.g. to keep a
# tool cache across jobs), accepting the unbounded-disk risk.
if [[ "${ACTIONFORGE_KEEP_WORK:-0}" != "1" ]]; then
echo "Cleaning work directory for a clean-slate job..."
rm -rf _work 2>/dev/null || true
fi
# ── Register (with retry + exponential backoff) ──────────────────────────────
MAX_RETRIES="${MAX_RETRIES:-5}"
RETRY_DELAY=10
FAIL_COOLDOWN="${FAIL_COOLDOWN:-300}" # 5 min sleep before exit to prevent restart storm
REG_TOKEN=""
attempt=1
delay=$RETRY_DELAY
while [[ $attempt -le $MAX_RETRIES ]]; do
echo "Registering runner '${RUNNER_NAME}' with ${CONFIG_URL} (attempt ${attempt}/${MAX_RETRIES})..."
REG_TOKEN=$(get_token "$TOKEN_URL")
if [[ -n "$REG_TOKEN" && "$REG_TOKEN" != "null" ]]; then
break
fi
echo "Token request failed."
if [[ $attempt -lt $MAX_RETRIES ]]; then
echo "Retrying in ${delay}s..."
sleep "$delay"
delay=$((delay * 2))
fi
attempt=$((attempt + 1))
done
if [[ -z "$REG_TOKEN" || "$REG_TOKEN" == "null" ]]; then
echo ""
echo "ERROR: Failed to get registration token after ${MAX_RETRIES} attempts."
echo ""
echo "API response: ${GET_TOKEN_RESPONSE}"
echo ""
echo "Check that GH_PAT has the correct scopes:"
echo " • repo (for repo-level runners)"
echo " • admin:org → manage_runners:org (for org-level runners)"
echo ""
echo "Sleeping ${FAIL_COOLDOWN}s before exit to prevent restart storm..."
sleep "$FAIL_COOLDOWN"
exit 1
fi
./config.sh \
--url "$CONFIG_URL" \
--token "$REG_TOKEN" \
--name "$RUNNER_NAME" \
--labels "$LABELS" \
--work "_work" \
--replace \
--unattended \
--ephemeral
echo "Runner registered. Waiting for jobs..."
# ── Run (ephemeral = exits after one job, Docker restarts it fresh) ──────────
./run.sh &
RUNNER_PID=$!
wait "$RUNNER_PID"