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:
288
phase11_custom_runners.sh
Executable file
288
phase11_custom_runners.sh
Executable 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"
|
||||
Reference in New Issue
Block a user