diff --git a/setup/configure_env.sh b/setup/configure_env.sh new file mode 100755 index 0000000..68dc90f --- /dev/null +++ b/setup/configure_env.sh @@ -0,0 +1,342 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# setup/configure_env.sh — Interactive .env configuration wizard +# Prompts for every required variable with validation and progress tracking. +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="${SCRIPT_DIR}/.." +ENV_FILE="${PROJECT_ROOT}/.env" +ENV_EXAMPLE="${PROJECT_ROOT}/.env.example" + +# Colors +if [[ -t 1 ]]; then + C_RESET='\033[0m'; C_BOLD='\033[1m'; C_GREEN='\033[0;32m' + C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'; C_CYAN='\033[0;36m'; C_DIM='\033[2m' +else + C_RESET=''; C_BOLD=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_CYAN=''; C_DIM='' +fi + +# --------------------------------------------------------------------------- +# Initialize .env if it doesn't exist +# --------------------------------------------------------------------------- +if [[ ! -f "$ENV_FILE" ]]; then + if [[ ! -f "$ENV_EXAMPLE" ]]; then + printf '%b[ERROR]%b .env.example not found at %s\n' "$C_RED" "$C_RESET" "$ENV_EXAMPLE" >&2 + exit 1 + fi + cp "$ENV_EXAMPLE" "$ENV_FILE" + printf '%b[INFO]%b Created .env from .env.example\n' "$C_CYAN" "$C_RESET" >&2 +fi + +# --------------------------------------------------------------------------- +# Load current values +# --------------------------------------------------------------------------- +declare -A CURRENT_VALUES +while IFS= read -r line; do + # Skip comments and blank lines + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "$line" ]] && continue + # Extract KEY=VALUE (strip inline comments) + if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*) ]]; then + key="${BASH_REMATCH[1]}" + val="${BASH_REMATCH[2]}" + # Strip inline comment (everything after # preceded by spaces) + val="${val%%#*}" + # Trim trailing whitespace + val="${val%"${val##*[![:space:]]}"}" + CURRENT_VALUES["$key"]="$val" + fi +done < "$ENV_FILE" + +# --------------------------------------------------------------------------- +# Validation functions +# --------------------------------------------------------------------------- +validate_ip() { + [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] +} + +validate_port() { + [[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -ge 1 ]] && [[ "$1" -le 65535 ]] +} + +validate_email() { + [[ "$1" == *@* ]] +} + +validate_path() { + [[ "$1" == /* ]] +} + +validate_url() { + [[ "$1" =~ ^https?:// ]] +} + +validate_bool() { + [[ "$1" == "true" ]] || [[ "$1" == "false" ]] +} + +validate_integer() { + [[ "$1" =~ ^[0-9]+$ ]] +} + +validate_nonempty() { + [[ -n "$1" ]] +} + +validate_password() { + [[ ${#1} -ge 8 ]] +} + +validate_ssl_mode() { + [[ "$1" == "letsencrypt" ]] || [[ "$1" == "existing" ]] +} + +# --------------------------------------------------------------------------- +# Prompt function +# --------------------------------------------------------------------------- +TOTAL_PROMPTS=50 +CURRENT_PROMPT=0 +LAST_SECTION="" + +# Collected SSL_MODE for conditional logic +COLLECTED_SSL_MODE="" + +prompt_var() { + local var_name="$1" + local description="$2" + local validation="$3" + local default="${4:-}" + local section="$5" + + CURRENT_PROMPT=$((CURRENT_PROMPT + 1)) + + # Print section header when entering a new section + if [[ "$section" != "$LAST_SECTION" ]]; then + LAST_SECTION="$section" + printf '\n%b── %s ──────────────────────────────────────────%b\n' "$C_BOLD" "$section" "$C_RESET" + fi + + # Determine current value (from .env or default) + local current="${CURRENT_VALUES[$var_name]:-$default}" + + # Build prompt + local progress + progress=$(printf '[%d/%d]' "$CURRENT_PROMPT" "$TOTAL_PROMPTS") + + local prompt_text + if [[ -n "$current" ]]; then + # Mask passwords + if [[ "$validation" == "password" ]]; then + prompt_text=$(printf '%b%s%b %s (%s) %b[****]%b: ' "$C_DIM" "$progress" "$C_RESET" "$var_name" "$description" "$C_YELLOW" "$C_RESET") + else + prompt_text=$(printf '%b%s%b %s (%s) %b[%s]%b: ' "$C_DIM" "$progress" "$C_RESET" "$var_name" "$description" "$C_YELLOW" "$current" "$C_RESET") + fi + else + prompt_text=$(printf '%b%s%b %s (%s): ' "$C_DIM" "$progress" "$C_RESET" "$var_name" "$description") + fi + + # Read input with validation loop + local value + while true; do + printf '%b' "$prompt_text" + read -r value + + # Use current/default if empty + if [[ -z "$value" ]]; then + value="$current" + fi + + # Validate + if [[ -z "$value" ]] && [[ "$validation" != "optional" ]]; then + printf '%b Invalid: value cannot be empty%b\n' "$C_RED" "$C_RESET" + continue + fi + + case "$validation" in + ip) + if validate_ip "$value"; then break; fi + printf '%b Invalid IP address format (expected: x.x.x.x)%b\n' "$C_RED" "$C_RESET" + ;; + port) + if validate_port "$value"; then break; fi + printf '%b Invalid port (expected: 1-65535)%b\n' "$C_RED" "$C_RESET" + ;; + email) + if validate_email "$value"; then break; fi + printf '%b Invalid email (must contain @)%b\n' "$C_RED" "$C_RESET" + ;; + path) + if validate_path "$value"; then break; fi + printf '%b Invalid path (must start with /)%b\n' "$C_RED" "$C_RESET" + ;; + url) + if validate_url "$value"; then break; fi + printf '%b Invalid URL (must start with http:// or https://)%b\n' "$C_RED" "$C_RESET" + ;; + bool) + if validate_bool "$value"; then break; fi + printf '%b Invalid: must be true or false%b\n' "$C_RED" "$C_RESET" + ;; + integer) + if validate_integer "$value"; then break; fi + printf '%b Invalid: must be a number%b\n' "$C_RED" "$C_RESET" + ;; + password) + if validate_password "$value"; then break; fi + printf '%b Invalid: password must be at least 8 characters%b\n' "$C_RED" "$C_RESET" + ;; + ssl_mode) + if validate_ssl_mode "$value"; then break; fi + printf '%b Invalid: must be "letsencrypt" or "existing"%b\n' "$C_RED" "$C_RESET" + ;; + nonempty|*) + if validate_nonempty "$value"; then break; fi + printf '%b Invalid: value cannot be empty%b\n' "$C_RED" "$C_RESET" + ;; + esac + done + + # Write to .env + write_env_var "$var_name" "$value" + + # Track SSL mode for conditional prompts + if [[ "$var_name" == "SSL_MODE" ]]; then + COLLECTED_SSL_MODE="$value" + fi +} + +# --------------------------------------------------------------------------- +# Write a variable to .env file (preserving structure) +# --------------------------------------------------------------------------- +write_env_var() { + local key="$1" value="$2" + + if grep -q "^${key}=" "$ENV_FILE"; then + # Replace existing line (preserve inline comment if present in .env.example) + local comment="" + if grep -q "^${key}=" "$ENV_EXAMPLE" 2>/dev/null; then + comment=$(grep "^${key}=" "$ENV_EXAMPLE" | sed "s/^${key}=[^#]*//" | sed 's/^[[:space:]]*//') + fi + if [[ -n "$comment" ]]; then + # Pad value to align comment + local padded + padded=$(printf '%-30s' "$value") + sed -i.bak "s|^${key}=.*|${key}=${padded}${comment}|" "$ENV_FILE" + else + sed -i.bak "s|^${key}=.*|${key}=${value}|" "$ENV_FILE" + fi + rm -f "${ENV_FILE}.bak" + else + printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE" + fi +} + +# =========================================================================== +# Main — walk through all variables in section order +# =========================================================================== + +printf '\n%b╔══════════════════════════════════════════════════════════╗%b\n' "$C_BOLD" "$C_RESET" +printf '%b║ Gitea Migration — Environment Setup ║%b\n' "$C_BOLD" "$C_RESET" +printf '%b╚══════════════════════════════════════════════════════════╝%b\n\n' "$C_BOLD" "$C_RESET" +printf 'Press Enter to keep the current value shown in [brackets].\n' +printf 'Auto-populated variables (tokens) will be skipped.\n' + +# --- UNRAID SERVER --- +prompt_var "UNRAID_IP" "Static IP of Unraid server" ip "" "UNRAID SERVER" +prompt_var "UNRAID_SSH_USER" "SSH username for Unraid" nonempty "" "UNRAID SERVER" +prompt_var "UNRAID_SSH_PORT" "SSH port" port "22" "UNRAID SERVER" +prompt_var "UNRAID_GITEA_PORT" "Port Gitea web UI will listen on" port "3000" "UNRAID SERVER" +prompt_var "UNRAID_GITEA_SSH_PORT" "Port for git-over-SSH" port "2222" "UNRAID SERVER" +prompt_var "UNRAID_GITEA_DATA_PATH" "Absolute path on NVMe for Gitea data" path "" "UNRAID SERVER" + +# --- FEDORA SERVER --- +prompt_var "FEDORA_IP" "Static IP of Fedora server" ip "" "FEDORA SERVER" +prompt_var "FEDORA_SSH_USER" "SSH username for Fedora" nonempty "" "FEDORA SERVER" +prompt_var "FEDORA_SSH_PORT" "SSH port" port "22" "FEDORA SERVER" +prompt_var "FEDORA_GITEA_PORT" "Port Gitea web UI will listen on" port "3000" "FEDORA SERVER" +prompt_var "FEDORA_GITEA_SSH_PORT" "Port for git-over-SSH" port "2222" "FEDORA SERVER" +prompt_var "FEDORA_GITEA_DATA_PATH" "Absolute path on NVMe for Gitea data" path "" "FEDORA SERVER" + +# --- GITEA SHARED CREDENTIALS --- +prompt_var "GITEA_ADMIN_USER" "Admin username (same on both instances)" nonempty "" "GITEA SHARED CREDENTIALS" +prompt_var "GITEA_ADMIN_PASSWORD" "Admin password (min 8 chars)" password "" "GITEA SHARED CREDENTIALS" +prompt_var "GITEA_ADMIN_EMAIL" "Admin email" email "" "GITEA SHARED CREDENTIALS" +prompt_var "GITEA_ORG_NAME" "Organization name (e.g. mifi-llc)" nonempty "" "GITEA SHARED CREDENTIALS" +prompt_var "GITEA_INSTANCE_NAME" "Display name for Gitea (e.g. MIFI Git)" nonempty "" "GITEA SHARED CREDENTIALS" +prompt_var "GITEA_DB_TYPE" "Database type" nonempty "sqlite3" "GITEA SHARED CREDENTIALS" +prompt_var "GITEA_VERSION" "Gitea Docker image tag" nonempty "1.23" "GITEA SHARED CREDENTIALS" +prompt_var "ACT_RUNNER_VERSION" "act_runner version" nonempty "0.2.11" "GITEA SHARED CREDENTIALS" + +# --- GITEA PRIMARY INSTANCE --- +prompt_var "GITEA_DOMAIN" "Public domain pointing to Unraid" nonempty "" "GITEA PRIMARY INSTANCE" +prompt_var "GITEA_INTERNAL_URL" "Internal URL (e.g. http://IP:3000)" url "" "GITEA PRIMARY INSTANCE" + +# --- GITEA BACKUP INSTANCE --- +prompt_var "GITEA_BACKUP_INTERNAL_URL" "Internal URL of Fedora Gitea" url "" "GITEA BACKUP INSTANCE" +prompt_var "GITEA_BACKUP_MIRROR_INTERVAL" "How often Fedora pulls from Unraid" nonempty "8h" "GITEA BACKUP INSTANCE" +prompt_var "BACKUP_STORAGE_PATH" "Path on Fedora for backup archives" path "" "GITEA BACKUP INSTANCE" +prompt_var "BACKUP_RETENTION_COUNT" "Number of backup archives to keep" integer "5" "GITEA BACKUP INSTANCE" + +# --- REPOSITORIES --- +prompt_var "GITHUB_USERNAME" "GitHub username or org name" nonempty "" "REPOSITORIES" +prompt_var "GITHUB_TOKEN" "GitHub personal access token (repo read)" nonempty "" "REPOSITORIES" +prompt_var "REPO_1_NAME" "First repo name (exact match)" nonempty "" "REPOSITORIES" +prompt_var "REPO_2_NAME" "Second repo name (exact match)" nonempty "" "REPOSITORIES" +prompt_var "REPO_3_NAME" "Third repo name (exact match)" nonempty "" "REPOSITORIES" +prompt_var "MIGRATE_ISSUES" "Migrate GitHub issues" bool "false" "REPOSITORIES" +prompt_var "MIGRATE_LABELS" "Migrate GitHub labels" bool "true" "REPOSITORIES" +prompt_var "MIGRATE_MILESTONES" "Migrate GitHub milestones" bool "false" "REPOSITORIES" +prompt_var "MIGRATE_WIKI" "Migrate GitHub wiki" bool "false" "REPOSITORIES" + +# --- GITHUB MIRROR --- +prompt_var "GITHUB_MIRROR_TOKEN" "GitHub PAT with repo write scope" nonempty "" "GITHUB MIRROR" +prompt_var "GITHUB_MIRROR_INTERVAL" "How often Gitea pushes to GitHub" nonempty "8h" "GITHUB MIRROR" + +# --- NGINX REVERSE PROXY --- +prompt_var "NGINX_CONTAINER_NAME" "Name of existing Nginx Docker container" nonempty "" "NGINX REVERSE PROXY" +prompt_var "NGINX_CONF_PATH" "Host path to Nginx conf.d directory" path "" "NGINX REVERSE PROXY" +prompt_var "SSL_MODE" "SSL mode: letsencrypt or existing" ssl_mode "letsencrypt" "NGINX REVERSE PROXY" + +# Conditional SSL prompts +if [[ "$COLLECTED_SSL_MODE" == "letsencrypt" ]]; then + prompt_var "SSL_EMAIL" "Email for Let's Encrypt" email "" "NGINX REVERSE PROXY" + # Skip cert path prompts but still count them for progress + CURRENT_PROMPT=$((CURRENT_PROMPT + 2)) +else + # Skip email prompt but count it + CURRENT_PROMPT=$((CURRENT_PROMPT + 1)) + prompt_var "SSL_CERT_PATH" "Absolute path to SSL cert on Unraid" path "" "NGINX REVERSE PROXY" + prompt_var "SSL_KEY_PATH" "Absolute path to SSL key on Unraid" path "" "NGINX REVERSE PROXY" +fi + +# --- BRANCH PROTECTION --- +prompt_var "PROTECTED_BRANCH" "Branch to protect across all repos" nonempty "main" "BRANCH PROTECTION" +prompt_var "REQUIRE_PR_REVIEW" "Require PR review before merge" bool "false" "BRANCH PROTECTION" +prompt_var "REQUIRED_APPROVALS" "Number of approvals required" integer "1" "BRANCH PROTECTION" + +# --- SECURITY --- +prompt_var "SEMGREP_VERSION" "Semgrep OSS version" nonempty "latest" "SECURITY" +prompt_var "TRIVY_VERSION" "Trivy version" nonempty "latest" "SECURITY" +prompt_var "GITLEAKS_VERSION" "Gitleaks version" nonempty "latest" "SECURITY" +prompt_var "SECURITY_FAIL_ON_ERROR" "Block PR merge if security scan fails" bool "true" "SECURITY" + +# =========================================================================== +# Summary +# =========================================================================== +printf '\n%b╔══════════════════════════════════════════════════════════╗%b\n' "$C_GREEN" "$C_RESET" +printf '%b║ Configuration Complete ║%b\n' "$C_GREEN" "$C_RESET" +printf '%b╚══════════════════════════════════════════════════════════╝%b\n\n' "$C_GREEN" "$C_RESET" + +printf '%bSummary:%b\n' "$C_BOLD" "$C_RESET" +printf ' Unraid: %s@%s:%s\n' "${CURRENT_VALUES[UNRAID_SSH_USER]:-}" "${CURRENT_VALUES[UNRAID_IP]:-}" "${CURRENT_VALUES[UNRAID_SSH_PORT]:-22}" +printf ' Fedora: %s@%s:%s\n' "${CURRENT_VALUES[FEDORA_SSH_USER]:-}" "${CURRENT_VALUES[FEDORA_IP]:-}" "${CURRENT_VALUES[FEDORA_SSH_PORT]:-22}" +printf ' Gitea: %s (admin: %s, password: ****)\n' "${CURRENT_VALUES[GITEA_DOMAIN]:-}" "${CURRENT_VALUES[GITEA_ADMIN_USER]:-}" +printf ' Org: %s\n' "${CURRENT_VALUES[GITEA_ORG_NAME]:-}" +printf ' Repos: %s, %s, %s\n' "${CURRENT_VALUES[REPO_1_NAME]:-}" "${CURRENT_VALUES[REPO_2_NAME]:-}" "${CURRENT_VALUES[REPO_3_NAME]:-}" +printf ' SSL: %s\n' "${COLLECTED_SSL_MODE}" +printf ' .env saved: %s\n\n' "$ENV_FILE" + +printf 'Next step: run %bsetup/macbook.sh%b to install local prerequisites.\n' "$C_BOLD" "$C_RESET" diff --git a/setup/fedora.sh b/setup/fedora.sh new file mode 100755 index 0000000..5c17c61 --- /dev/null +++ b/setup/fedora.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# setup/fedora.sh — Install prerequisites on Fedora (runs via SSH from MacBook) +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/../lib/common.sh" + +load_env +require_vars FEDORA_IP FEDORA_SSH_USER FEDORA_GITEA_DATA_PATH + +log_info "=== Fedora Setup ===" + +# -------------------------------------------------------------------------- +# SSH connectivity +# -------------------------------------------------------------------------- +log_info "Testing SSH to Fedora..." +if ! ssh_check FEDORA; then + log_error "Cannot SSH to Fedora at $FEDORA_IP as $FEDORA_SSH_USER" + exit 1 +fi +log_success "SSH to Fedora OK" + +# -------------------------------------------------------------------------- +# Docker CE +# -------------------------------------------------------------------------- +if ssh_exec FEDORA "docker --version" &>/dev/null; then + log_success "Docker found on Fedora" +else + log_info "Installing Docker CE on Fedora..." + ssh_exec FEDORA "sudo dnf -y install dnf-plugins-core" + ssh_exec FEDORA "sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo" + ssh_exec FEDORA "sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin" + ssh_exec FEDORA "sudo systemctl enable --now docker" + + # Add user to docker group + ssh_exec FEDORA "sudo usermod -aG docker $FEDORA_SSH_USER" + log_warn "User $FEDORA_SSH_USER added to docker group. You may need to re-login for this to take effect." + + if ssh_exec FEDORA "docker --version" &>/dev/null; then + log_success "Docker CE installed on Fedora" + else + log_error "Failed to install Docker on Fedora" + exit 1 + fi +fi + +# -------------------------------------------------------------------------- +# docker compose plugin +# -------------------------------------------------------------------------- +if ssh_exec FEDORA "docker compose version" &>/dev/null; then + log_success "docker compose (plugin) found on Fedora" +else + log_error "docker compose plugin not found. It should have been installed with docker-ce." + exit 1 +fi + +# -------------------------------------------------------------------------- +# jq +# -------------------------------------------------------------------------- +if ssh_exec FEDORA "jq --version" &>/dev/null; then + log_success "jq found on Fedora" +else + log_info "Installing jq on Fedora..." + ssh_exec FEDORA "sudo dnf -y install jq" + if ssh_exec FEDORA "jq --version" &>/dev/null; then + log_success "jq installed on Fedora" + else + log_error "Failed to install jq on Fedora" + exit 1 + fi +fi + +# -------------------------------------------------------------------------- +# Verify Docker works without sudo +# -------------------------------------------------------------------------- +log_info "Testing Docker without sudo..." +if ssh_exec FEDORA "docker run --rm hello-world" &>/dev/null; then + log_success "Docker runs without sudo" +else + log_warn "Docker requires sudo or group membership hasn't taken effect." + log_warn "Log out and back in to Fedora, then re-run this script." +fi + +# -------------------------------------------------------------------------- +# Data path +# -------------------------------------------------------------------------- +log_info "Verifying data path: $FEDORA_GITEA_DATA_PATH" +if ssh_exec FEDORA "mkdir -p '$FEDORA_GITEA_DATA_PATH' && touch '$FEDORA_GITEA_DATA_PATH/.write-test' && rm '$FEDORA_GITEA_DATA_PATH/.write-test'"; then + log_success "Data path exists and is writable" +else + log_error "Cannot write to $FEDORA_GITEA_DATA_PATH on Fedora" + exit 1 +fi + +# -------------------------------------------------------------------------- +# Summary +# -------------------------------------------------------------------------- +log_success "All Fedora prerequisites satisfied" diff --git a/setup/macbook.sh b/setup/macbook.sh new file mode 100755 index 0000000..a3eab11 --- /dev/null +++ b/setup/macbook.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# setup/macbook.sh — Install prerequisites on MacBook (runs locally) +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/../lib/common.sh" + +log_info "=== MacBook Setup ===" + +# -------------------------------------------------------------------------- +# Homebrew +# -------------------------------------------------------------------------- +if ! command -v brew &>/dev/null; then + log_error "Homebrew is not installed." + log_error "Install it manually: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + log_error "Then re-run this script." + exit 1 +fi +log_success "Homebrew found" + +# -------------------------------------------------------------------------- +# Brew packages +# -------------------------------------------------------------------------- +BREW_PACKAGES=(jq curl gettext shellcheck gh) + +for pkg in "${BREW_PACKAGES[@]}"; do + if brew list "$pkg" &>/dev/null; then + log_success "$pkg already installed" + else + log_info "Installing $pkg..." + brew install "$pkg" + log_success "$pkg installed" + fi +done + +# -------------------------------------------------------------------------- +# Verify built-in tools +# -------------------------------------------------------------------------- +BUILTINS=(ssh git scp) +for tool in "${BUILTINS[@]}"; do + if command -v "$tool" &>/dev/null; then + log_success "$tool found" + else + log_error "$tool not found — this should be built into macOS" + exit 1 + fi +done + +# Verify envsubst specifically (comes from gettext) +if command -v envsubst &>/dev/null; then + log_success "envsubst found (from gettext)" +else + log_error "envsubst not found. Try: brew link --force gettext" + exit 1 +fi + +# -------------------------------------------------------------------------- +# Xcode Command Line Tools +# -------------------------------------------------------------------------- +if xcode-select -p &>/dev/null; then + log_success "Xcode Command Line Tools found" +else + log_info "Installing Xcode Command Line Tools..." + xcode-select --install + log_warn "Xcode CLI Tools installation started. Wait for it to finish, then re-run this script." + exit 0 +fi + +# -------------------------------------------------------------------------- +# Network access check (if .env exists) +# -------------------------------------------------------------------------- +if [[ -f "${SCRIPT_DIR}/../.env" ]]; then + load_env + if [[ -n "${UNRAID_IP:-}" ]]; then + if ssh_check UNRAID; then + log_success "SSH to Unraid ($UNRAID_IP) OK" + else + log_warn "Cannot SSH to Unraid ($UNRAID_IP) — check SSH config" + fi + fi + if [[ -n "${FEDORA_IP:-}" ]]; then + if ssh_check FEDORA; then + log_success "SSH to Fedora ($FEDORA_IP) OK" + else + log_warn "Cannot SSH to Fedora ($FEDORA_IP) — check SSH config" + fi + fi +else + log_info "No .env file — skipping network checks. Run setup/configure_env.sh first." +fi + +# -------------------------------------------------------------------------- +# Summary +# -------------------------------------------------------------------------- +log_success "All MacBook prerequisites satisfied" diff --git a/setup/unraid.sh b/setup/unraid.sh new file mode 100755 index 0000000..6478ef5 --- /dev/null +++ b/setup/unraid.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# setup/unraid.sh — Install prerequisites on Unraid (runs via SSH from MacBook) +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/../lib/common.sh" + +load_env +require_vars UNRAID_IP UNRAID_SSH_USER UNRAID_GITEA_DATA_PATH + +log_info "=== Unraid Setup ===" + +# -------------------------------------------------------------------------- +# SSH connectivity +# -------------------------------------------------------------------------- +log_info "Testing SSH to Unraid..." +if ! ssh_check UNRAID; then + log_error "Cannot SSH to Unraid at $UNRAID_IP as $UNRAID_SSH_USER" + exit 1 +fi +log_success "SSH to Unraid OK" + +# -------------------------------------------------------------------------- +# Docker (pre-installed on Unraid — DO NOT attempt to install) +# -------------------------------------------------------------------------- +if ssh_exec UNRAID "docker --version" &>/dev/null; then + log_success "Docker found on Unraid" +else + log_error "Docker not found on Unraid." + log_error "Enable Docker via Unraid's Settings > Docker page." + log_error "This script does not install Docker on Unraid." + exit 1 +fi + +# -------------------------------------------------------------------------- +# docker-compose (try plugin first, then standalone) +# -------------------------------------------------------------------------- +if ssh_exec UNRAID "docker compose version" &>/dev/null; then + log_success "docker compose (plugin) found on Unraid" +elif ssh_exec UNRAID "docker-compose --version" &>/dev/null; then + log_success "docker-compose (standalone) found on Unraid" +else + log_info "Installing docker-compose standalone binary on Unraid..." + COMPOSE_VERSION="v2.29.1" + ssh_exec UNRAID "curl -SL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose" + if ssh_exec UNRAID "docker-compose --version" &>/dev/null; then + log_success "docker-compose installed on Unraid" + else + log_error "Failed to install docker-compose on Unraid" + exit 1 + fi +fi + +# -------------------------------------------------------------------------- +# jq (install via static binary — Unraid has no package manager) +# -------------------------------------------------------------------------- +if ssh_exec UNRAID "jq --version" &>/dev/null; then + log_success "jq found on Unraid" +else + log_info "Installing jq static binary on Unraid..." + JQ_VERSION="1.7.1" + ssh_exec UNRAID "curl -SL https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/jq-linux-amd64 -o /usr/local/bin/jq && chmod +x /usr/local/bin/jq" + if ssh_exec UNRAID "jq --version" &>/dev/null; then + log_success "jq installed on Unraid" + else + log_error "Failed to install jq on Unraid" + exit 1 + fi +fi + +# -------------------------------------------------------------------------- +# Data path +# -------------------------------------------------------------------------- +log_info "Verifying data path: $UNRAID_GITEA_DATA_PATH" +if ssh_exec UNRAID "mkdir -p '$UNRAID_GITEA_DATA_PATH' && touch '$UNRAID_GITEA_DATA_PATH/.write-test' && rm '$UNRAID_GITEA_DATA_PATH/.write-test'"; then + log_success "Data path exists and is writable" +else + log_error "Cannot write to $UNRAID_GITEA_DATA_PATH on Unraid" + exit 1 +fi + +# -------------------------------------------------------------------------- +# Summary +# -------------------------------------------------------------------------- +log_success "All Unraid prerequisites satisfied"