#!/bin/bash

# Ubuntu 26.04 Post-Installation Script
# Run with: sudo bash post-install.sh
# This script is rerunnable - it detects existing installations

# ============================================================================
# COMMAND-LINE ARGUMENT PARSING
# ============================================================================

DRY_RUN=false
UNATTENDED=false
LOG_FILE="/var/log/post-install.log"
WHIPTAIL_USED=false

show_help() {
    echo "Ubuntu 26.04 Post-Installation Script"
    echo ""
    echo "Usage: sudo ./post-install.sh [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --dry-run      Preview what would be installed without making changes"
    echo "  --unattended   Run with default options (no prompts)"
    echo "                 Defaults: skip key generation, no SSH imports,"
    echo "                 install Docker, skip VPNs/Remote Desktop/Backup"
    echo "  --restore      Disaster recovery mode - restore from backup"
    echo "  --help         Show this help message"
    echo ""
    echo "Examples:"
    echo "  sudo ./post-install.sh                # Interactive mode"
    echo "  sudo ./post-install.sh --dry-run      # Preview installations"
    echo "  sudo ./post-install.sh --unattended   # Automated install"
    echo "  sudo ./post-install.sh --restore      # Disaster recovery"
    echo ""
    exit 0
}

RESTORE_MODE=false

for arg in "$@"; do
    case $arg in
        --dry-run)
            DRY_RUN=true
            ;;
        --unattended)
            UNATTENDED=true
            ;;
        --restore)
            RESTORE_MODE=true
            ;;
        --help|-h)
            show_help
            ;;
        *)
            echo "Unknown option: $arg"
            echo "Use --help for usage information"
            exit 1
            ;;
    esac
done

# ============================================================================
# LOGGING SETUP
# ============================================================================

# Create log file and tee all output
exec > >(tee -a "$LOG_FILE") 2>&1
echo ""
echo "=== Post-Install Log Started: $(date) ===" >> "$LOG_FILE"

echo "=== Ubuntu 26.04 Post-Installation Script ==="
echo ""

if [ "$DRY_RUN" = true ]; then
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DRY RUN MODE - No changes will be made"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
fi

if [ "$UNATTENDED" = true ]; then
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "UNATTENDED MODE - Using default options"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
fi

# Check if running as root
if [ "$EUID" -ne 0 ]; then
    echo "Please run as root (use sudo)"
    exit 1
fi

# Get the actual user (not root)
ACTUAL_USER="${SUDO_USER:-$USER}"
ACTUAL_HOME=$(getent passwd "$ACTUAL_USER" | cut -d: -f6)
DOCKER_DIR="$ACTUAL_HOME/docker"

echo "Note: Script will continue even if individual packages fail to install"
echo "Log file: $LOG_FILE"
echo ""

# ============================================================================
# INSTALLATION MODE SELECTOR
# ============================================================================

INSTALL_MODE="normal"

# Disaster recovery function
run_disaster_recovery() {
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DISASTER RECOVERY MODE"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "This will restore your system and Docker apps from a Kopia backup."
    echo ""
    echo "Requirements:"
    echo "  • Backup drive connected (with kopia-repo folder)"
    echo "  • Kopia password (saved in .env or you remember it)"
    echo ""

    # Step 1: Install core utilities
    echo "Step 1: Installing core utilities"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Installing essential packages..."
    apt-get update
    apt-get install -y \
        net-tools \
        openssh-server \
        git \
        curl \
        wget \
        htop \
        ncdu \
        tree \
        zip \
        unzip \
        whiptail \
        2>/dev/null || echo "  ⚠ Some packages may have failed"

    # Install Kopia for backup management
    echo "Installing Kopia..."
    if ! command -v kopia &> /dev/null; then
        curl -s https://kopia.io/signing-key | gpg --dearmor -o /usr/share/keyrings/kopia-keyring.gpg 2>/dev/null
        echo "deb [signed-by=/usr/share/keyrings/kopia-keyring.gpg] https://packages.kopia.io/apt/ stable main" | tee /etc/apt/sources.list.d/kopia.list
        apt-get update
        apt-get install -y kopia 2>/dev/null || echo "  ⚠ Kopia install failed"
    fi

    # Start SSH
    systemctl enable ssh 2>/dev/null || true
    systemctl start ssh 2>/dev/null || true
    echo "✓ Core utilities installed"
    echo ""

    # Step 2: Find/mount backup drive
    echo "Step 2: Locate backup drive"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Available block devices:"
    lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,LABEL 2>/dev/null || lsblk
    echo ""

    # Check if drives are already mounted
    if [ -d "$ACTUAL_HOME/drives" ]; then
        echo "Existing mount points in ~/drives/:"
        ls -la "$ACTUAL_HOME/drives/" 2>/dev/null || echo "  (none)"
        echo ""
    fi

    read -p "Enter path to backup drive or Kopia repo (e.g., /dev/sdb1 or ~/drives/backup1): " BACKUP_PATH

    # If it's a device, mount it
    if [[ "$BACKUP_PATH" == /dev/* ]]; then
        MOUNT_POINT="$ACTUAL_HOME/drives/restore-backup"
        mkdir -p "$MOUNT_POINT" 2>/dev/null
        echo "Mounting $BACKUP_PATH to $MOUNT_POINT..."
        mount "$BACKUP_PATH" "$MOUNT_POINT" || { echo "Failed to mount. Check device path."; return 1; }
        BACKUP_PATH="$MOUNT_POINT"
    fi

    # Expand ~ to home directory
    BACKUP_PATH="${BACKUP_PATH/#\~/$ACTUAL_HOME}"

    # Look for kopia-repo
    KOPIA_REPO=""
    if [ -d "$BACKUP_PATH/kopia-repo" ]; then
        KOPIA_REPO="$BACKUP_PATH/kopia-repo"
    elif [ -d "$BACKUP_PATH/kopia.repository" ] || [ -f "$BACKUP_PATH/kopia.repository.f" ]; then
        KOPIA_REPO="$BACKUP_PATH"
    else
        echo ""
        echo "Looking for Kopia repository..."
        FOUND_REPO=$(find "$BACKUP_PATH" -maxdepth 3 -name "kopia.repository*" -type f 2>/dev/null | head -1)
        if [ -n "$FOUND_REPO" ]; then
            KOPIA_REPO=$(dirname "$FOUND_REPO")
        fi
    fi

    if [ -z "$KOPIA_REPO" ] || [ ! -d "$KOPIA_REPO" ]; then
        echo "❌ Could not find Kopia repository at $BACKUP_PATH"
        echo "   Look for a folder containing 'kopia.repository' files"
        return 1
    fi

    echo "✓ Found Kopia repository at: $KOPIA_REPO"
    echo ""

    # Step 3: Get Kopia password
    echo "Step 3: Kopia password"
    echo "━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    # Try to find password in backed-up .env
    if [ -f "$BACKUP_PATH/kopia/.env" ]; then
        KOPIA_PASS=$(grep "KOPIA_PASSWORD" "$BACKUP_PATH/kopia/.env" 2>/dev/null | cut -d= -f2)
        if [ -n "$KOPIA_PASS" ]; then
            echo "Found password in backup. Use this? (y/n)"
            read -p "> " USE_FOUND_PASS
            [ "$USE_FOUND_PASS" != "y" ] && KOPIA_PASS=""
        fi
    fi

    if [ -z "$KOPIA_PASS" ]; then
        read -s -p "Enter Kopia repository password: " KOPIA_PASS
        echo ""
    fi

    # Step 4: Install Docker if needed
    echo ""
    echo "Step 4: Ensure Docker is installed"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    if ! command -v docker &> /dev/null; then
        echo "Installing Docker..."
        apt-get update
        apt-get install -y ca-certificates curl gnupg
        install -m 0755 -d /etc/apt/keyrings
        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
        chmod a+r /etc/apt/keyrings/docker.gpg
        echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
        apt-get update
        apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        usermod -aG docker "$ACTUAL_USER"
        echo "✓ Docker installed"
    else
        echo "✓ Docker already installed"
    fi
    echo ""

    # Step 5: List snapshots and let user choose
    echo "Step 5: Select snapshot to restore"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Connecting to repository and listing snapshots..."
    echo ""

    # Use docker to run kopia
    SNAPSHOT_LIST=$(docker run --rm \
        -v "$KOPIA_REPO:/repository" \
        -e KOPIA_PASSWORD="$KOPIA_PASS" \
        kopia/kopia:latest \
        snapshot list --all 2>&1)

    if echo "$SNAPSHOT_LIST" | grep -q "invalid password"; then
        echo "❌ Invalid password"
        return 1
    fi

    echo "$SNAPSHOT_LIST"
    echo ""
    read -p "Enter snapshot ID to restore (or 'latest' for most recent): " SNAPSHOT_ID

    if [ "$SNAPSHOT_ID" = "latest" ]; then
        SNAPSHOT_ID=$(echo "$SNAPSHOT_LIST" | grep -oE "^[a-f0-9]+" | tail -1)
        echo "Using latest snapshot: $SNAPSHOT_ID"
    fi

    # Step 6: Restore to temp location
    echo ""
    echo "Step 6: Restoring from backup"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    RESTORE_TEMP="$ACTUAL_HOME/docker-restore-temp"
    mkdir -p "$RESTORE_TEMP"

    echo "Restoring to $RESTORE_TEMP..."
    docker run --rm \
        -v "$KOPIA_REPO:/repository" \
        -v "$RESTORE_TEMP:/restore" \
        -e KOPIA_PASSWORD="$KOPIA_PASS" \
        kopia/kopia:latest \
        restore "$SNAPSHOT_ID" /restore

    if [ $? -ne 0 ]; then
        echo "❌ Restore failed"
        return 1
    fi
    echo "✓ Snapshot restored to temp location"
    echo ""

    # Step 7: Detect and select services to restore
    echo "Step 7: Select services to restore"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    mkdir -p "$DOCKER_DIR"
    ensure_docker_dir_ownership "$DOCKER_DIR"

    # Find all docker-compose.yml files in restored backup
    declare -A SERVICE_DIRS
    SERVICES_FOUND=()
    for compose_file in $(find "$RESTORE_TEMP" -name "docker-compose.yml" -o -name "compose.yml" 2>/dev/null); do
        SERVICE_DIR=$(dirname "$compose_file")
        SERVICE_NAME=$(basename "$SERVICE_DIR")
        SERVICES_FOUND+=("$SERVICE_NAME")
        SERVICE_DIRS["$SERVICE_NAME"]="$SERVICE_DIR"
    done

    if [ ${#SERVICES_FOUND[@]} -eq 0 ]; then
        echo "No Docker services found in backup"
    else
        echo "Found ${#SERVICES_FOUND[@]} services in backup."
        echo ""

        # Check if whiptail is available for nice UI
        if command -v whiptail &> /dev/null; then
            # Build whiptail checklist arguments
            CHECKLIST_ARGS=()
            for svc in "${SERVICES_FOUND[@]}"; do
                CHECKLIST_ARGS+=("$svc" "" "ON")
            done

            # Show checklist - returns selected items
            SELECTED=$(whiptail --title "Select Services to Restore" \
                --checklist "Use SPACE to select/deselect, ENTER to confirm:" \
                20 60 12 \
                "${CHECKLIST_ARGS[@]}" \
                3>&1 1>&2 2>&3)

            # Parse selected services (whiptail returns quoted strings)
            SELECTED_SERVICES=()
            for item in $SELECTED; do
                # Remove quotes
                svc=$(echo "$item" | tr -d '"')
                SELECTED_SERVICES+=("$svc")
            done
        else
            # Fallback: text-based selection
            echo "Select services to restore:"
            echo "  [A] All services"
            echo "  [N] None (skip restore)"
            echo "  [S] Select individually"
            echo ""
            read -p "Choice (A/N/S) [A]: " RESTORE_CHOICE

            case "${RESTORE_CHOICE^^}" in
                N)
                    SELECTED_SERVICES=()
                    ;;
                S)
                    SELECTED_SERVICES=()
                    for svc in "${SERVICES_FOUND[@]}"; do
                        read -p "  Restore $svc? (y/n) [y]: " RESTORE_SVC
                        if [ "$RESTORE_SVC" != "n" ] && [ "$RESTORE_SVC" != "N" ]; then
                            SELECTED_SERVICES+=("$svc")
                        fi
                    done
                    ;;
                *)
                    SELECTED_SERVICES=("${SERVICES_FOUND[@]}")
                    ;;
            esac
        fi

        # Restore selected services
        echo ""
        if [ ${#SELECTED_SERVICES[@]} -eq 0 ]; then
            echo "No services selected for restore."
        else
            echo "Restoring ${#SELECTED_SERVICES[@]} services..."
            echo ""
            for svc in "${SELECTED_SERVICES[@]}"; do
                SERVICE_DIR="${SERVICE_DIRS[$svc]}"
                TARGET_DIR="$DOCKER_DIR/$svc"

                echo "  Restoring $svc..."

                # Copy entire service directory
                cp -r "$SERVICE_DIR" "$TARGET_DIR" 2>/dev/null || true

                # Fix ownership
                chown -R "$ACTUAL_USER:$ACTUAL_USER" "$TARGET_DIR" 2>/dev/null || true

                echo "    ✓ $svc restored to $TARGET_DIR"
            done
        fi
    fi

    # Step 8: Start services
    echo ""
    echo "Step 8: Starting services"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    # Count restored services
    RESTORED_COUNT=0
    for dir in "$DOCKER_DIR"/*/; do
        if [ -f "$dir/docker-compose.yml" ] || [ -f "$dir/compose.yml" ]; then
            ((RESTORED_COUNT++))
        fi
    done

    if [ $RESTORED_COUNT -eq 0 ]; then
        echo "No services to start."
    else
        echo "Start restored services?"
        echo "  [A] All services"
        echo "  [S] Select which to start"
        echo "  [N] None (start manually later)"
        echo ""
        read -p "Choice (A/S/N) [A]: " START_CHOICE

        case "${START_CHOICE^^}" in
            N)
                echo "Services not started. Start manually with:"
                echo "  cd ~/docker/{service} && docker compose up -d"
                ;;
            S)
                for dir in "$DOCKER_DIR"/*/; do
                    if [ -f "$dir/docker-compose.yml" ] || [ -f "$dir/compose.yml" ]; then
                        SERVICE_NAME=$(basename "$dir")
                        read -p "  Start $SERVICE_NAME? (y/n) [y]: " START_SVC
                        if [ "$START_SVC" != "n" ] && [ "$START_SVC" != "N" ]; then
                            echo "    Starting $SERVICE_NAME..."
                            (cd "$dir" && docker compose up -d 2>/dev/null) || echo "    ⚠ Failed to start $SERVICE_NAME"
                        fi
                    fi
                done
                ;;
            *)
                for dir in "$DOCKER_DIR"/*/; do
                    if [ -f "$dir/docker-compose.yml" ] || [ -f "$dir/compose.yml" ]; then
                        SERVICE_NAME=$(basename "$dir")
                        echo "  Starting $SERVICE_NAME..."
                        (cd "$dir" && docker compose up -d 2>/dev/null) || echo "    ⚠ Failed to start $SERVICE_NAME"
                    fi
                done
                ;;
        esac
    fi

    # Cleanup
    echo ""
    read -p "Remove temporary restore files? (y/n): " CLEANUP
    if [ "$CLEANUP" = "y" ]; then
        rm -rf "$RESTORE_TEMP"
        echo "✓ Cleaned up temp files"
    fi

    # Step 9: Reconnect Kopia for future backups
    echo ""
    echo "Step 9: Setting up future backups"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Reconnecting Kopia to backup repository for future backups..."

    if command -v kopia &> /dev/null && [ -n "$KOPIA_REPO" ]; then
        # Reconnect to the repository
        if kopia repository connect filesystem --path="$KOPIA_REPO" --password="$KOPIA_PASSWORD" 2>/dev/null; then
            echo "✓ Kopia reconnected to repository"
            echo "  Repository: $KOPIA_REPO"

            # Verify backup scripts exist
            if [ -f "$DOCKER_DIR/kopia/backup-containers.sh" ]; then
                chmod +x "$DOCKER_DIR/kopia/backup-containers.sh" 2>/dev/null || true
                echo "✓ Backup script ready: $DOCKER_DIR/kopia/backup-containers.sh"
            else
                echo "  ⚠ Backup script not found - run normal install to set up"
            fi
        else
            echo "  ⚠ Could not reconnect to repository"
            echo "  Run manually: kopia repository connect filesystem --path=\"$KOPIA_REPO\""
        fi
    else
        echo "  ⚠ Kopia not available or repository not found"
        echo "  Run the normal install script to set up backups"
    fi
    echo ""

    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DISASTER RECOVERY COMPLETE"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Services restored to: $DOCKER_DIR"
    echo ""
    echo "Backups: $(command -v kopia &>/dev/null && kopia repository status &>/dev/null && echo "✓ Connected" || echo "⚠ Not connected")"
    echo "  Run backups: $DOCKER_DIR/kopia/backup-containers.sh"
    echo ""
    echo "To check running containers:"
    echo "  docker ps"
    echo ""
    echo "To view logs:"
    echo "  docker compose logs -f"
    echo ""
    echo "⚠️  Some services may need manual configuration:"
    echo "  • Frigate: Edit config/config.yml with camera URLs"
    echo "  • Caddy: Update Caddyfile with your domain"
    echo "  • ddclient: Edit config/ddclient.conf with DNS credentials"
    echo ""

    return 0
}

# Migration function - import existing Docker containers
run_migration() {
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "MIGRATION MODE"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "This will migrate existing Docker containers to this script's structure."
    echo "Containers will be copied (not moved) and versions preserved."
    echo ""

    # Step 1: Get source Docker directory
    echo "Step 1: Locate existing containers"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    # Try to auto-detect Docker directories
    DETECTED_DIRS=()

    # Check common locations
    for check_dir in /var/docker /opt/docker "$HOME/docker" /home/*/docker; do
        if [ -d "$check_dir" ] && [ "$(find "$check_dir" -maxdepth 2 -name 'docker-compose.yml' -o -name 'compose.yml' 2>/dev/null | head -1)" ]; then
            DETECTED_DIRS+=("$check_dir")
        fi
    done

    # Check mounted drives: ~/drives/*, /mnt/*, /media/*
    for mount_base in "$HOME/drives" "$HOME_DIR/drives" /mnt /media; do
        if [ -d "$mount_base" ]; then
            for mount_dir in "$mount_base"/*; do
                if [ -d "$mount_dir/docker" ]; then
                    check_dir="$mount_dir/docker"
                    if [ "$(find "$check_dir" -maxdepth 2 -name 'docker-compose.yml' -o -name 'compose.yml' 2>/dev/null | head -1)" ]; then
                        DETECTED_DIRS+=("$check_dir")
                    fi
                fi
            done
        fi
    done

    if [ ${#DETECTED_DIRS[@]} -gt 0 ]; then
        echo "Auto-detected Docker directories:"
        for i in "${!DETECTED_DIRS[@]}"; do
            dir="${DETECTED_DIRS[$i]}"
            count=$(find "$dir" -maxdepth 2 \( -name 'docker-compose.yml' -o -name 'compose.yml' \) 2>/dev/null | wc -l)
            echo "  [$((i+1))] $dir ($count containers)"
        done
        echo ""
        echo "Enter a number to select, or type a custom path:"
    else
        echo "No Docker directories auto-detected."
        echo ""
        echo "Common locations:"
        echo "  • ~/docker"
        echo "  • ~/drives/primary/docker"
        echo "  • /var/docker"
        echo "  • /opt/docker"
        echo "  • /mnt/data/docker"
        echo ""
        echo "Enter the full path to your Docker directory:"
    fi
    echo ""
    read -p "Source directory: " SOURCE_DOCKER_DIR

    # Check if user entered a number (to select from detected list)
    if [[ "$SOURCE_DOCKER_DIR" =~ ^[0-9]+$ ]] && [ ${#DETECTED_DIRS[@]} -gt 0 ]; then
        idx=$((SOURCE_DOCKER_DIR - 1))
        if [ $idx -ge 0 ] && [ $idx -lt ${#DETECTED_DIRS[@]} ]; then
            SOURCE_DOCKER_DIR="${DETECTED_DIRS[$idx]}"
            echo "Selected: $SOURCE_DOCKER_DIR"
        fi
    fi

    # Expand ~ if used
    SOURCE_DOCKER_DIR="${SOURCE_DOCKER_DIR/#\~/$HOME}"

    if [ ! -d "$SOURCE_DOCKER_DIR" ]; then
        echo "❌ Directory not found: $SOURCE_DOCKER_DIR"
        return 1
    fi

    # Step 2: Scan for containers
    echo ""
    echo "Step 2: Scanning for containers"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    CONTAINERS_FOUND=()
    CONTAINER_PATHS=()

    while IFS= read -r compose_file; do
        if [ -n "$compose_file" ]; then
            container_dir=$(dirname "$compose_file")
            container_name=$(basename "$container_dir")
            CONTAINERS_FOUND+=("$container_name")
            CONTAINER_PATHS+=("$container_dir")
        fi
    done < <(find "$SOURCE_DOCKER_DIR" -maxdepth 2 \( -name 'docker-compose.yml' -o -name 'compose.yml' \) 2>/dev/null)

    if [ ${#CONTAINERS_FOUND[@]} -eq 0 ]; then
        echo "❌ No Docker containers found in $SOURCE_DOCKER_DIR"
        return 1
    fi

    echo "Found ${#CONTAINERS_FOUND[@]} container(s):"
    for i in "${!CONTAINERS_FOUND[@]}"; do
        container="${CONTAINERS_FOUND[$i]}"
        path="${CONTAINER_PATHS[$i]}"
        size=$(du -sh "$path" 2>/dev/null | cut -f1)
        echo "  [$((i+1))] $container ($size)"
    done
    echo ""

    # Step 3: Select containers to migrate
    echo "Step 3: Select containers to migrate"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    SELECTED_CONTAINERS=()

    if command -v whiptail &> /dev/null && [ ${#CONTAINERS_FOUND[@]} -gt 3 ]; then
        # Use whiptail for many containers
        CHECKLIST_ARGS=()
        for container in "${CONTAINERS_FOUND[@]}"; do
            CHECKLIST_ARGS+=("$container" "" "ON")
        done

        SELECTED=$(whiptail --title "Select Containers to Migrate" \
            --checklist "Use SPACE to select/deselect, ENTER to confirm:" \
            20 60 12 \
            "${CHECKLIST_ARGS[@]}" \
            3>&1 1>&2 2>&3)

        if [ $? -eq 0 ] && [ -n "$SELECTED" ]; then
            # Parse whiptail output
            for container in $SELECTED; do
                # Remove quotes
                container="${container//\"/}"
                SELECTED_CONTAINERS+=("$container")
            done
        fi
    else
        # Text-based selection
        echo "Select containers to migrate:"
        echo "  [A] All containers"
        echo "  [S] Select individually"
        echo "  [N] None (cancel)"
        echo ""
        read -p "Choice (A/S/N) [A]: " MIGRATE_CHOICE

        case "${MIGRATE_CHOICE^^}" in
            N)
                echo "Migration cancelled."
                return 0
                ;;
            S)
                for container in "${CONTAINERS_FOUND[@]}"; do
                    read -p "  Migrate $container? (y/n) [y]: " MIGRATE_THIS
                    if [ "$MIGRATE_THIS" != "n" ] && [ "$MIGRATE_THIS" != "N" ]; then
                        SELECTED_CONTAINERS+=("$container")
                    fi
                done
                ;;
            *)
                SELECTED_CONTAINERS=("${CONTAINERS_FOUND[@]}")
                ;;
        esac
    fi

    if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
        echo "No containers selected."
        return 0
    fi

    echo ""
    echo "Will migrate ${#SELECTED_CONTAINERS[@]} container(s):"
    for container in "${SELECTED_CONTAINERS[@]}"; do
        echo "  • $container"
    done
    echo ""

    # Step 4: Stop running containers (optional)
    echo "Step 4: Container status"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Should running containers be stopped before migration?"
    echo "  • Stopping ensures clean copy of data/databases"
    echo "  • Not stopping may result in inconsistent state"
    echo ""
    read -p "Stop containers before copying? (y/n) [y]: " STOP_CONTAINERS

    if [ "$STOP_CONTAINERS" != "n" ] && [ "$STOP_CONTAINERS" != "N" ]; then
        echo "Stopping containers..."
        for container in "${SELECTED_CONTAINERS[@]}"; do
            for i in "${!CONTAINERS_FOUND[@]}"; do
                if [ "${CONTAINERS_FOUND[$i]}" = "$container" ]; then
                    container_dir="${CONTAINER_PATHS[$i]}"
                    if [ -f "$container_dir/docker-compose.yml" ] || [ -f "$container_dir/compose.yml" ]; then
                        echo "  Stopping $container..."
                        (cd "$container_dir" && docker compose down 2>/dev/null) || true
                    fi
                fi
            done
        done
        echo ""
    fi

    # Step 5: Decide migration method
    echo "Step 5: Migration method"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    # Check if source is on a different drive (mounted drive, not home on OS)
    SOURCE_ON_EXTERNAL=false
    if [[ "$SOURCE_DOCKER_DIR" == /mnt/* ]] || [[ "$SOURCE_DOCKER_DIR" == /media/* ]] || [[ "$SOURCE_DOCKER_DIR" == */drives/* ]]; then
        SOURCE_ON_EXTERNAL=true
    fi

    MIGRATION_METHOD="copy"
    if [ "$SOURCE_ON_EXTERNAL" = true ]; then
        echo "Source is on a mounted drive: $SOURCE_DOCKER_DIR"
        echo ""
        echo "How would you like to migrate?"
        echo ""
        echo "  [C] Copy - Copy containers to ~/docker on OS drive"
        echo "             Best for: old OS drive mounted temporarily"
        echo ""
        echo "  [S] Symlink - Create ~/docker as symlink to source location"
        echo "                Best for: data drive you'll keep using"
        echo ""
        echo "  [U] Use in-place - Use source location directly, no copy"
        echo "                     Best for: already on your data drive"
        echo ""
        read -p "Method (C/S/U) [C]: " MIGRATION_METHOD

        case "${MIGRATION_METHOD^^}" in
            S)
                MIGRATION_METHOD="symlink"
                ;;
            U)
                MIGRATION_METHOD="use"
                ;;
            *)
                MIGRATION_METHOD="copy"
                ;;
        esac
    else
        echo "Source: $SOURCE_DOCKER_DIR"
        echo "Target: $DOCKER_DIR"
        echo ""
        echo "Will copy containers to $DOCKER_DIR"
    fi

    echo ""

    # Handle different migration methods
    case "$MIGRATION_METHOD" in
        symlink)
            echo "Creating symlink: $DOCKER_DIR → $SOURCE_DOCKER_DIR"
            echo ""

            # Check if target exists
            if [ -e "$DOCKER_DIR" ] || [ -L "$DOCKER_DIR" ]; then
                echo "  ⚠ $DOCKER_DIR already exists"
                read -p "  Remove and create symlink? (y/n) [n]: " REMOVE_EXISTING
                if [ "$REMOVE_EXISTING" = "y" ] || [ "$REMOVE_EXISTING" = "Y" ]; then
                    rm -rf "$DOCKER_DIR"
                else
                    echo "  Cancelled."
                    return 1
                fi
            fi

            ln -s "$SOURCE_DOCKER_DIR" "$DOCKER_DIR"
            chown -h "$ACTUAL_USER:$ACTUAL_USER" "$DOCKER_DIR"

            echo "✓ Symlink created"
            echo "  Containers stay at: $SOURCE_DOCKER_DIR"
            echo "  Accessible via: $DOCKER_DIR → $SOURCE_DOCKER_DIR"
            MIGRATED_COUNT=${#SELECTED_CONTAINERS[@]}
            ;;

        use)
            echo "Using source location directly"
            echo ""
            DOCKER_DIR="$SOURCE_DOCKER_DIR"
            echo "✓ DOCKER_DIR set to: $DOCKER_DIR"
            echo "  No files copied - containers remain in place"
            MIGRATED_COUNT=${#SELECTED_CONTAINERS[@]}
            ;;

        copy|*)
            echo "Copying containers to $DOCKER_DIR"
            echo ""

            # Create target directory
            mkdir -p "$DOCKER_DIR"
            chown "$ACTUAL_USER:$ACTUAL_USER" "$DOCKER_DIR"

            MIGRATED_COUNT=0
            for container in "${SELECTED_CONTAINERS[@]}"; do
                for i in "${!CONTAINERS_FOUND[@]}"; do
                    if [ "${CONTAINERS_FOUND[$i]}" = "$container" ]; then
                        source_dir="${CONTAINER_PATHS[$i]}"
                        target_dir="$DOCKER_DIR/$container"

                        # Check if already exists
                        if [ -d "$target_dir" ]; then
                            echo "  ⚠ $container already exists at $target_dir"
                            read -p "    Overwrite? (y/n) [n]: " OVERWRITE
                            if [ "$OVERWRITE" != "y" ] && [ "$OVERWRITE" != "Y" ]; then
                                echo "    Skipping $container"
                                continue
                            fi
                            rm -rf "$target_dir"
                        fi

                        echo "  Copying $container..."
                        cp -a "$source_dir" "$target_dir"
                        chown -R "$ACTUAL_USER:$ACTUAL_USER" "$target_dir"
                        ((MIGRATED_COUNT++))
                        echo "    ✓ $container copied"
                    fi
                done
            done
            ;;
    esac

    echo ""
    echo "✓ Migrated $MIGRATED_COUNT container(s)"
    echo ""

    # Step 6: Update volume paths
    echo "Step 6: Update volume paths"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Scanning docker-compose files for volume mounts that may need updating..."
    echo ""

    # Find all absolute paths in volume mounts (excluding relative paths like ./ or named volumes)
    PATHS_FOUND=()
    PATHS_FILES=()

    for container in "${SELECTED_CONTAINERS[@]}"; do
        compose_file="$DOCKER_DIR/$container/docker-compose.yml"
        [ -f "$compose_file" ] || compose_file="$DOCKER_DIR/$container/compose.yml"
        [ -f "$compose_file" ] || continue

        # Extract volume mount paths (lines with : that look like /path:/container/path)
        while IFS= read -r line; do
            # Match absolute paths in volume mounts (starting with /)
            if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*(/[^:]+): ]]; then
                host_path="${BASH_REMATCH[1]}"
                # Skip if it's a relative path or standard paths
                if [[ "$host_path" != "./"* ]] && [[ "$host_path" != "/etc/"* ]] && [[ "$host_path" != "/var/run/"* ]]; then
                    # Check if this path doesn't exist on current system
                    if [ ! -e "$host_path" ]; then
                        # Avoid duplicates
                        if [[ ! " ${PATHS_FOUND[*]} " =~ " ${host_path} " ]]; then
                            PATHS_FOUND+=("$host_path")
                            PATHS_FILES+=("$compose_file")
                        fi
                    fi
                fi
            fi
        done < "$compose_file"
    done

    if [ ${#PATHS_FOUND[@]} -eq 0 ]; then
        echo "✓ No volume paths need updating (all paths exist or are relative)"
        echo ""
    else
        echo "Found ${#PATHS_FOUND[@]} volume path(s) that don't exist on this system:"
        echo ""

        # Default new base path
        DEFAULT_NEW_BASE="$HOME_DIR/drives/primary"

        for i in "${!PATHS_FOUND[@]}"; do
            old_path="${PATHS_FOUND[$i]}"
            compose_file="${PATHS_FILES[$i]}"
            container_name=$(basename "$(dirname "$compose_file")")

            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo "Container: $container_name"
            echo "Old path:  $old_path"

            # Suggest a new path based on the old path structure
            # Extract the last part of the path for suggestion
            path_tail=$(basename "$old_path")
            suggested_path="$DEFAULT_NEW_BASE/$path_tail"

            echo "Suggested: $suggested_path"
            echo ""
            echo "Options:"
            echo "  [Enter] Accept suggested path"
            echo "  [S] Skip - keep original path"
            echo "  [path]  Enter custom path"
            echo ""
            read -p "New path: " NEW_PATH_INPUT

            case "$NEW_PATH_INPUT" in
                ""|" ")
                    new_path="$suggested_path"
                    ;;
                [Ss])
                    echo "  Skipping - keeping original path"
                    continue
                    ;;
                *)
                    new_path="${NEW_PATH_INPUT/#\~/$HOME_DIR}"
                    ;;
            esac

            # Update the compose file
            echo "  Updating: $old_path → $new_path"

            # Escape paths for sed (handle slashes)
            old_escaped=$(printf '%s\n' "$old_path" | sed 's/[[\.*^$()+?{|]/\\&/g; s/\//\\\//g')
            new_escaped=$(printf '%s\n' "$new_path" | sed 's/[[\.*^$()+?{|]/\\&/g; s/\//\\\//g')

            sed -i "s|$old_path|$new_path|g" "$compose_file"

            # Create directory if it doesn't exist
            if [ ! -d "$new_path" ]; then
                echo "  Creating directory: $new_path"
                mkdir -p "$new_path" 2>/dev/null || echo "    ⚠ Could not create (may need manual creation)"
                chown -R "$ACTUAL_USER:$ACTUAL_USER" "$new_path" 2>/dev/null || true
            fi

            echo "  ✓ Updated"
            echo ""
        done

        echo "✓ Volume paths updated"
        echo ""
    fi

    # Step 7: Start migrated containers (optional)
    echo "Step 7: Start containers"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    read -p "Start migrated containers? (y/n) [y]: " START_MIGRATED

    if [ "$START_MIGRATED" != "n" ] && [ "$START_MIGRATED" != "N" ]; then
        for container in "${SELECTED_CONTAINERS[@]}"; do
            target_dir="$DOCKER_DIR/$container"
            if [ -d "$target_dir" ]; then
                echo "  Starting $container..."
                (cd "$target_dir" && docker compose up -d 2>/dev/null) || echo "    ⚠ Failed to start $container"
            fi
        done
        echo ""
    fi

    # Step 8: Offer additional services
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "MIGRATION COMPLETE"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Containers migrated to: $DOCKER_DIR"
    echo ""
    echo "Would you like to install additional services not in your migration?"
    echo "This will continue with the normal install process where you can"
    echo "choose which additional apps to install (Immich, Frigate, etc.)"
    echo ""
    read -p "Continue to install additional services? (y/n) [y]: " INSTALL_MORE

    if [ "$INSTALL_MORE" = "n" ] || [ "$INSTALL_MORE" = "N" ]; then
        echo ""
        echo "Migration complete. You can run this script again to install more services."
        echo ""
        echo "To check running containers:"
        echo "  docker ps"
        echo ""
        return 0
    fi

    # Return to continue with normal install
    INSTALL_MODE="post-migration"
    return 0
}

# Check for restore mode flag
if [ "$RESTORE_MODE" = true ]; then
    run_disaster_recovery
    exit $?
fi

# Interactive mode selector (if not unattended)
if [ "$UNATTENDED" != true ]; then
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "INSTALLATION MODE"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "  [N] Normal install - Fresh install or modify existing"
    echo "  [M] Migration - Import existing Docker containers"
    echo "  [R] Disaster recovery - Restore from Kopia backup"
    echo ""
    read -p "Select mode (N/M/R) [N]: " MODE_SELECT

    case "${MODE_SELECT^^}" in
        R)
            run_disaster_recovery
            exit $?
            ;;
        M)
            run_migration
            # If migration returns 0 and INSTALL_MODE is post-migration, continue
            if [ $? -ne 0 ]; then
                exit 1
            fi
            if [ "$INSTALL_MODE" != "post-migration" ]; then
                exit 0
            fi
            echo ""
            echo "Continuing with additional service installation..."
            echo ""
            ;;
        *)
            INSTALL_MODE="normal"
            ;;
    esac
    echo ""
fi

# ============================================================================
# DRIVE SETUP (Runs before everything else)
# ============================================================================

setup_drives() {
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DRIVE SETUP"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "This script uses ~/drives/ for data storage:"
    echo "  ~/drives/primary  - Main data drive (media, photos, etc.)"
    echo "  ~/drives/backup1  - Backup drive(s)"
    echo ""

    # Create base drives directory
    mkdir -p "$ACTUAL_HOME/drives"
    chown "$ACTUAL_USER:$ACTUAL_USER" "$ACTUAL_HOME/drives"

    # Check for existing mounts
    if mount | grep -q "$ACTUAL_HOME/drives/"; then
        echo "Existing mounted drives:"
        df -h | grep "$ACTUAL_HOME/drives" | awk '{print "  " $6 " (" $2 " total, " $4 " free)"}'
        echo ""
        prompt_yn "Configure additional drives? (y/n):" "n" SETUP_DRIVES
    else
        echo "No drives currently mounted in ~/drives/"
        echo ""
        prompt_yn "Set up drives now? (y/n):" "y" SETUP_DRIVES
    fi

    if [ "$SETUP_DRIVES" != "y" ] && [ "$SETUP_DRIVES" != "Y" ]; then
        echo "Skipping drive setup. You can run this script again later."
        return 0
    fi

    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "AVAILABLE BLOCK DEVICES"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL
    echo ""

    # Detect drives without partition tables (new drives)
    echo "Checking for new/unpartitioned drives..."
    NEW_DRIVES=()
    for disk in /dev/sd? /dev/nvme?n?; do
        [ -b "$disk" ] || continue
        # Check if disk has no partitions
        if ! lsblk -n "$disk" | grep -q "part"; then
            # Check if it has no partition table
            if ! blkid "$disk" &>/dev/null; then
                size=$(lsblk -n -d -o SIZE "$disk" 2>/dev/null)
                NEW_DRIVES+=("$disk ($size)")
            fi
        fi
    done

    if [ ${#NEW_DRIVES[@]} -gt 0 ]; then
        echo ""
        echo "⚠️  Found unpartitioned drives (no filesystem):"
        for drive in "${NEW_DRIVES[@]}"; do
            echo "  • $drive"
        done
        echo ""
        prompt_yn "Would you like to partition and format any of these? (y/n):" "n" FORMAT_DRIVES

        if [ "$FORMAT_DRIVES" = "y" ] || [ "$FORMAT_DRIVES" = "Y" ]; then
            for drive_info in "${NEW_DRIVES[@]}"; do
                drive=$(echo "$drive_info" | cut -d' ' -f1)
                echo ""
                prompt_yn "Format $drive_info as ext4? (ALL DATA WILL BE ERASED) (y/n):" "n" FORMAT_THIS

                if [ "$FORMAT_THIS" = "y" ] || [ "$FORMAT_THIS" = "Y" ]; then
                    echo "  Creating partition table on $drive..."
                    parted -s "$drive" mklabel gpt
                    parted -s "$drive" mkpart primary ext4 0% 100%

                    # Wait for partition to appear
                    sleep 2

                    # Format the partition
                    part="${drive}1"
                    [ -b "${drive}p1" ] && part="${drive}p1"  # nvme drives

                    if [ -b "$part" ]; then
                        echo "  Formatting $part as ext4..."
                        mkfs.ext4 -F "$part"
                        echo "  ✓ Formatted $part"
                    else
                        echo "  ⚠ Could not find partition after creation"
                    fi
                fi
            done
            echo ""
            echo "Updated block devices:"
            lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL
            echo ""
        fi
    fi

    # Configure mount points
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "CONFIGURE MOUNT POINTS"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""

    # Primary drive
    echo "PRIMARY DRIVE (for media, photos, docker data):"
    prompt_text "  Mount point name [primary]:" "primary" PRIMARY_NAME
    PRIMARY_NAME="${PRIMARY_NAME:-primary}"
    mkdir -p "$ACTUAL_HOME/drives/$PRIMARY_NAME"

    # Check if already mounted
    if mount | grep -q "$ACTUAL_HOME/drives/$PRIMARY_NAME"; then
        echo "  ✓ Already mounted"
    else
        echo "  Enter device (e.g., /dev/sdb1) or leave blank to skip:"
        read -p "  Device for $PRIMARY_NAME: " PRIMARY_DEV

        if [ -n "$PRIMARY_DEV" ] && [ -b "$PRIMARY_DEV" ]; then
            # Add to fstab
            PRIMARY_UUID=$(blkid -s UUID -o value "$PRIMARY_DEV" 2>/dev/null)
            if [ -n "$PRIMARY_UUID" ]; then
                # Check if not already in fstab
                if ! grep -q "$PRIMARY_UUID" /etc/fstab 2>/dev/null; then
                    cp /etc/fstab /etc/fstab.backup-$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
                    echo "UUID=$PRIMARY_UUID $ACTUAL_HOME/drives/$PRIMARY_NAME auto defaults,nofail 0 2" >> /etc/fstab
                    echo "  ✓ Added to fstab"
                fi
            fi
        fi
    fi

    # Backup drives
    echo ""
    echo "BACKUP DRIVES (optional, for rsync backups):"
    prompt_text "  How many backup drives? [0-4, default: 0]:" "0" NUM_BACKUPS
    NUM_BACKUPS="${NUM_BACKUPS:-0}"

    # Validate number
    case $NUM_BACKUPS in
        0|1|2|3|4) ;;
        *) NUM_BACKUPS=0 ;;
    esac

    declare -a BACKUP_NAMES
    declare -a BACKUP_DEVS
    for i in $(seq 1 $NUM_BACKUPS); do
        echo ""
        prompt_text "  Backup drive $i name [backup$i]:" "backup$i" "BACKUP_NAME"
        BACKUP_NAMES[$i]="${BACKUP_NAME:-backup$i}"
        mkdir -p "$ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}"

        if ! mount | grep -q "$ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}"; then
            read -p "  Device for ${BACKUP_NAMES[$i]}: " "BACKUP_DEV"
            BACKUP_DEVS[$i]="$BACKUP_DEV"

            if [ -n "$BACKUP_DEV" ] && [ -b "$BACKUP_DEV" ]; then
                BACKUP_UUID=$(blkid -s UUID -o value "$BACKUP_DEV" 2>/dev/null)
                if [ -n "$BACKUP_UUID" ]; then
                    if ! grep -q "$BACKUP_UUID" /etc/fstab 2>/dev/null; then
                        echo "UUID=$BACKUP_UUID $ACTUAL_HOME/drives/${BACKUP_NAMES[$i]} auto defaults,nofail 0 2" >> /etc/fstab
                        echo "  ✓ Added ${BACKUP_NAMES[$i]} to fstab"
                    fi
                fi
            fi
        else
            echo "  ✓ Already mounted"
        fi
    done

    # Set permissions
    chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ACTUAL_HOME/drives"

    # Mount all from fstab
    echo ""
    echo "Mounting drives from fstab..."
    mount -a 2>/dev/null || true

    echo ""
    echo "Current mounts in ~/drives/:"
    df -h | grep "$ACTUAL_HOME/drives" | awk '{print "  " $6 " - " $2 " total, " $4 " free"}' || echo "  (none)"
    echo ""

    # Export drive names for later use
    export PRIMARY_DRIVE_NAME="$PRIMARY_NAME"
    export BACKUP_DRIVE_COUNT="$NUM_BACKUPS"
    for i in $(seq 1 $NUM_BACKUPS); do
        export "BACKUP_DRIVE_${i}_NAME=${BACKUP_NAMES[$i]}"
    done

    echo "✓ Drive setup complete"
    echo ""
}

# Run drive setup for normal installs (not migration/recovery)
if [ "$INSTALL_MODE" = "normal" ] && [ "$UNATTENDED" != true ]; then
    setup_drives
fi

# ============================================================================
# SOFTWARE DETECTION FUNCTIONS
# ============================================================================

is_docker_installed() {
    command -v docker &> /dev/null && systemctl is-active --quiet docker 2>/dev/null
}

is_samba_installed() {
    command -v smbd &> /dev/null && systemctl is-active --quiet smbd 2>/dev/null
}

is_netbird_installed() {
    command -v netbird &> /dev/null
}

is_rustdesk_installed() {
    command -v rustdesk &> /dev/null || dpkg -l rustdesk &> /dev/null
}

is_rclone_installed() {
    command -v rclone &> /dev/null
}

is_rsync_installed() {
    command -v rsync &> /dev/null
}

is_ufw_installed() {
    command -v ufw &> /dev/null
}

is_wireguard_installed() {
    command -v wg &> /dev/null
}

is_tailscale_installed() {
    command -v tailscale &> /dev/null
}

is_teamviewer_installed() {
    command -v teamviewer &> /dev/null || dpkg -l teamviewer &> /dev/null 2>&1
}

is_meshcentral_installed() {
    # MeshCentral agent is typically installed as meshagent
    command -v meshagent &> /dev/null || [ -f /usr/local/mesh_services/meshagent/meshagent ]
}

# ============================================================================
# DRY-RUN AND UNATTENDED HELPER FUNCTIONS
# ============================================================================

# Run a command, but skip it in dry-run mode
run_cmd() {
    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would execute: $*"
        return 0
    else
        "$@"
    fi
}

# Ensure Docker directories are owned by the actual user (not root)
# Usage: ensure_docker_dir_ownership /path/to/dir [additional_paths...]
ensure_docker_dir_ownership() {
    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would set ownership of $* to $ACTUAL_USER:$ACTUAL_USER"
        return 0
    fi

    for dir in "$@"; do
        if [ -d "$dir" ]; then
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$dir" 2>/dev/null || true
        fi
    done
}

# Generate a secure random password with alphanumeric characters only (no special chars)
# Usage: generate_password [length]
# Default length: 32
generate_password() {
    local length="${1:-32}"
    openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length"
}

# Validate password for Keycloak (alphanumeric only, minimum length)
# Usage: validate_password "password" [min_length]
# Returns 0 if valid, 1 if invalid
validate_password() {
    local password="$1"
    local min_length="${2:-12}"

    # Check length
    if [ ${#password} -lt "$min_length" ]; then
        echo "  ⚠ Password must be at least $min_length characters long"
        return 1
    fi

    # Check for special characters (not allowed for Keycloak)
    if echo "$password" | grep -q '[^a-zA-Z0-9]'; then
        echo "  ⚠ Password must contain only letters and numbers (no special characters)"
        return 1
    fi

    return 0
}

# Prompt for yes/no, with unattended default
# Usage: prompt_yn "Question?" "default" VARNAME
# default can be "y" or "n"
prompt_yn() {
    local question="$1"
    local default="$2"
    local varname="$3"

    if [ "$UNATTENDED" = true ]; then
        eval "$varname='$default'"
        echo "$question [auto: $default]"
        return
    fi

    read -p "$question " response
    eval "$varname='$response'"
}

# Prompt for text input, with unattended default
# Usage: prompt_text "Question?" "default" VARNAME
prompt_text() {
    local question="$1"
    local default="$2"
    local varname="$3"

    if [ "$UNATTENDED" = true ]; then
        eval "$varname='$default'"
        echo "$question [auto: $default]"
        return
    fi

    read -p "$question " response
    eval "$varname='$response'"
}

# ============================================================================
# CADDY CONFIGURATION HELPER
# ============================================================================
configure_caddy_for_service() {
    local SERVICE_NAME="$1"
    local SERVICE_PORT="$2"
    local DEFAULT_SUBDOMAIN="$3"
    local EXTRA_CONFIG="${4:-}"  # Optional extra Caddy directives

    # Check if Caddy is installed
    if [ ! -d "$DOCKER_DIR/caddy" ]; then
        return 0  # Caddy not installed, skip configuration
    fi

    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "  CADDY REVERSE PROXY CONFIGURATION"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Caddy is installed. You can configure reverse proxy for $SERVICE_NAME."
    echo ""

    local CONFIGURE_CADDY=""
    prompt_yn "Configure Caddy reverse proxy for $SERVICE_NAME? (y/n):" "n" CONFIGURE_CADDY

    if [ "$CONFIGURE_CADDY" != "y" ] && [ "$CONFIGURE_CADDY" != "Y" ]; then
        echo "  Skipping Caddy configuration."
        echo "  Access $SERVICE_NAME at: http://localhost:$SERVICE_PORT"
        return 0
    fi

    # Get domain/subdomain
    echo ""
    echo "Enter the full domain for $SERVICE_NAME:"
    echo "  Examples: $DEFAULT_SUBDOMAIN.example.com, $DEFAULT_SUBDOMAIN.yourdomain.com"
    echo ""
    local SERVICE_DOMAIN=""
    prompt_text "Domain:" "" SERVICE_DOMAIN

    if [ -z "$SERVICE_DOMAIN" ]; then
        echo "  ⚠ No domain provided, skipping Caddy configuration."
        return 0
    fi

    # Backup Caddyfile
    local CADDY_DIR="$DOCKER_DIR/caddy"
    local CADDYFILE="$CADDY_DIR/Caddyfile"
    local BACKUP_FILE="$CADDY_DIR/Caddyfile.backup.$(date +%Y%m%d-%H%M%S)"

    if [ -f "$CADDYFILE" ]; then
        echo "  Backing up Caddyfile to: $(basename $BACKUP_FILE)"
        cp "$CADDYFILE" "$BACKUP_FILE"
    else
        echo "  Creating new Caddyfile"
        touch "$CADDYFILE"
    fi

    # Check if service already configured
    if grep -q "^${SERVICE_DOMAIN}" "$CADDYFILE" 2>/dev/null; then
        echo "  ⚠ $SERVICE_DOMAIN already exists in Caddyfile"
        local OVERWRITE=""
        prompt_yn "Overwrite existing configuration? (y/n):" "n" OVERWRITE
        if [ "$OVERWRITE" != "y" ] && [ "$OVERWRITE" != "Y" ]; then
            echo "  Keeping existing configuration."
            return 0
        fi
        # Remove existing configuration
        sed -i "/^${SERVICE_DOMAIN}/,/^}/d" "$CADDYFILE"
    fi

    # Add service configuration to Caddyfile
    echo "  Adding $SERVICE_NAME configuration to Caddyfile..."

    cat >> "$CADDYFILE" << CADDY_BLOCK

# $SERVICE_NAME
$SERVICE_DOMAIN {
    reverse_proxy localhost:$SERVICE_PORT

    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    # Logging for fail2ban
    log {
        output file /var/log/caddy/${SERVICE_DOMAIN}.log
        format json
    }
$EXTRA_CONFIG
}
CADDY_BLOCK

    echo "  ✓ Configuration added to Caddyfile"

    # Reload Caddy
    echo "  Reloading Caddy configuration..."
    if docker exec caddy caddy reload --config /etc/caddy/Caddyfile 2>/dev/null; then
        echo "  ✓ Caddy reloaded successfully"
    else
        echo "  ⚠ Failed to reload Caddy (will retry after formatting)"
    fi

    # Format Caddyfile
    echo "  Formatting Caddyfile..."
    if docker exec caddy caddy fmt --overwrite /etc/caddy/Caddyfile 2>/dev/null; then
        echo "  ✓ Caddyfile formatted"
    else
        echo "  ⚠ Failed to format Caddyfile"
    fi

    # Final reload after formatting
    echo "  Final reload..."
    if docker exec caddy caddy reload --config /etc/caddy/Caddyfile 2>/dev/null; then
        echo "  ✓ Caddy configuration active"
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "  $SERVICE_NAME is now accessible at:"
        echo "  https://$SERVICE_DOMAIN"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    else
        echo "  ⚠ Failed final reload. Check Caddy logs:"
        echo "     docker logs caddy"
        echo "  You can restore from backup: $BACKUP_FILE"
    fi

    echo ""
}

# ============================================================================
# CHECK IF SERVICE EXISTS AND ASK USER ACTION
# ============================================================================
check_service_exists() {
    local SERVICE_NAME="$1"
    local SERVICE_DIR="$2"
    local RETURN_VAR="$3"  # Variable name to set (true/false)

    if [ -f "$SERVICE_DIR/docker-compose.yml" ]; then
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "  $SERVICE_NAME is already installed at $SERVICE_DIR"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo "Options:"
        echo "  1. Skip (keep existing configuration)"
        echo "  2. Reconfigure (will backup existing config)"
        echo "  3. Restart containers only"
        echo ""
        local ACTION=""
        prompt_text "Choose option [1/2/3]:" "1" ACTION

        case "$ACTION" in
            2)
                echo "  Backing up existing configuration..."
                local BACKUP_NAME=$(echo "$SERVICE_NAME" | tr '[:upper:] ' '[:lower:]-')
                BACKUP_DIR="$DOCKER_DIR/backups/$(date +%Y%m%d-%H%M%S)-$BACKUP_NAME"
                mkdir -p "$BACKUP_DIR"
                cp -r "$SERVICE_DIR" "$BACKUP_DIR/"
                echo "  ✓ Backup saved to: $BACKUP_DIR"
                eval "$RETURN_VAR=true"
                return 0
                ;;
            3)
                echo "  Restarting $SERVICE_NAME containers..."
                cd "$SERVICE_DIR"
                docker compose restart 2>/dev/null && echo "  ✓ $SERVICE_NAME restarted" || echo "  ⚠ Failed to restart"
                eval "$RETURN_VAR=false"
                return 0
                ;;
            *)
                echo "  Skipping $SERVICE_NAME (already configured)"
                eval "$RETURN_VAR=false"
                return 0
                ;;
        esac
    else
        # Service doesn't exist, proceed with installation
        eval "$RETURN_VAR=true"
        return 0
    fi
}

# ============================================================================
# DETECT AVAILABLE DRIVES (RUN ONCE)
# ============================================================================
detect_drives() {
    # Global variables set by this function:
    # - DRIVES_DETECTED: true/false
    # - PRIMARY_DRIVE: name of first drive (e.g., "storage1")
    # - PRIMARY_DRIVE_PATH: full path to first drive
    # - DRIVES_DIR: base drives directory
    # - AVAILABLE_DRIVES_COUNT: number of drives found

    DRIVES_DIR="$ACTUAL_HOME/drives"
    DRIVES_DETECTED=false
    PRIMARY_DRIVE=""
    PRIMARY_DRIVE_PATH=""
    AVAILABLE_DRIVES_COUNT=0

    if [ -d "$DRIVES_DIR" ]; then
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "  DETECTED DRIVES"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""

        for drive in "$DRIVES_DIR"/*; do
            if [ -d "$drive" ]; then
                drive_name=$(basename "$drive")
                drive_size=$(df -h "$drive" 2>/dev/null | awk 'NR==2 {print $2}')
                drive_used=$(df -h "$drive" 2>/dev/null | awk 'NR==2 {print $3}')
                drive_avail=$(df -h "$drive" 2>/dev/null | awk 'NR==2 {print $4}')

                echo "  ✓ $drive_name"
                if [ -n "$drive_size" ]; then
                    echo "    Path: $drive"
                    echo "    Size: $drive_size (Used: $drive_used, Available: $drive_avail)"
                else
                    echo "    Path: $drive"
                fi

                # Set PRIMARY_DRIVE to the first drive found
                if [ -z "$PRIMARY_DRIVE" ]; then
                    PRIMARY_DRIVE="$drive_name"
                    PRIMARY_DRIVE_PATH="$drive"
                fi

                AVAILABLE_DRIVES_COUNT=$((AVAILABLE_DRIVES_COUNT + 1))
            fi
        done

        if [ $AVAILABLE_DRIVES_COUNT -gt 0 ]; then
            DRIVES_DETECTED=true
            echo ""
            echo "  Using '$PRIMARY_DRIVE' as primary drive for default paths"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        fi
    fi

    # If no drives detected, use home directory
    if [ "$DRIVES_DETECTED" = "false" ]; then
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "  No ~/drives directory found"
        echo "  Using $ACTUAL_HOME for default paths"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        PRIMARY_DRIVE_PATH="$ACTUAL_HOME"
    fi
}

# ============================================================================
# SHOW CURRENT INSTALLATION STATUS
# ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "CURRENT SYSTEM STATUS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if is_docker_installed; then
    echo "  ✓ Docker: Installed ($(docker --version 2>/dev/null | cut -d' ' -f3 | tr -d ','))"
else
    echo "  ○ Docker: Not installed"
fi
if is_samba_installed; then
    echo "  ✓ Samba: Installed and running"
else
    echo "  ○ Samba: Not installed"
fi
if is_netbird_installed; then
    echo "  ✓ NetBird: Installed"
else
    echo "  ○ NetBird: Not installed"
fi
if is_rustdesk_installed; then
    echo "  ✓ RustDesk: Installed"
else
    echo "  ○ RustDesk: Not installed"
fi
if is_teamviewer_installed; then
    echo "  ✓ TeamViewer: Installed"
else
    echo "  ○ TeamViewer: Not installed"
fi
if is_meshcentral_installed; then
    echo "  ✓ MeshCentral Agent: Installed"
else
    echo "  ○ MeshCentral Agent: Not installed"
fi
if is_wireguard_installed; then
    echo "  ✓ WireGuard: Installed"
else
    echo "  ○ WireGuard: Not installed"
fi
if is_tailscale_installed; then
    echo "  ✓ Tailscale: Installed"
else
    echo "  ○ Tailscale: Not installed"
fi
if is_rclone_installed; then
    echo "  ✓ rclone: Installed"
else
    echo "  ○ rclone: Not installed"
fi
if is_rsync_installed; then
    echo "  ✓ rsync: Installed"
else
    echo "  ○ rsync: Not installed"
fi
if is_ufw_installed; then
    if ufw status 2>/dev/null | grep -q "Status: active"; then
        echo "  ✓ UFW Firewall: Enabled"
    else
        echo "  ○ UFW Firewall: Installed but disabled"
    fi
else
    echo "  ○ UFW Firewall: Not installed"
fi
echo ""
echo "This script can reinstall/reconfigure any component."
echo ""

# Update package list
echo "Updating package lists..."
run_cmd apt update

# Install basic utilities
echo ""
echo "Installing basic utilities..."
echo "  - net-tools: Network configuration tools (ifconfig, netstat, etc.)"
echo "  - ncdu: Disk usage analyzer with ncurses interface"
echo "  - git: Version control system"
echo "  - curl: Command-line tool for transferring data with URLs"
echo "  - wget: Network downloader"
echo "  - htop: Interactive process viewer"
echo "  - tree: Display directory structure in tree format"
echo "  - zip/unzip: Archive compression utilities"
echo "  - rclone: Rsync for cloud storage and local drives (backup tool)"
echo ""

run_cmd apt install -y \
    net-tools \
    ncdu \
    git \
    curl \
    wget \
    htop \
    tree \
    zip \
    unzip \
    rclone || echo "Warning: Some utilities failed to install, continuing..."

# Install OpenSSH Server
echo ""
echo "Installing OpenSSH Server..."
echo "  - openssh-server: SSH server for remote access"
echo ""

apt install -y openssh-server || echo "Warning: OpenSSH server installation failed, continuing..."

# Start and enable SSH service
systemctl start ssh || echo "Warning: Failed to start SSH"
systemctl enable ssh || echo "Warning: Failed to enable SSH"

# On rerun, skip SSH configuration if already set up
SKIP_SSH_CONFIG=false
if (systemctl is-active ssh &>/dev/null || systemctl is-active sshd &>/dev/null) && \
   { [ -f "$ACTUAL_HOME/.ssh/authorized_keys" ] && [ -s "$ACTUAL_HOME/.ssh/authorized_keys" ]; } || \
   [ -f "$ACTUAL_HOME/.ssh/id_rsa" ]; then
    echo ""
    echo "SSH is already configured on this system."
    prompt_yn "Reconfigure SSH keys and authentication? (y/n):" "n" RECONFIG_SSH
    if [ "$RECONFIG_SSH" != "y" ] && [ "$RECONFIG_SSH" != "Y" ]; then
        SKIP_SSH_CONFIG=true
    fi
fi

if [ "$SKIP_SSH_CONFIG" != true ]; then

# Generate SSH key for this computer
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "SSH KEY GENERATION"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
prompt_yn "Generate a new SSH key for this computer? (y/n):" "n" GENERATE_KEY

if [ "$GENERATE_KEY" = "y" ] || [ "$GENERATE_KEY" = "Y" ]; then
    echo ""
    read -p "Enter a label/comment for the key (e.g., email or hostname) [default: $ACTUAL_USER@$(hostname)]: " KEY_COMMENT
    
    if [ -z "$KEY_COMMENT" ]; then
        KEY_COMMENT="$ACTUAL_USER@$(hostname)"
    fi
    
    # Check if key already exists
    if [ -f "$ACTUAL_HOME/.ssh/id_rsa" ]; then
        echo ""
        echo "⚠️  WARNING: SSH key already exists at $ACTUAL_HOME/.ssh/id_rsa"
        read -p "Overwrite existing key? This cannot be undone! (y/n): " OVERWRITE_KEY
        
        if [ "$OVERWRITE_KEY" != "y" ] && [ "$OVERWRITE_KEY" != "Y" ]; then
            echo "Skipping key generation."
            GENERATE_KEY="n"
        fi
    fi
    
    if [ "$GENERATE_KEY" = "y" ] || [ "$GENERATE_KEY" = "Y" ]; then
        echo ""
        echo "Generating 4096-bit RSA key pair..."
        echo "This may take a moment..."
        
        # Generate key as the actual user, not root
        sudo -u "$ACTUAL_USER" ssh-keygen -t rsa -b 4096 -C "$KEY_COMMENT" -f "$ACTUAL_HOME/.ssh/id_rsa" -N ""
        
        if [ $? -eq 0 ]; then
            echo ""
            echo "✓ SSH key generated successfully!"
            echo ""
            echo "Private key: $ACTUAL_HOME/.ssh/id_rsa (keep this secret!)"
            echo "Public key:  $ACTUAL_HOME/.ssh/id_rsa.pub"
            echo ""
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo "Your PUBLIC key (safe to share):"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            cat "$ACTUAL_HOME/.ssh/id_rsa.pub"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo ""
            echo "To use this key:"
            echo "  • Add to GitHub: Settings → SSH and GPG keys → New SSH key"
            echo "  • Add to servers: Copy above key to remote ~/.ssh/authorized_keys"
            echo "  • View anytime: cat ~/.ssh/id_rsa.pub"
            echo ""
        else
            echo "✗ Failed to generate SSH key"
        fi
    fi
else
    echo "Skipping SSH key generation."
fi

# Import SSH keys from GitHub/Launchpad
echo ""
echo "You can import SSH public keys from GitHub or Launchpad for easier SSH access."
echo ""
prompt_yn "Import SSH keys from GitHub or Launchpad? (y/n):" "n" IMPORT_SSH_KEYS

KEYS_IMPORTED=false
GITHUB_USER=""
LAUNCHPAD_USER=""

if [ "$IMPORT_SSH_KEYS" = "y" ] || [ "$IMPORT_SSH_KEYS" = "Y" ]; then
    prompt_text "GitHub username (or leave blank to skip):" "" GITHUB_USER
    prompt_text "Launchpad username (or leave blank to skip):" "" LAUNCHPAD_USER
fi

# Create .ssh directory if it doesn't exist
mkdir -p "$ACTUAL_HOME/.ssh"
touch "$ACTUAL_HOME/.ssh/authorized_keys"
chmod 700 "$ACTUAL_HOME/.ssh"
chmod 600 "$ACTUAL_HOME/.ssh/authorized_keys"

if [ -n "$GITHUB_USER" ]; then
    echo "Importing SSH keys from GitHub user: $GITHUB_USER"
    if curl -fsSL "https://github.com/$GITHUB_USER.keys" >> "$ACTUAL_HOME/.ssh/authorized_keys" 2>/dev/null; then
        echo "✓ GitHub keys imported successfully"
        KEYS_IMPORTED=true
    else
        echo "✗ Failed to import GitHub keys"
    fi
fi

if [ -n "$LAUNCHPAD_USER" ]; then
    echo "Importing SSH keys from Launchpad user: $LAUNCHPAD_USER"
    if curl -fsSL "https://launchpad.net/~$LAUNCHPAD_USER/+sshkeys" >> "$ACTUAL_HOME/.ssh/authorized_keys" 2>/dev/null; then
        echo "✓ Launchpad keys imported successfully"
        KEYS_IMPORTED=true
    else
        echo "✗ Failed to import Launchpad keys"
    fi
fi

# Fix ownership
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ACTUAL_HOME/.ssh"

# Disable password authentication if keys were imported
if [ "$KEYS_IMPORTED" = true ]; then
    echo ""
    echo "SSH keys imported. Disabling password authentication..."
    
    # Backup sshd_config
    cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
    
    # Disable password authentication
    sed -i 's/^#*PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
    sed -i 's/^#*PasswordAuthentication no/PasswordAuthentication no/' /etc/ssh/sshd_config
    
    # Ensure these settings are also set
    grep -q "^PasswordAuthentication" /etc/ssh/sshd_config || echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
    
    # Restart SSH service to apply changes
    systemctl restart ssh
    
    echo "✓ Password authentication disabled. SSH key authentication required."
    echo "✓ Backup saved to /etc/ssh/sshd_config.backup"
else
    echo ""
    echo "No SSH keys imported. Password authentication remains enabled."

    # Offer fail2ban since password auth is still enabled
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "FAIL2BAN (Recommended with password SSH)"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Since password authentication is enabled, fail2ban is recommended"
    echo "to protect against brute-force SSH attacks."
    echo ""

    prompt_yn "Install and enable fail2ban? (y/n):" "y" INSTALL_FAIL2BAN

    if [ "$INSTALL_FAIL2BAN" = "y" ] || [ "$INSTALL_FAIL2BAN" = "Y" ]; then
        echo ""
        echo "Installing fail2ban..."
        run_cmd apt install -y fail2ban

        if [ "$DRY_RUN" != true ]; then
            # Create local config to protect SSH
            cat > /etc/fail2ban/jail.local << 'FAIL2BAN_CONFIG'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
FAIL2BAN_CONFIG

            run_cmd systemctl enable fail2ban
            run_cmd systemctl restart fail2ban
            echo "✓ fail2ban installed and configured"
            echo "  - 5 failed attempts = 1 hour ban"
            echo "  - View banned IPs: sudo fail2ban-client status sshd"
        fi
    else
        echo "Skipping fail2ban installation."
    fi
fi

INSTALL_FAIL2BAN="${INSTALL_FAIL2BAN:-n}"

fi  # End SKIP_SSH_CONFIG

# Docker Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "DOCKER INSTALLATION"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

if is_docker_installed; then
    echo "Docker is already installed: $(docker --version 2>/dev/null)"
    prompt_yn "Reinstall Docker? (y/n):" "n" INSTALL_DOCKER
else
    prompt_yn "Install Docker? (y/n):" "y" INSTALL_DOCKER
fi

if [ "$INSTALL_DOCKER" = "y" ] || [ "$INSTALL_DOCKER" = "Y" ]; then
    echo ""
    echo "Installing Docker prerequisites..."
    echo "  - ca-certificates: SSL/TLS certificates for secure connections"
    echo "  - gnupg: GNU Privacy Guard for package verification"
    echo "  - lsb-release: Provides Ubuntu version information"
    echo ""

    run_cmd apt install -y \
        ca-certificates \
        gnupg \
        lsb-release || echo "Warning: Some prerequisites failed to install, continuing..."

    echo ""
    echo "Installing Docker..."
    echo "  - Docker Engine: Container runtime platform"
    echo "  - Docker Compose: Multi-container application orchestration"
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would remove old Docker packages"
        echo "[DRY-RUN] Would add Docker GPG key and repository"
        echo "[DRY-RUN] Would install docker-ce, docker-ce-cli, containerd.io, plugins"
        echo "[DRY-RUN] Would start and enable Docker service"
        echo "[DRY-RUN] Would add $SUDO_USER to docker group"
    else
        # Remove old Docker packages if they exist
        apt remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true

        # Add Docker's official GPG key
        install -m 0755 -d /etc/apt/keyrings
        curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
        chmod a+r /etc/apt/keyrings/docker.asc

        # Add Docker repository
        echo \
          "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
          $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
          tee /etc/apt/sources.list.d/docker.list > /dev/null

        # Update package list with Docker repo
        apt update

        # Install Docker Engine, CLI, containerd, and Docker Compose plugin
        apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin || echo "Warning: Docker installation failed, continuing..."

        # Start and enable Docker service
        systemctl start docker || echo "Warning: Failed to start Docker"
        systemctl enable docker || echo "Warning: Failed to enable Docker"

        # Add current user to docker group (if not root)
        if [ -n "$SUDO_USER" ]; then
            usermod -aG docker "$SUDO_USER"
            echo "User $SUDO_USER added to docker group"
        fi

        # Verify Docker installation
        echo ""
        echo "Verifying Docker installation..."
        docker --version || echo "Warning: Docker verification failed"
        docker compose version || echo "Warning: Docker Compose verification failed"
    fi
else
    echo "Skipping Docker installation."
fi

# Samba File Sharing (Optional)
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "SAMBA FILE SHARING (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Samba allows you to share folders over the network to Windows, Mac, and Linux."
echo "The script will share your primary drive at ~/drives/primary"
echo ""

if is_samba_installed; then
    echo "Samba is already installed and running."
    if grep -q "\[Primary\]" /etc/samba/smb.conf 2>/dev/null; then
        echo "  Share 'Primary' is configured at: $ACTUAL_HOME/drives/primary"
    fi
    echo ""
    prompt_yn "Reconfigure Samba? (y/n):" "n" INSTALL_SAMBA
else
    prompt_yn "Install and configure Samba file sharing? (y/n):" "n" INSTALL_SAMBA
fi

if [ "$INSTALL_SAMBA" = "y" ] || [ "$INSTALL_SAMBA" = "Y" ]; then
    echo ""
    echo "Installing Samba file sharing..."
    echo "  - Samba: SMB/CIFS file server for network file sharing"
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would install samba"
        echo "[DRY-RUN] Would configure [Primary] share at $ACTUAL_HOME/drives/primary"
        echo "[DRY-RUN] Would prompt for Samba password"
        echo "[DRY-RUN] Would enable and start smbd/nmbd services"
    else
        run_cmd apt install -y samba || echo "Warning: Samba installation failed, continuing..."

        # Configure Samba share for primary drive
        if command -v smbd &> /dev/null; then
            echo ""
            echo "Configuring Samba share for primary drive..."

            # Backup existing config
            cp /etc/samba/smb.conf /etc/samba/smb.conf.backup-$(date +%Y%m%d-%H%M%S)

            # Add Primary share configuration
            if ! grep -q "\[Primary\]" /etc/samba/smb.conf; then
                cat >> /etc/samba/smb.conf << SAMBA_CONFIG

# Primary drive share - added by post-install script
[Primary]
   comment = Primary Drive
   path = $ACTUAL_HOME/drives/primary
   browseable = yes
   read only = no
   writable = yes
   valid users = $ACTUAL_USER
   create mask = 0775
   directory mask = 0775
SAMBA_CONFIG
                echo "✓ Added [Primary] share to Samba configuration"
            else
                echo "Samba [Primary] share already configured, skipping..."
            fi

            # Add Samba user (skip in unattended mode - user must set password manually)
            if [ "$UNATTENDED" != true ]; then
                echo ""
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo "SAMBA PASSWORD SETUP"
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo ""
                echo "Set a password for Samba file sharing access."
                echo "Tip: Using the same password as your system login is convenient."
                echo ""
                smbpasswd -a "$ACTUAL_USER"
            else
                echo "Skipping Samba password setup (unattended mode)"
                echo "Set password later with: sudo smbpasswd -a $ACTUAL_USER"
            fi

            # Enable and restart Samba services
            systemctl enable smbd nmbd || echo "Warning: Failed to enable Samba services"
            systemctl restart smbd nmbd || echo "Warning: Failed to restart Samba services"

            echo ""
            echo "✓ Samba configured successfully"
            echo "  Share name: Primary"
            echo "  Path: $ACTUAL_HOME/drives/primary"
            echo "  Access: \\\\$(hostname)\\Primary (Windows) or smb://$(hostname)/Primary (Mac/Linux)"
            echo ""
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo "ADDING MORE SAMBA SHARES"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo ""
            echo "To add another share, edit the Samba config file:"
            echo ""
            echo "  sudo nano /etc/samba/smb.conf"
            echo ""
            echo "Add a new section at the end:"
            echo ""
            echo "  [ShareName]"
            echo "     comment = Description of share"
            echo "     path = /path/to/folder"
            echo "     browseable = yes"
            echo "     read only = no"
            echo "     writable = yes"
            echo "     valid users = $ACTUAL_USER"
            echo "     create mask = 0775"
            echo "     directory mask = 0775"
            echo ""
            echo "Save (Ctrl+O, Enter) and exit (Ctrl+X), then restart Samba:"
            echo ""
            echo "  sudo systemctl restart smbd nmbd"
            echo ""
            echo "Verify the share is active:"
            echo ""
            echo "  testparm -s"
            echo ""
        else
            echo "✗ Samba installation failed, skipping configuration"
        fi
    fi
else
    echo "Skipping Samba installation."
fi

# On rerun, skip VPN configuration if already set up
SKIP_VPN_CONFIG=false
if is_netbird_installed || is_wireguard_installed || is_tailscale_installed; then
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "VPN SERVICES"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "VPN services already configured:"
    is_netbird_installed && echo "  ✓ NetBird"
    is_wireguard_installed && echo "  ✓ WireGuard"
    is_tailscale_installed && echo "  ✓ Tailscale"
    echo ""
    prompt_yn "Reconfigure VPN services? (y/n):" "n" RECONFIG_VPN
    if [ "$RECONFIG_VPN" != "y" ] && [ "$RECONFIG_VPN" != "Y" ]; then
        SKIP_VPN_CONFIG=true
    fi
fi

if [ "$SKIP_VPN_CONFIG" != true ]; then

# NetBird Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "NETBIRD MESH VPN (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "NetBird is a secure mesh VPN for connecting devices across networks."
echo ""

if is_netbird_installed; then
    echo "NetBird is already installed."
    netbird status 2>/dev/null || true
    echo ""
    prompt_yn "Reinstall NetBird? (y/n):" "n" INSTALL_NETBIRD
else
    prompt_yn "Install NetBird? (y/n):" "n" INSTALL_NETBIRD
fi

if [ "$INSTALL_NETBIRD" = "y" ] || [ "$INSTALL_NETBIRD" = "Y" ]; then
    echo ""
    echo "Installing NetBird..."
    echo "  - NetBird: Secure mesh VPN for connecting devices"
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would download and run NetBird install script"
        echo "[DRY-RUN] Would ensure openssh-server is installed (required for NetBird SSH)"
        echo "[DRY-RUN] Would configure netbird systemd service with --allow-server-ssh"
    else
        # NetBird v0.60.0+ requires openssh-server for SSH access.
        # It injects /etc/ssh/sshd_config.d/99-netbird.conf and listens on port 22022.
        echo "Ensuring openssh-server is installed (required for NetBird SSH)..."
        apt install -y openssh-server 2>/dev/null || echo "Warning: openssh-server install failed, continuing..."
        systemctl enable ssh 2>/dev/null || true
        systemctl start ssh 2>/dev/null || true

        curl -fsSL https://pkgs.netbird.io/install.sh | sh || echo "Warning: NetBird installation failed, continuing..."

        # Persist --allow-server-ssh so this machine accepts NetBird SSH connections
        # without requiring interactive re-authentication on every connection.
        echo "Configuring NetBird to allow SSH server (persistent across reboots)..."
        mkdir -p /etc/systemd/system/netbird.service.d
        cat > /etc/systemd/system/netbird.service.d/ssh-server.conf << 'NETBIRD_OVERRIDE'
[Service]
ExecStart=
ExecStart=/usr/bin/netbird service run --allow-server-ssh
NETBIRD_OVERRIDE
        systemctl daemon-reload 2>/dev/null || true
        echo "  ✓ NetBird will start with --allow-server-ssh on every boot"

        echo ""
        echo "NetBird installed. Setup instructions:"
        echo "  1. Create account at https://app.netbird.io (or self-host)"
        echo "  2. Run 'netbird up' and authenticate via browser"
        echo ""
        echo "For NetBird SSH functionality (v0.60.0+ method):"
        echo "  • openssh-server is installed and --allow-server-ssh is persisted"
        echo "  • Enable SSH per-peer in the NetBird dashboard (Peers > [peer] > SSH)"
        echo "  • NetBird injects /etc/ssh/sshd_config.d/99-netbird.conf automatically"
        echo "  • Connect from another NetBird peer: ssh user@<netbird-ip>"
        echo "  • Get peer IPs with: netbird status"
        echo "  • SSH will work without re-authenticating each connection"
        echo ""
    fi
else
    echo "Skipping NetBird installation."
fi

# WireGuard Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "WIREGUARD VPN (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "WireGuard is a fast, modern VPN protocol."
echo "  • Lightweight and high-performance"
echo "  • Simple configuration via config files"
echo "  • Built into Linux kernel"
echo ""

if is_wireguard_installed; then
    echo "WireGuard is already installed."
    echo ""
    prompt_yn "Reinstall WireGuard? (y/n):" "n" INSTALL_WIREGUARD
else
    prompt_yn "Install WireGuard? (y/n):" "n" INSTALL_WIREGUARD
fi

if [ "$INSTALL_WIREGUARD" = "y" ] || [ "$INSTALL_WIREGUARD" = "Y" ]; then
    echo ""
    echo "Installing WireGuard..."
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would install wireguard wireguard-tools"
    else
        apt install -y wireguard wireguard-tools || echo "Warning: WireGuard installation failed, continuing..."

        echo ""
        echo "WireGuard installed. Setup instructions:"
        echo ""
        echo "Generate keys:"
        echo "  wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey"
        echo ""
        echo "Create config at /etc/wireguard/wg0.conf:"
        echo "  [Interface]"
        echo "  PrivateKey = <your-private-key>"
        echo "  Address = 10.0.0.1/24"
        echo "  ListenPort = 51820"
        echo ""
        echo "  [Peer]"
        echo "  PublicKey = <peer-public-key>"
        echo "  AllowedIPs = 10.0.0.2/32"
        echo "  Endpoint = peer.example.com:51820"
        echo ""
        echo "Start WireGuard:"
        echo "  sudo wg-quick up wg0"
        echo "  sudo systemctl enable wg-quick@wg0  # Start on boot"
        echo ""
    fi
else
    echo "Skipping WireGuard installation."
fi

# Tailscale Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "TAILSCALE VPN (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Tailscale is a zero-config mesh VPN built on WireGuard."
echo "  • Easy setup - just sign in"
echo "  • Built on WireGuard for performance"
echo "  • Automatic NAT traversal"
echo "  • Built-in SSH (Tailscale SSH)"
echo ""

if is_tailscale_installed; then
    echo "Tailscale is already installed."
    tailscale status 2>/dev/null || true
    echo ""
    prompt_yn "Reinstall Tailscale? (y/n):" "n" INSTALL_TAILSCALE
else
    prompt_yn "Install Tailscale? (y/n):" "n" INSTALL_TAILSCALE
fi

if [ "$INSTALL_TAILSCALE" = "y" ] || [ "$INSTALL_TAILSCALE" = "Y" ]; then
    echo ""
    echo "Installing Tailscale..."
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would add Tailscale apt repository"
        echo "[DRY-RUN] Would install tailscale"
    else
        # Add Tailscale's package signing key and repository
        curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/resolute.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
        curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/resolute.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list

        apt update
        apt install -y tailscale || echo "Warning: Tailscale installation failed, continuing..."

        echo ""
        echo "Tailscale installed. Setup instructions:"
        echo ""
        echo "Connect to Tailscale network:"
        echo "  sudo tailscale up"
        echo ""
        echo "This opens a browser to authenticate. After that:"
        echo "  tailscale status     # View connected devices"
        echo "  tailscale ip         # Show your Tailscale IP"
        echo ""
        echo "Tailscale SSH (optional - enable in admin console):"
        echo "  • Enable 'SSH' in Tailscale admin console for this machine"
        echo "  • Connect with: ssh user@device-name (uses Tailscale identity)"
        echo "  • No SSH keys needed - Tailscale handles authentication"
        echo ""
    fi
else
    echo "Skipping Tailscale installation."
fi

fi  # End SKIP_VPN_CONFIG

# On rerun, skip remote desktop configuration if already set up
SKIP_RD_CONFIG=false
if is_rustdesk_installed || is_teamviewer_installed || is_meshcentral_installed; then
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "REMOTE DESKTOP SERVICES"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Remote desktop services already configured:"
    is_rustdesk_installed && echo "  ✓ RustDesk"
    is_teamviewer_installed && echo "  ✓ TeamViewer"
    is_meshcentral_installed && echo "  ✓ MeshCentral Agent"
    echo ""
    prompt_yn "Reconfigure remote desktop services? (y/n):" "n" RECONFIG_RD
    if [ "$RECONFIG_RD" != "y" ] && [ "$RECONFIG_RD" != "Y" ]; then
        SKIP_RD_CONFIG=true
    fi
fi

if [ "$SKIP_RD_CONFIG" != true ]; then

# RustDesk Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "RUSTDESK REMOTE DESKTOP (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "RustDesk is an open-source remote desktop software."
echo ""

if is_rustdesk_installed; then
    echo "RustDesk is already installed."
    echo ""
    prompt_yn "Reinstall RustDesk? (y/n):" "n" INSTALL_RUSTDESK
else
    prompt_yn "Install RustDesk? (y/n):" "n" INSTALL_RUSTDESK
fi

if [ "$INSTALL_RUSTDESK" = "y" ] || [ "$INSTALL_RUSTDESK" = "Y" ]; then
    echo ""
    echo "Installing RustDesk..."
    echo "  - RustDesk: Open-source remote desktop software"
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would download latest RustDesk from GitHub"
        echo "[DRY-RUN] Would install rustdesk .deb package"
    else
        # Download latest RustDesk .deb package
        RUSTDESK_VERSION=$(curl -s https://api.github.com/repos/rustdesk/rustdesk/releases/latest | grep -oP '"tag_name": "\K(.*)(?=")')
        RUSTDESK_URL="https://github.com/rustdesk/rustdesk/releases/download/${RUSTDESK_VERSION}/rustdesk-${RUSTDESK_VERSION}-x86_64.deb"

        wget -O /tmp/rustdesk.deb "$RUSTDESK_URL" || echo "Warning: RustDesk download failed, continuing..."

        if [ -f /tmp/rustdesk.deb ]; then
            apt install -y /tmp/rustdesk.deb || echo "Warning: RustDesk installation failed, continuing..."
            rm /tmp/rustdesk.deb
            echo "✓ RustDesk installed"
        fi
    fi
else
    echo "Skipping RustDesk installation."
fi

# TeamViewer Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "TEAMVIEWER REMOTE DESKTOP (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "TeamViewer is commercial remote desktop software with a free tier."
echo "  • Cross-platform (Windows, Mac, Linux, mobile)"
echo "  • Easy to use - no port forwarding needed"
echo "  • Requires TeamViewer account for unattended access"
echo ""

if is_teamviewer_installed; then
    echo "TeamViewer is already installed."
    echo ""
    prompt_yn "Reinstall TeamViewer? (y/n):" "n" INSTALL_TEAMVIEWER
else
    prompt_yn "Install TeamViewer? (y/n):" "n" INSTALL_TEAMVIEWER
fi

if [ "$INSTALL_TEAMVIEWER" = "y" ] || [ "$INSTALL_TEAMVIEWER" = "Y" ]; then
    echo ""
    echo "Installing TeamViewer..."
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would download TeamViewer .deb package"
        echo "[DRY-RUN] Would install teamviewer"
    else
        # Download TeamViewer .deb package
        wget -O /tmp/teamviewer.deb "https://download.teamviewer.com/download/linux/teamviewer_amd64.deb" || echo "Warning: TeamViewer download failed, continuing..."

        if [ -f /tmp/teamviewer.deb ]; then
            apt install -y /tmp/teamviewer.deb || echo "Warning: TeamViewer installation failed, continuing..."
            rm /tmp/teamviewer.deb

            echo ""
            echo "TeamViewer installed. Setup instructions:"
            echo ""
            echo "Start TeamViewer:"
            echo "  teamviewer"
            echo ""
            echo "For unattended access:"
            echo "  1. Open TeamViewer"
            echo "  2. Go to Extras → Options → Security"
            echo "  3. Set a personal password for unattended access"
            echo "  4. Note your TeamViewer ID (shown in main window)"
            echo ""
            echo "✓ TeamViewer installed"
        fi
    fi
else
    echo "Skipping TeamViewer installation."
fi

# MeshCentral Installation
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "MESHCENTRAL AGENT (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "MeshCentral is an open-source remote management solution."
echo "  • Self-hosted or use public servers"
echo "  • Web-based remote desktop (no client software needed)"
echo "  • Terminal, file transfer, and remote desktop"
echo "  • Requires a MeshCentral server to connect to"
echo ""

if is_meshcentral_installed; then
    echo "MeshCentral Agent is already installed."
    echo ""
    prompt_yn "Reinstall MeshCentral Agent? (y/n):" "n" INSTALL_MESHCENTRAL
else
    prompt_yn "Install MeshCentral Agent? (y/n):" "n" INSTALL_MESHCENTRAL
fi

if [ "$INSTALL_MESHCENTRAL" = "y" ] || [ "$INSTALL_MESHCENTRAL" = "Y" ]; then
    echo ""
    echo "MeshCentral requires a server URL to connect to."
    echo ""
    echo "If you have a MeshCentral server, the agent install is typically done by:"
    echo "  1. Log into your MeshCentral web interface"
    echo "  2. Go to 'My Devices' → 'Add Agent'"
    echo "  3. Download and run the Linux agent installer"
    echo ""
    echo "Example (replace with your server's URL):"
    echo "  wget -O meshagent https://your-meshcentral-server/meshagents?id=XXXXX"
    echo "  chmod +x meshagent"
    echo "  sudo ./meshagent -install"
    echo ""

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would prompt for MeshCentral server URL"
        echo "[DRY-RUN] Would download and install meshagent"
    else
        prompt_text "Enter MeshCentral agent URL (or leave blank to skip):" "" MESHCENTRAL_URL

        if [ -n "$MESHCENTRAL_URL" ]; then
            wget -O /tmp/meshagent "$MESHCENTRAL_URL" || echo "Warning: MeshCentral agent download failed"

            if [ -f /tmp/meshagent ]; then
                chmod +x /tmp/meshagent
                /tmp/meshagent -install || echo "Warning: MeshCentral agent installation failed"
                rm /tmp/meshagent
                echo ""
                echo "✓ MeshCentral Agent installed"
                echo "  Check your MeshCentral server - this device should appear shortly."
            fi
        else
            echo ""
            echo "No URL provided. Skipping MeshCentral agent installation."
            echo "You can install later by downloading the agent from your MeshCentral server."
        fi
    fi
else
    echo "Skipping MeshCentral installation."
fi

fi  # End SKIP_RD_CONFIG

# ============================================================================
# SELF-HOSTED DOCKER APPLICATIONS (Optional)
# ============================================================================
# These applications run in Docker containers using docker-compose
# Each app is installed to ~/docker/{appname}/

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "SELF-HOSTED DOCKER APPLICATIONS (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Install self-hosted applications using Docker Compose."
echo "Each app will be installed to ~/docker/{appname}/"
echo ""
echo "Note: Docker must be installed for these applications."
echo ""

if ! is_docker_installed && [ "$INSTALL_DOCKER" != "y" ] && [ "$INSTALL_DOCKER" != "Y" ]; then
    echo "Docker is not installed. Skipping self-hosted applications."
    echo "Install Docker first, then rerun this script."
else
    # Create docker apps directory
    DOCKER_DIR="$ACTUAL_HOME/docker"
    if [ "$DRY_RUN" != true ]; then
        mkdir -p "$DOCKER_DIR"
        chown "$ACTUAL_USER:$ACTUAL_USER" "$DOCKER_DIR"
    fi

    # ============================================================================
    # DETECT DRIVES (ONCE FOR ALL SERVICES)
    # ============================================================================
    detect_drives

    # ============================================================================
    # SERVICE SELECTION MENU
    # ============================================================================

    # Use whiptail for service selection if available
    if command -v whiptail &> /dev/null; then
        # Detect existing services
        echo "Detecting existing services..."
        declare -A EXISTING_SERVICES
        [ -d "$DOCKER_DIR/immich" ] && EXISTING_SERVICES[IMMICH]="ON"
        [ -d "$DOCKER_DIR/audiobookshelf" ] && EXISTING_SERVICES[AUDIOBOOKSHELF]="ON"
        [ -d "$DOCKER_DIR/emby" ] && EXISTING_SERVICES[EMBY]="ON"
        [ -d "$DOCKER_DIR/arm" ] && EXISTING_SERVICES[ARM]="ON"
        [ -d "$DOCKER_DIR/filebrowser" ] && EXISTING_SERVICES[FILEBROWSER]="ON"
        [ -d "$DOCKER_DIR/magicmirror" ] && EXISTING_SERVICES[MAGICMIRROR]="ON"
        [ -d "$DOCKER_DIR/actualbudget" ] && EXISTING_SERVICES[ACTUALBUDGET]="ON"
        [ -d "$DOCKER_DIR/keycloak" ] && EXISTING_SERVICES[KEYCLOAK]="ON"
        [ -d "$DOCKER_DIR/authelia" ] && EXISTING_SERVICES[AUTHELIA]="ON"
        [ -d "$DOCKER_DIR/caddy" ] && EXISTING_SERVICES[CADDY]="ON"
        [ -d "$DOCKER_DIR/lms" ] && EXISTING_SERVICES[LYRION]="ON"
        [ -d "$DOCKER_DIR/mealie" ] && EXISTING_SERVICES[MEALIE]="ON"
        [ -d "$DOCKER_DIR/minecraft" ] && EXISTING_SERVICES[MINECRAFT]="ON"
        [ -d "$DOCKER_DIR/jellyfin" ] && EXISTING_SERVICES[JELLYFIN]="ON"
        [ -d "$DOCKER_DIR/frigate" ] && EXISTING_SERVICES[FRIGATE]="ON"
        [ -d "$DOCKER_DIR/ntfy" ] && EXISTING_SERVICES[NTFY]="ON"
        [ -d "$DOCKER_DIR/uptime-kuma" ] && EXISTING_SERVICES[UPTIMEKUMA]="ON"
        [ -d "$DOCKER_DIR/wg-easy" ] && EXISTING_SERVICES[WGEASY]="ON"
        [ -d "$DOCKER_DIR/traccar" ] && EXISTING_SERVICES[TRACCAR]="ON"
        [ -d "$DOCKER_DIR/portainer" ] && EXISTING_SERVICES[PORTAINER]="ON"
        [ -d "$DOCKER_DIR/meshcentral" ] && EXISTING_SERVICES[MESHCENTRAL]="ON"
        [ -d "$DOCKER_DIR/fmd" ] && EXISTING_SERVICES[FINDMYDEVICE]="ON"
        [ -d "$DOCKER_DIR/frigate-notify" ] && EXISTING_SERVICES[FRIGATE_NOTIFY]="ON"
        [ -d "$DOCKER_DIR/watchtower" ] && EXISTING_SERVICES[WATCHTOWER]="ON"
        command -v fail2ban-client &> /dev/null && EXISTING_SERVICES[FAIL2BAN]="ON"

        # Ask user what action to perform
        ACTION=$(whiptail --title "Service Management" --menu \
            "Choose an action:" 15 60 3 \
            "1" "Install new services" \
            "2" "Uninstall existing services" \
            "3" "Cancel and skip" \
            3>&1 1>&2 2>&3)

        case "$ACTION" in
            1)
                # INSTALL MODE
                MENU_MODE="INSTALL"
                # Build checklist - mark existing services as ON
                SELECTED_SERVICES=$(whiptail --title "Select Docker Services to Install" \
                    --checklist "Use SPACE to select, ENTER to confirm. [*] = already installed" 25 78 17 \
                    "IMMICH" "Photo & video backup (like Google Photos)" ${EXISTING_SERVICES[IMMICH]:-OFF} \
                    "AUDIOBOOKSHELF" "Audiobook & podcast server" ${EXISTING_SERVICES[AUDIOBOOKSHELF]:-OFF} \
                    "EMBY" "Media server for movies, TV, music" ${EXISTING_SERVICES[EMBY]:-OFF} \
                    "ARM" "Automatic Ripping Machine for DVDs/Blu-rays" ${EXISTING_SERVICES[ARM]:-OFF} \
                    "FILEBROWSER" "Web-based file manager" ${EXISTING_SERVICES[FILEBROWSER]:-OFF} \
                    "MAGICMIRROR" "Smart mirror / dashboard display" ${EXISTING_SERVICES[MAGICMIRROR]:-OFF} \
                    "ACTUALBUDGET" "Personal finance management with bank sync" ${EXISTING_SERVICES[ACTUALBUDGET]:-OFF} \
                    "KEYCLOAK" "Identity & Access Management (SSO)" ${EXISTING_SERVICES[KEYCLOAK]:-OFF} \
                    "AUTHELIA" "SSO + 2FA auth portal for Caddy" ${EXISTING_SERVICES[AUTHELIA]:-OFF} \
                    "CADDY" "Reverse proxy with automatic HTTPS" ${EXISTING_SERVICES[CADDY]:-OFF} \
                    "FAIL2BAN" "Intrusion prevention system" ${EXISTING_SERVICES[FAIL2BAN]:-OFF} \
                    "LYRION" "Music streaming server (LMS)" ${EXISTING_SERVICES[LYRION]:-OFF} \
                    "MEALIE" "Recipe manager & meal planner" ${EXISTING_SERVICES[MEALIE]:-OFF} \
                    "MINECRAFT" "Minecraft game server" ${EXISTING_SERVICES[MINECRAFT]:-OFF} \
                    "JELLYFIN" "Free media server (Emby alternative)" ${EXISTING_SERVICES[JELLYFIN]:-OFF} \
                    "FRIGATE" "AI-powered NVR for security cameras" ${EXISTING_SERVICES[FRIGATE]:-OFF} \
                    "NTFY" "Push notifications server" ${EXISTING_SERVICES[NTFY]:-OFF} \
                    "UPTIMEKUMA" "Service monitoring dashboard" ${EXISTING_SERVICES[UPTIMEKUMA]:-OFF} \
                    "WGEASY" "WireGuard VPN with web UI" ${EXISTING_SERVICES[WGEASY]:-OFF} \
                    "TRACCAR" "GPS tracking server" ${EXISTING_SERVICES[TRACCAR]:-OFF} \
                    "PORTAINER" "Docker management web UI" ${EXISTING_SERVICES[PORTAINER]:-OFF} \
                    "MESHCENTRAL" "Remote management server" ${EXISTING_SERVICES[MESHCENTRAL]:-OFF} \
                    "FINDMYDEVICE" "Device tracking (like Find My)" ${EXISTING_SERVICES[FINDMYDEVICE]:-OFF} \
                    "FRIGATE_NOTIFY" "Push notifications for Frigate" ${EXISTING_SERVICES[FRIGATE_NOTIFY]:-OFF} \
                    "WATCHTOWER" "Automatic container updates" ${EXISTING_SERVICES[WATCHTOWER]:-OFF} \
                    3>&1 1>&2 2>&3)
                ;;
            2)
                # UNINSTALL MODE
                MENU_MODE="UNINSTALL"
                # Only show services that exist
                UNINSTALL_OPTIONS=""
                [ -n "${EXISTING_SERVICES[IMMICH]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS IMMICH \"Photo & video backup\" ON"
                [ -n "${EXISTING_SERVICES[AUDIOBOOKSHELF]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS AUDIOBOOKSHELF \"Audiobook server\" ON"
                [ -n "${EXISTING_SERVICES[EMBY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS EMBY \"Media server\" ON"
                [ -n "${EXISTING_SERVICES[ARM]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ARM \"Automatic Ripping Machine\" ON"
                [ -n "${EXISTING_SERVICES[FILEBROWSER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FILEBROWSER \"Web file manager\" ON"
                [ -n "${EXISTING_SERVICES[MAGICMIRROR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MAGICMIRROR \"Smart mirror\" ON"
                [ -n "${EXISTING_SERVICES[ACTUALBUDGET]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ACTUALBUDGET \"Personal finance\" ON"
                [ -n "${EXISTING_SERVICES[KEYCLOAK]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS KEYCLOAK \"Identity management\" ON"
                [ -n "${EXISTING_SERVICES[AUTHELIA]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS AUTHELIA \"SSO + 2FA auth portal\" ON"
                [ -n "${EXISTING_SERVICES[CADDY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS CADDY \"Reverse proxy\" ON"
                [ -n "${EXISTING_SERVICES[FAIL2BAN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FAIL2BAN \"Intrusion prevention\" ON"
                [ -n "${EXISTING_SERVICES[LYRION]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS LYRION \"Music server\" ON"
                [ -n "${EXISTING_SERVICES[MEALIE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MEALIE \"Recipe manager\" ON"
                [ -n "${EXISTING_SERVICES[MINECRAFT]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MINECRAFT \"Game server\" ON"
                [ -n "${EXISTING_SERVICES[JELLYFIN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS JELLYFIN \"Media server\" ON"
                [ -n "${EXISTING_SERVICES[FRIGATE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FRIGATE \"NVR cameras\" ON"
                [ -n "${EXISTING_SERVICES[NTFY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS NTFY \"Push notifications\" ON"
                [ -n "${EXISTING_SERVICES[UPTIMEKUMA]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS UPTIMEKUMA \"Service monitoring\" ON"
                [ -n "${EXISTING_SERVICES[WGEASY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS WGEASY \"WireGuard VPN\" ON"
                [ -n "${EXISTING_SERVICES[TRACCAR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS TRACCAR \"GPS tracking\" ON"
                [ -n "${EXISTING_SERVICES[PORTAINER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS PORTAINER \"Docker management\" ON"
                [ -n "${EXISTING_SERVICES[MESHCENTRAL]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MESHCENTRAL \"Remote management\" ON"
                [ -n "${EXISTING_SERVICES[FINDMYDEVICE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FINDMYDEVICE \"Device tracking\" ON"
                [ -n "${EXISTING_SERVICES[FRIGATE_NOTIFY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FRIGATE_NOTIFY \"Frigate notifications\" ON"
                [ -n "${EXISTING_SERVICES[WATCHTOWER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS WATCHTOWER \"Container updates\" ON"

                if [ -z "$UNINSTALL_OPTIONS" ]; then
                    whiptail --title "No Services Found" --msgbox "No services detected to uninstall." 8 50
                    SELECTED_SERVICES=""
                else
                    SELECTED_SERVICES=$(eval "whiptail --title 'Select Services to Uninstall' \
                        --checklist 'WARNING: Data will be backed up but services will be removed' 25 78 17 \
                        $UNINSTALL_OPTIONS \
                        3>&1 1>&2 2>&3")
                fi
                ;;
            3|*)
                # Cancel
                SELECTED_SERVICES=""
                MENU_MODE="CANCEL"
                ;;
        esac

        # Check if user cancelled
        if [ $? -ne 0 ]; then
            echo "Service selection cancelled. Skipping Docker applications."
            SELECTED_SERVICES=""
        fi

        # Parse selections (whiptail returns quoted strings)
        # Only set to "n" if not already set (preserve any earlier choices)
        : ${INSTALL_IMMICH:="n"}
        : ${INSTALL_AUDIOBOOKSHELF:="n"}
        : ${INSTALL_EMBY:="n"}
        : ${INSTALL_ARM:="n"}
        : ${INSTALL_FILEBROWSER:="n"}
        : ${INSTALL_MAGICMIRROR:="n"}
        : ${INSTALL_ACTUALBUDGET:="n"}
        : ${INSTALL_KEYCLOAK:="n"}
        : ${INSTALL_AUTHELIA:="n"}
        : ${INSTALL_CADDY:="n"}
        : ${INSTALL_FAIL2BAN:="n"}
        : ${INSTALL_LMS:="n"}
        : ${INSTALL_MEALIE:="n"}
        : ${INSTALL_MINECRAFT:="n"}
        : ${INSTALL_JELLYFIN:="n"}
        : ${INSTALL_FRIGATE:="n"}
        : ${INSTALL_NTFY:="n"}
        : ${INSTALL_UPTIMEKUMA:="n"}
        : ${INSTALL_WGEASY:="n"}
        : ${INSTALL_TRACCAR:="n"}
        : ${INSTALL_PORTAINER:="n"}
        : ${INSTALL_MESHCENTRAL_SERVER:="n"}
        : ${INSTALL_FMD:="n"}
        : ${INSTALL_FRIGATE_NOTIFY:="n"}
        : ${INSTALL_WATCHTOWER:="n"}

        # Set installation flags based on selections
        if echo "$SELECTED_SERVICES" | grep -q "IMMICH"; then INSTALL_IMMICH="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "AUDIOBOOKSHELF"; then INSTALL_AUDIOBOOKSHELF="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "EMBY"; then INSTALL_EMBY="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "ARM"; then INSTALL_ARM="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "FILEBROWSER"; then INSTALL_FILEBROWSER="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "MAGICMIRROR"; then INSTALL_MAGICMIRROR="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "ACTUALBUDGET"; then INSTALL_ACTUALBUDGET="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "KEYCLOAK"; then INSTALL_KEYCLOAK="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "AUTHELIA"; then INSTALL_AUTHELIA="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "CADDY"; then INSTALL_CADDY="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "FAIL2BAN"; then INSTALL_FAIL2BAN="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "LYRION"; then INSTALL_LMS="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "MEALIE"; then INSTALL_MEALIE="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "MINECRAFT"; then INSTALL_MINECRAFT="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "JELLYFIN"; then INSTALL_JELLYFIN="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "FRIGATE\""; then INSTALL_FRIGATE="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "NTFY"; then INSTALL_NTFY="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "UPTIMEKUMA"; then INSTALL_UPTIMEKUMA="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "WGEASY"; then INSTALL_WGEASY="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "TRACCAR"; then INSTALL_TRACCAR="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "PORTAINER"; then INSTALL_PORTAINER="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "MESHCENTRAL"; then INSTALL_MESHCENTRAL_SERVER="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "FINDMYDEVICE"; then INSTALL_FMD="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "FRIGATE_NOTIFY"; then INSTALL_FRIGATE_NOTIFY="y"; fi
        if echo "$SELECTED_SERVICES" | grep -q "WATCHTOWER"; then INSTALL_WATCHTOWER="y"; fi

        if [ "$MENU_MODE" = "UNINSTALL" ]; then
            echo ""
            echo "Services selected for uninstallation:"
            echo "$SELECTED_SERVICES" | tr '"' '\n' | grep -v '^$' | sed 's/^/  - /'
            echo ""

            # Process uninstalls
            if [ -n "$SELECTED_SERVICES" ]; then
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo "  UNINSTALLING SERVICES"
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo ""

                # Define uninstall function
                uninstall_service() {
                    local SERVICE_NAME=$1
                    local SERVICE_DIR=$2
                    local CONTAINER_NAME=$3

                    echo "Uninstalling $SERVICE_NAME..."

                    if [ -d "$SERVICE_DIR" ]; then
                        cd "$SERVICE_DIR" 2>/dev/null || return

                        # Stop and remove containers
                        if [ -f "docker-compose.yml" ]; then
                            echo "  Stopping containers..."
                            docker compose down 2>/dev/null || docker-compose down 2>/dev/null || true
                        fi

                        # Backup data
                        BACKUP_DIR="$DOCKER_DIR/backups/$(date +%Y%m%d-%H%M%S)-$SERVICE_NAME"
                        mkdir -p "$BACKUP_DIR"
                        echo "  Backing up to $BACKUP_DIR..."
                        cp -r "$SERVICE_DIR" "$BACKUP_DIR/" 2>/dev/null || true

                        # Remove service directory
                        echo "  Removing $SERVICE_DIR..."
                        rm -rf "$SERVICE_DIR"

                        echo "  ✓ $SERVICE_NAME uninstalled (backup: $BACKUP_DIR)"
                    else
                        echo "  ⚠ $SERVICE_NAME directory not found, skipping"
                    fi
                    echo ""
                }

                # Uninstall selected services
                if echo "$SELECTED_SERVICES" | grep -q "IMMICH"; then uninstall_service "Immich" "$DOCKER_DIR/immich" "immich"; fi
                if echo "$SELECTED_SERVICES" | grep -q "AUDIOBOOKSHELF"; then uninstall_service "AudioBookshelf" "$DOCKER_DIR/audiobookshelf" "audiobookshelf"; fi
                if echo "$SELECTED_SERVICES" | grep -q "EMBY"; then uninstall_service "Emby" "$DOCKER_DIR/emby" "emby"; fi
                if echo "$SELECTED_SERVICES" | grep -q "ARM"; then uninstall_service "ARM" "$DOCKER_DIR/arm" "arm"; fi
                if echo "$SELECTED_SERVICES" | grep -q "FILEBROWSER"; then uninstall_service "FileBrowser" "$DOCKER_DIR/filebrowser" "filebrowser"; fi
                if echo "$SELECTED_SERVICES" | grep -q "MAGICMIRROR"; then uninstall_service "MagicMirror" "$DOCKER_DIR/magicmirror" "magicmirror"; fi
                if echo "$SELECTED_SERVICES" | grep -q "ACTUALBUDGET"; then uninstall_service "ActualBudget" "$DOCKER_DIR/actualbudget" "actualbudget"; fi
                if echo "$SELECTED_SERVICES" | grep -q "KEYCLOAK"; then uninstall_service "Keycloak" "$DOCKER_DIR/keycloak" "keycloak"; fi
                if echo "$SELECTED_SERVICES" | grep -q "AUTHELIA"; then uninstall_service "Authelia" "$DOCKER_DIR/authelia" "authelia"; fi
                if echo "$SELECTED_SERVICES" | grep -q "CADDY"; then uninstall_service "Caddy" "$DOCKER_DIR/caddy" "caddy"; fi
                if echo "$SELECTED_SERVICES" | grep -q "LYRION"; then uninstall_service "Lyrion" "$DOCKER_DIR/lms" "lms"; fi
                if echo "$SELECTED_SERVICES" | grep -q "MEALIE"; then uninstall_service "Mealie" "$DOCKER_DIR/mealie" "mealie"; fi
                if echo "$SELECTED_SERVICES" | grep -q "MINECRAFT"; then uninstall_service "Minecraft" "$DOCKER_DIR/minecraft" "minecraft"; fi
                if echo "$SELECTED_SERVICES" | grep -q "JELLYFIN"; then uninstall_service "Jellyfin" "$DOCKER_DIR/jellyfin" "jellyfin"; fi
                if echo "$SELECTED_SERVICES" | grep -q "FRIGATE\""; then uninstall_service "Frigate" "$DOCKER_DIR/frigate" "frigate"; fi
                if echo "$SELECTED_SERVICES" | grep -q "NTFY"; then uninstall_service "ntfy" "$DOCKER_DIR/ntfy" "ntfy"; fi
                if echo "$SELECTED_SERVICES" | grep -q "UPTIMEKUMA"; then uninstall_service "Uptime Kuma" "$DOCKER_DIR/uptime-kuma" "uptime-kuma"; fi
                if echo "$SELECTED_SERVICES" | grep -q "WGEASY"; then uninstall_service "wg-easy" "$DOCKER_DIR/wg-easy" "wg-easy"; fi
                if echo "$SELECTED_SERVICES" | grep -q "TRACCAR"; then uninstall_service "Traccar" "$DOCKER_DIR/traccar" "traccar"; fi
                if echo "$SELECTED_SERVICES" | grep -q "PORTAINER"; then uninstall_service "Portainer" "$DOCKER_DIR/portainer" "portainer"; fi
                if echo "$SELECTED_SERVICES" | grep -q "MESHCENTRAL"; then uninstall_service "MeshCentral" "$DOCKER_DIR/meshcentral" "meshcentral"; fi
                if echo "$SELECTED_SERVICES" | grep -q "FINDMYDEVICE"; then uninstall_service "FindMyDevice" "$DOCKER_DIR/fmd" "fmd"; fi
                if echo "$SELECTED_SERVICES" | grep -q "FRIGATE_NOTIFY"; then uninstall_service "Frigate-Notify" "$DOCKER_DIR/frigate-notify" "frigate-notify"; fi
                if echo "$SELECTED_SERVICES" | grep -q "WATCHTOWER"; then uninstall_service "Watchtower" "$DOCKER_DIR/watchtower" "watchtower"; fi

                # Special handling for fail2ban (system package)
                if echo "$SELECTED_SERVICES" | grep -q "FAIL2BAN"; then
                    echo "Uninstalling fail2ban..."
                    echo "  Stopping fail2ban service..."
                    systemctl stop fail2ban 2>/dev/null || true
                    systemctl disable fail2ban 2>/dev/null || true
                    echo "  Removing fail2ban package..."
                    apt-get remove --purge -y fail2ban 2>/dev/null || true
                    echo "  ✓ fail2ban uninstalled"
                    echo ""
                fi

                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo "  Uninstallation complete!"
                echo "  Backups saved to: $DOCKER_DIR/backups/"
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo ""
            fi

            # After uninstall, exit the Docker apps section
            WHIPTAIL_USED=true

        elif [ "$MENU_MODE" = "INSTALL" ]; then
            echo ""
            echo "Selected services:"
            echo "$SELECTED_SERVICES" | tr '"' '\n' | grep -v '^$' | sed 's/^/  - /'
            echo ""

            # Mark that whiptail was used - skip all individual prompts
            WHIPTAIL_USED=true
        else
            # Cancel or other mode
            WHIPTAIL_USED=false
        fi
    else
        # Whiptail not available - will use individual prompts
        WHIPTAIL_USED=false
        MENU_MODE="INSTALL"
    fi

    # Skip installation section if we just did uninstalls
    if [ "$MENU_MODE" = "UNINSTALL" ]; then
        echo "Skipping installation section (uninstall mode was selected)."
        # Jump to end of Docker section by setting a flag
        SKIP_DOCKER_INSTALLS=true
    else
        SKIP_DOCKER_INSTALLS=false
    fi

    # ============================================================================
    # DOCKER SERVICE INSTALLATIONS
    # ============================================================================

    if [ "$SKIP_DOCKER_INSTALLS" != true ]; then
        # ---- IMMICH ----
        if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_IMMICH" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ IMMICH - Self-hosted photo & video backup                       │"
        echo "│ Like Google Photos but private. Mobile app auto-uploads.        │"
        echo "│ Port: 2283                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Immich? (y/n):" "n" INSTALL_IMMICH
    fi

    if [ "$INSTALL_IMMICH" = "y" ] || [ "$INSTALL_IMMICH" = "Y" ]; then
        IMMICH_DIR="$DOCKER_DIR/immich"

        # Check if already installed and ask what to do
        check_service_exists "Immich" "$IMMICH_DIR" IMMICH_RECONFIGURE

        if [ "$IMMICH_RECONFIGURE" = "true" ]; then
            echo "Installing Immich..."

        # ── Photo library setup ─────────────────────────────────────
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ PHOTO LIBRARY SETUP                                            │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        echo ""

        DEFAULT_PHOTOS_DIR="$PRIMARY_DRIVE_PATH/photos"
        IMMICH_STRATEGY=""
        EXTERNAL_LIBRARY=""
        EXISTING_PHOTOS_SOURCE=""

        prompt_yn "  Do you have existing photos to include? (y/n):" "n" HAS_EXISTING_PHOTOS

        if [ "$HAS_EXISTING_PHOTOS" = "y" ] || [ "$HAS_EXISTING_PHOTOS" = "Y" ]; then
            echo ""
            echo "  How should Immich handle your existing photos?"
            echo ""
            echo "    [1] Import everything into Immich (recommended)"
            echo "        Immich manages all photos in one unified library."
            echo "        Dates preserved via EXIF metadata. Organized by date."
            echo "        Your original folder names are NOT kept on disk"
            echo "        (use Immich albums to organize instead)."
            echo ""
            echo "    [2] Keep existing photos in place (read-only external library)"
            echo "        Immich indexes your existing photos without moving them."
            echo "        New uploads go to a separate folder."
            echo "        Your folder structure stays intact."
            echo ""

            if [ "$UNATTENDED" = true ]; then
                IMMICH_STRATEGY="1"
                echo "  Strategy: [auto: 1]"
            else
                read -p "  Choose [1/2]: " IMMICH_STRATEGY
                IMMICH_STRATEGY="${IMMICH_STRATEGY:-1}"
            fi

            if [ "$IMMICH_STRATEGY" = "2" ]; then
                # Strategy 2: external library - ask where existing photos are
                echo ""
                echo "  Where are your existing photos?"
                echo "  Default: $DEFAULT_PHOTOS_DIR"
                prompt_text "  Existing photos path:" "$DEFAULT_PHOTOS_DIR" EXISTING_PHOTOS_SOURCE 2>/dev/null || EXISTING_PHOTOS_SOURCE="$DEFAULT_PHOTOS_DIR"
                EXISTING_PHOTOS_SOURCE="${EXISTING_PHOTOS_SOURCE/#\~/$ACTUAL_HOME}"
                EXISTING_PHOTOS_SOURCE="${EXISTING_PHOTOS_SOURCE%/}"

                UPLOAD_LOCATION="$PRIMARY_DRIVE_PATH/immich-uploads"
                EXTERNAL_LIBRARY="$EXISTING_PHOTOS_SOURCE"

                echo ""
                echo "  Setup:"
                echo "    Existing photos: $EXISTING_PHOTOS_SOURCE  (read-only)"
                echo "    New uploads:     $UPLOAD_LOCATION"
            else
                # Strategy 1 with existing photos: ask for library path, will import into it
                echo ""
                echo "  Where should Immich store your photo library?"
                echo "  (Existing photos will be imported here)"
                echo "  Default: $DEFAULT_PHOTOS_DIR"
                prompt_text "  Photo library path:" "$DEFAULT_PHOTOS_DIR" PHOTOS_DIR 2>/dev/null || PHOTOS_DIR="$DEFAULT_PHOTOS_DIR"
                PHOTOS_DIR="${PHOTOS_DIR/#\~/$ACTUAL_HOME}"
                PHOTOS_DIR="${PHOTOS_DIR%/}"

                echo ""
                echo "  Where are your existing photos now?"
                echo "  Default: $PHOTOS_DIR"
                prompt_text "  Existing photos path:" "$PHOTOS_DIR" EXISTING_PHOTOS_SOURCE 2>/dev/null || EXISTING_PHOTOS_SOURCE="$PHOTOS_DIR"
                EXISTING_PHOTOS_SOURCE="${EXISTING_PHOTOS_SOURCE/#\~/$ACTUAL_HOME}"
                EXISTING_PHOTOS_SOURCE="${EXISTING_PHOTOS_SOURCE%/}"

                UPLOAD_LOCATION="$PHOTOS_DIR"
                EXTERNAL_LIBRARY=""

                echo ""
                echo "  All photos (existing + new) will live in:"
                echo "    $PHOTOS_DIR"
                echo "    └── 2026/01/filename.jpg            organized by date"
            fi
        else
            # No existing photos: simplest path - just ask where to store
            IMMICH_STRATEGY="1"
            echo ""
            echo "  Where should Immich store your photo library?"
            echo "  Default: $DEFAULT_PHOTOS_DIR"
            prompt_text "  Photo library path:" "$DEFAULT_PHOTOS_DIR" PHOTOS_DIR 2>/dev/null || PHOTOS_DIR="$DEFAULT_PHOTOS_DIR"
            PHOTOS_DIR="${PHOTOS_DIR/#\~/$ACTUAL_HOME}"
            PHOTOS_DIR="${PHOTOS_DIR%/}"

            UPLOAD_LOCATION="$PHOTOS_DIR"
            EXTERNAL_LIBRARY=""

            echo ""
            echo "  All photos will be stored in:"
            echo "    $PHOTOS_DIR"
            echo "    └── 2026/01/filename.jpg            organized by date"
        fi
        echo ""

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $IMMICH_DIR"
            echo "[DRY-RUN] Would store uploads at $UPLOAD_LOCATION"
            [ -n "$EXTERNAL_LIBRARY" ] && echo "[DRY-RUN] Would mount external library at $EXTERNAL_LIBRARY"
            echo "[DRY-RUN] Would create docker-compose.yml and .env"
        else
            mkdir -p "$IMMICH_DIR" 2>/dev/null || true
            mkdir -p "$UPLOAD_LOCATION" 2>/dev/null || true
            [ -n "$EXTERNAL_LIBRARY" ] && mkdir -p "$EXTERNAL_LIBRARY" 2>/dev/null || true

            # Create required subdirectories with .immich marker files
            # Immich checks these on startup and fails if they're missing
            for subdir in thumbs upload backups library profile encoded-video; do
                mkdir -p "$UPLOAD_LOCATION/$subdir" 2>/dev/null || true
                touch "$UPLOAD_LOCATION/$subdir/.immich" 2>/dev/null || true
            done
            cd "$IMMICH_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            # Create docker-compose.yml
            if [ -n "$EXTERNAL_LIBRARY" ]; then
                # Strategy 2: external library mount included
                cat > docker-compose.yml << 'IMMICH_COMPOSE'
name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - ${EXTERNAL_LIBRARY}:/usr/src/app/external:ro
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - 2283:2283
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  redis:
    container_name: immich_redis
    image: docker.io/valkey/valkey:9-bookworm
    healthcheck:
      test: valkey-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    restart: always

volumes:
  model-cache:
IMMICH_COMPOSE
            else
                # Strategy 1: unified library, no external mount
                cat > docker-compose.yml << 'IMMICH_COMPOSE'
name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - 2283:2283
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  redis:
    container_name: immich_redis
    image: docker.io/valkey/valkey:9-bookworm
    healthcheck:
      test: valkey-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    restart: always

volumes:
  model-cache:
IMMICH_COMPOSE
            fi

            # Generate random password
            DB_PASS=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)

            # Create .env file with strategy-appropriate comments
            if [ "$IMMICH_STRATEGY" = "2" ]; then
                cat > .env << IMMICH_ENV
# ============================================================
# IMMICH CONFIGURATION — External Library Mode
# ============================================================
#
# STORAGE TEMPLATE (configure in Immich web UI):
#   Admin → Settings → Storage Template → Enable
#   Template: {{y}}/{{MM}}/{{filename}}
#   This organizes new uploads into: immich-uploads/2026/01/filename.jpg
#
# EXTERNAL LIBRARY SETUP:
#   Admin → External Libraries → Create Library
#   Import path: /usr/src/app/external
#   Click "Scan" to index your existing photos.
#
# ============================================================

# New uploads from phone/web
UPLOAD_LOCATION=$UPLOAD_LOCATION

# Existing photos (read-only, indexed by Immich)
# Mounted at /usr/src/app/external inside the container
EXTERNAL_LIBRARY=$EXTERNAL_LIBRARY

# Database location (keep on fast storage)
DB_DATA_LOCATION=./postgres

IMMICH_VERSION=release

DB_PASSWORD=$DB_PASS
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
IMMICH_ENV
            else
                cat > .env << IMMICH_ENV
# ============================================================
# IMMICH CONFIGURATION — Unified Library
# ============================================================
#
# All photos (imported + new uploads) are stored in one location.
# Storage template organizes files by date automatically.
#
# To import existing photos, run:
#   $IMMICH_DIR/import-photos.sh
#
# The import script handles storage template configuration,
# CLI installation, and uploads with EXIF date preservation.
#
# ============================================================

# All photos stored here (uploads + imported)
UPLOAD_LOCATION=$UPLOAD_LOCATION

# Database location (keep on fast storage)
DB_DATA_LOCATION=./postgres

IMMICH_VERSION=release

DB_PASSWORD=$DB_PASS
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
IMMICH_ENV
            fi

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$IMMICH_DIR" 2>/dev/null || true
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$UPLOAD_LOCATION" 2>/dev/null || true
            [ -n "$EXTERNAL_LIBRARY" ] && chown -R "$ACTUAL_USER:$ACTUAL_USER" "$EXTERNAL_LIBRARY" 2>/dev/null || true

            # Create import helper script for Strategy 1 with existing photos
            if [ "$IMMICH_STRATEGY" != "2" ] && [ -n "$EXISTING_PHOTOS_SOURCE" ]; then
                cat > "$IMMICH_DIR/import-photos.sh" << 'IMPORT_SCRIPT_HEAD'
#!/bin/bash
################################################################################
# Immich Photo Import Script
# Generated by ubuntu-post-install
#
# This script imports your existing photo collection into Immich with proper
# EXIF date preservation. Photos are uploaded through the API, which triggers
# Immich's metadata extraction pipeline — dates, GPS, camera info are all
# read from the original files.
#
# What this script does:
#   1. Creates admin account (if first run) or logs in
#   2. Generates an API key automatically
#   3. Configures the storage template (date-based organization)
#   4. Installs the Immich CLI (if needed)
#   5. Uploads all photos with EXIF metadata preserved
#
# Usage:
#   ./import-photos.sh                  # interactive (prompts for everything)
#   ./import-photos.sh <api-key>        # skip account setup, use existing key
################################################################################

IMPORT_SCRIPT_HEAD

                cat >> "$IMMICH_DIR/import-photos.sh" << IMPORT_SCRIPT_VARS
IMMICH_URL="http://localhost:2283"
SOURCE_DIR="$EXISTING_PHOTOS_SOURCE"
IMMICH_DIR="$IMMICH_DIR"
IMPORT_SCRIPT_VARS

                cat >> "$IMMICH_DIR/import-photos.sh" << 'IMPORT_SCRIPT_BODY'

echo ""
echo "┌─────────────────────────────────────────────────────────────────┐"
echo "│ IMMICH PHOTO IMPORT                                            │"
echo "└─────────────────────────────────────────────────────────────────┘"
echo ""

# ── Preflight checks ────────────────────────────────────────────
echo "Checking Immich server..."
if ! curl -s "$IMMICH_URL/api/server/ping" > /dev/null 2>&1; then
    echo ""
    echo "  ✗ Immich is not running at $IMMICH_URL"
    echo "    Start it with: cd $IMMICH_DIR && docker compose up -d"
    echo ""
    exit 1
fi
echo "  ✓ Immich is running"

# Check source directory
if [ ! -d "$SOURCE_DIR" ]; then
    echo ""
    echo "  ✗ Source directory not found: $SOURCE_DIR"
    echo "    Update SOURCE_DIR in this script if your photos moved."
    echo ""
    exit 1
fi
echo "  ✓ Source directory: $SOURCE_DIR"

echo -n "  Scanning for photos/videos (this may take a while)..."
PHOTO_COUNT=$(find "$SOURCE_DIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.heic" -o -iname "*.heif" -o -iname "*.webp" -o -iname "*.gif" -o -iname "*.tiff" -o -iname "*.bmp" -o -iname "*.mp4" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mkv" -o -iname "*.webm" \) 2>/dev/null | wc -l)
echo " done"
echo "  ✓ Found ~$PHOTO_COUNT photos/videos"

# ── Get or create API key ───────────────────────────────────────
API_KEY="${1:-}"

if [ -z "$API_KEY" ]; then
    echo ""
    # Check if server needs initial setup
    SERVER_CONFIG=$(curl -s "$IMMICH_URL/api/server/config" 2>/dev/null)
    IS_INITIALIZED=$(echo "$SERVER_CONFIG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('isInitialized', True))" 2>/dev/null)

    if [ "$IS_INITIALIZED" = "False" ]; then
        # ── First-time setup: create admin account ──────────────
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ FIRST-TIME SETUP — Creating admin account                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        echo ""
        echo "  Immich needs an admin account. Enter your details:"
        echo ""
        read -p "  Admin email: " ADMIN_EMAIL
        while [ -z "$ADMIN_EMAIL" ]; do
            read -p "  Admin email (required): " ADMIN_EMAIL
        done
        read -sp "  Admin password: " ADMIN_PASS
        echo ""
        while [ ${#ADMIN_PASS} -lt 8 ]; do
            echo "  Password must be at least 8 characters."
            read -sp "  Admin password: " ADMIN_PASS
            echo ""
        done
        read -p "  Your name [Admin]: " ADMIN_NAME
        ADMIN_NAME="${ADMIN_NAME:-Admin}"

        echo ""
        echo "  Creating admin account..."
        SIGNUP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
            -H "Content-Type: application/json" \
            "$IMMICH_URL/api/auth/admin-sign-up" \
            -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASS\",\"name\":\"$ADMIN_NAME\"}" 2>/dev/null)
        SIGNUP_CODE=$(echo "$SIGNUP_RESPONSE" | tail -1)
        SIGNUP_BODY=$(echo "$SIGNUP_RESPONSE" | sed '$d')

        if [ "$SIGNUP_CODE" = "201" ]; then
            echo "  ✓ Admin account created"
        else
            echo "  ✗ Failed to create admin account (HTTP $SIGNUP_CODE)"
            echo "  Response: $SIGNUP_BODY"
            echo ""
            echo "  Create your account manually at $IMMICH_URL"
            echo "  Then re-run this script with your API key:"
            echo "    $0 <api-key>"
            exit 1
        fi
    else
        # Server already initialized, need credentials to log in
        echo "  Immich is already set up. Log in to generate an API key."
        echo ""
        read -p "  Admin email: " ADMIN_EMAIL
        while [ -z "$ADMIN_EMAIL" ]; do
            read -p "  Admin email (required): " ADMIN_EMAIL
        done
        read -sp "  Admin password: " ADMIN_PASS
        echo ""
    fi

    # ── Login to get bearer token ───────────────────────────────
    echo "  Logging in..."
    LOGIN_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
        -H "Content-Type: application/json" \
        "$IMMICH_URL/api/auth/login" \
        -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASS\"}" 2>/dev/null)
    LOGIN_CODE=$(echo "$LOGIN_RESPONSE" | tail -1)
    LOGIN_BODY=$(echo "$LOGIN_RESPONSE" | sed '$d')

    if [ "$LOGIN_CODE" != "201" ]; then
        echo "  ✗ Login failed (HTTP $LOGIN_CODE)"
        echo ""
        echo "  Check your email/password and try again, or pass an API key:"
        echo "    $0 <api-key>"
        exit 1
    fi

    ACCESS_TOKEN=$(echo "$LOGIN_BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['accessToken'])" 2>/dev/null)
    if [ -z "$ACCESS_TOKEN" ]; then
        echo "  ✗ Could not extract access token from login response"
        exit 1
    fi
    echo "  ✓ Logged in"

    # ── Create API key ──────────────────────────────────────────
    echo "  Creating API key..."
    APIKEY_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        "$IMMICH_URL/api/api-keys" \
        -d '{"name":"import-photos-script"}' 2>/dev/null)
    APIKEY_CODE=$(echo "$APIKEY_RESPONSE" | tail -1)
    APIKEY_BODY=$(echo "$APIKEY_RESPONSE" | sed '$d')

    if [ "$APIKEY_CODE" = "201" ]; then
        API_KEY=$(echo "$APIKEY_BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret'])" 2>/dev/null)
        if [ -n "$API_KEY" ]; then
            echo "  ✓ API key created"
        else
            echo "  ✗ Could not extract API key from response"
            echo "  Create one manually: $IMMICH_URL → Account Settings → API Keys"
            echo "  Then re-run: $0 <api-key>"
            exit 1
        fi
    else
        echo "  ✗ Failed to create API key (HTTP $APIKEY_CODE)"
        echo "  Create one manually: $IMMICH_URL → Account Settings → API Keys"
        echo "  Then re-run: $0 <api-key>"
        exit 1
    fi
else
    # API key provided as argument — verify it
    echo ""
    echo "  Verifying API key..."
    VERIFY_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
        -H "x-api-key: $API_KEY" "$IMMICH_URL/api/users/me" 2>/dev/null)
    if [ "$VERIFY_CODE" != "200" ]; then
        echo "  ✗ Invalid API key (HTTP $VERIFY_CODE)"
        echo "    Double-check the key and try again."
        exit 1
    fi
    echo "  ✓ API key valid"
fi

# ── Configure storage template via API ──────────────────────────
echo ""
echo "  Configuring storage template ({{y}}/{{MM}}/{{filename}})..."
CURRENT_CONFIG=$(curl -s -H "x-api-key: $API_KEY" "$IMMICH_URL/api/system-config" 2>/dev/null)
if [ -n "$CURRENT_CONFIG" ] && command -v python3 &> /dev/null; then
    UPDATED_CONFIG=$(echo "$CURRENT_CONFIG" | python3 -c "
import sys, json
config = json.load(sys.stdin)
config['storageTemplate']['enabled'] = True
config['storageTemplate']['template'] = '{{y}}/{{MM}}/{{filename}}'
json.dump(config, sys.stdout)
" 2>/dev/null)
    if [ -n "$UPDATED_CONFIG" ]; then
        RESULT=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
            -H "x-api-key: $API_KEY" \
            -H "Content-Type: application/json" \
            "$IMMICH_URL/api/system-config" \
            -d "$UPDATED_CONFIG" 2>/dev/null)
        if [ "$RESULT" = "200" ]; then
            echo "  ✓ Storage template configured"
        else
            echo "  ⚠ Could not set storage template (HTTP $RESULT)"
            echo "    Set manually: Admin → Settings → Storage Template → Enable"
            echo "    Template: {{y}}/{{MM}}/{{filename}}"
        fi
    else
        echo "  ⚠ Could not parse config. Set storage template manually:"
        echo "    Admin → Settings → Storage Template → Enable"
        echo "    Template: {{y}}/{{MM}}/{{filename}}"
    fi
else
    echo "  ⚠ python3 not found. Set storage template manually:"
    echo "    Admin → Settings → Storage Template → Enable"
    echo "    Template: {{y}}/{{MM}}/{{filename}}"
fi

# ── Install immich-cli if needed ────────────────────────────────
echo ""
IMMICH_CMD=""

# Immich CLI requires Node.js >= 20 (File global class)
NODE_OK=false
if command -v node &> /dev/null; then
    NODE_MAJOR=$(node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1)
    if [ "$NODE_MAJOR" -ge 20 ] 2>/dev/null; then
        NODE_OK=true
    fi
fi

if [ "$NODE_OK" = false ]; then
    echo "  Immich CLI requires Node.js >= 20 (found: $(node -v 2>/dev/null || echo 'none'))."
    read -p "  Install Node.js 22 LTS now? (y/n): " INSTALL_NODE_YN
    if [ "$INSTALL_NODE_YN" = "y" ] || [ "$INSTALL_NODE_YN" = "Y" ]; then
        echo "  Installing Node.js 22 LTS from NodeSource..."
        curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - 2>/dev/null
        sudo apt-get install -y -qq nodejs 2>/dev/null
        NODE_MAJOR=$(node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1)
        if [ "$NODE_MAJOR" -ge 20 ] 2>/dev/null; then
            NODE_OK=true
            echo "  ✓ Node.js $(node -v) installed"
        else
            echo "  ✗ Installation failed."
            echo "    Install Node.js 20+ manually: https://nodejs.org/"
            echo "    Then re-run: $0 $API_KEY"
            exit 1
        fi
    else
        echo ""
        echo "  Install Node.js 20+ later and re-run this script:"
        echo "    curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"
        echo "    sudo apt-get install -y nodejs"
        echo "    $0 $API_KEY"
        exit 0
    fi
fi

if command -v immich &> /dev/null; then
    IMMICH_CMD="immich"
    echo "  ✓ Immich CLI found"
elif command -v npx &> /dev/null; then
    echo "  Immich CLI not installed. Will use npx (downloads temporarily)."
    IMMICH_CMD="npx --yes @immich/cli"
elif command -v npm &> /dev/null; then
    echo "  Installing Immich CLI globally..."
    if npm install -g @immich/cli 2>/dev/null; then
        IMMICH_CMD="immich"
        echo "  ✓ Immich CLI installed"
    else
        echo "  ✗ npm install failed. Trying npx..."
        IMMICH_CMD="npx --yes @immich/cli"
    fi
fi

if [ -z "$IMMICH_CMD" ]; then
    echo "  ✗ Could not find npm or npx despite Node.js being installed."
    echo "    Install manually: npm install -g @immich/cli"
    echo "    Then re-run: $0 $API_KEY"
    exit 1
fi

# ── Run the import ──────────────────────────────────────────────
echo ""
echo "  Authenticating CLI with Immich..."
if ! $IMMICH_CMD login "$IMMICH_URL/api" "$API_KEY"; then
    echo "  ✗ CLI login failed"
    exit 1
fi

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  Starting import from: $SOURCE_DIR"
echo "  Importing ~$PHOTO_COUNT files. This may take a while."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

$IMMICH_CMD upload --recursive "$SOURCE_DIR"

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  Import complete!"
echo "  View your photos at: $IMMICH_URL"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
IMPORT_SCRIPT_BODY

                chmod +x "$IMMICH_DIR/import-photos.sh"
                chown "$ACTUAL_USER:$ACTUAL_USER" "$IMMICH_DIR/import-photos.sh"
            fi

            echo ""
            echo "✓ Immich configured at $IMMICH_DIR"
            if [ -n "$EXTERNAL_LIBRARY" ]; then
                echo "  Existing photos: $EXTERNAL_LIBRARY (read-only)"
                echo "  New uploads:     $UPLOAD_LOCATION"
            else
                echo "  Photo library: $UPLOAD_LOCATION"
            fi
            echo ""

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "Immich" "2283" "photos"

            # Ask to start container
            prompt_yn "Start Immich now? (y/n):" "y" START_IMMICH
            if [ "$START_IMMICH" = "y" ] || [ "$START_IMMICH" = "Y" ]; then
                echo "  Starting Immich..."
                docker compose up -d 2>/dev/null && echo "  ✓ Immich started" || echo "  ⚠ Failed to start"
            fi

            # ── Post-setup instructions ─────────────────────────────
            echo ""
            echo "┌─────────────────────────────────────────────────────────────────┐"
            echo "│ IMMICH SETUP STEPS                                             │"
            echo "└─────────────────────────────────────────────────────────────────┘"
            echo ""

            if [ -n "$EXISTING_PHOTOS_SOURCE" ] && [ "$IMMICH_STRATEGY" != "2" ]; then
                echo "  Import your existing photos AFTER this script finishes:"
                echo "    $IMMICH_DIR/import-photos.sh"
                echo ""
                echo "  The script handles everything automatically:"
                echo "    creates your admin account, generates an API key,"
                echo "    configures the storage template, installs the CLI,"
                echo "    and imports all photos from $EXISTING_PHOTOS_SOURCE"
                echo "    with EXIF dates preserved."
                echo ""
                echo "  NOTE: Run import-photos.sh separately after this setup"
                echo "  script has completed. It may take a while for large"
                echo "  photo collections."
                echo ""
                echo "  After import, open http://localhost:2283 to browse your photos."
            elif [ "$IMMICH_STRATEGY" = "2" ]; then
                echo "  1. Open http://localhost:2283 and create your admin account"
                echo ""
                echo "  2. Enable storage template (keeps new uploads organized):"
                echo "     Admin → Settings → Storage Template → Enable"
                echo "     Template: {{y}}/{{MM}}/{{filename}}"
                echo ""
                echo "  3. Set up your existing photo library:"
                echo "     Admin → External Libraries → Create Library"
                echo "     Import path: /usr/src/app/external"
                echo "     Click Scan to index your photos"
                echo ""
                echo "  New uploads from the mobile app or web UI are stored"
                echo "  separately in: $UPLOAD_LOCATION"
            else
                echo "  1. Open http://localhost:2283 and create your admin account"
                echo ""
                echo "  2. Enable storage template (keeps files organized by date):"
                echo "     Admin → Settings → Storage Template → Enable"
                echo "     Template: {{y}}/{{MM}}/{{filename}}"
                echo ""
                echo "  That's it. Upload photos from the mobile app or web UI."
                echo "  All photos are stored in: $PHOTOS_DIR"
            fi
            echo ""
        fi
        fi  # End IMMICH_RECONFIGURE check
    fi  # End INSTALL_IMMICH check

    # ---- AUDIOBOOKSHELF ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_AUDIOBOOKSHELF" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ AUDIOBOOKSHELF - Audiobook & podcast server                     │"
        echo "│ Stream audiobooks with progress sync across devices.            │"
        echo "│ Port: 13378                                                     │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Audiobookshelf? (y/n):" "n" INSTALL_AUDIOBOOKSHELF
    fi

    if [ "$INSTALL_AUDIOBOOKSHELF" = "y" ] || [ "$INSTALL_AUDIOBOOKSHELF" = "Y" ]; then
        ABS_DIR="$DOCKER_DIR/audiobookshelf"

        # Check if already installed and ask what to do
        check_service_exists "AudioBookshelf" "$ABS_DIR" ABS_RECONFIGURE

        if [ "$ABS_RECONFIGURE" = "true" ]; then
            echo "Installing Audiobookshelf..."

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $ABS_DIR"
        else
            mkdir -p "$ABS_DIR"
            ensure_docker_dir_ownership "$ABS_DIR"
            cd "$ABS_DIR"

            prompt_text "Path to audiobooks folder [default: $PRIMARY_DRIVE_PATH/audiobooks]:" "$PRIMARY_DRIVE_PATH/audiobooks" AUDIOBOOKS_PATH

            cat > docker-compose.yml << ABS_COMPOSE
name: audiobookshelf

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    hostname: audiobookshelf
    restart: unless-stopped
    environment:
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    volumes:
      - ./config:/config
      - ./metadata:/metadata
      - ${AUDIOBOOKS_PATH}:/audiobooks
      - ${PODCASTS_PATH:-./podcasts}:/podcasts
    ports:
      - "13378:80"
ABS_COMPOSE

            cat > .env << ABS_ENV
AUDIOBOOKS_PATH=$AUDIOBOOKS_PATH
PODCASTS_PATH=./podcasts
ABS_ENV

            mkdir -p config metadata podcasts
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ABS_DIR"

            echo ""
            echo "✓ Audiobookshelf configured at $ABS_DIR"

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "AudioBookshelf" "13378" "audiobooks"

            prompt_yn "Start Audiobookshelf now? (y/n):" "y" START_ABS
            if [ "$START_ABS" = "y" ] || [ "$START_ABS" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Audiobookshelf started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:13378"
            echo ""
        fi
        fi  # End ABS_RECONFIGURE check
    fi  # End INSTALL_AUDIOBOOKSHELF check

    # ---- EMBY ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_EMBY" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ EMBY - Media server for movies, TV, music                       │"
        echo "│ Stream your media library to any device.                        │"
        echo "│ Port: 8096 (web), 8920 (https)                                  │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Emby? (y/n):" "n" INSTALL_EMBY
    fi

    if [ "$INSTALL_EMBY" = "y" ] || [ "$INSTALL_EMBY" = "Y" ]; then
        EMBY_DIR="$DOCKER_DIR/emby"

        # Check if already installed and ask what to do
        check_service_exists "Emby" "$EMBY_DIR" EMBY_RECONFIGURE

        if [ "$EMBY_RECONFIGURE" = "true" ]; then
            echo "Installing Emby..."

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $EMBY_DIR"
        else
            mkdir -p "$EMBY_DIR"
            ensure_docker_dir_ownership "$EMBY_DIR"
            cd "$EMBY_DIR"

            prompt_text "Path to media folder [default: $PRIMARY_DRIVE_PATH/media]:" "$PRIMARY_DRIVE_PATH/media" MEDIA_PATH

            cat > docker-compose.yml << EMBY_COMPOSE
name: emby

services:
  emby:
    image: emby/embyserver:latest
    container_name: emby
    hostname: emby
    restart: unless-stopped
    environment:
      - UID=$(id -u "$ACTUAL_USER")
      - GID=$(id -g "$ACTUAL_USER")
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    volumes:
      - ./config:/config
      - ${MEDIA_PATH}:/media
    ports:
      - "8096:8096"
      - "8920:8920"
    # Uncomment for hardware transcoding (Intel/AMD)
    # devices:
    #   - /dev/dri:/dev/dri
EMBY_COMPOSE

            cat > .env << EMBY_ENV
MEDIA_PATH=$MEDIA_PATH
EMBY_ENV

            mkdir -p config
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$EMBY_DIR"

            echo ""
            echo "✓ Emby configured at $EMBY_DIR"

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "Emby" "8096" "emby"

            prompt_yn "Start Emby now? (y/n):" "y" START_EMBY
            if [ "$START_EMBY" = "y" ] || [ "$START_EMBY" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Emby started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8096"
            echo ""
        fi
        fi  # End EMBY_RECONFIGURE check
    fi  # End INSTALL_EMBY check

    # ---- A.R.M. (Automatic Ripping Machine) ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_ARM" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ A.R.M. - Automatic Ripping Machine                              │"
        echo "│ Automatically rip DVDs, Blu-rays, and CDs.                      │"
        echo "│ Port: 8080                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install A.R.M.? (y/n):" "n" INSTALL_ARM
    fi

    if [ "$INSTALL_ARM" = "y" ] || [ "$INSTALL_ARM" = "Y" ]; then
        ARM_DIR="$DOCKER_DIR/arm"
        check_service_exists "A.R.M." "$ARM_DIR" ARM_RECONFIGURE

        if [ "$ARM_RECONFIGURE" = "true" ]; then
            echo "Installing A.R.M...."

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $ARM_DIR"
        else
            mkdir -p "$ARM_DIR"
            ensure_docker_dir_ownership "$ARM_DIR"
            cd "$ARM_DIR"

            prompt_text "Path for ripped media output [default: $PRIMARY_DRIVE_PATH/ripped]:" "$PRIMARY_DRIVE_PATH/ripped" ARM_OUTPUT

            # Detect optical drives
            echo ""
            echo "Detecting optical drives..."
            OPTICAL_DRIVES=$(ls /dev/sr* 2>/dev/null || echo "")
            if [ -n "$OPTICAL_DRIVES" ]; then
                echo "Found: $OPTICAL_DRIVES"
            else
                echo "No optical drives detected. You can add them later."
                OPTICAL_DRIVES="/dev/sr0"
            fi

            cat > docker-compose.yml << ARM_COMPOSE
name: arm

services:
  automatic-ripping-machine:
    image: automaticrippingmachine/automatic-ripping-machine:latest
    container_name: arm
    hostname: arm
    restart: unless-stopped
    environment:
      - ARM_UID=$(id -u "$ACTUAL_USER")
      - ARM_GID=$(id -g "$ACTUAL_USER")
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    volumes:
      - ./config:/etc/arm/config
      - ./logs:/home/arm/logs
      - ${ARM_OUTPUT}/movies:/home/arm/media/completed
      - ${ARM_OUTPUT}/music:/home/arm/music
    ports:
      - "8080:8080"
    devices:
      - /dev/sr0:/dev/sr0
      # Add more drives as needed:
      # - /dev/sr1:/dev/sr1
    privileged: true
ARM_COMPOSE

            cat > .env << ARM_ENV
ARM_OUTPUT=$ARM_OUTPUT
ARM_ENV

            mkdir -p config logs
            mkdir -p "$ARM_OUTPUT/movies" "$ARM_OUTPUT/music"
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ARM_DIR"

            echo ""
            echo "✓ A.R.M. configured at $ARM_DIR"

            prompt_yn "Start A.R.M. now? (y/n):" "y" START_ARM
            if [ "$START_ARM" = "y" ] || [ "$START_ARM" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ A.R.M. started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8080"
            echo "  ⚠️  Complete setup in browser on first visit!"
            echo "  Note: Edit docker-compose.yml to add more optical drives"
            echo ""
        fi
        fi  # End ARM_RECONFIGURE check
    fi  # End INSTALL_ARM check

    # ---- FILEBROWSER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_FILEBROWSER" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ FILEBROWSER - Web-based file manager                            │"
        echo "│ Browse, upload, download files via web interface.               │"
        echo "│ Port: 8085                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Filebrowser? (y/n):" "n" INSTALL_FILEBROWSER
    fi

    if [ "$INSTALL_FILEBROWSER" = "y" ] || [ "$INSTALL_FILEBROWSER" = "Y" ]; then
        FB_DIR="$DOCKER_DIR/filebrowser"
        check_service_exists "FileBrowser" "$FB_DIR" FB_RECONFIGURE

        if [ "$FB_RECONFIGURE" = "true" ]; then
            echo "Installing Filebrowser..."

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $FB_DIR"
        else
            mkdir -p "$FB_DIR"
            ensure_docker_dir_ownership "$FB_DIR"
            cd "$FB_DIR"

            prompt_text "Path to browse [default: $PRIMARY_DRIVE_PATH]:" "$PRIMARY_DRIVE_PATH" FB_PATH

            cat > docker-compose.yml << FB_COMPOSE
name: filebrowser

services:
  filebrowser:
    image: filebrowser/filebrowser:s6
    container_name: filebrowser
    hostname: filebrowser
    restart: unless-stopped
    environment:
      - PUID=$(id -u "$ACTUAL_USER")
      - PGID=$(id -g "$ACTUAL_USER")
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    volumes:
      - ${FB_PATH}:/srv
      - ./database/filebrowser.db:/database/filebrowser.db
      - ./config/settings.json:/config/settings.json
    ports:
      - "8085:80"
FB_COMPOSE

            cat > .env << FB_ENV
FB_PATH=$FB_PATH
FB_ENV

            mkdir -p database config
            touch database/filebrowser.db
            cat > config/settings.json << 'FB_SETTINGS'
{
  "port": 80,
  "baseURL": "",
  "address": "",
  "log": "stdout",
  "database": "/database/filebrowser.db",
  "root": "/srv"
}
FB_SETTINGS

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FB_DIR"

            echo ""
            echo "✓ Filebrowser configured at $FB_DIR"

            prompt_yn "Start Filebrowser now? (y/n):" "y" START_FB
            if [ "$START_FB" = "y" ] || [ "$START_FB" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Filebrowser started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8085"
            echo "  Default login: admin / admin (change immediately!)"
            echo ""
        fi
        fi  # End FB_RECONFIGURE check
    fi  # End INSTALL_FILEBROWSER check

    # ---- MAGIC MIRROR ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_MAGICMIRROR" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ MAGIC MIRROR - Smart mirror / dashboard display                 │"
        echo "│ Modular smart mirror platform. Run up to 3 instances.           │"
        echo "│ Ports: 8081, 8082, 8083                                         │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Magic Mirror? (y/n):" "n" INSTALL_MAGICMIRROR
    fi

    if [ "$INSTALL_MAGICMIRROR" = "y" ] || [ "$INSTALL_MAGICMIRROR" = "Y" ]; then
        echo ""
        prompt_text "How many Magic Mirror instances? [1-3, default: 1]:" "1" MM_COUNT
        MM_COUNT=${MM_COUNT:-1}
        if [ "$MM_COUNT" -gt 3 ]; then MM_COUNT=3; fi
        if [ "$MM_COUNT" -lt 1 ]; then MM_COUNT=1; fi

        echo "Installing $MM_COUNT Magic Mirror instance(s)..."

        for i in $(seq 1 $MM_COUNT); do
            MM_PORT=$((8080 + i))
            MM_DIR="$DOCKER_DIR/magicmirror-$MM_PORT"

            if [ "$DRY_RUN" = true ]; then
                echo "[DRY-RUN] Would create $MM_DIR (port $MM_PORT)"
            else
                mkdir -p "$MM_DIR"
                ensure_docker_dir_ownership "$MM_DIR"
                cd "$MM_DIR"

                cat > docker-compose.yml << MM_COMPOSE
name: mm-$MM_PORT

services:
  magicmirror:
    image: karsten13/magicmirror:latest
    container_name: magicmirror-$MM_PORT
    hostname: magicmirror-$MM_PORT
    restart: unless-stopped
    environment:
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    volumes:
      - ./config:/opt/magic_mirror/config
      - ./modules:/opt/magic_mirror/modules
      - ./css:/opt/magic_mirror/css
    ports:
      - "$MM_PORT:8080"
MM_COMPOSE

                mkdir -p config modules css

                # Ask if user has existing config to copy
                echo ""
                echo "  Config options:"
                echo "    [1] Use default config (basic modules)"
                echo "    [2] Copy existing config from path"
                read -p "  Choose [1]: " MM_CONFIG_CHOICE
                MM_CONFIG_CHOICE=${MM_CONFIG_CHOICE:-1}

                if [ "$MM_CONFIG_CHOICE" = "2" ]; then
                    read -p "  Path to config.js: " MM_CONFIG_PATH
                    if [ -f "$MM_CONFIG_PATH" ]; then
                        cp "$MM_CONFIG_PATH" config/config.js
                        echo "  ✓ Copied config from $MM_CONFIG_PATH"

                        # Also copy custom.css if exists in same directory
                        MM_CSS_PATH="${MM_CONFIG_PATH%/*}/custom.css"
                        if [ -f "$MM_CSS_PATH" ]; then
                            cp "$MM_CSS_PATH" css/custom.css
                            echo "  ✓ Copied custom.css"
                        fi

                        # Parse config for third-party modules and offer to download
                        echo ""
                        echo "  Scanning for third-party modules..."

                        # Extract module names starting with MMM- (third-party convention)
                        THIRD_PARTY_MODULES=$(grep -oP 'module:\s*["\x27]MMM-[^"\x27]+["\x27]' config/config.js 2>/dev/null | sed "s/module:\s*[\"']//g" | sed "s/[\"']//g" | sort -u)

                        if [ -n "$THIRD_PARTY_MODULES" ]; then
                            echo "  Found third-party modules:"
                            echo "$THIRD_PARTY_MODULES" | while read mod; do
                                echo "    - $mod"
                            done
                            echo ""
                            prompt_yn "  Download these modules from GitHub? (y/n):" "y" MM_DOWNLOAD_MODS
                            if [ "$MM_DOWNLOAD_MODS" = "y" ] || [ "$MM_DOWNLOAD_MODS" = "Y" ]; then
                                cd modules
                                echo "$THIRD_PARTY_MODULES" | while read mod; do
                                    if [ -n "$mod" ] && [ ! -d "$mod" ]; then
                                        echo "  Downloading $mod..."
                                        # Try common GitHub patterns
                                        git clone --depth 1 "https://github.com/MichMich/$mod.git" 2>/dev/null || \
                                        git clone --depth 1 "https://github.com/bugsounet/$mod.git" 2>/dev/null || \
                                        git clone --depth 1 "https://github.com/MagicMirrorOrg/$mod.git" 2>/dev/null || \
                                        echo "    ⚠ Could not find $mod - search at https://github.com/topics/magicmirror"
                                    fi
                                done
                                cd ..

                                # Run npm install for each downloaded module
                                # Note: This runs AFTER container is started, inside the container
                                echo ""
                                echo "  Note: Module dependencies will be installed when container starts"
                                echo "  If you need to manually install module dependencies, run:"
                                echo "    docker exec magicmirror-$MM_PORT sh -c 'cd /opt/magic_mirror/modules/<module-name> && npm install --production'"
                                echo ""
                            fi
                        else
                            echo "  No third-party modules (MMM-*) found in config"
                        fi
                    else
                        echo "  ⚠ File not found: $MM_CONFIG_PATH"
                        echo "  Using default config..."
                        MM_CONFIG_CHOICE="1"
                    fi
                fi

                # Create default config if not copying
                if [ "$MM_CONFIG_CHOICE" != "2" ]; then
                    # Create basic config.js
                    cat > config/config.js << 'MM_CONFIG'
let config = {
    address: "0.0.0.0",
    port: 8080,
    ipWhitelist: [],
    language: "en",
    timeFormat: 12,
    units: "imperial",
    modules: [
        {
            module: "alert",
        },
        {
            module: "clock",
            position: "top_left"
        },
        {
            module: "calendar",
            header: "Calendar",
            position: "top_left",
            config: {
                calendars: [
                    {
                        symbol: "calendar-check",
                        url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics"
                    }
                ]
            }
        },
        {
            module: "weather",
            position: "top_right",
            config: {
                weatherProvider: "openmeteo",
                type: "current",
                lat: 40.7128,
                lon: -74.0060
            }
        },
        {
            module: "weather",
            position: "top_right",
            header: "Weather Forecast",
            config: {
                weatherProvider: "openmeteo",
                type: "forecast",
                lat: 40.7128,
                lon: -74.0060
            }
        },
        {
            module: "newsfeed",
            position: "bottom_bar",
            config: {
                feeds: [
                    {
                        title: "BBC",
                        url: "https://feeds.bbci.co.uk/news/rss.xml"
                    }
                ],
                showSourceTitle: true,
                showPublishDate: true,
                broadcastNewsFeeds: true,
                broadcastNewsUpdates: true
            }
        },
    ]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}
MM_CONFIG
                fi

                chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MM_DIR"

                echo "  ✓ Magic Mirror #$i configured at $MM_DIR (port $MM_PORT)"
            fi
        done

        if [ "$DRY_RUN" != true ]; then
            echo ""
            prompt_yn "Start Magic Mirror instance(s) now? (y/n):" "y" START_MM
            if [ "$START_MM" = "y" ] || [ "$START_MM" = "Y" ]; then
                for i in $(seq 1 $MM_COUNT); do
                    MM_PORT=$((8080 + i))
                    MM_DIR="$DOCKER_DIR/magicmirror-$MM_PORT"
                    (cd "$MM_DIR" && docker compose up -d 2>/dev/null) && echo "  ✓ Magic Mirror #$i started (port $MM_PORT)" || echo "  ⚠ Failed to start Magic Mirror #$i"

                    # Install npm dependencies for third-party modules inside container
                    if [ -d "$MM_DIR/modules" ]; then
                        echo "  Installing module dependencies inside container..."
                        sleep 3  # Wait for container to fully start
                        for mod_dir in "$MM_DIR/modules"/MMM-*/; do
                            if [ -d "$mod_dir" ] && [ -f "$mod_dir/package.json" ]; then
                                mod_name=$(basename "$mod_dir")
                                echo "    Installing $mod_name dependencies..."
                                docker exec magicmirror-$MM_PORT sh -c "cd /opt/magic_mirror/modules/$mod_name && npm install --production" 2>/dev/null && \
                                    echo "      ✓ $mod_name dependencies installed" || \
                                    echo "      ⚠ $mod_name - npm install failed (container may need restart)"
                            fi
                        done
                    fi
                done
            fi

            echo "  Access at:  http://localhost:808X"
            echo "  Edit config: ~/docker/magicmirror-808X/config/config.js"
            echo ""
        fi
    fi

    # ---- ACTUALBUDGET ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_ACTUALBUDGET" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ ACTUALBUDGET - Open-source Personal Finance Management         │"
        echo "│ Budget tracking with bank account synchronization via SimpleFIN│"
        echo "│ Port: 5006                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install ActualBudget? (y/n):" "n" INSTALL_ACTUALBUDGET
    fi

    if [ "$INSTALL_ACTUALBUDGET" = "y" ] || [ "$INSTALL_ACTUALBUDGET" = "Y" ]; then
        AB_DIR="$DOCKER_DIR/actualbudget"
        check_service_exists "ActualBudget" "$AB_DIR" AB_RECONFIGURE

        if [ "$AB_RECONFIGURE" = "true" ]; then
        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $AB_DIR"
        else
            echo "Installing ActualBudget..."
            mkdir -p "$AB_DIR/data"
            ensure_docker_dir_ownership "$AB_DIR"
            cd "$AB_DIR"

            # Create .env file for environment variables
            cat > .env << 'AB_ENV'
# ActualBudget Environment Variables
TZ=UTC
AB_ENV

            cat > docker-compose.yml << 'AB_COMPOSE'
name: actualbudget

services:
  actualbudget:
    image: actualbudget/actual-server:latest
    container_name: actualbudget
    restart: unless-stopped
    ports:
      - "5006:5006"
    volumes:
      - ./data:/data
    env_file:
      - .env
    labels:
      - "io.podman.annotations.label/fail2ban.enable=true"
      - "io.podman.annotations.label/fail2ban.filter=caddy-auth"
AB_COMPOSE

            echo "  ✓ ActualBudget configured at $AB_DIR"

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "ActualBudget" "5006" "budget"

            prompt_yn "Start ActualBudget now? (y/n):" "y" START_AB
            if [ "$START_AB" = "y" ] || [ "$START_AB" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ ActualBudget started" || echo "  ⚠ Failed to start ActualBudget"
            fi

            echo ""
            echo "  Access at:  http://localhost:5006"
            echo "  Bank sync:  https://simplefin.org/ (SimpleFIN account required)"
            echo "  Data dir:   $AB_DIR/data"
            echo ""
        fi
        fi  # End AB_RECONFIGURE check
    fi  # End INSTALL_ACTUALBUDGET check

    # ---- KEYCLOAK ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_KEYCLOAK" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ KEYCLOAK - Identity and Access Management (IAM)                │"
        echo "│ SSO, OAuth2, SAML, User Management, MFA                        │"
        echo "│ Port: 8180 (HTTP) - Use reverse proxy for HTTPS                │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Keycloak? (y/n):" "n" INSTALL_KEYCLOAK
    fi

    if [ "$INSTALL_KEYCLOAK" = "y" ] || [ "$INSTALL_KEYCLOAK" = "Y" ]; then
        KC_DIR="$DOCKER_DIR/keycloak"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $KC_DIR"
        else
            echo "Installing Keycloak..."
            echo ""
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo "PASSWORD REQUIREMENTS:"
            echo "  • Minimum 12 characters (16+ recommended)"
            echo "  • Letters and numbers ONLY (no special characters)"
            echo "  • Press ENTER for secure auto-generated password"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo ""

            # Prompt for admin password with validation
            KC_ADMIN_PASS=""
            while true; do
                read -s -p "Enter Keycloak admin password [auto-generate]: " KC_ADMIN_PASS
                echo ""

                # Generate secure password if user pressed Enter
                if [ -z "$KC_ADMIN_PASS" ]; then
                    KC_ADMIN_PASS=$(generate_password 20)
                    echo "  ✓ Generated secure admin password (saved in .env)"
                    break
                fi

                # Validate password
                if validate_password "$KC_ADMIN_PASS" 12; then
                    echo "  ✓ Admin password accepted"
                    break
                fi
                echo "  Please try again."
            done

            # Prompt for database password with validation
            KC_DB_PASS=""
            while true; do
                read -s -p "Enter database password [auto-generate]: " KC_DB_PASS
                echo ""

                # Generate secure password if user pressed Enter
                if [ -z "$KC_DB_PASS" ]; then
                    KC_DB_PASS=$(generate_password 32)
                    echo "  ✓ Generated secure database password (saved in .env)"
                    break
                fi

                # Validate password
                if validate_password "$KC_DB_PASS" 12; then
                    echo "  ✓ Database password accepted"
                    break
                fi
                echo "  Please try again."
            done
            echo ""

            # Ask about production vs development mode
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo "DEPLOYMENT MODE:"
            echo "  • Production: Requires HTTPS via Caddy2 (recommended)"
            echo "  • Development: HTTP only, relaxed security (testing only)"
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            prompt_yn "Use production mode? (requires Caddy2 reverse proxy) (y/n):" "y" KC_PRODUCTION

            KC_HOSTNAME=""
            KC_START_CMD="start-dev"
            KC_HOSTNAME_STRICT="false"

            if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then
                KC_START_CMD="start"
                KC_HOSTNAME_STRICT="false"

                echo ""
                echo "Enter your Keycloak hostname (e.g., auth.yourdomain.com)"
                echo "This should match your Caddy2 configuration."
                read -p "Hostname: " KC_HOSTNAME

                if [ -n "$KC_HOSTNAME" ]; then
                    echo "  ✓ Production mode enabled with hostname: $KC_HOSTNAME"
                    echo "  ⚠ Make sure Caddy2 is configured for this domain!"
                else
                    echo "  ⚠ No hostname provided - using relaxed mode"
                    KC_HOSTNAME=""
                fi
            fi

            mkdir -p "$KC_DIR/data" "$KC_DIR/postgres-data"
            ensure_docker_dir_ownership "$KC_DIR"
            cd "$KC_DIR"

            # Create .env file for sensitive credentials
            cat > .env << KC_ENV
# Keycloak Environment Variables
# ⚠ KEEP THIS FILE SECURE - Contains sensitive passwords

# Admin Credentials
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=$KC_ADMIN_PASS

# Database Credentials
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=$KC_DB_PASS
KC_DB=postgres
KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=$KC_DB_PASS

# Keycloak Configuration
# Proxy settings (v2) - Trust X-Forwarded-* headers from Caddy2
KC_PROXY_HEADERS=xforwarded
KC_HTTP_ENABLED=true
KC_HOSTNAME_STRICT=$KC_HOSTNAME_STRICT
KC_LOG_LEVEL=INFO
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
KC_ENV

            # Add hostname to .env if provided
            if [ -n "$KC_HOSTNAME" ]; then
                echo "KC_HOSTNAME=$KC_HOSTNAME" >> .env
            fi

            # Create docker-compose.yml
            cat > docker-compose.yml << KC_COMPOSE
name: keycloak

services:
  postgres:
    image: postgres:16-alpine
    container_name: keycloak-db
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: keycloak
    restart: unless-stopped
    command:
      - $KC_START_CMD
    env_file:
      - .env
    ports:
      - "8180:8080"
    volumes:
      - ./data:/opt/keycloak/data
    depends_on:
      postgres:
        condition: service_healthy
    labels:
      - "io.podman.annotations.label/fail2ban.enable=true"
      - "io.podman.annotations.label/fail2ban.filter=caddy-auth"
KC_COMPOSE

            echo ""
            echo "  ✓ Keycloak configured at $KC_DIR"
            echo "  ✓ Credentials saved in .env file"
            if [ "$KC_PRODUCTION" = "y" ] || [ "$KC_PRODUCTION" = "Y" ]; then
                echo "  ✓ Production mode enabled"
            else
                echo "  ℹ Development mode (use production mode for internet-facing deployments)"
            fi
            echo ""

            # If Caddy is installed/being installed, offer to configure it for Keycloak
            if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ] || [ -d "$DOCKER_DIR/caddy" ]; then
                echo ""
                prompt_yn "Configure Caddy reverse proxy for Keycloak? (y/n):" "y" CONFIGURE_CADDY_KC

                if [ "$CONFIGURE_CADDY_KC" = "y" ] || [ "$CONFIGURE_CADDY_KC" = "Y" ]; then
                    CADDY_DIR="$DOCKER_DIR/caddy"

                    # Ask for domain
                    prompt_text "  Domain for Keycloak (e.g., auth.yourdomain.com):" "auth.localhost" KC_CADDY_DOMAIN

                    if [ -f "$CADDY_DIR/Caddyfile" ]; then
                        # Backup existing Caddyfile
                        mkdir -p "$CADDY_DIR/backups"
                        cp "$CADDY_DIR/Caddyfile" "$CADDY_DIR/backups/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)"
                        echo "  ✓ Backed up existing Caddyfile"

                        # Check if Keycloak config already exists
                        if ! grep -q "$KC_CADDY_DOMAIN" "$CADDY_DIR/Caddyfile"; then
                            # Add Keycloak configuration
                            cat >> "$CADDY_DIR/Caddyfile" << EOF

# Keycloak - Identity and Access Management
$KC_CADDY_DOMAIN {
    log {
        output file /var/log/caddy/keycloak-access.log
        format json
        level INFO
    }

    reverse_proxy localhost:8180

    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
    }
}
EOF
                            echo "  ✓ Added Keycloak configuration to Caddyfile"

                            # Reload Caddy if it's running
                            if docker ps --format '{{.Names}}' | grep -q "caddy"; then
                                CADDY_CONTAINER=$(docker ps --format '{{.Names}}' | grep "caddy" | head -1)
                                echo "  Reloading Caddy configuration..."

                                if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy fmt --overwrite 2>/dev/null; then
                                    echo "  ✓ Formatted Caddyfile"
                                fi

                                if docker exec -w /etc/caddy "$CADDY_CONTAINER" caddy reload 2>/dev/null; then
                                    echo "  ✓ Caddy reloaded successfully"
                                    echo ""
                                    echo "  Keycloak will be available at: https://$KC_CADDY_DOMAIN"
                                else
                                    echo "  ⚠ Failed to reload Caddy - check logs"
                                    echo "  Manual reload: cd $CADDY_DIR && docker exec -w /etc/caddy caddy caddy reload"
                                fi
                            else
                                echo "  ⚠ Caddy container not running - start it to use this configuration"
                            fi
                        else
                            echo "  ℹ Keycloak configuration already exists in Caddyfile"
                        fi
                    else
                        echo "  ⚠ Caddyfile not found at $CADDY_DIR/Caddyfile"
                        echo "  You can configure Caddy manually later"
                    fi
                fi
            fi

            prompt_yn "Start Keycloak now? (y/n):" "y" START_KC
            if [ "$START_KC" = "y" ] || [ "$START_KC" = "Y" ]; then
                echo "  Starting Keycloak (this may take a minute)..."
                docker compose up -d 2>/dev/null && echo "  ✓ Keycloak started" || echo "  ⚠ Failed to start Keycloak"

                # Automated initial configuration
                echo ""
                prompt_yn "Configure Keycloak with initial realm and clients? (y/n):" "y" CONFIGURE_KC

                if [ "$CONFIGURE_KC" = "y" ] || [ "$CONFIGURE_KC" = "Y" ]; then
                    echo ""
                    echo "  Configuring Keycloak..."
                    echo "  This will create a realm and OAuth2 clients for your services."
                    echo ""

                    # Get realm name
                    prompt_text "  Realm name (e.g., homelab, services):" "homelab" KC_REALM

                    # Get domain configuration for redirect URIs
                    echo ""
                    echo "  ──────────────────────────────────────────────────────────────"
                    echo "  DOMAIN CONFIGURATION"
                    echo "  ──────────────────────────────────────────────────────────────"
                    echo ""
                    echo "  Keycloak needs to know where your services are hosted."
                    echo ""
                    echo "  Options:"
                    echo "    1. Local only (http://localhost:PORT)"
                    echo "    2. Public domain (https://yourdomain.com)"
                    echo "    3. Both local and public"
                    echo ""
                    prompt_text "  Enter your setup (1/2/3):" "1" KC_SETUP_TYPE

                    KC_DOMAIN="localhost"
                    KC_PUBLIC_DOMAIN=""
                    KC_EXTERNAL_SERVICE=""

                    if [ "$KC_SETUP_TYPE" = "2" ] || [ "$KC_SETUP_TYPE" = "3" ]; then
                        echo ""
                        prompt_text "  Your public domain (e.g., example.com):" "" KC_PUBLIC_DOMAIN

                        echo ""
                        echo "  ⚠  IMPORTANT: For Keycloak to work with external services,"
                        echo "     it MUST be accessible at https://auth.$KC_PUBLIC_DOMAIN"
                        echo ""
                        echo "  This requires:"
                        echo "    ✓ DNS A record: auth.$KC_PUBLIC_DOMAIN → Your Server IP"
                        echo "    ✓ Caddy reverse proxy configured"
                        echo "    ✓ Ports 80/443 open in firewall"
                        echo ""
                        prompt_yn "  Is Keycloak accessible at https://auth.$KC_PUBLIC_DOMAIN? (y/n):" "n" KC_DOMAIN_READY

                        if [ "$KC_DOMAIN_READY" != "y" ] && [ "$KC_DOMAIN_READY" != "Y" ]; then
                            echo ""
                            echo "  ⚠  WARNING: Keycloak won't work with external services until"
                            echo "     you configure Caddy and DNS. See KEYCLOAK-SETUP-GUIDE.md"
                            echo ""
                            echo "  You can still proceed and configure Caddy later."
                            echo ""
                        fi

                        # Ask about external services (like Pikapod)
                        echo ""
                        prompt_yn "  Are you using external hosted services (e.g., Pikapod)? (y/n):" "n" KC_HAS_EXTERNAL

                        if [ "$KC_HAS_EXTERNAL" = "y" ] || [ "$KC_HAS_EXTERNAL" = "Y" ]; then
                            echo ""
                            echo "  Enter the URL of your external service (e.g., https://actualbudget-abc.pikapod.net)"
                            prompt_text "  External service URL:" "" KC_EXTERNAL_SERVICE
                        fi
                    fi

                    if [ "$KC_SETUP_TYPE" = "1" ] || [ "$KC_SETUP_TYPE" = "3" ]; then
                        KC_DOMAIN="localhost"
                    fi

                    # Wait for Keycloak to be fully ready (can take 30-60 seconds)
                    echo ""
                    echo "  Waiting for Keycloak to be ready..."
                    KC_READY=false
                    for i in {1..60}; do
                        if docker exec keycloak curl -sf http://localhost:8080/health/ready > /dev/null 2>&1; then
                            KC_READY=true
                            echo "  ✓ Keycloak is ready"
                            break
                        fi
                        echo -n "."
                        sleep 2
                    done
                    echo ""

                    if [ "$KC_READY" = true ]; then
                        # Login to Keycloak admin CLI
                        echo "  Logging in to Keycloak admin CLI..."
                        docker exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
                            --server http://localhost:8080 \
                            --realm master \
                            --user admin \
                            --password "$KC_ADMIN_PASS" > /dev/null 2>&1

                        if [ $? -eq 0 ]; then
                            echo "  ✓ Logged in to Keycloak"

                            # Create realm
                            echo "  Creating realm '$KC_REALM'..."
                            docker exec keycloak /opt/keycloak/bin/kcadm.sh create realms \
                                -s realm="$KC_REALM" \
                                -s enabled=true \
                                -s displayName="$KC_REALM" \
                                -s registrationAllowed=false \
                                -s resetPasswordAllowed=true \
                                -s rememberMe=true \
                                -s loginWithEmailAllowed=true \
                                -s duplicateEmailsAllowed=false \
                                -s sslRequired=EXTERNAL > /dev/null 2>&1

                            if [ $? -eq 0 ]; then
                                echo "  ✓ Created realm '$KC_REALM'"
                            fi

                            # Create OAuth2 client for ActualBudget
                            if [ "$INSTALL_ACTUALBUDGET" = "y" ] || [ "$INSTALL_ACTUALBUDGET" = "Y" ]; then
                                echo "  Creating OAuth2 client for ActualBudget..."
                                AB_CLIENT_SECRET=$(openssl rand -hex 32)

                                # Build redirect URIs based on configuration
                                AB_REDIRECT_URIS='["http://localhost:5006/*","http://localhost:5006/callback"'

                                if [ -n "$KC_PUBLIC_DOMAIN" ]; then
                                    AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://budget.'$KC_PUBLIC_DOMAIN'/*","https://budget.'$KC_PUBLIC_DOMAIN'/callback"'
                                    AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"https://'$KC_PUBLIC_DOMAIN':5006/*","https://'$KC_PUBLIC_DOMAIN':5006/callback"'
                                fi

                                if [ -n "$KC_EXTERNAL_SERVICE" ]; then
                                    AB_REDIRECT_URIS="$AB_REDIRECT_URIS"',"'$KC_EXTERNAL_SERVICE'/*","'$KC_EXTERNAL_SERVICE'/callback"'
                                fi

                                AB_REDIRECT_URIS="$AB_REDIRECT_URIS"']'

                                # Build web origins
                                AB_WEB_ORIGINS='["http://localhost:5006"'

                                if [ -n "$KC_PUBLIC_DOMAIN" ]; then
                                    AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"https://budget.'$KC_PUBLIC_DOMAIN'","https://'$KC_PUBLIC_DOMAIN':5006"'
                                fi

                                if [ -n "$KC_EXTERNAL_SERVICE" ]; then
                                    AB_WEB_ORIGINS="$AB_WEB_ORIGINS"',"'$KC_EXTERNAL_SERVICE'"'
                                fi

                                AB_WEB_ORIGINS="$AB_WEB_ORIGINS"']'

                                docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \
                                    -s clientId=actualbudget \
                                    -s name="ActualBudget" \
                                    -s description="Personal Finance Management" \
                                    -s enabled=true \
                                    -s clientAuthenticatorType=client-secret \
                                    -s secret="$AB_CLIENT_SECRET" \
                                    -s publicClient=false \
                                    -s standardFlowEnabled=true \
                                    -s directAccessGrantsEnabled=true \
                                    -s serviceAccountsEnabled=false \
                                    -s "redirectUris=$AB_REDIRECT_URIS" \
                                    -s "webOrigins=$AB_WEB_ORIGINS" \
                                    -s protocol=openid-connect > /dev/null 2>&1

                                if [ $? -eq 0 ]; then
                                    echo "  ✓ Created ActualBudget client"
                                    echo "      Client ID: actualbudget"
                                    echo "      Client Secret: $AB_CLIENT_SECRET"
                                    echo ""

                                    # Save to file with appropriate URLs
                                    KC_AUTH_URL="http://localhost:8180"
                                    if [ -n "$KC_PUBLIC_DOMAIN" ]; then
                                        KC_AUTH_URL="https://auth.$KC_PUBLIC_DOMAIN"
                                    fi

                                    cat > "$KC_DIR/actualbudget-oauth.txt" << EOF
ActualBudget OAuth2 Configuration
==================================

Client ID: actualbudget
Client Secret: $AB_CLIENT_SECRET

LOCAL DEVELOPMENT:
Authorization URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth
Token URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token
User Info URL: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo
EOF

                                    if [ -n "$KC_PUBLIC_DOMAIN" ]; then
                                        cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF

PRODUCTION (with Caddy at https://auth.$KC_PUBLIC_DOMAIN):
Authorization URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/auth
Token URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/token
User Info URL: https://auth.$KC_PUBLIC_DOMAIN/realms/$KC_REALM/protocol/openid-connect/userinfo
EOF
                                    fi

                                    if [ -n "$KC_EXTERNAL_SERVICE" ]; then
                                        cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF

EXTERNAL SERVICE ($KC_EXTERNAL_SERVICE):
- Use PRODUCTION URLs above
- Keycloak MUST be accessible at: https://auth.$KC_PUBLIC_DOMAIN
- Redirect URI configured: $KC_EXTERNAL_SERVICE/*
EOF
                                    fi

                                    cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF

Redirect URIs configured:
- http://localhost:5006/* (local)
EOF

                                    if [ -n "$KC_PUBLIC_DOMAIN" ]; then
                                        cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
- https://budget.$KC_PUBLIC_DOMAIN/* (self-hosted)
EOF
                                    fi

                                    if [ -n "$KC_EXTERNAL_SERVICE" ]; then
                                        cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF
- $KC_EXTERNAL_SERVICE/* (external)
EOF
                                    fi

                                    cat >> "$KC_DIR/actualbudget-oauth.txt" << EOF

To configure ActualBudget:
1. Go to ActualBudget settings
2. Enable OpenID/OAuth authentication
3. Enter the Client ID and Secret above
4. Use the URLs above based on your setup
EOF
                                    echo "  ✓ Saved OAuth config to $KC_DIR/actualbudget-oauth.txt"
                                fi
                            fi

                            # Create a generic OAuth2 client template for other services
                            echo "  Creating generic OAuth2 client for other services..."
                            GENERIC_CLIENT_SECRET=$(openssl rand -hex 32)

                            docker exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r "$KC_REALM" \
                                -s clientId=generic-app \
                                -s name="Generic Application" \
                                -s description="Template client for other services" \
                                -s enabled=true \
                                -s clientAuthenticatorType=client-secret \
                                -s secret="$GENERIC_CLIENT_SECRET" \
                                -s publicClient=false \
                                -s standardFlowEnabled=true \
                                -s directAccessGrantsEnabled=true \
                                -s 'redirectUris=["http://localhost:*/*","https://'$KC_DOMAIN'/*","https://*.'$KC_DOMAIN'/*"]' \
                                -s 'webOrigins=["*"]' \
                                -s protocol=openid-connect > /dev/null 2>&1

                            if [ $? -eq 0 ]; then
                                echo "  ✓ Created generic OAuth2 client template"
                                cat > "$KC_DIR/generic-oauth.txt" << EOF
Generic OAuth2 Client Configuration
====================================

Client ID: generic-app
Client Secret: $GENERIC_CLIENT_SECRET

Use this as a template for other services. You can clone this client
in the Keycloak admin console and modify the redirect URIs.

Base URLs:
- Authorization: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/auth
- Token: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/token
- User Info: http://localhost:8180/realms/$KC_REALM/protocol/openid-connect/userinfo

For production: Replace localhost:8180 with https://auth.$KC_DOMAIN
EOF
                                echo "  ✓ Saved config to $KC_DIR/generic-oauth.txt"
                            fi

                            # Optionally create initial user
                            echo ""
                            prompt_yn "Create an initial user in realm '$KC_REALM'? (y/n):" "y" CREATE_USER

                            if [ "$CREATE_USER" = "y" ] || [ "$CREATE_USER" = "Y" ]; then
                                prompt_text "  Username:" "$ACTUAL_USER" KC_USERNAME
                                prompt_text "  Email:" "${KC_USERNAME}@${KC_DOMAIN}" KC_EMAIL
                                prompt_text "  First name:" "" KC_FIRSTNAME
                                prompt_text "  Last name:" "" KC_LASTNAME

                                echo "  Password for $KC_USERNAME:"
                                read -s KC_USER_PASS
                                echo ""

                                docker exec keycloak /opt/keycloak/bin/kcadm.sh create users -r "$KC_REALM" \
                                    -s username="$KC_USERNAME" \
                                    -s email="$KC_EMAIL" \
                                    -s firstName="$KC_FIRSTNAME" \
                                    -s lastName="$KC_LASTNAME" \
                                    -s enabled=true \
                                    -s emailVerified=true > /dev/null 2>&1

                                if [ $? -eq 0 ]; then
                                    # Set password
                                    KC_USER_ID=$(docker exec keycloak /opt/keycloak/bin/kcadm.sh get users -r "$KC_REALM" -q username="$KC_USERNAME" 2>/dev/null | grep -o '"id" : "[^"]*"' | cut -d'"' -f4)

                                    docker exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r "$KC_REALM" \
                                        --username "$KC_USERNAME" \
                                        --new-password "$KC_USER_PASS" > /dev/null 2>&1

                                    echo "  ✓ Created user: $KC_USERNAME"
                                    echo "  ✓ Password set"
                                    echo ""
                                    echo "  This user can now log in to ActualBudget and other services!"
                                fi
                            fi

                            echo ""
                            echo "  ✓ Keycloak configuration complete!"
                            echo ""
                            echo "  Next steps:"
                            echo "    1. Go to http://localhost:8180/admin"
                            echo "    2. Login with admin / $KC_ADMIN_PASS"
                            echo "    3. Switch to realm '$KC_REALM' (top-left dropdown)"
                            echo "    4. Manage users in Users menu"
                            echo "    5. OAuth configs saved to $KC_DIR/*.txt"
                            echo ""

                        else
                            echo "  ⚠ Failed to login to Keycloak admin CLI"
                            echo "  You can configure Keycloak manually via the web UI"
                        fi
                    else
                        echo "  ⚠ Keycloak did not become ready in time"
                        echo "  You can configure it manually after it starts"
                    fi
                fi
            fi

            echo ""
            echo "  Admin console:  http://localhost:8180/admin"
            echo "  Username:       admin"
            echo "  Password:       $KC_ADMIN_PASS"
            echo "  Database:       PostgreSQL (./postgres-data)"
            if [ -n "$KC_REALM" ]; then
                echo "  Realm:          $KC_REALM"
                echo "  Config files:   $KC_DIR/*.txt"
            fi
            echo ""
            echo "  ⚠  For production:"
            echo "     - Use HTTPS via reverse proxy (Caddy)"
            echo "     - Change command to 'start' instead of 'start-dev'"
            echo "     - Set KC_HOSTNAME to your domain"
            echo ""
        fi
    fi

    # ---- CADDY WEB SERVER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_CADDY" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ CADDY - Modern Web Server & Reverse Proxy                      │"
        echo "│ Automatic HTTPS, reverse proxy for all your services           │"
        echo "│ Port: 80 (HTTP), 443 (HTTPS)                                   │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Caddy reverse proxy? (y/n):" "n" INSTALL_CADDY
    fi

    if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ]; then
        CADDY_DIR="$DOCKER_DIR/caddy"

        # Check if Caddy is already installed
        if [ -f "$CADDY_DIR/Caddyfile" ] || [ -f "$CADDY_DIR/docker-compose.yml" ]; then
            echo ""
            echo "⚠  Caddy appears to be already installed at $CADDY_DIR"
            prompt_yn "Do you want to reconfigure it? (y/n):" "n" RECONFIGURE_CADDY
            if [ "$RECONFIGURE_CADDY" != "y" ] && [ "$RECONFIGURE_CADDY" != "Y" ]; then
                echo "  Skipping Caddy installation"
                INSTALL_CADDY="n"
            fi
        fi

        if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ]; then
            if [ "$DRY_RUN" = true ]; then
                echo "[DRY-RUN] Would create $CADDY_DIR"
            else
                echo "Installing Caddy..."
                mkdir -p "$CADDY_DIR/data" "$CADDY_DIR/config"
                ensure_docker_dir_ownership "$CADDY_DIR"

                # Backup existing Caddyfile if it exists
                if [ -f "$CADDY_DIR/Caddyfile" ]; then
                    mkdir -p "$CADDY_DIR/backups"
                    BACKUP_FILE="$CADDY_DIR/backups/Caddyfile.backup.$(date +%Y%m%d_%H%M%S)"
                    cp "$CADDY_DIR/Caddyfile" "$BACKUP_FILE"
                    echo "  ✓ Backed up existing Caddyfile to: $BACKUP_FILE"
                fi

                cd "$CADDY_DIR"

                cat > docker-compose.yml << 'CADDY_COMPOSE'
name: caddy

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"  # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./data:/data
      - ./config:/config
      - /var/log/caddy:/var/log/caddy
    environment:
      - ACME_AGREE=true
    labels:
      - "io.podman.annotations.label/fail2ban.enable=true"
CADDY_COMPOSE

                # Create Caddyfile if it doesn't exist
                if [ ! -f "Caddyfile" ]; then
                    cat > Caddyfile << 'CADDYFILE'
{
    # Global options
    admin off
    # Email for Let's Encrypt notifications
    # email admin@yourdomain.com
}

# Example configuration - edit this for your services
# Uncomment and modify these examples:

# ActualBudget
# budget.yourdomain.com {
#     log {
#         output file /var/log/caddy/actualbudget-access.log
#         format json
#         level INFO
#     }
#     reverse_proxy localhost:5006
#     header {
#         Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
#         X-Frame-Options "SAMEORIGIN"
#         X-Content-Type-Options "nosniff"
#         X-XSS-Protection "1; mode=block"
#         Referrer-Policy "strict-origin-when-cross-origin"
#     }
# }

# Keycloak
# auth.yourdomain.com {
#     log {
#         output file /var/log/caddy/keycloak-access.log
#         format json
#         level INFO
#     }
#     reverse_proxy localhost:8180
#     header {
#         Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
#         X-Frame-Options "SAMEORIGIN"
#         X-Content-Type-Options "nosniff"
#         X-XSS-Protection "1; mode=block"
#         Referrer-Policy "strict-origin-when-cross-origin"
#     }
# }

# Add more services here...
CADDYFILE
                    echo "  ✓ Created example Caddyfile"
                else
                    echo "  ℹ Using existing Caddyfile"
                fi

                echo "  ✓ Caddy configured at $CADDY_DIR"

                prompt_yn "Start Caddy now? (y/n):" "y" START_CADDY
                if [ "$START_CADDY" = "y" ] || [ "$START_CADDY" = "Y" ]; then
                    docker compose up -d 2>/dev/null && echo "  ✓ Caddy started" || echo "  ⚠ Failed to start Caddy"
                fi

                echo ""
                echo "  Configuration file: $CADDY_DIR/Caddyfile"
                echo "  Edit Caddyfile to add your domains and services"
                echo "  Reload config:      cd $CADDY_DIR && docker exec -w /etc/caddy caddy caddy reload"
                echo ""
                echo "  ⚠  IMPORTANT: Edit the Caddyfile to configure your domains!"
                echo "     - Uncomment and modify the example configurations"
                echo "     - Add your domain names"
                echo "     - Configure services you want to expose"
                echo ""
            fi
        fi
    fi

    # ---- AUTHELIA ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_AUTHELIA" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ AUTHELIA - SSO & Two-Factor Authentication Portal              │"
        echo "│ Protects any Caddy subdomain with a single login + TOTP       │"
        echo "│ Port: 9091 (internal only, accessed via Caddy)                 │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Authelia? (y/n):" "n" INSTALL_AUTHELIA
    fi

    if [ "$INSTALL_AUTHELIA" = "y" ] || [ "$INSTALL_AUTHELIA" = "Y" ]; then
        AUTHELIA_DIR="$DOCKER_DIR/authelia"
        check_service_exists "Authelia" "$AUTHELIA_DIR" AUTHELIA_RECONFIGURE

        if [ "$AUTHELIA_RECONFIGURE" = "true" ]; then
        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $AUTHELIA_DIR"
        else
            echo "Installing Authelia..."
            mkdir -p "$AUTHELIA_DIR/config/secrets" "$AUTHELIA_DIR/data"

            # Collect configuration from user
            echo ""
            echo "  Authelia needs a few details to configure."
            echo ""
            prompt_text "  Your domain (e.g., example.com):" "example.com" AUTHELIA_DOMAIN
            prompt_text "  Admin username:" "admin" AUTHELIA_ADMIN_USER
            prompt_text "  Admin display name:" "Administrator" AUTHELIA_ADMIN_DISPLAY
            prompt_text "  Admin email:" "admin@${AUTHELIA_DOMAIN}" AUTHELIA_ADMIN_EMAIL
            prompt_text "  SMTP server (e.g., smtp.migadu.com):" "smtp.migadu.com" AUTHELIA_SMTP_HOST
            prompt_text "  SMTP port:" "587" AUTHELIA_SMTP_PORT
            prompt_text "  SMTP username (full email):" "authelia@${AUTHELIA_DOMAIN}" AUTHELIA_SMTP_USER
            prompt_text "  SMTP password:" "" AUTHELIA_SMTP_PASS
            prompt_text "  Timezone (e.g., America/New_York):" "America/New_York" AUTHELIA_TZ

            # Generate secrets
            echo ""
            echo "  Generating secrets..."
            JWT_SECRET=$(openssl rand -hex 32)
            SESSION_SECRET=$(openssl rand -hex 32)
            STORAGE_SECRET=$(openssl rand -hex 32)

            echo "$JWT_SECRET"     > "$AUTHELIA_DIR/config/secrets/jwt_secret"
            echo "$SESSION_SECRET" > "$AUTHELIA_DIR/config/secrets/session_secret"
            echo "$STORAGE_SECRET" > "$AUTHELIA_DIR/config/secrets/storage_secret"
            echo "$AUTHELIA_SMTP_PASS" > "$AUTHELIA_DIR/config/secrets/smtp_password"
            chmod 600 "$AUTHELIA_DIR/config/secrets/"*
            echo "  ✓ Secrets generated"

            # Generate password hash for admin user
            echo ""
            echo "  Generating password hash for admin user..."
            prompt_text "  Temporary password for admin (users reset via email):" "TempPass2026!" AUTHELIA_TEMP_PASS
            AUTHELIA_HASH=$(docker run --rm authelia/authelia:4.39.20 \
                authelia crypto hash generate argon2 --password "$AUTHELIA_TEMP_PASS" 2>/dev/null \
                | grep -oP '(?<=Digest: ).*' || echo "REPLACE_WITH_HASH")
            if [ "$AUTHELIA_HASH" = "REPLACE_WITH_HASH" ]; then
                echo "  ⚠ Could not generate hash automatically. Run this after install:"
                echo "    docker run --rm authelia/authelia:4.39.20 authelia crypto hash generate argon2 --password 'yourpassword'"
                echo "    Then update $AUTHELIA_DIR/config/users.yml"
            else
                echo "  ✓ Password hash generated"
            fi

            ensure_docker_dir_ownership "$AUTHELIA_DIR"
            # Authelia requires its config/data owned by uid 1000
            chown -R 1000:1000 "$AUTHELIA_DIR/config" "$AUTHELIA_DIR/data"

            cd "$AUTHELIA_DIR"

            # .env file
            cat > .env << AUTHELIA_ENV
MY_DOMAIN=${AUTHELIA_DOMAIN}
SMTP_USER=${AUTHELIA_SMTP_USER}
DOCKER_MY_NETWORK=caddy_net
TZ=${AUTHELIA_TZ}
AUTHELIA_ENV

            # docker-compose.yml
            cat > docker-compose.yml << 'AUTHELIA_COMPOSE'
name: authelia

services:
  authelia:
    image: authelia/authelia:4.39.20
    pull_policy: missing
    container_name: authelia
    user: "1000:1000"
    volumes:
      - ./config:/config
      - ./data:/data
    environment:
      - AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/config/secrets/jwt_secret
      - AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session_secret
      - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage_secret
      - AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/config/secrets/smtp_password
      - AUTHELIA_NOTIFIER_SMTP_USERNAME=${SMTP_USER}
      - AUTHELIA_NOTIFIER_SMTP_SENDER=Authelia <${SMTP_USER}>
    expose:
      - 9091
    restart: unless-stopped
    networks:
      - caddy_net

networks:
  caddy_net:
    external: true
AUTHELIA_COMPOSE

            # configuration.yml
            cat > config/configuration.yml << AUTHELIA_CONFIG
---
# Authelia configuration
# Secrets are injected via AUTHELIA_* environment variables in docker-compose.yml

theme: dark

server:
  address: tcp://0.0.0.0:9091

log:
  level: info
  file_path: /data/authelia.log

totp:
  period: 30
  skew: 1

authentication_backend:
  file:
    path: /config/users.yml
    password:
      algorithm: argon2
      argon2:
        variant: argon2id
        iterations: 3
        memory: 65536
        parallelism: 4
        key_length: 32
        salt_length: 16

access_control:
  default_policy: deny
  rules:
    - domain: "*.${AUTHELIA_DOMAIN}"
      policy: two_factor

session:
  name: authelia_session
  expiration: 12h
  inactivity: 2h
  remember_me: 7d
  cookies:
    - domain: ${AUTHELIA_DOMAIN}
      authelia_url: https://auth.${AUTHELIA_DOMAIN}
      default_redirection_url: https://${AUTHELIA_DOMAIN}

storage:
  local:
    path: /data/db.sqlite3

notifier:
  disable_startup_check: false
  smtp:
    address: smtp://${AUTHELIA_SMTP_HOST}:${AUTHELIA_SMTP_PORT}
    timeout: 10s
    identifier: localhost
    subject: "[Authelia] {title}"
    startup_check_address: ${AUTHELIA_SMTP_USER}
    disable_require_tls: false
    disable_starttls: false
AUTHELIA_CONFIG

            # users.yml
            cat > config/users.yml << AUTHELIA_USERS
---
# Authelia users database
# To add users: copy a block, update username/email/displayname, restart authelia
# To generate a hash: docker run --rm authelia/authelia:4.39.20 authelia crypto hash generate argon2 --password 'thepassword'
# Login with username (not email). Tell users to use "Forgot Password" to set their own password.

users:
  ${AUTHELIA_ADMIN_USER}:
    displayname: "${AUTHELIA_ADMIN_DISPLAY}"
    email: ${AUTHELIA_ADMIN_EMAIL}
    password: "${AUTHELIA_HASH}"
    groups:
      - admins
      - users
AUTHELIA_USERS

            chown -R 1000:1000 "$AUTHELIA_DIR/config" "$AUTHELIA_DIR/data"

            echo "  ✓ Authelia configured at $AUTHELIA_DIR"
            echo ""

            # Ensure caddy_net Docker network exists
            if ! docker network ls --format '{{.Name}}' | grep -q "^caddy_net$"; then
                echo "  Creating Docker network caddy_net..."
                docker network create caddy_net && echo "  ✓ caddy_net created" || echo "  ⚠ Failed to create caddy_net"
            else
                echo "  ✓ Docker network caddy_net already exists"
            fi

            # Inject Authelia snippet into Caddyfile if Caddy is installed
            CADDY_FILE="$DOCKER_DIR/caddy/Caddyfile"
            if [ -f "$CADDY_FILE" ]; then
                echo ""
                echo "  Configuring Caddy for Authelia..."

                # Add (authelia) snippet at top if not already present
                if ! grep -q "(authelia)" "$CADDY_FILE"; then
                    cp "$CADDY_FILE" "$CADDY_FILE.backup.$(date +%Y%m%d-%H%M%S)"
                    AUTHELIA_SNIPPET=$(cat << 'SNIPPET_EOF'
# ── Authelia forward auth snippet ─────────────────────────────────────────────
(authelia) {
    forward_auth authelia:9091 {
        uri /api/authz/forward-auth
        copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
    }
}

SNIPPET_EOF
)
                    # Prepend snippet before existing content
                    echo "$AUTHELIA_SNIPPET" | cat - "$CADDY_FILE" > "$CADDY_FILE.tmp" && mv "$CADDY_FILE.tmp" "$CADDY_FILE"
                    echo "  ✓ Authelia snippet added to Caddyfile"
                else
                    echo "  ✓ Authelia snippet already in Caddyfile"
                fi

                # Add auth portal site block if not already present
                AUTH_DOMAIN="auth.${AUTHELIA_DOMAIN}"
                if ! grep -q "$AUTH_DOMAIN" "$CADDY_FILE"; then
                    cat >> "$CADDY_FILE" << CADDY_AUTH_BLOCK

# ── Authelia login portal ──────────────────────────────────────────────────────
${AUTH_DOMAIN} {
    reverse_proxy authelia:9091
    log {
        output file /var/log/caddy/auth.log
    }
}
CADDY_AUTH_BLOCK
                    echo "  ✓ Authelia portal block added for ${AUTH_DOMAIN}"
                fi

                # Reload Caddy if it's running
                if docker ps --format '{{.Names}}' | grep -q "^caddy$"; then
                    docker exec -w /etc/caddy caddy caddy reload 2>/dev/null \
                        && echo "  ✓ Caddy reloaded" \
                        || echo "  ⚠ Caddy reload failed — reload manually after fixing Caddyfile"
                fi
            else
                echo "  ℹ Caddy not yet installed. Install Caddy and add this to your Caddyfile:"
                echo ""
                echo '  (authelia) {'
                echo '      forward_auth authelia:9091 {'
                echo '          uri /api/authz/forward-auth'
                echo '          copy_headers Remote-User Remote-Groups Remote-Name Remote-Email'
                echo '      }'
                echo '  }'
                echo ""
                echo "  auth.${AUTHELIA_DOMAIN} {"
                echo "      reverse_proxy authelia:9091"
                echo "  }"
                echo ""
            fi

            prompt_yn "Start Authelia now? (y/n):" "y" START_AUTHELIA
            if [ "$START_AUTHELIA" = "y" ] || [ "$START_AUTHELIA" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Authelia started" || echo "  ⚠ Failed to start Authelia"
                sleep 2
                docker compose logs --tail=20 authelia 2>/dev/null || true
            fi

            echo ""
            echo "  Auth portal:  https://auth.${AUTHELIA_DOMAIN}"
            echo "  Config dir:   $AUTHELIA_DIR/config"
            echo "  Users file:   $AUTHELIA_DIR/config/users.yml"
            echo "  Data dir:     $AUTHELIA_DIR/data"
            echo ""
            echo "  Admin login:  ${AUTHELIA_ADMIN_USER}  (use Forgot Password to set real password)"
            echo ""
            echo "  To protect a Caddy site, add 'import authelia' to its block:"
            echo "    myservice.${AUTHELIA_DOMAIN} {"
            echo "        import authelia"
            echo "        reverse_proxy localhost:PORT"
            echo "    }"
            echo ""
            echo "  To add users:"
            echo "    Edit $AUTHELIA_DIR/config/users.yml then:"
            echo "    cd $AUTHELIA_DIR && docker compose restart authelia"
            echo ""
        fi
        fi  # End AUTHELIA_RECONFIGURE check
    fi  # End INSTALL_AUTHELIA check

    # ---- FAIL2BAN ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_FAIL2BAN" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ FAIL2BAN - Intrusion Prevention System                         │"
        echo "│ Automatically ban IPs with failed auth attempts                │"
        echo "│ Protects SSH, Caddy, and other services                        │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install and configure fail2ban? (y/n):" "n" INSTALL_FAIL2BAN
    fi

    if [ "$INSTALL_FAIL2BAN" = "y" ] || [ "$INSTALL_FAIL2BAN" = "Y" ]; then
        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would install fail2ban"
        else
            echo "Installing fail2ban..."

            # Check if fail2ban is already installed
            if command -v fail2ban-client &> /dev/null; then
                echo "  ✓ fail2ban is already installed"
            else
                echo "  Installing fail2ban package..."
                if sudo apt update && sudo apt install -y fail2ban; then
                    echo "  ✓ fail2ban installed successfully"
                else
                    echo "  ⚠ Failed to install fail2ban"
                    echo "  You may need to install it manually: sudo apt install fail2ban"
                fi
            fi

            # Create log directory for Caddy
            if [ ! -d "/var/log/caddy" ]; then
                sudo mkdir -p /var/log/caddy
                sudo chmod 755 /var/log/caddy
                echo "  ✓ Created /var/log/caddy directory"
            fi

            # Check if Caddy filter exists
            FILTER_FILE="/etc/fail2ban/filter.d/caddy-auth.conf"
            if [ ! -f "$FILTER_FILE" ]; then
                echo "  Creating fail2ban filter for Caddy..."

                FILTER_CONTENT='[Definition]
failregex = ^.*"remote_ip":"<HOST>".*"status":(?:401|403|429).*$
            ^.*"remote_addr":"<HOST>.*"status":(?:401|403|429).*$
ignoreregex = ^.*"remote_ip":"(?:127\.0\.0\.1|::1)".*$
datepattern = "ts":%%s'

                if echo "$FILTER_CONTENT" | sudo tee "$FILTER_FILE" > /dev/null; then
                    echo "  ✓ Created Caddy fail2ban filter"
                else
                    echo "  ⚠ Failed to create filter - you may need to create it manually"
                fi
            else
                echo "  ✓ Caddy fail2ban filter already exists"
            fi

            # Check if Caddy jail exists
            JAIL_FILE="/etc/fail2ban/jail.d/caddy.conf"
            if [ ! -f "$JAIL_FILE" ]; then
                echo "  Creating fail2ban jail for Caddy..."
                echo ""
                echo "  Configure fail2ban settings (press Enter for defaults):"

                prompt_text "  Max retries before ban:" "5" F2B_MAXRETRY
                prompt_text "  Find time window (seconds):" "600" F2B_FINDTIME
                prompt_text "  Ban duration (seconds):" "3600" F2B_BANTIME

                JAIL_CONTENT="[caddy-auth]
enabled = true
port = http,https
filter = caddy-auth
logpath = /var/log/caddy/access.log
          /var/log/caddy/*-access.log
maxretry = $F2B_MAXRETRY
findtime = $F2B_FINDTIME
bantime = $F2B_BANTIME
action = iptables-multiport[name=CaddyAuth, port=\"http,https\", protocol=tcp]
backend = auto"

                if echo "$JAIL_CONTENT" | sudo tee "$JAIL_FILE" > /dev/null; then
                    echo "  ✓ Created Caddy fail2ban jail"
                else
                    echo "  ⚠ Failed to create jail - you may need to create it manually"
                fi
            else
                echo "  ✓ Caddy fail2ban jail already exists"
            fi

            # Test fail2ban configuration
            echo ""
            echo "  Testing fail2ban configuration..."
            if sudo fail2ban-client -t &> /dev/null; then
                echo "  ✓ fail2ban configuration is valid"
            else
                echo "  ⚠ fail2ban configuration has errors"
                echo "  Check with: sudo fail2ban-client -t"
            fi

            # Restart fail2ban
            prompt_yn "Restart fail2ban to apply changes? (y/n):" "y" RESTART_F2B
            if [ "$RESTART_F2B" = "y" ] || [ "$RESTART_F2B" = "Y" ]; then
                if sudo systemctl restart fail2ban; then
                    echo "  ✓ fail2ban restarted successfully"

                    # Wait for fail2ban to start
                    sleep 2

                    # Check jail status
                    if sudo fail2ban-client status caddy-auth &> /dev/null; then
                        echo "  ✓ caddy-auth jail is active"
                        echo ""
                        sudo fail2ban-client status caddy-auth
                    else
                        echo "  ⚠ caddy-auth jail is not active (may need Caddy logs to exist first)"
                    fi
                else
                    echo "  ⚠ Failed to restart fail2ban"
                    echo "  Check logs: sudo journalctl -u fail2ban -n 50"
                fi
            fi

            echo ""
            echo "  Useful commands:"
            echo "    Check jail status:  sudo fail2ban-client status caddy-auth"
            echo "    View banned IPs:    sudo fail2ban-client get caddy-auth banip"
            echo "    Unban IP:           sudo fail2ban-client set caddy-auth unbanip 1.2.3.4"
            echo "    View logs:          sudo tail -f /var/log/fail2ban.log"
            echo ""
        fi
    fi

    # ---- LYRION MUSIC SERVER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_LMS" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ LYRION MUSIC SERVER (LMS) - Music streaming server              │"
        echo "│ Stream music to Squeezebox devices, apps, and Chromecast.       │"
        echo "│ Port: 9000 (web), 9090 (CLI), 3483 (players)                    │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Lyrion Music Server? (y/n):" "n" INSTALL_LMS
    fi

    if [ "$INSTALL_LMS" = "y" ] || [ "$INSTALL_LMS" = "Y" ]; then
        echo "Installing Lyrion Music Server..."
        LMS_DIR="$DOCKER_DIR/lyrion"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $LMS_DIR"
        else
            mkdir -p "$LMS_DIR"
            ensure_docker_dir_ownership "$LMS_DIR"
            cd "$LMS_DIR"

            prompt_text "Path to music folder [default: $PRIMARY_DRIVE_PATH/music]:" "$PRIMARY_DRIVE_PATH/music" MUSIC_PATH

            cat > docker-compose.yml << LMS_COMPOSE
name: lyrion

services:
  lyrion:
    image: lmscommunity/lyrionmusicserver:stable
    container_name: lyrion
    hostname: lyrion
    restart: unless-stopped
    network_mode: host
    environment:
      - HTTP_PORT=9000
      - PUID=$(id -u "$ACTUAL_USER")
      - PGID=$(id -g "$ACTUAL_USER")
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    volumes:
      - ./config:/config:rw
      - ${MUSIC_PATH}:/music:ro
      - ./playlists:/playlists:rw
      - /etc/localtime:/etc/localtime:ro
LMS_COMPOSE

            cat > .env << LMS_ENV
MUSIC_PATH=$MUSIC_PATH
LMS_ENV

            mkdir -p config playlists
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$LMS_DIR"

            echo ""
            echo "✓ Lyrion Music Server configured at $LMS_DIR"

            prompt_yn "Start Lyrion Music Server now? (y/n):" "y" START_LMS
            if [ "$START_LMS" = "y" ] || [ "$START_LMS" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Lyrion Music Server started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:9000"
            echo "  Note: Uses host networking for Chromecast support"
            echo ""
        fi
    fi

    # ---- MEALIE ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_MEALIE" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ MEALIE - Recipe manager & meal planner                          │"
        echo "│ Save recipes, plan meals, generate shopping lists.              │"
        echo "│ Port: 9925                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Mealie? (y/n):" "n" INSTALL_MEALIE
    fi

    if [ "$INSTALL_MEALIE" = "y" ] || [ "$INSTALL_MEALIE" = "Y" ]; then
        echo "Installing Mealie..."
        MEALIE_DIR="$DOCKER_DIR/mealie"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $MEALIE_DIR"
        else
            mkdir -p "$MEALIE_DIR"
            ensure_docker_dir_ownership "$MEALIE_DIR"
            cd "$MEALIE_DIR"

            cat > docker-compose.yml << MEALIE_COMPOSE
name: mealie

services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:latest
    container_name: mealie
    hostname: mealie
    restart: unless-stopped
    environment:
      - PUID=$(id -u "$ACTUAL_USER")
      - PGID=$(id -g "$ACTUAL_USER")
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
      - ALLOW_SIGNUP=true
      - MAX_WORKERS=1
      - WEB_CONCURRENCY=1
      - BASE_URL=http://localhost:9925
    volumes:
      - ./data:/app/data
    ports:
      - "9925:9000"
MEALIE_COMPOSE

            mkdir -p data
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MEALIE_DIR"

            echo ""
            echo "✓ Mealie configured at $MEALIE_DIR"

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "Mealie" "9925" "recipes"

            prompt_yn "Start Mealie now? (y/n):" "y" START_MEALIE
            if [ "$START_MEALIE" = "y" ] || [ "$START_MEALIE" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Mealie started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:9925"
            echo "  Default:    changeme@email.com / MyPassword"
            echo ""
        fi
    fi

    # ---- MINECRAFT SERVER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_MINECRAFT" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ MINECRAFT SERVER - Game server with RAM limit                   │"
        echo "│ Fabric server with configurable memory allocation.              │"
        echo "│ Port: 25565                                                     │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Minecraft Server? (y/n):" "n" INSTALL_MINECRAFT
    fi

    if [ "$INSTALL_MINECRAFT" = "y" ] || [ "$INSTALL_MINECRAFT" = "Y" ]; then
        echo "Installing Minecraft Server..."
        MC_DIR="$DOCKER_DIR/minecraft"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $MC_DIR"
        else
            mkdir -p "$MC_DIR"
            ensure_docker_dir_ownership "$MC_DIR"
            cd "$MC_DIR"

            echo ""
            prompt_text "Maximum RAM for Minecraft (e.g., 2G, 4G) [default: 2G]:" "2G" MC_RAM
            MC_RAM=${MC_RAM:-2G}

            cat > docker-compose.yml << MC_COMPOSE
name: minecraft

services:
  minecraft:
    image: itzg/minecraft-server:latest
    container_name: minecraft
    hostname: minecraft
    restart: unless-stopped
    tty: true
    stdin_open: true
    environment:
      - EULA=TRUE
      - TYPE=FABRIC
      - VERSION=LATEST
      - MEMORY=${MC_RAM}
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
      - OPS=
      - MOTD=A Minecraft Server
      - DIFFICULTY=normal
      - MODE=survival
    volumes:
      - ./data:/data
    ports:
      - "25565:25565"
    deploy:
      resources:
        limits:
          memory: ${MC_RAM}
MC_COMPOSE

            cat > .env << MC_ENV
MC_RAM=$MC_RAM
MC_ENV

            mkdir -p data
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MC_DIR"

            echo ""
            echo "✓ Minecraft Server configured at $MC_DIR"

            prompt_yn "Start Minecraft Server now? (y/n):" "y" START_MC
            if [ "$START_MC" = "y" ] || [ "$START_MC" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Minecraft Server started" || echo "  ⚠ Failed to start"
            fi

            echo "  Connect:    localhost:25565"
            echo "  RAM limit:  $MC_RAM"
            echo "  Console:    docker attach minecraft (Ctrl+P, Ctrl+Q to detach)"
            echo ""
        fi
    fi

    # ---- LINUX-TO-SYNC (Private Repo) ----
    if [ "$WHIPTAIL_USED" != true ]; then
    echo ""
    echo "┌─────────────────────────────────────────────────────────────────┐"
    echo "│ LINUX-TO-SYNC - Private sync repository                         │"
    echo "│ Clone and set up your private linux-to-sync repository.         │"
    echo "└─────────────────────────────────────────────────────────────────┘"
    echo ""
    echo "Note: This requires access to github.com/outis1one/linux-to-sync"
    echo ""
    echo "To grant access, you need ONE of these:"
    echo "  1. SSH key already added to your GitHub account"
    echo "  2. GitHub Personal Access Token (PAT)"
    echo "  3. GitHub CLI (gh) authenticated"
    echo ""
    prompt_yn "Set up linux-to-sync? (y/n):" "n" INSTALL_LINUXTOSYNC

    if [ "$INSTALL_LINUXTOSYNC" = "y" ] || [ "$INSTALL_LINUXTOSYNC" = "Y" ]; then
        SYNC_DIR="$DOCKER_DIR/linux-to-sync"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would clone linux-to-sync to $SYNC_DIR"
        else
            echo ""
            echo "Choose authentication method:"
            echo "  [1] SSH (if you have SSH key added to GitHub)"
            echo "  [2] HTTPS with token (requires Personal Access Token)"
            echo ""
            prompt_text "Enter 1 or 2 [default: 1]:" "1" AUTH_METHOD

            if [ "$AUTH_METHOD" = "2" ]; then
                echo ""
                echo "Create a Personal Access Token at:"
                echo "  https://github.com/settings/tokens/new"
                echo "  - Select 'repo' scope for full repository access"
                echo ""
                prompt_text "Enter your GitHub Personal Access Token:" "" GH_TOKEN

                if [ -n "$GH_TOKEN" ]; then
                    git clone "https://$GH_TOKEN@github.com/outis1one/linux-to-sync.git" "$SYNC_DIR" 2>/dev/null
                    if [ $? -eq 0 ]; then
                        # Remove token from remote URL for security
                        cd "$SYNC_DIR"
                        git remote set-url origin "https://github.com/outis1one/linux-to-sync.git"
                        chown -R "$ACTUAL_USER:$ACTUAL_USER" "$SYNC_DIR"
                        echo ""
                        echo "✓ linux-to-sync cloned to $SYNC_DIR"
                        echo "  Note: You'll need to enter token again for push/pull"
                        echo "  Or set up: git config credential.helper store"
                    else
                        echo "✗ Clone failed. Check your token and try again."
                    fi
                else
                    echo "No token provided. Skipping."
                fi
            else
                echo ""
                echo "Attempting SSH clone..."
                echo "(Make sure your SSH key is added to GitHub)"
                echo ""
                git clone git@github.com:outis1one/linux-to-sync.git "$SYNC_DIR" 2>/dev/null
                if [ $? -eq 0 ]; then
                    chown -R "$ACTUAL_USER:$ACTUAL_USER" "$SYNC_DIR"
                    echo ""
                    echo "✓ linux-to-sync cloned to $SYNC_DIR"
                else
                    echo ""
                    echo "✗ SSH clone failed."
                    echo ""
                    echo "To add your SSH key to GitHub:"
                    echo "  1. Copy your public key: cat ~/.ssh/id_rsa.pub"
                    echo "  2. Go to: https://github.com/settings/keys"
                    echo "  3. Click 'New SSH key' and paste your key"
                    echo ""
                    echo "Then retry this script or manually clone:"
                    echo "  git clone git@github.com:outis1one/linux-to-sync.git ~/docker/linux-to-sync"
                fi
            fi
        fi
    fi
    fi  # End WHIPTAIL_USED check for linux-to-sync

    # ---- JELLYFIN (Alternative to Emby) ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_JELLYFIN" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ JELLYFIN - Free media server (alternative to Emby)              │"
        echo "│ Stream movies, TV, music. No premium features locked.           │"
        echo "│ Port: 8096                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Jellyfin? (y/n):" "n" INSTALL_JELLYFIN
    fi

    if [ "$INSTALL_JELLYFIN" = "y" ] || [ "$INSTALL_JELLYFIN" = "Y" ]; then
        echo "Installing Jellyfin..."
        JELLYFIN_DIR="$DOCKER_DIR/jellyfin"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $JELLYFIN_DIR"
        else
            mkdir -p "$JELLYFIN_DIR"
            ensure_docker_dir_ownership "$JELLYFIN_DIR"
            cd "$JELLYFIN_DIR"

            prompt_text "Path to media folder [default: $PRIMARY_DRIVE_PATH/media]:" "$PRIMARY_DRIVE_PATH/media" MEDIA_PATH

            # Get render group ID for hardware acceleration
            RENDER_GID=$(getent group render | cut -d: -f3 2>/dev/null || echo "989")

            cat > docker-compose.yml << JELLYFIN_COMPOSE
name: jellyfin

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    hostname: jellyfin
    restart: unless-stopped
    environment:
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    group_add:
      - "$RENDER_GID"
    volumes:
      - ./config:/config
      - ./cache:/cache
      - \${MEDIA_PATH}:/media:ro
    ports:
      - "8096:8096"
      - "1900:1900/udp"
      - "7359:7359/udp"
JELLYFIN_COMPOSE

            cat > .env << JELLYFIN_ENV
MEDIA_PATH=$MEDIA_PATH
JELLYFIN_ENV

            mkdir -p config cache
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$JELLYFIN_DIR"

            echo ""
            echo "✓ Jellyfin configured at $JELLYFIN_DIR"

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "Jellyfin" "8096" "jellyfin"

            prompt_yn "Start Jellyfin now? (y/n):" "y" START_JELLYFIN
            if [ "$START_JELLYFIN" = "y" ] || [ "$START_JELLYFIN" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Jellyfin started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8096"
            echo "  Note: Hardware acceleration enabled (Intel GPU)"
            echo ""
        fi
    fi

    # ---- FRIGATE NVR ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_FRIGATE" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ FRIGATE - AI-powered NVR for security cameras                   │"
        echo "│ Object detection, recordings, 24/7 monitoring.                  │"
        echo "│ Port: 5000 (web), 8554 (RTSP), 8555 (WebRTC)                    │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Frigate? (y/n):" "n" INSTALL_FRIGATE
    fi

    if [ "$INSTALL_FRIGATE" = "y" ] || [ "$INSTALL_FRIGATE" = "Y" ]; then
        echo "Installing Frigate..."
        FRIGATE_DIR="$DOCKER_DIR/frigate"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $FRIGATE_DIR"
        else
            # STEP 1: Create directory and install docker-compose
            mkdir -p "$FRIGATE_DIR" 2>/dev/null || true
            ensure_docker_dir_ownership "$FRIGATE_DIR"
            cd "$FRIGATE_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            # Default path
            FRIGATE_PATH="$ACTUAL_HOME/drives/primary/frigate"

            cat > docker-compose.yml << FRIGATE_COMPOSE
name: frigate

services:
  frigate:
    image: ghcr.io/blakeblackshear/frigate:stable
    container_name: frigate
    hostname: frigate
    restart: unless-stopped
    privileged: true
    shm_size: "256mb"
    environment:
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    volumes:
      - ./config:/config
      - \${FRIGATE_MEDIA}:/media/frigate
      - type: tmpfs
        target: /tmp/cache
        tmpfs:
          size: 1000000000
    ports:
      - "5000:5000"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
FRIGATE_COMPOSE
            echo "✓ Installed docker-compose.yml"

            # STEP 2: Try to configure (uses defaults if fails)
            prompt_text "Path for recordings [$FRIGATE_PATH]:" "$FRIGATE_PATH" FRIGATE_PATH 2>/dev/null || FRIGATE_PATH="$ACTUAL_HOME/drives/primary/frigate"

            cat > .env << FRIGATE_ENV
FRIGATE_MEDIA=$FRIGATE_PATH
FRIGATE_ENV

            mkdir -p config 2>/dev/null || true
            mkdir -p "$FRIGATE_PATH" 2>/dev/null || echo "  ⚠ Could not create $FRIGATE_PATH - create manually"

            # Create template config
            cat > config/config.yml << 'FRIGATE_CONFIG'
# Frigate Configuration
# Docs: https://docs.frigate.video
#
# ⚠️  YOU MUST EDIT THIS FILE to add your cameras!

mqtt:
  enabled: false

cameras:
  # EXAMPLE - Replace with your camera:
  # front_door:
  #   ffmpeg:
  #     inputs:
  #       - path: rtsp://user:pass@192.168.1.100:554/stream
  #         roles: [detect, record]
  #   detect:
  #     width: 1280
  #     height: 720
  #     fps: 5

detectors:
  default:
    type: cpu

record:
  enabled: true
  retain:
    days: 7
    mode: motion

snapshots:
  enabled: true
  retain:
    default: 7
FRIGATE_CONFIG

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FRIGATE_DIR" 2>/dev/null || true

            echo ""
            echo "✓ Frigate installed at $FRIGATE_DIR"
            echo ""
            echo "  ⚠️  Note: You should edit config/config.yml to add cameras before starting."
            prompt_yn "Start Frigate now anyway? (y/n):" "n" START_FRIGATE
            if [ "$START_FRIGATE" = "y" ] || [ "$START_FRIGATE" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Frigate started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access: http://localhost:5000"
            echo "  Docs: https://docs.frigate.video"
            echo ""
        fi
    fi

    # ---- CADDY REVERSE PROXY (Legacy) ----
    # Note: This is the legacy Caddy installation
    # The newer installation above includes fail2ban support
    # This section is kept for backwards compatibility
    if [ "$WHIPTAIL_USED" != true ] && [ "$INSTALL_CADDY" != "y" ] && [ "$INSTALL_CADDY" != "Y" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ CADDY - Automatic HTTPS reverse proxy (Legacy)                  │"
        echo "│ Route domains to containers with automatic SSL certificates.    │"
        echo "│ Ports: 80, 443                                                  │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        if [ -z "$INSTALL_CADDY_LEGACY" ]; then
            prompt_yn "Install Caddy reverse proxy? (y/n):" "n" INSTALL_CADDY_LEGACY
        fi
        INSTALL_CADDY="$INSTALL_CADDY_LEGACY"
    fi

    if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ]; then
        echo "Installing Caddy..."
        CADDY_DIR="$DOCKER_DIR/caddy"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $CADDY_DIR"
        else
            # STEP 1: Create directory and install docker-compose
            mkdir -p "$CADDY_DIR" 2>/dev/null || true
            cd "$CADDY_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            cat > docker-compose.yml << 'CADDY_COMPOSE'
name: caddy

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    hostname: caddy
    restart: unless-stopped
    env_file: .env
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./config:/config
      - ./data:/data
      - ./site:/srv

networks:
  default:
    name: caddy_net
    external: true
CADDY_COMPOSE
            echo "✓ Installed docker-compose.yml"

            # STEP 2: Try configuration (uses defaults if fails)
            CADDY_DOMAIN=""
            prompt_text "Your domain (e.g., example.com) [blank for local]:" "" CADDY_DOMAIN 2>/dev/null || CADDY_DOMAIN=""

            cat > .env << CADDY_ENV
MY_DOMAIN=${CADDY_DOMAIN:-localhost}
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
CADDY_ENV

            # Create Docker network for Caddy (ignore if exists)
            docker network create caddy_net 2>/dev/null || true

            # Create comprehensive Caddyfile with all services
            cat > Caddyfile << 'CADDY_FILE'
# Caddy reverse proxy configuration
# Edit MY_DOMAIN in .env file, then uncomment services below
#
# To use: containers must be on 'caddy_net' network
# Add to each container's docker-compose.yml:
#   networks:
#     default:
#       name: caddy_net
#       external: true

# ============================================================================
# GLOBAL OPTIONS
# ============================================================================
{
    # Uncomment for local-only (no domain/SSL):
    # auto_https off
}

# ============================================================================
# MEDIA SERVERS
# ============================================================================

# Immich (photo backup)
# immich.{$MY_DOMAIN} {
#     reverse_proxy immich_server:2283
# }

# Jellyfin (media server)
# jellyfin.{$MY_DOMAIN} {
#     reverse_proxy jellyfin:8096
# }

# Emby (media server)
# emby.{$MY_DOMAIN} {
#     reverse_proxy emby:8096
# }

# Audiobookshelf
# audiobooks.{$MY_DOMAIN} {
#     reverse_proxy audiobookshelf:80
# }

# Lyrion Music Server
# music.{$MY_DOMAIN} {
#     reverse_proxy lms:9000
# }

# ============================================================================
# HOME AUTOMATION & MONITORING
# ============================================================================

# Frigate NVR
# frigate.{$MY_DOMAIN} {
#     reverse_proxy frigate:5000
# }

# Uptime Kuma
# status.{$MY_DOMAIN} {
#     reverse_proxy uptime-kuma:3001
# }

# Magic Mirror
# mirror.{$MY_DOMAIN} {
#     reverse_proxy magicmirror:8080
# }

# Traccar GPS
# gps.{$MY_DOMAIN} {
#     reverse_proxy traccar:8082
# }

# FindMyDevice
# fmd.{$MY_DOMAIN} {
#     reverse_proxy fmd:8080
# }

# ============================================================================
# UTILITIES
# ============================================================================

# Filebrowser
# files.{$MY_DOMAIN} {
#     reverse_proxy filebrowser:80
# }

# Mealie (recipes)
# recipes.{$MY_DOMAIN} {
#     reverse_proxy mealie:9000
# }

# ntfy (notifications)
# ntfy.{$MY_DOMAIN} {
#     reverse_proxy ntfy:80
# }

# Portainer
# docker.{$MY_DOMAIN} {
#     reverse_proxy portainer:9000
# }

# Kopia backup UI
# backup.{$MY_DOMAIN} {
#     reverse_proxy kopia:51515
# }

# ============================================================================
# TESTING / CATCH-ALL
# ============================================================================

# Local testing (always responds)
:80 {
    respond "Caddy is running! Edit Caddyfile to enable your services."
}
CADDY_FILE

            mkdir -p config data site 2>/dev/null || true
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$CADDY_DIR" 2>/dev/null || true

            echo ""
            echo "✓ Caddy installed at $CADDY_DIR"

            prompt_yn "Start Caddy now? (y/n):" "y" START_CADDY
            if [ "$START_CADDY" = "y" ] || [ "$START_CADDY" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Caddy started" || echo "  ⚠ Failed to start"
            fi

            echo "  Domain: ${CADDY_DOMAIN:-localhost} (edit .env)"
            echo "  Config: $CADDY_DIR/Caddyfile (uncomment services)"
            echo ""
            echo "  ⚠️  Containers must be on 'caddy_net' network"
            echo "  Docs: https://caddyserver.com/docs/"
            echo ""
        fi
    fi

    # ---- DDCLIENT DYNAMIC DNS ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_DDCLIENT" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ DDCLIENT - Dynamic DNS updater                                  │"
        echo "│ Keep your domain pointing to your home IP.                      │"
        echo "│ Supports: Cloudflare, DuckDNS, No-IP, and more.                 │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install ddclient? (y/n):" "n" INSTALL_DDCLIENT
    fi

    if [ "$INSTALL_DDCLIENT" = "y" ] || [ "$INSTALL_DDCLIENT" = "Y" ]; then
        echo "Installing ddclient..."
        DDCLIENT_DIR="$DOCKER_DIR/ddclient"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $DDCLIENT_DIR"
        else
            # STEP 1: Install docker-compose
            mkdir -p "$DDCLIENT_DIR" 2>/dev/null || true
            cd "$DDCLIENT_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            cat > docker-compose.yml << 'DDCLIENT_COMPOSE'
name: ddclient

services:
  ddclient:
    image: lscr.io/linuxserver/ddclient:latest
    container_name: ddclient
    hostname: ddclient
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TZ}
    volumes:
      - ./config:/config
DDCLIENT_COMPOSE
            echo "✓ Installed docker-compose.yml"

            cat > .env << DDCLIENT_ENV
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
DDCLIENT_ENV

            mkdir -p config 2>/dev/null || true

            # Create template config
            cat > config/ddclient.conf << 'DDCLIENT_CONF'
# ddclient configuration
# ⚠️  YOU MUST EDIT THIS FILE!
# Uncomment and edit for your DNS provider

daemon=300
syslog=yes
pid=/var/run/ddclient/ddclient.pid
ssl=yes

# Cloudflare example:
# use=web, web=cloudflare
# protocol=cloudflare
# zone=example.com
# login=token
# password=your-api-token
# example.com

# DuckDNS example:
# use=web
# protocol=duckdns
# password=your-duckdns-token
# yourdomain.duckdns.org
DDCLIENT_CONF

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$DDCLIENT_DIR" 2>/dev/null || true

            echo ""
            echo "✓ ddclient installed at $DDCLIENT_DIR"
            echo ""
            echo "  ⚠️  Note: You should edit config/ddclient.conf before starting."
            prompt_yn "Start ddclient now anyway? (y/n):" "n" START_DDCLIENT
            if [ "$START_DDCLIENT" = "y" ] || [ "$START_DDCLIENT" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ ddclient started" || echo "  ⚠ Failed to start"
            fi

            echo "  Docs: https://ddclient.net/"
            echo ""
        fi
    fi

    # ---- NTFY NOTIFICATIONS ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_NTFY" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ NTFY - Push notifications server                                │"
        echo "│ Send notifications from scripts to your phone.                  │"
        echo "│ Port: 8090                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install ntfy? (y/n):" "n" INSTALL_NTFY
    fi

    if [ "$INSTALL_NTFY" = "y" ] || [ "$INSTALL_NTFY" = "Y" ]; then
        echo "Installing ntfy..."
        NTFY_DIR="$DOCKER_DIR/ntfy"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $NTFY_DIR"
        else
            mkdir -p "$NTFY_DIR"
            ensure_docker_dir_ownership "$NTFY_DIR"
            cd "$NTFY_DIR"

            cat > docker-compose.yml << 'NTFY_COMPOSE'
name: ntfy

services:
  ntfy:
    image: binwiederhier/ntfy:latest
    container_name: ntfy
    hostname: ntfy
    restart: unless-stopped
    command: serve
    environment:
      - TZ=${TZ}
    volumes:
      - ./cache:/var/cache/ntfy
      - ./config:/etc/ntfy
    ports:
      - "8090:80"
NTFY_COMPOSE

            cat > .env << NTFY_ENV
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
NTFY_ENV

            mkdir -p cache config
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$NTFY_DIR"

            echo ""
            echo "✓ ntfy configured at $NTFY_DIR"

            prompt_yn "Start ntfy now? (y/n):" "y" START_NTFY
            if [ "$START_NTFY" = "y" ] || [ "$START_NTFY" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ ntfy started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8090"
            echo ""
            echo "  Send notification: curl -d \"Hello!\" localhost:8090/mytopic"
            echo "  Subscribe on phone: ntfy app → Add subscription → localhost:8090/mytopic"
            echo ""
        fi
    fi

    # ---- UPTIME KUMA ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_UPTIMEKUMA" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ UPTIME KUMA - Service monitoring dashboard                      │"
        echo "│ Monitor websites, servers, Docker containers.                   │"
        echo "│ Port: 3001                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Uptime Kuma? (y/n):" "n" INSTALL_UPTIMEKUMA
    fi

    if [ "$INSTALL_UPTIMEKUMA" = "y" ] || [ "$INSTALL_UPTIMEKUMA" = "Y" ]; then
        echo "Installing Uptime Kuma..."
        UPTIME_DIR="$DOCKER_DIR/uptime-kuma"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $UPTIME_DIR"
        else
            mkdir -p "$UPTIME_DIR"
            ensure_docker_dir_ownership "$UPTIME_DIR"
            cd "$UPTIME_DIR"

            cat > docker-compose.yml << 'UPTIME_COMPOSE'
name: uptime-kuma

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    hostname: uptime-kuma
    restart: unless-stopped
    volumes:
      - ./data:/app/data
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - "3001:3001"
UPTIME_COMPOSE

            mkdir -p data
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$UPTIME_DIR"

            echo ""
            echo "✓ Uptime Kuma configured at $UPTIME_DIR"

            # Configure Caddy reverse proxy before starting
            configure_caddy_for_service "Uptime Kuma" "3001" "uptime"

            prompt_yn "Start Uptime Kuma now? (y/n):" "y" START_UPTIME
            if [ "$START_UPTIME" = "y" ] || [ "$START_UPTIME" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Uptime Kuma started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:3001"
            echo ""
        fi
    fi

    # ---- WG-EASY (WireGuard with Web UI) ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_WGEASY" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ WG-EASY - WireGuard VPN with web management                     │"
        echo "│ Easy WireGuard setup with QR codes for clients.                 │"
        echo "│ Port: 51821 (web), 51820 (VPN)                                  │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install wg-easy? (y/n):" "n" INSTALL_WGEASY
    fi

    if [ "$INSTALL_WGEASY" = "y" ] || [ "$INSTALL_WGEASY" = "Y" ]; then
        echo "Installing wg-easy..."
        WGEASY_DIR="$DOCKER_DIR/wg-easy"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $WGEASY_DIR"
        else
            mkdir -p "$WGEASY_DIR"
            ensure_docker_dir_ownership "$WGEASY_DIR"
            cd "$WGEASY_DIR"

            # Get public IP or hostname
            PUBLIC_IP=$(curl -s ifconfig.me 2>/dev/null || echo "your-public-ip")
            WG_PASSWORD=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16)

            prompt_text "Public IP or hostname for VPN [default: $PUBLIC_IP]:" "$PUBLIC_IP" WG_HOST

            cat > docker-compose.yml << WGEASY_COMPOSE
name: wg-easy

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:latest
    container_name: wg-easy
    hostname: wg-easy
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    environment:
      - WG_HOST=\${WG_HOST}
      - PASSWORD=\${WG_PASSWORD}
      - WG_DEFAULT_DNS=1.1.1.1
    volumes:
      - ./config:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
WGEASY_COMPOSE

            cat > .env << WGEASY_ENV
WG_HOST=$WG_HOST
WG_PASSWORD=$WG_PASSWORD
WGEASY_ENV

            mkdir -p config
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$WGEASY_DIR"

            echo ""
            echo "✓ wg-easy configured at $WGEASY_DIR"

            prompt_yn "Start wg-easy now? (y/n):" "y" START_WGEASY
            if [ "$START_WGEASY" = "y" ] || [ "$START_WGEASY" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ wg-easy started" || echo "  ⚠ Failed to start"
            fi

            echo "  Web UI:     http://localhost:51821"
            echo "  Password:   $WG_PASSWORD (saved in .env)"
            echo "  VPN Port:   51820/udp (forward this in your router)"
            echo ""
        fi
    fi

    # ---- TRACCAR GPS TRACKING ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_TRACCAR" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ TRACCAR - GPS tracking server                                   │"
        echo "│ Track phones, vehicles, assets with OwnTracks/Traccar apps.     │"
        echo "│ Port: 8082 (web), 5055 (OsmAnd), 5000+ (devices)                │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Traccar? (y/n):" "n" INSTALL_TRACCAR
    fi

    if [ "$INSTALL_TRACCAR" = "y" ] || [ "$INSTALL_TRACCAR" = "Y" ]; then
        echo "Installing Traccar..."
        TRACCAR_DIR="$DOCKER_DIR/traccar"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $TRACCAR_DIR"
        else
            mkdir -p "$TRACCAR_DIR"
            ensure_docker_dir_ownership "$TRACCAR_DIR"
            cd "$TRACCAR_DIR"

            cat > docker-compose.yml << 'TRACCAR_COMPOSE'
name: traccar

services:
  traccar:
    image: traccar/traccar:latest
    container_name: traccar
    hostname: traccar
    restart: unless-stopped
    volumes:
      - ./logs:/opt/traccar/logs:rw
      - ./data:/opt/traccar/data:rw
      - ./config/traccar.xml:/opt/traccar/conf/traccar.xml:ro
    ports:
      - "8082:8082"
      - "5000-5150:5000-5150"
      - "5000-5150:5000-5150/udp"
TRACCAR_COMPOSE

            mkdir -p logs data config

            # Create basic traccar.xml config
            cat > config/traccar.xml << 'TRACCAR_XML'
<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'>

<properties>
    <entry key='config.default'>./conf/default.xml</entry>
    <entry key='database.driver'>org.h2.Driver</entry>
    <entry key='database.url'>jdbc:h2:/opt/traccar/data/database</entry>
    <entry key='database.user'>sa</entry>
    <entry key='database.password'></entry>
</properties>
TRACCAR_XML

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$TRACCAR_DIR"

            echo ""
            echo "✓ Traccar configured at $TRACCAR_DIR"

            prompt_yn "Start Traccar now? (y/n):" "y" START_TRACCAR
            if [ "$START_TRACCAR" = "y" ] || [ "$START_TRACCAR" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Traccar started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8082"
            echo "  Default:    admin@admin.com / admin (change immediately!)"
            echo ""
        fi
    fi

    # ---- PORTAINER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_PORTAINER" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ PORTAINER - Docker management web UI                            │"
        echo "│ Manage containers, images, volumes via browser.                 │"
        echo "│ Port: 9443 (https), 9000 (http)                                 │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Portainer? (y/n):" "n" INSTALL_PORTAINER
    fi

    if [ "$INSTALL_PORTAINER" = "y" ] || [ "$INSTALL_PORTAINER" = "Y" ]; then
        echo "Installing Portainer..."
        PORTAINER_DIR="$DOCKER_DIR/portainer"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $PORTAINER_DIR"
        else
            mkdir -p "$PORTAINER_DIR"
            ensure_docker_dir_ownership "$PORTAINER_DIR"
            cd "$PORTAINER_DIR"

            cat > docker-compose.yml << 'PORTAINER_COMPOSE'
name: portainer

services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    hostname: portainer
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data
    ports:
      - "9000:9000"
      - "9443:9443"
PORTAINER_COMPOSE

            mkdir -p data
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$PORTAINER_DIR"

            echo ""
            echo "✓ Portainer configured at $PORTAINER_DIR"

            prompt_yn "Start Portainer now? (y/n):" "y" START_PORTAINER
            if [ "$START_PORTAINER" = "y" ] || [ "$START_PORTAINER" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Portainer started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  https://localhost:9443"
            echo "  Create admin account on first visit"
            echo ""
        fi
    fi

    # ---- MESHCENTRAL SERVER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_MESHCENTRAL_SERVER" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ MESHCENTRAL SERVER - Self-hosted remote management             │"
        echo "│ Full MeshCentral server (not just agent). Manage all devices.  │"
        echo "│ Port: 4430 (https), 4433 (agent)                               │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install MeshCentral Server? (y/n):" "n" INSTALL_MESHCENTRAL_SERVER
    fi

    if [ "$INSTALL_MESHCENTRAL_SERVER" = "y" ] || [ "$INSTALL_MESHCENTRAL_SERVER" = "Y" ]; then
        echo "Installing MeshCentral Server..."
        MC_DIR="$DOCKER_DIR/meshcentral"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $MC_DIR"
        else
            mkdir -p "$MC_DIR" 2>/dev/null || true
            ensure_docker_dir_ownership "$MC_DIR"
            cd "$MC_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            cat > docker-compose.yml << 'MC_COMPOSE'
name: meshcentral

services:
  meshcentral:
    image: ghcr.io/ylianst/meshcentral:latest
    container_name: meshcentral
    hostname: meshcentral
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - HOSTNAME=${MC_HOSTNAME:-localhost}
      - REVERSE_PROXY=${MC_REVERSE_PROXY:-false}
      - REVERSE_PROXY_TLS_PORT=${MC_TLS_PORT:-443}
      - IFRAME=false
      - ALLOW_NEW_ACCOUNTS=true
      - WEBRTC=true
    volumes:
      - ./data:/opt/meshcentral/meshcentral-data
      - ./files:/opt/meshcentral/meshcentral-files
      - ./backups:/opt/meshcentral/meshcentral-backups
    ports:
      - "4430:443"
      - "4433:4433"
MC_COMPOSE
            echo "✓ Installed docker-compose.yml"

            # Ask for hostname
            echo ""
            prompt_text "MeshCentral hostname (domain or IP) [localhost]:" "localhost" MC_HOSTNAME 2>/dev/null || MC_HOSTNAME="localhost"

            cat > .env << MC_ENV
MC_HOSTNAME=$MC_HOSTNAME
MC_REVERSE_PROXY=false
MC_TLS_PORT=443
MC_ENV

            mkdir -p data files backups 2>/dev/null || true
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MC_DIR" 2>/dev/null || true

            echo ""
            echo "✓ MeshCentral Server installed at $MC_DIR"

            prompt_yn "Start MeshCentral now? (y/n):" "y" START_MESHCENTRAL
            if [ "$START_MESHCENTRAL" = "y" ] || [ "$START_MESHCENTRAL" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ MeshCentral started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access: https://localhost:4430"
            echo ""
            echo "  First visit: Create admin account"
            echo "  Then: Add devices → Download agent for each OS"
            echo ""
            echo "  ⚠️  For remote access, set MC_HOSTNAME in .env to your domain/IP"
            echo "  Docs: https://meshcentral.com/docs/"
            echo ""
        fi
    fi

    # ---- FINDMYDEVICE (FMD) ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_FMD" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ FINDMYDEVICE - Self-hosted device tracking                      │"
        echo "│ Track and locate Android devices. Alternative to Google Find.  │"
        echo "│ Port: 8084                                                      │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install FindMyDevice server? (y/n):" "n" INSTALL_FMD
    fi

    if [ "$INSTALL_FMD" = "y" ] || [ "$INSTALL_FMD" = "Y" ]; then
        echo "Installing FindMyDevice..."
        FMD_DIR="$DOCKER_DIR/fmd"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $FMD_DIR"
        else
            mkdir -p "$FMD_DIR"
            ensure_docker_dir_ownership "$FMD_DIR"
            cd "$FMD_DIR"

            # Generate random admin password
            FMD_ADMIN_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16)

            cat > docker-compose.yml << FMD_COMPOSE
name: fmd

services:
  fmd:
    image: nulide/findmydevice
    container_name: fmd
    hostname: fmd
    restart: unless-stopped
    environment:
      - FMD_ADMIN_PASSWORD=\${FMD_ADMIN_PASSWORD}
    volumes:
      - ./data:/fmd/data
    ports:
      - "8084:8080"
FMD_COMPOSE

            cat > .env << FMD_ENV
FMD_ADMIN_PASSWORD=$FMD_ADMIN_PASS
FMD_ENV

            mkdir -p data
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FMD_DIR"

            echo ""
            echo "✓ FindMyDevice configured at $FMD_DIR"

            prompt_yn "Start FindMyDevice now? (y/n):" "y" START_FMD
            if [ "$START_FMD" = "y" ] || [ "$START_FMD" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ FindMyDevice started" || echo "  ⚠ Failed to start"
            fi

            echo "  Access at:  http://localhost:8084"
            echo "  Admin password: $FMD_ADMIN_PASS (saved in .env)"
            echo ""
            echo "  Mobile app: Install 'FindMyDevice' from F-Droid"
            echo "  Configure app to point to: http://YOUR-SERVER-IP:8084"
            echo ""
        fi
    fi

    # ---- FRIGATE-NOTIFY ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_FRIGATE_NOTIFY" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ FRIGATE-NOTIFY - Push notifications for Frigate events         │"
        echo "│ Get alerts when Frigate detects people, cars, etc.             │"
        echo "│ Sends to: ntfy, Pushover, Discord, Gotify, and more.           │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Frigate-Notify? (y/n):" "n" INSTALL_FRIGATE_NOTIFY
    fi

    if [ "$INSTALL_FRIGATE_NOTIFY" = "y" ] || [ "$INSTALL_FRIGATE_NOTIFY" = "Y" ]; then
        echo "Installing Frigate-Notify..."
        FN_DIR="$DOCKER_DIR/frigate-notify"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $FN_DIR"
        else
            # STEP 1: Create directory and docker-compose (always succeeds)
            mkdir -p "$FN_DIR" 2>/dev/null || true
            ensure_docker_dir_ownership "$FN_DIR"
            cd "$FN_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            cat > docker-compose.yml << 'FN_COMPOSE'
name: frigate-notify

services:
  frigate-notify:
    image: ghcr.io/0x2142/frigate-notify:latest
    container_name: frigate-notify
    hostname: frigate-notify
    restart: unless-stopped
    volumes:
      - ./config.yml:/app/config.yml:ro
FN_COMPOSE
            echo "✓ Installed docker-compose.yml"

            # STEP 2: Try configuration (uses defaults if prompts fail)
            echo ""
            echo "Attempting auto-configuration..."

            # Set smart defaults based on what's installed
            FRIGATE_URL="http://frigate:5000"
            NTFY_URL="https://ntfy.sh"
            NTFY_TOPIC="frigate-alerts"

            [ -d "$DOCKER_DIR/frigate" ] && echo "  ✓ Frigate detected" || echo "  ⚠ Frigate not found (using default URL)"
            [ -d "$DOCKER_DIR/ntfy" ] && { NTFY_URL="http://ntfy:80"; echo "  ✓ ntfy detected"; } || echo "  ⚠ ntfy not found (using ntfy.sh)"

            # Try prompts, use defaults if they fail
            echo ""
            prompt_text "Frigate URL [$FRIGATE_URL]:" "$FRIGATE_URL" FRIGATE_URL 2>/dev/null || FRIGATE_URL="http://frigate:5000"
            prompt_text "ntfy server [$NTFY_URL]:" "$NTFY_URL" NTFY_URL 2>/dev/null || NTFY_URL="https://ntfy.sh"
            prompt_text "ntfy topic [frigate-alerts]:" "frigate-alerts" NTFY_TOPIC 2>/dev/null || NTFY_TOPIC="frigate-alerts"

            # Create config (template with user values or defaults)
            cat > config.yml << FN_CONFIG
# Frigate-Notify Configuration
# Docs: https://frigate-notify.0x2142.com
#
# ⚠️  YOU MAY NEED TO EDIT THIS FILE!
# If notifications don't work, check:
#   - Frigate server URL is correct
#   - ntfy server is reachable
#   - Containers are on same Docker network

frigate:
  server: $FRIGATE_URL
  webapi:
    enabled: true
    interval: 30

alerts:
  general:
    send_startup_message: true
  labels:
    - person
    - car
    # - dog
    # - package

notifiers:
  - name: ntfy
    enabled: true
    provider: ntfy
    config:
      server: $NTFY_URL
      topic: $NTFY_TOPIC
FN_CONFIG

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FN_DIR" 2>/dev/null || true

            echo ""
            echo "✓ Frigate-Notify installed at $FN_DIR"

            prompt_yn "Start Frigate-Notify now? (y/n):" "y" START_FN
            if [ "$START_FN" = "y" ] || [ "$START_FN" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Frigate-Notify started" || echo "  ⚠ Failed to start"
            fi

            echo "  Config: $FN_DIR/config.yml (edit if needed)"
            echo "  Docs: https://frigate-notify.0x2142.com"
            echo ""
        fi
    fi

    # ---- WATCHTOWER ----
    if [ "$WHIPTAIL_USED" != true ] && [ -z "$INSTALL_WATCHTOWER" ]; then
        echo ""
        echo "┌─────────────────────────────────────────────────────────────────┐"
        echo "│ WATCHTOWER - Container update monitoring                        │"
        echo "│ Monitor containers for updates. NOTIFY ONLY by default.         │"
        echo "│ Why notify-only? Apps like Immich have breaking DB migrations.  │"
        echo "└─────────────────────────────────────────────────────────────────┘"
        prompt_yn "Install Watchtower? (y/n):" "n" INSTALL_WATCHTOWER
    fi

    if [ "$INSTALL_WATCHTOWER" = "y" ] || [ "$INSTALL_WATCHTOWER" = "Y" ]; then
        echo "Installing Watchtower..."
        WT_DIR="$DOCKER_DIR/watchtower"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $WT_DIR"
        else
            mkdir -p "$WT_DIR" 2>/dev/null || true
            ensure_docker_dir_ownership "$WT_DIR"
            cd "$WT_DIR" 2>/dev/null || cd "$DOCKER_DIR"

            # Ask about mode
            echo ""
            echo "Watchtower Mode:"
            echo "  [M] Monitor only - Get notifications about available updates (SAFE)"
            echo "  [A] Auto-update - Automatically pull and restart containers (RISKY)"
            echo ""
            echo "  ⚠️  Auto-update can break apps like Immich that need DB migrations!"
            echo "  Recommendation: Use monitor mode, update manually when ready."
            echo ""
            WT_MODE="M"
            prompt_text "Mode [M/A]:" "M" WT_MODE 2>/dev/null || WT_MODE="M"
            WT_MODE=$(echo "$WT_MODE" | tr '[:lower:]' '[:upper:]')

            if [ "$WT_MODE" = "A" ]; then
                MONITOR_ONLY="false"
                echo "  Mode: Auto-update (containers will be updated automatically)"
            else
                MONITOR_ONLY="true"
                echo "  Mode: Monitor only (you'll be notified of updates)"
            fi

            # Check for ntfy
            NTFY_URL=""
            if [ -d "$DOCKER_DIR/ntfy" ]; then
                echo "  ✓ ntfy detected - configuring notifications"
                NTFY_URL="http://ntfy/watchtower"
            fi

            cat > docker-compose.yml << WT_COMPOSE
name: watchtower

services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    hostname: watchtower
    restart: unless-stopped
    environment:
      # Check for updates daily at 4 AM
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
      # Monitor only - don't auto-update (change to false for auto-update)
      - WATCHTOWER_MONITOR_ONLY=${MONITOR_ONLY}
      # Cleanup old images after update
      - WATCHTOWER_CLEANUP=true
      # Include stopped containers
      - WATCHTOWER_INCLUDE_STOPPED=true
      # Notification URL (ntfy, Discord, Slack, etc.)
      - WATCHTOWER_NOTIFICATION_URL=${NOTIFICATION_URL:-}
      # Show debug info
      - WATCHTOWER_DEBUG=false
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
WT_COMPOSE

            # Create .env
            cat > .env << WT_ENV
# Watchtower Configuration
# =========================
#
# Monitor-only mode: Watchtower checks for updates but doesn't apply them.
# This is SAFER because some apps (Immich, Mealie) have database migrations
# that can break if you update without proper procedures.
#
# To update manually:
#   cd ~/docker/{app}
#   docker compose pull
#   docker compose up -d

# Set to "false" to enable auto-updates (RISKY!)
MONITOR_ONLY=$MONITOR_ONLY

# Notification URL (optional)
# Examples:
#   ntfy:    ntfy://ntfy.example.com/watchtower
#   Discord: discord://token@id
#   Slack:   slack://hook-url
#   Gotify:  gotify://hostname/token
#
# Full list: https://containrrr.dev/shoutrrr/services/overview/
NOTIFICATION_URL=$NTFY_URL
WT_ENV

            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$WT_DIR" 2>/dev/null || true

            echo ""
            echo "✓ Watchtower installed at $WT_DIR"

            prompt_yn "Start Watchtower now? (y/n):" "y" START_WATCHTOWER
            if [ "$START_WATCHTOWER" = "y" ] || [ "$START_WATCHTOWER" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Watchtower started" || echo "  ⚠ Failed to start"
            fi

            echo "  Mode: $([ "$MONITOR_ONLY" = "true" ] && echo "Monitor only" || echo "Auto-update")"
            echo ""
            echo "  Checks for updates daily at 4 AM."
            if [ -n "$NTFY_URL" ]; then
                echo "  Notifications: $NTFY_URL"
            else
                echo "  Configure NOTIFICATION_URL in .env for alerts."
            fi
            echo ""
            echo "  To exclude a container from Watchtower:"
            echo "    Add label: com.centurylinklabs.watchtower.enable=false"
            echo ""
        fi
    fi

    fi  # End SKIP_DOCKER_INSTALLS check

    # ============================================================================
    # KOPIA BACKUP FOR DOCKER CONTAINERS
    # ============================================================================
    if [ "$WHIPTAIL_USED" != true ]; then
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DOCKER CONTAINER BACKUP (Kopia)"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Backup all Docker containers (configs, databases, app data) to your"
    echo "backup drives. Essential for disaster recovery - if your OS drive"
    echo "fails, you can restore everything including:"
    echo "  • Immich memories, facial recognition data"
    echo "  • Emby/Jellyfin metadata, watch history"
    echo "  • Minecraft worlds, mods, permissions"
    echo "  • All app configs, users, and databases"
    echo ""
    prompt_yn "Set up Kopia container backup? (y/n):" "n" INSTALL_KOPIA

    if [ "$INSTALL_KOPIA" = "y" ] || [ "$INSTALL_KOPIA" = "Y" ]; then
        echo "Installing Kopia backup..."
        KOPIA_DIR="$DOCKER_DIR/kopia"

        if [ "$DRY_RUN" = true ]; then
            echo "[DRY-RUN] Would create $KOPIA_DIR"
        else
            mkdir -p "$KOPIA_DIR"
            ensure_docker_dir_ownership "$KOPIA_DIR"
            cd "$KOPIA_DIR"

            KOPIA_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)

            echo ""
            echo "Select backup destination(s):"
            echo "  Backups will be stored in ~/drives/{backup-drive}/kopia-repo/"
            echo ""

            # List available backup drives
            echo "Available mount points in ~/drives/:"
            ls -1 "$ACTUAL_HOME/drives/" 2>/dev/null | grep -v "^primary$" || echo "  (none found - set up drives first)"
            echo ""

            prompt_text "Backup drive name [default: backup1]:" "backup1" KOPIA_BACKUP_DRIVE

            KOPIA_REPO="$ACTUAL_HOME/drives/$KOPIA_BACKUP_DRIVE/kopia-repo"

            cat > docker-compose.yml << KOPIA_COMPOSE
name: kopia

services:
  kopia:
    image: kopia/kopia:latest
    container_name: kopia
    hostname: kopia
    restart: unless-stopped
    privileged: true
    devices:
      - /dev/fuse:/dev/fuse:rwm
    environment:
      - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
      - KOPIA_PASSWORD=\${KOPIA_PASSWORD}
    command: >
      server start
      --tls-generate-cert
      --disable-csrf-token-checks
      --address=0.0.0.0:51515
      --server-username=admin
      --server-password=\${KOPIA_PASSWORD}
    volumes:
      - ./config:/app/config
      - ./cache:/app/cache
      - ./logs:/app/logs
      - $DOCKER_DIR:/data/docker:ro
      - $KOPIA_REPO:/repository
      - ./tmp:/tmp:shared
    ports:
      - "51515:51515"
KOPIA_COMPOSE

            cat > .env << KOPIA_ENV
KOPIA_PASSWORD=$KOPIA_PASSWORD
KOPIA_ENV

            mkdir -p config cache logs tmp
            mkdir -p "$KOPIA_REPO"

            # Create backup script
            cat > backup-containers.sh << 'BACKUP_SCRIPT'
#!/bin/bash
# Backup all Docker containers using Kopia

DOCKER_DIR="$HOME/docker"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

echo "=== Docker Container Backup: $TIMESTAMP ==="
echo ""

# Stop containers before backup for consistency (optional)
read -p "Stop containers during backup for consistency? (y/n): " STOP_CONTAINERS

if [ "$STOP_CONTAINERS" = "y" ]; then
    echo "Stopping containers..."
    for dir in "$DOCKER_DIR"/*/; do
        if [ -f "$dir/docker-compose.yml" ]; then
            echo "  Stopping $(basename $dir)..."
            (cd "$dir" && docker compose stop) 2>/dev/null
        fi
    done
fi

echo ""
echo "Running Kopia backup..."
docker exec kopia kopia snapshot create /data/docker --description "Container backup $TIMESTAMP"

if [ "$STOP_CONTAINERS" = "y" ]; then
    echo ""
    echo "Restarting containers..."
    for dir in "$DOCKER_DIR"/*/; do
        if [ -f "$dir/docker-compose.yml" ]; then
            echo "  Starting $(basename $dir)..."
            (cd "$dir" && docker compose start) 2>/dev/null
        fi
    done
fi

echo ""
echo "=== Backup Complete ==="
docker exec kopia kopia snapshot list /data/docker --max-results 5
BACKUP_SCRIPT

            # Create restore script
            cat > restore-containers.sh << 'RESTORE_SCRIPT'
#!/bin/bash
# Restore Docker containers from Kopia backup

echo "=== Docker Container Restore ==="
echo ""
echo "Available snapshots:"
docker exec kopia kopia snapshot list /data/docker

echo ""
echo "To restore a specific snapshot:"
echo "  docker exec kopia kopia restore <snapshot-id> /tmp/restore"
echo "  Then copy files from ~/docker/kopia/tmp/restore/ to ~/docker/"
echo ""
echo "To mount snapshots for browsing:"
echo "  docker exec kopia kopia mount all /tmp/mnt &"
echo "  Then browse: ~/docker/kopia/tmp/mnt/"
RESTORE_SCRIPT

            chmod +x backup-containers.sh restore-containers.sh
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$KOPIA_DIR"

            echo ""
            echo "✓ Kopia backup configured at $KOPIA_DIR"

            prompt_yn "Start Kopia now? (y/n):" "y" START_KOPIA
            if [ "$START_KOPIA" = "y" ] || [ "$START_KOPIA" = "Y" ]; then
                docker compose up -d 2>/dev/null && echo "  ✓ Kopia started" || echo "  ⚠ Failed to start"
            fi

            echo "  Web UI:         https://localhost:51515"
            echo "  Username:       admin"
            echo "  Password:       $KOPIA_PASSWORD (saved in .env)"
            echo ""
            echo "  Repository at:  $KOPIA_REPO"
            echo ""
            echo "  Backup now:     cd $KOPIA_DIR && ./backup-containers.sh"
            echo "  Restore:        cd $KOPIA_DIR && ./restore-containers.sh"
            echo ""
            echo "  ⚠️  SAVE YOUR KOPIA PASSWORD! Without it, backups cannot be restored."
            echo ""
        fi
    fi
    fi  # End WHIPTAIL_USED check for Kopia

    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "Docker applications configured in: $DOCKER_DIR"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "To start an application:"
    echo "  cd ~/docker/{appname}"
    echo "  docker compose up -d"
    echo ""
    echo "To view logs:"
    echo "  docker compose logs -f"
    echo ""
    echo "To stop:"
    echo "  docker compose down"
    echo ""
fi

# Backup System (Optional)
if [ "$WHIPTAIL_USED" != true ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "LOCAL BACKUP SYSTEM (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Set up rsync backup from primary drive(s) to backup drive(s)."
echo ""
echo "Why rsync instead of RAID?"
echo "  • RAID mirrors corruption instantly - rsync gives you time to notice"
echo "  • RAID requires identical drives - rsync works with any sizes"
echo "  • RAID is complex to set up/recover - rsync is simple copy"
echo "  • rsync can run on schedule - RAID is always-on (more wear)"
echo "  • With rsync, backup drives can be disconnected for safety"
echo ""

# Check if backup is already configured
BACKUP_CONFIGURED=false
if [ -f /usr/local/bin/backup-scripts/rsync-backup.sh ]; then
    BACKUP_CONFIGURED=true
    echo "Local backup system is already configured."
    echo ""
    prompt_yn "Reconfigure local backup system? (y/n):" "n" SETUP_BACKUP
else
    prompt_yn "Set up local backup system? (y/n):" "n" SETUP_BACKUP
fi

if [ "$SETUP_BACKUP" = "y" ] || [ "$SETUP_BACKUP" = "Y" ]; then
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DRIVE CONFIGURATION"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Configure your drive mount points in ~/drives/"
    echo "Default names: primary, backup1, backup2, etc."
    echo "You can customize these (e.g., 'media', 'documents', 'photos-backup')"
    echo ""

    # Install rsync if needed
    if ! is_rsync_installed; then
        run_cmd apt install -y rsync || echo "Warning: rsync installation failed"
    fi

    # Create backup script directory
    mkdir -p /usr/local/bin/backup-scripts

    # Ask for primary drive name
    echo "PRIMARY DRIVE (source for backups):"
    prompt_text "  Mount point name [default: primary]:" "primary" PRIMARY_NAME
    PRIMARY_NAME="${PRIMARY_NAME:-primary}"

    # Ask for number of backup drives
    echo ""
    echo "BACKUP DRIVES (destinations):"
    prompt_text "  How many backup drives? [1-4, default: 1]:" "1" NUM_BACKUPS
    NUM_BACKUPS="${NUM_BACKUPS:-1}"

    # Validate number
    case $NUM_BACKUPS in
        1|2|3|4) ;;
        *) NUM_BACKUPS=1 ;;
    esac

    # Collect backup drive names
    declare -a BACKUP_NAMES
    for i in $(seq 1 $NUM_BACKUPS); do
        prompt_text "  Backup drive $i name [default: backup$i]:" "backup$i" "BACKUP_NAME_$i"
        eval "BACKUP_NAMES[$i]=\${BACKUP_NAME_$i:-backup$i}"
    done

    # Create mount point directories
    echo ""
    echo "Creating mount point directories..."
    mkdir -p "$ACTUAL_HOME/drives/$PRIMARY_NAME"
    for i in $(seq 1 $NUM_BACKUPS); do
        mkdir -p "$ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}"
    done
    chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ACTUAL_HOME/drives"

    echo "✓ Created mount points:"
    echo "  Primary: $ACTUAL_HOME/drives/$PRIMARY_NAME"
    for i in $(seq 1 $NUM_BACKUPS); do
        echo "  Backup$i: $ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}"
    done

    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "DRIVE MOUNTING"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo "Available block devices:"
    echo ""
    lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL
    echo ""

    prompt_yn "Mount drives now? (y/n):" "n" MOUNT_NOW

    if [ "$MOUNT_NOW" = "y" ] || [ "$MOUNT_NOW" = "Y" ]; then
        echo ""
        echo "Enter device paths (e.g., /dev/sdb1) or leave blank to skip"
        echo ""

        read -p "Primary drive ($PRIMARY_NAME) device: " PRIMARY_DEV

        declare -a BACKUP_DEVS
        for i in $(seq 1 $NUM_BACKUPS); do
            read -p "Backup drive ${BACKUP_NAMES[$i]} device: " "BACKUP_DEV_$i"
            eval "BACKUP_DEVS[$i]=\$BACKUP_DEV_$i"
        done

        # Mount primary
        if [ -n "$PRIMARY_DEV" ] && [ -b "$PRIMARY_DEV" ]; then
            echo "Mounting $PRIMARY_DEV to $ACTUAL_HOME/drives/$PRIMARY_NAME..."
            mount "$PRIMARY_DEV" "$ACTUAL_HOME/drives/$PRIMARY_NAME" && echo "✓ $PRIMARY_NAME mounted" || echo "✗ Failed to mount $PRIMARY_NAME"
        fi

        # Mount backups
        for i in $(seq 1 $NUM_BACKUPS); do
            if [ -n "${BACKUP_DEVS[$i]}" ] && [ -b "${BACKUP_DEVS[$i]}" ]; then
                echo "Mounting ${BACKUP_DEVS[$i]} to $ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}..."
                mount "${BACKUP_DEVS[$i]}" "$ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}" && echo "✓ ${BACKUP_NAMES[$i]} mounted" || echo "✗ Failed to mount ${BACKUP_NAMES[$i]}"
            fi
        done

        echo ""
        echo "Current mounts:"
        df -h | grep "$ACTUAL_HOME/drives" || echo "  (no drives currently mounted)"

        echo ""
        prompt_yn "Add to /etc/fstab for auto-mount at boot? (y/n):" "n" ADD_FSTAB

        if [ "$ADD_FSTAB" = "y" ] || [ "$ADD_FSTAB" = "Y" ]; then
            echo ""
            echo "Adding entries to /etc/fstab..."
            cp /etc/fstab /etc/fstab.backup-$(date +%Y%m%d-%H%M%S)

            if [ -n "$PRIMARY_DEV" ] && [ -b "$PRIMARY_DEV" ]; then
                PRIMARY_UUID=$(blkid -s UUID -o value "$PRIMARY_DEV")
                if [ -n "$PRIMARY_UUID" ]; then
                    echo "UUID=$PRIMARY_UUID $ACTUAL_HOME/drives/$PRIMARY_NAME auto defaults,nofail 0 2" >> /etc/fstab
                    echo "✓ Added $PRIMARY_NAME to fstab"
                fi
            fi

            for i in $(seq 1 $NUM_BACKUPS); do
                if [ -n "${BACKUP_DEVS[$i]}" ] && [ -b "${BACKUP_DEVS[$i]}" ]; then
                    BACKUP_UUID=$(blkid -s UUID -o value "${BACKUP_DEVS[$i]}")
                    if [ -n "$BACKUP_UUID" ]; then
                        echo "UUID=$BACKUP_UUID $ACTUAL_HOME/drives/${BACKUP_NAMES[$i]} auto defaults,nofail 0 2" >> /etc/fstab
                        echo "✓ Added ${BACKUP_NAMES[$i]} to fstab"
                    fi
                fi
            done
        fi
    fi

    # Create rsync backup script
    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "CREATING BACKUP SCRIPT"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

    # Build backup destinations list
    BACKUP_DESTS=""
    for i in $(seq 1 $NUM_BACKUPS); do
        BACKUP_DESTS="$BACKUP_DESTS \"$ACTUAL_HOME/drives/${BACKUP_NAMES[$i]}\""
    done

    cat > /usr/local/bin/backup-scripts/rsync-backup.sh << BACKUP_SCRIPT
#!/bin/bash
################################################################################
# rsync Local Backup Script
# Backs up primary drive to all backup drives
################################################################################

PRIMARY="$ACTUAL_HOME/drives/$PRIMARY_NAME"
BACKUP_DRIVES=($BACKUP_DESTS)
LOG="/var/log/rsync-backup.log"

echo "=== rsync Backup Started: \$(date) ===" | tee -a "\$LOG"
echo "Source: \$PRIMARY" | tee -a "\$LOG"
echo "" | tee -a "\$LOG"

if [ ! -d "\$PRIMARY" ] || [ -z "\$(ls -A \$PRIMARY 2>/dev/null)" ]; then
    echo "ERROR: Primary drive not mounted or empty at \$PRIMARY" | tee -a "\$LOG"
    exit 1
fi

for BACKUP in "\${BACKUP_DRIVES[@]}"; do
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | tee -a "\$LOG"
    echo "Backing up to: \$BACKUP" | tee -a "\$LOG"

    if [ ! -d "\$BACKUP" ]; then
        echo "⚠️  WARNING: \$BACKUP not mounted, skipping" | tee -a "\$LOG"
        continue
    fi

    # rsync options:
    #   -a = archive mode (preserves permissions, timestamps, etc.)
    #   -v = verbose
    #   -h = human-readable sizes
    #   --delete = remove files from backup that don't exist on primary
    #   --progress = show progress

    rsync -avh --delete --progress "\$PRIMARY/" "\$BACKUP/" 2>&1 | tee -a "\$LOG"

    if [ \$? -eq 0 ]; then
        echo "✓ Backup to \$BACKUP completed" | tee -a "\$LOG"
    else
        echo "✗ Backup to \$BACKUP FAILED" | tee -a "\$LOG"
    fi
done

echo "" | tee -a "\$LOG"
echo "=== Backup Completed: \$(date) ===" | tee -a "\$LOG"
BACKUP_SCRIPT

    chmod +x /usr/local/bin/backup-scripts/rsync-backup.sh

    # Create systemd service
    cat > /etc/systemd/system/rsync-backup.service << SERVICE
[Unit]
Description=rsync Local Backup Service
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-scripts/rsync-backup.sh
User=root
SERVICE

    # Create systemd timer for daily backups at 2 AM
    cat > /etc/systemd/system/rsync-backup.timer << TIMER
[Unit]
Description=Daily rsync Backup Timer
Requires=rsync-backup.service

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
TIMER

    echo ""
    echo "✓ Backup script created: /usr/local/bin/backup-scripts/rsync-backup.sh"
    echo "✓ Systemd service/timer created (disabled by default)"
    echo ""
    echo "Quick start:"
    echo "  Test backup:    sudo /usr/local/bin/backup-scripts/rsync-backup.sh"
    echo "  Enable daily:   sudo systemctl enable --now rsync-backup.timer"
    echo "  View log:       tail -f /var/log/rsync-backup.log"
else
    echo "Skipping local backup setup."
fi
fi  # End WHIPTAIL_USED check for local backup

# Cloud Backup with rclone (Optional)
if [ "$WHIPTAIL_USED" != true ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "CLOUD BACKUP (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Set up encrypted cloud backup using rclone."
echo "Supports: Google Drive, OneDrive, Dropbox, and 40+ other providers."
echo ""
echo "Your files are encrypted BEFORE upload - the cloud provider cannot read them."
echo ""

prompt_yn "Set up encrypted cloud backup? (y/n):" "n" SETUP_CLOUD_BACKUP

if [ "$SETUP_CLOUD_BACKUP" = "y" ] || [ "$SETUP_CLOUD_BACKUP" = "Y" ]; then
    echo ""

    # Install rclone if needed
    if ! is_rclone_installed; then
        echo "Installing rclone..."
        run_cmd apt install -y rclone || echo "Warning: rclone installation failed"
    fi

    if is_rclone_installed; then
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "CHOOSE CLOUD PROVIDER"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo "  [1] Google Drive (15GB free)"
        echo "  [2] Microsoft OneDrive (5GB free, 1TB with Microsoft 365)"
        echo "  [3] Other (manual rclone config)"
        echo ""
        prompt_text "Select provider [1/2/3]:" "1" CLOUD_PROVIDER

        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "RCLONE CONFIGURATION"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""

        case $CLOUD_PROVIDER in
            1)
                echo "Setting up Google Drive..."
                echo ""
                echo "Step 1: Run 'rclone config' to create a Google Drive remote"
                echo "Step 2: When prompted:"
                echo "   - Choose 'n' for new remote"
                echo "   - Name it 'gdrive'"
                echo "   - Choose 'drive' (Google Drive)"
                echo "   - Leave client_id and client_secret blank"
                echo "   - Choose scope '1' (full access)"
                echo "   - Leave root_folder_id blank"
                echo "   - Leave service_account_file blank"
                echo "   - Choose 'n' for advanced config"
                echo "   - Choose 'y' for auto config (opens browser)"
                echo "   - Choose 'n' for team drive"
                echo "   - Confirm with 'y'"
                echo ""
                CLOUD_REMOTE="gdrive"
                ;;
            2)
                echo "Setting up Microsoft OneDrive..."
                echo ""
                echo "Step 1: Run 'rclone config' to create a OneDrive remote"
                echo "Step 2: When prompted:"
                echo "   - Choose 'n' for new remote"
                echo "   - Name it 'onedrive'"
                echo "   - Choose 'onedrive' (Microsoft OneDrive)"
                echo "   - Leave client_id and client_secret blank"
                echo "   - Choose region (usually 'global')"
                echo "   - Choose 'n' for advanced config"
                echo "   - Choose 'y' for auto config (opens browser)"
                echo "   - Choose 'onedrive' for account type"
                echo "   - Choose your drive from the list (usually option 0)"
                echo "   - Confirm with 'y'"
                echo ""
                CLOUD_REMOTE="onedrive"
                ;;
            *)
                echo "Manual configuration selected."
                echo "Run 'rclone config' to set up your cloud provider."
                echo ""
                prompt_text "Enter the remote name you will create:" "cloud" CLOUD_REMOTE
                ;;
        esac

        if [ "$UNATTENDED" != true ]; then
            echo "Press Enter to launch rclone config..."
            read
            sudo -u "$ACTUAL_USER" rclone config
        else
            echo "Skipping interactive rclone config (unattended mode)"
            echo "Run 'rclone config' manually to complete setup"
        fi

        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "ENCRYPTION SETUP"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo "Now we'll create an encrypted wrapper around your cloud storage."
        echo "This encrypts file names AND contents before upload."
        echo ""
        echo "⚠️  IMPORTANT: You will set a password. Without this password and"
        echo "   the rclone config file, your files CANNOT be recovered!"
        echo ""

        if [ "$UNATTENDED" != true ]; then
            echo "In rclone config:"
            echo "   - Choose 'n' for new remote"
            echo "   - Name it '${CLOUD_REMOTE}-crypt'"
            echo "   - Choose 'crypt' (Encrypt/Decrypt)"
            echo "   - Remote: '${CLOUD_REMOTE}:backup' (folder on cloud storage)"
            echo "   - Choose 'standard' for filename encryption"
            echo "   - Choose 'true' for directory name encryption"
            echo "   - Choose 'y' to enter your own password"
            echo "   - Enter a STRONG password (you'll need this to decrypt!)"
            echo "   - Choose 'y' for salt password (or 'n' to skip)"
            echo "   - Confirm with 'y'"
            echo ""
            echo "Press Enter to continue rclone config..."
            read
            sudo -u "$ACTUAL_USER" rclone config
        fi

        # Backup the rclone config to local drives
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "BACKUP YOUR RCLONE CONFIG"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo "Your rclone config contains your encryption keys."
        echo "WITHOUT IT, YOUR ENCRYPTED CLOUD FILES CANNOT BE DECRYPTED!"
        echo ""
        echo "The config file is at: $ACTUAL_HOME/.config/rclone/rclone.conf"
        echo ""

        # Copy config to any mounted backup drives
        if [ -d "$ACTUAL_HOME/drives" ]; then
            for drive_dir in "$ACTUAL_HOME/drives"/*/; do
                if [ -d "$drive_dir" ] && mountpoint -q "$drive_dir" 2>/dev/null; then
                    mkdir -p "${drive_dir}.rclone-config-backup"
                    if [ -f "$ACTUAL_HOME/.config/rclone/rclone.conf" ]; then
                        cp "$ACTUAL_HOME/.config/rclone/rclone.conf" "${drive_dir}.rclone-config-backup/rclone.conf.backup"
                        echo "✓ Config backed up to: ${drive_dir}.rclone-config-backup/"
                    fi
                fi
            done
        fi

        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "SECURE OFF-SITE BACKUP OF CONFIG"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo "For disaster recovery, store a copy of rclone.conf OFF-SITE:"
        echo ""
        echo "Recommended secure methods:"
        echo "  • Signal (end-to-end encrypted, send to yourself or trusted contact)"
        echo "  • Box.com (better privacy policy than Dropbox)"
        echo "  • Password manager (1Password, Bitwarden, etc.)"
        echo "  • Encrypted USB drive stored at another location"
        echo ""
        echo "⚠️  Dropbox: Works but has broader data access policies."
        echo "    Consider encrypting the config file before uploading."
        echo ""
        echo "To use on another computer:"
        echo "  1. Install rclone"
        echo "  2. Copy rclone.conf to ~/.config/rclone/"
        echo "  3. Run: rclone ls ${CLOUD_REMOTE}-crypt:"
        echo ""

        # Create cloud backup script
        mkdir -p /usr/local/bin/backup-scripts

        SOURCE_PATH="${PRIMARY_NAME:-primary}"

        cat > /usr/local/bin/backup-scripts/cloud-backup.sh << CLOUD_SCRIPT
#!/bin/bash
################################################################################
# rclone Encrypted Cloud Backup Script
################################################################################

SOURCE="$ACTUAL_HOME/drives/$SOURCE_PATH"
REMOTE="${CLOUD_REMOTE:-gdrive}-crypt"
LOG="/var/log/cloud-backup.log"

echo "=== Cloud Backup Started: \$(date) ===" | tee -a "\$LOG"
echo "Source: \$SOURCE" | tee -a "\$LOG"
echo "Destination: \$REMOTE:" | tee -a "\$LOG"
echo "" | tee -a "\$LOG"

if [ ! -d "\$SOURCE" ] || [ -z "\$(ls -A \$SOURCE 2>/dev/null)" ]; then
    echo "ERROR: Source not mounted or empty at \$SOURCE" | tee -a "\$LOG"
    exit 1
fi

# Sync to encrypted cloud storage
rclone sync "\$SOURCE" "\$REMOTE:" \\
    --progress \\
    --stats=30s \\
    --log-file="\$LOG" \\
    --log-level INFO

if [ \$? -eq 0 ]; then
    echo "" | tee -a "\$LOG"
    echo "✓ Cloud backup completed: \$(date)" | tee -a "\$LOG"
else
    echo "" | tee -a "\$LOG"
    echo "✗ Cloud backup FAILED: \$(date)" | tee -a "\$LOG"
fi
CLOUD_SCRIPT

        chmod +x /usr/local/bin/backup-scripts/cloud-backup.sh
        chown "$ACTUAL_USER:$ACTUAL_USER" /usr/local/bin/backup-scripts/cloud-backup.sh

        echo ""
        echo "✓ Cloud backup script created: /usr/local/bin/backup-scripts/cloud-backup.sh"
        echo ""
        echo "Quick start:"
        echo "  Test backup:  sudo /usr/local/bin/backup-scripts/cloud-backup.sh"
        echo "  View log:     tail -f /var/log/cloud-backup.log"
    else
        echo "rclone installation failed. Skipping cloud backup setup."
    fi
else
    echo "Skipping cloud backup setup."
fi
fi  # End WHIPTAIL_USED check for cloud backup

# UFW Firewall Configuration (Optional)
if [ "$WHIPTAIL_USED" != true ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "UFW FIREWALL (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "UFW (Uncomplicated Firewall) provides a simple interface for managing"
echo "iptables firewall rules."
echo ""

UFW_ACTIVE=false
if is_ufw_installed && ufw status 2>/dev/null | grep -q "Status: active"; then
    UFW_ACTIVE=true
    echo "UFW is already enabled."
    ufw status 2>/dev/null | head -20
    echo ""
    prompt_yn "Reconfigure UFW? (y/n):" "n" CONFIGURE_UFW
else
    if is_ufw_installed; then
        echo "UFW is installed but not enabled."
    else
        echo "UFW is not installed."
    fi
    echo ""
    prompt_yn "Enable and configure UFW firewall? (y/n):" "y" CONFIGURE_UFW
fi

if [ "$CONFIGURE_UFW" = "y" ] || [ "$CONFIGURE_UFW" = "Y" ]; then
    echo ""
    echo "Configuring UFW firewall..."

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would install ufw if needed"
        echo "[DRY-RUN] Would allow SSH (port 22)"
        if [ "$INSTALL_SAMBA" = "y" ] || [ "$INSTALL_SAMBA" = "Y" ]; then
            echo "[DRY-RUN] Would allow Samba"
        fi
        echo "[DRY-RUN] Would enable UFW"
    else
        # Install UFW if not present
        if ! is_ufw_installed; then
            apt install -y ufw || echo "Warning: UFW installation failed"
        fi

        if is_ufw_installed; then
            # Always allow SSH first (before enabling!)
            ufw allow ssh
            echo "✓ Allowed SSH (port 22)"

            # Allow Samba if installed
            if [ "$INSTALL_SAMBA" = "y" ] || [ "$INSTALL_SAMBA" = "Y" ] || is_samba_installed; then
                ufw allow samba
                echo "✓ Allowed Samba"
            fi

            # Allow Docker service ports (only if Docker was installed)
            if [ "$INSTALL_DOCKER" = "y" ] || [ "$INSTALL_DOCKER" = "Y" ]; then
                echo ""
                echo "Opening firewall ports for Docker services..."

                prompt_yn "Open firewall ports for installed Docker services? (y/n):" "y" OPEN_DOCKER_PORTS
                if [ "$OPEN_DOCKER_PORTS" = "y" ] || [ "$OPEN_DOCKER_PORTS" = "Y" ]; then

                    # Reverse proxies (NPM or Caddy)
                    if [ "$INSTALL_CADDY" = "y" ] || [ "$INSTALL_CADDY" = "Y" ]; then
                        ufw allow 80/tcp comment 'HTTP' 2>/dev/null
                        ufw allow 443/tcp comment 'HTTPS' 2>/dev/null
                        echo "  ✓ Allowed HTTP/HTTPS (80, 443)"
                    fi

                    # Media servers
                    if [ "$INSTALL_IMMICH" = "y" ] || [ "$INSTALL_IMMICH" = "Y" ]; then
                        ufw allow 2283/tcp comment 'Immich' 2>/dev/null
                        echo "  ✓ Allowed Immich (2283)"
                    fi
                    if [ "$INSTALL_JELLYFIN" = "y" ] || [ "$INSTALL_JELLYFIN" = "Y" ]; then
                        ufw allow 8096/tcp comment 'Jellyfin' 2>/dev/null
                        echo "  ✓ Allowed Jellyfin (8096)"
                    fi
                    if [ "$INSTALL_EMBY" = "y" ] || [ "$INSTALL_EMBY" = "Y" ]; then
                        ufw allow 8096/tcp comment 'Emby' 2>/dev/null
                        echo "  ✓ Allowed Emby (8096)"
                    fi

                    # NVR
                    if [ "$INSTALL_FRIGATE" = "y" ] || [ "$INSTALL_FRIGATE" = "Y" ]; then
                        ufw allow 5000/tcp comment 'Frigate' 2>/dev/null
                        ufw allow 8554/tcp comment 'Frigate RTSP' 2>/dev/null
                        ufw allow 8555/tcp comment 'Frigate WebRTC' 2>/dev/null
                        ufw allow 8555/udp comment 'Frigate WebRTC UDP' 2>/dev/null
                        echo "  ✓ Allowed Frigate (5000, 8554, 8555)"
                    fi

                    # Utilities
                    if [ "$INSTALL_PORTAINER" = "y" ] || [ "$INSTALL_PORTAINER" = "Y" ]; then
                        ufw allow 9000/tcp comment 'Portainer HTTP' 2>/dev/null
                        ufw allow 9443/tcp comment 'Portainer HTTPS' 2>/dev/null
                        echo "  ✓ Allowed Portainer (9000, 9443)"
                    fi
                    if [ "$INSTALL_UPTIMEKUMA" = "y" ] || [ "$INSTALL_UPTIMEKUMA" = "Y" ]; then
                        ufw allow 3001/tcp comment 'Uptime Kuma' 2>/dev/null
                        echo "  ✓ Allowed Uptime Kuma (3001)"
                    fi

                    # VPN
                    if [ "$INSTALL_WGEASY" = "y" ] || [ "$INSTALL_WGEASY" = "Y" ]; then
                        ufw allow 51820/udp comment 'WireGuard VPN' 2>/dev/null
                        ufw allow 51821/tcp comment 'WG-Easy Web UI' 2>/dev/null
                        echo "  ✓ Allowed WireGuard (51820/udp, 51821)"
                    fi

                    # GPS Tracking
                    if [ "$INSTALL_TRACCAR" = "y" ] || [ "$INSTALL_TRACCAR" = "Y" ]; then
                        ufw allow 8082/tcp comment 'Traccar' 2>/dev/null
                        ufw allow 5055/tcp comment 'Traccar OsmAnd' 2>/dev/null
                        echo "  ✓ Allowed Traccar (8082, 5055)"
                    fi

                    # Music server
                    if [ "$INSTALL_LMS" = "y" ] || [ "$INSTALL_LMS" = "Y" ]; then
                        ufw allow 9000/tcp comment 'Lyrion Music Server' 2>/dev/null
                        ufw allow 3483/tcp comment 'LMS Players' 2>/dev/null
                        ufw allow 3483/udp comment 'LMS Players UDP' 2>/dev/null
                        echo "  ✓ Allowed Lyrion Music Server (9000, 3483)"
                    fi

                    # Notifications
                    if [ "$INSTALL_NTFY" = "y" ] || [ "$INSTALL_NTFY" = "Y" ]; then
                        ufw allow 8090/tcp comment 'ntfy' 2>/dev/null
                        echo "  ✓ Allowed ntfy (8090)"
                    fi

                    # Minecraft
                    if [ "$INSTALL_MINECRAFT" = "y" ] || [ "$INSTALL_MINECRAFT" = "Y" ]; then
                        ufw allow 25565/tcp comment 'Minecraft' 2>/dev/null
                        echo "  ✓ Allowed Minecraft (25565)"
                    fi

                    # Other services
                    if [ "$INSTALL_FILEBROWSER" = "y" ] || [ "$INSTALL_FILEBROWSER" = "Y" ]; then
                        ufw allow 8085/tcp comment 'Filebrowser' 2>/dev/null
                        echo "  ✓ Allowed Filebrowser (8085)"
                    fi
                    if [ "$INSTALL_FMD" = "y" ] || [ "$INSTALL_FMD" = "Y" ]; then
                        ufw allow 8084/tcp comment 'FindMyDevice' 2>/dev/null
                        echo "  ✓ Allowed FindMyDevice (8084)"
                    fi
                    if [ "$INSTALL_MEALIE" = "y" ] || [ "$INSTALL_MEALIE" = "Y" ]; then
                        ufw allow 9925/tcp comment 'Mealie' 2>/dev/null
                        echo "  ✓ Allowed Mealie (9925)"
                    fi
                    if [ "$INSTALL_MAGICMIRROR" = "y" ] || [ "$INSTALL_MAGICMIRROR" = "Y" ]; then
                        ufw allow 8081:8083/tcp comment 'MagicMirror' 2>/dev/null
                        echo "  ✓ Allowed MagicMirror (8081-8083)"
                    fi
                    if [ "$INSTALL_ARM" = "y" ] || [ "$INSTALL_ARM" = "Y" ]; then
                        ufw allow 8080/tcp comment 'A.R.M.' 2>/dev/null
                        echo "  ✓ Allowed A.R.M. (8080)"
                    fi
                    if [ "$INSTALL_AUDIOBOOKSHELF" = "y" ] || [ "$INSTALL_AUDIOBOOKSHELF" = "Y" ]; then
                        ufw allow 13378/tcp comment 'Audiobookshelf' 2>/dev/null
                        echo "  ✓ Allowed Audiobookshelf (13378)"
                    fi

                    echo ""
                fi
            fi

            # Enable UFW (with --force to avoid prompt)
            ufw --force enable
            echo "✓ UFW firewall enabled"

            echo ""
            echo "Current UFW status:"
            ufw status
        else
            echo "✗ UFW installation failed, skipping configuration"
        fi
    fi
else
    echo "Skipping UFW configuration."
fi

CONFIGURE_UFW="${CONFIGURE_UFW:-n}"
fi  # End WHIPTAIL_USED check for UFW

# Full system upgrade (optional)
echo ""
prompt_yn "Perform full system upgrade? (y/n):" "n" DO_SYSTEM_UPGRADE
if [ "$DO_SYSTEM_UPGRADE" = "y" ] || [ "$DO_SYSTEM_UPGRADE" = "Y" ]; then
    echo "Performing full system upgrade..."
    run_cmd apt upgrade -y
else
    echo "Skipping system upgrade."
fi

# Clean up
echo ""
echo "Cleaning up..."
run_cmd apt autoremove -y
run_cmd apt autoclean

echo ""
echo "=== Installation Complete! ==="
echo ""
echo "Installed Software:"
echo "  ✓ net-tools, ncdu, git, curl, wget, htop, tree, zip/unzip"
echo "  ✓ OpenSSH Server - SSH remote access"
if [ "$INSTALL_FAIL2BAN" = "y" ] || [ "$INSTALL_FAIL2BAN" = "Y" ]; then
    echo "  ✓ fail2ban - SSH brute-force protection"
fi
if [ "$INSTALL_DOCKER" = "y" ] || [ "$INSTALL_DOCKER" = "Y" ]; then
    echo "  ✓ Docker Engine + Docker Compose"
fi
if [ "$INSTALL_SAMBA" = "y" ] || [ "$INSTALL_SAMBA" = "Y" ]; then
    echo "  ✓ Samba - File sharing (Primary drive shared)"
fi
if [ "$INSTALL_NETBIRD" = "y" ] || [ "$INSTALL_NETBIRD" = "Y" ]; then
    echo "  ✓ NetBird - Mesh VPN"
fi
if [ "$INSTALL_WIREGUARD" = "y" ] || [ "$INSTALL_WIREGUARD" = "Y" ]; then
    echo "  ✓ WireGuard - VPN"
fi
if [ "$INSTALL_TAILSCALE" = "y" ] || [ "$INSTALL_TAILSCALE" = "Y" ]; then
    echo "  ✓ Tailscale - Mesh VPN"
fi
if [ "$INSTALL_RUSTDESK" = "y" ] || [ "$INSTALL_RUSTDESK" = "Y" ]; then
    echo "  ✓ RustDesk - Remote desktop"
fi
if [ "$INSTALL_TEAMVIEWER" = "y" ] || [ "$INSTALL_TEAMVIEWER" = "Y" ]; then
    echo "  ✓ TeamViewer - Remote desktop"
fi
if [ "$INSTALL_MESHCENTRAL" = "y" ] || [ "$INSTALL_MESHCENTRAL" = "Y" ]; then
    echo "  ✓ MeshCentral Agent - Remote management"
fi
if [ "$SETUP_BACKUP" = "y" ] || [ "$SETUP_BACKUP" = "Y" ]; then
    echo "  ✓ Local backup configured (rsync)"
fi
if [ "$SETUP_CLOUD_BACKUP" = "y" ] || [ "$SETUP_CLOUD_BACKUP" = "Y" ]; then
    echo "  ✓ Cloud backup configured (rclone)"
fi
if [ "$CONFIGURE_UFW" = "y" ] || [ "$CONFIGURE_UFW" = "Y" ]; then
    echo "  ✓ UFW Firewall - enabled"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "SSH AUTHENTICATION SETUP"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "  SSH Key for This Computer:"
if [ "$GENERATE_KEY" = "y" ] || [ "$GENERATE_KEY" = "Y" ]; then
    if [ -f "$ACTUAL_HOME/.ssh/id_rsa.pub" ]; then
echo "    ✓ 4096-bit RSA key generated"
echo "    Public key: $ACTUAL_HOME/.ssh/id_rsa.pub"
echo "    View with: cat ~/.ssh/id_rsa.pub"
    else
echo "    ✗ Key generation was attempted but may have failed"
    fi
else
echo "    Not generated (skipped during install)"
echo "    Generate later: ssh-keygen -t rsa -b 4096 -C \"your@email.com\""
fi
echo ""
echo "  SSH Server Status:"
if [ "$KEYS_IMPORTED" = true ]; then
echo "    Password authentication: DISABLED (key-only access)"
echo "    Imported SSH keys: ~/.ssh/authorized_keys"
else
echo "    Password authentication: ENABLED"
fi
echo ""
echo "  Traditional SSH Access:"
echo "    - Uses keys from GitHub/Launchpad (if imported)"
echo "    - Connect with: ssh user@hostname"
if [ "$KEYS_IMPORTED" = true ]; then
echo "    - Password login: DISABLED (keys required)"
else
echo "    - Password login: ENABLED"
fi
echo ""
echo "  NetBird SSH Access (v0.60.0+ method):"
echo "    - Requires openssh-server running (installed by this script)"
echo "    - Enable per-peer in NetBird dashboard: Peers > [peer] > SSH"
echo "    - NetBird injects /etc/ssh/sshd_config.d/99-netbird.conf"
echo "    - Connect from another peer: ssh user@<netbird-ip>"
echo "    - Get peer IPs with: netbird status"
echo "    - Note: old 'netbird ssh <peer-name>' command no longer works"
echo ""
echo "  You can use ANY combination:"
echo "    ✓ GitHub + Launchpad + NetBird SSH"
echo "    ✓ GitHub + NetBird SSH"
echo "    ✓ Launchpad + NetBird SSH"
echo "    ✓ GitHub + Launchpad (no NetBird)"
echo "    ✓ Just GitHub or just Launchpad"
echo "    ✓ Just NetBird SSH"
echo "    ✓ None (password auth only - if no keys imported)"
echo ""
if [ "$SETUP_BACKUP" = "y" ] || [ "$SETUP_BACKUP" = "Y" ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "BACKUP SYSTEM - $BACKUP_TOOL ($BACKUP_MODE mode)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "  Mount points:"
echo "    $ACTUAL_HOME/drives/primary"
echo "    $ACTUAL_HOME/drives/backup1"
if [ "$BACKUP_MODE" = "split" ]; then
echo "    $ACTUAL_HOME/drives/backup2"
fi
echo ""
echo "  Backup script: /usr/local/bin/backup-scripts/${BACKUP_TOOL}-backup.sh"
echo "  Log file: /var/log/${BACKUP_TOOL}-backup.log"
echo ""
if [ "$BACKUP_MODE" = "split" ]; then
echo "  ⚠️  CONFIGURE BEFORE RUNNING:"
echo "     Edit the script to set which folders go to which backup drive."
echo "     sudo nano /usr/local/bin/backup-scripts/${BACKUP_TOOL}-backup.sh"
echo ""
fi
echo "  Quick start:"
echo "    1. Test (dry-run):  sudo ${BACKUP_TOOL}-backup.sh --dry-run  # (edit script first)"
echo "    2. Run manually:    sudo /usr/local/bin/backup-scripts/${BACKUP_TOOL}-backup.sh"
echo "    3. Enable auto:     sudo systemctl enable ${BACKUP_TOOL}-backup.timer"
echo "                        sudo systemctl start ${BACKUP_TOOL}-backup.timer"
echo ""
echo "  Monitor: tail -f /var/log/${BACKUP_TOOL}-backup.log"
fi

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "OTHER IMPORTANT NOTES"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [ "$INSTALL_SAMBA" = "y" ] || [ "$INSTALL_SAMBA" = "Y" ]; then
    echo "  Samba File Sharing:"
    if command -v smbd &> /dev/null && grep -q "\[Primary\]" /etc/samba/smb.conf 2>/dev/null; then
        echo "    ✓ Share 'Primary' is accessible at:"
        echo "      Windows: \\\\$(hostname)\\Primary"
        echo "      Mac/Linux: smb://$(hostname)/Primary"
        echo "    • Username: $ACTUAL_USER"
        echo "    • Use the Samba password you just set"
        echo ""
    else
        echo "    ✗ Installation may have failed - check 'systemctl status smbd'"
        echo ""
    fi
fi
if [ "$INSTALL_DOCKER" = "y" ] || [ "$INSTALL_DOCKER" = "Y" ]; then
    echo "  Docker: Log out and back in for group membership to take effect"
    echo ""
fi
if [ "$INSTALL_NETBIRD" = "y" ] || [ "$INSTALL_NETBIRD" = "Y" ]; then
    echo "  NetBird:"
    echo "    1. Run 'netbird up' (opens browser for authentication)"
    echo "    2. View connected peers: netbird status"
    echo "    3. Configure ACLs in dashboard: https://app.netbird.io"
    echo "    4. Enable SSH per-peer: Peers > [peer] > SSH (in dashboard)"
    echo "    5. Connect via SSH: ssh user@<netbird-ip>"
    echo "    Note: --allow-server-ssh is pre-configured in systemd override"
    echo "          (/etc/systemd/system/netbird.service.d/ssh-server.conf)"
    echo "          so SSH works without re-authenticating on every connection"
    echo ""
fi
if [ "$INSTALL_RUSTDESK" = "y" ] || [ "$INSTALL_RUSTDESK" = "Y" ]; then
    echo "  RustDesk: Launch from applications menu or run 'rustdesk'"
    echo ""
fi
if [ "$INSTALL_FAIL2BAN" = "y" ] || [ "$INSTALL_FAIL2BAN" = "Y" ]; then
    echo "  fail2ban:"
    echo "    • Check status: sudo fail2ban-client status sshd"
    echo "    • View banned IPs: sudo fail2ban-client status sshd"
    echo "    • Unban IP: sudo fail2ban-client set sshd unbanip <IP>"
    echo ""
fi
if [ "$CONFIGURE_UFW" = "y" ] || [ "$CONFIGURE_UFW" = "Y" ]; then
    echo "  UFW Firewall:"
    echo "    • Check status: sudo ufw status"
    echo "    • Allow port: sudo ufw allow <port>"
    echo "    • Deny port: sudo ufw deny <port>"
    echo "    • Disable: sudo ufw disable"
    echo ""
fi