2026-09-03 21:08:19 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# shellcheck shell=bash
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
##@Version : 202609030524-git
|
|
|
|
|
# @@Author : Jason Hempstead
|
|
|
|
|
# @@Contact : jason@casjaysdev.pro
|
|
|
|
|
# @@License : LICENSE.md
|
|
|
|
|
# @@ReadME : start-runners --help
|
|
|
|
|
# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments
|
|
|
|
|
# @@Created : Friday, Jun 05, 2026 18:14 EDT
|
|
|
|
|
# @@File : start-runners
|
|
|
|
|
# @@Description : Start act runners
|
|
|
|
|
# @@Changelog : New script
|
|
|
|
|
# @@TODO : Better documentation
|
|
|
|
|
# @@Other :
|
|
|
|
|
# @@Resource :
|
|
|
|
|
# @@Terminal App : no
|
|
|
|
|
# @@sudo/root : no
|
|
|
|
|
# @@Template : shell/bash
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
VERSION="202609030524-git"
|
|
|
|
|
set -e
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
trap 'retVal=$?; emoji=$( [ -z "$NO_COLOR" ] && echo "❌ " || echo "" ); \
|
|
|
|
|
echo "${emoji}Fatal error occurred: Exit code $retVal at line $LINENO in command: $BASH_COMMAND"; \
|
|
|
|
|
kill -TERM 1' ERR
|
|
|
|
|
trap 'retVal=$?;if [ "$SERVICE_IS_RUNNING" != "yes" ] && [ -f "$SERVICE_PID_FILE" ]; then rm -Rf "$SERVICE_PID_FILE"; fi;exit $retVal' SIGINT SIGTERM SIGPWR
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2026-10-05 10:21:09 -04:00
|
|
|
__help() {
|
|
|
|
|
cat <<'EOF'
|
|
|
|
|
Usage: start-runners
|
|
|
|
|
|
|
|
|
|
Register and start one or more Forgejo act_runner daemons in parallel.
|
|
|
|
|
|
|
|
|
|
Environment:
|
|
|
|
|
SERVER_ADDRESS Forgejo server URL (defaults to http://$HOSTNAME)
|
|
|
|
|
SERVER_TOKEN Required runner registration token
|
|
|
|
|
RUNNERS_LOG_DIR Runner log directory
|
|
|
|
|
RUNNERS_START Number of runners to start (default: 1)
|
|
|
|
|
RUNNER_NAME_PREFIX Runner name prefix (default: runner)
|
|
|
|
|
RUNNER_LABELS Comma-separated runner labels
|
|
|
|
|
RUNNERS_CONFIG_BASE Base act_runner config file
|
|
|
|
|
RUNNER_CACHE_HOST External cache host (used with RUNNER_CACHE_PORT and
|
|
|
|
|
RUNNER_CACHE_SECRET when no base config is available)
|
|
|
|
|
RUNNER_CACHE_PORT External cache port
|
|
|
|
|
RUNNER_CACHE_SECRET External cache secret
|
|
|
|
|
EOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "${1:-}" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
__help
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
2026-09-03 21:08:19 -04:00
|
|
|
# Function to __log messages with timestamp
|
|
|
|
|
__log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >>"$RUNNERS_LOG_DIR/runners" 2>&1; }
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Function to cleanup child processes on exit
|
|
|
|
|
__cleanup() {
|
|
|
|
|
__log "Shutting down runners..."
|
|
|
|
|
kill $(jobs -p) 2>/dev/null || true
|
|
|
|
|
wait
|
|
|
|
|
__log "All runners stopped"
|
|
|
|
|
}
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Set up signal handling
|
|
|
|
|
trap __cleanup SIGTERM SIGINT
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Validate required environment variables
|
|
|
|
|
if [ -n "$SERVER_ADDRESS" ]; then
|
|
|
|
|
if [[ "$SERVER_ADDRESS" != *"://"* ]]; then
|
|
|
|
|
SERVER_ADDRESS="http://$SERVER_ADDRESS"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
SERVER_ADDRESS=http://$HOSTNAME
|
|
|
|
|
fi
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
if [ -z "$SERVER_TOKEN" ]; then
|
|
|
|
|
__log "ERROR: SERVER_TOKEN environment variable is required"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
if [ -z "$RUNNER_LABELS" ]; then
|
|
|
|
|
_default_runner_labels=(
|
|
|
|
|
"linux:host"
|
|
|
|
|
"node14:docker://node:14"
|
|
|
|
|
"node16:docker://node:16"
|
|
|
|
|
"node18:docker://node:18"
|
|
|
|
|
"node20:docker://node:20"
|
|
|
|
|
"node22:docker://node:22"
|
|
|
|
|
"node:docker://node:latest"
|
|
|
|
|
"perl:docker://perl:latest"
|
|
|
|
|
"ruby:docker://ruby:latest"
|
|
|
|
|
"python:docker://python:latest"
|
|
|
|
|
"python3:docker://python:latest"
|
|
|
|
|
"php7:docker://casjaysdevdocker/php:7"
|
|
|
|
|
"php8:docker://casjaysdevdocker/php:8"
|
|
|
|
|
"php:docker://casjaysdevdocker/php:latest"
|
|
|
|
|
"alpine:docker://casjaysdev/alpine:latest"
|
|
|
|
|
"debian:docker://casjaysdev/debian:latest"
|
|
|
|
|
"ubuntu:docker://casjaysdev/ubuntu:latest"
|
|
|
|
|
"rhel:docker://casjaysdev/almalinux:latest"
|
|
|
|
|
"redhat:docker://casjaysdev/almalinux:latest"
|
|
|
|
|
"almalinux:docker://casjaysdev/almalinux:latest"
|
|
|
|
|
"act_runner:docker://catthehacker/ubuntu:full-latest"
|
|
|
|
|
"ubuntu-latest:docker://catthehacker/ubuntu:full-latest"
|
|
|
|
|
)
|
|
|
|
|
RUNNER_LABELS="$(
|
|
|
|
|
IFS=,
|
|
|
|
|
echo "${_default_runner_labels[*]}"
|
|
|
|
|
)"
|
|
|
|
|
unset _default_runner_labels
|
|
|
|
|
fi
|
|
|
|
|
# Determine number of runners to start
|
|
|
|
|
RUNNERS_START=${RUNNERS_START:-1}
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Validate RUNNERS_START is a positive integer
|
|
|
|
|
if ! [[ "$RUNNERS_START" =~ ^[0-9]+$ ]] || [ "$RUNNERS_START" -lt 1 ]; then
|
|
|
|
|
__log "WARNING: Invalid RUNNERS_START value '$RUNNERS_START', defaulting to 1"
|
|
|
|
|
RUNNERS_START=1
|
|
|
|
|
fi
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
__log "Starting $RUNNERS_START act_runner instance(s)"
|
|
|
|
|
__log "Server Address: $SERVER_ADDRESS"
|
|
|
|
|
__log "Runner Name Prefix: ${RUNNER_NAME_PREFIX:-runner}"
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2026-10-05 10:21:09 -04:00
|
|
|
# Full base config rendered by zz-act_runner.sh. Fall back to a shared cache-only
|
|
|
|
|
# config when this script is invoked without a rendered Forgejo runner config.
|
|
|
|
|
RUNNERS_DEFAULT_HOME="${RUNNER_DEFAULT_HOME:-/config/act_runner/forgejo}"
|
|
|
|
|
# Keep the established registration path by default; callers may override it
|
|
|
|
|
# independently when relocating runner state.
|
|
|
|
|
RUNNERS_REGISTRATION_ROOT="${RUNNER_REGISTRATION_ROOT:-/config/act_runner/reg}"
|
2026-09-03 21:08:19 -04:00
|
|
|
RUNNERS_CACHE_CONFIG=""
|
2026-10-05 10:21:09 -04:00
|
|
|
if [ -n "$RUNNERS_CONFIG_BASE" ] && [ -f "$RUNNERS_CONFIG_BASE" ]; then
|
|
|
|
|
__log "Base config: $RUNNERS_CONFIG_BASE"
|
|
|
|
|
elif [ -n "$RUNNER_CACHE_HOST" ] && [ -n "$RUNNER_CACHE_PORT" ] && [ -n "$RUNNER_CACHE_SECRET" ]; then
|
|
|
|
|
RUNNERS_CONFIG_BASE=""
|
2026-09-03 21:08:19 -04:00
|
|
|
RUNNERS_CACHE_CONFIG="/config/act_runner/runners-cache.yaml"
|
|
|
|
|
cat <<EOF >"$RUNNERS_CACHE_CONFIG"
|
2026-10-05 10:21:09 -04:00
|
|
|
runner:
|
|
|
|
|
file: REPLACE_RUNNER_STATE/.runner
|
2026-09-03 21:08:19 -04:00
|
|
|
cache:
|
|
|
|
|
enabled: true
|
|
|
|
|
external_server: 'http://${RUNNER_CACHE_HOST}:${RUNNER_CACHE_PORT}/'
|
2026-09-08 22:37:44 -04:00
|
|
|
secret: '${RUNNER_CACHE_SECRET}'
|
2026-09-03 21:08:19 -04:00
|
|
|
EOF
|
2026-10-05 10:21:09 -04:00
|
|
|
else
|
|
|
|
|
RUNNERS_CONFIG_BASE=""
|
2026-09-03 21:08:19 -04:00
|
|
|
fi
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2026-10-05 10:21:09 -04:00
|
|
|
# Create the config used by one runner, isolating its registration state.
|
|
|
|
|
__runner_config() {
|
|
|
|
|
local runner_dir=$1
|
|
|
|
|
local runner_config="$runner_dir/config.yaml"
|
|
|
|
|
if [ -n "$RUNNERS_CONFIG_BASE" ]; then
|
|
|
|
|
sed "s|^\([[:space:]]*\)file:.*|\1file: $runner_dir/.runner|" "$RUNNERS_CONFIG_BASE" >"$runner_config" || return 1
|
|
|
|
|
echo "$runner_config"
|
|
|
|
|
elif [ -n "$RUNNERS_CACHE_CONFIG" ]; then
|
|
|
|
|
sed "s|REPLACE_RUNNER_STATE|$runner_dir|" "$RUNNERS_CACHE_CONFIG" >"$runner_config" || return 1
|
|
|
|
|
echo "$runner_config"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2026-09-03 21:08:19 -04:00
|
|
|
# Register a single runner synchronously (no daemon start)
|
|
|
|
|
__register_runner() {
|
|
|
|
|
local runner_id=$1
|
|
|
|
|
local runner_name="${RUNNER_NAME_PREFIX:-runner}-${runner_id}"
|
2026-10-05 10:21:09 -04:00
|
|
|
local runner_dir="$RUNNERS_REGISTRATION_ROOT/${runner_name}"
|
2026-09-03 21:08:19 -04:00
|
|
|
local config_args=()
|
2026-10-05 10:21:09 -04:00
|
|
|
local runner_config=""
|
2026-09-03 21:08:19 -04:00
|
|
|
|
|
|
|
|
mkdir -p "$runner_dir"
|
|
|
|
|
[ -d "$runner_dir" ] && cd "$runner_dir" || return 1
|
2026-10-05 10:21:09 -04:00
|
|
|
runner_config="$(__runner_config "$runner_dir")" || return 1
|
|
|
|
|
[ -n "$runner_config" ] && config_args=(--config "$runner_config")
|
2026-09-03 21:08:19 -04:00
|
|
|
|
|
|
|
|
if [ ! -f "$runner_dir/.runner" ]; then
|
|
|
|
|
__log "Registering runner: $runner_name (ID: $runner_id)"
|
|
|
|
|
act_runner register "${config_args[@]}" --instance "$SERVER_ADDRESS" --token "$SERVER_TOKEN" --name "$runner_name" --labels "$RUNNER_LABELS" --no-interactive
|
|
|
|
|
if [ $? -ne 0 ]; then
|
|
|
|
|
__log "ERROR: Failed to register runner $runner_name"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
__log "Runner $runner_name registered"
|
|
|
|
|
}
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Start daemon for a single runner (called in background after all are registered)
|
|
|
|
|
__start_runner_daemon() {
|
|
|
|
|
local runner_id=$1
|
|
|
|
|
local runner_name="${RUNNER_NAME_PREFIX:-runner}-${runner_id}"
|
2026-10-05 10:21:09 -04:00
|
|
|
local runner_dir="$RUNNERS_REGISTRATION_ROOT/${runner_name}"
|
2026-09-03 21:08:19 -04:00
|
|
|
local config_args=()
|
2026-10-05 10:21:09 -04:00
|
|
|
local runner_config=""
|
2026-09-03 21:08:19 -04:00
|
|
|
|
|
|
|
|
[ -d "$runner_dir" ] && cd "$runner_dir" || return 1
|
2026-10-05 10:21:09 -04:00
|
|
|
runner_config="$(__runner_config "$runner_dir")" || return 1
|
|
|
|
|
[ -n "$runner_config" ] && config_args=(--config "$runner_config")
|
2026-09-03 21:08:19 -04:00
|
|
|
__log "Starting daemon for runner: $runner_name"
|
|
|
|
|
exec act_runner daemon "${config_args[@]}"
|
|
|
|
|
}
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Phase 1: register all runners sequentially so IDs are assigned in order
|
|
|
|
|
for i in $(seq 1 $RUNNERS_START); do
|
|
|
|
|
__register_runner "$i" || { __log "Aborting: registration failed for runner-$i"; exit 1; }
|
|
|
|
|
done
|
|
|
|
|
unset i
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
# Phase 2: start all daemons in parallel
|
2026-10-05 10:21:09 -04:00
|
|
|
runner_pids=()
|
2026-09-03 21:08:19 -04:00
|
|
|
for i in $(seq 1 $RUNNERS_START); do
|
|
|
|
|
(__start_runner_daemon "$i") &
|
2026-10-05 10:21:09 -04:00
|
|
|
runner_pids+=("$!")
|
2026-09-03 21:08:19 -04:00
|
|
|
done
|
|
|
|
|
unset i
|
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2026-10-05 10:21:09 -04:00
|
|
|
RUNNERS_ID="${runner_pids[*]}"
|
|
|
|
|
__log "All $RUNNERS_START runners launched"
|
|
|
|
|
__log "Process IDs: $RUNNERS_ID"
|
|
|
|
|
printf 'All %s runners launched: %s\n' "$RUNNERS_START" "$RUNNERS_ID"
|
|
|
|
|
# Wait for all background processes and preserve a failure status. A daemon that
|
|
|
|
|
# exits is reported as failed instead of being treated as successfully started.
|
|
|
|
|
runner_failures=0
|
|
|
|
|
for runner_pid in "${runner_pids[@]}"; do
|
|
|
|
|
if ! wait "$runner_pid"; then
|
|
|
|
|
runner_failures=$((runner_failures + 1))
|
|
|
|
|
__log "ERROR: Runner process $runner_pid exited unsuccessfully"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
if [ "$runner_failures" -gt 0 ]; then
|
|
|
|
|
__log "$runner_failures runner process(es) failed"
|
|
|
|
|
printf '%s\n' "$runner_failures runner process(es) failed" >&2
|
|
|
|
|
exit 1
|
2026-09-03 21:08:19 -04:00
|
|
|
fi
|