feat: add phases 10-11, enhance phase 8 direct-check mode, and update Caddy migration

- 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>
This commit is contained in:
S
2026-03-03 14:14:11 -06:00
parent 63f5bf6ea7
commit b799cb7970
19 changed files with 1931 additions and 55 deletions

288
phase11_custom_runners.sh Executable file
View File

@@ -0,0 +1,288 @@
#!/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"