599 lines
24 KiB
Bash
Executable File
599 lines
24 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# shellcheck disable=SC2329
|
|
|
|
# =============================================================================
|
|
# preflight.sh — Validate everything before running migration phases
|
|
# Installs nothing. Exits 0 only if ALL checks pass.
|
|
#
|
|
# Usage:
|
|
# ./preflight.sh # Run all checks
|
|
# ./preflight.sh --skip-port-checks # Skip port-free checks (for --start-from)
|
|
# =============================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
source "${SCRIPT_DIR}/lib/common.sh"
|
|
|
|
SKIP_PORT_CHECKS=false
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--skip-port-checks) SKIP_PORT_CHECKS=true ;;
|
|
esac
|
|
done
|
|
|
|
log_info "=== Preflight Checks ==="
|
|
|
|
PASS_COUNT=0
|
|
FAIL_COUNT=0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check helper — runs a check function, tracks pass/fail count.
|
|
# Intentionally does NOT exit on failure — we want to run ALL checks
|
|
# so the user sees every issue at once, not one at a time.
|
|
# ---------------------------------------------------------------------------
|
|
check() {
|
|
local num="$1" description="$2"
|
|
shift 2
|
|
|
|
if "$@" 2>/dev/null; then
|
|
log_success "[${num}] ${description}"
|
|
PASS_COUNT=$((PASS_COUNT + 1))
|
|
else
|
|
log_error "[${num}] FAIL: ${description}"
|
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 1: Local machine is macOS (control plane uses brew, launchctl, macOS sed)
|
|
# ---------------------------------------------------------------------------
|
|
check_local_os() {
|
|
[[ "$(uname -s)" == "Darwin" ]]
|
|
}
|
|
check 1 "Local machine is macOS (control plane)" check_local_os
|
|
if ! check_local_os 2>/dev/null; then
|
|
log_error " → This toolkit is designed to run from macOS. Detected: $(uname -s)"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 2: Unraid is Linux (via SSH)
|
|
# ---------------------------------------------------------------------------
|
|
check_unraid_os() {
|
|
local remote_os
|
|
remote_os="$(ssh_exec UNRAID "uname -s" 2>/dev/null)" || return 1
|
|
[[ "$remote_os" == "Linux" ]]
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 3: Fedora is Linux with dnf (RPM-based)
|
|
# ---------------------------------------------------------------------------
|
|
check_fedora_os() {
|
|
local remote_os
|
|
remote_os="$(ssh_exec FEDORA "uname -s" 2>/dev/null)" || return 1
|
|
[[ "$remote_os" == "Linux" ]] && ssh_exec FEDORA "command -v dnf" &>/dev/null
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 4: .env exists
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_env_exists() {
|
|
[[ -f "${SCRIPT_DIR}/.env" ]]
|
|
}
|
|
check 4 ".env file exists" check_env_exists
|
|
if [[ ! -f "${SCRIPT_DIR}/.env" ]]; then
|
|
log_error " → .env not found. Copy .env.example to .env and fill in values."
|
|
log_error " → Or run: setup/configure_env.sh"
|
|
# Can't continue without .env — run remaining checks but they'll mostly fail
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 5: runners.conf exists
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_runners_conf() {
|
|
[[ -f "${SCRIPT_DIR}/runners.conf" ]]
|
|
}
|
|
check 5 "runners.conf file exists" check_runners_conf
|
|
if [[ ! -f "${SCRIPT_DIR}/runners.conf" ]]; then
|
|
log_error " → runners.conf not found. Run ./setup/configure_runners.sh to create it."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 5b: runners.conf INI format validation
|
|
# Validates required fields, enum values, and format per runner section.
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_runners_ini_format() {
|
|
local conf="${SCRIPT_DIR}/runners.conf"
|
|
[[ -f "$conf" ]] || return 1
|
|
|
|
local errors=0
|
|
local section
|
|
|
|
while IFS= read -r section; do
|
|
[[ -z "$section" ]] && continue
|
|
|
|
# Required fields
|
|
local host type data_path labels capacity repos
|
|
host=$(ini_get "$conf" "$section" "host" "")
|
|
type=$(ini_get "$conf" "$section" "type" "")
|
|
data_path=$(ini_get "$conf" "$section" "data_path" "")
|
|
labels=$(ini_get "$conf" "$section" "labels" "")
|
|
capacity=$(ini_get "$conf" "$section" "capacity" "")
|
|
repos=$(ini_get "$conf" "$section" "repos" "all")
|
|
|
|
# host: must be unraid, fedora, local, or custom
|
|
case "$host" in
|
|
unraid|fedora|local|custom) ;;
|
|
"")
|
|
log_error " → [$section] host is empty (must be unraid, fedora, local, or custom)"
|
|
errors=$((errors + 1))
|
|
;;
|
|
*)
|
|
log_error " → [$section] host='$host' (must be unraid, fedora, local, or custom)"
|
|
errors=$((errors + 1))
|
|
;;
|
|
esac
|
|
|
|
# type: must be docker or native
|
|
case "$type" in
|
|
docker|native) ;;
|
|
"")
|
|
log_error " → [$section] type is empty (must be docker or native)"
|
|
errors=$((errors + 1))
|
|
;;
|
|
*)
|
|
log_error " → [$section] type='$type' (must be docker or native)"
|
|
errors=$((errors + 1))
|
|
;;
|
|
esac
|
|
|
|
# host/type compatibility:
|
|
# - host=local requires type=native
|
|
# - host=unraid|fedora|custom requires type=docker
|
|
if [[ "$host" =~ ^(unraid|fedora|local|custom)$ ]] && [[ "$type" =~ ^(docker|native)$ ]]; then
|
|
if [[ "$host" == "local" ]] && [[ "$type" != "native" ]]; then
|
|
log_error " → [$section] invalid combo host='$host' type='$type' (host=local requires type=native)"
|
|
errors=$((errors + 1))
|
|
fi
|
|
if [[ "$host" != "local" ]] && [[ "$type" != "docker" ]]; then
|
|
log_error " → [$section] invalid combo host='$host' type='$type' (host=$host requires type=docker)"
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
|
|
# data_path: must start with / or ~/
|
|
# shellcheck disable=SC2088 # tilde intentionally stored as literal string
|
|
if [[ -z "$data_path" ]]; then
|
|
log_error " → [$section] data_path is empty"
|
|
errors=$((errors + 1))
|
|
elif [[ "$data_path" != /* ]] && [[ "$data_path" != "~/"* ]] && [[ "$data_path" != "~" ]]; then
|
|
log_error " → [$section] data_path='$data_path' (must start with / or ~/)"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# labels: non-empty
|
|
if [[ -z "$labels" ]]; then
|
|
log_error " → [$section] labels is empty"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# capacity: positive integer >= 1 (defaults to 1 at runtime if empty)
|
|
if [[ -z "$capacity" ]]; then
|
|
log_warn " → [$section] capacity is empty (will default to 1 at runtime)"
|
|
elif ! [[ "$capacity" =~ ^[1-9][0-9]*$ ]]; then
|
|
log_error " → [$section] capacity='$capacity' (must be positive integer >= 1)"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# repos: must be "all" or a known repo name.
|
|
# Comma-separated values mean configure_runners.sh didn't expand — warn the user.
|
|
if [[ "$repos" != "all" ]] && [[ -n "$repos" ]]; then
|
|
if [[ "$repos" == *,* ]]; then
|
|
log_error " → [$section] repos='$repos' contains commas — run ./setup/configure_runners.sh to expand into separate sections"
|
|
errors=$((errors + 1))
|
|
else
|
|
local repos_valid=false
|
|
local _rn
|
|
for _rn in ${REPO_NAMES:-}; do
|
|
if [[ "$repos" == "$_rn" ]]; then repos_valid=true; break; fi
|
|
done
|
|
if ! $repos_valid; then
|
|
log_error " → [$section] repos='$repos' (must be 'all' or a name from REPO_NAMES: ${REPO_NAMES:-<empty>})"
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# boot: must be "true" or "false" if present (native runners only)
|
|
local boot
|
|
boot=$(ini_get "$conf" "$section" "boot" "")
|
|
if [[ -n "$boot" ]] && [[ "$boot" != "true" ]] && [[ "$boot" != "false" ]]; then
|
|
log_error " → [$section] boot='$boot' (must be 'true' or 'false')"
|
|
errors=$((errors + 1))
|
|
fi
|
|
if [[ "$boot" == "true" ]] && [[ "$type" == "docker" ]]; then
|
|
log_warn " → [$section] boot=true has no effect on docker runners (ignored)"
|
|
fi
|
|
|
|
# docker runner: default_image should be set
|
|
if [[ "$type" == "docker" ]]; then
|
|
local default_image
|
|
default_image=$(ini_get "$conf" "$section" "default_image" "")
|
|
if [[ -z "$default_image" ]] && [[ -z "${RUNNER_DEFAULT_IMAGE:-}" ]]; then
|
|
log_error " → [$section] default_image is empty and RUNNER_DEFAULT_IMAGE not set in .env"
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
|
|
# custom host: extra SSH keys required
|
|
if [[ "$host" == "custom" ]]; then
|
|
local ssh_host ssh_user
|
|
ssh_host=$(ini_get "$conf" "$section" "ssh_host" "")
|
|
ssh_user=$(ini_get "$conf" "$section" "ssh_user" "")
|
|
if [[ -z "$ssh_host" ]]; then
|
|
log_error " → [$section] ssh_host is empty (required for host=custom)"
|
|
errors=$((errors + 1))
|
|
fi
|
|
if [[ -z "$ssh_user" ]]; then
|
|
log_error " → [$section] ssh_user is empty (required for host=custom)"
|
|
errors=$((errors + 1))
|
|
fi
|
|
fi
|
|
done < <(ini_list_sections "$conf")
|
|
|
|
[[ $errors -eq 0 ]]
|
|
}
|
|
if [[ -f "${SCRIPT_DIR}/runners.conf" ]]; then
|
|
check 5b "runners.conf INI format validation" check_runners_ini_format
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Load env for remaining checks (may fail if .env missing)
|
|
# ---------------------------------------------------------------------------
|
|
if [[ -f "${SCRIPT_DIR}/.env" ]]; then
|
|
load_env
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 6: Required .env vars
|
|
# ---------------------------------------------------------------------------
|
|
REQUIRED_VARS=(
|
|
UNRAID_IP UNRAID_SSH_USER UNRAID_GITEA_DATA_PATH
|
|
FEDORA_IP FEDORA_SSH_USER FEDORA_GITEA_DATA_PATH
|
|
UNRAID_MACVLAN_PARENT UNRAID_MACVLAN_SUBNET UNRAID_MACVLAN_GATEWAY
|
|
UNRAID_MACVLAN_IP_RANGE UNRAID_GITEA_IP UNRAID_CADDY_IP
|
|
FEDORA_MACVLAN_PARENT FEDORA_MACVLAN_SUBNET FEDORA_MACVLAN_GATEWAY
|
|
FEDORA_MACVLAN_IP_RANGE FEDORA_GITEA_IP
|
|
GITEA_ADMIN_USER GITEA_ADMIN_PASSWORD GITEA_ADMIN_EMAIL
|
|
GITEA_ORG_NAME GITEA_INSTANCE_NAME
|
|
GITEA_DOMAIN GITEA_INTERNAL_URL
|
|
GITEA_BACKUP_INTERNAL_URL BACKUP_STORAGE_PATH
|
|
GITHUB_USERNAME GITHUB_TOKEN
|
|
REPO_NAMES
|
|
RUNNER_DEFAULT_IMAGE
|
|
TLS_MODE CADDY_DOMAIN CADDY_DATA_PATH
|
|
)
|
|
|
|
# shellcheck disable=SC2329
|
|
check_required_vars() {
|
|
local missing=0
|
|
for var in "${REQUIRED_VARS[@]}"; do
|
|
if [[ -z "${!var:-}" ]]; then
|
|
log_error " → Missing required var: $var"
|
|
missing=1
|
|
fi
|
|
done
|
|
|
|
# DB vars are conditional on GITEA_DB_TYPE (required when NOT sqlite3)
|
|
if [[ "${GITEA_DB_TYPE:-sqlite3}" != "sqlite3" ]]; then
|
|
for var in GITEA_DB_HOST GITEA_DB_PORT GITEA_DB_NAME GITEA_DB_USER GITEA_DB_PASSWD; do
|
|
if [[ -z "${!var:-}" ]]; then
|
|
log_error " → Missing required var: $var (required when GITEA_DB_TYPE=${GITEA_DB_TYPE})"
|
|
missing=1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# TLS vars are conditional on TLS_MODE:
|
|
# - cloudflare => CLOUDFLARE_API_TOKEN is required
|
|
# - existing => SSL_CERT_PATH + SSL_KEY_PATH are required
|
|
case "${TLS_MODE:-}" in
|
|
cloudflare)
|
|
if [[ -z "${CLOUDFLARE_API_TOKEN:-}" ]]; then
|
|
log_error " → Missing required var: CLOUDFLARE_API_TOKEN (required when TLS_MODE=cloudflare)"
|
|
missing=1
|
|
fi
|
|
;;
|
|
existing)
|
|
if [[ -z "${SSL_CERT_PATH:-}" ]]; then
|
|
log_error " → Missing required var: SSL_CERT_PATH (required when TLS_MODE=existing)"
|
|
missing=1
|
|
fi
|
|
if [[ -z "${SSL_KEY_PATH:-}" ]]; then
|
|
log_error " → Missing required var: SSL_KEY_PATH (required when TLS_MODE=existing)"
|
|
missing=1
|
|
fi
|
|
;;
|
|
*)
|
|
log_error " → Invalid TLS_MODE='${TLS_MODE:-<empty>}' (must be 'cloudflare' or 'existing')"
|
|
missing=1
|
|
;;
|
|
esac
|
|
|
|
return "$missing"
|
|
}
|
|
check 6 "All required .env vars are set" check_required_vars
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 6b: .env value format validation (IPs, ports, emails, booleans, etc.)
|
|
# ---------------------------------------------------------------------------
|
|
check 6b ".env values pass format validation" validate_env
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 7: SSH to Unraid
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_ssh_unraid() {
|
|
ssh_check UNRAID
|
|
}
|
|
check 7 "SSH to Unraid (${UNRAID_IP:-<not set>})" check_ssh_unraid
|
|
if ! ssh_check UNRAID 2>/dev/null; then
|
|
log_error " → Cannot SSH to Unraid. Run setup/unraid.sh or check SSH config."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 8: SSH to Fedora
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_ssh_fedora() {
|
|
ssh_check FEDORA
|
|
}
|
|
check 8 "SSH to Fedora (${FEDORA_IP:-<not set>})" check_ssh_fedora
|
|
if ! ssh_check FEDORA 2>/dev/null; then
|
|
log_error " → Cannot SSH to Fedora. Run setup/fedora.sh or check SSH config."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Checks 2-3: Remote OS checks (deferred until after SSH is confirmed)
|
|
# These are numbered 2-3 in the output but run after SSH checks because
|
|
# they require SSH connectivity to `uname -s` on the remote machines.
|
|
# ---------------------------------------------------------------------------
|
|
check 2 "Unraid is Linux" check_unraid_os
|
|
if ! check_unraid_os 2>/dev/null; then
|
|
log_error " → UNRAID_IP points to a non-Linux machine. Check your .env."
|
|
fi
|
|
check 3 "Fedora is Linux with dnf (RPM-based)" check_fedora_os
|
|
if ! check_fedora_os 2>/dev/null; then
|
|
log_error " → FEDORA_IP points to a machine that isn't RPM-based Linux. Check your .env."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 9: Docker on Unraid
|
|
# ---------------------------------------------------------------------------
|
|
check_docker_unraid() {
|
|
ssh_exec UNRAID "docker --version" &>/dev/null
|
|
}
|
|
check 9 "Docker available on Unraid" check_docker_unraid
|
|
if ! check_docker_unraid 2>/dev/null; then
|
|
log_error " → Docker not found on Unraid. Run setup/unraid.sh."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 10: Docker on Fedora
|
|
# ---------------------------------------------------------------------------
|
|
check_docker_fedora() {
|
|
ssh_exec FEDORA "docker --version" &>/dev/null
|
|
}
|
|
check 10 "Docker available on Fedora" check_docker_fedora
|
|
if ! check_docker_fedora 2>/dev/null; then
|
|
log_error " → Docker not found on Fedora. Run setup/fedora.sh."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 11: docker-compose on Unraid
|
|
# ---------------------------------------------------------------------------
|
|
check_compose_unraid() {
|
|
ssh_exec UNRAID "docker compose version" &>/dev/null || ssh_exec UNRAID "docker-compose --version" &>/dev/null
|
|
}
|
|
check 11 "docker-compose available on Unraid" check_compose_unraid
|
|
if ! check_compose_unraid 2>/dev/null; then
|
|
log_error " → docker-compose not found on Unraid. Run setup/unraid.sh."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 12: docker-compose on Fedora
|
|
# ---------------------------------------------------------------------------
|
|
check_compose_fedora() {
|
|
ssh_exec FEDORA "docker compose version" &>/dev/null || ssh_exec FEDORA "docker-compose --version" &>/dev/null
|
|
}
|
|
check 12 "docker-compose available on Fedora" check_compose_fedora
|
|
if ! check_compose_fedora 2>/dev/null; then
|
|
log_error " → docker-compose not found on Fedora. Run setup/fedora.sh."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 13: Container IPs not already in use
|
|
# Ping the requested macvlan IPs to verify they're available.
|
|
# Skipped when --skip-port-checks is set (containers may already be running).
|
|
# ---------------------------------------------------------------------------
|
|
if [[ "$SKIP_PORT_CHECKS" == "true" ]]; then
|
|
log_info "[13] Container IP availability — SKIPPED (--skip-port-checks)"
|
|
else
|
|
# shellcheck disable=SC2329
|
|
check_ips_available() {
|
|
local fail=0
|
|
for ip_var in UNRAID_GITEA_IP UNRAID_CADDY_IP FEDORA_GITEA_IP; do
|
|
local ip="${!ip_var:-}"
|
|
[[ -z "$ip" ]] && continue
|
|
# ping -c1 -W1: one packet, 1-second timeout
|
|
if ping -c1 -W1 "$ip" &>/dev/null; then
|
|
log_warn " → $ip_var ($ip) is already responding to ping (may be in use)"
|
|
fail=1
|
|
fi
|
|
done
|
|
return "$fail"
|
|
}
|
|
check 13 "Container IPs not already in use" check_ips_available
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 14a: python3 available for DNS resolution checks
|
|
# ---------------------------------------------------------------------------
|
|
check_python3() {
|
|
command -v python3 >/dev/null 2>&1
|
|
}
|
|
check 14a "python3 available for DNS resolution checks" check_python3
|
|
if ! check_python3 2>/dev/null; then
|
|
log_error " → python3 is required for DNS validation (Check 14b). Install it (e.g. brew install python)."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 14b: DNS resolves (informational — only needed for Phase 8 TLS)
|
|
# ---------------------------------------------------------------------------
|
|
if ! check_python3 2>/dev/null; then
|
|
log_warn "[14b] DNS: skipped — python3 not found (install python3 to run DNS validation before Phase 8)"
|
|
elif [[ -n "${GITEA_DOMAIN:-}" ]] && [[ -n "${UNRAID_IP:-}" ]]; then
|
|
dns_resolved=$(python3 -c "import socket; print('\n'.join(r[4][0] for r in socket.getaddrinfo('${GITEA_DOMAIN}', None, socket.AF_INET)))" 2>/dev/null | sort -u) || dns_resolved=""
|
|
if [[ -n "$dns_resolved" ]] && printf '%s\n' "$dns_resolved" | grep -Fxq "${UNRAID_IP}"; then
|
|
log_success "[14b] DNS: ${GITEA_DOMAIN} resolves to ${UNRAID_IP}"
|
|
PASS_COUNT=$((PASS_COUNT + 1))
|
|
else
|
|
if [[ -z "$dns_resolved" ]]; then
|
|
log_warn "[14b] DNS: ${GITEA_DOMAIN} does not resolve (needed before Phase 8)"
|
|
else
|
|
log_warn "[14b] DNS: ${GITEA_DOMAIN} resolves to ${dns_resolved} but expected ${UNRAID_IP} (needed before Phase 8)"
|
|
fi
|
|
fi
|
|
else
|
|
log_warn "[14b] DNS: skipped — GITEA_DOMAIN or UNRAID_IP not set"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 15: GitHub token valid
|
|
# ---------------------------------------------------------------------------
|
|
check_github_token() {
|
|
[[ -n "${GITHUB_TOKEN:-}" ]] && curl -sf -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/user -o /dev/null
|
|
}
|
|
check 15 "GitHub token valid" check_github_token
|
|
if ! check_github_token 2>/dev/null; then
|
|
log_error " → GitHub token invalid. Check GITHUB_TOKEN in .env."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 16: GitHub repos exist
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_github_repos() {
|
|
local all_ok=0
|
|
|
|
if [[ -z "${GITHUB_USERNAME:-}" ]]; then
|
|
log_error " → GITHUB_USERNAME is empty"
|
|
return 1
|
|
fi
|
|
|
|
local repo
|
|
for repo in ${REPO_NAMES:-}; do
|
|
if ! curl -sf -H "Authorization: token ${GITHUB_TOKEN:-}" "https://api.github.com/repos/${GITHUB_USERNAME}/${repo}" -o /dev/null 2>/dev/null; then
|
|
log_error " → GitHub repo ${repo} not found under ${GITHUB_USERNAME}"
|
|
all_ok=1
|
|
fi
|
|
done
|
|
return "$all_ok"
|
|
}
|
|
check 16 "All GitHub repos exist" check_github_repos
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 17: Caddy data path writable on Unraid
|
|
# ---------------------------------------------------------------------------
|
|
check_caddy_path() {
|
|
local caddy_parent
|
|
caddy_parent=$(dirname "${CADDY_DATA_PATH:-/nonexistent}")
|
|
ssh_exec UNRAID "test -d '${CADDY_DATA_PATH}' && test -w '${CADDY_DATA_PATH}'" 2>/dev/null \
|
|
|| ssh_exec UNRAID "test -w '${caddy_parent}'" 2>/dev/null
|
|
}
|
|
check 17 "Caddy data path writable (${CADDY_DATA_PATH:-<not set>})" check_caddy_path
|
|
if ! check_caddy_path 2>/dev/null; then
|
|
log_error " → Caddy data path ${CADDY_DATA_PATH:-} not writable on Unraid (or parent dir doesn't exist)."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 18: Local tool minimum versions
|
|
# Validates that tools on the MacBook meet minimum requirements.
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_local_versions() {
|
|
local fail=0
|
|
check_min_version "jq" "jq --version" "1.6" || fail=1
|
|
check_min_version "curl" "curl --version" "7.70" || fail=1
|
|
check_min_version "git" "git --version" "2.30" || fail=1
|
|
return "$fail"
|
|
}
|
|
check 18 "Local tool minimum versions (jq>=1.6, curl>=7.70, git>=2.30)" check_local_versions
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 19: Unraid tool minimum versions
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_unraid_versions() {
|
|
local fail=0
|
|
check_remote_min_version "UNRAID" "docker" "docker --version" "20.0" || fail=1
|
|
check_remote_min_version "UNRAID" "docker-compose" "docker compose version 2>/dev/null || docker-compose --version" "2.0" || fail=1
|
|
check_remote_min_version "UNRAID" "jq" "jq --version" "1.6" || fail=1
|
|
return "$fail"
|
|
}
|
|
check 19 "Unraid tool minimum versions (docker>=20, compose>=2, jq>=1.6)" check_unraid_versions
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 20: Fedora tool minimum versions
|
|
# ---------------------------------------------------------------------------
|
|
# shellcheck disable=SC2329
|
|
check_fedora_versions() {
|
|
local fail=0
|
|
check_remote_min_version "FEDORA" "docker" "docker --version" "20.0" || fail=1
|
|
check_remote_min_version "FEDORA" "docker-compose" "docker compose version" "2.0" || fail=1
|
|
check_remote_min_version "FEDORA" "jq" "jq --version" "1.6" || fail=1
|
|
return "$fail"
|
|
}
|
|
check 20 "Fedora tool minimum versions (docker>=20, compose>=2, jq>=1.6)" check_fedora_versions
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 21: Unraid can SSH to Fedora (required for backup transfer)
|
|
# ---------------------------------------------------------------------------
|
|
check_unraid_to_fedora_ssh() {
|
|
ssh_exec UNRAID "ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o BatchMode=yes -p '${FEDORA_SSH_PORT:-22}' '${FEDORA_SSH_USER}@${FEDORA_IP}' true" &>/dev/null
|
|
}
|
|
check 21 "Unraid can SSH to Fedora (host-to-host backup path)" check_unraid_to_fedora_ssh
|
|
if ! check_unraid_to_fedora_ssh 2>/dev/null; then
|
|
log_error " → Unraid cannot SSH to Fedora with key auth (needed by backup/backup_primary.sh)."
|
|
log_error " → Configure SSH keys so Unraid can run: ssh ${FEDORA_SSH_USER}@${FEDORA_IP}"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Check 22: Fedora can SSH to Unraid (required for restore transfer)
|
|
# ---------------------------------------------------------------------------
|
|
check_fedora_to_unraid_ssh() {
|
|
ssh_exec FEDORA "ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o BatchMode=yes -p '${UNRAID_SSH_PORT:-22}' '${UNRAID_SSH_USER}@${UNRAID_IP}' true" &>/dev/null
|
|
}
|
|
check 22 "Fedora can SSH to Unraid (host-to-host restore path)" check_fedora_to_unraid_ssh
|
|
if ! check_fedora_to_unraid_ssh 2>/dev/null; then
|
|
log_error " → Fedora cannot SSH to Unraid with key auth (needed by backup/restore_to_primary.sh)."
|
|
log_error " → Configure SSH keys so Fedora can run: ssh ${UNRAID_SSH_USER}@${UNRAID_IP}"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Summary
|
|
# ---------------------------------------------------------------------------
|
|
TOTAL_CHECKS=$((PASS_COUNT + FAIL_COUNT))
|
|
printf '\n'
|
|
log_info "Results: ${PASS_COUNT} passed, ${FAIL_COUNT} failed (out of ${TOTAL_CHECKS} checks)"
|
|
|
|
if [[ $FAIL_COUNT -gt 0 ]]; then
|
|
log_error "Preflight FAILED — fix the issues above before proceeding."
|
|
exit 1
|
|
else
|
|
log_success "All preflight checks passed. Ready to run migration phases."
|
|
exit 0
|
|
fi
|