#!/bin/bash

#===============================================================================
# Docker + Felhom Infrastructure Setup v4.0
# Prepares a Debian 13 server for Felhom homeserver deployment
#
# This script sets up the infrastructure:
#   - Docker Engine + Compose
#   - Traefik reverse proxy
#   - TLS certificates (Let's Encrypt via Cloudflare DNS or self-signed)
#   - Cloudflare Tunnel connector (optional, for external access)
#   - FileBrowser Quantum (web-based file manager for HDD data)
#   - Felhom Controller (deployed separately)
#
# Application stacks are managed via the felhom-controller dashboard.
#
# Usage:
#   ./docker-setup.sh --bootstrap    # Install sudo (fresh Debian only)
#   sudo ./docker-setup.sh [OPTIONS] # Run full setup
#
# Options:
#   --bootstrap           Install sudo and configure sudoers
#   --ip ADDRESS          Static IP address (e.g., 192.168.0.50)
#   --gateway ADDRESS     Gateway address (default: 192.168.0.1)
#   --dns ADDRESS         DNS server (default: 1.1.1.1,8.8.8.8)
#   --interface NAME      Network interface (default: auto-detect)
#   --domain DOMAIN       Base domain for services (default: homeserver.local)
#   --email EMAIL         Email for Let's Encrypt certificates
#   --cf-token TOKEN      Cloudflare API token for DNS-01 challenge
#   --cf-tunnel-token TK  Cloudflare Tunnel token for external access
#   --traefik-password PW Password for Traefik dashboard (default: auto-generated)
#   --self-signed-cert    Generate self-signed wildcard certificate
#   --skip-filebrowser    Skip FileBrowser installation
#   --dry-run             Show what would be done without making changes
#   --debug               Enable bash debug tracing (set -x)
#   -h, --help            Show this help message
#
# Example:
#   sudo ./docker-setup.sh --domain demo-felhom.eu --customer demo-felhom \
#       --email certs@felhom.eu --cf-token cf-xxx --cf-tunnel-token eyJhIjo...
#
#===============================================================================

set -euo pipefail

#-------------------------------------------------------------------------------
# Bootstrap: Install sudo on fresh Debian
#-------------------------------------------------------------------------------
bootstrap_sudo() {
    echo ""
    echo "==========================================="
    echo "  Sudo Bootstrap for Fresh Debian Install"
    echo "==========================================="
    echo ""
    
    # Check if current user already has working sudo
    if command -v sudo &> /dev/null; then
        local check_user=""
        if [[ $EUID -ne 0 ]]; then
            check_user="$(whoami)"
        else
            check_user="${SUDO_USER:-}"
        fi
        if [[ -n "$check_user" ]] && sudo -u "$check_user" sudo -n true 2>/dev/null; then
            echo -e "\033[0;32m[INFO]\033[0m sudo is already installed and configured for '$check_user'!"
            exit 0
        fi
    fi
    
    local target_user=""
    if [[ -n "${SUDO_USER:-}" ]]; then
        target_user="$SUDO_USER"
    elif [[ $EUID -ne 0 ]]; then
        target_user="$(whoami)"
    else
        target_user=$(getent passwd 1000 | cut -d: -f1 || echo "")
        if [[ -z "$target_user" ]]; then
            echo -e "\033[0;31m[ERROR]\033[0m Cannot determine target user"
            exit 1
        fi
    fi
    
    echo "This will install sudo and configure NOPASSWD for '$target_user'"
    echo "You will need to enter the ROOT password."
    read -p "Continue? [y/N] " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        exit 0
    fi
    
    local bootstrap_script
    bootstrap_script=$(mktemp)
    cat > "$bootstrap_script" << ROOTSCRIPT
#!/bin/bash
set -e
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
apt-get update -qq
apt-get install -qq -y curl sudo > /dev/null
usermod -aG sudo "$target_user"
echo "$target_user ALL=(ALL:ALL) NOPASSWD: ALL" > "/etc/sudoers.d/90-${target_user}-nopasswd"
chmod 440 "/etc/sudoers.d/90-${target_user}-nopasswd"
visudo -c -f "/etc/sudoers.d/90-${target_user}-nopasswd"
echo "[SUCCESS] sudo configured. Log out and back in, then run the script with sudo."
ROOTSCRIPT
    
    chmod +x "$bootstrap_script"
    echo "Enter ROOT password:"
    su -c "bash $bootstrap_script"
    rm -f "$bootstrap_script"
    exit 0
}

for arg in "$@"; do
    [[ "$arg" == "--bootstrap" ]] && bootstrap_sudo
done

#-------------------------------------------------------------------------------
# Configuration
#-------------------------------------------------------------------------------
SCRIPT_VERSION="3.6.0"

# Default values
STATIC_IP=""
GATEWAY="192.168.0.1"
DNS_SERVERS_STR="1.1.1.1,8.8.8.8"
INTERFACE=""
BASE_DOMAIN="homeserver.local"
ACME_EMAIL=""
CF_DNS_API_TOKEN=""
CF_TUNNEL_TOKEN=""
TRAEFIK_PASSWORD=""
SKIP_FILEBROWSER=false
HDD_PATH=""
DRY_RUN=false
SELF_SIGNED_CERT=false
DEBUG_MODE=false
CUSTOMER_ID=""

# Directories
DOCKER_DATA_DIR="/opt/docker"
TRAEFIK_DIR="${DOCKER_DATA_DIR}/traefik"
FILEBROWSER_DIR="${DOCKER_DATA_DIR}/stacks/filebrowser"
CLOUDFLARED_DIR="${DOCKER_DATA_DIR}/cloudflared"
CERTS_DIR="${TRAEFIK_DIR}/certs"

#-------------------------------------------------------------------------------
# Colors and logging
#-------------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

log_info()    { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error()   { echo -e "${RED}[ERROR]${NC} $1"; }
log_step()    { echo -e "${BLUE}[STEP]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_skip()    { echo -e "${CYAN}[SKIP]${NC} $1"; }
log_debug()   { [[ "$DEBUG_MODE" == true ]] && echo -e "${CYAN}[DEBUG]${NC} $1" || true; }

on_error() {
    echo ""
    log_error "Script failed at line $1. Collecting diagnostics..."
    echo "--- Network ---"
    ip a 2>/dev/null || true
    ip r 2>/dev/null || true
    echo "--- DNS ---"
    cat /etc/resolv.conf 2>/dev/null || true
    echo "--- DNS Test ---"
    getent hosts download.docker.com 2>/dev/null || echo "DNS resolution failed"
    echo "--- Docker ---"
    systemctl --no-pager status docker 2>/dev/null || true
    docker ps 2>/dev/null || true
    echo "--- Network Manager Detection ---"
    detect_network_manager_debug 2>/dev/null || true
}
trap 'on_error $LINENO' ERR

print_banner() {
    echo ""
    echo -e "${BOLD}${BLUE}-==================================================================¬${NC}"
    echo -e "${BOLD}${BLUE}¦       Docker + Felhom Infrastructure Setup v${SCRIPT_VERSION}              ¦${NC}"
    echo -e "${BOLD}${BLUE}L==================================================================-${NC}"
    echo ""
}

print_help() {
    cat << 'EOF'
Docker + Felhom Infrastructure Setup v4.0

This script prepares a Debian 13 server with Docker infrastructure for Felhom homeserver.
Application stacks are managed via the felhom-controller dashboard.

USAGE:
    ./docker-setup.sh --bootstrap           # First, on fresh Debian
    sudo ./docker-setup.sh [OPTIONS]        # Then, full setup

OPTIONS:
    --bootstrap           Install sudo (run first on fresh Debian)
    --customer ID         Customer identifier (e.g., demo-felhom)
    --ip ADDRESS          Static IP address
    --gateway ADDRESS     Gateway (default: 192.168.0.1)
    --dns ADDRESS         DNS servers, comma-separated (default: 1.1.1.1,8.8.8.8)
    --interface NAME      Network interface (default: auto-detect)
    --domain DOMAIN       Base domain for services (default: homeserver.local)
    --email EMAIL         Email for Let's Encrypt certificates
    --cf-token TOKEN      Cloudflare API token for DNS-01 challenge (recommended)
    --cf-tunnel-token TK  Cloudflare Tunnel token for external access
    --hdd-path PATH       HDD mount path (e.g., /mnt/hdd_1) for FileBrowser
    --traefik-password PW Password for Traefik dashboard (default: auto-generated)
    --self-signed-cert    Generate self-signed wildcard certificate (fallback)
    --skip-filebrowser    Skip FileBrowser installation
    --dry-run             Show what would be done
    --debug               Enable verbose debug output
    -h, --help            Show this help

TLS CERTIFICATE OPTIONS:
    There are three TLS modes (in order of preference):

    1. Let's Encrypt + Cloudflare DNS (recommended for Felhom customers):
       --email certs@felhom.eu --cf-token <cloudflare-api-token>
       Uses DNS-01 challenge. Works with Cloudflare Tunnel, no port 80 needed.

    2. Let's Encrypt + HTTP-01 (public servers without Cloudflare Tunnel):
       --email admin@example.com
       Requires port 80 open to the internet.

    3. Self-signed certificate (offline / no internet):
       --self-signed-cert
       Generates a CA + wildcard cert. Requires manual CA import on all devices.

WHAT THIS SCRIPT INSTALLS:
    1. Base packages (curl, git, htop, etc.)
    2. Docker Engine + Docker Compose
    3. Traefik reverse proxy (with dashboard)
    4. Cloudflare Tunnel connector (if --cf-tunnel-token provided)
    5. TLS certificates (Let's Encrypt or self-signed)
    6. FileBrowser Quantum (web file manager at files.<domain>)
    7. Helper tools (ctop, lazydocker, shell aliases)

DEPLOYING APPLICATIONS:
    After infrastructure setup, deploy the felhom-controller to manage apps
    via the web dashboard at felhom.<domain>.

EXAMPLES:
    # Felhom customer deployment (recommended — full stack with tunnel)
    sudo ./docker-setup.sh --domain demo-felhom.eu --customer demo-felhom \
        --email certs@felhom.eu --cf-token cf-xxxxxxxxxxxx \
        --cf-tunnel-token eyJhIjoixxxxxxxx...

    # Without tunnel (local access only, or custom ingress)
    sudo ./docker-setup.sh --domain demo-felhom.eu --customer demo-felhom \
        --email certs@felhom.eu --cf-token cf-xxxxxxxxxxxx

    # Self-signed cert (offline/testing)
    sudo ./docker-setup.sh --domain example.com --self-signed-cert

    # Full setup with static IP
    sudo ./docker-setup.sh --domain demo-felhom.eu --customer demo-felhom \
        --ip 192.168.0.50 --email certs@felhom.eu --cf-token cf-xxxxxxxxxxxx \
        --cf-tunnel-token eyJhIjoixxxxxxxx...
EOF
}

#-------------------------------------------------------------------------------
# Argument parsing (with missing-value protection for set -u)
#-------------------------------------------------------------------------------
require_arg() {
    if [[ $# -lt 2 || "$2" == --* ]]; then
        log_error "Option '$1' requires a value"
        exit 1
    fi
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --bootstrap) bootstrap_sudo ;;
            --ip)
                require_arg "$1" "${2:-}"
                STATIC_IP="$2"; shift 2 ;;
            --gateway)
                require_arg "$1" "${2:-}"
                GATEWAY="$2"; shift 2 ;;
            --dns)
                require_arg "$1" "${2:-}"
                DNS_SERVERS_STR="$2"; shift 2 ;;
            --interface)
                require_arg "$1" "${2:-}"
                INTERFACE="$2"; shift 2 ;;
            --domain)
                require_arg "$1" "${2:-}"
                BASE_DOMAIN="$2"; shift 2 ;;
            --email)
                require_arg "$1" "${2:-}"
                ACME_EMAIL="$2"; shift 2 ;;
            --cf-token)
                require_arg "$1" "${2:-}"
                CF_DNS_API_TOKEN="$2"; shift 2 ;;
            --cf-tunnel-token)
                require_arg "$1" "${2:-}"
                CF_TUNNEL_TOKEN="$2"; shift 2 ;;
            --traefik-password)
                require_arg "$1" "${2:-}"
                TRAEFIK_PASSWORD="$2"; shift 2 ;;
            --customer)
                require_arg "$1" "${2:-}"
                CUSTOMER_ID="$2"; shift 2 ;;
            --self-signed-cert) SELF_SIGNED_CERT=true; shift ;;
            --skip-filebrowser) SKIP_FILEBROWSER=true; shift ;;
            --hdd-path) HDD_PATH="$2"; shift 2 ;;
            --dry-run) DRY_RUN=true; shift ;;
            --debug) DEBUG_MODE=true; shift ;;
            -h|--help) print_help; exit 0 ;;
            *) log_error "Unknown option: $1"; print_help; exit 1 ;;
        esac
    done
    
    # Generate Traefik password if not provided (use enough random bytes)
    if [[ -z "$TRAEFIK_PASSWORD" ]]; then
        TRAEFIK_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 16)
        # Fallback if openssl somehow produces too few chars
        if [[ ${#TRAEFIK_PASSWORD} -lt 12 ]]; then
            TRAEFIK_PASSWORD=$(head -c 32 /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16)
        fi
    fi
    
    # Validate IP format if provided
    if [[ -n "$STATIC_IP" ]]; then
        if ! [[ "$STATIC_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
            log_error "Invalid IP address format: $STATIC_IP"
            exit 1
        fi
        # Validate each octet is 0-255
        IFS='.' read -ra OCTETS <<< "$STATIC_IP"
        for octet in "${OCTETS[@]}"; do
            if (( octet > 255 )); then
                log_error "Invalid IP address (octet > 255): $STATIC_IP"
                exit 1
            fi
        done
    fi
    
    # Validate gateway format if static IP is set
    if [[ -n "$STATIC_IP" && -n "$GATEWAY" ]]; then
        if ! [[ "$GATEWAY" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
            log_error "Invalid gateway address format: $GATEWAY"
            exit 1
        fi
    fi
    
    # Validate TLS configuration
    if [[ -n "$CF_DNS_API_TOKEN" && -z "$ACME_EMAIL" ]]; then
        log_error "--cf-token requires --email for Let's Encrypt registration"
        exit 1
    fi
    
    if [[ -n "$ACME_EMAIL" && -z "$CF_DNS_API_TOKEN" ]]; then
        log_warn "Let's Encrypt with HTTP-01 challenge (no --cf-token)"
        log_warn "This requires port 80 open to the internet"
        log_warn "It will NOT work with Cloudflare Tunnel!"
        log_warn "For Cloudflare Tunnel setups, use: --email EMAIL --cf-token TOKEN"
    fi
    
    if [[ -n "$ACME_EMAIL" && "$SELF_SIGNED_CERT" == true ]]; then
        log_warn "Both --email and --self-signed-cert specified"
        log_warn "Let's Encrypt will be used; self-signed cert will serve as fallback"
    fi
    
    # Warn if tunnel token provided without Cloudflare DNS (unusual but valid)
    if [[ -n "$CF_TUNNEL_TOKEN" && -z "$CF_DNS_API_TOKEN" ]]; then
        log_warn "Cloudflare Tunnel without DNS-01 challenge"
        log_warn "External HTTPS will work, but certs must be managed separately"
    fi
    
    # Validate CUSTOMER_ID format if provided
    if [[ -n "$CUSTOMER_ID" ]]; then
        if [[ ! "$CUSTOMER_ID" =~ ^[a-zA-Z0-9_-]+$ ]]; then
            log_error "Customer ID must be alphanumeric (hyphens/underscores allowed): $CUSTOMER_ID"
            exit 1
        fi
    fi
}

#-------------------------------------------------------------------------------
# Helper functions
#-------------------------------------------------------------------------------
check_root() {
    if [[ $EUID -ne 0 ]]; then
        log_error "This script must be run as root (use sudo)"
        exit 1
    fi
}

check_debian() {
    if ! grep -qi "debian" /etc/os-release 2>/dev/null; then
        log_warn "This script is designed for Debian. Proceeding anyway..."
    fi
}

get_current_user() {
    if [[ -n "${SUDO_USER:-}" ]]; then
        echo "$SUDO_USER"
    else
        echo "root"
    fi
}

get_server_ip() {
    if [[ -n "$STATIC_IP" ]]; then
        echo "$STATIC_IP"
    else
        hostname -I | awk '{print $1}'
    fi
}

detect_interface() {
    if [[ -n "$INTERFACE" ]]; then
        echo "$INTERFACE"
    else
        ip route | grep default | awk '{print $5}' | head -1
    fi
}

# Detect which network manager is active on the system
# Returns: "ifupdown", "networkmanager", "systemd-networkd", or "unknown"
detect_network_manager() {
    # Check what's actively managing the default route interface
    if systemctl is-active --quiet NetworkManager 2>/dev/null; then
        echo "networkmanager"
    elif systemctl is-active --quiet systemd-networkd 2>/dev/null; then
        echo "systemd-networkd"
    elif [[ -f /etc/network/interfaces ]] && dpkg -l ifupdown 2>/dev/null | grep -q '^ii'; then
        echo "ifupdown"
    else
        echo "unknown"
    fi
}

# Debug version for error diagnostics
detect_network_manager_debug() {
    echo "NetworkManager: $(systemctl is-active NetworkManager 2>/dev/null || echo 'not found')"
    echo "systemd-networkd: $(systemctl is-active systemd-networkd 2>/dev/null || echo 'not found')"
    echo "ifupdown installed: $(dpkg -l ifupdown 2>/dev/null | grep -q '^ii' && echo 'yes' || echo 'no')"
    echo "/etc/network/interfaces exists: $([[ -f /etc/network/interfaces ]] && echo 'yes' || echo 'no')"
    echo "Active network manager: $(detect_network_manager)"
}

# Calculate total number of steps dynamically
get_total_steps() {
    local total=5  # base: packages, network, docker, traefik, tools
    [[ -n "$CF_TUNNEL_TOKEN" ]] && ((total++))
    [[ "$SELF_SIGNED_CERT" == true ]] && ((total++))
    [[ "$SKIP_FILEBROWSER" != true ]] && ((total++))
    echo "$total"
}

#-------------------------------------------------------------------------------
# Step 1: Install base packages
#-------------------------------------------------------------------------------
install_base_packages() {
    log_step "1/$(get_total_steps) - Installing base packages..."
    
    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${CYAN}[DRY-RUN]${NC} Would install: curl wget gnupg lsb-release ca-certificates git htop jq apache2-utils openssl"
        return
    fi
    
    apt-get update -qq
    apt-get install -qq -y \
        curl \
        wget \
        gnupg \
        lsb-release \
        ca-certificates \
        apt-transport-https \
        git \
        htop \
        jq \
        apache2-utils \
        openssl \
        resolvconf \
        > /dev/null
    
    log_success "Base packages installed"
}

#-------------------------------------------------------------------------------
# Step 2: Configure static IP (optional, auto-detects network manager)
#-------------------------------------------------------------------------------

# Apply DNS immediately so it works without reboot
# The network manager hooks will take over on next boot
apply_dns_immediately() {
    local iface="$1"
    shift
    local dns_servers=("$@")
    
    log_info "Applying DNS immediately..."
    
    # Method 1: Feed to resolvconf (if installed)
    if command -v resolvconf &>/dev/null; then
        local ns_lines=""
        for dns in "${dns_servers[@]}"; do
            ns_lines+="nameserver ${dns}"$'\n'
        done
        echo "$ns_lines" | resolvconf -a "${iface}.inet"
        log_debug "DNS fed to resolvconf for ${iface}.inet"
    fi
    
    # Method 2: Direct write as fallback / belt-and-suspenders
    # Only if resolv.conf is empty or has no nameserver lines
    if ! grep -q "^nameserver" /etc/resolv.conf 2>/dev/null; then
        log_warn "/etc/resolv.conf has no nameservers, writing directly"
        {
            echo "# Written by docker-setup.sh as fallback"
            for dns in "${dns_servers[@]}"; do
                echo "nameserver ${dns}"
            done
        } > /etc/resolv.conf
    fi
    
    # Verify DNS works
    if getent hosts download.docker.com &>/dev/null; then
        log_success "DNS resolution working"
    else
        log_warn "DNS resolution test failed — may need manual intervention"
    fi
}

configure_static_ip() {
    log_step "2/$(get_total_steps) - Configuring network..."
    
    if [[ -z "$STATIC_IP" ]]; then
        log_skip "No static IP specified, keeping DHCP"
        return
    fi
    
    if [[ "$DRY_RUN" == true ]]; then
        local nm_type
        nm_type=$(detect_network_manager)
        echo -e "${CYAN}[DRY-RUN]${NC} Would configure static IP: $STATIC_IP (via $nm_type)"
        return
    fi
    
    local iface
    iface=$(detect_interface)
    if [[ -z "$iface" ]]; then
        log_error "Could not detect network interface. Use --interface to specify."
        exit 1
    fi
    
    # Parse DNS servers
    IFS=',' read -ra DNS_ARRAY <<< "$DNS_SERVERS_STR"
    
    local nm_type
    nm_type=$(detect_network_manager)
    log_info "Detected network manager: $nm_type"
    
    case "$nm_type" in
        networkmanager)
            configure_static_ip_nm "$iface"
            ;;
        systemd-networkd)
            configure_static_ip_networkd "$iface"
            ;;
        ifupdown)
            configure_static_ip_ifupdown "$iface"
            ;;
        *)
            log_warn "Unknown network manager. Attempting ifupdown configuration..."
            # Install ifupdown as fallback
            if ! dpkg -l ifupdown 2>/dev/null | grep -q '^ii'; then
                log_info "Installing ifupdown..."
                apt-get install -qq -y ifupdown > /dev/null
            fi
            configure_static_ip_ifupdown "$iface"
            ;;
    esac
    
    # Apply IP and DNS immediately (don't wait for reboot)
    log_info "Applying network changes immediately..."
    
    # Apply static IP directly via ip command
    local current_ip
    current_ip=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1)
    if [[ "$current_ip" != "$STATIC_IP" ]]; then
        ip addr flush dev "$iface" 2>/dev/null || true
        ip addr add "${STATIC_IP}/24" dev "$iface" 2>/dev/null || true
        ip route add default via "$GATEWAY" dev "$iface" 2>/dev/null || true
        ip link set "$iface" up
        log_info "Static IP $STATIC_IP applied to $iface"
    else
        log_debug "IP already set to $STATIC_IP, skipping"
    fi
    
    # Apply DNS immediately
    apply_dns_immediately "$iface" "${DNS_ARRAY[@]}"
    
    log_success "Network configured"
}

configure_static_ip_nm() {
    local iface="$1"
    log_info "Configuring static IP via NetworkManager..."
    
    # Find the active connection name for this interface
    local conn_name
    conn_name=$(nmcli -t -f NAME,DEVICE con show --active | grep ":${iface}$" | cut -d: -f1 | head -1)
    
    if [[ -z "$conn_name" ]]; then
        log_warn "No active NM connection for $iface, creating one..."
        conn_name="static-${iface}"
        nmcli con add type ethernet ifname "$iface" con-name "$conn_name"
    fi
    
    local dns_space="${DNS_ARRAY[*]}"
    
    nmcli con mod "$conn_name" \
        ipv4.method manual \
        ipv4.addresses "${STATIC_IP}/24" \
        ipv4.gateway "$GATEWAY" \
        ipv4.dns "${dns_space// /,}"
    
    log_info "Static IP configured: $STATIC_IP on $iface (NetworkManager)"
    log_warn "Run 'nmcli con up \"$conn_name\"' or reboot to apply."
}

configure_static_ip_networkd() {
    local iface="$1"
    log_info "Configuring static IP via systemd-networkd..."
    
    local config_file="/etc/systemd/network/10-static-${iface}.network"
    
    # Backup existing config
    if [[ -f "$config_file" ]]; then
        cp "$config_file" "${config_file}.bak.$(date +%s)"
    fi
    
    local dns_lines=""
    for dns in "${DNS_ARRAY[@]}"; do
        dns_lines+="DNS=${dns}"$'\n'
    done
    
    cat > "$config_file" << EOF
# Static IP configuration for $iface
# Generated by docker-setup.sh on $(date)
[Match]
Name=$iface

[Network]
Address=${STATIC_IP}/24
Gateway=$GATEWAY
${dns_lines}
EOF

    log_info "Static IP configured: $STATIC_IP on $iface (systemd-networkd)"
    log_warn "Run 'systemctl restart systemd-networkd' or reboot to apply."
}

configure_static_ip_ifupdown() {
    local iface="$1"
    log_info "Configuring static IP via ifupdown..."
    
    local config_file="/etc/network/interfaces.d/${iface}-static"
    local main_interfaces="/etc/network/interfaces"
    
    # Backup existing drop-in config
    if [[ -f "$config_file" ]]; then
        cp "$config_file" "${config_file}.bak.$(date +%s)"
    fi
    
    local dns_line="${DNS_ARRAY[*]}"
    
    # Handle conflict: if the interface is configured in /etc/network/interfaces,
    # we must comment it out or the two configs will fight each other.
    # On fresh Debian 13, enp* is typically defined here with DHCP.
    if grep -qE "^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+${iface}" "$main_interfaces" 2>/dev/null; then
        log_warn "Interface $iface is configured in $main_interfaces — commenting out to avoid conflict"
        cp "$main_interfaces" "${main_interfaces}.bak.$(date +%s)"
        
        # Comment out the entire stanza block for this interface
        # awk handles: header lines (auto/allow-hotplug/iface) AND their
        # continuation lines (address, netmask, gateway, dns-nameservers, etc.)
        # which are indented lines following an iface declaration
        awk -v iface="$iface" '
        BEGIN { in_stanza = 0 }
        /^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+/ {
            if ($0 ~ iface) {
                in_stanza = 1
                print "# [docker-setup] " $0
                next
            } else {
                in_stanza = 0
            }
        }
        in_stanza && /^[[:space:]]+/ {
            print "# [docker-setup] " $0
            next
        }
        in_stanza && /^[^#[:space:]]/ {
            in_stanza = 0
        }
        { print }
        ' "$main_interfaces" > "${main_interfaces}.tmp" && mv "${main_interfaces}.tmp" "$main_interfaces"
        
        log_info "Original config backed up and conflicting lines commented out"
        
        if [[ "$DEBUG_MODE" == true ]]; then
            log_debug "Modified $main_interfaces:"
            grep -n "${iface}" "$main_interfaces" || true
        fi
    fi
    
    cat > "$config_file" << EOF
# Static IP configuration for $iface
# Generated by docker-setup.sh on $(date)
auto $iface
iface $iface inet static
    address $STATIC_IP
    netmask 255.255.255.0
    gateway $GATEWAY
    dns-nameservers $dns_line
EOF

    log_info "Static IP configured: $STATIC_IP on $iface (ifupdown)"
    log_info "Config written to: $config_file"
}

#-------------------------------------------------------------------------------
# Step 3: Install Docker
#-------------------------------------------------------------------------------
install_docker() {
    log_step "3/$(get_total_steps) - Installing Docker..."
    
    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${CYAN}[DRY-RUN]${NC} Would install Docker Engine + Compose"
        return
    fi
    
    # Check if Docker is already installed
    if command -v docker &> /dev/null; then
        log_info "Docker already installed: $(docker --version)"
    else
        # Add Docker's official GPG key
        install -m 0755 -d /etc/apt/keyrings
        
        # Only re-import GPG key if not already present or outdated
        if [[ ! -f /etc/apt/keyrings/docker.gpg ]] || \
           [[ $(find /etc/apt/keyrings/docker.gpg -mtime +30 2>/dev/null) ]]; then
            curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
            chmod a+r /etc/apt/keyrings/docker.gpg
            log_debug "Docker GPG key imported"
        else
            log_debug "Docker GPG key already present and recent, skipping import"
        fi
        
        # Determine codename - fall back to bookworm if trixie repo doesn't exist
        local codename
        codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
        log_debug "Detected Debian codename: $codename"
        
        # Add the repository (single line, no extra whitespace)
        echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${codename} stable" \
            > /etc/apt/sources.list.d/docker.list
        
        # Test if the repo works, fall back to bookworm if not
        if ! apt-get update -qq 2>/dev/null; then
            log_warn "Docker repo for '${codename}' failed, falling back to 'bookworm'..."
            echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" \
                > /etc/apt/sources.list.d/docker.list
            apt-get update -qq
        fi
        
        apt-get install -qq -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin > /dev/null
    fi
    
    # Add current user to docker group
    local current_user
    current_user=$(get_current_user)
    if [[ "$current_user" != "root" ]]; then
        usermod -aG docker "$current_user"
        log_info "Added $current_user to docker group"
    fi
    
    # Configure Docker DNS fallback in daemon.json
    # This ensures all containers get proper DNS even if host resolv.conf is empty
    local daemon_json="/etc/docker/daemon.json"
    if [[ -f "$daemon_json" ]]; then
        # Merge dns into existing config (if not already present)
        if ! grep -q '"dns"' "$daemon_json"; then
            log_info "Adding DNS fallback to existing $daemon_json"
            local tmp_json
            tmp_json=$(mktemp)
            jq '. + {"dns": ["1.1.1.1", "8.8.8.8"]}' "$daemon_json" > "$tmp_json" 2>/dev/null && \
                mv "$tmp_json" "$daemon_json" || {
                    log_warn "Failed to merge daemon.json with jq, overwriting"
                    rm -f "$tmp_json"
                    echo '{ "dns": ["1.1.1.1", "8.8.8.8"] }' > "$daemon_json"
                }
        else
            log_debug "daemon.json already has DNS config"
        fi
    else
        log_info "Creating $daemon_json with DNS fallback"
        echo '{ "dns": ["1.1.1.1", "8.8.8.8"] }' > "$daemon_json"
    fi
    
    # Enable and start Docker
    systemctl enable --now docker
    
    # Wait for Docker to be fully ready
    log_info "Waiting for Docker daemon..."
    local docker_ready=false
    for i in {1..30}; do
        if docker info &>/dev/null; then
            docker_ready=true
            log_debug "Docker ready after ${i}s"
            break
        fi
        sleep 1
    done
    
    if [[ "$docker_ready" != true ]]; then
        log_error "Docker daemon did not start within 30 seconds"
        systemctl --no-pager status docker || true
        exit 1
    fi
    
    # Create docker network for Traefik
    if docker network inspect traefik-public &>/dev/null; then
        log_debug "traefik-public network already exists"
    else
        docker network create traefik-public
        log_debug "Created traefik-public network"
    fi
    
    log_success "Docker installed: $(docker --version)"
    log_success "Docker Compose: $(docker compose version)"
}

#-------------------------------------------------------------------------------
# Step 4: Install Traefik (reverse proxy)
#-------------------------------------------------------------------------------
install_traefik() {
    log_step "4/$(get_total_steps) - Installing Traefik reverse proxy..."
    
    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${CYAN}[DRY-RUN]${NC} Would install Traefik"
        return
    fi
    
    mkdir -p "${TRAEFIK_DIR}/dynamic"
    mkdir -p "${CERTS_DIR}"
    
    # Create acme.json for Let's Encrypt certificates
    touch "${TRAEFIK_DIR}/acme.json"
    chmod 600 "${TRAEFIK_DIR}/acme.json"
    
    # Generate basicAuth hash
    local auth_hash
    auth_hash=$(htpasswd -nb admin "$TRAEFIK_PASSWORD")
    
    # Determine certificate resolver configuration
    local cert_resolver_config=""
    local entrypoint_tls_config=""
    
    if [[ -n "$ACME_EMAIL" ]]; then
        if [[ -n "$CF_DNS_API_TOKEN" ]]; then
            # DNS-01 challenge via Cloudflare (works with Cloudflare Tunnel)
            cert_resolver_config="
certificatesResolvers:
  letsencrypt:
    acme:
      email: ${ACME_EMAIL}
      storage: /etc/traefik/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - \"1.1.1.1:53\"
          - \"8.8.8.8:53\""
            log_info "Let's Encrypt: Cloudflare DNS-01 challenge (works with Tunnel)"
        else
            # HTTP-01 challenge (requires port 80 accessible from internet)
            cert_resolver_config="
certificatesResolvers:
  letsencrypt:
    acme:
      email: ${ACME_EMAIL}
      storage: /etc/traefik/acme.json
      httpChallenge:
        entryPoint: web"
            log_info "Let's Encrypt: HTTP-01 challenge (requires public port 80)"
        fi
        
        # Set default certResolver on websecure entrypoint
        # This means ALL routers on websecure automatically get Let's Encrypt certs
        # without needing certresolver labels on each individual service
        entrypoint_tls_config="    http:
      tls:
        certResolver: letsencrypt"
    fi
    
    # Create Traefik static configuration
    cat > "${TRAEFIK_DIR}/traefik.yml" << EOF
# Traefik Static Configuration
# Generated by docker-setup.sh v${SCRIPT_VERSION}

api:
  dashboard: true
  insecure: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
${entrypoint_tls_config}

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik-public
  file:
    directory: /etc/traefik/dynamic
    watch: true

log:
  level: INFO

accessLog: {}
${cert_resolver_config}
EOF

    # Determine dashboard TLS config
    local dashboard_tls_config="tls: {}"
    if [[ -n "$ACME_EMAIL" ]]; then
        dashboard_tls_config="tls:
        certResolver: letsencrypt"
    fi

    # Create dynamic configuration for Traefik dashboard
    cat > "${TRAEFIK_DIR}/dynamic/dashboard.yml" << EOF
# Traefik Dashboard Configuration
http:
  routers:
    dashboard:
      rule: "Host(\`traefik.${BASE_DOMAIN}\`)"
      service: api@internal
      entryPoints:
        - websecure
      ${dashboard_tls_config}
      middlewares:
        - dashboard-auth

  middlewares:
    dashboard-auth:
      basicAuth:
        users:
          - "${auth_hash}"
EOF

    # Create Cloudflare .env file if DNS-01 challenge is used
    local cf_env_section=""
    if [[ -n "$CF_DNS_API_TOKEN" ]]; then
        cat > "${TRAEFIK_DIR}/.env" << ENVEOF
# Cloudflare API token for Let's Encrypt DNS-01 challenge
# Token needs Zone:DNS:Edit permission for the domain zone
CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
ENVEOF
        chmod 600 "${TRAEFIK_DIR}/.env"
        cf_env_section="    env_file:
      - .env"
        log_info "Cloudflare API token stored in ${TRAEFIK_DIR}/.env"
    fi

    # Create docker-compose.yml for Traefik
    cat > "${TRAEFIK_DIR}/docker-compose.yml" << EOF
# Traefik Reverse Proxy
services:
  traefik:
    image: traefik:v3.6.7
    container_name: traefik
    restart: unless-stopped
    dns:
      - 1.1.1.1
      - 8.8.8.8
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
${cf_env_section}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - ./acme.json:/etc/traefik/acme.json
      - ./certs:/etc/traefik/certs:ro
    networks:
      - traefik-public

networks:
  traefik-public:
    external: true
EOF

    # Start Traefik
    cd "${TRAEFIK_DIR}"
    log_info "Pulling Traefik image..."
    docker compose pull -q
    log_info "Starting Traefik..."
    docker compose up -d --quiet-pull
    
    # Verify Traefik started
    sleep 3
    if docker ps --filter "name=traefik" --filter "status=running" -q | grep -q .; then
        log_success "Traefik installed and running"
    else
        log_error "Traefik container is not running!"
        docker compose logs --tail=20 traefik
        exit 1
    fi
}

#-------------------------------------------------------------------------------
# Step 4b: Install Cloudflare Tunnel connector (optional)
#-------------------------------------------------------------------------------
install_cloudflare_tunnel() {
    if [[ -z "$CF_TUNNEL_TOKEN" ]]; then
        log_skip "Cloudflare Tunnel not configured (no --cf-tunnel-token)"
        return
    fi

    log_step "5/$(get_total_steps) - Installing Cloudflare Tunnel connector..."

    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${CYAN}[DRY-RUN]${NC} Would deploy cloudflared tunnel connector"
        return
    fi

    mkdir -p "$CLOUDFLARED_DIR"

    cat > "${CLOUDFLARED_DIR}/docker-compose.yml" << EOF
# Cloudflare Tunnel — External access connector
# Routes are configured in the Cloudflare dashboard:
#   Zero Trust > Networks > Tunnels > <tunnel-name> > Public Hostname
#
# This container connects to Cloudflare's edge network and routes
# external traffic to Traefik (which handles TLS + routing internally).
#
# Deployed by docker-setup.sh v${SCRIPT_VERSION}
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=${CF_TUNNEL_TOKEN}
    dns:
      - 1.1.1.1
      - 8.8.8.8
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik-public

networks:
  traefik-public:
    external: true
EOF

    cd "$CLOUDFLARED_DIR"
    log_info "Pulling cloudflared image..."
    docker compose pull -q
    log_info "Starting Cloudflare Tunnel..."
    docker compose up -d --quiet-pull

    # cloudflared takes a few seconds to establish the tunnel
    sleep 5
    if docker ps --filter "name=cloudflared" --filter "status=running" -q | grep -q .; then
        # Check logs for successful connection indicator
        if docker logs cloudflared 2>&1 | grep -qi "registered tunnel connection"; then
            log_success "Cloudflare Tunnel connected"
        else
            log_success "Cloudflare Tunnel container running (connection establishing...)"
            log_info "Verify with: docker logs cloudflared"
        fi
    else
        log_warn "cloudflared container may not be running yet"
        log_warn "Check: docker logs cloudflared"
        log_warn "This is non-fatal — tunnel will retry connection automatically"
    fi
}

#-------------------------------------------------------------------------------
# Step 5: Generate Self-Signed Certificate (optional)
#-------------------------------------------------------------------------------
generate_self_signed_cert() {
    if [[ "$SELF_SIGNED_CERT" != true ]]; then
        log_skip "Self-signed certificate not requested"
        return
    fi

    # Calculate step number dynamically
    local step_num=5
    [[ -n "$CF_TUNNEL_TOKEN" ]] && ((step_num++))
    log_step "${step_num}/$(get_total_steps) - Generating self-signed certificate..."
    
    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${CYAN}[DRY-RUN]${NC} Would generate self-signed cert for *.${BASE_DOMAIN}"
        return
    fi
    
    cd "${CERTS_DIR}"
    
    # 1. Create CA (Certificate Authority)
    log_info "Creating Certificate Authority..."
    openssl genrsa -out ca.key 4096 2>/dev/null
    openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
        -out ca.crt \
        -subj "/C=HU/ST=Budapest/L=Budapest/O=${BASE_DOMAIN}/CN=${BASE_DOMAIN} CA" 2>/dev/null
    
    # 2. Create wildcard certificate config
    cat > "${BASE_DOMAIN}.cnf" << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
C = HU
ST = Budapest
L = Budapest
O = ${BASE_DOMAIN}
CN = *.${BASE_DOMAIN}

[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.${BASE_DOMAIN}
DNS.2 = ${BASE_DOMAIN}
EOF

    # 3. Generate key and CSR
    log_info "Generating wildcard certificate for *.${BASE_DOMAIN}..."
    openssl genrsa -out "${BASE_DOMAIN}.key" 2048 2>/dev/null
    openssl req -new -key "${BASE_DOMAIN}.key" -out "${BASE_DOMAIN}.csr" -config "${BASE_DOMAIN}.cnf" 2>/dev/null
    
    # 4. Sign with CA
    openssl x509 -req -in "${BASE_DOMAIN}.csr" \
        -CA ca.crt -CAkey ca.key -CAcreateserial \
        -out "${BASE_DOMAIN}.crt" -days 825 -sha256 \
        -extfile "${BASE_DOMAIN}.cnf" -extensions v3_req 2>/dev/null
    
    # 5. Set permissions (explicit file names, not globs)
    chmod 600 ca.key "${BASE_DOMAIN}.key"
    chmod 644 ca.crt "${BASE_DOMAIN}.crt"
    
    # 6. Create Traefik TLS configuration
    cat > "${TRAEFIK_DIR}/dynamic/certs.yml" << EOF
# TLS Certificate Configuration
tls:
  certificates:
    - certFile: /etc/traefik/certs/${BASE_DOMAIN}.crt
      keyFile: /etc/traefik/certs/${BASE_DOMAIN}.key
  stores:
    default:
      defaultCertificate:
        certFile: /etc/traefik/certs/${BASE_DOMAIN}.crt
        keyFile: /etc/traefik/certs/${BASE_DOMAIN}.key
EOF

    # 7. Restart Traefik to load new certs
    cd "${TRAEFIK_DIR}"
    docker compose restart traefik
    
    # Verify Traefik is still running after restart
    sleep 3
    if ! docker ps --filter "name=traefik" --filter "status=running" -q | grep -q .; then
        log_error "Traefik failed to restart after cert update!"
        docker compose logs --tail=20 traefik
        exit 1
    fi
    
    # 8. Copy CA cert to accessible location
    local current_user
    current_user=$(get_current_user)
    local user_home
    user_home=$(eval echo "~${current_user}")
    cp "${CERTS_DIR}/ca.crt" "${user_home}/${BASE_DOMAIN}-ca.crt"
    chown "${current_user}:${current_user}" "${user_home}/${BASE_DOMAIN}-ca.crt"
    
    log_success "Self-signed certificate generated"
    
    echo ""
    echo -e "${BOLD}${YELLOW}===================================================================${NC}"
    echo -e "${BOLD}${YELLOW}  IMPORTANT: Import the CA certificate on your devices!${NC}"
    echo -e "${BOLD}${YELLOW}===================================================================${NC}"
    echo ""
    echo -e "CA certificate location: ${BOLD}${user_home}/${BASE_DOMAIN}-ca.crt${NC}"
    echo ""
    echo -e "${BOLD}Windows:${NC}"
    echo "  1. Copy ca.crt to your Windows machine"
    echo "  2. Double-click > Install Certificate > Local Machine"
    echo "  3. Place in 'Trusted Root Certification Authorities'"
    echo "  4. Restart browser"
    echo ""
    echo -e "${BOLD}macOS:${NC}"
    echo "  1. Double-click ca.crt > Add to System keychain"
    echo "  2. Keychain Access > Find '${BASE_DOMAIN} CA' > Trust > Always Trust"
    echo ""
    echo -e "${BOLD}Linux:${NC}"
    echo "  sudo cp ${user_home}/${BASE_DOMAIN}-ca.crt /usr/local/share/ca-certificates/"
    echo "  sudo update-ca-certificates"
    echo ""
}

#-------------------------------------------------------------------------------
# Step 6: Install FileBrowser Quantum (infrastructure file manager)
#-------------------------------------------------------------------------------
install_filebrowser() {
    if [[ "$SKIP_FILEBROWSER" == true ]]; then
        log_skip "FileBrowser installation skipped"
        return
    fi

    if [[ -z "$HDD_PATH" ]]; then
        log_warn "FileBrowser skipped — no HDD path configured (use --hdd-path)."
        log_warn "Deploy manually after HDD setup."
        return
    fi

    if [[ ! -d "$HDD_PATH" ]]; then
        log_warn "FileBrowser skipped — HDD path $HDD_PATH does not exist."
        log_warn "Run hdd-setup.sh first, then re-run with --hdd-path."
        return
    fi

    # Calculate step number dynamically
    local step_num=5
    [[ -n "$CF_TUNNEL_TOKEN" ]] && ((step_num++))
    [[ "$SELF_SIGNED_CERT" == true ]] && ((step_num++))
    log_step "${step_num}/$(get_total_steps) - Installing FileBrowser Quantum..."

    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${CYAN}[DRY-RUN]${NC} Would install FileBrowser Quantum at files.${BASE_DOMAIN}"
        return
    fi

    mkdir -p "${FILEBROWSER_DIR}"

    # Build certresolver label if Let's Encrypt is configured
    local fb_certresolver_label=""
    if [[ -n "$ACME_EMAIL" ]]; then
        fb_certresolver_label='      - "traefik.http.routers.filebrowser.tls.certresolver=letsencrypt"'
    fi

    # Create docker-compose.yml
    cat > "${FILEBROWSER_DIR}/docker-compose.yml" << EOF
# FileBrowser Quantum — Infrastructure file manager
# Domain: files.${BASE_DOMAIN}
# Deployed by docker-setup.sh — do NOT remove
#
# Mount permissions:
#   /srv/storage/       → HDD storage/ (READ-ONLY — app data)
#   /srv/media/         → HDD media/   (read-write — user media)
#   /srv/Dokumentumok/  → HDD Dokumentumok/ (read-write — user documents)

services:
  filebrowser:
    image: gtstef/filebrowser:latest
    container_name: filebrowser
    restart: unless-stopped
    environment:
      - TZ=Europe/Budapest
    volumes:
      - filebrowser_data:/home/filebrowser/data
      - \${HDD_PATH}/storage:/srv/storage:ro
      - \${HDD_PATH}/media:/srv/media
      - \${HDD_PATH}/Dokumentumok:/srv/Dokumentumok
    networks:
      - traefik-public
    deploy:
      resources:
        limits:
          memory: 256M
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:80/"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.filebrowser.rule=Host(\`files.\${DOMAIN}\`)"
      - "traefik.http.routers.filebrowser.entrypoints=websecure"
      - "traefik.http.routers.filebrowser.tls=true"
${fb_certresolver_label}
      - "traefik.http.services.filebrowser.loadbalancer.server.port=80"
      - "traefik.docker.network=traefik-public"

volumes:
  filebrowser_data:

networks:
  traefik-public:
    external: true
EOF

    # Create .felhom.yml metadata
    cat > "${FILEBROWSER_DIR}/.felhom.yml" << 'METAEOF'
display_name: Filebrowser
slug: filebrowser
description: Fájlkezelő a külső merevlemezhez
subdomain: files
category: storage
resources:
  mem_request: "128M"
  mem_limit: "256M"
  pi_compatible: true
  needs_hdd: true
app_info:
  tagline: Web-alapú fájlkezelő a külső merevlemezhez
  use_cases:
    - Fájlok böngészése és letöltése a külső HDD-ről
    - Médiafájlok megosztása családtagokkal
    - Dokumentumok feltöltése és kezelése
  first_steps:
    - Nyisd meg a files.DOMAIN címet a böngészőben
    - Jelentkezz be az admin fiókkal
    - Tallózd a /srv mappákat
  prerequisites:
    - Külső HDD csatlakoztatva és felcsatolva
METAEOF

    # Create .env file
    cat > "${FILEBROWSER_DIR}/.env" << ENVEOF
# FileBrowser environment — generated by docker-setup.sh
DOMAIN=${BASE_DOMAIN}
HDD_PATH=${HDD_PATH}
ENVEOF

    cd "${FILEBROWSER_DIR}"
    log_info "Pulling FileBrowser image..."
    docker compose pull -q
    log_info "Starting FileBrowser..."
    docker compose up -d --quiet-pull

    # Verify FileBrowser started
    sleep 3
    if docker ps --filter "name=filebrowser" --filter "status=running" -q | grep -q .; then
        log_success "FileBrowser installed and running at files.${BASE_DOMAIN}"
    else
        log_error "FileBrowser container is not running!"
        docker compose logs --tail=20 filebrowser
        exit 1
    fi
}

#-------------------------------------------------------------------------------
# Install helper tools and configure environment
#-------------------------------------------------------------------------------
install_tools_and_configure() {
    log_info "Installing helper tools..."
    
    if [[ "$DRY_RUN" == true ]]; then
        return
    fi
    
    # ctop - container metrics
    if ! command -v ctop &> /dev/null; then
        log_info "Installing ctop..."
        curl -fsSL https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -o /usr/local/bin/ctop 2>/dev/null || log_warn "Failed to download ctop"
        chmod +x /usr/local/bin/ctop 2>/dev/null || true
    fi
    
    # lazydocker - TUI for Docker
    if ! command -v lazydocker &> /dev/null; then
        log_info "Installing lazydocker..."
        local LD_VERSION
        LD_VERSION=$(curl -s https://api.github.com/repos/jesseduffield/lazydocker/releases/latest 2>/dev/null | jq -r '.tag_name' | tr -d 'v')
        if [[ -n "$LD_VERSION" && "$LD_VERSION" != "null" ]]; then
            curl -fsSL "https://github.com/jesseduffield/lazydocker/releases/download/v${LD_VERSION}/lazydocker_${LD_VERSION}_Linux_x86_64.tar.gz" 2>/dev/null \
                | tar xz -C /usr/local/bin lazydocker 2>/dev/null || log_warn "Failed to download lazydocker"
        else
            log_warn "Could not determine lazydocker version, skipping"
        fi
    fi
    
    # Configure shell for current user
    local current_user
    current_user=$(get_current_user)
    local user_home
    user_home=$(eval echo "~${current_user}")
    local bashrc="${user_home}/.bashrc"
    
    # Add Docker aliases if not present
    if [[ -f "$bashrc" ]] && ! grep -q "# Docker aliases" "$bashrc" 2>/dev/null; then
        cat >> "$bashrc" << 'EOF'

# Docker aliases
alias dc='sudo docker compose'
alias dcu='sudo docker compose up -d'
alias dcd='sudo docker compose down'
alias dcl='sudo docker compose logs -f'
alias dps='sudo docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dlogs='sudo docker logs -f'
alias dexec='sudo docker exec -it'
alias dprune='sudo docker system prune -af'

# Quick access
alias traefik-logs='sudo docker logs -f traefik'
alias filebrowser-logs='sudo docker logs -f filebrowser'
EOF
    elif [[ ! -f "$bashrc" ]]; then
        log_warn "No .bashrc found at $bashrc, skipping alias setup"
    fi
    
    log_success "Helper tools installed"
}


#-------------------------------------------------------------------------------
# Print summary
#-------------------------------------------------------------------------------
print_summary() {
    local server_ip
    server_ip=$(get_server_ip)
    
    echo ""
    echo -e "${BOLD}${GREEN}-==================================================================¬${NC}"
    echo -e "${BOLD}${GREEN}¦                 Infrastructure Setup Complete!                   ¦${NC}"
    echo -e "${BOLD}${GREEN}L==================================================================-${NC}"
    echo ""
    echo -e "${BOLD}Server IP:${NC} ${server_ip}"
    echo -e "${BOLD}Domain:${NC}    *.${BASE_DOMAIN}"
    echo ""
    echo -e "${BOLD}Services:${NC}"
    echo "  • Traefik Dashboard: https://traefik.${BASE_DOMAIN}/dashboard/"
    echo "    L| Credentials: admin / ${TRAEFIK_PASSWORD}"
    if [[ -n "$CF_TUNNEL_TOKEN" ]]; then
        echo "  • Cloudflare Tunnel: Active (routes configured in CF dashboard)"
        echo "    L| Container: cloudflared"
        echo "    L| Config:    ${CLOUDFLARED_DIR}/docker-compose.yml"
        echo "    L| Check:     docker logs cloudflared"
    fi
    if [[ "$SKIP_FILEBROWSER" != true ]] && [[ -n "$HDD_PATH" ]]; then
        echo "  • FileBrowser:       https://files.${BASE_DOMAIN}"
        echo "    L| Default login: admin / admin (change immediately!)"
    fi
    echo ""
    echo -e "${BOLD}DNS Setup Required:${NC}"
    echo "  Add wildcard record to your DNS (Pi-hole, router, etc.):"
    echo "  *.${BASE_DOMAIN}  →  ${server_ip}"
    echo ""
    if [[ "$SELF_SIGNED_CERT" == true ]]; then
        local current_user
        current_user=$(get_current_user)
        local user_home
        user_home=$(eval echo "~${current_user}")
        echo -e "${BOLD}${YELLOW}Self-Signed Certificate:${NC}"
        echo "  Import CA on your devices: ${user_home}/${BASE_DOMAIN}-ca.crt"
        echo ""
    fi
    if [[ -n "$ACME_EMAIL" && -n "$CF_DNS_API_TOKEN" ]]; then
        echo -e "${BOLD}${GREEN}TLS: Let's Encrypt (Cloudflare DNS-01)${NC}"
        echo "  Certificates are automatically issued and renewed by Traefik."
        echo "  All *.${BASE_DOMAIN} services get browser-trusted HTTPS."
        echo "  ACME email: ${ACME_EMAIL}"
        echo "  CF token stored in: ${TRAEFIK_DIR}/.env"
        echo ""
    elif [[ -n "$ACME_EMAIL" ]]; then
        echo -e "${BOLD}${YELLOW}TLS: Let's Encrypt (HTTP-01)${NC}"
        echo "  Requires port 80 accessible from the internet."
        echo "  Will NOT work with Cloudflare Tunnel!"
        echo "  ACME email: ${ACME_EMAIL}"
        echo ""
    fi
    echo -e "${BOLD}${CYAN}===================================================================${NC}"
    echo -e "${BOLD}${CYAN}                    DEPLOYING APPLICATION STACKS                   ${NC}"
    echo -e "${BOLD}${CYAN}===================================================================${NC}"
    echo ""
    echo "  Deploy applications via the Felhom Controller dashboard:"
    echo ""
    echo "  1. Deploy felhom-controller (see controller/README.md)"
    echo "  2. Open https://felhom.${BASE_DOMAIN}"
    echo "  3. Browse available apps on the Alkalmazások page"
    echo "  4. Click Telepítés to deploy"
    if [[ -n "$CUSTOMER_ID" ]]; then
        echo ""
        echo -e "${BOLD}Customer:${NC} ${CUSTOMER_ID}"
        echo ""
        echo -e "${YELLOW}Note: No --customer specified. Templates use the generic catalog.${NC}"
        echo -e "${YELLOW}Env vars (passwords, secrets) must be filled in manually.${NC}"
    fi
    echo ""
    echo -e "${BOLD}Quick Commands:${NC}"
    echo "  dps          → List containers"
    echo "  dlogs <n>    → View container logs"
    echo "  lazydocker   → TUI for Docker management"
    echo ""
    echo -e "${CYAN}Log out and back in for Docker group membership to take effect.${NC}"
    echo ""
}

#-------------------------------------------------------------------------------
# Main
#-------------------------------------------------------------------------------
main() {
    parse_args "$@"
    
    # Root check BEFORE setting up logging to /var/log
    check_root
    
    # Now safe to set up logging (we know we're root)
    LOG_FILE="/var/log/docker-setup.log"
    mkdir -p /var/log 2>/dev/null || true
    exec > >(tee -a "$LOG_FILE") 2>&1
    
    # Enable bash tracing if --debug
    if [[ "$DEBUG_MODE" == true ]]; then
        set -x
    fi
    
    print_banner
    check_debian
    
    # Show plan
    echo ""
    echo -e "${BOLD}Installation Plan:${NC}"
    echo "  1. Install base packages"
    echo "  2. Configure network (Static IP: ${STATIC_IP:-DHCP})"
    echo "  3. Install Docker Engine + Compose"
    echo "  4. Install Traefik reverse proxy"
    if [[ -n "$CF_TUNNEL_TOKEN" ]]; then
        echo -e "  5. Install Cloudflare Tunnel connector: ${GREEN}yes${NC}"
    else
        echo -e "  -  Cloudflare Tunnel: ${CYAN}skip (no --cf-tunnel-token)${NC}"
    fi
    if [[ "$SELF_SIGNED_CERT" == true ]]; then
        echo "  -  Generate self-signed certificate: yes"
    fi
    echo "  -  Install FileBrowser: $([[ "$SKIP_FILEBROWSER" == true ]] && echo "skip" || echo "$([[ -n "$HDD_PATH" ]] && echo "yes (HDD: $HDD_PATH)" || echo "skip (no --hdd-path)")")"
    echo "  -  Install helper tools (ctop, lazydocker, aliases)"
    echo ""
    echo "  Domain: *.${BASE_DOMAIN}"
    echo "  Customer: ${CUSTOMER_ID:-<none — generic catalog>}"
    echo "  Traefik password: ${TRAEFIK_PASSWORD}"
    if [[ -n "$ACME_EMAIL" && -n "$CF_DNS_API_TOKEN" ]]; then
        echo -e "  TLS: ${GREEN}Let's Encrypt (Cloudflare DNS-01)${NC}"
        echo "  ACME email: ${ACME_EMAIL}"
        echo "  CF DNS token: ${CF_DNS_API_TOKEN:0:8}...${CF_DNS_API_TOKEN: -4} (masked)"
    elif [[ -n "$ACME_EMAIL" ]]; then
        echo -e "  TLS: ${YELLOW}Let's Encrypt (HTTP-01 — needs public port 80)${NC}"
        echo "  ACME email: ${ACME_EMAIL}"
    elif [[ "$SELF_SIGNED_CERT" == true ]]; then
        echo -e "  TLS: ${YELLOW}Self-signed certificate${NC}"
    else
        echo -e "  TLS: ${RED}None (Traefik default cert)${NC}"
    fi
    if [[ -n "$CF_TUNNEL_TOKEN" ]]; then
        echo "  CF tunnel token: ${CF_TUNNEL_TOKEN:0:12}... (masked)"
    fi
    echo ""
    
    if [[ "$DRY_RUN" == true ]]; then
        echo -e "${YELLOW}DRY RUN - no changes will be made${NC}"
        echo ""
    fi
    
    read -p "Continue? [y/N] " -n 1 -r
    echo
    [[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
    
    install_base_packages
    configure_static_ip
    install_docker
    install_traefik
    install_cloudflare_tunnel
    generate_self_signed_cert
    install_filebrowser
    install_tools_and_configure
    
    print_summary
}

main "$@"