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:
S
2026-02-28 23:14:46 -05:00
parent fcd966f97d
commit f4a6b04d14
12 changed files with 1000 additions and 329 deletions

View File

@@ -3,7 +3,7 @@ set -euo pipefail
# =============================================================================
# manage_runner.sh — Add, remove, or list Gitea Actions runners
# Reads runner definitions from runners.conf (pipe-delimited format).
# Reads runner definitions from runners.conf (INI format).
# Supports two runner types:
# docker — Linux hosts, deployed as Docker container via docker-compose
# native — macOS, deployed as binary + launchd service
@@ -31,16 +31,19 @@ Commands:
list Show all runners with status
Options:
--name <name> Runner name as defined in runners.conf
--name <name> Runner name (= section name in runners.conf)
--help Show this help
EOF
exit 1
}
# ---------------------------------------------------------------------------
# Parse a runner entry from runners.conf by name
# Sets: RUNNER_NAME, RUNNER_SSH_HOST, RUNNER_SSH_USER, RUNNER_SSH_PORT,
# RUNNER_DATA_PATH, RUNNER_LABELS, RUNNER_TYPE
# Parse a runner entry from runners.conf (INI format) by section name.
# Sets globals: RUNNER_NAME, RUNNER_HOST, RUNNER_TYPE, RUNNER_DATA_PATH,
# RUNNER_LABELS, RUNNER_DEFAULT_IMAGE, RUNNER_REPOS, RUNNER_CAPACITY,
# RUNNER_CPU, RUNNER_MEMORY
# Also resolves: RUNNER_SSH_HOST, RUNNER_SSH_USER, RUNNER_SSH_PORT,
# RUNNER_SSH_KEY (from .env or custom section keys)
# Returns 1 if not found.
# ---------------------------------------------------------------------------
parse_runner_entry() {
@@ -51,63 +54,205 @@ parse_runner_entry() {
return 1
fi
while IFS='|' read -r name host user port path labels type; do
# Skip comments and blank lines
[[ "$name" =~ ^[[:space:]]*# ]] && continue
[[ -z "$name" ]] && continue
# Trim whitespace from fields
name=$(echo "$name" | xargs)
if [[ "$name" == "$target_name" ]]; then
RUNNER_NAME="$name"
RUNNER_SSH_HOST=$(echo "$host" | xargs)
RUNNER_SSH_USER=$(echo "$user" | xargs)
RUNNER_SSH_PORT=$(echo "$port" | xargs)
RUNNER_DATA_PATH=$(echo "$path" | xargs)
RUNNER_LABELS=$(echo "$labels" | xargs)
RUNNER_TYPE=$(echo "$type" | xargs)
return 0
fi
done < "$RUNNERS_CONF"
log_error "Runner '$target_name' not found in runners.conf"
return 1
}
# ---------------------------------------------------------------------------
# List all runner entries from runners.conf (without looking up a name)
# Outputs lines to stdout: name|host|user|port|path|labels|type
# ---------------------------------------------------------------------------
all_runner_entries() {
if [[ ! -f "$RUNNERS_CONF" ]]; then
log_error "runners.conf not found at $RUNNERS_CONF"
# Check section exists
if ! ini_list_sections "$RUNNERS_CONF" | grep -qx "$target_name"; then
log_error "Runner '$target_name' not found in runners.conf"
return 1
fi
while IFS= read -r line; do
# Skip comments and blank lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "$line" ]] && continue
echo "$line"
done < "$RUNNERS_CONF"
RUNNER_NAME="$target_name"
RUNNER_HOST=$(ini_get "$RUNNERS_CONF" "$target_name" "host" "")
RUNNER_TYPE=$(ini_get "$RUNNERS_CONF" "$target_name" "type" "")
RUNNER_DATA_PATH=$(ini_get "$RUNNERS_CONF" "$target_name" "data_path" "")
RUNNER_LABELS=$(ini_get "$RUNNERS_CONF" "$target_name" "labels" "")
RUNNER_DEFAULT_IMAGE=$(ini_get "$RUNNERS_CONF" "$target_name" "default_image" "")
RUNNER_REPOS=$(ini_get "$RUNNERS_CONF" "$target_name" "repos" "all")
RUNNER_CAPACITY=$(ini_get "$RUNNERS_CONF" "$target_name" "capacity" "${RUNNER_DEFAULT_CAPACITY:-1}")
RUNNER_CPU=$(ini_get "$RUNNERS_CONF" "$target_name" "cpu" "")
RUNNER_MEMORY=$(ini_get "$RUNNERS_CONF" "$target_name" "memory" "")
# --- Host resolution ---
case "$RUNNER_HOST" in
unraid)
RUNNER_SSH_HOST="${UNRAID_IP:-}"
RUNNER_SSH_USER="${UNRAID_SSH_USER:-}"
RUNNER_SSH_PORT="${UNRAID_SSH_PORT:-22}"
RUNNER_SSH_KEY="${UNRAID_SSH_KEY:-}"
;;
fedora)
RUNNER_SSH_HOST="${FEDORA_IP:-}"
RUNNER_SSH_USER="${FEDORA_SSH_USER:-}"
RUNNER_SSH_PORT="${FEDORA_SSH_PORT:-22}"
RUNNER_SSH_KEY="${FEDORA_SSH_KEY:-}"
;;
local)
RUNNER_SSH_HOST="local"
RUNNER_SSH_USER=""
RUNNER_SSH_PORT=""
RUNNER_SSH_KEY=""
;;
custom)
RUNNER_SSH_HOST=$(ini_get "$RUNNERS_CONF" "$target_name" "ssh_host" "")
RUNNER_SSH_USER=$(ini_get "$RUNNERS_CONF" "$target_name" "ssh_user" "")
RUNNER_SSH_PORT=$(ini_get "$RUNNERS_CONF" "$target_name" "ssh_port" "22")
RUNNER_SSH_KEY=$(ini_get "$RUNNERS_CONF" "$target_name" "ssh_key" "")
;;
*)
log_error "Runner '$target_name': unknown host '$RUNNER_HOST' (must be unraid, fedora, local, or custom)"
return 1
;;
esac
# --- Validate required fields ---
if [[ -z "$RUNNER_TYPE" ]]; then
log_error "Runner '$target_name': type is empty (must be docker or native)"
return 1
fi
if [[ "$RUNNER_TYPE" != "docker" ]] && [[ "$RUNNER_TYPE" != "native" ]]; then
log_error "Runner '$target_name': type='$RUNNER_TYPE' (must be docker or native)"
return 1
fi
if [[ -z "$RUNNER_DATA_PATH" ]]; then
log_error "Runner '$target_name': data_path is empty"
return 1
fi
if [[ -z "$RUNNER_LABELS" ]]; then
log_error "Runner '$target_name': labels is empty"
return 1
fi
if ! [[ "$RUNNER_CAPACITY" =~ ^[1-9][0-9]*$ ]]; then
log_error "Runner '$target_name': capacity='$RUNNER_CAPACITY' (must be positive integer >= 1)"
return 1
fi
return 0
}
# ---------------------------------------------------------------------------
# Resolve the runner's image (with LOCAL_REGISTRY prefix if set).
# Sets RUNNER_RESOLVED_IMAGE.
# ---------------------------------------------------------------------------
resolve_runner_image() {
local image="${RUNNER_DEFAULT_IMAGE:-${RUNNER_DEFAULT_IMAGE_ENV:-}}"
if [[ -z "$image" ]] && [[ "$RUNNER_TYPE" == "docker" ]]; then
image="${RUNNER_DEFAULT_IMAGE:-catthehacker/ubuntu:act-latest}"
fi
if [[ -n "$image" ]] && [[ -n "${LOCAL_REGISTRY:-}" ]]; then
RUNNER_RESOLVED_IMAGE="${LOCAL_REGISTRY}/${image}"
else
RUNNER_RESOLVED_IMAGE="${image}"
fi
}
# ---------------------------------------------------------------------------
# Build act_runner label specs from labels + default_image.
# Docker: "linux:docker://image" | Native: "macos:host"
# Sets RUNNER_LABELS_CSV and RUNNER_LABELS_YAML.
# ---------------------------------------------------------------------------
build_runner_labels() {
resolve_runner_image
local labels_csv=""
local labels_yaml=""
local IFS=','
# shellcheck disable=SC2206
local -a parts=($RUNNER_LABELS)
local label spec
for label in "${parts[@]}"; do
label=$(echo "$label" | xargs)
[[ -z "$label" ]] && continue
if [[ "$label" == *:* ]]; then
# Already a full spec (e.g. linux:docker://node:20)
spec="$label"
elif [[ "$RUNNER_TYPE" == "docker" ]]; then
spec="${label}:docker://${RUNNER_RESOLVED_IMAGE}"
else
spec="${label}:host"
fi
if [[ -z "$labels_csv" ]]; then
labels_csv="$spec"
else
labels_csv="${labels_csv},${spec}"
fi
# shellcheck disable=SC2089 # intentional — value rendered via envsubst, not shell expansion
labels_yaml="${labels_yaml} - \"${spec}\"
"
done
export RUNNER_LABELS_CSV="$labels_csv"
# shellcheck disable=SC2090 # intentional — value rendered via envsubst, not shell expansion
export RUNNER_LABELS_YAML="$labels_yaml"
}
# ---------------------------------------------------------------------------
# Resolve registration token based on repos field.
# repos=all → instance-level token from .env
# repos=<name> → fetch repo-level token from Gitea API
# Sets RUNNER_REG_TOKEN.
# ---------------------------------------------------------------------------
resolve_registration_token() {
if [[ "$RUNNER_REPOS" == "all" ]] || [[ -z "$RUNNER_REPOS" ]]; then
RUNNER_REG_TOKEN="${GITEA_RUNNER_REGISTRATION_TOKEN:-}"
else
# Fetch repo-level registration token from Gitea API
local owner="${GITEA_ORG_NAME:-}"
if [[ -z "$owner" ]]; then
log_error "GITEA_ORG_NAME is empty — cannot fetch repo-level token"
return 1
fi
log_info "Fetching registration token for repo '${owner}/${RUNNER_REPOS}'..."
local response
response=$(gitea_api GET "/repos/${owner}/${RUNNER_REPOS}/actions/runners/registration-token" 2>/dev/null) || {
log_error "Failed to fetch registration token for repo '${RUNNER_REPOS}'"
return 1
}
RUNNER_REG_TOKEN=$(printf '%s' "$response" | jq -r '.token // empty' 2>/dev/null)
if [[ -z "$RUNNER_REG_TOKEN" ]]; then
log_error "Empty registration token returned for repo '${RUNNER_REPOS}'"
return 1
fi
fi
export RUNNER_REG_TOKEN
}
# ---------------------------------------------------------------------------
# Build Docker deploy.resources block (only when cpu/memory are set).
# Sets RUNNER_DEPLOY_RESOURCES.
# ---------------------------------------------------------------------------
build_deploy_resources() {
if [[ -n "$RUNNER_CPU" ]] || [[ -n "$RUNNER_MEMORY" ]]; then
local block=" deploy:\n resources:\n limits:"
if [[ -n "$RUNNER_CPU" ]]; then
block="${block}\n cpus: \"${RUNNER_CPU}\""
fi
if [[ -n "$RUNNER_MEMORY" ]]; then
block="${block}\n memory: \"${RUNNER_MEMORY}\""
fi
RUNNER_DEPLOY_RESOURCES=$(printf '%b' "$block")
else
RUNNER_DEPLOY_RESOURCES=""
fi
export RUNNER_DEPLOY_RESOURCES
}
# ---------------------------------------------------------------------------
# Execute a command on the runner's host.
# For "local" hosts (macOS), runs the command directly.
# For remote hosts, SSHs into them — uses direct ssh (not ssh_exec from
# common.sh) because runner hosts have their own SSH creds defined in
# runners.conf, not from the standard *_IP/*_SSH_USER env vars.
# For remote hosts, SSHs into them using resolved SSH credentials.
# ---------------------------------------------------------------------------
runner_ssh() {
local cmd="$*"
if [[ "$RUNNER_SSH_HOST" == "local" ]]; then
# macOS runner — execute directly on this machine
eval "$cmd"
else
ssh -o ConnectTimeout=10 \
# shellcheck disable=SC2086
ssh ${RUNNER_SSH_KEY:+-i "$RUNNER_SSH_KEY"} \
-o ConnectTimeout=10 \
-o StrictHostKeyChecking=accept-new \
-o BatchMode=yes \
-p "${RUNNER_SSH_PORT}" \
@@ -126,7 +271,9 @@ runner_scp() {
if [[ "$RUNNER_SSH_HOST" == "local" ]]; then
cp "$src" "$dst"
else
scp -o ConnectTimeout=10 \
# shellcheck disable=SC2086
scp ${RUNNER_SSH_KEY:+-i "$RUNNER_SSH_KEY"} \
-o ConnectTimeout=10 \
-o StrictHostKeyChecking=accept-new \
-o BatchMode=yes \
-P "${RUNNER_SSH_PORT}" \
@@ -136,16 +283,13 @@ runner_scp() {
# ---------------------------------------------------------------------------
# add_docker_runner — Deploy a runner as a Docker container on a Linux host
# Steps:
# 1. Create data directory
# 2. Render + SCP docker-compose.yml
# 3. Render + SCP runner-config.yaml
# 4. Start container
# The act_runner Docker image auto-registers with Gitea using env vars
# (GITEA_INSTANCE_URL + GITEA_RUNNER_REGISTRATION_TOKEN) on first boot.
# ---------------------------------------------------------------------------
add_docker_runner() {
log_info "Deploying Docker runner '${RUNNER_NAME}' on ${RUNNER_SSH_HOST}..."
log_info "Deploying Docker runner '${RUNNER_NAME}' on ${RUNNER_HOST} (${RUNNER_SSH_HOST})..."
build_runner_labels
resolve_registration_token
build_deploy_resources
# Check if container is already running
local status
@@ -158,44 +302,41 @@ add_docker_runner() {
# Create data directory on remote host
runner_ssh "mkdir -p '${RUNNER_DATA_PATH}'"
# Render docker-compose template with runner-specific vars
# Render docker-compose template
local tmpfile
tmpfile=$(mktemp)
export RUNNER_NAME RUNNER_LABELS RUNNER_DATA_PATH
export GITEA_RUNNER_REGISTRATION_TOKEN="${GITEA_RUNNER_REGISTRATION_TOKEN:-}"
export RUNNER_NAME RUNNER_DATA_PATH RUNNER_LABELS_CSV RUNNER_REG_TOKEN RUNNER_DEPLOY_RESOURCES
render_template "${SCRIPT_DIR}/templates/docker-compose-runner.yml.tpl" "$tmpfile" \
"\${ACT_RUNNER_VERSION} \${RUNNER_NAME} \${GITEA_INTERNAL_URL} \${GITEA_RUNNER_REGISTRATION_TOKEN} \${RUNNER_LABELS} \${RUNNER_DATA_PATH}"
"\${ACT_RUNNER_VERSION} \${RUNNER_NAME} \${GITEA_INTERNAL_URL} \${RUNNER_REG_TOKEN} \${RUNNER_LABELS_CSV} \${RUNNER_DATA_PATH} \${RUNNER_DEPLOY_RESOURCES}"
runner_scp "$tmpfile" "${RUNNER_DATA_PATH}/docker-compose.yml"
rm -f "$tmpfile"
# Render runner config
tmpfile=$(mktemp)
# shellcheck disable=SC2090 # intentional — RUNNER_LABELS_YAML rendered via envsubst
export RUNNER_LABELS_YAML
export RUNNER_CAPACITY
render_template "${SCRIPT_DIR}/templates/runner-config.yaml.tpl" "$tmpfile" \
"\${RUNNER_NAME} \${RUNNER_LABELS}"
"\${RUNNER_NAME} \${RUNNER_LABELS_YAML} \${RUNNER_CAPACITY}"
runner_scp "$tmpfile" "${RUNNER_DATA_PATH}/config.yaml"
rm -f "$tmpfile"
# Start the container
runner_ssh "cd '${RUNNER_DATA_PATH}' && docker compose up -d 2>/dev/null || docker-compose up -d"
log_success "Docker runner '${RUNNER_NAME}' started on ${RUNNER_SSH_HOST}"
log_success "Docker runner '${RUNNER_NAME}' started on ${RUNNER_HOST} (${RUNNER_SSH_HOST})"
}
# ---------------------------------------------------------------------------
# add_native_runner — Deploy a runner as a native binary on macOS
# Steps:
# 1. Download act_runner binary for macOS (arm64 or amd64)
# 2. Register with Gitea (--no-interactive)
# 3. Render launchd plist
# 4. Load plist via launchctl
# Native runners are used on macOS because Docker Desktop is heavyweight
# and unreliable for long-running background services.
# ---------------------------------------------------------------------------
add_native_runner() {
# Native runners use launchctl + macOS-specific paths — must be macOS
require_local_os "Darwin" "Native runner deployment requires macOS (uses launchctl)"
log_info "Deploying native runner '${RUNNER_NAME}' on local machine..."
build_runner_labels
resolve_registration_token
# Resolve ~ to actual home directory for local execution
RUNNER_DATA_PATH="${RUNNER_DATA_PATH/#\~/$HOME}"
export RUNNER_DATA_PATH
@@ -214,7 +355,6 @@ add_native_runner() {
# Download act_runner binary if not present
if [[ ! -x "${RUNNER_DATA_PATH}/act_runner" ]]; then
# Detect architecture — Apple Silicon (arm64) vs Intel (x86_64)
local arch
arch=$(uname -m)
case "$arch" in
@@ -230,26 +370,26 @@ add_native_runner() {
log_success "act_runner binary downloaded"
fi
# Register the runner with Gitea (generates .runner file in data dir)
# --no-interactive skips prompts, --config generates default config if missing
# Register the runner with Gitea
if [[ ! -f "${RUNNER_DATA_PATH}/.runner" ]]; then
log_info "Registering runner with Gitea..."
"${RUNNER_DATA_PATH}/act_runner" register \
--no-interactive \
--instance "${GITEA_INTERNAL_URL}" \
--token "${GITEA_RUNNER_REGISTRATION_TOKEN:-}" \
--token "${RUNNER_REG_TOKEN}" \
--name "${RUNNER_NAME}" \
--labels "${RUNNER_LABELS}" \
--labels "${RUNNER_LABELS_CSV}" \
--config "${RUNNER_DATA_PATH}/config.yaml"
log_success "Runner registered"
fi
# Render runner config (overwrites any default generated by register)
# Render runner config
local tmpfile
tmpfile=$(mktemp)
export RUNNER_NAME RUNNER_LABELS RUNNER_DATA_PATH
# shellcheck disable=SC2090 # intentional — RUNNER_LABELS_YAML rendered via envsubst
export RUNNER_NAME RUNNER_DATA_PATH RUNNER_LABELS_YAML RUNNER_CAPACITY
render_template "${SCRIPT_DIR}/templates/runner-config.yaml.tpl" "$tmpfile" \
"\${RUNNER_NAME} \${RUNNER_LABELS}"
"\${RUNNER_NAME} \${RUNNER_LABELS_YAML} \${RUNNER_CAPACITY}"
cp "$tmpfile" "${RUNNER_DATA_PATH}/config.yaml"
rm -f "$tmpfile"
@@ -270,9 +410,8 @@ add_native_runner() {
# remove_docker_runner — Stop + remove Docker runner container
# ---------------------------------------------------------------------------
remove_docker_runner() {
log_info "Removing Docker runner '${RUNNER_NAME}' from ${RUNNER_SSH_HOST}..."
log_info "Removing Docker runner '${RUNNER_NAME}' from ${RUNNER_HOST} (${RUNNER_SSH_HOST})..."
# Check if docker-compose file exists
if runner_ssh "test -f '${RUNNER_DATA_PATH}/docker-compose.yml'" 2>/dev/null; then
runner_ssh "cd '${RUNNER_DATA_PATH}' && docker compose down 2>/dev/null || docker-compose down" || true
log_success "Docker runner '${RUNNER_NAME}' stopped"
@@ -285,30 +424,25 @@ remove_docker_runner() {
# remove_native_runner — Unload launchd service + remove binary + plist
# ---------------------------------------------------------------------------
remove_native_runner() {
# Native runners use launchctl + macOS-specific paths — must be macOS
require_local_os "Darwin" "Native runner removal requires macOS (uses launchctl)"
log_info "Removing native runner '${RUNNER_NAME}' from local machine..."
# Resolve ~ to actual home directory
RUNNER_DATA_PATH="${RUNNER_DATA_PATH/#\~/$HOME}"
local plist_name="com.gitea.runner.${RUNNER_NAME}.plist"
local plist_path="$HOME/Library/LaunchAgents/${plist_name}"
# Unload launchd service if loaded
if launchctl list 2>/dev/null | grep -q "com.gitea.runner.${RUNNER_NAME}"; then
launchctl unload "$plist_path" 2>/dev/null || true
log_success "Launchd service unloaded"
fi
# Remove plist file
if [[ -f "$plist_path" ]]; then
rm -f "$plist_path"
log_success "Plist removed: $plist_path"
fi
# Remove runner data directory (binary, config, registration)
if [[ -d "${RUNNER_DATA_PATH}" ]]; then
printf 'Remove runner data at %s? [y/N] ' "${RUNNER_DATA_PATH}"
read -r confirm
@@ -323,41 +457,35 @@ remove_native_runner() {
# ---------------------------------------------------------------------------
# list_runners — Print table of all runners with their Gitea status
# Queries the Gitea admin API for registered runners and cross-references
# with runners.conf to show which are online/offline/unregistered.
# ---------------------------------------------------------------------------
list_runners() {
log_info "Listing runners..."
# Fetch registered runners from Gitea admin API
if [[ ! -f "$RUNNERS_CONF" ]]; then
log_error "runners.conf not found at $RUNNERS_CONF"
return 1
fi
local api_runners
api_runners=$(gitea_api GET "/admin/runners" 2>/dev/null || echo "[]")
# Print header
printf '%-20s %-16s %-10s %-8s %-10s\n' "NAME" "HOST" "LABELS" "TYPE" "STATUS"
printf '%-20s %-16s %-10s %-8s %-10s\n' "----" "----" "------" "----" "------"
printf '%-20s %-10s %-10s %-8s %-6s %-10s\n' "NAME" "HOST" "LABELS" "TYPE" "CAP" "STATUS"
printf '%-20s %-10s %-10s %-8s %-6s %-10s\n' "----" "----" "------" "----" "---" "------"
# For each entry in runners.conf, look up status in API response
while IFS='|' read -r name host user port path labels type; do
# Skip comments and blank lines
[[ "$name" =~ ^[[:space:]]*# ]] && continue
[[ -z "$name" ]] && continue
local name host labels runner_type capacity status
while IFS= read -r name; do
host=$(ini_get "$RUNNERS_CONF" "$name" "host" "")
labels=$(ini_get "$RUNNERS_CONF" "$name" "labels" "")
runner_type=$(ini_get "$RUNNERS_CONF" "$name" "type" "")
capacity=$(ini_get "$RUNNERS_CONF" "$name" "capacity" "1")
name=$(echo "$name" | xargs)
host=$(echo "$host" | xargs)
labels=$(echo "$labels" | xargs)
type=$(echo "$type" | xargs)
# Search for this runner in the API response by name
local status
status=$(printf '%s' "$api_runners" | jq -r --arg n "$name" '.[] | select(.name == $n) | .status' 2>/dev/null || true)
if [[ -z "$status" ]]; then
status="not-found"
fi
printf '%-20s %-16s %-10s %-8s %-10s\n' "$name" "$host" "$labels" "$type" "$status"
done < "$RUNNERS_CONF"
printf '%-20s %-10s %-10s %-8s %-6s %-10s\n' "$name" "$host" "$labels" "$runner_type" "$capacity" "$status"
done < <(ini_list_sections "$RUNNERS_CONF")
}
# ---------------------------------------------------------------------------
@@ -388,7 +516,6 @@ case "$COMMAND" in
log_error "add requires --name <runner_name>"
usage
fi
require_vars GITEA_RUNNER_REGISTRATION_TOKEN
parse_runner_entry "$RUNNER_ARG_NAME"
case "$RUNNER_TYPE" in
docker) add_docker_runner ;;