- Phase 10: local repo cutover (rename origin→github, add Gitea remote, push branches/tags) - Phase 11: custom runner infrastructure with toolchain-based naming (go-node-runner, jvm-android-runner) and repo variables via Gitea API - Add container_options support to manage_runner.sh for KVM passthrough - Phase 8: add --allow-direct-checks flag for LAN/split-DNS staging - Phase 7.5: add Cloudflare TLS block, retry logic for probes, multi-upstream support - Add toggle_dns.sh helper and update orchestration scripts for phases 10-11 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
289 lines
10 KiB
Bash
Executable File
289 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# =============================================================================
|
|
# phase11_custom_runners.sh — Deploy per-repo runner infrastructure & variables
|
|
# Depends on: Phase 3 complete (runner infra), Phase 4 complete (repos on Gitea)
|
|
#
|
|
# Steps:
|
|
# 1. Build custom toolchain images on Unraid (go-node-runner, jvm-android-runner)
|
|
# 2. Consolidate macOS runners into a shared instance-level runner
|
|
# 3. Deploy per-repo Docker runners via manage_runner.sh
|
|
# 4. Set Gitea repository variables from repo_variables.conf
|
|
#
|
|
# Runner strategy:
|
|
# - Linux runners: repo-scoped, separate toolchain images per repo
|
|
# - Android emulator: shared (repos=all) — any repo can use it
|
|
# - macOS runner: shared (repos=all) — any repo can use it
|
|
#
|
|
# Idempotent: skips images that already exist, runners already running,
|
|
# and variables that already match.
|
|
# =============================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
source "${SCRIPT_DIR}/lib/common.sh"
|
|
|
|
load_env
|
|
require_vars GITEA_ADMIN_TOKEN GITEA_INTERNAL_URL GITEA_ORG_NAME \
|
|
UNRAID_IP UNRAID_SSH_USER UNRAID_SSH_PORT \
|
|
GO_NODE_RUNNER_CONTEXT JVM_ANDROID_RUNNER_CONTEXT \
|
|
ACT_RUNNER_VERSION
|
|
|
|
phase_header 11 "Custom Runner Infrastructure"
|
|
|
|
REPO_VARS_CONF="${SCRIPT_DIR}/repo_variables.conf"
|
|
REBUILD_IMAGES=false
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--rebuild-images) REBUILD_IMAGES=true ;;
|
|
*) ;;
|
|
esac
|
|
done
|
|
|
|
SUCCESS=0
|
|
FAILED=0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper: rsync a build context directory to Unraid
|
|
# ---------------------------------------------------------------------------
|
|
rsync_to_unraid() {
|
|
local src="$1" dest="$2"
|
|
local ssh_key="${UNRAID_SSH_KEY:-}"
|
|
local ssh_opts="ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -p ${UNRAID_SSH_PORT}"
|
|
if [[ -n "$ssh_key" ]]; then
|
|
ssh_opts="${ssh_opts} -i ${ssh_key}"
|
|
fi
|
|
rsync -az --delete \
|
|
--exclude='.env' \
|
|
--exclude='.env.*' \
|
|
--exclude='envs/' \
|
|
--exclude='.git' \
|
|
--exclude='.gitignore' \
|
|
-e "$ssh_opts" \
|
|
"${src}/" "${UNRAID_SSH_USER}@${UNRAID_IP}:${dest}/"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper: check if a Docker image exists on Unraid
|
|
# ---------------------------------------------------------------------------
|
|
image_exists_on_unraid() {
|
|
local tag="$1"
|
|
ssh_exec "UNRAID" "docker image inspect '${tag}' >/dev/null 2>&1"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper: list all keys in an INI section (for repo_variables.conf)
|
|
# ---------------------------------------------------------------------------
|
|
ini_list_keys() {
|
|
local file="$1" section="$2"
|
|
local in_section=false
|
|
local line k
|
|
while IFS= read -r line; do
|
|
line="${line#"${line%%[![:space:]]*}"}"
|
|
line="${line%"${line##*[![:space:]]}"}"
|
|
[[ -z "$line" ]] && continue
|
|
[[ "$line" == \#* ]] && continue
|
|
if [[ "$line" =~ ^\[([^]]+)\] ]]; then
|
|
if [[ "${BASH_REMATCH[1]}" == "$section" ]]; then
|
|
in_section=true
|
|
elif $in_section; then
|
|
break
|
|
fi
|
|
continue
|
|
fi
|
|
if $in_section && [[ "$line" =~ ^([^=]+)= ]]; then
|
|
k="${BASH_REMATCH[1]}"
|
|
k="${k#"${k%%[![:space:]]*}"}"
|
|
k="${k%"${k##*[![:space:]]}"}"
|
|
printf '%s\n' "$k"
|
|
fi
|
|
done < "$file"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper: upsert a Gitea repo variable (create or update)
|
|
# ---------------------------------------------------------------------------
|
|
upsert_repo_variable() {
|
|
local repo="$1" var_name="$2" var_value="$3"
|
|
local owner="${GITEA_ORG_NAME}"
|
|
|
|
# Check if variable already exists with correct value
|
|
local existing
|
|
if existing=$(gitea_api GET "/repos/${owner}/${repo}/actions/variables/${var_name}" 2>/dev/null); then
|
|
local current_value
|
|
current_value=$(printf '%s' "$existing" | jq -r '.value // .data // empty' 2>/dev/null)
|
|
if [[ "$current_value" == "$var_value" ]]; then
|
|
log_info " ${var_name} already set correctly — skipping"
|
|
return 0
|
|
fi
|
|
# Update existing variable
|
|
if gitea_api PUT "/repos/${owner}/${repo}/actions/variables/${var_name}" \
|
|
"$(jq -n --arg v "$var_value" '{value: $v}')" >/dev/null 2>&1; then
|
|
log_success " Updated ${var_name}"
|
|
return 0
|
|
else
|
|
log_error " Failed to update ${var_name}"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Create new variable
|
|
if gitea_api POST "/repos/${owner}/${repo}/actions/variables" \
|
|
"$(jq -n --arg n "$var_name" --arg v "$var_value" '{name: $n, value: $v}')" >/dev/null 2>&1; then
|
|
log_success " Created ${var_name}"
|
|
return 0
|
|
else
|
|
log_error " Failed to create ${var_name}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# =========================================================================
|
|
# Step 1: Build toolchain images on Unraid
|
|
# =========================================================================
|
|
log_step 1 "Building toolchain images on Unraid"
|
|
|
|
REMOTE_BUILD_BASE="/tmp/gitea-runner-builds"
|
|
|
|
# Image build definitions: TAG|LOCAL_CONTEXT|DOCKER_TARGET
|
|
IMAGE_BUILDS=(
|
|
"go-node-runner:latest|${GO_NODE_RUNNER_CONTEXT}|"
|
|
"jvm-android-runner:slim|${JVM_ANDROID_RUNNER_CONTEXT}|slim"
|
|
"jvm-android-runner:full|${JVM_ANDROID_RUNNER_CONTEXT}|full"
|
|
)
|
|
|
|
for build_entry in "${IMAGE_BUILDS[@]}"; do
|
|
IFS='|' read -r img_tag build_context docker_target <<< "$build_entry"
|
|
|
|
if [[ "$REBUILD_IMAGES" != "true" ]] && image_exists_on_unraid "$img_tag"; then
|
|
log_info "Image ${img_tag} already exists on Unraid — skipping"
|
|
continue
|
|
fi
|
|
|
|
# Derive a unique remote directory name from the image tag
|
|
remote_dir="${REMOTE_BUILD_BASE}/${img_tag%%:*}"
|
|
|
|
log_info "Syncing build context for ${img_tag}..."
|
|
ssh_exec "UNRAID" "mkdir -p '${remote_dir}'"
|
|
rsync_to_unraid "$build_context" "$remote_dir"
|
|
|
|
log_info "Building ${img_tag} on Unraid (this may take a while)..."
|
|
local_build_args=""
|
|
if [[ -n "$docker_target" ]]; then
|
|
local_build_args="--target ${docker_target}"
|
|
fi
|
|
|
|
# shellcheck disable=SC2086
|
|
if ssh_exec "UNRAID" "cd '${remote_dir}' && docker build ${local_build_args} -t '${img_tag}' ."; then
|
|
log_success "Built ${img_tag}"
|
|
else
|
|
log_error "Failed to build ${img_tag}"
|
|
FAILED=$((FAILED + 1))
|
|
fi
|
|
done
|
|
|
|
# Clean up build contexts on Unraid
|
|
ssh_exec "UNRAID" "rm -rf '${REMOTE_BUILD_BASE}'" 2>/dev/null || true
|
|
|
|
# =========================================================================
|
|
# Step 2: Consolidate macOS runners into shared instance-level runner
|
|
# =========================================================================
|
|
log_step 2 "Consolidating macOS runners"
|
|
|
|
# Old per-repo macOS runners to remove
|
|
OLD_MAC_RUNNERS=(
|
|
macbook-runner-periodvault
|
|
macbook-runner-intermittent-fasting-tracker
|
|
)
|
|
|
|
for old_runner in "${OLD_MAC_RUNNERS[@]}"; do
|
|
if ini_list_sections "${SCRIPT_DIR}/runners.conf" | grep -qx "$old_runner" 2>/dev/null; then
|
|
log_info "Old runner section '${old_runner}' found — phase 11 runners.conf already has it removed"
|
|
log_info " (If still registered in Gitea, run: manage_runner.sh remove --name ${old_runner})"
|
|
fi
|
|
# Remove from Gitea if still registered (launchd service)
|
|
if launchctl list 2>/dev/null | grep -q "com.gitea.runner.${old_runner}"; then
|
|
log_info "Removing old macOS runner '${old_runner}'..."
|
|
"${SCRIPT_DIR}/manage_runner.sh" remove --name "$old_runner" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Deploy the new shared macOS runner
|
|
if launchctl list 2>/dev/null | grep -q "com.gitea.runner.macbook-runner"; then
|
|
log_info "Shared macOS runner 'macbook-runner' already registered — skipping"
|
|
else
|
|
log_info "Deploying shared macOS runner 'macbook-runner'..."
|
|
if "${SCRIPT_DIR}/manage_runner.sh" add --name macbook-runner; then
|
|
log_success "Shared macOS runner deployed"
|
|
else
|
|
log_error "Failed to deploy shared macOS runner"
|
|
FAILED=$((FAILED + 1))
|
|
fi
|
|
fi
|
|
|
|
# =========================================================================
|
|
# Step 3: Deploy per-repo and shared Docker runners
|
|
# =========================================================================
|
|
log_step 3 "Deploying Docker runners"
|
|
|
|
# Phase 11 Docker runners (defined in runners.conf)
|
|
PHASE11_DOCKER_RUNNERS=(
|
|
unraid-go-node-1
|
|
unraid-go-node-2
|
|
unraid-go-node-3
|
|
unraid-jvm-slim-1
|
|
unraid-jvm-slim-2
|
|
unraid-android-emulator
|
|
)
|
|
|
|
for runner_name in "${PHASE11_DOCKER_RUNNERS[@]}"; do
|
|
log_info "--- Deploying runner: ${runner_name} ---"
|
|
if "${SCRIPT_DIR}/manage_runner.sh" add --name "$runner_name"; then
|
|
SUCCESS=$((SUCCESS + 1))
|
|
else
|
|
log_error "Failed to deploy runner '${runner_name}'"
|
|
FAILED=$((FAILED + 1))
|
|
fi
|
|
done
|
|
|
|
# =========================================================================
|
|
# Step 4: Set repository variables from repo_variables.conf
|
|
# =========================================================================
|
|
log_step 4 "Setting Gitea repository variables"
|
|
|
|
if [[ ! -f "$REPO_VARS_CONF" ]]; then
|
|
log_warn "repo_variables.conf not found — skipping variable setup"
|
|
else
|
|
# Iterate all sections (repos) in repo_variables.conf
|
|
while IFS= read -r repo; do
|
|
[[ -z "$repo" ]] && continue
|
|
log_info "--- Setting variables for repo: ${repo} ---"
|
|
|
|
# Iterate all keys in this section
|
|
while IFS= read -r var_name; do
|
|
[[ -z "$var_name" ]] && continue
|
|
var_value=$(ini_get "$REPO_VARS_CONF" "$repo" "$var_name" "")
|
|
if [[ -z "$var_value" ]]; then
|
|
log_warn " ${var_name} has empty value — skipping"
|
|
continue
|
|
fi
|
|
upsert_repo_variable "$repo" "$var_name" "$var_value" || FAILED=$((FAILED + 1))
|
|
done < <(ini_list_keys "$REPO_VARS_CONF" "$repo")
|
|
|
|
done < <(ini_list_sections "$REPO_VARS_CONF")
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Summary
|
|
# ---------------------------------------------------------------------------
|
|
printf '\n'
|
|
log_info "Results: ${SUCCESS} runners deployed, ${FAILED} failures"
|
|
|
|
if [[ $FAILED -gt 0 ]]; then
|
|
log_error "Some operations failed — check logs above"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Phase 11 complete — custom runner infrastructure deployed"
|