feat: add post-migration check script for infrastructure state validation
This commit is contained in:
663
post-migration-check.sh
Executable file
663
post-migration-check.sh
Executable file
@@ -0,0 +1,663 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# post-migration-check.sh — Read-only infrastructure state check
|
||||||
|
# Probes live infrastructure and reports the state of every migration phase.
|
||||||
|
# No mutations — purely diagnostic. Safe to run at any time.
|
||||||
|
#
|
||||||
|
# Three states:
|
||||||
|
# [DONE] — already exists/running, phase would skip this step
|
||||||
|
# [TODO] — not done yet, phase would execute this step
|
||||||
|
# [ERROR] — something is broken (unreachable, invalid token, misconfigured)
|
||||||
|
#
|
||||||
|
# Only [ERROR] means something is wrong. [TODO] is normal for phases not yet run.
|
||||||
|
# Exit code: 0 if no errors, 1 if any [ERROR] found.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
|
||||||
|
load_env
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Counters
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
TOTAL_DONE=0
|
||||||
|
TOTAL_TODO=0
|
||||||
|
TOTAL_ERROR=0
|
||||||
|
|
||||||
|
# Per-phase counters (parallel arrays indexed by phase number 0-9, where 0=connectivity)
|
||||||
|
declare -a PHASE_DONE PHASE_TODO PHASE_ERROR PHASE_TOTAL
|
||||||
|
for i in $(seq 0 9); do
|
||||||
|
PHASE_DONE[$i]=0
|
||||||
|
PHASE_TODO[$i]=0
|
||||||
|
PHASE_ERROR[$i]=0
|
||||||
|
PHASE_TOTAL[$i]=0
|
||||||
|
done
|
||||||
|
|
||||||
|
# Current phase being checked (set by section_header)
|
||||||
|
CURRENT_PHASE=0
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Output helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
check_done() {
|
||||||
|
local msg="$1"
|
||||||
|
printf ' %b[DONE]%b %s\n' "$_C_GREEN" "$_C_RESET" "$msg" >&2
|
||||||
|
TOTAL_DONE=$((TOTAL_DONE + 1))
|
||||||
|
PHASE_DONE[$CURRENT_PHASE]=$((PHASE_DONE[CURRENT_PHASE] + 1))
|
||||||
|
PHASE_TOTAL[$CURRENT_PHASE]=$((PHASE_TOTAL[CURRENT_PHASE] + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
check_todo() {
|
||||||
|
local msg="$1"
|
||||||
|
printf ' %b[TODO]%b %s\n' "$_C_YELLOW" "$_C_RESET" "$msg" >&2
|
||||||
|
TOTAL_TODO=$((TOTAL_TODO + 1))
|
||||||
|
PHASE_TODO[$CURRENT_PHASE]=$((PHASE_TODO[CURRENT_PHASE] + 1))
|
||||||
|
PHASE_TOTAL[$CURRENT_PHASE]=$((PHASE_TOTAL[CURRENT_PHASE] + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
check_error() {
|
||||||
|
local msg="$1"
|
||||||
|
printf ' %b[ERROR]%b %s\n' "$_C_RED" "$_C_RESET" "$msg" >&2
|
||||||
|
TOTAL_ERROR=$((TOTAL_ERROR + 1))
|
||||||
|
PHASE_ERROR[$CURRENT_PHASE]=$((PHASE_ERROR[CURRENT_PHASE] + 1))
|
||||||
|
PHASE_TOTAL[$CURRENT_PHASE]=$((PHASE_TOTAL[CURRENT_PHASE] + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
section_header() {
|
||||||
|
local phase_num="$1" title="$2"
|
||||||
|
CURRENT_PHASE="$phase_num"
|
||||||
|
printf '\n%b--- %s ---%b\n' "$_C_BOLD" "$title" "$_C_RESET" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Connectivity gates — track which hosts are reachable
|
||||||
|
# Later sections skip checks for unreachable hosts rather than spamming errors.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
UNRAID_SSH_OK=false
|
||||||
|
FEDORA_SSH_OK=false
|
||||||
|
UNRAID_DOCKER_OK=false
|
||||||
|
FEDORA_DOCKER_OK=false
|
||||||
|
GITHUB_API_OK=false
|
||||||
|
GITEA_API_OK=false
|
||||||
|
GITEA_BACKUP_API_OK=false
|
||||||
|
|
||||||
|
printf '\n%b=== Post-Migration Check ===%b\n' "$_C_BOLD" "$_C_RESET" >&2
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Connectivity
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 0 "Connectivity"
|
||||||
|
|
||||||
|
# SSH to Unraid
|
||||||
|
if ssh_exec UNRAID "true" 2>/dev/null; then
|
||||||
|
check_done "SSH to Unraid (${UNRAID_IP:-<not set>})"
|
||||||
|
UNRAID_SSH_OK=true
|
||||||
|
else
|
||||||
|
check_error "SSH to Unraid (${UNRAID_IP:-<not set>}) — connection failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSH to Fedora
|
||||||
|
if ssh_exec FEDORA "true" 2>/dev/null; then
|
||||||
|
check_done "SSH to Fedora (${FEDORA_IP:-<not set>})"
|
||||||
|
FEDORA_SSH_OK=true
|
||||||
|
else
|
||||||
|
check_error "SSH to Fedora (${FEDORA_IP:-<not set>}) — connection failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Docker on Unraid
|
||||||
|
if $UNRAID_SSH_OK; then
|
||||||
|
if ssh_exec UNRAID "docker info" &>/dev/null; then
|
||||||
|
check_done "Docker daemon on Unraid"
|
||||||
|
UNRAID_DOCKER_OK=true
|
||||||
|
else
|
||||||
|
check_error "Docker daemon on Unraid — not running"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Docker on Fedora
|
||||||
|
if $FEDORA_SSH_OK; then
|
||||||
|
if ssh_exec FEDORA "docker info" &>/dev/null; then
|
||||||
|
check_done "Docker daemon on Fedora"
|
||||||
|
FEDORA_DOCKER_OK=true
|
||||||
|
else
|
||||||
|
check_error "Docker daemon on Fedora — not running"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GitHub API
|
||||||
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||||
|
GH_USER_RESPONSE=$(curl -sf -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
"https://api.github.com/user" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$GH_USER_RESPONSE" ]]; then
|
||||||
|
GH_ACTUAL_USER=$(printf '%s' "$GH_USER_RESPONSE" | jq -r '.login // ""' 2>/dev/null)
|
||||||
|
if [[ "$GH_ACTUAL_USER" == "${GITHUB_USERNAME:-}" ]]; then
|
||||||
|
check_done "GitHub API — token valid for ${GITHUB_USERNAME}"
|
||||||
|
GITHUB_API_OK=true
|
||||||
|
elif [[ -n "$GH_ACTUAL_USER" ]]; then
|
||||||
|
check_error "GitHub API — token belongs to '${GH_ACTUAL_USER}', expected '${GITHUB_USERNAME:-}'"
|
||||||
|
else
|
||||||
|
check_error "GitHub API — token returned unexpected response"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_error "GitHub API — request failed (bad token or no internet)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_error "GitHub API — GITHUB_TOKEN not set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 1: Gitea on Unraid
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 1 "Phase 1: Gitea on Unraid"
|
||||||
|
|
||||||
|
if ! $UNRAID_DOCKER_OK; then
|
||||||
|
check_error "Cannot check — Unraid Docker unreachable"
|
||||||
|
else
|
||||||
|
# Docker network
|
||||||
|
if ssh_exec UNRAID "docker network inspect br0" &>/dev/null; then
|
||||||
|
check_done "Docker network br0 exists"
|
||||||
|
else
|
||||||
|
check_todo "Docker network br0 — would create"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# docker-compose.yml
|
||||||
|
COMPOSE_DIR="${UNRAID_COMPOSE_DIR:-}/gitea"
|
||||||
|
if ssh_exec UNRAID "test -f '${COMPOSE_DIR}/docker-compose.yml'" 2>/dev/null; then
|
||||||
|
check_done "docker-compose.yml deployed at ${COMPOSE_DIR}"
|
||||||
|
else
|
||||||
|
check_todo "docker-compose.yml — would deploy to ${COMPOSE_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# app.ini
|
||||||
|
DATA_PATH="${UNRAID_GITEA_DATA_PATH:-}"
|
||||||
|
if ssh_exec UNRAID "test -f '${DATA_PATH}/config/app.ini'" 2>/dev/null; then
|
||||||
|
check_done "app.ini deployed"
|
||||||
|
else
|
||||||
|
check_todo "app.ini — would deploy"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gitea container running + healthy
|
||||||
|
CONTAINER_STATUS=$(ssh_exec UNRAID "docker ps --filter name='^/gitea$' --format '{{.Status}}'" 2>/dev/null || true)
|
||||||
|
if [[ "$CONTAINER_STATUS" == *"Up"* ]]; then
|
||||||
|
if [[ "$CONTAINER_STATUS" == *"healthy"* ]]; then
|
||||||
|
check_done "Gitea container running (healthy)"
|
||||||
|
else
|
||||||
|
check_done "Gitea container running (${CONTAINER_STATUS})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_todo "Gitea container — would start"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gitea HTTP responds
|
||||||
|
if [[ -n "${GITEA_INTERNAL_URL:-}" ]]; then
|
||||||
|
VERSION_RESPONSE=$(curl -sf "${GITEA_INTERNAL_URL}/api/v1/version" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$VERSION_RESPONSE" ]]; then
|
||||||
|
ACTUAL_VERSION=$(printf '%s' "$VERSION_RESPONSE" | jq -r '.version // ""' 2>/dev/null)
|
||||||
|
if [[ -n "$ACTUAL_VERSION" ]]; then
|
||||||
|
check_done "Gitea API responds (v${ACTUAL_VERSION})"
|
||||||
|
GITEA_API_OK=true
|
||||||
|
else
|
||||||
|
check_error "Gitea API — unexpected response format"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_todo "Gitea API — not responding (container not started?)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Admin auth
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_USER:-}" ]] && [[ -n "${GITEA_ADMIN_PASSWORD:-}" ]]; then
|
||||||
|
ADMIN_RESPONSE=$(curl -sf -u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD}" \
|
||||||
|
"${GITEA_INTERNAL_URL}/api/v1/user" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$ADMIN_RESPONSE" ]]; then
|
||||||
|
ADMIN_LOGIN=$(printf '%s' "$ADMIN_RESPONSE" | jq -r '.login // ""' 2>/dev/null)
|
||||||
|
if [[ "$ADMIN_LOGIN" == "${GITEA_ADMIN_USER}" ]]; then
|
||||||
|
check_done "Admin user '${GITEA_ADMIN_USER}' credentials valid"
|
||||||
|
else
|
||||||
|
check_error "Admin auth — returned user '${ADMIN_LOGIN}', expected '${GITEA_ADMIN_USER}'"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_todo "Admin user — would create"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# API token
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
TOKEN_RESPONSE=$(curl -sf -H "Authorization: token ${GITEA_ADMIN_TOKEN}" \
|
||||||
|
"${GITEA_INTERNAL_URL}/api/v1/user" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$TOKEN_RESPONSE" ]]; then
|
||||||
|
TOKEN_USER=$(printf '%s' "$TOKEN_RESPONSE" | jq -r '.login // ""' 2>/dev/null)
|
||||||
|
if [[ "$TOKEN_USER" == "${GITEA_ADMIN_USER:-}" ]]; then
|
||||||
|
check_done "API token valid for '${GITEA_ADMIN_USER}'"
|
||||||
|
else
|
||||||
|
check_error "API token — returned user '${TOKEN_USER}', expected '${GITEA_ADMIN_USER:-}'"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_error "API token — rejected by Gitea"
|
||||||
|
fi
|
||||||
|
elif $GITEA_API_OK; then
|
||||||
|
check_todo "API token — would generate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Organization
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
if gitea_api GET "/orgs/${GITEA_ORG_NAME:-}" >/dev/null 2>&1; then
|
||||||
|
check_done "Organization '${GITEA_ORG_NAME}' exists"
|
||||||
|
else
|
||||||
|
check_todo "Organization '${GITEA_ORG_NAME}' — would create"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 2: Gitea on Fedora
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 2 "Phase 2: Gitea on Fedora"
|
||||||
|
|
||||||
|
if ! $FEDORA_DOCKER_OK; then
|
||||||
|
check_error "Cannot check — Fedora Docker unreachable"
|
||||||
|
else
|
||||||
|
# Docker network
|
||||||
|
FEDORA_NETWORK="${FEDORA_MACVLAN_PARENT:-gitea_net}"
|
||||||
|
if ssh_exec FEDORA "docker network inspect gitea_net" &>/dev/null; then
|
||||||
|
check_done "Docker network gitea_net exists"
|
||||||
|
else
|
||||||
|
check_todo "Docker network gitea_net — would create"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# docker-compose.yml
|
||||||
|
FEDORA_COMPOSE="${FEDORA_COMPOSE_DIR:-}/gitea"
|
||||||
|
if ssh_exec FEDORA "test -f '${FEDORA_COMPOSE}/docker-compose.yml'" 2>/dev/null; then
|
||||||
|
check_done "docker-compose.yml deployed at ${FEDORA_COMPOSE}"
|
||||||
|
else
|
||||||
|
check_todo "docker-compose.yml — would deploy to ${FEDORA_COMPOSE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# app.ini
|
||||||
|
FEDORA_DATA="${FEDORA_GITEA_DATA_PATH:-}"
|
||||||
|
if ssh_exec FEDORA "test -f '${FEDORA_DATA}/config/app.ini'" 2>/dev/null; then
|
||||||
|
check_done "app.ini deployed"
|
||||||
|
else
|
||||||
|
check_todo "app.ini — would deploy"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gitea container running + healthy
|
||||||
|
FEDORA_CONTAINER=$(ssh_exec FEDORA "docker ps --filter name='^/gitea$' --format '{{.Status}}'" 2>/dev/null || true)
|
||||||
|
if [[ "$FEDORA_CONTAINER" == *"Up"* ]]; then
|
||||||
|
if [[ "$FEDORA_CONTAINER" == *"healthy"* ]]; then
|
||||||
|
check_done "Gitea container running (healthy)"
|
||||||
|
else
|
||||||
|
check_done "Gitea container running (${FEDORA_CONTAINER})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_todo "Gitea container — would start"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gitea HTTP responds
|
||||||
|
if [[ -n "${GITEA_BACKUP_INTERNAL_URL:-}" ]]; then
|
||||||
|
BACKUP_VERSION=$(curl -sf "${GITEA_BACKUP_INTERNAL_URL}/api/v1/version" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$BACKUP_VERSION" ]]; then
|
||||||
|
BACKUP_VER=$(printf '%s' "$BACKUP_VERSION" | jq -r '.version // ""' 2>/dev/null)
|
||||||
|
check_done "Gitea backup API responds (v${BACKUP_VER})"
|
||||||
|
GITEA_BACKUP_API_OK=true
|
||||||
|
else
|
||||||
|
check_todo "Gitea backup API — not responding"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup API token
|
||||||
|
if $GITEA_BACKUP_API_OK && [[ -n "${GITEA_BACKUP_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
BACKUP_TOKEN_RESP=$(curl -sf -H "Authorization: token ${GITEA_BACKUP_ADMIN_TOKEN}" \
|
||||||
|
"${GITEA_BACKUP_INTERNAL_URL}/api/v1/user" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$BACKUP_TOKEN_RESP" ]]; then
|
||||||
|
check_done "Backup API token valid"
|
||||||
|
else
|
||||||
|
check_error "Backup API token — rejected by Gitea"
|
||||||
|
fi
|
||||||
|
elif $GITEA_BACKUP_API_OK; then
|
||||||
|
check_todo "Backup API token — would generate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cross-host connectivity: Fedora → Unraid
|
||||||
|
if $FEDORA_SSH_OK && [[ -n "${UNRAID_GITEA_IP:-}" ]]; then
|
||||||
|
if ssh_exec FEDORA "curl -sf -o /dev/null http://${UNRAID_GITEA_IP}:3000/api/v1/version" 2>/dev/null; then
|
||||||
|
check_done "Fedora can reach Unraid Gitea (${UNRAID_GITEA_IP}:3000)"
|
||||||
|
else
|
||||||
|
if $GITEA_API_OK; then
|
||||||
|
check_error "Fedora cannot reach Unraid Gitea at ${UNRAID_GITEA_IP}:3000"
|
||||||
|
else
|
||||||
|
check_todo "Fedora → Unraid connectivity (Unraid Gitea not running)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 3: Runners
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 3 "Phase 3: Runners"
|
||||||
|
|
||||||
|
RUNNERS_CONF="$(_project_root)/runners.conf"
|
||||||
|
if [[ -f "$RUNNERS_CONF" ]]; then
|
||||||
|
RUNNER_COUNT=$(ini_list_sections "$RUNNERS_CONF" | wc -l | xargs)
|
||||||
|
check_done "runners.conf exists (${RUNNER_COUNT} runner(s) defined)"
|
||||||
|
else
|
||||||
|
check_todo "runners.conf — not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Registration token
|
||||||
|
if [[ -n "${GITEA_RUNNER_REGISTRATION_TOKEN:-}" ]]; then
|
||||||
|
check_done "Runner registration token in .env"
|
||||||
|
else
|
||||||
|
if $GITEA_API_OK; then
|
||||||
|
check_todo "Runner registration token — would fetch from Gitea"
|
||||||
|
else
|
||||||
|
check_todo "Runner registration token — Gitea not available to fetch"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check each runner's status via Gitea admin API
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]] && [[ -f "$RUNNERS_CONF" ]]; then
|
||||||
|
# Get all registered runners from Gitea
|
||||||
|
API_RUNNERS=$(gitea_api GET "/admin/runners" 2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
while IFS= read -r runner_name; do
|
||||||
|
[[ -z "$runner_name" ]] && continue
|
||||||
|
RUNNER_HOST=$(ini_get "$RUNNERS_CONF" "$runner_name" "host" "")
|
||||||
|
|
||||||
|
# Search for this runner in API response
|
||||||
|
RUNNER_STATUS=$(printf '%s' "$API_RUNNERS" | jq -r --arg n "$runner_name" \
|
||||||
|
'.[] | select(.name == $n) | .status // "unknown"' 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ -z "$RUNNER_STATUS" ]]; then
|
||||||
|
# Try matching by label substring if exact name match fails
|
||||||
|
check_todo "Runner '${runner_name}' (${RUNNER_HOST:-local}) — not registered"
|
||||||
|
elif [[ "$RUNNER_STATUS" == "online" ]]; then
|
||||||
|
check_done "Runner '${runner_name}' (${RUNNER_HOST:-local}) — online"
|
||||||
|
else
|
||||||
|
check_error "Runner '${runner_name}' (${RUNNER_HOST:-local}) — ${RUNNER_STATUS}"
|
||||||
|
fi
|
||||||
|
done < <(ini_list_sections "$RUNNERS_CONF")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 4: Migrate Repos
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 4 "Phase 4: Migrate Repos"
|
||||||
|
|
||||||
|
read -ra REPOS <<< "${REPO_NAMES:-}"
|
||||||
|
|
||||||
|
if [[ ${#REPOS[@]} -eq 0 ]]; then
|
||||||
|
check_error "REPO_NAMES is empty — no repos to migrate"
|
||||||
|
else
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
# GitHub source accessible
|
||||||
|
if $GITHUB_API_OK; then
|
||||||
|
GH_REPO_RESP=$(curl -sf -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_USERNAME}/${repo}" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$GH_REPO_RESP" ]]; then
|
||||||
|
check_done "GitHub source: ${GITHUB_USERNAME}/${repo} accessible"
|
||||||
|
else
|
||||||
|
check_error "GitHub source: ${GITHUB_USERNAME}/${repo} — not found or no access"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gitea primary
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
GITEA_REPO_RESP=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$GITEA_REPO_RESP" ]]; then
|
||||||
|
check_done "Gitea primary: ${GITEA_ORG_NAME}/${repo} exists"
|
||||||
|
else
|
||||||
|
check_todo "Gitea primary: ${GITEA_ORG_NAME}/${repo} — would migrate"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fedora mirror
|
||||||
|
if $GITEA_BACKUP_API_OK && [[ -n "${GITEA_BACKUP_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
BACKUP_REPO_RESP=$(gitea_backup_api GET "/repos/${GITEA_ADMIN_USER}/${repo}" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$BACKUP_REPO_RESP" ]]; then
|
||||||
|
IS_MIRROR=$(printf '%s' "$BACKUP_REPO_RESP" | jq -r '.mirror // false' 2>/dev/null)
|
||||||
|
if [[ "$IS_MIRROR" == "true" ]]; then
|
||||||
|
check_done "Fedora mirror: ${GITEA_ADMIN_USER}/${repo} (pull mirror active)"
|
||||||
|
else
|
||||||
|
check_done "Fedora mirror: ${GITEA_ADMIN_USER}/${repo} exists (not a mirror)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_todo "Fedora mirror: ${GITEA_ADMIN_USER}/${repo} — would create"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 5: Migrate Pipelines
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 5 "Phase 5: Migrate Pipelines"
|
||||||
|
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
WORKFLOWS_RESP=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$WORKFLOWS_RESP" ]] && [[ "$WORKFLOWS_RESP" != "null" ]]; then
|
||||||
|
WORKFLOW_COUNT=$(printf '%s' "$WORKFLOWS_RESP" | jq 'length' 2>/dev/null || echo 0)
|
||||||
|
check_done "${repo}: .gitea/workflows/ exists (${WORKFLOW_COUNT} file(s))"
|
||||||
|
else
|
||||||
|
# Check if source repo has GitHub Actions workflows
|
||||||
|
if $GITHUB_API_OK; then
|
||||||
|
GH_WORKFLOWS=$(curl -sf -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_USERNAME}/${repo}/contents/.github/workflows" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$GH_WORKFLOWS" ]] && [[ "$GH_WORKFLOWS" != *"Not Found"* ]]; then
|
||||||
|
check_todo "${repo}: has GitHub workflows — would migrate"
|
||||||
|
else
|
||||||
|
check_done "${repo}: no workflows in source — nothing to migrate"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check_todo "${repo}: .gitea/workflows/ — would check"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
check_todo "Pipeline checks — Gitea API not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 6: GitHub Mirrors
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 6 "Phase 6: GitHub Mirrors"
|
||||||
|
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
# Push mirror configured
|
||||||
|
MIRROR_RESP=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/push_mirrors" 2>/dev/null || echo "[]")
|
||||||
|
MIRROR_COUNT=$(printf '%s' "$MIRROR_RESP" | jq 'length' 2>/dev/null || echo 0)
|
||||||
|
if [[ "$MIRROR_COUNT" -gt 0 ]]; then
|
||||||
|
check_done "${repo}: push mirror configured"
|
||||||
|
else
|
||||||
|
check_todo "${repo}: push mirror — would configure"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GitHub Actions disabled
|
||||||
|
if $GITHUB_API_OK; then
|
||||||
|
ACTIONS_RESP=$(curl -sf -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_USERNAME}/${repo}/actions/permissions" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$ACTIONS_RESP" ]]; then
|
||||||
|
ACTIONS_ENABLED=$(printf '%s' "$ACTIONS_RESP" | jq -r '.enabled // true' 2>/dev/null)
|
||||||
|
if [[ "$ACTIONS_ENABLED" == "false" ]]; then
|
||||||
|
check_done "${repo}: GitHub Actions disabled"
|
||||||
|
else
|
||||||
|
check_todo "${repo}: GitHub Actions — would disable"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
check_todo "Mirror checks — Gitea API not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 7: Branch Protection
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 7 "Phase 7: Branch Protection"
|
||||||
|
|
||||||
|
BRANCH="${PROTECTED_BRANCH:-main}"
|
||||||
|
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
BP_RESP=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/branch_protections/${BRANCH}" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$BP_RESP" ]]; then
|
||||||
|
APPROVALS=$(printf '%s' "$BP_RESP" | jq -r '.required_approvals // 0' 2>/dev/null)
|
||||||
|
check_done "${repo}: branch protection on '${BRANCH}' (${APPROVALS} approval(s) required)"
|
||||||
|
else
|
||||||
|
check_todo "${repo}: branch protection on '${BRANCH}' — would create"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
check_todo "Branch protection checks — Gitea API not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 8: Cutover (HTTPS + GitHub mirror marking)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 8 "Phase 8: Cutover"
|
||||||
|
|
||||||
|
# DNS resolution
|
||||||
|
if command -v dig &>/dev/null; then
|
||||||
|
DNS_RESULT=$(dig +short "${GITEA_DOMAIN:-}" 2>/dev/null | head -1)
|
||||||
|
elif command -v host &>/dev/null; then
|
||||||
|
DNS_RESULT=$(host "${GITEA_DOMAIN:-}" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||||
|
else
|
||||||
|
DNS_RESULT=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$DNS_RESULT" ]]; then
|
||||||
|
check_done "DNS: ${GITEA_DOMAIN} resolves to ${DNS_RESULT}"
|
||||||
|
else
|
||||||
|
check_todo "DNS: ${GITEA_DOMAIN} — does not resolve"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Caddy container
|
||||||
|
if $UNRAID_DOCKER_OK; then
|
||||||
|
CADDY_STATUS=$(ssh_exec UNRAID "docker ps --filter name=caddy --format '{{.Status}}'" 2>/dev/null || true)
|
||||||
|
if [[ "$CADDY_STATUS" == *"Up"* ]]; then
|
||||||
|
check_done "Caddy container running"
|
||||||
|
else
|
||||||
|
check_todo "Caddy container — would start"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# HTTPS end-to-end
|
||||||
|
HTTPS_RESP=$(curl -sf -o /dev/null -w "%{http_code}" "https://${GITEA_DOMAIN:-}/api/v1/version" 2>/dev/null || echo "000")
|
||||||
|
if [[ "$HTTPS_RESP" == "200" ]]; then
|
||||||
|
check_done "HTTPS end-to-end: https://${GITEA_DOMAIN} works"
|
||||||
|
else
|
||||||
|
check_todo "HTTPS: https://${GITEA_DOMAIN} — not responding (HTTP ${HTTPS_RESP})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# TLS certificate validity
|
||||||
|
TLS_INFO=$(curl -vI "https://${GITEA_DOMAIN:-}/" 2>&1 || true)
|
||||||
|
if printf '%s' "$TLS_INFO" | grep -q "SSL certificate verify ok" 2>/dev/null; then
|
||||||
|
check_done "TLS certificate valid"
|
||||||
|
elif printf '%s' "$TLS_INFO" | grep -q "SSL certificate" 2>/dev/null; then
|
||||||
|
check_error "TLS certificate — verification failed"
|
||||||
|
else
|
||||||
|
if [[ "$HTTPS_RESP" == "200" ]]; then
|
||||||
|
check_done "TLS certificate present (HTTPS working)"
|
||||||
|
else
|
||||||
|
check_todo "TLS certificate — HTTPS not active yet"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cloudflare token (if applicable)
|
||||||
|
if [[ "${TLS_MODE:-}" == "cloudflare" ]] && [[ -n "${CLOUDFLARE_API_TOKEN:-}" ]]; then
|
||||||
|
CF_VERIFY=$(curl -sf -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
|
||||||
|
"https://api.cloudflare.com/client/v4/user/tokens/verify" 2>/dev/null || echo "")
|
||||||
|
if printf '%s' "$CF_VERIFY" | jq -e '.success == true' &>/dev/null; then
|
||||||
|
check_done "Cloudflare API token valid"
|
||||||
|
else
|
||||||
|
check_error "Cloudflare API token — verification failed"
|
||||||
|
fi
|
||||||
|
elif [[ "${TLS_MODE:-}" == "cloudflare" ]]; then
|
||||||
|
check_error "Cloudflare API token — CLOUDFLARE_API_TOKEN not set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GitHub repos marked as mirror
|
||||||
|
if $GITHUB_API_OK; then
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
GH_DESC=$(curl -sf -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_USERNAME}/${repo}" 2>/dev/null \
|
||||||
|
| jq -r '.description // ""' 2>/dev/null || echo "")
|
||||||
|
if [[ "$GH_DESC" == "[MIRROR]"* ]]; then
|
||||||
|
check_done "${repo}: GitHub description marked as [MIRROR]"
|
||||||
|
else
|
||||||
|
check_todo "${repo}: GitHub description — would mark as [MIRROR]"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Phase 9: Security
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
section_header 9 "Phase 9: Security"
|
||||||
|
|
||||||
|
if $GITEA_API_OK && [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
SEC_RESP=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows/security-scan.yml" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$SEC_RESP" ]] && [[ "$SEC_RESP" != "null" ]]; then
|
||||||
|
check_done "${repo}: security-scan.yml exists"
|
||||||
|
else
|
||||||
|
check_todo "${repo}: security-scan.yml — would deploy"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
check_todo "Security checks — Gitea API not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
PHASE_NAMES=(
|
||||||
|
"Connectivity"
|
||||||
|
"Phase 1: Gitea on Unraid"
|
||||||
|
"Phase 2: Gitea on Fedora"
|
||||||
|
"Phase 3: Runners"
|
||||||
|
"Phase 4: Migrate Repos"
|
||||||
|
"Phase 5: Migrate Pipelines"
|
||||||
|
"Phase 6: GitHub Mirrors"
|
||||||
|
"Phase 7: Branch Protection"
|
||||||
|
"Phase 8: Cutover"
|
||||||
|
"Phase 9: Security"
|
||||||
|
)
|
||||||
|
|
||||||
|
printf '\n%b=== Summary ===%b\n' "$_C_BOLD" "$_C_RESET" >&2
|
||||||
|
printf ' %s error(s), %s todo(s), %s done\n\n' "$TOTAL_ERROR" "$TOTAL_TODO" "$TOTAL_DONE" >&2
|
||||||
|
|
||||||
|
for i in $(seq 0 9); do
|
||||||
|
DONE="${PHASE_DONE[$i]}"
|
||||||
|
TODO="${PHASE_TODO[$i]}"
|
||||||
|
ERRORS="${PHASE_ERROR[$i]}"
|
||||||
|
TOTAL="${PHASE_TOTAL[$i]}"
|
||||||
|
NAME="${PHASE_NAMES[$i]}"
|
||||||
|
|
||||||
|
if [[ "$TOTAL" -eq 0 ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$ERRORS" -gt 0 ]]; then
|
||||||
|
printf ' %b%-30s%b %s/%s done, %s error(s)\n' "$_C_RED" "$NAME" "$_C_RESET" "$DONE" "$TOTAL" "$ERRORS" >&2
|
||||||
|
elif [[ "$TODO" -gt 0 ]]; then
|
||||||
|
printf ' %b%-30s%b %s/%s done, %s todo\n' "$_C_YELLOW" "$NAME" "$_C_RESET" "$DONE" "$TOTAL" "$TODO" >&2
|
||||||
|
else
|
||||||
|
printf ' %b%-30s%b %s/%s done\n' "$_C_GREEN" "$NAME" "$_C_RESET" "$DONE" "$TOTAL" >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n' >&2
|
||||||
|
|
||||||
|
if [[ "$TOTAL_ERROR" -gt 0 ]]; then
|
||||||
|
log_error "${TOTAL_ERROR} error(s) found — see above for details"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
log_success "No errors found"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user