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:
@@ -96,6 +96,11 @@ LOCAL_REGISTRY= # Local registry prefix (e.g. registry.local:5
|
|||||||
# AUTO-POPULATED by phase3 scripts — do not fill manually:
|
# AUTO-POPULATED by phase3 scripts — do not fill manually:
|
||||||
GITEA_RUNNER_REGISTRATION_TOKEN= # Retrieved from Gitea admin panel via API
|
GITEA_RUNNER_REGISTRATION_TOKEN= # Retrieved from Gitea admin panel via API
|
||||||
|
|
||||||
|
# Custom runner image build contexts (phase 11)
|
||||||
|
# Absolute paths to directories containing Dockerfiles for custom runner images.
|
||||||
|
GO_NODE_RUNNER_CONTEXT= # Path to Go + Node toolchain Dockerfile (e.g. /path/to/augur/infra/runners)
|
||||||
|
JVM_ANDROID_RUNNER_CONTEXT= # Path to JDK + Android SDK toolchain Dockerfile (e.g. /path/to/periodvault/infra/runners)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# REPOSITORIES
|
# REPOSITORIES
|
||||||
|
|||||||
@@ -278,8 +278,8 @@ _ENV_CONDITIONAL_DB_NAMES=(GITEA_DB_PORT GITEA_DB_NAME GITEA_DB_USER GITEA_DB_PA
|
|||||||
_ENV_CONDITIONAL_DB_TYPES=(port nonempty nonempty password)
|
_ENV_CONDITIONAL_DB_TYPES=(port nonempty nonempty password)
|
||||||
|
|
||||||
# Optional variables — validated only when non-empty (never required).
|
# Optional variables — validated only when non-empty (never required).
|
||||||
_ENV_OPTIONAL_NAMES=(UNRAID_SSH_KEY FEDORA_SSH_KEY LOCAL_REGISTRY)
|
_ENV_OPTIONAL_NAMES=(UNRAID_SSH_KEY FEDORA_SSH_KEY LOCAL_REGISTRY GO_NODE_RUNNER_CONTEXT JVM_ANDROID_RUNNER_CONTEXT)
|
||||||
_ENV_OPTIONAL_TYPES=(optional_path optional_path nonempty)
|
_ENV_OPTIONAL_TYPES=(optional_path optional_path nonempty optional_path optional_path)
|
||||||
|
|
||||||
# Human-readable format hints for error messages.
|
# Human-readable format hints for error messages.
|
||||||
_validator_hint() {
|
_validator_hint() {
|
||||||
|
|||||||
223
lib/phase10_common.sh
Normal file
223
lib/phase10_common.sh
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# lib/phase10_common.sh — Shared helpers for phase 10 local repo cutover
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Shared discovery results (parallel arrays; bash 3.2 compatible).
|
||||||
|
PHASE10_REPO_NAMES=()
|
||||||
|
PHASE10_REPO_PATHS=()
|
||||||
|
PHASE10_GITHUB_URLS=()
|
||||||
|
PHASE10_DUPLICATES=()
|
||||||
|
|
||||||
|
# Parse common git remote URL formats into: host|owner|repo
|
||||||
|
# Supports:
|
||||||
|
# - https://host/owner/repo(.git)
|
||||||
|
# - ssh://git@host/owner/repo(.git)
|
||||||
|
# - git@host:owner/repo(.git)
|
||||||
|
phase10_parse_git_url() {
|
||||||
|
local url="$1"
|
||||||
|
local rest host path owner repo
|
||||||
|
|
||||||
|
if [[ "$url" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*:// ]]; then
|
||||||
|
rest="${url#*://}"
|
||||||
|
# Drop optional userinfo component.
|
||||||
|
rest="${rest#*@}"
|
||||||
|
host="${rest%%/*}"
|
||||||
|
path="${rest#*/}"
|
||||||
|
elif [[ "$url" == *@*:* ]]; then
|
||||||
|
rest="${url#*@}"
|
||||||
|
host="${rest%%:*}"
|
||||||
|
path="${rest#*:}"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
path="${path#/}"
|
||||||
|
path="${path%.git}"
|
||||||
|
owner="${path%%/*}"
|
||||||
|
repo="${path#*/}"
|
||||||
|
repo="${repo%%/*}"
|
||||||
|
|
||||||
|
if [[ -z "$host" ]] || [[ -z "$owner" ]] || [[ -z "$repo" ]] || [[ "$owner" == "$path" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s|%s|%s\n' "$host" "$owner" "$repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
phase10_host_matches() {
|
||||||
|
local host="$1" expected="$2"
|
||||||
|
[[ "$host" == "$expected" ]] || [[ "$host" == "${expected}:"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return 0 when URL matches github.com/<owner>/<repo>.
|
||||||
|
# If <repo> is omitted, only owner is checked.
|
||||||
|
phase10_url_is_github_repo() {
|
||||||
|
local url="$1" owner_expected="$2" repo_expected="${3:-}"
|
||||||
|
local parsed host owner repo
|
||||||
|
|
||||||
|
parsed=$(phase10_parse_git_url "$url" 2>/dev/null) || return 1
|
||||||
|
IFS='|' read -r host owner repo <<< "$parsed"
|
||||||
|
|
||||||
|
phase10_host_matches "$host" "github.com" || return 1
|
||||||
|
[[ "$owner" == "$owner_expected" ]] || return 1
|
||||||
|
if [[ -n "$repo_expected" ]] && [[ "$repo" != "$repo_expected" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
phase10_url_is_gitea_repo() {
|
||||||
|
local url="$1" domain="$2" org="$3" repo_expected="$4"
|
||||||
|
local parsed host owner repo
|
||||||
|
|
||||||
|
parsed=$(phase10_parse_git_url "$url" 2>/dev/null) || return 1
|
||||||
|
IFS='|' read -r host owner repo <<< "$parsed"
|
||||||
|
|
||||||
|
phase10_host_matches "$host" "$domain" || return 1
|
||||||
|
[[ "$owner" == "$org" ]] || return 1
|
||||||
|
[[ "$repo" == "$repo_expected" ]] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
phase10_canonical_github_url() {
|
||||||
|
local owner="$1" repo="$2"
|
||||||
|
printf 'https://github.com/%s/%s.git' "$owner" "$repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
phase10_canonical_gitea_url() {
|
||||||
|
local domain="$1" org="$2" repo="$3"
|
||||||
|
printf 'https://%s/%s/%s.git' "$domain" "$org" "$repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stable in-place sort by repo name (keeps arrays aligned).
|
||||||
|
phase10_sort_repo_arrays() {
|
||||||
|
local i j tmp
|
||||||
|
for ((i = 0; i < ${#PHASE10_REPO_NAMES[@]}; i++)); do
|
||||||
|
for ((j = i + 1; j < ${#PHASE10_REPO_NAMES[@]}; j++)); do
|
||||||
|
if [[ "${PHASE10_REPO_NAMES[$i]}" > "${PHASE10_REPO_NAMES[$j]}" ]]; then
|
||||||
|
tmp="${PHASE10_REPO_NAMES[$i]}"
|
||||||
|
PHASE10_REPO_NAMES[i]="${PHASE10_REPO_NAMES[j]}"
|
||||||
|
PHASE10_REPO_NAMES[j]="$tmp"
|
||||||
|
|
||||||
|
tmp="${PHASE10_REPO_PATHS[i]}"
|
||||||
|
PHASE10_REPO_PATHS[i]="${PHASE10_REPO_PATHS[j]}"
|
||||||
|
PHASE10_REPO_PATHS[j]="$tmp"
|
||||||
|
|
||||||
|
tmp="${PHASE10_GITHUB_URLS[i]}"
|
||||||
|
PHASE10_GITHUB_URLS[i]="${PHASE10_GITHUB_URLS[j]}"
|
||||||
|
PHASE10_GITHUB_URLS[j]="$tmp"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Discover local repos under root that map to github.com/<github_owner>.
|
||||||
|
# Discovery rules:
|
||||||
|
# - Only direct children of root are considered.
|
||||||
|
# - Excludes exclude_path (typically this toolkit repo).
|
||||||
|
# - Accepts a repo if either "github" or "origin" points at GitHub owner.
|
||||||
|
# - Deduplicates by repo slug, preferring directory basename == slug.
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# $1 root dir (e.g., /Users/s/development)
|
||||||
|
# $2 github owner (from GITHUB_USERNAME)
|
||||||
|
# $3 exclude absolute path (optional; pass "" for none)
|
||||||
|
# $4 expected count (0 = don't enforce)
|
||||||
|
phase10_discover_local_repos() {
|
||||||
|
local root="$1"
|
||||||
|
local github_owner="$2"
|
||||||
|
local exclude_path="${3:-}"
|
||||||
|
local expected_count="${4:-0}"
|
||||||
|
|
||||||
|
PHASE10_REPO_NAMES=()
|
||||||
|
PHASE10_REPO_PATHS=()
|
||||||
|
PHASE10_GITHUB_URLS=()
|
||||||
|
PHASE10_DUPLICATES=()
|
||||||
|
|
||||||
|
if [[ ! -d "$root" ]]; then
|
||||||
|
log_error "Local repo root not found: ${root}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local dir top github_url parsed host owner repo canonical
|
||||||
|
local i idx existing existing_base new_base duplicate
|
||||||
|
for dir in "$root"/*; do
|
||||||
|
[[ -d "$dir" ]] || continue
|
||||||
|
if [[ -n "$exclude_path" ]] && [[ "$dir" == "$exclude_path" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
top=$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null || true)
|
||||||
|
[[ "$top" == "$dir" ]] || continue
|
||||||
|
|
||||||
|
github_url=""
|
||||||
|
if github_url=$(git -C "$dir" remote get-url github 2>/dev/null); then
|
||||||
|
if ! phase10_url_is_github_repo "$github_url" "$github_owner"; then
|
||||||
|
github_url=""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$github_url" ]] && github_url=$(git -C "$dir" remote get-url origin 2>/dev/null); then
|
||||||
|
if ! phase10_url_is_github_repo "$github_url" "$github_owner"; then
|
||||||
|
github_url=""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$github_url" ]] || continue
|
||||||
|
|
||||||
|
parsed=$(phase10_parse_git_url "$github_url" 2>/dev/null) || continue
|
||||||
|
IFS='|' read -r host owner repo <<< "$parsed"
|
||||||
|
canonical=$(phase10_canonical_github_url "$owner" "$repo")
|
||||||
|
|
||||||
|
idx=-1
|
||||||
|
for i in "${!PHASE10_REPO_NAMES[@]}"; do
|
||||||
|
if [[ "${PHASE10_REPO_NAMES[$i]}" == "$repo" ]]; then
|
||||||
|
idx="$i"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$idx" -ge 0 ]]; then
|
||||||
|
existing="${PHASE10_REPO_PATHS[$idx]}"
|
||||||
|
existing_base="$(basename "$existing")"
|
||||||
|
new_base="$(basename "$dir")"
|
||||||
|
if [[ "$new_base" == "$repo" ]] && [[ "$existing_base" != "$repo" ]]; then
|
||||||
|
PHASE10_REPO_PATHS[idx]="$dir"
|
||||||
|
PHASE10_GITHUB_URLS[idx]="$canonical"
|
||||||
|
PHASE10_DUPLICATES+=("${repo}: preferred ${dir} over ${existing}")
|
||||||
|
else
|
||||||
|
PHASE10_DUPLICATES+=("${repo}: ignored duplicate ${dir} (using ${existing})")
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
PHASE10_REPO_NAMES+=("$repo")
|
||||||
|
PHASE10_REPO_PATHS+=("$dir")
|
||||||
|
PHASE10_GITHUB_URLS+=("$canonical")
|
||||||
|
done
|
||||||
|
|
||||||
|
phase10_sort_repo_arrays
|
||||||
|
|
||||||
|
for duplicate in "${PHASE10_DUPLICATES[@]}"; do
|
||||||
|
log_info "$duplicate"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${#PHASE10_REPO_NAMES[@]}" -eq 0 ]]; then
|
||||||
|
log_error "No local GitHub repos found under ${root} for owner '${github_owner}'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$expected_count" -gt 0 ]] && [[ "${#PHASE10_REPO_NAMES[@]}" -ne "$expected_count" ]]; then
|
||||||
|
log_error "Expected ${expected_count} local repos under ${root}; found ${#PHASE10_REPO_NAMES[@]}"
|
||||||
|
for i in "${!PHASE10_REPO_NAMES[@]}"; do
|
||||||
|
log_error " - ${PHASE10_REPO_NAMES[$i]} -> ${PHASE10_REPO_PATHS[$i]}"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -73,6 +73,9 @@ parse_runner_entry() {
|
|||||||
# "true" → /Library/LaunchDaemons/ (starts at boot, requires sudo)
|
# "true" → /Library/LaunchDaemons/ (starts at boot, requires sudo)
|
||||||
# "false" (default) → ~/Library/LaunchAgents/ (starts at login)
|
# "false" (default) → ~/Library/LaunchAgents/ (starts at login)
|
||||||
RUNNER_BOOT=$(ini_get "$RUNNERS_CONF" "$target_name" "boot" "false")
|
RUNNER_BOOT=$(ini_get "$RUNNERS_CONF" "$target_name" "boot" "false")
|
||||||
|
# container_options: extra Docker flags for act_runner job containers.
|
||||||
|
# e.g. "--device=/dev/kvm" for KVM passthrough. Ignored for native runners.
|
||||||
|
RUNNER_CONTAINER_OPTIONS=$(ini_get "$RUNNERS_CONF" "$target_name" "container_options" "")
|
||||||
|
|
||||||
# --- Host resolution ---
|
# --- Host resolution ---
|
||||||
# Also resolves RUNNER_COMPOSE_DIR: centralized compose dir on unraid/fedora,
|
# Also resolves RUNNER_COMPOSE_DIR: centralized compose dir on unraid/fedora,
|
||||||
@@ -354,8 +357,9 @@ add_docker_runner() {
|
|||||||
# shellcheck disable=SC2090 # intentional — RUNNER_LABELS_YAML rendered via envsubst
|
# shellcheck disable=SC2090 # intentional — RUNNER_LABELS_YAML rendered via envsubst
|
||||||
export RUNNER_LABELS_YAML
|
export RUNNER_LABELS_YAML
|
||||||
export RUNNER_CAPACITY
|
export RUNNER_CAPACITY
|
||||||
|
export RUNNER_CONTAINER_OPTIONS
|
||||||
render_template "${SCRIPT_DIR}/templates/runner-config.yaml.tpl" "$tmpfile" \
|
render_template "${SCRIPT_DIR}/templates/runner-config.yaml.tpl" "$tmpfile" \
|
||||||
"\${RUNNER_NAME} \${RUNNER_LABELS_YAML} \${RUNNER_CAPACITY}"
|
"\${RUNNER_NAME} \${RUNNER_LABELS_YAML} \${RUNNER_CAPACITY} \${RUNNER_CONTAINER_OPTIONS}"
|
||||||
runner_scp "$tmpfile" "${RUNNER_DATA_PATH}/config.yaml"
|
runner_scp "$tmpfile" "${RUNNER_DATA_PATH}/config.yaml"
|
||||||
rm -f "$tmpfile"
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
@@ -422,9 +426,9 @@ add_native_runner() {
|
|||||||
local tmpfile
|
local tmpfile
|
||||||
tmpfile=$(mktemp)
|
tmpfile=$(mktemp)
|
||||||
# shellcheck disable=SC2090 # intentional — RUNNER_LABELS_YAML rendered via envsubst
|
# shellcheck disable=SC2090 # intentional — RUNNER_LABELS_YAML rendered via envsubst
|
||||||
export RUNNER_NAME RUNNER_DATA_PATH RUNNER_LABELS_YAML RUNNER_CAPACITY
|
export RUNNER_NAME RUNNER_DATA_PATH RUNNER_LABELS_YAML RUNNER_CAPACITY RUNNER_CONTAINER_OPTIONS
|
||||||
render_template "${SCRIPT_DIR}/templates/runner-config.yaml.tpl" "$tmpfile" \
|
render_template "${SCRIPT_DIR}/templates/runner-config.yaml.tpl" "$tmpfile" \
|
||||||
"\${RUNNER_NAME} \${RUNNER_LABELS_YAML} \${RUNNER_CAPACITY}"
|
"\${RUNNER_NAME} \${RUNNER_LABELS_YAML} \${RUNNER_CAPACITY} \${RUNNER_CONTAINER_OPTIONS}"
|
||||||
cp "$tmpfile" "${RUNNER_DATA_PATH}/config.yaml"
|
cp "$tmpfile" "${RUNNER_DATA_PATH}/config.yaml"
|
||||||
rm -f "$tmpfile"
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
|
|||||||
511
phase10_local_repo_cutover.sh
Executable file
511
phase10_local_repo_cutover.sh
Executable file
@@ -0,0 +1,511 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# phase10_local_repo_cutover.sh — Re-point local repos from GitHub to Gitea
|
||||||
|
# Depends on: Phase 8 complete (Gitea publicly reachable) + Phase 4 migrated
|
||||||
|
#
|
||||||
|
# For each discovered local repo under /Users/s/development:
|
||||||
|
# 1. Rename origin -> github (if needed)
|
||||||
|
# 2. Ensure repo exists on Gitea (create if missing)
|
||||||
|
# 3. Add/update origin to point at Gitea
|
||||||
|
# 4. Push all branches and tags to Gitea origin
|
||||||
|
# 5. Ensure every local branch tracks origin/<branch> (Gitea)
|
||||||
|
#
|
||||||
|
# Discovery is based on local git remotes:
|
||||||
|
# - repo root is a direct child of PHASE10_LOCAL_ROOT (default /Users/s/development)
|
||||||
|
# - repo has origin/github pointing to github.com/${GITHUB_USERNAME}/<repo>
|
||||||
|
# - duplicate clones are deduped by repo slug
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/phase10_common.sh"
|
||||||
|
|
||||||
|
load_env
|
||||||
|
require_vars GITEA_ADMIN_TOKEN GITEA_ADMIN_USER GITEA_ORG_NAME GITEA_DOMAIN GITEA_INTERNAL_URL GITHUB_USERNAME
|
||||||
|
|
||||||
|
phase_header 10 "Local Repo Remote Cutover"
|
||||||
|
|
||||||
|
LOCAL_REPO_ROOT="${PHASE10_LOCAL_ROOT:-/Users/s/development}"
|
||||||
|
EXPECTED_REPO_COUNT="${PHASE10_EXPECTED_REPO_COUNT:-3}"
|
||||||
|
DRY_RUN=false
|
||||||
|
ASKPASS_SCRIPT=""
|
||||||
|
PHASE10_GITEA_REPO_EXISTS=false
|
||||||
|
PHASE10_REMOTE_BRANCHES=""
|
||||||
|
PHASE10_REMOTE_TAGS=""
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--local-root=*) LOCAL_REPO_ROOT="${arg#*=}" ;;
|
||||||
|
--expected-count=*) EXPECTED_REPO_COUNT="${arg#*=}" ;;
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
--help|-h)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--local-root=PATH Root folder containing local repos (default: /Users/s/development)
|
||||||
|
--expected-count=N Require exactly N discovered repos (default: 3, 0 disables)
|
||||||
|
--dry-run Print planned actions only (no mutations)
|
||||||
|
--help Show this help
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown argument: $arg"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! [[ "$EXPECTED_REPO_COUNT" =~ ^[0-9]+$ ]]; then
|
||||||
|
log_error "--expected-count must be a non-negative integer"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$ASKPASS_SCRIPT" ]]; then
|
||||||
|
rm -f "$ASKPASS_SCRIPT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
setup_git_auth() {
|
||||||
|
ASKPASS_SCRIPT=$(mktemp)
|
||||||
|
cat > "$ASKPASS_SCRIPT" <<'EOF'
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
case "$1" in
|
||||||
|
*sername*) printf '%s\n' "$GITEA_GIT_USERNAME" ;;
|
||||||
|
*assword*) printf '%s\n' "$GITEA_GIT_TOKEN" ;;
|
||||||
|
*) printf '\n' ;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod 700 "$ASKPASS_SCRIPT"
|
||||||
|
}
|
||||||
|
|
||||||
|
git_with_auth() {
|
||||||
|
GIT_TERMINAL_PROMPT=0 \
|
||||||
|
GIT_ASKPASS="$ASKPASS_SCRIPT" \
|
||||||
|
GITEA_GIT_USERNAME="$GITEA_ADMIN_USER" \
|
||||||
|
GITEA_GIT_TOKEN="$GITEA_ADMIN_TOKEN" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_github_remote() {
|
||||||
|
local repo_path="$1" repo_name="$2" github_url="$3"
|
||||||
|
local existing origin_existing has_bad_github
|
||||||
|
has_bad_github=false
|
||||||
|
|
||||||
|
if existing=$(git -C "$repo_path" remote get-url github 2>/dev/null); then
|
||||||
|
if phase10_url_is_github_repo "$existing" "$GITHUB_USERNAME" "$repo_name"; then
|
||||||
|
if [[ "$existing" != "$github_url" ]]; then
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would set github URL -> ${github_url}"
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote set-url github "$github_url"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
has_bad_github=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if origin_existing=$(git -C "$repo_path" remote get-url origin 2>/dev/null); then
|
||||||
|
if phase10_url_is_github_repo "$origin_existing" "$GITHUB_USERNAME" "$repo_name"; then
|
||||||
|
if [[ "$has_bad_github" == "true" ]]; then
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_warn "${repo_name}: would remove misconfigured 'github' remote and rebuild it from origin"
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote remove github
|
||||||
|
log_warn "${repo_name}: removed misconfigured 'github' remote and rebuilt it from origin"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would rename origin -> github"
|
||||||
|
log_info "${repo_name}: would set github URL -> ${github_url}"
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote rename origin github
|
||||||
|
git -C "$repo_path" remote set-url github "$github_url"
|
||||||
|
log_success "${repo_name}: renamed origin -> github"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$has_bad_github" == "true" ]]; then
|
||||||
|
log_error "${repo_name}: existing 'github' remote does not point to GitHub repo ${GITHUB_USERNAME}/${repo_name}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_error "${repo_name}: could not find GitHub remote in 'origin' or 'github'"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_gitea_origin() {
|
||||||
|
local repo_path="$1" repo_name="$2" gitea_url="$3"
|
||||||
|
local existing
|
||||||
|
|
||||||
|
if existing=$(git -C "$repo_path" remote get-url origin 2>/dev/null); then
|
||||||
|
if phase10_url_is_gitea_repo "$existing" "$GITEA_DOMAIN" "$GITEA_ORG_NAME" "$repo_name"; then
|
||||||
|
if [[ "$existing" != "$gitea_url" ]]; then
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would normalize origin URL -> ${gitea_url}"
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote set-url origin "$gitea_url"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# origin exists but points somewhere else; force it to Gitea.
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would set origin URL -> ${gitea_url}"
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote set-url origin "$gitea_url"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would add origin -> ${gitea_url}"
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote add origin "$gitea_url"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_gitea_repo_exists() {
|
||||||
|
local repo_name="$1"
|
||||||
|
local create_payload http_code
|
||||||
|
|
||||||
|
get_gitea_repo_http_code() {
|
||||||
|
local target_repo="$1"
|
||||||
|
local tmpfile curl_code
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
curl_code=$(curl \
|
||||||
|
-s \
|
||||||
|
-o "$tmpfile" \
|
||||||
|
-w "%{http_code}" \
|
||||||
|
-H "Authorization: token ${GITEA_ADMIN_TOKEN}" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"${GITEA_INTERNAL_URL}/api/v1/repos/${GITEA_ORG_NAME}/${target_repo}") || {
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
printf '%s' "$curl_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
PHASE10_GITEA_REPO_EXISTS=false
|
||||||
|
if ! http_code="$(get_gitea_repo_http_code "$repo_name")"; then
|
||||||
|
log_error "${repo_name}: failed to query Gitea API for repo existence"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" == "200" ]]; then
|
||||||
|
PHASE10_GITEA_REPO_EXISTS=true
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: Gitea repo already exists (${GITEA_ORG_NAME}/${repo_name})"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$http_code" != "404" ]]; then
|
||||||
|
log_error "${repo_name}: unexpected Gitea API status while checking repo (${http_code})"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_payload=$(jq -n \
|
||||||
|
--arg name "$repo_name" \
|
||||||
|
'{name: $name, auto_init: false}')
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would create missing Gitea repo ${GITEA_ORG_NAME}/${repo_name}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if gitea_api POST "/orgs/${GITEA_ORG_NAME}/repos" "$create_payload" >/dev/null 2>&1; then
|
||||||
|
log_success "${repo_name}: created missing Gitea repo ${GITEA_ORG_NAME}/${repo_name}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_error "${repo_name}: failed to create Gitea repo ${GITEA_ORG_NAME}/${repo_name}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
count_items() {
|
||||||
|
local list="$1"
|
||||||
|
if [[ -z "$list" ]]; then
|
||||||
|
printf '0'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$list" | sed '/^$/d' | wc -l | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
list_contains() {
|
||||||
|
local list="$1" needle="$2"
|
||||||
|
[[ -n "$list" ]] && printf '%s\n' "$list" | grep -Fxq "$needle"
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_remote_refs() {
|
||||||
|
local url="$1"
|
||||||
|
local refs ref short
|
||||||
|
|
||||||
|
PHASE10_REMOTE_BRANCHES=""
|
||||||
|
PHASE10_REMOTE_TAGS=""
|
||||||
|
|
||||||
|
refs=$(git_with_auth git ls-remote --heads --tags "$url" 2>/dev/null) || return 1
|
||||||
|
[[ -n "$refs" ]] || return 0
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
ref="${line#*[[:space:]]}"
|
||||||
|
ref="${ref#"${ref%%[![:space:]]*}"}"
|
||||||
|
[[ -n "$ref" ]] || continue
|
||||||
|
case "$ref" in
|
||||||
|
refs/heads/*)
|
||||||
|
short="${ref#refs/heads/}"
|
||||||
|
PHASE10_REMOTE_BRANCHES="${PHASE10_REMOTE_BRANCHES}${short}"$'\n'
|
||||||
|
;;
|
||||||
|
refs/tags/*)
|
||||||
|
short="${ref#refs/tags/}"
|
||||||
|
[[ "$short" == *"^{}" ]] && continue
|
||||||
|
PHASE10_REMOTE_TAGS="${PHASE10_REMOTE_TAGS}${short}"$'\n'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<< "$refs"
|
||||||
|
|
||||||
|
PHASE10_REMOTE_BRANCHES="$(printf '%s' "$PHASE10_REMOTE_BRANCHES" | sed '/^$/d' | LC_ALL=C sort -u)"
|
||||||
|
PHASE10_REMOTE_TAGS="$(printf '%s' "$PHASE10_REMOTE_TAGS" | sed '/^$/d' | LC_ALL=C sort -u)"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_diff_summary() {
|
||||||
|
local repo_name="$1" kind="$2" local_list="$3" remote_list="$4"
|
||||||
|
local missing_count extra_count item
|
||||||
|
local missing_preview="" extra_preview=""
|
||||||
|
local preview_limit=5
|
||||||
|
|
||||||
|
missing_count=0
|
||||||
|
while IFS= read -r item; do
|
||||||
|
[[ -z "$item" ]] && continue
|
||||||
|
if ! list_contains "$remote_list" "$item"; then
|
||||||
|
missing_count=$((missing_count + 1))
|
||||||
|
if [[ "$missing_count" -le "$preview_limit" ]]; then
|
||||||
|
missing_preview="${missing_preview}${item}, "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$local_list"
|
||||||
|
|
||||||
|
extra_count=0
|
||||||
|
while IFS= read -r item; do
|
||||||
|
[[ -z "$item" ]] && continue
|
||||||
|
if ! list_contains "$local_list" "$item"; then
|
||||||
|
extra_count=$((extra_count + 1))
|
||||||
|
if [[ "$extra_count" -le "$preview_limit" ]]; then
|
||||||
|
extra_preview="${extra_preview}${item}, "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$remote_list"
|
||||||
|
|
||||||
|
if [[ "$missing_count" -eq 0 ]] && [[ "$extra_count" -eq 0 ]]; then
|
||||||
|
log_success "${repo_name}: local ${kind}s match Gitea"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$missing_count" -gt 0 ]]; then
|
||||||
|
missing_preview="${missing_preview%, }"
|
||||||
|
log_info "${repo_name}: ${missing_count} ${kind}(s) missing on Gitea"
|
||||||
|
if [[ -n "$missing_preview" ]]; then
|
||||||
|
log_info " missing ${kind} sample: ${missing_preview}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$extra_count" -gt 0 ]]; then
|
||||||
|
extra_preview="${extra_preview%, }"
|
||||||
|
log_info "${repo_name}: ${extra_count} ${kind}(s) exist on Gitea but not locally"
|
||||||
|
if [[ -n "$extra_preview" ]]; then
|
||||||
|
log_info " remote-only ${kind} sample: ${extra_preview}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dry_run_compare_local_and_remote() {
|
||||||
|
local repo_path="$1" repo_name="$2" gitea_url="$3"
|
||||||
|
local local_branches local_tags
|
||||||
|
local local_branch_count local_tag_count remote_branch_count remote_tag_count
|
||||||
|
|
||||||
|
local_branches="$(git -C "$repo_path" for-each-ref --format='%(refname:short)' refs/heads | LC_ALL=C sort -u)"
|
||||||
|
local_tags="$(git -C "$repo_path" tag -l | LC_ALL=C sort -u)"
|
||||||
|
local_branch_count="$(count_items "$local_branches")"
|
||||||
|
local_tag_count="$(count_items "$local_tags")"
|
||||||
|
|
||||||
|
log_info "${repo_name}: local state = ${local_branch_count} branch(es), ${local_tag_count} tag(s)"
|
||||||
|
|
||||||
|
if [[ "$PHASE10_GITEA_REPO_EXISTS" != "true" ]]; then
|
||||||
|
log_info "${repo_name}: remote state = repo missing (would be created)"
|
||||||
|
if [[ "$local_branch_count" -gt 0 ]]; then
|
||||||
|
log_info "${repo_name}: all local branches would be pushed to new Gitea repo"
|
||||||
|
fi
|
||||||
|
if [[ "$local_tag_count" -gt 0 ]]; then
|
||||||
|
log_info "${repo_name}: all local tags would be pushed to new Gitea repo"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! fetch_remote_refs "$gitea_url"; then
|
||||||
|
log_warn "${repo_name}: could not read Gitea refs via ls-remote; skipping diff"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_branch_count="$(count_items "$PHASE10_REMOTE_BRANCHES")"
|
||||||
|
remote_tag_count="$(count_items "$PHASE10_REMOTE_TAGS")"
|
||||||
|
log_info "${repo_name}: remote Gitea state = ${remote_branch_count} branch(es), ${remote_tag_count} tag(s)"
|
||||||
|
|
||||||
|
print_diff_summary "$repo_name" "branch" "$local_branches" "$PHASE10_REMOTE_BRANCHES"
|
||||||
|
print_diff_summary "$repo_name" "tag" "$local_tags" "$PHASE10_REMOTE_TAGS"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_all_refs_to_origin() {
|
||||||
|
local repo_path="$1" repo_name="$2"
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would push all branches to origin"
|
||||||
|
log_info "${repo_name}: would push all tags to origin"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git_with_auth git -C "$repo_path" push --all origin >/dev/null; then
|
||||||
|
log_error "${repo_name}: failed pushing branches to Gitea origin"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! git_with_auth git -C "$repo_path" push --tags origin >/dev/null; then
|
||||||
|
log_error "${repo_name}: failed pushing tags to Gitea origin"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
retarget_tracking_to_origin() {
|
||||||
|
local repo_path="$1" repo_name="$2"
|
||||||
|
local branch upstream_remote upstream_short branch_count
|
||||||
|
branch_count=0
|
||||||
|
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
[[ -z "$branch" ]] && continue
|
||||||
|
branch_count=$((branch_count + 1))
|
||||||
|
|
||||||
|
if ! git -C "$repo_path" show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
|
||||||
|
# A local branch can exist without an origin ref if it never got pushed.
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would create origin/${branch} by pushing local ${branch}"
|
||||||
|
else
|
||||||
|
if ! git_with_auth git -C "$repo_path" push origin "refs/heads/${branch}:refs/heads/${branch}" >/dev/null; then
|
||||||
|
log_error "${repo_name}: could not create origin/${branch} while setting tracking"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_info "${repo_name}: would set upstream ${branch} -> origin/${branch}"
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
if ! git -C "$repo_path" branch --set-upstream-to="origin/${branch}" "$branch" >/dev/null 2>&1; then
|
||||||
|
log_error "${repo_name}: failed to set upstream for branch '${branch}' to origin/${branch}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
upstream_remote=$(git -C "$repo_path" for-each-ref --format='%(upstream:remotename)' "refs/heads/${branch}")
|
||||||
|
upstream_short=$(git -C "$repo_path" for-each-ref --format='%(upstream:short)' "refs/heads/${branch}")
|
||||||
|
if [[ "$upstream_remote" != "origin" ]] || [[ "$upstream_short" != "origin/${branch}" ]]; then
|
||||||
|
log_error "${repo_name}: branch '${branch}' upstream is '${upstream_short:-<none>}' (expected origin/${branch})"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(git -C "$repo_path" for-each-ref --format='%(refname:short)' refs/heads)
|
||||||
|
|
||||||
|
if [[ "$branch_count" -eq 0 ]]; then
|
||||||
|
log_warn "${repo_name}: no local branches found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! phase10_discover_local_repos "$LOCAL_REPO_ROOT" "$GITHUB_USERNAME" "$SCRIPT_DIR" "$EXPECTED_REPO_COUNT"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Discovered ${#PHASE10_REPO_NAMES[@]} local repos in ${LOCAL_REPO_ROOT}"
|
||||||
|
for i in "${!PHASE10_REPO_NAMES[@]}"; do
|
||||||
|
log_info " - ${PHASE10_REPO_NAMES[$i]} -> ${PHASE10_REPO_PATHS[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
setup_git_auth
|
||||||
|
|
||||||
|
SUCCESS=0
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
for i in "${!PHASE10_REPO_NAMES[@]}"; do
|
||||||
|
repo_name="${PHASE10_REPO_NAMES[$i]}"
|
||||||
|
repo_path="${PHASE10_REPO_PATHS[$i]}"
|
||||||
|
github_url="${PHASE10_GITHUB_URLS[$i]}"
|
||||||
|
gitea_url="$(phase10_canonical_gitea_url "$GITEA_DOMAIN" "$GITEA_ORG_NAME" "$repo_name")"
|
||||||
|
|
||||||
|
log_info "--- Processing repo: ${repo_name} (${repo_path}) ---"
|
||||||
|
|
||||||
|
if ! ensure_github_remote "$repo_path" "$repo_name" "$github_url"; then
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ensure_gitea_repo_exists "$repo_name"; then
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
dry_run_compare_local_and_remote "$repo_path" "$repo_name" "$gitea_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ensure_gitea_origin "$repo_path" "$repo_name" "$gitea_url"; then
|
||||||
|
log_error "${repo_name}: failed to set origin to ${gitea_url}"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! push_all_refs_to_origin "$repo_path" "$repo_name"; then
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! retarget_tracking_to_origin "$repo_path" "$repo_name"; then
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
log_success "${repo_name}: dry-run plan complete"
|
||||||
|
else
|
||||||
|
log_success "${repo_name}: origin now points to Gitea and tracking updated"
|
||||||
|
fi
|
||||||
|
SUCCESS=$((SUCCESS + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
TOTAL=${#PHASE10_REPO_NAMES[@]}
|
||||||
|
log_info "Results: ${SUCCESS} succeeded, ${FAILED} failed (out of ${TOTAL})"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
if [[ "$FAILED" -gt 0 ]]; then
|
||||||
|
log_error "Phase 10 dry-run found ${FAILED} error(s); no changes were made"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "Phase 10 dry-run complete — no changes were made"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$FAILED" -gt 0 ]]; then
|
||||||
|
log_error "Phase 10 failed for one or more repos"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Phase 10 complete — local repos now push/track via Gitea origin"
|
||||||
112
phase10_post_check.sh
Executable file
112
phase10_post_check.sh
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# phase10_post_check.sh — Verify local repo remote cutover to Gitea
|
||||||
|
# Checks for each discovered local repo:
|
||||||
|
# 1. origin points to Gitea org/repo
|
||||||
|
# 2. github points to GitHub owner/repo
|
||||||
|
# 3. every local branch tracks origin/<branch>
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/phase10_common.sh"
|
||||||
|
|
||||||
|
load_env
|
||||||
|
require_vars GITEA_ORG_NAME GITEA_DOMAIN GITHUB_USERNAME
|
||||||
|
|
||||||
|
phase_header 10 "Local Repo Remote Cutover — Post-Check"
|
||||||
|
|
||||||
|
LOCAL_REPO_ROOT="${PHASE10_LOCAL_ROOT:-/Users/s/development}"
|
||||||
|
EXPECTED_REPO_COUNT="${PHASE10_EXPECTED_REPO_COUNT:-3}"
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--local-root=*) LOCAL_REPO_ROOT="${arg#*=}" ;;
|
||||||
|
--expected-count=*) EXPECTED_REPO_COUNT="${arg#*=}" ;;
|
||||||
|
--help|-h)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--local-root=PATH Root folder containing local repos (default: /Users/s/development)
|
||||||
|
--expected-count=N Require exactly N discovered repos (default: 3, 0 disables)
|
||||||
|
--help Show this help
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown argument: $arg"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! [[ "$EXPECTED_REPO_COUNT" =~ ^[0-9]+$ ]]; then
|
||||||
|
log_error "--expected-count must be a non-negative integer"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! phase10_discover_local_repos "$LOCAL_REPO_ROOT" "$GITHUB_USERNAME" "$SCRIPT_DIR" "$EXPECTED_REPO_COUNT"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
for i in "${!PHASE10_REPO_NAMES[@]}"; do
|
||||||
|
repo_name="${PHASE10_REPO_NAMES[$i]}"
|
||||||
|
repo_path="${PHASE10_REPO_PATHS[$i]}"
|
||||||
|
github_url="${PHASE10_GITHUB_URLS[$i]}"
|
||||||
|
gitea_url="$(phase10_canonical_gitea_url "$GITEA_DOMAIN" "$GITEA_ORG_NAME" "$repo_name")"
|
||||||
|
|
||||||
|
log_info "--- Checking repo: ${repo_name} (${repo_path}) ---"
|
||||||
|
|
||||||
|
origin_url="$(git -C "$repo_path" remote get-url origin 2>/dev/null || true)"
|
||||||
|
if [[ -n "$origin_url" ]] && phase10_url_is_gitea_repo "$origin_url" "$GITEA_DOMAIN" "$GITEA_ORG_NAME" "$repo_name"; then
|
||||||
|
log_success "origin points to Gitea (${gitea_url})"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
log_error "FAIL: origin does not point to ${gitea_url} (found: ${origin_url:-<missing>})"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
github_remote_url="$(git -C "$repo_path" remote get-url github 2>/dev/null || true)"
|
||||||
|
if [[ -n "$github_remote_url" ]] && phase10_url_is_github_repo "$github_remote_url" "$GITHUB_USERNAME" "$repo_name"; then
|
||||||
|
log_success "github points to GitHub (${github_url})"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
log_error "FAIL: github does not point to ${github_url} (found: ${github_remote_url:-<missing>})"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch_count=0
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
[[ -z "$branch" ]] && continue
|
||||||
|
branch_count=$((branch_count + 1))
|
||||||
|
upstream_remote=$(git -C "$repo_path" for-each-ref --format='%(upstream:remotename)' "refs/heads/${branch}")
|
||||||
|
upstream_short=$(git -C "$repo_path" for-each-ref --format='%(upstream:short)' "refs/heads/${branch}")
|
||||||
|
if [[ "$upstream_remote" == "origin" ]] && [[ "$upstream_short" == "origin/${branch}" ]]; then
|
||||||
|
log_success "branch ${branch} tracks origin/${branch}"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
log_error "FAIL: branch ${branch} tracks ${upstream_short:-<none>} (expected origin/${branch})"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
done < <(git -C "$repo_path" for-each-ref --format='%(refname:short)' refs/heads)
|
||||||
|
|
||||||
|
if [[ "$branch_count" -eq 0 ]]; then
|
||||||
|
log_warn "No local branches found in ${repo_name}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
log_info "Results: ${PASS} passed, ${FAIL} failed"
|
||||||
|
|
||||||
|
if [[ "$FAIL" -gt 0 ]]; then
|
||||||
|
log_error "Phase 10 post-check FAILED"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Phase 10 post-check PASSED — local repos track Gitea origin"
|
||||||
172
phase10_teardown.sh
Executable file
172
phase10_teardown.sh
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# phase10_teardown.sh — Reverse local repo remote cutover from phase 10
|
||||||
|
# Reverts local repos so GitHub is origin again:
|
||||||
|
# 1. Move Gitea origin -> gitea (if present)
|
||||||
|
# 2. Move github -> origin
|
||||||
|
# 3. Set local branch upstreams to origin/<branch> where available
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/phase10_common.sh"
|
||||||
|
|
||||||
|
load_env
|
||||||
|
require_vars GITEA_ORG_NAME GITEA_DOMAIN GITHUB_USERNAME
|
||||||
|
|
||||||
|
phase_header 10 "Local Repo Remote Cutover — Teardown"
|
||||||
|
|
||||||
|
LOCAL_REPO_ROOT="${PHASE10_LOCAL_ROOT:-/Users/s/development}"
|
||||||
|
EXPECTED_REPO_COUNT="${PHASE10_EXPECTED_REPO_COUNT:-3}"
|
||||||
|
AUTO_YES=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--local-root=*) LOCAL_REPO_ROOT="${arg#*=}" ;;
|
||||||
|
--expected-count=*) EXPECTED_REPO_COUNT="${arg#*=}" ;;
|
||||||
|
--yes|-y) AUTO_YES=true ;;
|
||||||
|
--help|-h)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--local-root=PATH Root folder containing local repos (default: /Users/s/development)
|
||||||
|
--expected-count=N Require exactly N discovered repos (default: 3, 0 disables)
|
||||||
|
--yes, -y Skip confirmation prompt
|
||||||
|
--help Show this help
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown argument: $arg"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! [[ "$EXPECTED_REPO_COUNT" =~ ^[0-9]+$ ]]; then
|
||||||
|
log_error "--expected-count must be a non-negative integer"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$AUTO_YES" != "true" ]]; then
|
||||||
|
log_warn "This will revert local repo remotes so GitHub is origin again."
|
||||||
|
printf 'Continue? [y/N] ' >&2
|
||||||
|
read -r confirm
|
||||||
|
if [[ "$confirm" != "y" ]] && [[ "$confirm" != "Y" ]]; then
|
||||||
|
log_info "Teardown cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! phase10_discover_local_repos "$LOCAL_REPO_ROOT" "$GITHUB_USERNAME" "$SCRIPT_DIR" "$EXPECTED_REPO_COUNT"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set_tracking_to_origin_where_available() {
|
||||||
|
local repo_path="$1" repo_name="$2"
|
||||||
|
local branch branch_count
|
||||||
|
branch_count=0
|
||||||
|
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
[[ -z "$branch" ]] && continue
|
||||||
|
branch_count=$((branch_count + 1))
|
||||||
|
|
||||||
|
if git -C "$repo_path" show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
|
||||||
|
if git -C "$repo_path" branch --set-upstream-to="origin/${branch}" "$branch" >/dev/null 2>&1; then
|
||||||
|
log_success "${repo_name}: branch ${branch} now tracks origin/${branch}"
|
||||||
|
else
|
||||||
|
log_warn "${repo_name}: could not set upstream for ${branch}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warn "${repo_name}: origin/${branch} not found (upstream unchanged)"
|
||||||
|
fi
|
||||||
|
done < <(git -C "$repo_path" for-each-ref --format='%(refname:short)' refs/heads)
|
||||||
|
|
||||||
|
if [[ "$branch_count" -eq 0 ]]; then
|
||||||
|
log_warn "${repo_name}: no local branches found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_origin_is_github() {
|
||||||
|
local repo_path="$1" repo_name="$2" github_url="$3" gitea_url="$4"
|
||||||
|
local origin_url github_url_existing gitea_url_existing
|
||||||
|
|
||||||
|
origin_url="$(git -C "$repo_path" remote get-url origin 2>/dev/null || true)"
|
||||||
|
github_url_existing="$(git -C "$repo_path" remote get-url github 2>/dev/null || true)"
|
||||||
|
gitea_url_existing="$(git -C "$repo_path" remote get-url gitea 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -n "$origin_url" ]]; then
|
||||||
|
if phase10_url_is_github_repo "$origin_url" "$GITHUB_USERNAME" "$repo_name"; then
|
||||||
|
git -C "$repo_path" remote set-url origin "$github_url"
|
||||||
|
if [[ -n "$github_url_existing" ]]; then
|
||||||
|
git -C "$repo_path" remote remove github
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if phase10_url_is_gitea_repo "$origin_url" "$GITEA_DOMAIN" "$GITEA_ORG_NAME" "$repo_name"; then
|
||||||
|
if [[ -z "$gitea_url_existing" ]]; then
|
||||||
|
git -C "$repo_path" remote rename origin gitea
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote set-url gitea "$gitea_url"
|
||||||
|
git -C "$repo_path" remote remove origin
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "${repo_name}: origin remote is unexpected (${origin_url})"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git -C "$repo_path" remote get-url origin >/dev/null 2>&1; then
|
||||||
|
:
|
||||||
|
elif [[ -n "$github_url_existing" ]]; then
|
||||||
|
git -C "$repo_path" remote rename github origin
|
||||||
|
else
|
||||||
|
git -C "$repo_path" remote add origin "$github_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$repo_path" remote set-url origin "$github_url"
|
||||||
|
|
||||||
|
if git -C "$repo_path" remote get-url github >/dev/null 2>&1; then
|
||||||
|
git -C "$repo_path" remote remove github
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git -C "$repo_path" remote get-url gitea >/dev/null 2>&1; then
|
||||||
|
git -C "$repo_path" remote set-url gitea "$gitea_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
SUCCESS=0
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
for i in "${!PHASE10_REPO_NAMES[@]}"; do
|
||||||
|
repo_name="${PHASE10_REPO_NAMES[$i]}"
|
||||||
|
repo_path="${PHASE10_REPO_PATHS[$i]}"
|
||||||
|
github_url="${PHASE10_GITHUB_URLS[$i]}"
|
||||||
|
gitea_url="$(phase10_canonical_gitea_url "$GITEA_DOMAIN" "$GITEA_ORG_NAME" "$repo_name")"
|
||||||
|
|
||||||
|
log_info "--- Reverting repo: ${repo_name} (${repo_path}) ---"
|
||||||
|
if ! ensure_origin_is_github "$repo_path" "$repo_name" "$github_url" "$gitea_url"; then
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
set_tracking_to_origin_where_available "$repo_path" "$repo_name"
|
||||||
|
SUCCESS=$((SUCCESS + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
TOTAL=${#PHASE10_REPO_NAMES[@]}
|
||||||
|
log_info "Results: ${SUCCESS} reverted, ${FAILED} failed (out of ${TOTAL})"
|
||||||
|
|
||||||
|
if [[ "$FAILED" -gt 0 ]]; then
|
||||||
|
log_error "Phase 10 teardown completed with failures"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Phase 10 teardown complete"
|
||||||
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"
|
||||||
204
phase11_post_check.sh
Executable file
204
phase11_post_check.sh
Executable file
@@ -0,0 +1,204 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# phase11_post_check.sh — Verify custom runner infrastructure deployment
|
||||||
|
# Checks:
|
||||||
|
# 1. Toolchain images exist on Unraid
|
||||||
|
# 2. All phase 11 runners registered and online in Gitea
|
||||||
|
# 3. Shared macOS runner has correct labels
|
||||||
|
# 4. Repository variables set correctly
|
||||||
|
# 5. KVM available on Unraid (warning only)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
phase_header 11 "Custom Runners — Post-Check"
|
||||||
|
|
||||||
|
REPO_VARS_CONF="${SCRIPT_DIR}/repo_variables.conf"
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
WARN=0
|
||||||
|
|
||||||
|
run_check() {
|
||||||
|
local desc="$1"
|
||||||
|
shift
|
||||||
|
if "$@"; then
|
||||||
|
log_success "$desc"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
log_error "FAIL: $desc"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_warn_check() {
|
||||||
|
local desc="$1"
|
||||||
|
shift
|
||||||
|
if "$@"; then
|
||||||
|
log_success "$desc"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
log_warn "WARN: $desc"
|
||||||
|
WARN=$((WARN + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Check 1: Toolchain images exist on Unraid
|
||||||
|
# =========================================================================
|
||||||
|
log_info "--- Checking toolchain images ---"
|
||||||
|
|
||||||
|
check_image() {
|
||||||
|
local tag="$1"
|
||||||
|
ssh_exec "UNRAID" "docker image inspect '${tag}' >/dev/null 2>&1"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_check "Image go-node-runner:latest exists on Unraid" check_image "go-node-runner:latest"
|
||||||
|
run_check "Image jvm-android-runner:slim exists on Unraid" check_image "jvm-android-runner:slim"
|
||||||
|
run_check "Image jvm-android-runner:full exists on Unraid" check_image "jvm-android-runner:full"
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Check 2: All phase 11 runners registered and online
|
||||||
|
# =========================================================================
|
||||||
|
log_info "--- Checking runner status ---"
|
||||||
|
|
||||||
|
# Fetch all runners from Gitea admin API (single call)
|
||||||
|
ALL_RUNNERS=$(gitea_api GET "/admin/runners" 2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
check_runner_online() {
|
||||||
|
local name="$1"
|
||||||
|
local status
|
||||||
|
status=$(printf '%s' "$ALL_RUNNERS" | jq -r --arg n "$name" \
|
||||||
|
'[.[] | select(.name == $n)] | .[0].status // "not-found"' 2>/dev/null)
|
||||||
|
if [[ "$status" == "not-found" ]] || [[ -z "$status" ]]; then
|
||||||
|
log_error " Runner '${name}' not found in Gitea"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ "$status" == "offline" ]] || [[ "$status" == "2" ]]; then
|
||||||
|
log_error " Runner '${name}' is offline"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
PHASE11_RUNNERS=(
|
||||||
|
macbook-runner
|
||||||
|
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 in "${PHASE11_RUNNERS[@]}"; do
|
||||||
|
run_check "Runner '${runner}' registered and online" check_runner_online "$runner"
|
||||||
|
done
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Check 3: Shared macOS runner has correct labels
|
||||||
|
# =========================================================================
|
||||||
|
log_info "--- Checking macOS runner labels ---"
|
||||||
|
|
||||||
|
check_mac_labels() {
|
||||||
|
local labels
|
||||||
|
labels=$(printf '%s' "$ALL_RUNNERS" | jq -r \
|
||||||
|
'[.[] | select(.name == "macbook-runner")] | .[0].labels // [] | .[].name' 2>/dev/null)
|
||||||
|
local missing=0
|
||||||
|
for expected in "self-hosted" "macOS" "ARM64"; do
|
||||||
|
if ! printf '%s' "$labels" | grep -qx "$expected" 2>/dev/null; then
|
||||||
|
log_error " macbook-runner missing label: ${expected}"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return "$missing"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_check "macbook-runner has labels: self-hosted, macOS, ARM64" check_mac_labels
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Check 4: Repository variables set correctly
|
||||||
|
# =========================================================================
|
||||||
|
log_info "--- Checking repository variables ---"
|
||||||
|
|
||||||
|
check_repo_variable() {
|
||||||
|
local repo="$1" var_name="$2" expected="$3"
|
||||||
|
local owner="${GITEA_ORG_NAME}"
|
||||||
|
local response
|
||||||
|
if ! response=$(gitea_api GET "/repos/${owner}/${repo}/actions/variables/${var_name}" 2>/dev/null); then
|
||||||
|
log_error " Variable ${var_name} not found on ${repo}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local actual
|
||||||
|
actual=$(printf '%s' "$response" | jq -r '.value // .data // empty' 2>/dev/null)
|
||||||
|
if [[ "$actual" != "$expected" ]]; then
|
||||||
|
log_error " Variable ${var_name} on ${repo}: expected '${expected}', got '${actual}'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -f "$REPO_VARS_CONF" ]]; then
|
||||||
|
while IFS= read -r repo; do
|
||||||
|
[[ -z "$repo" ]] && continue
|
||||||
|
# Read all keys from the section using inline parsing
|
||||||
|
local_in_section=false
|
||||||
|
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]}" == "$repo" ]]; then
|
||||||
|
local_in_section=true
|
||||||
|
elif $local_in_section; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if $local_in_section && [[ "$line" =~ ^([^=]+)=(.*) ]]; then
|
||||||
|
k="${BASH_REMATCH[1]}"
|
||||||
|
v="${BASH_REMATCH[2]}"
|
||||||
|
k="${k#"${k%%[![:space:]]*}"}"
|
||||||
|
k="${k%"${k##*[![:space:]]}"}"
|
||||||
|
v="${v#"${v%%[![:space:]]*}"}"
|
||||||
|
v="${v%"${v##*[![:space:]]}"}"
|
||||||
|
run_check "Variable ${k} on ${repo}" check_repo_variable "$repo" "$k" "$v"
|
||||||
|
fi
|
||||||
|
done < "$REPO_VARS_CONF"
|
||||||
|
done < <(ini_list_sections "$REPO_VARS_CONF")
|
||||||
|
else
|
||||||
|
log_warn "repo_variables.conf not found — skipping variable checks"
|
||||||
|
WARN=$((WARN + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Check 5: KVM available on Unraid
|
||||||
|
# =========================================================================
|
||||||
|
log_info "--- Checking KVM availability ---"
|
||||||
|
|
||||||
|
check_kvm() {
|
||||||
|
ssh_exec "UNRAID" "test -c /dev/kvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_warn_check "KVM device available on Unraid (/dev/kvm)" check_kvm
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
printf '\n'
|
||||||
|
TOTAL=$((PASS + FAIL + WARN))
|
||||||
|
log_info "Results: ${PASS} passed, ${FAIL} failed, ${WARN} warnings (out of ${TOTAL})"
|
||||||
|
|
||||||
|
if [[ $FAIL -gt 0 ]]; then
|
||||||
|
log_error "Some checks failed — review above"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Phase 11 post-check complete"
|
||||||
185
phase11_teardown.sh
Executable file
185
phase11_teardown.sh
Executable file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# phase11_teardown.sh — Remove custom runner infrastructure deployed by phase 11
|
||||||
|
# Reverses:
|
||||||
|
# 1. Repository variables
|
||||||
|
# 2. Docker runners (per-repo + shared emulator)
|
||||||
|
# 3. Shared macOS runner → restore original per-repo macOS runners
|
||||||
|
# 4. Toolchain images on Unraid
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
phase_header 11 "Custom Runners — Teardown"
|
||||||
|
|
||||||
|
REPO_VARS_CONF="${SCRIPT_DIR}/repo_variables.conf"
|
||||||
|
AUTO_YES=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--yes|-y) AUTO_YES=true ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$AUTO_YES" != "true" ]]; then
|
||||||
|
log_warn "This will remove all phase 11 custom runners and repo variables."
|
||||||
|
printf 'Continue? [y/N] ' >&2
|
||||||
|
read -r confirm
|
||||||
|
if [[ "$confirm" != "y" ]] && [[ "$confirm" != "Y" ]]; then
|
||||||
|
log_info "Aborted"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
REMOVED=0
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 1: Delete repository variables
|
||||||
|
# =========================================================================
|
||||||
|
log_step 1 "Removing repository variables"
|
||||||
|
|
||||||
|
if [[ -f "$REPO_VARS_CONF" ]]; then
|
||||||
|
while IFS= read -r repo; do
|
||||||
|
[[ -z "$repo" ]] && continue
|
||||||
|
log_info "--- Removing variables for repo: ${repo} ---"
|
||||||
|
|
||||||
|
# Parse keys from section
|
||||||
|
in_section=false
|
||||||
|
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]}" == "$repo" ]]; 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:]]}"}"
|
||||||
|
if gitea_api DELETE "/repos/${GITEA_ORG_NAME}/${repo}/actions/variables/${k}" >/dev/null 2>&1; then
|
||||||
|
log_success " Deleted ${k} from ${repo}"
|
||||||
|
REMOVED=$((REMOVED + 1))
|
||||||
|
else
|
||||||
|
log_warn " Could not delete ${k} from ${repo} (may not exist)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$REPO_VARS_CONF"
|
||||||
|
|
||||||
|
done < <(ini_list_sections "$REPO_VARS_CONF")
|
||||||
|
else
|
||||||
|
log_info "repo_variables.conf not found — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 2: Remove Docker runners
|
||||||
|
# =========================================================================
|
||||||
|
log_step 2 "Removing Docker runners"
|
||||||
|
|
||||||
|
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 "Removing runner '${runner_name}'..."
|
||||||
|
if "${SCRIPT_DIR}/manage_runner.sh" remove --name "$runner_name" 2>/dev/null; then
|
||||||
|
log_success "Removed ${runner_name}"
|
||||||
|
REMOVED=$((REMOVED + 1))
|
||||||
|
else
|
||||||
|
log_warn "Could not remove ${runner_name} (may not exist)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 3: Remove shared macOS runner, restore original per-repo runners
|
||||||
|
# =========================================================================
|
||||||
|
log_step 3 "Restoring original macOS runner configuration"
|
||||||
|
|
||||||
|
# Remove shared runner
|
||||||
|
if launchctl list 2>/dev/null | grep -q "com.gitea.runner.macbook-runner"; then
|
||||||
|
log_info "Removing shared macOS runner 'macbook-runner'..."
|
||||||
|
"${SCRIPT_DIR}/manage_runner.sh" remove --name macbook-runner 2>/dev/null || true
|
||||||
|
REMOVED=$((REMOVED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Note: original per-repo macOS runner sections were replaced in runners.conf
|
||||||
|
# during phase 11. They need to be re-added manually or by re-running
|
||||||
|
# configure_runners.sh. This teardown only cleans up deployed resources.
|
||||||
|
log_info "Note: original macOS runner sections (macbook-runner-periodvault,"
|
||||||
|
log_info " macbook-runner-intermittent-fasting-tracker) must be restored in"
|
||||||
|
log_info " runners.conf manually or via git checkout."
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 4: Remove toolchain images from Unraid
|
||||||
|
# =========================================================================
|
||||||
|
log_step 4 "Removing toolchain images from Unraid"
|
||||||
|
|
||||||
|
IMAGES_TO_REMOVE=(
|
||||||
|
"go-node-runner:latest"
|
||||||
|
"jvm-android-runner:slim"
|
||||||
|
"jvm-android-runner:full"
|
||||||
|
)
|
||||||
|
|
||||||
|
for img in "${IMAGES_TO_REMOVE[@]}"; do
|
||||||
|
if ssh_exec "UNRAID" "docker rmi '${img}' 2>/dev/null"; then
|
||||||
|
log_success "Removed image ${img}"
|
||||||
|
REMOVED=$((REMOVED + 1))
|
||||||
|
else
|
||||||
|
log_warn "Could not remove image ${img} (may not exist or in use)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 5: Remove phase 11 runner sections from runners.conf
|
||||||
|
# =========================================================================
|
||||||
|
log_step 5 "Cleaning runners.conf"
|
||||||
|
|
||||||
|
RUNNERS_CONF="${SCRIPT_DIR}/runners.conf"
|
||||||
|
PHASE11_SECTIONS=(
|
||||||
|
unraid-go-node-1
|
||||||
|
unraid-go-node-2
|
||||||
|
unraid-go-node-3
|
||||||
|
unraid-jvm-slim-1
|
||||||
|
unraid-jvm-slim-2
|
||||||
|
unraid-android-emulator
|
||||||
|
macbook-runner
|
||||||
|
)
|
||||||
|
|
||||||
|
for section in "${PHASE11_SECTIONS[@]}"; do
|
||||||
|
if ini_list_sections "$RUNNERS_CONF" | grep -qx "$section" 2>/dev/null; then
|
||||||
|
ini_remove_section "$RUNNERS_CONF" "$section"
|
||||||
|
log_success "Removed [${section}] from runners.conf"
|
||||||
|
REMOVED=$((REMOVED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
printf '\n'
|
||||||
|
log_info "Results: ${REMOVED} items removed, ${FAILED} failures"
|
||||||
|
|
||||||
|
if [[ $FAILED -gt 0 ]]; then
|
||||||
|
log_error "Some removals failed — check logs above"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Phase 11 teardown complete"
|
||||||
@@ -87,7 +87,7 @@ phase_header "7.5" "Nginx to Caddy Migration (Multi-domain)"
|
|||||||
|
|
||||||
# host|upstream|streaming(true/false)|body_limit|insecure_skip_verify(true/false)
|
# host|upstream|streaming(true/false)|body_limit|insecure_skip_verify(true/false)
|
||||||
FULL_HOST_MAP=(
|
FULL_HOST_MAP=(
|
||||||
"ai.sintheus.com|http://192.168.1.82:8181|true|50MB|false"
|
"ai.sintheus.com|http://192.168.1.82:8181 http://192.168.1.83:8181|true|50MB|false"
|
||||||
"photos.sintheus.com|http://192.168.1.222:2283|false|50GB|false"
|
"photos.sintheus.com|http://192.168.1.222:2283|false|50GB|false"
|
||||||
"fin.sintheus.com|http://192.168.1.233:8096|true||false"
|
"fin.sintheus.com|http://192.168.1.233:8096|true||false"
|
||||||
"disk.sintheus.com|http://192.168.1.52:80|false|20GB|false"
|
"disk.sintheus.com|http://192.168.1.52:80|false|20GB|false"
|
||||||
@@ -95,11 +95,11 @@ FULL_HOST_MAP=(
|
|||||||
"plex.sintheus.com|http://192.168.1.111:32400|true||false"
|
"plex.sintheus.com|http://192.168.1.111:32400|true||false"
|
||||||
"sync.sintheus.com|http://192.168.1.119:8384|false||false"
|
"sync.sintheus.com|http://192.168.1.119:8384|false||false"
|
||||||
"syno.sintheus.com|https://100.108.182.16:5001|false||true"
|
"syno.sintheus.com|https://100.108.182.16:5001|false||true"
|
||||||
"tower.sintheus.com|https://192.168.1.82:443|false||true"
|
"tower.sintheus.com|https://192.168.1.82:443 https://192.168.1.83:443|false||true"
|
||||||
)
|
)
|
||||||
|
|
||||||
CANARY_HOST_MAP=(
|
CANARY_HOST_MAP=(
|
||||||
"tower.sintheus.com|https://192.168.1.82:443|false||true"
|
"tower.sintheus.com|https://192.168.1.82:443 https://192.168.1.83:443|false||true"
|
||||||
)
|
)
|
||||||
|
|
||||||
GITEA_ENTRY="${GITEA_DOMAIN}|http://${UNRAID_GITEA_IP}:3000|false||false"
|
GITEA_ENTRY="${GITEA_DOMAIN}|http://${UNRAID_GITEA_IP}:3000|false||false"
|
||||||
@@ -175,7 +175,11 @@ emit_site_block_standalone() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
echo "${host} {"
|
echo "${host} {"
|
||||||
if [[ "$TLS_MODE" == "existing" ]]; then
|
if [[ "$TLS_MODE" == "cloudflare" ]]; then
|
||||||
|
echo " tls {"
|
||||||
|
echo " dns cloudflare {env.CF_API_TOKEN}"
|
||||||
|
echo " }"
|
||||||
|
elif [[ "$TLS_MODE" == "existing" ]]; then
|
||||||
echo " tls ${SSL_CERT_PATH} ${SSL_KEY_PATH}"
|
echo " tls ${SSL_CERT_PATH} ${SSL_KEY_PATH}"
|
||||||
fi
|
fi
|
||||||
echo " encode zstd gzip"
|
echo " encode zstd gzip"
|
||||||
@@ -524,15 +528,19 @@ probe_http_code_ok() {
|
|||||||
|
|
||||||
probe_host_via_caddy() {
|
probe_host_via_caddy() {
|
||||||
local host="$1" upstream="$2" role="$3"
|
local host="$1" upstream="$2" role="$3"
|
||||||
|
local max_attempts="${4:-5}" wait_secs="${5:-5}"
|
||||||
local path="/"
|
local path="/"
|
||||||
if [[ "$role" == "gitea_api" ]]; then
|
if [[ "$role" == "gitea_api" ]]; then
|
||||||
path="/api/v1/version"
|
path="/api/v1/version"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local tmp_body http_code
|
local tmp_body http_code attempt
|
||||||
tmp_body=$(mktemp)
|
tmp_body=$(mktemp)
|
||||||
|
|
||||||
|
for (( attempt=1; attempt<=max_attempts; attempt++ )); do
|
||||||
http_code=$(curl -sk --resolve "${host}:443:${UNRAID_CADDY_IP}" \
|
http_code=$(curl -sk --resolve "${host}:443:${UNRAID_CADDY_IP}" \
|
||||||
-o "$tmp_body" -w "%{http_code}" "https://${host}${path}" 2>/dev/null || echo "000")
|
-o "$tmp_body" -w "%{http_code}" "https://${host}${path}" 2>/dev/null) || true
|
||||||
|
[[ -z "$http_code" ]] && http_code="000"
|
||||||
|
|
||||||
if probe_http_code_ok "$http_code" "$role"; then
|
if probe_http_code_ok "$http_code" "$role"; then
|
||||||
log_success "Probe passed: ${host} (HTTP ${http_code})"
|
log_success "Probe passed: ${host} (HTTP ${http_code})"
|
||||||
@@ -540,7 +548,13 @@ probe_host_via_caddy() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_error "Probe failed: ${host} (HTTP ${http_code})"
|
if [[ $attempt -lt $max_attempts ]]; then
|
||||||
|
log_info "Probe attempt ${attempt}/${max_attempts} for ${host} (HTTP ${http_code}) — retrying in ${wait_secs}s..."
|
||||||
|
sleep "$wait_secs"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "Probe failed: ${host} (HTTP ${http_code}) after ${max_attempts} attempts"
|
||||||
if [[ "$http_code" == "502" || "$http_code" == "503" || "$http_code" == "504" || "$http_code" == "000" ]]; then
|
if [[ "$http_code" == "502" || "$http_code" == "503" || "$http_code" == "504" || "$http_code" == "000" ]]; then
|
||||||
local upstream_probe_raw upstream_code
|
local upstream_probe_raw upstream_code
|
||||||
upstream_probe_raw=$(ssh_exec UNRAID "curl -sk -o /dev/null -w '%{http_code}' '${upstream}' || true" 2>/dev/null || true)
|
upstream_probe_raw=$(ssh_exec UNRAID "curl -sk -o /dev/null -w '%{http_code}' '${upstream}' || true" 2>/dev/null || true)
|
||||||
|
|||||||
@@ -16,6 +16,31 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
source "${SCRIPT_DIR}/lib/common.sh"
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
|
||||||
|
ALLOW_DIRECT_CHECKS=false
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--allow-direct-checks Allow fallback to direct Caddy-IP checks via --resolve
|
||||||
|
(LAN/split-DNS staging mode; not a full public cutover)
|
||||||
|
--help, -h Show this help
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--allow-direct-checks) ALLOW_DIRECT_CHECKS=true ;;
|
||||||
|
--help|-h) usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown argument: $arg"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
load_env
|
load_env
|
||||||
require_vars UNRAID_IP UNRAID_SSH_USER UNRAID_GITEA_IP UNRAID_CADDY_IP \
|
require_vars UNRAID_IP UNRAID_SSH_USER UNRAID_GITEA_IP UNRAID_CADDY_IP \
|
||||||
UNRAID_COMPOSE_DIR \
|
UNRAID_COMPOSE_DIR \
|
||||||
@@ -236,6 +261,16 @@ caddyfile_has_domain_block() {
|
|||||||
sub(/[[:space:]]+$/, "", s)
|
sub(/[[:space:]]+$/, "", s)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
function matches_domain(label, dom, wild_suffix, dot_pos) {
|
||||||
|
if (label == dom) return 1
|
||||||
|
# Wildcard match: *.example.com covers sub.example.com
|
||||||
|
if (substr(label, 1, 2) == "*.") {
|
||||||
|
wild_suffix = substr(label, 2)
|
||||||
|
dot_pos = index(dom, ".")
|
||||||
|
if (dot_pos > 0 && substr(dom, dot_pos) == wild_suffix) return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
{
|
{
|
||||||
line = $0
|
line = $0
|
||||||
if (line ~ /^[[:space:]]*#/) next
|
if (line ~ /^[[:space:]]*#/) next
|
||||||
@@ -248,7 +283,7 @@ caddyfile_has_domain_block() {
|
|||||||
gsub(/[[:space:]]+/, "", labels)
|
gsub(/[[:space:]]+/, "", labels)
|
||||||
n = split(labels, parts, ",")
|
n = split(labels, parts, ",")
|
||||||
for (i = 1; i <= n; i++) {
|
for (i = 1; i <= n; i++) {
|
||||||
if (parts[i] == domain) {
|
if (matches_domain(parts[i], domain)) {
|
||||||
found = 1
|
found = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,7 +398,6 @@ fi
|
|||||||
log_step 2 "Deploying Caddyfile..."
|
log_step 2 "Deploying Caddyfile..."
|
||||||
GITEA_CONTAINER_IP="${UNRAID_GITEA_IP}"
|
GITEA_CONTAINER_IP="${UNRAID_GITEA_IP}"
|
||||||
export GITEA_CONTAINER_IP GITEA_DOMAIN CADDY_DOMAIN
|
export GITEA_CONTAINER_IP GITEA_DOMAIN CADDY_DOMAIN
|
||||||
CADDYFILE_UPDATED=0
|
|
||||||
|
|
||||||
# Build TLS block based on TLS_MODE
|
# Build TLS block based on TLS_MODE
|
||||||
if [[ "$TLS_MODE" == "cloudflare" ]]; then
|
if [[ "$TLS_MODE" == "cloudflare" ]]; then
|
||||||
@@ -404,7 +438,6 @@ if ssh_exec UNRAID "test -f '${CADDY_DATA_PATH}/Caddyfile'" 2>/dev/null; then
|
|||||||
cat "$TMP_UPDATED" "$TMP_ROUTE_BLOCK" > "${TMP_UPDATED}.final"
|
cat "$TMP_UPDATED" "$TMP_ROUTE_BLOCK" > "${TMP_UPDATED}.final"
|
||||||
scp_to UNRAID "${TMP_UPDATED}.final" "${CADDY_DATA_PATH}/Caddyfile"
|
scp_to UNRAID "${TMP_UPDATED}.final" "${CADDY_DATA_PATH}/Caddyfile"
|
||||||
log_success "Appended managed Gitea route to existing Caddyfile"
|
log_success "Appended managed Gitea route to existing Caddyfile"
|
||||||
CADDYFILE_UPDATED=1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$TMP_EXISTING" "$TMP_UPDATED" "$TMP_ROUTE_BLOCK" "${TMP_UPDATED}.final"
|
rm -f "$TMP_EXISTING" "$TMP_UPDATED" "$TMP_ROUTE_BLOCK" "${TMP_UPDATED}.final"
|
||||||
@@ -416,7 +449,6 @@ else
|
|||||||
scp_to UNRAID "$TMPFILE" "${CADDY_DATA_PATH}/Caddyfile"
|
scp_to UNRAID "$TMPFILE" "${CADDY_DATA_PATH}/Caddyfile"
|
||||||
rm -f "$TMPFILE"
|
rm -f "$TMPFILE"
|
||||||
log_success "Caddyfile deployed"
|
log_success "Caddyfile deployed"
|
||||||
CADDYFILE_UPDATED=1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -505,12 +537,18 @@ fi
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
log_step 6 "Waiting for HTTPS (Caddy auto-provisions cert)..."
|
log_step 6 "Waiting for HTTPS (Caddy auto-provisions cert)..."
|
||||||
check_unraid_gitea_backend
|
check_unraid_gitea_backend
|
||||||
if wait_for_https_public "${GITEA_DOMAIN}" 30; then
|
if wait_for_https_public "${GITEA_DOMAIN}" 60; then
|
||||||
log_success "HTTPS verified through current domain routing — https://${GITEA_DOMAIN} works"
|
log_success "HTTPS verified through current domain routing — https://${GITEA_DOMAIN} works"
|
||||||
else
|
else
|
||||||
log_warn "Public-domain routing to Caddy is not ready yet"
|
log_warn "Public-domain routing to Caddy is not ready yet"
|
||||||
|
if [[ "$ALLOW_DIRECT_CHECKS" == "true" ]]; then
|
||||||
wait_for_https_via_resolve "${GITEA_DOMAIN}" "${UNRAID_CADDY_IP}" 300
|
wait_for_https_via_resolve "${GITEA_DOMAIN}" "${UNRAID_CADDY_IP}" 300
|
||||||
log_success "HTTPS verified via direct Caddy path; public routing can be completed later"
|
log_warn "Proceeding with direct-only HTTPS validation (--allow-direct-checks)"
|
||||||
|
else
|
||||||
|
log_error "Refusing to continue cutover without public HTTPS reachability"
|
||||||
|
log_error "Fix DNS/ingress routing and rerun Phase 8, or use --allow-direct-checks for staging only"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -15,6 +15,31 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
source "${SCRIPT_DIR}/lib/common.sh"
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
|
||||||
|
ALLOW_DIRECT_CHECKS=false
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--allow-direct-checks Allow fallback to direct Caddy-IP checks via --resolve
|
||||||
|
(LAN/split-DNS staging mode; not a full public cutover check)
|
||||||
|
--help, -h Show this help
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--allow-direct-checks) ALLOW_DIRECT_CHECKS=true ;;
|
||||||
|
--help|-h) usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown argument: $arg"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
load_env
|
load_env
|
||||||
require_vars GITEA_DOMAIN UNRAID_CADDY_IP GITEA_ADMIN_TOKEN GITEA_ORG_NAME \
|
require_vars GITEA_DOMAIN UNRAID_CADDY_IP GITEA_ADMIN_TOKEN GITEA_ORG_NAME \
|
||||||
GITHUB_USERNAME GITHUB_TOKEN \
|
GITHUB_USERNAME GITHUB_TOKEN \
|
||||||
@@ -39,9 +64,15 @@ run_check() {
|
|||||||
|
|
||||||
ACCESS_MODE="public"
|
ACCESS_MODE="public"
|
||||||
if ! curl -sf -o /dev/null "https://${GITEA_DOMAIN}/api/v1/version" 2>/dev/null; then
|
if ! curl -sf -o /dev/null "https://${GITEA_DOMAIN}/api/v1/version" 2>/dev/null; then
|
||||||
ACCESS_MODE="direct"
|
|
||||||
log_warn "Public routing to ${GITEA_DOMAIN} not reachable from control plane"
|
log_warn "Public routing to ${GITEA_DOMAIN} not reachable from control plane"
|
||||||
|
if [[ "$ALLOW_DIRECT_CHECKS" == "true" ]]; then
|
||||||
|
ACCESS_MODE="direct"
|
||||||
log_warn "Using direct Caddy-IP checks via --resolve (${UNRAID_CADDY_IP})"
|
log_warn "Using direct Caddy-IP checks via --resolve (${UNRAID_CADDY_IP})"
|
||||||
|
else
|
||||||
|
log_error "Public HTTPS check failed; this is not a complete Phase 8 validation"
|
||||||
|
log_error "Fix DNS/ingress routing and rerun, or use --allow-direct-checks for staging-only checks"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_info "Using public-domain checks for ${GITEA_DOMAIN}"
|
log_info "Using public-domain checks for ${GITEA_DOMAIN}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
20
repo_variables.conf.example
Normal file
20
repo_variables.conf.example
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# repo_variables.conf — Gitea Actions Repository Variables (INI format)
|
||||||
|
# Copy to repo_variables.conf and edit.
|
||||||
|
# Used by phase11_custom_runners.sh to set per-repo CI dispatch variables.
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Each [section] = Gitea repository name (must exist in GITEA_ORG_NAME).
|
||||||
|
# Keys = variable names. Values = literal string set via Gitea API.
|
||||||
|
# Workflows access these as ${{ vars.VARIABLE_NAME }}.
|
||||||
|
#
|
||||||
|
# Common pattern: repos use fromJSON(vars.CI_RUNS_ON || '["ubuntu-latest"]')
|
||||||
|
# in runs-on to dynamically select runners.
|
||||||
|
|
||||||
|
#[my-go-repo]
|
||||||
|
#CI_RUNS_ON = ["self-hosted","Linux","X64"]
|
||||||
|
|
||||||
|
#[my-mobile-repo]
|
||||||
|
#CI_RUNS_ON = ["self-hosted","Linux","X64"]
|
||||||
|
#CI_RUNS_ON_MACOS = ["self-hosted","macOS","ARM64"]
|
||||||
|
#CI_RUNS_ON_ANDROID = ["self-hosted","Linux","X64","android-emulator"]
|
||||||
27
run_all.sh
27
run_all.sh
@@ -3,11 +3,11 @@ set -euo pipefail
|
|||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# run_all.sh — Orchestrate the full Gitea migration pipeline
|
# run_all.sh — Orchestrate the full Gitea migration pipeline
|
||||||
# Runs: setup → preflight → phase 1-9 (each with post-check) sequentially.
|
# Runs: setup → preflight → phase 1-11 (each with post-check) sequentially.
|
||||||
# Stops on first failure, prints summary of what completed.
|
# Stops on first failure, prints summary of what completed.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./run_all.sh # Full run: setup + preflight + phases 1-9
|
# ./run_all.sh # Full run: setup + preflight + phases 1-11
|
||||||
# ./run_all.sh --skip-setup # Skip setup scripts, start at preflight
|
# ./run_all.sh --skip-setup # Skip setup scripts, start at preflight
|
||||||
# ./run_all.sh --start-from=3 # Run preflight, then start at phase 3
|
# ./run_all.sh --start-from=3 # Run preflight, then start at phase 3
|
||||||
# ./run_all.sh --skip-setup --start-from=5
|
# ./run_all.sh --skip-setup --start-from=5
|
||||||
@@ -28,10 +28,12 @@ require_local_os "Darwin" "run_all.sh must run from macOS (the control plane)"
|
|||||||
SKIP_SETUP=false
|
SKIP_SETUP=false
|
||||||
START_FROM=0
|
START_FROM=0
|
||||||
START_FROM_SET=false
|
START_FROM_SET=false
|
||||||
|
ALLOW_DIRECT_CHECKS=false
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--skip-setup) SKIP_SETUP=true ;;
|
--skip-setup) SKIP_SETUP=true ;;
|
||||||
|
--allow-direct-checks) ALLOW_DIRECT_CHECKS=true ;;
|
||||||
--dry-run)
|
--dry-run)
|
||||||
exec "${SCRIPT_DIR}/post-migration-check.sh"
|
exec "${SCRIPT_DIR}/post-migration-check.sh"
|
||||||
;;
|
;;
|
||||||
@@ -39,11 +41,11 @@ for arg in "$@"; do
|
|||||||
START_FROM="${arg#*=}"
|
START_FROM="${arg#*=}"
|
||||||
START_FROM_SET=true
|
START_FROM_SET=true
|
||||||
if ! [[ "$START_FROM" =~ ^[0-9]+$ ]]; then
|
if ! [[ "$START_FROM" =~ ^[0-9]+$ ]]; then
|
||||||
log_error "--start-from must be a number (1-9)"
|
log_error "--start-from must be a number (1-11)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ "$START_FROM" -lt 1 ]] || [[ "$START_FROM" -gt 9 ]]; then
|
if [[ "$START_FROM" -lt 1 ]] || [[ "$START_FROM" -gt 11 ]]; then
|
||||||
log_error "--start-from must be between 1 and 9"
|
log_error "--start-from must be between 1 and 11"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -54,13 +56,16 @@ Usage: $(basename "$0") [options]
|
|||||||
Options:
|
Options:
|
||||||
--skip-setup Skip configure_env + machine setup, start at preflight
|
--skip-setup Skip configure_env + machine setup, start at preflight
|
||||||
--start-from=N Skip phases before N (still runs preflight)
|
--start-from=N Skip phases before N (still runs preflight)
|
||||||
|
--allow-direct-checks Pass --allow-direct-checks to Phase 8 scripts
|
||||||
|
(LAN/split-DNS staging mode)
|
||||||
--dry-run Run read-only infrastructure check (no mutations)
|
--dry-run Run read-only infrastructure check (no mutations)
|
||||||
--help Show this help
|
--help Show this help
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$(basename "$0") Full run
|
$(basename "$0") Full run
|
||||||
$(basename "$0") --skip-setup Skip setup, start at preflight
|
$(basename "$0") --skip-setup Skip setup, start at preflight
|
||||||
$(basename "$0") --start-from=3 Run preflight, then phases 3-9
|
$(basename "$0") --start-from=3 Run preflight, then phases 3-11
|
||||||
|
$(basename "$0") --allow-direct-checks LAN mode: use direct Caddy-IP checks
|
||||||
$(basename "$0") --dry-run Check current state without changing anything
|
$(basename "$0") --dry-run Check current state without changing anything
|
||||||
EOF
|
EOF
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
@@ -157,7 +162,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Phases 1-9 — run sequentially, each followed by its post-check
|
# Phases 1-11 — run sequentially, each followed by its post-check
|
||||||
# The phase scripts are the "do" step, post-checks verify success.
|
# The phase scripts are the "do" step, post-checks verify success.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
PHASES=(
|
PHASES=(
|
||||||
@@ -170,6 +175,8 @@ PHASES=(
|
|||||||
"7|Phase 7: Branch Protection|phase7_branch_protection.sh|phase7_post_check.sh"
|
"7|Phase 7: Branch Protection|phase7_branch_protection.sh|phase7_post_check.sh"
|
||||||
"8|Phase 8: Cutover|phase8_cutover.sh|phase8_post_check.sh"
|
"8|Phase 8: Cutover|phase8_cutover.sh|phase8_post_check.sh"
|
||||||
"9|Phase 9: Security|phase9_security.sh|phase9_post_check.sh"
|
"9|Phase 9: Security|phase9_security.sh|phase9_post_check.sh"
|
||||||
|
"10|Phase 10: Local Repo Cutover|phase10_local_repo_cutover.sh|phase10_post_check.sh"
|
||||||
|
"11|Phase 11: Custom Runners|phase11_custom_runners.sh|phase11_post_check.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
for phase_entry in "${PHASES[@]}"; do
|
for phase_entry in "${PHASES[@]}"; do
|
||||||
@@ -181,8 +188,14 @@ for phase_entry in "${PHASES[@]}"; do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Phase 8 scripts accept --allow-direct-checks for LAN/split-DNS setups.
|
||||||
|
if [[ "$phase_num" -eq 8 ]] && [[ "$ALLOW_DIRECT_CHECKS" == "true" ]]; then
|
||||||
|
run_step "$phase_name" "$phase_script" --allow-direct-checks
|
||||||
|
run_step "${phase_name} — post-check" "$post_check" --allow-direct-checks
|
||||||
|
else
|
||||||
run_step "$phase_name" "$phase_script"
|
run_step "$phase_name" "$phase_script"
|
||||||
run_step "${phase_name} — post-check" "$post_check"
|
run_step "${phase_name} — post-check" "$post_check"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -55,6 +55,11 @@
|
|||||||
# (starts at login, no sudo needed).
|
# (starts at login, no sudo needed).
|
||||||
# Ignored for docker runners.
|
# Ignored for docker runners.
|
||||||
#
|
#
|
||||||
|
# container_options — Extra Docker flags for act_runner job containers.
|
||||||
|
# Passed to the container.options field in act_runner config.
|
||||||
|
# e.g. "--device=/dev/kvm" for KVM passthrough.
|
||||||
|
# Empty = no extra flags. Ignored for native runners.
|
||||||
|
#
|
||||||
# STARTER ENTRIES (uncomment and edit):
|
# STARTER ENTRIES (uncomment and edit):
|
||||||
|
|
||||||
#[unraid-runner]
|
#[unraid-runner]
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ set -euo pipefail
|
|||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# teardown_all.sh — Tear down migration in reverse order
|
# teardown_all.sh — Tear down migration in reverse order
|
||||||
# Runs phase teardown scripts from phase 9 → phase 1 (or a subset).
|
# Runs phase teardown scripts from phase 11 → phase 1 (or a subset).
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./teardown_all.sh # Tear down everything (phases 9 → 1)
|
# ./teardown_all.sh # Tear down everything (phases 11 → 1)
|
||||||
# ./teardown_all.sh --through=5 # Tear down phases 9 → 5 (leave 1-4)
|
# ./teardown_all.sh --through=5 # Tear down phases 11 → 5 (leave 1-4)
|
||||||
# ./teardown_all.sh --yes # Skip confirmation prompts
|
# ./teardown_all.sh --yes # Skip confirmation prompts
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ for arg in "$@"; do
|
|||||||
case "$arg" in
|
case "$arg" in
|
||||||
--through=*)
|
--through=*)
|
||||||
THROUGH="${arg#*=}"
|
THROUGH="${arg#*=}"
|
||||||
if ! [[ "$THROUGH" =~ ^[0-9]+$ ]] || [[ "$THROUGH" -lt 1 ]] || [[ "$THROUGH" -gt 9 ]]; then
|
if ! [[ "$THROUGH" =~ ^[0-9]+$ ]] || [[ "$THROUGH" -lt 1 ]] || [[ "$THROUGH" -gt 11 ]]; then
|
||||||
log_error "--through must be a number between 1 and 9"
|
log_error "--through must be a number between 1 and 11"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -37,14 +37,14 @@ for arg in "$@"; do
|
|||||||
Usage: $(basename "$0") [options]
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--through=N Only tear down phases N through 9 (default: 1 = everything)
|
--through=N Only tear down phases N through 11 (default: 1 = everything)
|
||||||
--cleanup Also run setup/cleanup.sh to uninstall setup prerequisites
|
--cleanup Also run setup/cleanup.sh to uninstall setup prerequisites
|
||||||
--yes, -y Skip all confirmation prompts
|
--yes, -y Skip all confirmation prompts
|
||||||
--help Show this help
|
--help Show this help
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$(basename "$0") Tear down everything
|
$(basename "$0") Tear down everything
|
||||||
$(basename "$0") --through=5 Tear down phases 5-9, leave 1-4
|
$(basename "$0") --through=5 Tear down phases 11-5, leave 1-4
|
||||||
$(basename "$0") --cleanup Full teardown + uninstall prerequisites
|
$(basename "$0") --cleanup Full teardown + uninstall prerequisites
|
||||||
$(basename "$0") --yes Non-interactive teardown
|
$(basename "$0") --yes Non-interactive teardown
|
||||||
EOF
|
EOF
|
||||||
@@ -58,9 +58,9 @@ done
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
if [[ "$AUTO_YES" == "false" ]]; then
|
if [[ "$AUTO_YES" == "false" ]]; then
|
||||||
if [[ "$THROUGH" -eq 1 ]]; then
|
if [[ "$THROUGH" -eq 1 ]]; then
|
||||||
log_warn "This will tear down ALL phases (9 → 1)."
|
log_warn "This will tear down ALL phases (11 → 1)."
|
||||||
else
|
else
|
||||||
log_warn "This will tear down phases 9 → ${THROUGH}."
|
log_warn "This will tear down phases 11 → ${THROUGH}."
|
||||||
fi
|
fi
|
||||||
printf 'Are you sure? [y/N] '
|
printf 'Are you sure? [y/N] '
|
||||||
read -r confirm
|
read -r confirm
|
||||||
@@ -70,9 +70,11 @@ if [[ "$AUTO_YES" == "false" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Teardown scripts in reverse order (9 → 1)
|
# Teardown scripts in reverse order (11 → 1)
|
||||||
# Each entry: phase_num|script_path
|
# Each entry: phase_num|script_path
|
||||||
TEARDOWNS=(
|
TEARDOWNS=(
|
||||||
|
"11|phase11_teardown.sh"
|
||||||
|
"10|phase10_teardown.sh"
|
||||||
"9|phase9_teardown.sh"
|
"9|phase9_teardown.sh"
|
||||||
"8|phase8_teardown.sh"
|
"8|phase8_teardown.sh"
|
||||||
"7|phase7_teardown.sh"
|
"7|phase7_teardown.sh"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# act_runner configuration — rendered by manage_runner.sh
|
# act_runner configuration — rendered by manage_runner.sh
|
||||||
# Variables: RUNNER_NAME, RUNNER_LABELS_YAML, RUNNER_CAPACITY
|
# Variables: RUNNER_NAME, RUNNER_LABELS_YAML, RUNNER_CAPACITY, RUNNER_CONTAINER_OPTIONS
|
||||||
# Deployed alongside docker-compose.yml (docker) or act_runner binary (native).
|
# Deployed alongside docker-compose.yml (docker) or act_runner binary (native).
|
||||||
|
|
||||||
log:
|
log:
|
||||||
@@ -22,7 +22,7 @@ cache:
|
|||||||
container:
|
container:
|
||||||
network: "" # Empty = use default Docker network.
|
network: "" # Empty = use default Docker network.
|
||||||
privileged: false # Never run job containers as privileged.
|
privileged: false # Never run job containers as privileged.
|
||||||
options:
|
options: ${RUNNER_CONTAINER_OPTIONS}
|
||||||
workdir_parent:
|
workdir_parent:
|
||||||
|
|
||||||
host:
|
host:
|
||||||
|
|||||||
49
toggle_dns.sh
Executable file
49
toggle_dns.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Toggle DNS between Pi-hole and Cloudflare on all active network services.
|
||||||
|
# Usage: ./toggle_dns.sh
|
||||||
|
# Requires sudo for networksetup.
|
||||||
|
|
||||||
|
PIHOLE="pi.sintheus.com"
|
||||||
|
CLOUDFLARE="1.1.1.1"
|
||||||
|
|
||||||
|
# Get all hardware network services (Wi-Fi, Ethernet, Thunderbolt, USB, etc.)
|
||||||
|
services=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ "$line" == *"*"* ]] && continue # skip disabled services
|
||||||
|
services+=("$line")
|
||||||
|
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
|
||||||
|
|
||||||
|
if [[ ${#services[@]} -eq 0 ]]; then
|
||||||
|
echo "No network services found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect current mode from the first service that has a DNS set
|
||||||
|
current_dns=""
|
||||||
|
for svc in "${services[@]}"; do
|
||||||
|
dns=$(networksetup -getdnsservers "$svc" 2>/dev/null | head -1)
|
||||||
|
if [[ "$dns" != *"aren't any"* ]] && [[ -n "$dns" ]]; then
|
||||||
|
current_dns="$dns"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$current_dns" == "$CLOUDFLARE" ]]; then
|
||||||
|
target="$PIHOLE"
|
||||||
|
label="Pi-hole"
|
||||||
|
else
|
||||||
|
target="$CLOUDFLARE"
|
||||||
|
label="Cloudflare"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Switching all services to ${label} (${target})..."
|
||||||
|
for svc in "${services[@]}"; do
|
||||||
|
sudo networksetup -setdnsservers "$svc" "$target"
|
||||||
|
echo " ${svc} → ${target}"
|
||||||
|
done
|
||||||
|
|
||||||
|
sudo dscacheutil -flushcache
|
||||||
|
sudo killall -HUP mDNSResponder 2>/dev/null || true
|
||||||
|
echo "DNS set to ${label} (${target})"
|
||||||
Reference in New Issue
Block a user