feat: rework runner config to INI format with full field support
Replace pipe-delimited runners.conf with INI-style sections supporting host resolution, container images, repo-scoped tokens, resource limits, capacity, and SSH key passthrough. All defaults pulled from .env. - Add INI parsing helpers (ini_list_sections, ini_get, ini_set) to common.sh - Add SSH key support (UNRAID_SSH_KEY, FEDORA_SSH_KEY) to ssh_exec/scp_to - Add .env vars: RUNNER_DEFAULT_IMAGE, RUNNER_DEFAULT_CAPACITY, RUNNER_DEFAULT_DATA_PATH, LOCAL_RUNNER_DATA_PATH, LOCAL_REGISTRY - Rewrite manage_runner.sh with host/image/token resolution and resource limits - Rewrite configure_runners.sh wizard for INI format with all 9 fields - Update phase3 scripts to use ini_list_sections instead of pipe parsing - Add runners.conf INI validation to preflight.sh (check 5b) - Update templates to use resolved labels, capacity, and deploy resources Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
191
preflight.sh
191
preflight.sh
@@ -99,6 +99,122 @@ if [[ ! -f "${SCRIPT_DIR}/runners.conf" ]]; then
|
||||
log_error " → runners.conf not found. Copy runners.conf.example to runners.conf."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Check 5b: runners.conf INI format validation
|
||||
# Validates required fields, enum values, and format per runner section.
|
||||
# ---------------------------------------------------------------------------
|
||||
# shellcheck disable=SC2329
|
||||
check_runners_ini_format() {
|
||||
local conf="${SCRIPT_DIR}/runners.conf"
|
||||
[[ -f "$conf" ]] || return 1
|
||||
|
||||
local errors=0
|
||||
local section
|
||||
|
||||
while IFS= read -r section; do
|
||||
[[ -z "$section" ]] && continue
|
||||
|
||||
# Required fields
|
||||
local host type data_path labels capacity repos
|
||||
host=$(ini_get "$conf" "$section" "host" "")
|
||||
type=$(ini_get "$conf" "$section" "type" "")
|
||||
data_path=$(ini_get "$conf" "$section" "data_path" "")
|
||||
labels=$(ini_get "$conf" "$section" "labels" "")
|
||||
capacity=$(ini_get "$conf" "$section" "capacity" "")
|
||||
repos=$(ini_get "$conf" "$section" "repos" "all")
|
||||
|
||||
# host: must be unraid, fedora, local, or custom
|
||||
case "$host" in
|
||||
unraid|fedora|local|custom) ;;
|
||||
"")
|
||||
log_error " → [$section] host is empty (must be unraid, fedora, local, or custom)"
|
||||
errors=$((errors + 1))
|
||||
;;
|
||||
*)
|
||||
log_error " → [$section] host='$host' (must be unraid, fedora, local, or custom)"
|
||||
errors=$((errors + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
# type: must be docker or native
|
||||
case "$type" in
|
||||
docker|native) ;;
|
||||
"")
|
||||
log_error " → [$section] type is empty (must be docker or native)"
|
||||
errors=$((errors + 1))
|
||||
;;
|
||||
*)
|
||||
log_error " → [$section] type='$type' (must be docker or native)"
|
||||
errors=$((errors + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
# data_path: must start with / or ~/
|
||||
# shellcheck disable=SC2088 # tilde intentionally stored as literal string
|
||||
if [[ -z "$data_path" ]]; then
|
||||
log_error " → [$section] data_path is empty"
|
||||
errors=$((errors + 1))
|
||||
elif [[ "$data_path" != /* ]] && [[ "$data_path" != "~/"* ]] && [[ "$data_path" != "~" ]]; then
|
||||
log_error " → [$section] data_path='$data_path' (must start with / or ~/)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# labels: non-empty
|
||||
if [[ -z "$labels" ]]; then
|
||||
log_error " → [$section] labels is empty"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# capacity: positive integer >= 1
|
||||
if [[ -n "$capacity" ]] && ! [[ "$capacity" =~ ^[1-9][0-9]*$ ]]; then
|
||||
log_error " → [$section] capacity='$capacity' (must be positive integer >= 1)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# repos: must be "all" or a known REPO_*_NAME
|
||||
if [[ "$repos" != "all" ]] && [[ -n "$repos" ]]; then
|
||||
local repos_valid=false
|
||||
for var in REPO_1_NAME REPO_2_NAME REPO_3_NAME; do
|
||||
if [[ "$repos" == "${!var:-}" ]]; then repos_valid=true; break; fi
|
||||
done
|
||||
if ! $repos_valid; then
|
||||
log_error " → [$section] repos='$repos' (must be 'all' or a known repo name)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# docker runner: default_image should be set
|
||||
if [[ "$type" == "docker" ]]; then
|
||||
local default_image
|
||||
default_image=$(ini_get "$conf" "$section" "default_image" "")
|
||||
if [[ -z "$default_image" ]] && [[ -z "${RUNNER_DEFAULT_IMAGE:-}" ]]; then
|
||||
log_error " → [$section] default_image is empty and RUNNER_DEFAULT_IMAGE not set in .env"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# custom host: extra SSH keys required
|
||||
if [[ "$host" == "custom" ]]; then
|
||||
local ssh_host ssh_user
|
||||
ssh_host=$(ini_get "$conf" "$section" "ssh_host" "")
|
||||
ssh_user=$(ini_get "$conf" "$section" "ssh_user" "")
|
||||
if [[ -z "$ssh_host" ]]; then
|
||||
log_error " → [$section] ssh_host is empty (required for host=custom)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
if [[ -z "$ssh_user" ]]; then
|
||||
log_error " → [$section] ssh_user is empty (required for host=custom)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
done < <(ini_list_sections "$conf")
|
||||
|
||||
[[ $errors -eq 0 ]]
|
||||
}
|
||||
if [[ -f "${SCRIPT_DIR}/runners.conf" ]]; then
|
||||
check 5b "runners.conf INI format validation" check_runners_ini_format
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Load env for remaining checks (may fail if .env missing)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -118,6 +234,7 @@ REQUIRED_VARS=(
|
||||
GITEA_BACKUP_INTERNAL_URL BACKUP_STORAGE_PATH
|
||||
GITHUB_USERNAME GITHUB_TOKEN
|
||||
REPO_1_NAME REPO_2_NAME REPO_3_NAME
|
||||
RUNNER_DEFAULT_IMAGE RUNNER_DEFAULT_DATA_PATH LOCAL_RUNNER_DATA_PATH
|
||||
GITHUB_MIRROR_TOKEN
|
||||
NGINX_CONTAINER_NAME NGINX_CONF_PATH SSL_MODE
|
||||
)
|
||||
@@ -262,11 +379,31 @@ if [[ "$SKIP_PORT_CHECKS" == "true" ]]; then
|
||||
else
|
||||
check_port_unraid() {
|
||||
local port="${UNRAID_GITEA_PORT:-3000}"
|
||||
! ssh_exec UNRAID "ss -tlnp | grep -q ':${port} '" 2>/dev/null
|
||||
local ss_output
|
||||
local grep_rc
|
||||
|
||||
# Fail closed on SSH/remote command errors.
|
||||
ss_output=$(ssh_exec UNRAID "ss -tlnp" 2>/dev/null) || return 1
|
||||
|
||||
# grep exit codes:
|
||||
# 0 => match found (port in use) => FAIL check
|
||||
# 1 => no match (port free) => PASS check
|
||||
# >1 => grep error => FAIL check
|
||||
if printf '%s\n' "$ss_output" | grep -q ":${port} "; then
|
||||
grep_rc=0
|
||||
else
|
||||
grep_rc=$?
|
||||
fi
|
||||
|
||||
case "$grep_rc" in
|
||||
0) return 1 ;;
|
||||
1) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
check 13 "Port ${UNRAID_GITEA_PORT:-3000} free on Unraid" check_port_unraid
|
||||
if ! check_port_unraid 2>/dev/null; then
|
||||
log_error " → Port ${UNRAID_GITEA_PORT:-3000} already in use on Unraid."
|
||||
log_error " → Port check failed or port ${UNRAID_GITEA_PORT:-3000} is already in use on Unraid."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -274,11 +411,31 @@ else
|
||||
# ---------------------------------------------------------------------------
|
||||
check_port_fedora() {
|
||||
local port="${FEDORA_GITEA_PORT:-3000}"
|
||||
! ssh_exec FEDORA "ss -tlnp | grep -q ':${port} '" 2>/dev/null
|
||||
local ss_output
|
||||
local grep_rc
|
||||
|
||||
# Fail closed on SSH/remote command errors.
|
||||
ss_output=$(ssh_exec FEDORA "ss -tlnp" 2>/dev/null) || return 1
|
||||
|
||||
# grep exit codes:
|
||||
# 0 => match found (port in use) => FAIL check
|
||||
# 1 => no match (port free) => PASS check
|
||||
# >1 => grep error => FAIL check
|
||||
if printf '%s\n' "$ss_output" | grep -q ":${port} "; then
|
||||
grep_rc=0
|
||||
else
|
||||
grep_rc=$?
|
||||
fi
|
||||
|
||||
case "$grep_rc" in
|
||||
0) return 1 ;;
|
||||
1) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
check 14 "Port ${FEDORA_GITEA_PORT:-3000} free on Fedora" check_port_fedora
|
||||
if ! check_port_fedora 2>/dev/null; then
|
||||
log_error " → Port ${FEDORA_GITEA_PORT:-3000} already in use on Fedora."
|
||||
log_error " → Port check failed or port ${FEDORA_GITEA_PORT:-3000} is already in use on Fedora."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -286,9 +443,19 @@ fi
|
||||
# Check 15: DNS resolves
|
||||
# ---------------------------------------------------------------------------
|
||||
check_dns() {
|
||||
# Fail closed when required values are missing.
|
||||
[[ -n "${GITEA_DOMAIN:-}" ]] || return 1
|
||||
[[ -n "${UNRAID_IP:-}" ]] || return 1
|
||||
|
||||
local resolved
|
||||
resolved=$(dig +short "${GITEA_DOMAIN:-}" 2>/dev/null | head -1)
|
||||
[[ "$resolved" == "${UNRAID_IP:-}" ]]
|
||||
resolved=$(dig +short "${GITEA_DOMAIN}" A 2>/dev/null | sed '/^[[:space:]]*$/d') || return 1
|
||||
[[ -n "$resolved" ]] || return 1
|
||||
|
||||
# Pass only if one of the domain's A records exactly matches UNRAID_IP.
|
||||
if printf '%s\n' "$resolved" | grep -Fxq "${UNRAID_IP}"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
check 15 "DNS: ${GITEA_DOMAIN:-<not set>} resolves to ${UNRAID_IP:-<not set>}" check_dns
|
||||
if ! check_dns 2>/dev/null; then
|
||||
@@ -312,13 +479,21 @@ fi
|
||||
# shellcheck disable=SC2329
|
||||
check_github_repos() {
|
||||
local all_ok=0
|
||||
|
||||
if [[ -z "${GITHUB_USERNAME:-}" ]]; then
|
||||
log_error " → GITHUB_USERNAME is empty"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for var in REPO_1_NAME REPO_2_NAME REPO_3_NAME; do
|
||||
local repo="${!var:-}"
|
||||
if [[ -z "$repo" ]]; then
|
||||
log_error " → ${var} is empty"
|
||||
all_ok=1
|
||||
continue
|
||||
fi
|
||||
if ! curl -sf -H "Authorization: token ${GITHUB_TOKEN:-}" "https://api.github.com/repos/${GITHUB_USERNAME:-}/${repo}" -o /dev/null 2>/dev/null; then
|
||||
log_error " → GitHub repo ${repo} not found under ${GITHUB_USERNAME:-}"
|
||||
if ! curl -sf -H "Authorization: token ${GITHUB_TOKEN:-}" "https://api.github.com/repos/${GITHUB_USERNAME}/${repo}" -o /dev/null 2>/dev/null; then
|
||||
log_error " → GitHub repo ${repo} not found under ${GITHUB_USERNAME}"
|
||||
all_ok=1
|
||||
fi
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user