#!/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:-})" UNRAID_SSH_OK=true else check_error "SSH to Unraid (${UNRAID_IP:-}) — connection failed" fi # SSH to Fedora if ssh_exec FEDORA "true" 2>/dev/null; then check_done "SSH to Fedora (${FEDORA_IP:-})" FEDORA_SSH_OK=true else check_error "SSH to Fedora (${FEDORA_IP:-}) — 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