#!/bin/bash

# deploy-rvs-prxy.sh - Automated deployment script for Reverse Proxy setup
# This script supports two modes:
#   1) Setup Reverse Proxy (Traefik or FRP Tunnel with Traefik)
#   2) Setup New Domain into existing reverse proxy
# This script runs on the same computer as the service it connects to

set -e  # Exit on any error

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Global variables
THIS_INSTANCE=""
SELECTED_NETWORK=""
INTERNAL_SERVICE_PORT=""
SERVER_ADDR=""
SERVER_PORT="16999"
RAD_REMOTE_PORT=""
SSH_REMOTE_PORT=""
WEB_REMOTE_PORT=""
DOMAIN_NAME=""

# FRP Mode variables
FRP=true

# Instance configuration variables
INSTANCE_NAME="traefik"  # Default container name, can be overridden in .env.rvs-prxy
DOCKER_NETWORK="${DOCKER_NETWORK}"  # Default Docker network name, can be overridden in .env.rvs-prxy
DASHBOARD_PORT="18080"  # Default dashboard port, can be overridden in .env.rvs-prxy

# Extra entrypoints beyond default web (:80/tcp) and websecure (:443/tcp)
# Format: comma-separated list of name=address
# Example: "quic=:443/udp, custom-tcp=:8888"
# Address format: :port or :port/protocol (default protocol: tcp)
EXTRA_ENTRYPOINTS=""

# Target server variables (for Traefik mode - now running locally)
LOCAL_SERVICE_NAME=""
LOCAL_SERVICE_PORT=""
LOCAL_DOCKER_NETWORK=""

# Deployment mode: "proxy" (setup reverse proxy), "domain" (setup new domain), or "port" (expose host port)
DEPLOY_MODE=""

# Domain configuration file pattern: .env-your-domain-com
DOMAIN_ENV_FILE=""

# Port configuration file pattern: .env-port-<port_number>
PORT_ENV_FILE=""

# Array to store all domain configuration files for batch processing
declare -a DOMAIN_ENV_FILES=()

# Array to store all port configuration files for batch processing
declare -a PORT_ENV_FILES=()

# Function to load environment variables from .env.rvs-prxy files
load_env() {
    local proxy_env_loaded=false
    local domain_env_loaded=false

    # Step 1: Load reverse proxy instance configuration from .env.rvs-prxy
    if [ -f ".env.rvs-prxy" ]; then
        echo -e "${BLUE}[INFO]${NC} Loading reverse proxy config from .env.rvs-prxy" >&2
        source .env.rvs-prxy
        proxy_env_loaded=true
    elif [ -f "$(dirname "$0")/.env.rvs-prxy" ]; then
        echo -e "${BLUE}[INFO]${NC} Loading reverse proxy config from $(dirname "$0")/.env.rvs-prxy" >&2
        source "$(dirname "$0")/.env.rvs-prxy"
        proxy_env_loaded=true
    fi

    # Step 2: Collect all domain-specific configuration files (.env-* pattern)
    # These files contain domain-specific settings like DOMAIN_NAME, LOCAL_SERVICE_NAME, LOCAL_SERVICE_PORT
    # Pattern matches any domain format: .env-example-com, .env-sub-domain-org-hk, etc.
    # All found files will be processed in batch mode
    for env_file in .env-*; do
        # Skip the main reverse proxy config file .env.rvs-prxy
        if [ "$env_file" = ".env.rvs-prxy" ]; then
            continue
        fi
        # Skip port configuration files (.env-port-*)
        if [[ "$env_file" == .env-port-* ]]; then
            continue
        fi
        if [ -f "$env_file" ]; then
            DOMAIN_ENV_FILES+=("$env_file")
        fi
    done

    # Collect port configuration files (.env-port-* pattern)
    # These files contain port-based routing settings: HOST_PORT, CONTAINER_NAME, CONTAINER_PORT
    for port_file in .env-port-*; do
        if [ -f "$port_file" ]; then
            PORT_ENV_FILES+=("$port_file")
        fi
    done

    # Check for legacy .env.port-* files and auto-rename to .env-port-*
    for legacy_file in .env.port-*; do
        if [ -f "$legacy_file" ]; then
            # Extract port number from filename
            local port_num="${legacy_file#.env.port-}"
            local new_file=".env-port-${port_num}"

            log_warn "Legacy port config file detected: $legacy_file"
            log_warn "Auto-renaming to new format: $new_file"
            mv "$legacy_file" "$new_file"
            log_info "File renamed successfully"

            # Add to port files list
            PORT_ENV_FILES+=("$new_file")
        fi
    done

    # Load the first domain file for initial variable setup (backward compatibility)
    if [ ${#DOMAIN_ENV_FILES[@]} -gt 0 ]; then
        echo -e "${BLUE}[INFO]${NC} Loading domain config from ${DOMAIN_ENV_FILES[0]}" >&2
        source "${DOMAIN_ENV_FILES[0]}"
        domain_env_loaded=true
        DOMAIN_ENV_FILE="${DOMAIN_ENV_FILES[0]}"

        if [ ${#DOMAIN_ENV_FILES[@]} -gt 1 ]; then
            log_info "Found ${#DOMAIN_ENV_FILES[@]} domain config files, will process all in batch mode"
        fi
    fi

    # Load the first port config file for initial variable setup
    if [ ${#PORT_ENV_FILES[@]} -gt 0 ]; then
        echo -e "${BLUE}[INFO]${NC} Loading port config from ${PORT_ENV_FILES[0]}" >&2
        source "${PORT_ENV_FILES[0]}"
        PORT_ENV_FILE="${PORT_ENV_FILES[0]}"

        if [ ${#PORT_ENV_FILES[@]} -gt 1 ]; then
            log_info "Found ${#PORT_ENV_FILES[@]} port config files, will process all in batch mode"
        fi
    fi

    # Step 3: Detect proxy mode from running containers
    # This allows domain setup even without .env.rvs-prxy file, by detecting running proxy
    # IMPORTANT: Running container detection takes precedence over .env settings
    local proxy_running=false
    local detected_frp_mode=""

    # Check if Traefik is running (takes precedence)
    if docker ps --filter name=^${INSTANCE_NAME}$ --format '{{.Names}}' | grep -q "^traefik$"; then
        proxy_running=true
        detected_frp_mode="false"
        log_info "Detected running Traefik reverse proxy"
    fi

    # Check if FRP client is running (look for any *-frpc container)
    local frpc_containers=$(docker ps --filter name="-frpc$" --format '{{.Names}}' 2>/dev/null || true)
    if [ -n "$frpc_containers" ]; then
        # FRP container found, extract instance name from first container
        local first_frpc=$(echo "$frpc_containers" | head -1)
        INSTANCE_NAME="${first_frpc%-frpc}"
        proxy_running=true
        # Only set FRP mode if Traefik is not detected (Traefik takes precedence)
        if [ -z "$detected_frp_mode" ]; then
            detected_frp_mode="true"
            log_info "Detected running FRP client container: ${first_frpc} (instance: ${INSTANCE_NAME})"

            # Load FRP configuration from volume if .env not loaded
            if [ "$proxy_env_loaded" = false ]; then
                load_frp_config_from_volume "$INSTANCE_NAME"
            fi
        fi
    fi

    # ALWAYS use detected FRP mode when proxy is running (override .env settings)
    # This ensures the script uses the actual running proxy mode, not stale config
    if [ "$proxy_running" = true ] && [ -n "$detected_frp_mode" ]; then
        FRP="$detected_frp_mode"
        log_info "Auto-detected FRP mode: $FRP (from running container)"
    fi

    # Determine deployment mode based on what configuration files exist and running proxy
    if [ ${#PORT_ENV_FILES[@]} -gt 0 ]; then
        # Port config exists - setup port-based routing
        DEPLOY_MODE="port"
        log_info "Deployment mode: Setup Port Exposure (found port config: ${PORT_ENV_FILE})"
    elif [ "$domain_env_loaded" = true ]; then
        # Domain config exists - setup new domain
        DEPLOY_MODE="domain"
        log_info "Deployment mode: Setup New Domain (found domain config: ${DOMAIN_ENV_FILE})"
    elif [ "$proxy_running" = true ]; then
        # Proxy is running (detected from containers) - setup new domain via wizard if no domain config
        DEPLOY_MODE="domain_wizard"
        log_info "Deployment mode: Setup New Domain (proxy running, no domain config)"
    elif [ "$proxy_env_loaded" = true ]; then
        # Proxy config exists but proxy not running - setup proxy
        DEPLOY_MODE="proxy"
        log_info "Deployment mode: Setup Reverse Proxy (config exists, proxy not running)"
    else
        # No configuration files found and no proxy running - run interactive wizard
        echo -e "${YELLOW}[INFO]${NC} No configuration file found." >&2
        interactive_setup_wizard
        # Source the newly created files
        if [ "$DEPLOY_MODE" = "domain" ] && [ -n "$DOMAIN_ENV_FILE" ]; then
            source "$DOMAIN_ENV_FILE"
        elif [ "$DEPLOY_MODE" = "port" ] && [ -n "$PORT_ENV_FILE" ]; then
            source "$PORT_ENV_FILE"
        else
            source .env.rvs-prxy
        fi
    fi
    
    # Validate required parameters based on mode (skip validation for domain_wizard - will be validated after wizard)
    if [ "$DEPLOY_MODE" = "port" ]; then
        # Port config file exists - validate it
        if [ -z "$HOST_PORT" ]; then
            log_error "HOST_PORT is required for port exposure mode"
            log_error "Please set HOST_PORT in your .env-port-<port> file"
            exit 1
        fi
        if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_PORT" ]; then
            log_error "CONTAINER_NAME and CONTAINER_PORT are required for port exposure"
            log_error "Please set these in your .env-port-<port> file"
            exit 1
        fi
    elif [ "$DEPLOY_MODE" = "domain" ]; then
        # Domain config file exists - validate it
        if [ -z "$DOMAIN_NAME" ]; then
            log_error "DOMAIN_NAME is required for domain setup mode"
            log_error "Please set DOMAIN_NAME in your .env-<domain> file"
            exit 1
        fi
        if [ -z "$LOCAL_SERVICE_NAME" ] || [ -z "$LOCAL_SERVICE_PORT" ]; then
            log_error "LOCAL_SERVICE_NAME and LOCAL_SERVICE_PORT are required for domain setup"
            log_error "Please set these in your .env-<domain> file"
            exit 1
        fi
    elif [ "$DEPLOY_MODE" = "proxy" ]; then
        # For proxy setup mode, validate proxy-specific settings
        if [ "$FRP" = false ]; then
            # Traefik mode - check for Traefik-specific settings
            if [ -z "$TRAEFIK_VER" ]; then
                log_error "TRAEFIK_VER is required for Traefik proxy setup"
                log_error "Please set TRAEFIK_VER in your .env.rvs-prxy file"
                exit 1
            fi
            if [ -z "$LETSENCRYPT_EMAIL" ]; then
                log_error "LETSENCRYPT_EMAIL is required for Traefik proxy setup"
                log_error "Please set LETSENCRYPT_EMAIL in your .env.rvs-prxy file"
                exit 1
            fi
        else
            # FRP mode - check for FRP-specific settings
            if [ -z "$FRP_SERVER_ADDR" ]; then
                log_error "FRP_SERVER_ADDR is required for FRP proxy setup"
                log_error "Please set FRP_SERVER_ADDR in your .env.rvs-prxy file"
                exit 1
            fi
        fi
    fi
    # Note: domain_wizard mode skips validation here - wizard will collect the required values
}

# Logging functions
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1" >&2
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" >&2
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1" >&2
}

log_debug() {
    if [ "$DEBUG" = "true" ]; then
        echo -e "${BLUE}[DEBUG]${NC} $1" >&2
    fi
}

# Function to save answered values to .env.rvs-prxy file
save_to_env() {
    local var_name="$1"
    local var_value="$2"
    local target_file="${3:-.env.rvs-prxy}"

    # Create or update .env.rvs-prxy file with the new value
    if [ -f "$target_file" ]; then
        # Check if variable already exists
        if grep -q "^${var_name}=" "$target_file"; then
            # Update existing variable
            sed -i "s/^${var_name}=.*/${var_name}=${var_value}/" "$target_file"
        else
            # Add new variable
            echo "${var_name}=${var_value}" >> "$target_file"
        fi
    else
        # Create new .env.rvs-prxy file
        echo "${var_name}=${var_value}" >> "$target_file"
    fi
}

# Function to save domain-specific configuration
save_domain_env() {
    local domain_name="$1"
    local service_name="$2"
    local service_port="$3"
    
    # Convert domain name to filename format: your-domain-com -> .env-your-domain-com
    local domain_file=".env-$(echo "$domain_name" | sed 's/\./-/g')"
    
    echo -e "${BLUE}[INFO]${NC} Saving domain configuration to $domain_file" >&2
    
    # Create domain-specific env file
    cat > "$domain_file" << EOF
# Domain configuration for ${domain_name}
# Generated by deploy-rvs-prxy.sh on $(date)

DOMAIN_NAME="${domain_name}"
LOCAL_SERVICE_NAME="${service_name}"
LOCAL_SERVICE_PORT="${service_port}"
EOF
    
    echo -e "${GREEN}[INFO]${NC} Domain configuration saved to $domain_file" >&2
    DOMAIN_ENV_FILE="$domain_file"
}

# Function to save port exposure configuration to .env-port-<port> file
save_port_env() {
    local host_port="$1"
    local container_name="$2"
    local container_port="$3"

    # Create port-specific filename: .env-port-<port>
    local port_file=".env-port-${host_port}"

    echo -e "${BLUE}[INFO]${NC} Saving port configuration to $port_file" >&2

    # Create port-specific env file
    cat > "$port_file" << EOF
# Port-based routing configuration
# Generated by deploy-rvs-prxy.sh on $(date)

HOST_PORT="${host_port}"
CONTAINER_NAME="${container_name}"
CONTAINER_PORT="${container_port}"
EOF

    echo -e "${GREEN}[INFO]${NC} Port configuration saved to $port_file" >&2
    PORT_ENV_FILE="$port_file"
}

# Function to read from terminal instead of pipe
read_terminal() {
    local prompt="$1"
    local default_value="$2"
    
    # Read from terminal (/dev/tty) instead of stdin
    read -p "$prompt" input_name </dev/tty
    echo "${input_name:-$default_value}"
}

# Interactive setup wizard for missing .env.rvs-prxy file
interactive_setup_wizard() {
    echo -e "${BLUE}=============================================${NC}" >&2
    echo -e "${GREEN}Welcome to Deploy Reverse Proxy Setup Wizard${NC}" >&2
    echo -e "${BLUE}=============================================${NC}" >&2
    echo "" >&2
    
    # Check if reverse proxy already exists
    local proxy_exists=false
    if docker ps -a --filter name=^${INSTANCE_NAME}$ --format '{{.Names}}' | grep -q "^traefik$"; then
        proxy_exists=true
    fi
    
    if [ "$proxy_exists" = true ]; then
        # Reverse proxy exists, ask whether to setup new domain or port exposure
        echo -e "${YELLOW}Existing reverse proxy (Traefik) detected.${NC}" >&2
        echo "" >&2
        
        # Ask what to setup
        echo -e "${BLUE}--- What would you like to setup? ---${NC}" >&2
        echo -e "  1) ${GREEN}Setup New Domain${NC} - Route traffic based on domain name" >&2
        echo -e "  2) ${GREEN}Setup Port Exposure${NC} - Expose host port to container port" >&2
        echo "" >&2

        while true; do
            setup_choice=$(read_terminal "Select option [1-2] (default: 1 for Domain): " "1")
            case "$setup_choice" in
                1|"")
                    # Setup New Domain
                    echo -e "${GREEN}Selected: Setup New Domain${NC}" >&2
                    echo "" >&2
                    echo -e "${BLUE}--- Setup New Domain ---${NC}" >&2
                    echo -e "Let's configure a new domain for your existing reverse proxy." >&2
                    echo "" >&2

                    # Get domain name
                    echo -e "Enter the domain name for this service:" >&2
                    while [ -z "$DOMAIN_NAME" ]; do
                        DOMAIN_NAME=$(read_terminal "Domain (e.g., app.example.com): " "")
                        if [ -z "$DOMAIN_NAME" ]; then
                            echo -e "${RED}Domain name cannot be empty.${NC}" >&2
                        fi
                    done

                    # Get service name
                    echo -e "Enter your Docker application container name:" >&2
                    while [ -z "$LOCAL_SERVICE_NAME" ]; do
                        LOCAL_SERVICE_NAME=$(read_terminal "Service name (e.g., my-app): " "")
                        if [ -z "$LOCAL_SERVICE_NAME" ]; then
                            echo -e "${RED}Service name cannot be empty.${NC}" >&2
                        fi
                    done

                    # Get service port(s)
                    echo -e "Enter your application's internal port(s):" >&2
                    echo -e "  Single port: 80" >&2
                    echo -e "  Multiple:    80,4443/udp (comma-separated, protocol defaults to tcp)" >&2
                    while [ -z "$LOCAL_SERVICE_PORT" ]; do
                        LOCAL_SERVICE_PORT=$(read_terminal "Service port (default: 3000): " "3000")
                        if [ -z "$LOCAL_SERVICE_PORT" ]; then
                            LOCAL_SERVICE_PORT="3000"
                        elif ! [[ "$LOCAL_SERVICE_PORT" =~ ^[0-9]+(/tcp|/udp)?(,[0-9]+(/tcp|/udp)?)*$ ]]; then
                            echo -e "${RED}Invalid format. Use comma-separated port/protocol, e.g. 80 or 80,4443/udp${NC}" >&2
                            LOCAL_SERVICE_PORT=""
                        fi
                    done

                    # Save to domain-specific .env.rvs-prxy file
                    save_domain_env "$DOMAIN_NAME" "$LOCAL_SERVICE_NAME" "$LOCAL_SERVICE_PORT"

                    # Load FRP setting from existing .env if it exists
                    if [ -f ".env.rvs-prxy" ]; then
                        source .env.rvs-prxy
                    else
                        # Set default FRP mode if .env doesn't exist
                        FRP=false
                    fi

                    DEPLOY_MODE="domain"
                    log_info "Deployment mode: Setup New Domain (interactive setup)"
                    break
                    ;;
                2)
                    # Setup Port Exposure
                    echo -e "${GREEN}Selected: Setup Port Exposure${NC}" >&2
                    echo "" >&2
                    echo -e "${BLUE}--- Setup Port Exposure ---${NC}" >&2
                    echo -e "Let's expose a host port to connect to a container port." >&2
                    echo "" >&2

                    # Get host port to expose
                    echo -e "Enter the host port number to expose:" >&2
                    while [ -z "$HOST_PORT" ]; do
                        HOST_PORT=$(read_terminal "Host port (e.g., 18081): " "")
                        if [ -z "$HOST_PORT" ]; then
                            echo -e "${RED}Port number cannot be empty.${NC}" >&2
                        elif ! [[ "$HOST_PORT" =~ ^[0-9]+$ ]] || [ "$HOST_PORT" -lt 1 ] || [ "$HOST_PORT" -gt 65535 ]; then
                            echo -e "${RED}Invalid port. Please enter a number between 1 and 65535.${NC}" >&2
                            HOST_PORT=""
                        fi
                    done

                    # Get container name
                    echo -e "Enter your Docker container name:" >&2
                    while [ -z "$CONTAINER_NAME" ]; do
                        CONTAINER_NAME=$(read_terminal "Container name (e.g., my-nginx): " "")
                        if [ -z "$CONTAINER_NAME" ]; then
                            echo -e "${RED}Container name cannot be empty.${NC}" >&2
                        fi
                    done

                    # Get container port
                    echo -e "Enter your container's internal port:" >&2
                    while [ -z "$CONTAINER_PORT" ]; do
                        CONTAINER_PORT=$(read_terminal "Container port (e.g., 80): " "")
                        if [ -z "$CONTAINER_PORT" ]; then
                            echo -e "${RED}Port number cannot be empty.${NC}" >&2
                        elif ! [[ "$CONTAINER_PORT" =~ ^[0-9]+$ ]] || [ "$CONTAINER_PORT" -lt 1 ] || [ "$CONTAINER_PORT" -gt 65535 ]; then
                            echo -e "${RED}Invalid port. Please enter a number between 1 and 65535.${NC}" >&2
                            CONTAINER_PORT=""
                        fi
                    done

                    # Save to port-specific .env.rvs-prxy file
                    save_port_env "$HOST_PORT" "$CONTAINER_NAME" "$CONTAINER_PORT"

                    # Load FRP setting from existing .env if it exists
                    if [ -f ".env.rvs-prxy" ]; then
                        source .env.rvs-prxy
                    else
                        # Set default FRP mode if .env doesn't exist
                        FRP=false
                    fi

                    DEPLOY_MODE="port"
                    log_info "Deployment mode: Setup Port Exposure (interactive setup)"
                    break
                    ;;
                *)
                    echo -e "${RED}Invalid choice. Please enter 1 or 2.${NC}" >&2
                    ;;
            esac
        done
        
    else
        # No reverse proxy exists, setup new one
        echo -e "${YELLOW}No configuration file found. Let's set up your deployment.${NC}" >&2
        echo "" >&2

        # Step 1: Select deployment mode
        echo -e "${BLUE}--- Step 1: Deployment Mode ---${NC}" >&2
        echo -e "Choose your deployment mode:" >&2
        echo -e "  1) ${GREEN}Traefik Mode${NC} - Local reverse proxy (runs on same server as your application)" >&2
        echo -e "  2) ${GREEN}FRP Mode${NC} - Remote proxy via FRP tunnel" >&2
        echo "" >&2

        while true; do
            mode_choice=$(read_terminal "Select mode [1-2] (default: 1 for Traefik): " "1")
            case "$mode_choice" in
                1|"")
                    FRP=false
                    echo -e "${GREEN}Selected: Traefik Mode${NC}" >&2
                    break
                    ;;
                2)
                    FRP=true
                    echo -e "${GREEN}Selected: FRP Mode${NC}" >&2
                    break
                    ;;
                *)
                    echo -e "${RED}Invalid choice. Please enter 1 or 2.${NC}" >&2
                    ;;
            esac
        done
        
        save_to_env "FRP" "$FRP" ".env.rvs-prxy"
        echo "" >&2

        # Step 2: Instance configuration
        echo -e "${BLUE}--- Step 2: Instance Configuration ---${NC}" >&2
        echo "" >&2

        # Instance name
        INSTANCE_NAME=$(read_terminal "Instance name / container name (default: traefik): " "traefik")
        save_to_env "INSTANCE_NAME" "$INSTANCE_NAME" ".env.rvs-prxy"
        echo "" >&2

        # Docker network
        DOCKER_NETWORK=$(read_terminal "Docker network name (default: traefik-public): " "traefik-public")
        save_to_env "DOCKER_NETWORK" "$DOCKER_NETWORK" ".env.rvs-prxy"
        echo "" >&2

        # Dashboard port
        DASHBOARD_PORT=$(read_terminal "Traefik dashboard port (default: 18080): " "18080")
        save_to_env "DASHBOARD_PORT" "$DASHBOARD_PORT" ".env.rvs-prxy"
        echo "" >&2

        # Step 3: Mode-specific configuration
        if [ "$FRP" = false ]; then
            # Traefik Mode configuration - Reverse Proxy Instance settings only
            echo -e "${BLUE}--- Step 3: Traefik Reverse Proxy Configuration ---${NC}" >&2
            echo "" >&2
            echo -e "${YELLOW}Note: This configures the Traefik reverse proxy instance itself.${NC}" >&2
            echo -e "${YELLOW}Domain configuration will be done separately later.${NC}" >&2
            echo "" >&2
            
            # Traefik version
            TRAEFIK_VER=$(read_terminal "Traefik version (default: v3.6.9): " "v3.6.9")
            save_to_env "TRAEFIK_VER" "$TRAEFIK_VER" ".env.rvs-prxy"
            echo "" >&2
            
            # Let's Encrypt email
            echo -e "Enter email for Let's Encrypt SSL certificates:" >&2
            while [ -z "$LETSENCRYPT_EMAIL" ]; do
                LETSENCRYPT_EMAIL=$(read_terminal "Email (e.g., admin@example.com): " "")
                if [ -z "$LETSENCRYPT_EMAIL" ]; then
                    echo -e "${RED}Email cannot be empty.${NC}" >&2
                fi
            done
            save_to_env "LETSENCRYPT_EMAIL" "$LETSENCRYPT_EMAIL" ".env.rvs-prxy"
            echo "" >&2

            # Extra entrypoints (e.g., QUIC)
            echo -e "Extra Traefik entrypoints (optional):" >&2
            echo -e "  Format: name=:port/protocol (comma-separated)" >&2
            echo -e "  Example: quic=:443/udp enables HTTP/3 QUIC on port 443/udp" >&2
            EXTRA_ENTRYPOINTS=$(read_terminal "Extra entrypoints (leave empty for none): " "")
            if [ -n "$EXTRA_ENTRYPOINTS" ]; then
                save_to_env "EXTRA_ENTRYPOINTS" "$EXTRA_ENTRYPOINTS" ".env.rvs-prxy"
            fi
            echo "" >&2
            
            # Optional: Debug mode
            DEBUG_MODE=$(read_terminal "Enable debug mode? (y/N): " "n")
            if [[ "$DEBUG_MODE" =~ ^[Yy]$ ]]; then
                save_to_env "DEBUG" "true" ".env.rvs-prxy"
            else
                save_to_env "DEBUG" "false" ".env.rvs-prxy"
            fi
            echo "" >&2
            
        else
            # FRP Mode configuration - Reverse Proxy Instance settings only
            echo -e "${BLUE}--- Step 3: FRP Tunnel Configuration ---${NC}" >&2
            echo "" >&2
            echo -e "${YELLOW}Note: This configures the FRP tunnel instance itself.${NC}" >&2
            echo -e "${YELLOW}Domain configuration will be done separately later.${NC}" >&2
            echo "" >&2
            
            # FRP server address
            echo -e "Enter FRP server address:" >&2
            while [ -z "$FRP_SERVER_ADDR" ]; do
                FRP_SERVER_ADDR=$(read_terminal "Server IP/hostname: " "")
                if [ -z "$FRP_SERVER_ADDR" ]; then
                    echo -e "${RED}Server address cannot be empty.${NC}" >&2
                fi
            done
            save_to_env "FRP_SERVER_ADDR" "$FRP_SERVER_ADDR" ".env.rvs-prxy"
            echo "" >&2
            
            # FRP server port
            FRP_SERVER_PORT=$(read_terminal "FRP server port (default: 16999): " "16999")
            save_to_env "FRP_SERVER_PORT" "$FRP_SERVER_PORT" ".env.rvs-prxy"
            echo "" >&2
            
            # Instance name
            INSTANCE_NAME=$(read_terminal "Instance name (default: auto-generated): " "")
            if [ -n "$INSTANCE_NAME" ]; then
                save_to_env "INSTANCE_NAME" "$INSTANCE_NAME" ".env.rvs-prxy"
            fi
            echo "" >&2
        fi
        
        # Step 3: Confirm setup completion
        echo -e "${BLUE}--- Step 3: Setup Complete ---${NC}" >&2
        echo "" >&2
        echo -e "${GREEN}Reverse proxy instance configuration saved to .env${NC}" >&2
        echo -e "${YELLOW}Next: Run the script again to setup your first domain.${NC}" >&2
        echo "" >&2
        
        DEPLOY_MODE="proxy"
    fi
    
    echo -e "${GREEN}=============================================${NC}" >&2
    echo -e "${GREEN}Setup Complete!${NC}" >&2
    if [ "$DEPLOY_MODE" = "domain" ]; then
        echo -e "${GREEN}Domain configuration saved to ${DOMAIN_ENV_FILE}${NC}" >&2
    elif [ "$DEPLOY_MODE" = "port" ]; then
        echo -e "${GREEN}Port configuration saved to ${PORT_ENV_FILE}${NC}" >&2
    else
        echo -e "${GREEN}Configuration saved to .env.rvs-prxy${NC}" >&2
    fi
    echo -e "${GREEN}=============================================${NC}" >&2
    echo "" >&2
    echo -e "${YELLOW}You can edit these settings later in the configuration file.${NC}" >&2
    echo "" >&2
}

# Prompt user for instance name
prompt_instance_name() {
    local system_name="$1"
    local default_name="${system_name#setup-}-$(date +%Y%m%d-%H%M%S)"
    
    # Check if INSTANCE_NAME is set from environment
    if [ -n "$INSTANCE_NAME" ]; then
        THIS_INSTANCE="$INSTANCE_NAME"
        log_info "Using environment variable for instance name: ${THIS_INSTANCE}"
    else
        # Scan Docker volumes for rad-git-sshd-.*-frpc to find existing instances
        local instances=()
        local volume_list=$(docker volume ls --format '{{.Name}}' | grep '^rad-git-sshd-.*-frpc' || true)
        if [ -n "$volume_list" ]; then
            for volume in $volume_list; do
                # Extract instance name from volume name (everything after first dash)
                local volume_instance=$(echo "$volume" | sed 's/^rad-git-sshd-\(.*\)-frpc$/\1/')
                # Validate the instance name
                if [[ "$volume_instance" =~ ^[a-zA-Z0-9_-]+$ ]]; then
                    instances+=("$volume_instance")
                fi
            done
        fi
        
        echo -e "${BLUE}=== Instance Setup ===${NC}" >&2
        echo -e "System: ${GREEN}${system_name}${NC}" >&2
        
        if [ ${#instances[@]} -eq 1 ]; then
            THIS_INSTANCE="${instances[0]}"
            log_info "Found single existing instance: ${THIS_INSTANCE}"
        elif [ ${#instances[@]} -gt 1 ]; then
            echo -e "${YELLOW}Existing instances found:${NC}" >&2
            for i in "${!instances[@]}"; do
                echo -e "${BLUE}$((i+1)).${NC} ${instances[i]}" >&2
            done
            echo -e "Enter number to select existing instance, or press Enter for new default: ${YELLOW}${default_name}${NC}" >&2
            local selection
            selection=$(read_terminal "Selection: " "")
            if [ -n "$selection" ] && [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#instances[@]} ]; then
                THIS_INSTANCE="${instances[$((selection-1))]}"
            else
                THIS_INSTANCE="$default_name"
            fi
        else
            THIS_INSTANCE="$default_name"
        fi
        
        # Allow user to override
        input_name=$(read_terminal "Enter instance name [${THIS_INSTANCE}]: " "${THIS_INSTANCE}")
        THIS_INSTANCE="${input_name:-${THIS_INSTANCE}}"
        
        # Save to env file if not already set
        if [ -z "$INSTANCE_NAME" ]; then
            save_to_env "INSTANCE_NAME" "$THIS_INSTANCE"
        fi
    fi
    
    # Validate instance name (alphanumeric, hyphens, underscores)
    if [[ "$THIS_INSTANCE" =~ ^[a-zA-Z0-9_-]+$ ]]; then
        log_info "Using instance name: ${THIS_INSTANCE}"
    else
        log_error "Invalid instance name. Only letters, numbers, hyphens, and underscores are allowed." >&2
        exit 1
    fi
    echo "" >&2
}

# Function to list and select Docker networks
select_docker_network() {
    # Check if DOCKER_NETWORK is set from environment
    if [ -n "$DOCKER_NETWORK" ]; then
        SELECTED_NETWORK="$DOCKER_NETWORK"
        log_info "Using environment variable for Docker network: ${SELECTED_NETWORK}"
        return 0
    fi
    
    echo -e "${BLUE}=== Docker Network Selection ===${NC}" >&2
    
    # Load existing network selection if available
    local existing_network
    existing_network=$(load_parameters)
    local default_option="1"
    
    # Get list of available Docker networks
    local networks
    networks=$(docker network ls --format '{{.Name}}' 2>/dev/null || true)
    
    if [ -z "$networks" ]; then
        log_warn "No Docker networks found" >&2
        return 1
    fi
    
    # Convert to array
    local network_array=()
    while IFS= read -r line; do
        network_array+=("$line")
    done <<< "$networks"
    
    # Display networks as a list
    echo -e "${YELLOW}Available Docker Networks:${NC}" >&2
    for i in "${!network_array[@]}"; do
        echo -e "${BLUE}$((i+1)).${NC} ${network_array[i]}" >&2
    done
    
    # Ask user to select or enter custom network
    echo -e "" >&2
    echo -e "${YELLOW}Options:${NC}" >&2
    echo -e "${BLUE}1-${#network_array[@]}.${NC} Select from list above" >&2
    echo -e "${BLUE}C.${NC} Custom network name" >&2
    echo -e "${BLUE}N.${NC} No network (default bridge)${NC}" >&2
    echo -e "" >&2
    
    # Set default option based on existing network
    if [ -n "$existing_network" ]; then
        # Check if existing network is in the list
        for i in "${!network_array[@]}"; do
            if [ "${network_array[i]}" = "$existing_network" ]; then
                default_option=$((i+1))
                break
            fi
        done
        # If not in list, check if it's a custom network or none
        if [ "$default_option" = "1" ] && [ -n "$existing_network" ]; then
            if [ "$existing_network" = "none" ]; then
                default_option="n"
            else
                default_option="c"
            fi
        fi
        log_info "Using existing network selection as default: $existing_network"
    fi
    
    while true; do
        local selection
        selection=$(read_terminal "Select option [${default_option}]: " "$default_option")
        
        case "${selection,,}" in
            c)
                # Custom network name
                local custom_network
                if [ -n "$existing_network" ] && [ "$existing_network" != "none" ]; then
                    custom_network=$(read_terminal "Enter custom network name [${existing_network}]: " "$existing_network")
                else
                    custom_network=$(read_terminal "Enter custom network name: " "")
                fi
                if [ -n "$custom_network" ]; then
                    SELECTED_NETWORK="$custom_network"
                    save_to_env "DOCKER_NETWORK" "$SELECTED_NETWORK"
                    return 0
                else
                    log_error "Network name cannot be empty" >&2
                fi
                ;;
            n|"")
                # No network (default)
                SELECTED_NETWORK=""
                save_to_env "DOCKER_NETWORK" "none"
                return 0
                ;;
            *)
                # Numeric selection
                if [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#network_array[@]} ]; then
                    SELECTED_NETWORK="${network_array[$((selection-1))]}"
                    save_to_env "DOCKER_NETWORK" "$SELECTED_NETWORK"
                    return 0
                else
                    log_error "Invalid selection. Please choose a valid option." >&2
                fi
                ;;
        esac
    done
}

# Function to prompt for internal service port
prompt_internal_service_port() {
    # Check if INTERNAL_SERVICE_PORT is set from environment
    if [ -n "$INTERNAL_SERVICE_PORT" ]; then
        log_info "Using environment variable for internal service port: ${INTERNAL_SERVICE_PORT}"
        return
    fi
    
    echo -e "${BLUE}=== Internal Service Configuration ===${NC}" >&2
    
    while [ -z "$INTERNAL_SERVICE_PORT" ]; do
        INTERNAL_SERVICE_PORT=$(read_terminal "Enter internal service port (exposed by Docker container) [3000]: " "3000")
        if [ -z "$INTERNAL_SERVICE_PORT" ]; then
            log_error "Internal service port cannot be empty" >&2
        elif ! [[ "$INTERNAL_SERVICE_PORT" =~ ^[0-9]+$ ]] || [ "$INTERNAL_SERVICE_PORT" -lt 1 ] || [ "$INTERNAL_SERVICE_PORT" -gt 65535 ]; then
            log_error "Invalid port number. Please enter a number between 1 and 65535." >&2
            INTERNAL_SERVICE_PORT=""
        fi
    done
    
    save_to_env "INTERNAL_SERVICE_PORT" "$INTERNAL_SERVICE_PORT"
}

# Function to save parameters to .para file
save_parameters() {
    local volume_name="${THIS_INSTANCE}-frpc"
    local temp_container="temp-para-config-$(date +%s)"
    local network_selection="$1"
    
    # Create a temporary directory for the config file
    local temp_dir="/tmp/para-config-${THIS_INSTANCE}"
    mkdir -p "$temp_dir"
    
    # Create JSON content for .para file
    local para_content="{\"network\":\"$network_selection\"}"
    
    # Write the parameters to a temporary file
    echo "$para_content" > "${temp_dir}/.para"
    
    # Copy the .para file to the volume
    # Create a temporary container to copy the file
    if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" -v "${temp_dir}:/tmp/config" alpine:latest sleep 10 >/dev/null 2>&1; then
        docker exec "$temp_container" cp /tmp/config/.para /etc/frp/.para 2>/dev/null || true
        docker stop "$temp_container" >/dev/null 2>&1
    fi
    
    # Clean up temporary directory
    rm -rf "$temp_dir"
}

# Function to load parameters from .para file
load_parameters() {
    local volume_name="${THIS_INSTANCE}-frpc"
    local temp_container="temp-para-config-$(date +%s)"
    local existing_network=""
    
    # Create a temporary container to check for existing config
    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        # Create temporary container to access volume
        if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
            # Check if .para exists
            if docker exec "$temp_container" test -f /etc/frp/.para >/dev/null 2>&1; then
                # Try to extract existing network selection from JSON
                existing_network=$(docker exec "$temp_container" grep '"network"' /etc/frp/.para | cut -d':' -f2 | tr -d ' "{}' 2>/dev/null || true)
                if [ -n "$existing_network" ]; then
                    log_info "Found existing network selection: $existing_network"
                fi
            fi
            # Stop the temporary container
            docker stop "$temp_container" >/dev/null 2>&1
        fi
    fi
    
    echo "$existing_network"
}

# Function to generate frpc.toml configuration (FRP Mode)
generate_frpc_config() {
    if [ "$FRP" = false ]; then
        log_info "Skipping FRP client configuration as FRP=false"
        return 0
    fi
    
    echo -e "${BLUE}=== FRP Client Configuration ===${NC}" >&2
    
    # Check if there's an existing frpc.toml in the volume
    local volume_name="${THIS_INSTANCE}-frpc"
    local temp_container="temp-frpc-config-$(date +%s)"
    local existing_server_addr=""
    local existing_server_port=""
    local existing_proxy1_remote_port=""
    local existing_proxy2_remote_port=""
    local existing_proxy3_remote_port=""
    
    # Create a temporary container to check for existing config
    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        log_info "Found existing volume: $volume_name"
        # Create temporary container to access volume
        if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
            # Check if frpc.toml exists
            if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
                # Try to extract existing values
                existing_server_addr=$(docker exec "$temp_container" grep "^serverAddr" /etc/frp/frpc.toml | cut -d'"' -f2 || true)
                existing_server_port=$(docker exec "$temp_container" grep "^serverPort" /etc/frp/frpc.toml | cut -d'=' -f2 | tr -d ' ' || true)
                existing_proxy1_remote_port=$(docker exec "$temp_container" grep -A 5 '\[\[proxies\]\]' /etc/frp/frpc.toml | grep "name = \"rad-${THIS_INSTANCE}\"" -A 5 | grep "remotePort" | cut -d'=' -f2 | tr -d ' ' | head -1 || true)
                existing_proxy2_remote_port=$(docker exec "$temp_container" grep -A 5 '\[\[proxies\]\]' /etc/frp/frpc.toml | grep "name = \"ssh-${THIS_INSTANCE}\"" -A 5 | grep "remotePort" | cut -d'=' -f2 | tr -d ' ' | head -1 || true)
                existing_proxy3_remote_port=$(docker exec "$temp_container" grep -A 5 '\[\[proxies\]\]' /etc/frp/frpc.toml | grep "name = \"web-${THIS_INSTANCE}\"" -A 5 | grep "remotePort" | cut -d'=' -f2 | tr -d ' ' | head -1 || true)
                
                if [ -n "$existing_server_addr" ]; then
                    log_info "Found existing server address: $existing_server_addr"
                fi
                if [ -n "$existing_server_port" ]; then
                    log_info "Found existing server port: $existing_server_port"
                fi
                if [ -n "$existing_proxy1_remote_port" ]; then
                    log_info "Found existing rad proxy remote port: $existing_proxy1_remote_port"
                fi
                if [ -n "$existing_proxy2_remote_port" ]; then
                    log_info "Found existing ssh proxy remote port: $existing_proxy2_remote_port"
                fi
                if [ -n "$existing_proxy3_remote_port" ]; then
                    log_info "Found existing web proxy remote port: $existing_proxy3_remote_port"
                fi
            fi
            # Stop the temporary container
            docker stop "$temp_container" >/dev/null 2>&1
        fi
    fi
    
    # Prompt for server address
    if [ -n "$FRP_SERVER_ADDR" ]; then
        SERVER_ADDR="$FRP_SERVER_ADDR"
        log_info "Using environment variable for FRP server address: $SERVER_ADDR"
    else
        if [ -n "$existing_server_addr" ]; then
            SERVER_ADDR=$(read_terminal "Enter FRP server address IP [${existing_server_addr}]: " "$existing_server_addr")
        else
            while [ -z "$SERVER_ADDR" ]; do
                SERVER_ADDR=$(read_terminal "Enter FRP server address IP: " "")
                if [ -z "$SERVER_ADDR" ]; then
                    log_error "Server address cannot be empty" >&2
                fi
            done
        fi
        save_to_env "FRP_SERVER_ADDR" "$SERVER_ADDR"
    fi
    
    # Use FRP server user from environment or default to 'teenfuture'
    if [ -n "$FRP_SERVER_USER" ]; then
        SERVER_USER="$FRP_SERVER_USER"
        log_info "Using environment variable for FRP server user: $SERVER_USER"
    else
        # Use default 'teenfuture' user if not specified
        SERVER_USER="teenfuture"
        log_info "Using default FRP server user: $SERVER_USER"
    fi
    
    # Use default server port (16999) without prompting, but use existing if available
    if [ -n "$FRP_SERVER_PORT" ]; then
        SERVER_PORT="$FRP_SERVER_PORT"
        log_info "Using environment variable for FRP server port: ${SERVER_PORT}"
    elif [ -n "$existing_server_port" ]; then
        SERVER_PORT="$existing_server_port"
    else
        # SERVER_PORT remains default 16999
        save_to_env "FRP_SERVER_PORT" "$SERVER_PORT"
    fi
    log_info "Using FRP server port: ${SERVER_PORT}"
    
    # Generate the frpc.toml content
    local config_content=""
    config_content+="serverAddr = \"${SERVER_ADDR}\"\n"
    config_content+="serverPort = ${SERVER_PORT}\n\n"
    
    # Dynamic proxy configuration based on environment variables
    # Look for environment variables in the format PROXY_*_PORT
    local proxy_configs=()
    
    # Process PROXY_*_PORT variables if they exist in environment
    local env_vars=($(compgen -v | grep -E "^PROXY_[A-Z0-9_]*_PORT$" 2>/dev/null))
    for var_name in "${env_vars[@]}"; do
        local var_value="${!var_name}"
        if [ -n "$var_value" ] && [[ "$var_value" =~ ^[0-9]+$ ]]; then
            # Extract proxy name from variable (e.g., PROXY_CUSTOM_PORT -> custom)
            local proxy_name=$(echo "$var_name" | sed 's/^PROXY_\([A-Z0-9_]*\)_PORT$/\1/' | tr '[:upper:]' '[:lower:]' | tr '_' '-')
            local local_port_var="PROXY_${proxy_name^^}_LOCAL_PORT"  # Convert back to uppercase with underscores
            
            # Get local port - either from PROXY_*_LOCAL_PORT or use defaults
            local local_port="$INTERNAL_SERVICE_PORT"  # Default to the main service port
            if [ -n "${!local_port_var}" ]; then
                local_port="${!local_port_var}"
            fi
            
            # Validate that local_port is not empty
            if [ -z "$local_port" ] || ! [[ "$local_port" =~ ^[0-9]+$ ]]; then
                log_error "Invalid or empty local port for ${proxy_name} proxy: '$local_port'"
                log_error "Make sure INTERNAL_SERVICE_PORT or ${local_port_var} is set correctly in your .env.rvs-prxy file"
                exit 1
            fi
            
            # Create the proxy configuration string
            proxy_configs+=("${proxy_name}-${local_port}:${var_value}")
            log_info "Adding ${proxy_name} proxy: local port ${local_port} to remote port ${var_value}"
        fi
    done
    
    # If no proxy variables were found, add a default proxy if needed
    if [ ${#proxy_configs[@]} -eq 0 ]; then
        # Add a default proxy using WEB_REMOTE_PORT if available, otherwise use a default
        local default_remote_port="${WEB_REMOTE_PORT:-8080}"
        # Validate that INTERNAL_SERVICE_PORT is not empty
        if [ -z "$INTERNAL_SERVICE_PORT" ] || ! [[ "$INTERNAL_SERVICE_PORT" =~ ^[0-9]+$ ]]; then
            log_error "Invalid or empty internal service port: '$INTERNAL_SERVICE_PORT'"
            log_error "Make sure INTERNAL_SERVICE_PORT is set correctly in your .env.rvs-prxy file"
            exit 1
        fi
        proxy_configs+=("default-${INTERNAL_SERVICE_PORT}:${default_remote_port}")
        log_info "Adding default proxy: local port $INTERNAL_SERVICE_PORT to remote port $default_remote_port"
    fi
    
    # Add all proxy configurations to the config content
    for proxy_config in "${proxy_configs[@]}"; do
        # Parse the proxy config format: proxy_name-local_port:remote_port
        # Example: web-3000:12077 -> proxy_name="web", local_port=3000, remote_port=12077
        local proxy_name=""
        local local_remote_port=""
        local local_port=""
        local remote_port=""
        
        # First, split on the LAST occurrence of '-' to get proxy name and local:remote part
        # This handles cases where the proxy name might contain hyphens after processing
        if [[ "$proxy_config" =~ ^(.*)-([0-9]+:[0-9]+)$ ]]; then
            proxy_name="${BASH_REMATCH[1]}"
            local_remote_port="${BASH_REMATCH[2]}"
        else
            log_error "Invalid proxy configuration format: $proxy_config"
            log_error "Expected format: proxy_name-local_port:remote_port (e.g., web-3000:12077)"
            exit 1
        fi
        
        # Then, split the local_remote_port on ':' to separate local and remote ports
        IFS=':' read -r local_port remote_port <<< "$local_remote_port"
        
        # Validate that both local and remote ports are set and are numeric
        if [ -z "$local_port" ] || [ -z "$remote_port" ] || ! [[ "$local_port" =~ ^[0-9]+$ ]] || ! [[ "$remote_port" =~ ^[0-9]+$ ]]; then
            log_error "Invalid proxy configuration format: $proxy_config"
            log_error "Local port: '$local_port', Remote port: '$remote_port'"
            exit 1
        fi
        
        config_content+="[[proxies]]\n"
        config_content+="name = \"${proxy_name}-${THIS_INSTANCE}\"\n"
        config_content+="type = \"tcp\"\n"
        config_content+="localPort = ${local_port}\n"
        # Use SERVICE_DOCKER_CONTAINER_NAME from .env* files if available, otherwise fall back to ${THIS_INSTANCE}-app
        if [ -n "$SERVICE_DOCKER_CONTAINER_NAME" ]; then
            config_content+="localIP = \"${SERVICE_DOCKER_CONTAINER_NAME}\"\n"
        else
            config_content+="localIP = \"${THIS_INSTANCE}-app\"\n"
        fi
        config_content+="remotePort = ${remote_port}\n\n"
    done
    
    # Create or update the frpc.toml file in the volume
    log_info "Creating/updating frpc.toml configuration"
    
    # Create a temporary directory for the config file
    local temp_dir="/tmp/frpc-config-${THIS_INSTANCE}"
    mkdir -p "$temp_dir"
    
    # Write the config to a temporary file
    echo -e "$config_content" > "${temp_dir}/frpc.toml"
    
    # Create the volume if it doesn't exist
    if ! docker volume inspect "$volume_name" >/dev/null 2>&1; then
        log_info "Creating Docker volume: $volume_name"
        docker volume create "$volume_name" >/dev/null
    fi
    
    # Copy the config file to the volume
    # Create a temporary container to copy the file
    if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" -v "${temp_dir}:/tmp/config" alpine:latest sleep 10 >/dev/null 2>&1; then
        docker exec "$temp_container" cp /tmp/config/frpc.toml /etc/frp/frpc.toml
        docker stop "$temp_container" >/dev/null 2>&1
    fi
    
    # Clean up temporary directory
    rm -rf "$temp_dir"
    
    log_info "FRP client configuration created successfully"
    echo "" >&2
}

# Function to check if any proxy configurations are web proxies
has_web_proxy() {
    # Check if any proxy config has "web" as the proxy name
    # Accept proxy configs as parameters
    local proxy_configs=("$@")
    
    for proxy_config in "${proxy_configs[@]}"; do
        # Split the proxy config into proxy_name-local_port:remote_port
        IFS='-' read -r proxy_name local_remote_port <<< "$proxy_config"
        if [ "$proxy_name" = "web" ]; then
            echo "true"
            return 0
        fi
    done
    
    echo "false"
    return 0
}

# Function to start the Docker container (FRP Mode)
start_docker_container() {
    if [ "$FRP" = false ]; then
        log_info "Skipping Docker container start as FRP=false"
        return 0
    fi
    
    echo -e "${BLUE}=== Starting Docker Container ===${NC}" >&2
    
    local container_name="${THIS_INSTANCE}-frpc"
    local volume_name="${THIS_INSTANCE}-frpc"
    
    # Check if container already exists
    if docker ps -a -f name=^${container_name}$ --format '{{.ID}}' | grep -q .; then
        log_info "Container ${container_name} already exists"
        if docker ps -f name=^${container_name}$ --format '{{.ID}}' | grep -q .; then
            log_info "Container is already running"
            return 0
        else
            log_info "Starting existing container ${container_name}"
            if ! docker start "${container_name}"; then
                log_error "Failed to start existing container"
                return 1
            fi
        fi
    else
        log_info "Starting new Docker container: ${container_name}"
        
        # Build docker run command
        local docker_run_cmd="docker run --restart=always -d"
        
        # Add network option if selected
        if [ -n "$SELECTED_NETWORK" ]; then
            docker_run_cmd+=" --network ${SELECTED_NETWORK}"
        fi
        
        # Add volume mount
        docker_run_cmd+=" -v ${volume_name}:/etc/frp"
        
        # Add container name
        docker_run_cmd+=" --name ${container_name}"
        
        # Add image
        docker_run_cmd+=" snowdreamtech/frpc:0.62.0-alpine3.21"
        
        # Execute the command
        log_info "Executing: $docker_run_cmd"
        if ! eval $docker_run_cmd; then
            log_error "Failed to start Docker container"
            return 1
        fi
    fi
    
    log_info "Docker container started successfully"
    echo "" >&2
}

# Function to display container operation commands (FRP Mode)
display_container_commands() {
    if [ "$FRP" = false ]; then
        log_info "Skipping container commands display as FRP=false"
        return 0
    fi
    
    local container_name="${THIS_INSTANCE}-frpc"
    local volume_name="${THIS_INSTANCE}-frpc"
    
    # Build the docker run command string with network option
    local display_docker_run_cmd="docker run --restart=always -d"
    if [ -n "$SELECTED_NETWORK" ]; then
        display_docker_run_cmd+=" --network ${SELECTED_NETWORK}"
    fi
    display_docker_run_cmd+=" -v ${volume_name}:/etc/frp --name ${container_name} snowdreamtech/frpc:0.62.0-alpine3.21"
    
    # Display container operation commands in a rectangle box without side borders
    echo -e "${BLUE}=============================================${NC}" >&2
    echo -e "${GREEN}Container Operation Commands${NC}" >&2
    echo -e "" >&2
    echo -e "Run Container: ${GREEN}${display_docker_run_cmd}${NC}" >&2
    echo -e "------------------------------------------------------------" >&2
    echo -e "Container Accessing: ${GREEN}docker exec -it ${container_name} sh${NC}" >&2
    echo -e "------------------------------------------------------------" >&2
    echo -e "View the Logs: ${GREEN}docker logs --tail 50 ${container_name}${NC}" >&2
    echo -e "------------------------------------------------------------" >&2
    echo -e "Inspect: ${GREEN}docker inspect ${container_name}${NC}" >&2
    echo -e "------------------------------------------------------------" >&2
    echo -e "Remove Container: ${GREEN}docker rm -f ${container_name}${NC}" >&2
    echo -e "${BLUE}=============================================${NC}" >&2
    echo -e "${YELLOW}[REMARK]${NC} Please copy down these operation commands for future use." >&2
    echo "" >&2
}

# Function to extract FRP server address from frpc.toml
extract_frp_server_address() {
    if [ "$FRP" = false ]; then
        log_info "Skipping FRP server address extraction as FRP=false"
        return 0
    fi
    
    local volume_name="${THIS_INSTANCE}-frpc"
    local temp_container="temp-extract-config-$(date +%s)"
    local server_addr=""
    
    # Create a temporary container to check for existing config
    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        # Create temporary container to access volume
        if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
            # Check if frpc.toml exists
            if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
                # Try to extract server address
                server_addr=$(docker exec "$temp_container" grep "^serverAddr" /etc/frp/frpc.toml | cut -d'"' -f2 || true)
            fi
            # Stop the temporary container
            docker stop "$temp_container" >/dev/null 2>&1
        fi
    fi
    
    echo "$server_addr"
}

# Function to extract all remote ports from frpc.toml
extract_all_remote_ports() {
    if [ "$FRP" = false ]; then
        log_info "Skipping remote port extraction as FRP=false"
        return 0
    fi
    
    local volume_name="${THIS_INSTANCE}-frpc"
    local temp_container="temp-extract-ports-$(date +%s)"
    local ports=()
    
    # Create a temporary container to check for existing config
    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        # Create temporary container to access volume
        if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
            # Check if frpc.toml exists
            if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
                # Get all remotePort values from the configuration
                local remote_ports=$(docker exec "$temp_container" grep "remotePort" /etc/frp/frpc.toml | cut -d'=' -f2 | tr -d ' ')
                
                # Convert to array
                while IFS= read -r port; do
                    if [ -n "$port" ]; then
                        ports+=("$port")
                    fi
                done <<< "$remote_ports"
            fi
            # Stop the temporary container
            docker stop "$temp_container" >/dev/null 2>&1
        fi
    fi
    
    # Output all ports as space-separated values
    echo "${ports[@]}"
}

# Function to load FRP configuration from volume (when .env is missing)
load_frp_config_from_volume() {
    local instance_name="$1"
    local volume_name="${instance_name}-frpc"
    local temp_container="temp-load-config-$(date +%s)"
    
    log_info "Loading FRP configuration from volume: $volume_name"
    
    # Create a temporary container to check for existing config
    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
            if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
                # Extract server address and port from frpc.toml
                local server_addr=$(docker exec "$temp_container" grep "^serverAddr" /etc/frp/frpc.toml | cut -d'"' -f2 || true)
                local server_port=$(docker exec "$temp_container" grep "^serverPort" /etc/frp/frpc.toml | cut -d'=' -f2 | tr -d ' ' || true)
                
                if [ -n "$server_addr" ]; then
                    FRP_SERVER_ADDR="$server_addr"
                    log_info "Loaded FRP_SERVER_ADDR from volume: $FRP_SERVER_ADDR"
                fi
                if [ -n "$server_port" ]; then
                    FRP_SERVER_PORT="$server_port"
                    log_info "Loaded FRP_SERVER_PORT from volume: $FRP_SERVER_PORT"
                fi
            fi
            docker stop "$temp_container" >/dev/null 2>&1
        fi
    fi
}

# Function to generate a new remote port for domain setup (FRP Mode)
generate_remote_port() {
    if [ "$FRP" = false ]; then
        log_info "Skipping remote port generation as FRP=false"
        return 0
    fi

    # Get existing remote ports to avoid conflicts
    local existing_ports
    existing_ports=$(extract_all_remote_ports)
    
    # Start from a base port (e.g., 12000) and find an available port
    local base_port=12000
    local new_port=$base_port
    
    # Check if any existing ports conflict
    while echo "$existing_ports" | grep -qw "$new_port"; do
        new_port=$((new_port + 1))
    done
    
    echo "$new_port"
}

# Function to update frpc.toml with new domain proxy configuration (FRP Mode)
update_frpc_config_for_domain() {
    local new_remote_port="$1"
    
    if [ "$FRP" = false ]; then
        log_info "Skipping frpc.toml update as FRP=false"
        return 0
    fi

    log_info "Updating frpc.toml with new domain proxy configuration..."

    local volume_name="${THIS_INSTANCE}-frpc"
    local temp_container="temp-update-config-$(date +%s)"
    local temp_dir="/tmp/frpc-update-${THIS_INSTANCE}"
    
    # Create temporary directory
    mkdir -p "$temp_dir"

    # Get existing configuration
    local existing_config=""
    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
            if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
                existing_config=$(docker exec "$temp_container" cat /etc/frp/frpc.toml)
            fi
            docker stop "$temp_container" >/dev/null 2>&1
        fi
    fi

    # Get server address and port from existing config or environment
    local server_addr="${FRP_SERVER_ADDR}"
    local server_port="${FRP_SERVER_PORT:-16999}"
    
    if [ -z "$existing_config" ]; then
        # No existing config, create new one
        log_info "Creating new frpc.toml configuration"
        existing_config="serverAddr = \"${server_addr}\"\nserverPort = ${server_port}\n\n"
    fi

    # Generate proxy name from domain
    local proxy_name
    proxy_name=$(echo "$DOMAIN_NAME" | sed 's/\./-/g' | tr '[:upper:]' '[:lower:]')
    
    # Add new proxy configuration for this domain
    local new_proxy_config
    new_proxy_config="[[proxies]]\n"
    new_proxy_config+="name = \"${proxy_name}-${THIS_INSTANCE}\"\n"
    new_proxy_config+="type = \"tcp\"\n"
    new_proxy_config+="localPort = ${LOCAL_SERVICE_PORT}\n"
    if [ -n "$SERVICE_DOCKER_CONTAINER_NAME" ]; then
        new_proxy_config+="localIP = \"${SERVICE_DOCKER_CONTAINER_NAME}\"\n"
    else
        new_proxy_config+="localIP = \"${THIS_INSTANCE}-app\"\n"
    fi
    new_proxy_config+="remotePort = ${new_remote_port}\n\n"

    # Append new proxy config to existing config
    local full_config="${existing_config}${new_proxy_config}"

    # Write the config to a temporary file
    echo -e "$full_config" > "${temp_dir}/frpc.toml"

    # Create the volume if it doesn't exist
    docker volume create "$volume_name" >/dev/null 2>&1 || true

    # Copy the config file to the volume
    docker run --rm -v "$volume_name:/etc/frp" -v "${temp_dir}:/tmp/source:ro" alpine:latest cp /tmp/source/frpc.toml /etc/frp/frpc.toml

    # Clean up
    rm -rf "$temp_dir"

    # Restart the FRP client container to apply new configuration
    log_info "Restarting FRP client container to apply new configuration..."
    docker restart "${THIS_INSTANCE}-frpc" >/dev/null 2>&1

    log_info "frpc.toml configuration updated successfully"
}

# Function to execute Docker commands locally (Traefik Mode)
local_docker_exec() {
    local command="$1"
    log_debug "Executing local Docker command: $command"
    eval "$command"
}

# Function to copy files to Docker volume locally (Traefik Mode)
local_volume_copy() {
    local source="$1"
    local volume="$2"
    local dest="$3"
    log_debug "Copying $source to volume $volume:$dest"
    
    docker run --rm -v "$volume:$dest" -v "$(dirname "$source"):/tmp/source:ro" alpine:latest cp -r "/tmp/source/$(basename "$source")" "$dest/"
}

# Function to parse EXTRA_ENTRYPOINTS variable and produce entrypoint args and port mappings
# Globals read: EXTRA_ENTRYPOINTS
# Globals set: EXTRA_ENTRYPOINT_ARGS (array of --entrypoints.NAME.address=ADDRESS)
#              EXTRA_PORT_MAPPINGS (array of port mappings for docker-compose/docker-run)
#              EXTRA_COMPOSE_ENTRYPOINTS (string of YAML command entries for compose)
#              EXTRA_COMPOSE_PORTS (string of YAML port entries for compose)
parse_extra_entrypoints() {
    EXTRA_ENTRYPOINT_ARGS=()
    EXTRA_PORT_MAPPINGS=()
    EXTRA_COMPOSE_ENTRYPOINTS=""
    EXTRA_COMPOSE_PORTS=""

    if [ -z "$EXTRA_ENTRYPOINTS" ]; then
        return
    fi

    local saved_ifs="$IFS"
    IFS=','
    for entry in $EXTRA_ENTRYPOINTS; do
        IFS="$saved_ifs"
        entry=$(echo "$entry" | xargs)
        local name="${entry%%=*}"
        local address="${entry#*=}"
        address=$(echo "$address" | xargs)

        # Determine port and protocol from address
        local port="${address#:}"
        local protocol="tcp"
        if [[ "$port" == *"/"* ]]; then
            protocol="${port#*/}"
            port="${port%%/*}"
        fi

        # Build entrypoint arg
        EXTRA_ENTRYPOINT_ARGS+=("--entrypoints.${name}.address=${address}")

        # Build port mapping
        if [ "$protocol" = "udp" ]; then
            EXTRA_PORT_MAPPINGS+=("${port}:${port}/udp")
        else
            EXTRA_PORT_MAPPINGS+=("${port}:${port}")
        fi

        # Build compose YAML strings (with real newlines for heredoc expansion)
        EXTRA_COMPOSE_ENTRYPOINTS="${EXTRA_COMPOSE_ENTRYPOINTS}      - '--entrypoints.${name}.address=${address}'
"
        if [ "$protocol" = "udp" ]; then
            EXTRA_COMPOSE_PORTS="${EXTRA_COMPOSE_PORTS}      - '${port}:${port}/udp'
"
        else
            EXTRA_COMPOSE_PORTS="${EXTRA_COMPOSE_PORTS}      - '${port}:${port}'
"
        fi
    done
    IFS="$saved_ifs"
}

# Function to check if Traefik container exists locally (Traefik Mode)
check_traefik_container() {
    log_info "Checking if Traefik container exists locally..."
    
    local container_exists
    container_exists=$(docker ps -a --filter name=traefik --format '{{.Names}}' 2>/dev/null || true)
    
    if [ -n "$container_exists" ] && [ "$container_exists" = "traefik" ]; then
        log_info "Traefik container already exists locally"
        return 0
    else
        log_info "Traefik container does not exist locally"
        return 1
    fi
}

# Function to create Traefik Docker container locally if it doesn't exist (Traefik Mode)
create_traefik_container_local() {
    log_info "Checking if Traefik container exists locally..."
    
    local container_exists
    container_exists=$(docker ps -a --filter name=traefik --format '{{.Names}}' 2>/dev/null || true)
    
    # Use fixed container name "traefik" regardless of instance name
    local TRAEFIK_CONTAINER_NAME="traefik"
    
    # Use fixed volume name "traefik_data" regardless of instance name
    local TRAEFIK_VOLUME_NAME="traefik_data"
    
    if [ -n "$container_exists" ] && [ "$container_exists" = "$TRAEFIK_CONTAINER_NAME" ]; then
        log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' already exists locally"

        # Parse extra entrypoints and check if container needs recreation
        parse_extra_entrypoints
        if [ ${#EXTRA_ENTRYPOINT_ARGS[@]} -gt 0 ]; then
            # Check if entrypoints are already in the container
            local current_cmd
            current_cmd=$(docker inspect traefik --format='{{json .Config.Cmd}}' 2>/dev/null || echo "[]")
            local needs_recreate=false
            for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
                if ! echo "$current_cmd" | grep -qF -- "${ep_arg}"; then
                    needs_recreate=true
                    break
                fi
            done
            if [ "$needs_recreate" = true ]; then
                log_info "Extra entrypoints changed, recreating Traefik container to apply..."
                # Re-enter the creation path by removing the container reference
                docker stop traefik 2>/dev/null || true
                sleep 2
                docker rm traefik 2>/dev/null || true
                container_exists=""
            fi
        fi
    fi

    if [ -n "$container_exists" ] && [ "$container_exists" = "$TRAEFIK_CONTAINER_NAME" ]; then
        log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' exists, updating configuration..."
        
        # Use specified Traefik version or default to v3.6.9
        local TRAEFIK_VER="${TRAEFIK_VER:-v3.6.9}"
        local TRAEFIK_IMAGE="traefik:$TRAEFIK_VER"
        
        # Use specified Let's Encrypt email or default
        local LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-admin@yourdomain.com}"
        
        # Create a minimal dynamic configuration file for the dashboard and basic routing, plus HTTP to HTTPS redirect
        local temp_config="/tmp/traefik-$$.yml"
        cat > "$temp_config" << 'EOF_HEREDOC'
http:
  routers:
    api:
      rule: "PathPrefix(`/api`)"
      service: api@internal
    dashboard:
      rule: "PathPrefix(`/dashboard`)"
      service: api@internal
    http-catchall:
      rule: "PathRegexp(`{any:.*}`)"
      entryPoints:
        - "web"
      service: "noop@internal"
      middlewares:
        - "redirect-to-https"
  middlewares:
    api-headers:
      headers:
        customFrameOptionsValue: SAMEORIGIN
        contentTypeNosniff: true
        browserXssFilter: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
    redirect-to-https:
      redirectScheme:
        scheme: "https"
        permanent: true
EOF_HEREDOC

        # Copy the configuration file and create necessary directories in the volume with proper permissions
        docker run --rm -v "$TRAEFIK_VOLUME_NAME:/etc/traefik" -v "/tmp:/tmp" alpine:latest sh -c 'mkdir -p /etc/traefik/config /etc/traefik/acme && cp /tmp/traefik.yml /etc/traefik/config/traefik.yml && touch /etc/traefik/acme/acme.json && chmod 600 /etc/traefik/acme/acme.json'
        cp "$temp_config" "/tmp/traefik.yml"
        docker run --rm -v "$TRAEFIK_VOLUME_NAME:/etc/traefik" -v "/tmp:/tmp" alpine:latest sh -c 'cp /tmp/traefik.yml /etc/traefik/config/traefik.yml'
        rm -f "$temp_config"
        
        log_info "Configuration updated in existing Traefik container"
        
        # Exit early since container already exists and configuration was updated
        log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' is running locally using image: $TRAEFIK_IMAGE with volume: $TRAEFIK_VOLUME_NAME"
    
        # Wait a bit for the container to start/update
        sleep 3
        
        # Commands will be shown at the end of setup_traefik_only()
        return 0
    else
        log_info "Creating Traefik container '$TRAEFIK_CONTAINER_NAME' locally..."
        
        # Create the Docker volume for persistent data using fixed name
        docker volume create "$TRAEFIK_VOLUME_NAME"
    fi
    
    # Use specified Traefik version or default to v3.6.9
    local TRAEFIK_VER="${TRAEFIK_VER:-v3.6.9}"
    local TRAEFIK_IMAGE="traefik:$TRAEFIK_VER"
    
    # Use specified Let's Encrypt email or default
    local LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-admin@yourdomain.com}"
    
    # Create a minimal dynamic configuration file for the dashboard and basic routing, plus HTTP to HTTPS redirect
    local temp_config="/tmp/traefik-$$.yml"
    cat > "$temp_config" << 'EOF_HEREDOC'
http:
  routers:
    api:
      rule: "PathPrefix(`/api`)"
      service: api@internal
    dashboard:
      rule: "PathPrefix(`/dashboard`)"
      service: api@internal
    http-catchall:
      rule: "PathRegexp(`{any:.*}`)"
      entryPoints:
        - "web"
      service: "noop@internal"
      middlewares:
        - "redirect-to-https"
  middlewares:
    api-headers:
      headers:
        customFrameOptionsValue: SAMEORIGIN
        contentTypeNosniff: true
        browserXssFilter: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
    redirect-to-https:
      redirectScheme:
        scheme: "https"
        permanent: true
EOF_HEREDOC

    # Copy the configuration file to volume
    cp "$temp_config" "/tmp/traefik.yml"
    docker run --rm -v "$TRAEFIK_VOLUME_NAME:/etc/traefik" -v "/tmp:/tmp" alpine:latest sh -c 'mkdir -p /etc/traefik/config /etc/traefik/acme && cp /tmp/traefik.yml /etc/traefik/config/traefik.yml && touch /etc/traefik/acme/acme.json && chmod 600 /etc/traefik/acme/acme.json'
    rm -f "$temp_config"
    
    # Parse extra entrypoints for dynamic entrypoint/port configuration
    parse_extra_entrypoints

    # Determine Docker version to decide if we need the version field
    local compose_version_field
    docker_version=$(docker --version | sed 's/Docker version //' | sed 's/,.*//')
    major_version=$(echo "$docker_version" | cut -d. -f1)
    minor_version=$(echo "$docker_version" | cut -d. -f2)
    
    # Docker Compose v2.0+ (around Docker 20.10.0+) deprecates the version field
    # But keep it for older versions for compatibility
    if [ "$major_version" -lt 20 ] || ([ "$major_version" -eq 20 ] && [ "$minor_version" -lt 10 ]); then
        compose_version_field="version: '3.8'"
    else
        compose_version_field=""
    fi
    
    # Create docker-compose.yml for Traefik using the specified version and volume
    local temp_compose="/tmp/traefik-compose-$$.yml"
    if [ -n "$compose_version_field" ]; then
        # Include version field for older Docker versions
        cat > "$temp_compose" << EOF
$compose_version_field

services:
  traefik:
    image: $TRAEFIK_IMAGE
    container_name: $TRAEFIK_CONTAINER_NAME
    restart: unless-stopped
    command:
      - '--api.insecure=true'
      - '--providers.docker=true'
      - '--providers.docker.exposedbydefault=false'
      - '--entrypoints.web.address=:80'
      - '--entrypoints.websecure.address=:443'
${EXTRA_COMPOSE_ENTRYPOINTS}      - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
      - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
      - '--certificatesresolvers.letsencrypt.acme.email=$LETSENCRYPT_EMAIL'
      - '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'
      - '--providers.file.directory=/etc/traefik/config'
      - '--providers.file.watch=true'
    ports:
      - '80:80'
      - '443:443'
${EXTRA_COMPOSE_PORTS}      - "127.0.0.1:${DASHBOARD_PORT}:8080"  # Bind dashboard to localhost only for security
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $TRAEFIK_VOLUME_NAME:/etc/traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.traefik.rule=Host(\`traefik.${DOMAIN_NAME:-traefik.localhost}\`)'
      - 'traefik.http.routers.traefik.service=api@internal'
      - 'traefik.http.routers.traefik.middlewares=auth'
      - 'traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6usmud2$$YqJ2a7vLp9J9l5zK8Y4hD1'

volumes:
  $TRAEFIK_VOLUME_NAME:
    external: true

networks:
  default:
    name: ${DOCKER_NETWORK}
    external: true
EOF
    else
        # No version field for newer Docker versions
        cat > "$temp_compose" << EOF
services:
  traefik:
    image: $TRAEFIK_IMAGE
    container_name: $TRAEFIK_CONTAINER_NAME
    restart: unless-stopped
    command:
      - '--api.insecure=true'
      - '--providers.docker=true'
      - '--providers.docker.exposedbydefault=false'
      - '--entrypoints.web.address=:80'
      - '--entrypoints.websecure.address=:443'
${EXTRA_COMPOSE_ENTRYPOINTS}      - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
      - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
      - '--certificatesresolvers.letsencrypt.acme.email=$LETSENCRYPT_EMAIL'
      - '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'
      - '--providers.file.directory=/etc/traefik/config'
      - '--providers.file.watch=true'
    ports:
      - '80:80'
      - '443:443'
${EXTRA_COMPOSE_PORTS}      - "127.0.0.1:${DASHBOARD_PORT}:8080"  # Bind dashboard to localhost only for security
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $TRAEFIK_VOLUME_NAME:/etc/traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.traefik.rule=Host(\`traefik.${DOMAIN_NAME:-traefik.localhost}\`)'
      - 'traefik.http.routers.traefik.service=api@internal'
      - 'traefik.http.routers.traefik.middlewares=auth'
      - 'traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6usmud2$$YqJ2a7vLp9J9l5zK8Y4hD1'

volumes:
  $TRAEFIK_VOLUME_NAME:
    external: true

networks:
  default:
    name: ${DOCKER_NETWORK}
    external: true
EOF
    fi

    # Create the network if it doesn't exist
    docker network create ${DOCKER_NETWORK} 2>/dev/null || true
    
    # Determine which docker compose command to use based on Docker version
    if [ "$major_version" -gt 20 ] || ([ "$major_version" -eq 20 ] && [ "$minor_version" -ge 10 ]); then
        compose_cmd="docker compose"
    else
        compose_cmd="docker-compose"
    fi
    
    # Start Traefik container using appropriate command
    $compose_cmd -f "$temp_compose" up -d
    cp "$temp_compose" /tmp/docker-compose.yml
    rm -f "$temp_compose"
    
    log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' is running locally using image: $TRAEFIK_IMAGE with volume: $TRAEFIK_VOLUME_NAME"
    log_info "Docker compose file saved to /tmp/docker-compose.yml for reference"
    log_info "Traefik dashboard: http://localhost:${DASHBOARD_PORT}/dashboard/"
    
    # Wait a bit for the container to start
    sleep 3
    
    # Commands will be shown at the end of setup_traefik_only()
}

# Function to configure Traefik locally to connect to Application Service (Traefik Mode)
configure_traefik_local() {
    log_info "Configuring Traefik locally to connect to Application Service..."
    
    # Parse LOCAL_SERVICE_PORT for multi-port support
    # Format: comma-separated port/protocol entries (protocol defaults to tcp)
    # Example: "80,4443/udp" → two ports: 80/tcp and 4443/udp
    local service_ports=()
    local service_protocols=()
    local first_tcp_port=""
    local saved_ifs="$IFS"
    local entry port protocol
    IFS=','
    for entry in $LOCAL_SERVICE_PORT; do
        IFS="$saved_ifs"
        entry=$(echo "$entry" | xargs)
        port="$entry"
        protocol="tcp"
        if [[ "$port" == *"/"* ]]; then
            protocol="${port#*/}"
            port="${port%%/*}"
        fi
        service_ports+=("$port")
        service_protocols+=("$protocol")
        if [ -z "$first_tcp_port" ] && [ "$protocol" = "tcp" ]; then
            first_tcp_port="$port"
        fi
    done
    IFS="$saved_ifs"
    
    if [ -z "$first_tcp_port" ]; then
        log_error "No TCP port found in LOCAL_SERVICE_PORT ($LOCAL_SERVICE_PORT)"
        log_error "At least one TCP port is required for HTTP routing"
        exit 1
    fi
    
    # Check if the target service is running
    local service_running
    service_running=$(docker ps --filter name="$LOCAL_SERVICE_NAME" --format '{{.Names}}' 2>/dev/null || true)
    
    if [ -z "$service_running" ]; then
        log_error "Target application service '$LOCAL_SERVICE_NAME' is not running."
        log_error "Please make sure your Application container is started."
        exit 1
    fi
    
    # Check if the target service is connected to the ${DOCKER_NETWORK} network
    local service_in_network
    service_in_network=$(docker inspect "$LOCAL_SERVICE_NAME" 2>/dev/null | grep -o "\"${DOCKER_NETWORK}\"" || true)
    
    if [ -z "$service_in_network" ]; then
        log_error "Target application service '$LOCAL_SERVICE_NAME' is not connected to '${DOCKER_NETWORK}' network."
        log_error "Please ensure the target application container is connected to the '${DOCKER_NETWORK}' network."
        log_error "Application should connecting to ${DOCKER_NETWORK} network, here is the docker compose yml example:"
        log_error "  services:"
        log_error "    you-app:"
        log_error "      networks:"
        log_error "        - ${DOCKER_NETWORK}"
        log_error "  networks:"
        log_error "    ${DOCKER_NETWORK}:"
        log_error "      external: true"
        log_error ""
        log_error "You can connect it with: docker network connect ${DOCKER_NETWORK} $LOCAL_SERVICE_NAME"
        log_error "Make sure your Application container is started."
        exit 1
    fi
    
    # Check if DOMAIN_NAME contains a sub-path (like event.cgu.org.hk/api)
    local domain_part
    local path_part
    if [[ "$DOMAIN_NAME" == *"/"* ]]; then
        # Extract domain and path parts
        domain_part=$(echo "$DOMAIN_NAME" | cut -d'/' -f1)
        path_part="/$(echo "$DOMAIN_NAME" | cut -d'/' -f2-)"
        log_info "Detected sub-path configuration: domain=$domain_part, path=$path_part"
    else
        domain_part="$DOMAIN_NAME"
        path_part=""
    fi
    
    # Create dynamic configuration file for the service inside the volume
    local temp_config="/tmp/app-service-$$.yml"
    
    if [ -n "$path_part" ]; then
        # Configure for sub-path routing
        cat > "$temp_config" << EOF
http:
  routers:
    ${domain_part}-https:
      rule: 'Host(\`${domain_part}\`) && PathPrefix(\`${path_part}\`)'
      service: ${domain_part}-service
      tls:
        certResolver: letsencrypt
      entryPoints:
        - websecure
      middlewares:
        - ${domain_part}-strip-prefix
    ${domain_part}-http:
      rule: 'Host(\`${domain_part}\`) && PathPrefix(\`${path_part}\`)'
      service: ${domain_part}-service
      entryPoints:
        - web
      middlewares:
        - redirect-to-https
        - ${domain_part}-strip-prefix
  middlewares:
    ${domain_part}-strip-prefix:
      stripPrefix:
        prefixes:
          - ${path_part}
  services:
    ${domain_part}-service:
      loadBalancer:
        servers:
          - url: 'http://${LOCAL_SERVICE_NAME}:${first_tcp_port}'
        passHostHeader: true
EOF
    else
        # Configure for root domain routing
        cat > "$temp_config" << EOF
http:
  routers:
    ${domain_part}-https:
      rule: 'Host(\`${DOMAIN_NAME}\`)'
      service: ${domain_part}-service
      tls:
        certResolver: letsencrypt
      entryPoints:
        - websecure
    ${domain_part}-http:
      rule: 'Host(\`${DOMAIN_NAME}\`)'
      service: ${domain_part}-service
      entryPoints:
        - web
      middlewares:
        - redirect-to-https
  services:
    ${domain_part}-service:
      loadBalancer:
        servers:
          - url: 'http://${LOCAL_SERVICE_NAME}:${first_tcp_port}'
        passHostHeader: true
EOF
    fi

    # Generate UDP/TCP router config for non-HTTP service ports (e.g., UDP relay ports)
    local udp_routers_added=false
    if [ ${#service_ports[@]} -gt 1 ]; then
        parse_extra_entrypoints

        local udp_ep_ports=()
        local udp_ep_names=()
        for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
            local ep_name="${ep_arg#--entrypoints.}"
            ep_name="${ep_name%%.address=*}"
            local ep_addr="${ep_arg#*=}"
            if [[ "$ep_addr" == *"/udp" ]]; then
                local ep_port="${ep_addr#:}"
                ep_port="${ep_port%/udp}"
                udp_ep_ports+=("$ep_port")
                udp_ep_names+=("$ep_name")
            fi
        done

        for i in "${!service_ports[@]}"; do
            if [ "${service_protocols[$i]}" = "udp" ]; then
                local container_port="${service_ports[$i]}"
                local matching_ep=""

                for j in "${!udp_ep_ports[@]}"; do
                    if [ "${udp_ep_ports[$j]}" = "$container_port" ]; then
                        matching_ep="${udp_ep_names[$j]}"
                        break
                    fi
                done

                if [ -z "$matching_ep" ] && [ ${#udp_ep_names[@]} -gt 0 ]; then
                    matching_ep="${udp_ep_names[0]}"
                fi

                if [ -n "$matching_ep" ]; then
                    cat >> "$temp_config" << EOF

udp:
  routers:
    ${domain_part}-${matching_ep}:
      entryPoints:
        - ${matching_ep}
      service: ${domain_part}-udp
  services:
    ${domain_part}-udp:
      loadBalancer:
        servers:
          - address: "${LOCAL_SERVICE_NAME}:${container_port}"
EOF
                    udp_routers_added=true
                    log_info "Added UDP routing: entrypoint '${matching_ep}' → ${LOCAL_SERVICE_NAME}:${container_port}"
                else
                    log_warn "UDP port ${container_port} in LOCAL_SERVICE_PORT has no matching EXTRA_ENTRYPOINTS entry"
                    log_warn "Add e.g. EXTRA_ENTRYPOINTS=\"myudp=:${container_port}/udp\" to your .env file"
                fi
            fi
        done
    fi

    # Generate a unique configuration file name based on the domain and service name to avoid conflicts
    # Use the domain (without path) and service name to create a unique identifier
    local domain_base=$(echo "$DOMAIN_NAME" | cut -d'/' -f1)
    local config_file_name="app-service-${domain_base}-${LOCAL_SERVICE_NAME//[^a-zA-Z0-9]/-}.yml"
    
    # Use fixed volume name for Traefik configuration
    local TRAEFIK_VOLUME_NAME="traefik_data"
    
    # Check if a config file already exists for this domain and service combination
    local config_exists
    config_exists=$(docker run --rm -v "${TRAEFIK_VOLUME_NAME}:/etc/traefik" alpine:latest ls -la /etc/traefik/config/ 2>/dev/null | grep "$config_file_name" || true)
    
    if [ -n "$config_exists" ]; then
        log_warn "Configuration file $config_file_name already exists, it will be overwritten"
        log_info "This will update the existing domain configuration for ${DOMAIN_NAME}"
    else
        log_info "Creating new configuration for domain ${DOMAIN_NAME}"
    fi
    
    # Copy the configuration file into the volume using a temporary container
    cp "$temp_config" "/tmp/app-service.yml"
    docker run --rm -v "${TRAEFIK_VOLUME_NAME}:/etc/traefik" -v "/tmp:/tmp" alpine:latest sh -c "cp /tmp/app-service.yml /etc/traefik/config/$config_file_name"
    rm -f "$temp_config"
    
    log_info "Added configuration for service ${LOCAL_SERVICE_NAME} on domain $domain_base to Traefik (file: $config_file_name)"
    
    # Connect the traefik container to the ${DOCKER_NETWORK} network to ensure communication
    docker network connect ${DOCKER_NETWORK} traefik 2>/dev/null || true
    
    # Check if restart is needed based on environment variable (default: false for dynamic loading)
    if [ "${RESTART_TRAEFIK:-false}" = "true" ]; then
        # Restart Traefik to apply the new configuration
        docker restart traefik
        log_info "Traefik restarted to apply new configuration"
    else
        log_info "Traefik dynamic configuration added (restart not performed, Traefik should auto-load the configuration)"
        log_info "If needed, you can manually restart Traefik with: docker restart traefik"
    fi
    
    log_info "Traefik configured to route traffic to Application Service"
    
    # Show success message based on configuration type
    if [ -n "$path_part" ]; then
        log_info "Service will be accessible at: https://$domain_part$path_part"
    else
        log_info "Service will be accessible at: https://$DOMAIN_NAME"
    fi
}

# Function to check if Application Service is running locally (Traefik Mode)
check_app_service_local() {
    log_info "Checking if Application Service is running locally..."
    
    local service_exists
    service_exists=$(docker ps --filter name="$LOCAL_SERVICE_NAME" --format '{{.Names}}' 2>/dev/null || true)
    
    if [ -n "$service_exists" ] && [ "$service_exists" = "$LOCAL_SERVICE_NAME" ]; then
        log_info "Application Service is running locally"
        return 0
    else
        log_error "Application Service is not running locally: $LOCAL_SERVICE_NAME"
        return 1
    fi
}

# Function to check status of Traefik and Application Service locally (Traefik Mode)
check_status_local() {
    log_info "Checking status of Traefik and Application Service locally..."
    
    local traefik_status
    local app_status
    
    traefik_status=$(docker ps --filter name=traefik --format '{{.Status}}' 2>/dev/null || echo 'Not running')
    app_status=$(docker ps --filter name="$LOCAL_SERVICE_NAME" --format '{{.Status}}' 2>/dev/null || echo 'Not running')
    
    echo -e "Traefik status: ${GREEN}$traefik_status${NC}"
    echo -e "Application Service ($LOCAL_SERVICE_NAME) status: ${GREEN}$app_status${NC}"
}

# Function to view Traefik logs locally (Traefik Mode)
view_traefik_logs_local() {
    log_info "Viewing Traefik logs..."
    docker logs --tail 50 traefik
}

# Function to update docker-compose.app.yml on proxy server with dynamic ports (FRP Mode)
update_proxy_server_config() {
    if [ "$FRP" = false ]; then
        log_info "Skipping proxy server configuration update as FRP=false"
        return 0
    fi
    
    local proxy_server_addr="$1"
    local has_web_proxy="$2"
    shift 2  # Remove the first two arguments (proxy_server_addr and has_web_proxy), rest are ports
    
    # The remaining arguments are all the ports
    local all_ports=("$@")
    
    # Make sure we have at least 1 port to work with
    if [ ${#all_ports[@]} -lt 1 ]; then
        log_error "Need at least 1 port for proxy configuration"
        return 1
    fi
    
    # Use the first available port as the web port for domain routing
    local web_port="${all_ports[0]}"
    
    echo -e "${BLUE}=== Updating Proxy Server Configuration ===${NC}" >&2
    
    # Check if SSH key exists
    if [ ! -f ~/.ssh/itl-devs1 ]; then
        log_error "SSH key ~/.ssh/itl-devs1 not found"
        return 1
    fi
    
    # Prompt for domain name if not already set
    if [ -z "$DOMAIN_NAME" ]; then
        DOMAIN_NAME=$(prompt_domain_name)
    fi
    
    # Create temporary script to run on proxy server that contains the direct update functions
    local temp_script="/tmp/update-compose-$THIS_INSTANCE.sh"
    cat > "$temp_script" << 'EOF'
#!/bin/bash

# Function to check if the instance configuration is complete and up-to-date in ports section
# This function is a simplified version - in a real implementation, this would check dynamically
check_ports_config_status() {
    local INSTANCE_NAME="$1"
    local PORTS_CONFIG=("${@:2}")  # All remaining arguments are ports
    local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
    
    # Set the instance marker for ports section
    local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting ports ===="
    
    # Check if the instance marker exists
    if [ ! -f "${COMPOSE_FILE}" ] || ! grep -q "${INSTANCE_MARKER}" "${COMPOSE_FILE}"; then
        echo "not_found"
        return
    fi
    
    # Find the exact lines of the configuration block
    local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
    local end_line=$(grep -n "# =========================" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
    
    # If we can't find the end marker, use a default range
    if [ -z "$end_line" ]; then
        end_line=$((start_line + ${#PORTS_CONFIG[@]} + 5))
    fi
    
    # Extract the configuration block
    local config_block=$(sed -n "${start_line},${end_line}p" "${COMPOSE_FILE}")
    
    # Count the port lines
    local port_lines=$(echo "$config_block" | grep -c '^      - "[0-9]*:[0-9]*/tcp"')
    
    # For simplicity in this implementation, we'll mark as 'outdated' to update
    echo "outdated"
}

# Function to check if the instance configuration is complete and up-to-date in labels section
check_labels_config_status() {
    local INSTANCE_NAME="$1"
    local WEB_PORT="$2"
    local DOMAIN_NAME="$3"
    local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
    
    # Set the instance marker for labels section (different from ports)
    local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting labels ===="
    
    # Check if the instance marker exists in labels section
    if [ ! -f "${COMPOSE_FILE}" ] || ! grep -q "${INSTANCE_MARKER}" "${COMPOSE_FILE}"; then
        echo "not_found"
        return
    fi
    
    # Find the exact lines of the configuration block
    local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
    local end_line=$(grep -n "# =========================" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
    
    # If we can't find the end marker, use a default range
    if [ -z "$end_line" ]; then
        end_line=$((start_line + 15))
    fi
    
    # Extract the configuration block
    local config_block=$(sed -n "${start_line},${end_line}p" "${COMPOSE_FILE}")
    
    # Count the label lines (should be 11 lines plus markers)
    local label_lines=$(echo "$config_block" | grep -c '^      - "traefik\.')
    
    if [ "$label_lines" -ge 11 ]; then
        # Check if the domain and port match our current configuration
        local current_domain=$(echo "$config_block" | grep 'rule=Host(' | head -1 | sed 's/.*Host(`\([^)]*\)`).*/\1/')
        local current_port=$(echo "$config_block" | grep 'loadbalancer.server.port' | sed 's/.*port=\([0-9]*\).*/\1/')
        
        if [ -n "$current_domain" ] && [ -n "$current_port" ] && \
           [ "$current_domain" = "$DOMAIN_NAME" ] && [ "$current_port" = "$WEB_PORT" ]; then
            echo "up_to_date"
        else
            echo "outdated"
        fi
    else
        echo "incomplete"
    fi
}

# Function to find the correct insertion point in a specific section
find_section_insertion_point() {
    local section_name="$1"
    local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
    
    # Find the section start line
    local section_start=$(grep -n "${section_name}:" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
    if [ -z "$section_start" ]; then
        echo "0"
        return
    fi
    
    # Look for the next section after this one or end of section
    local current_line=$((section_start + 1))
    local total_lines=$(wc -l < "${COMPOSE_FILE}")
    
    while [ "$current_line" -le "$total_lines" ]; do
        local line_content=$(sed -n "${current_line}p" "${COMPOSE_FILE}")
        
        # If we hit a line that starts a new section (not indented or comment)
        if [[ "$line_content" =~ ^[[:space:]]*[a-zA-Z] ]] && [[ ! "$line_content" =~ ^[[:space:]]*# ]] && [[ ! "$line_content" =~ ^[[:space:]]*- ]]; then
            echo "$current_line"
            return
        fi
        
        current_line=$((current_line + 1))
    done
    
    # If we didn't find the end, insert at the end of the file
    echo $((total_lines + 1))
}

# Function to update ports section with dynamic ports
update_ports_config() {
    local INSTANCE_NAME="$1"
    shift  # Remove first argument, remaining are ports
    local PORTS=("$@")  # All remaining arguments are ports
    local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
    
    local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting ports ===="
    local INSTANCE_END_MARKER="# ========================="
    
    local config_status=$(check_ports_config_status "$INSTANCE_NAME" "${PORTS[@]}")
    echo "Ports configuration status: $config_status"

    case "$config_status" in
        "not_found")
            echo "Adding new instance to ports section"
            local insertion_line=$(find_section_insertion_point "ports")
            
            # Build the port configuration dynamically, excluding standard web ports (80, 443)
            local port_config=""
            local non_standard_ports=()
            for port in "${PORTS[@]}"; do
                # Skip standard web ports since they may be managed by the system
                if [ "$port" != "80" ] && [ "$port" != "443" ]; then
                    port_config+="      - \"${port}:${port}/tcp\"\\n"
                    non_standard_ports+=("$port")
                fi
            done
            
            # Only add ports section if there are non-standard ports to map
            if [ -n "$port_config" ]; then
                # Insert the configuration at the correct location
                sed -i "${insertion_line}i\\
\\
      ${INSTANCE_MARKER}\\
${port_config}      ${INSTANCE_END_MARKER}\\
" "${COMPOSE_FILE}"
                echo "Ports configuration added successfully for ${#non_standard_ports[@]} non-standard port(s): ${non_standard_ports[*]}"
            else
                echo "No non-standard ports to add to ports section (ports 80 and 443 are standard)"
            fi
            ;;
        "up_to_date")
            echo "Ports configuration is already up to date"
            ;;
        "outdated"|"incomplete")
            echo "Updating existing ports configuration"
            # Find the exact lines of the existing configuration
            local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
            local end_line=$(grep -n "${INSTANCE_END_MARKER}" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
            
            # If we can't find the end marker, use a default range
            if [ -n "$start_line" ] && [ -z "$end_line" ]; then
                end_line=$((start_line + ${#PORTS[@]} + 5))
            fi
            
            # Remove only the specific configuration block (from start marker to end marker)
            if [ -n "$start_line" ] && [ -n "$end_line" ] && [ "$start_line" -gt 0 ] && [ "$end_line" -gt "$start_line" ]; then
                sed -i "${start_line},${end_line}d" "${COMPOSE_FILE}"
                
                # Build the updated port configuration dynamically, excluding standard web ports (80, 443)
                local port_config=""
                local non_standard_ports=()
                for port in "${PORTS[@]}"; do
                    # Skip standard web ports since they may be managed by the system
                    if [ "$port" != "80" ] && [ "$port" != "443" ]; then
                        port_config+="      - \"${port}:${port}/tcp\"\\n"
                        non_standard_ports+=("$port")
                    fi
                done
                
                # Only update ports section if there are non-standard ports to map
                if [ -n "$port_config" ]; then
                    # Insert the updated configuration at the same location
                    sed -i "${start_line}i\\
      ${INSTANCE_MARKER}\\
${port_config}      ${INSTANCE_END_MARKER}" "${COMPOSE_FILE}"
                    echo "Ports configuration updated successfully for ${#non_standard_ports[@]} non-standard port(s): ${non_standard_ports[*]}"
                else
                    echo "No non-standard ports to update in ports section (ports 80 and 443 are standard)"
                fi
            else
                echo "Warning: Could not determine valid line range for ports update"
            fi
            ;;
    esac
}

# Function to update labels section
update_labels_config() {
    local INSTANCE_NAME="$1"
    local WEB_PORT="$2"
    local DOMAIN_NAME="$3"
    local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
    
    local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting labels ===="
    local INSTANCE_END_MARKER="# ========================="
    
    local config_status=$(check_labels_config_status "$INSTANCE_NAME" "$WEB_PORT" "$DOMAIN_NAME")
    echo "Labels configuration status: $config_status"

    case "$config_status" in
        "not_found")
            echo "Adding new instance to labels section"
            local insertion_line=$(find_section_insertion_point "labels")
            
            # Insert the configuration at the correct location
            sed -i "${insertion_line}i\\
\\
      ${INSTANCE_MARKER}\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.entrypoints=web\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.rule=Host(\`${DOMAIN_NAME}\`)\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.service=frps-${INSTANCE_NAME}\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.middlewares=http2https@file\"\\
\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.entrypoints=websecure\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.rule=Host(\`${DOMAIN_NAME}\`)\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.service=frps-${INSTANCE_NAME}\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls=true\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.certresolver=letsencrypt\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.domains[0].main=${DOMAIN_NAME}\"\\
\\
      - \"traefik.http.services.frps-${INSTANCE_NAME}.loadbalancer.server.port=${WEB_PORT}\"\\
      ${INSTANCE_END_MARKER}\\
" "${COMPOSE_FILE}"
            echo "Labels configuration added successfully"
            ;;
        "up_to_date")
            echo "Labels configuration is already up to date"
            ;;
        "outdated"|"incomplete")
            echo "Updating existing labels configuration"
            # Find the exact lines of the existing configuration
            local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
            local end_line=$(grep -n "${INSTANCE_END_MARKER}" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
            
            # If we can't find the end marker, use a default range
            if [ -z "$end_line" ]; then
                end_line=$((start_line + 15))
            fi
            
            # Remove only the specific configuration block (from start marker to end marker)
            if [ -n "$start_line" ] && [ -n "$end_line" ] && [ "$start_line" -gt 0 ] && [ "$end_line" -gt "$start_line" ]; then
                sed -i "${start_line},${end_line}d" "${COMPOSE_FILE}"
                
                # Insert the updated configuration at the same location
                sed -i "${start_line}i\\
      ${INSTANCE_MARKER}\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.entrypoints=web\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.rule=Host(\`${DOMAIN_NAME}\`)\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.service=frps-${INSTANCE_NAME}\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-http.middlewares=http2https@file\"\\
\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.entrypoints=websecure\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.rule=Host(\`${DOMAIN_NAME}\`)\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.service=frps-${INSTANCE_NAME}\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls=true\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.certresolver=letsencrypt\"\\
      - \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.domains[0].main=${DOMAIN_NAME}\"\\
\\
      - \"traefik.http.services.frps-${INSTANCE_NAME}.loadbalancer.server.port=${WEB_PORT}\"\\
      ${INSTANCE_END_MARKER}" "${COMPOSE_FILE}"
                echo "Labels configuration updated successfully"
            else
                echo "Warning: Could not determine valid line range for labels update"
            fi
            ;;
    esac
}

# Function to restart Docker Compose service
restart_docker_compose() {
    local COMPOSE_DIR="/data/ssd/rvs-prxy/frps/"
    echo "Restarting Docker Compose service..."
    
    # Change to the compose directory
    cd "${COMPOSE_DIR}" || {
        echo "Error: Could not change to directory ${COMPOSE_DIR}"
        return 1
    }
    
    # Stop the service
    echo "Stopping the service..."
    if [ -f "./stop.sh" ]; then
        ./stop.sh
    else
        docker-compose -f docker-compose.app.yml down
    fi
    
    # Start the service
    echo "Starting the service..."
    if [ -f "./start.sh" ]; then
        ./start.sh
    else
        docker-compose -f docker-compose.app.yml up -d
    fi
    
    # Wait a moment for the service to start
    sleep 3
    
    # Show the logs
    echo "Showing the last 50 lines of logs:"
    docker logs --tail 50 frps_frps_1 2>/dev/null || docker logs --tail 50 "${COMPOSE_DIR##*/}_frps_1" 2>/dev/null || {
        echo "Warning: Could not retrieve logs for frps_frps_1. Trying alternative container names..."
        docker ps -a | grep frps
    }
    
    echo ""
    echo "Docker Compose service has been restarted."
    echo "You can view the logs with: docker logs --tail 50 frps_frps_1"
}


# Function to update the docker-compose configuration
update_compose_config() {
    local INSTANCE_NAME="$1"
    local HAS_WEB_PROXY="$2"
    shift 2  # Remove first two arguments, remaining are ports
    local PORTS=("$@")  # Get all port arguments
    local LAST_ARG="${PORTS[-1]}"  # Last argument should be domain name
    unset 'PORTS[-1]'  # Remove domain from ports array
    
    local DOMAIN_NAME="$LAST_ARG"
    local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
    
    # Use the first port as the web port for domain routing
    local WEB_PORT="${PORTS[0]}"
    
    # Create a backup of the original file with timestamp
    if [ -f "${COMPOSE_FILE}" ]; then
        local TIMESTAMP=$(date +%Y%m%d_%H%M%S)
        local BACKUP_FILE="${COMPOSE_FILE}.backup_${TIMESTAMP}"
        cp "${COMPOSE_FILE}" "${BACKUP_FILE}"
        echo "Created backup of docker-compose.app.yml as ${BACKUP_FILE}"
    else
        echo "NOTE: docker-compose.app.yml does not exist yet, will create it"
    fi

    # Update ports section based on whether we have a web proxy
    if [ "$HAS_WEB_PROXY" = "true" ]; then
        echo "Skipping port mappings for configuration with web proxy (access via domain only)"
    else
        # Update ports section with all ports (don't pass domain name to ports config)
        update_ports_config "$INSTANCE_NAME" "${PORTS[@]}"
    fi
    
    # Update labels section - only needs the web port for routing
    update_labels_config "$INSTANCE_NAME" "$WEB_PORT" "$DOMAIN_NAME"
    
    # Ask user if they want to restart the service
    echo ""
    read -p "Do you want to restart the Docker Compose service now? (y/N): " -n 1 -r
    echo ""
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        restart_docker_compose
    else
        echo "You can restart the service later with:"
        echo "  cd /data/ssd/rvs-prxy/frps/ && ./stop.sh && ./start.sh"
        echo "And view logs with:"
        echo "  docker logs --tail 50 frps_frps_1"
    fi
}

# Execute the configuration update with passed parameters
# Pass all ports and domain name as arguments to update_compose_config
update_compose_config "$@"

EOF
    
    # Make the script executable
    chmod +x "$temp_script"
    
    # Copy script to proxy server and execute it with all ports and domain
    log_info "Connecting to proxy server at $proxy_server_addr"
    if scp -i ~/.ssh/itl-devs1 "$temp_script" ${SERVER_USER}@$proxy_server_addr:/tmp/; then
        # Construct command with instance name, web proxy flag, all ports and domain name
        local cmd_args=("$THIS_INSTANCE" "$has_web")
        for port in "${all_ports[@]}"; do
            cmd_args+=("$port")
        done
        cmd_args+=("$DOMAIN_NAME")  # Add domain name as last argument
        
        if ssh -i ~/.ssh/itl-devs1 ${SERVER_USER}@$proxy_server_addr "bash /tmp/$(basename $temp_script) ${cmd_args[*]}"; then
            log_info "Proxy server configuration updated successfully"
            # Clean up remote script
            ssh -i ~/.ssh/itl-devs1 ${SERVER_USER}@$proxy_server_addr "rm /tmp/$(basename $temp_script)" >/dev/null 2>&1 || true
        else
            log_error "Failed to execute script on proxy server"
            # Clean up remote script
            ssh -i ~/.ssh/itl-devs1 ${SERVER_USER}@$proxy_server_addr "rm /tmp/$(basename $temp_script)" >/dev/null 2>&1 || true
            rm -f "$temp_script"
            return 1
        fi
    else
        log_error "Failed to copy script to proxy server"
        rm -f "$temp_script"
        return 1
    fi
    
    # Clean up local script
    rm -f "$temp_script"
    
    echo "" >&2
}

# Function to setup proxy in Traefik mode (runs locally on the same server)
setup_traefik_only() {
    log_info "Setting up Traefik-only proxy for instance: ${THIS_INSTANCE}"
    
    # Pre-request checks
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure your current user has permission to execute docker command." >&2
    echo "" >&2
    
    # Auto-proceed with deployment (smooth operation)
    echo "Auto-proceeding with Traefik setup..." >&2
    echo "" >&2
    
    # Note: Application service check is NOT performed here
    # Application service is only checked during domain setup (setup_new_domain)
    # This function only sets up the Traefik reverse proxy instance
    
    # Create Traefik container locally if it doesn't exist
    create_traefik_container_local
    
    log_info "Traefik reverse proxy instance setup completed successfully!"
    log_info "Next: Run the script again to setup your first domain."
    
    # Show helpful commands to the user using unified function
    show_traefik_commands
}

# Function to setup reverse proxy (main entry point for proxy setup mode)
setup_reverse_proxy() {
    log_info "Setting up reverse proxy..."
    
    # Proceed with setup based on mode
    if [ "$FRP" = false ]; then
        log_info "Setting up Traefik reverse proxy..."
        setup_traefik_only
    else
        log_info "Setting up FRP tunnel with Traefik..."
        setup_frp_with_traefik
    fi
}

# Function to setup port exposure - expose a host port to connect to a container port
setup_port_exposure() {
    log_info "Setting up port exposure: Host port ${HOST_PORT} -> Container ${CONTAINER_NAME}:${CONTAINER_PORT}"
    log_info "Port configuration loaded from: ${PORT_ENV_FILE}"

    # Check if Traefik is running
    if ! docker ps --filter name=^${INSTANCE_NAME}$ --format '{{.Names}}' | grep -q "^traefik$"; then
        log_error "Traefik reverse proxy is not running!"
        log_error "Please ensure the reverse proxy is setup first"
        exit 1
    fi

    # Check if target container is running
    local container_running
    container_running=$(docker ps --filter name="$CONTAINER_NAME" --format '{{.Names}}' 2>/dev/null || true)

    if [ -z "$container_running" ]; then
        log_error "Target container '$CONTAINER_NAME' is not running."
        log_error "Please make sure the container is started."
        exit 1
    fi

    # Configure port routing in Traefik
    configure_port_routing_local
    log_info "Port exposure ${HOST_PORT} has been configured in Traefik"
    log_info "Service will be accessible at: http://<host-ip>:${HOST_PORT}"
}

# Function to configure port routing in Traefik for port-based access
configure_port_routing_local() {
    log_info "Configuring Traefik port routing for container ${CONTAINER_NAME}..."

    # Check if the target container is running
    local container_running
    container_running=$(docker ps --filter name="$CONTAINER_NAME" --format '{{.Names}}' 2>/dev/null || true)

    if [ -z "$container_running" ]; then
        log_error "Target container '$CONTAINER_NAME' is not running."
        log_error "Please make sure the container is started."
        exit 1
    fi

    # Get the container's IP address on the ${DOCKER_NETWORK} network
    local container_ip
    container_ip=$(docker inspect "$CONTAINER_NAME" 2>/dev/null | grep -A 20 '"${DOCKER_NETWORK}"' | grep '"IPAddress"' | grep -o '[0-9.]*' | head -1)

    if [ -z "$container_ip" ]; then
        # Try to get any IP address from the container
        container_ip=$(docker inspect "$CONTAINER_NAME" 2>/dev/null | grep '"IPAddress"' | grep -o '[0-9.]*' | head -1)
    fi

    if [ -z "$container_ip" ]; then
        log_warn "Could not determine container IP address, using container name for routing"
        container_ip="$CONTAINER_NAME"
    else
        log_info "Container IP address: ${container_ip}"
    fi

    # Create dynamic configuration file for port-based routing
    local config_filename="port-${HOST_PORT}-${CONTAINER_NAME}.yml"

    # Build the YAML config content using printf to avoid escaping issues
    local yaml_content
    yaml_content=$(printf 'http:\n  routers:\n    port-%s-router:\n      rule: '"'"'PathPrefix(`/`)'"'"'\n      service: port-%s-service\n      entryPoints:\n        - port%s\n  services:\n    port-%s-service:\n      loadBalancer:\n        servers:\n          - url: '"'"'http://%s:%s'"'"'\n        passHostHeader: true' \
        "$HOST_PORT" "$HOST_PORT" "$HOST_PORT" "$HOST_PORT" "$container_ip" "$CONTAINER_PORT")

    # Base64 encode to avoid escaping issues
    local encoded_config
    encoded_config=$(echo "$yaml_content" | base64 -w 0)

    # Create the configuration directly in the Traefik volume using docker
    docker run --rm -v "traefik_data:/etc/traefik" alpine:latest sh -c "mkdir -p /etc/traefik/config && echo '${encoded_config}' | base64 -d > /etc/traefik/config/${config_filename}"

    # Add the port entry point and port mapping to Traefik
    add_traefik_entry_point "$HOST_PORT"

    log_info "Port routing configuration created for host port ${HOST_PORT}"
    log_info "Traefik has been updated with new entry point and port mapping"
}

# Function to add entry point to Traefik configuration
add_traefik_entry_point() {
    local port="$1"
    local entry_point_name="port${port}"

    log_info "Adding entry point '${entry_point_name}' for port ${port} to Traefik..."

    # Entry points in Traefik are defined via command-line arguments
    # We need to recreate the container with the new entry point

    # Get current Traefik configuration from running container
    local current_image
    current_image=$(docker inspect traefik --format='{{.Config.Image}}' 2>/dev/null || echo "traefik:latest")
    local current_cmd
    current_cmd=$(docker inspect traefik --format='{{json .Config.Cmd}}' 2>/dev/null || echo "[]")

    # Check if entry point already exists in command
    if echo "$current_cmd" | grep -q "entrypoints.${entry_point_name}.address"; then
        log_info "Entry point '${entry_point_name}' already exists in Traefik command"
        log_info "Entry point exists, port mapping should already be active"
        return 0
    fi

    # Extract Let's Encrypt email from existing config
    local letsencrypt_email
    letsencrypt_email=$(echo "$current_cmd" | grep -oP 'acme\.email=\K[^",]+' 2>/dev/null || echo "")
    if [ -z "$letsencrypt_email" ]; then
        letsencrypt_email=$(echo "$current_cmd" | grep -o 'acme\.email=[^",]*' | sed 's/acme\.email=//' | head -1)
    fi
    if [ -z "$letsencrypt_email" ]; then
        letsencrypt_email="admin@example.com"
    fi

    log_info "Updating Traefik container with new entry point '${entry_point_name}'..."
    log_info "Using Let's Encrypt email: ${letsencrypt_email}"

    # Parse extra entrypoints from config to include them in the recreation
    parse_extra_entrypoints

    # Build extra entrypoint args and port mappings
    local extra_ep_args=""
    local extra_port_maps=""
    for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
        extra_ep_args="${extra_ep_args} ${ep_arg}"
    done
    for pm in "${EXTRA_PORT_MAPPINGS[@]}"; do
        extra_port_maps="${extra_port_maps} -p ${pm}"
    done

    # Stop current container
    log_info "Stopping Traefik container..."
    docker stop traefik 2>/dev/null || true
    sleep 2

    # Remove old container (keep volume)
    docker rm traefik 2>/dev/null || true

    # Recreate container with all standard entry points plus the new one and extras
    docker run -d \
        --name ${INSTANCE_NAME} \
        --restart=unless-stopped \
        --network ${DOCKER_NETWORK} \
        -p 80:80 \
        -p 443:443 \
        -p "127.0.0.1:${DASHBOARD_PORT}:8080" \
        -p "${port}:${port}" \
        ${extra_port_maps} \
        -v /var/run/docker.sock:/var/run/docker.sock:ro \
        -v traefik_data:/etc/traefik \
        "${current_image}" \
        --api.insecure=true \
        --providers.docker=true \
        --providers.docker.exposedbydefault=false \
        --entrypoints.web.address=:80 \
        --entrypoints.websecure.address=:443 \
        "--entrypoints.${entry_point_name}.address=:${port}" \
        ${extra_ep_args} \
        --certificatesresolvers.letsencrypt.acme.httpchallenge=true \
        --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
        "--certificatesresolvers.letsencrypt.acme.email=${letsencrypt_email}" \
        --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
        --providers.file.directory=/etc/traefik/config \
        --providers.file.watch=true \
        2>/dev/null

    if [ $? -eq 0 ]; then
        log_info "Traefik container recreated with entry point '${entry_point_name}' on port ${port}"
        sleep 3
        log_info "Traefik is now running with the new entry point"
    else
        log_error "Failed to recreate Traefik container"
        log_error "Please manually update Traefik configuration"
        return 1
    fi
}

# Function to setup new domain in existing reverse proxy
setup_new_domain() {
    log_info "Setting up new domain: ${DOMAIN_NAME}"
    log_info "Domain configuration loaded from: ${DOMAIN_ENV_FILE:-.env}"

    if [ "$FRP" = false ]; then
        # Check if Traefik is running
        if ! docker ps --filter name=^${INSTANCE_NAME}$ --format '{{.Names}}' | grep -q "^traefik$"; then
            log_error "Traefik reverse proxy is not running!"
            log_error "Please ensure the reverse proxy is setup first"
            exit 1
        fi

        # Check if extra entrypoints need to be applied to the Traefik container
        if [ -n "$EXTRA_ENTRYPOINTS" ]; then
            parse_extra_entrypoints
            if [ ${#EXTRA_ENTRYPOINT_ARGS[@]} -gt 0 ]; then
                local current_cmd
                current_cmd=$(docker inspect traefik --format='{{json .Config.Cmd}}' 2>/dev/null || echo "[]")
                local needs_recreate=false
                for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
                    if ! echo "$current_cmd" | grep -qF -- "${ep_arg}"; then
                        needs_recreate=true
                        break
                    fi
                done
                if [ "$needs_recreate" = true ]; then
                    log_info "Extra entrypoints changed, recreating Traefik container to apply..."
                    docker stop traefik 2>/dev/null || true
                    sleep 2
                    docker rm traefik 2>/dev/null || true
                    create_traefik_container_local
                fi
            fi
        fi

        # Check if Application Service exists locally
        if ! check_app_service_local; then
            log_error "Application Service not found locally. Please ensure the service '$LOCAL_SERVICE_NAME' is running."
            exit 1
        fi

        # Configure the new domain in Traefik
        configure_traefik_local
        log_info "New domain ${DOMAIN_NAME} has been configured in Traefik"
        log_info "Service will be accessible at: https://${DOMAIN_NAME}"
    else
        # FRP Mode domain setup
        log_info "Setting up new domain in FRP mode..."

        # Ensure INSTANCE_NAME is set (from .env or detected from running container)
        if [ -z "$INSTANCE_NAME" ]; then
            # Try to detect from running FRP containers
            local frpc_containers=$(docker ps --filter name="-frpc$" --format '{{.Names}}' 2>/dev/null || true)
            if [ -n "$frpc_containers" ]; then
                local first_frpc=$(echo "$frpc_containers" | head -1)
                INSTANCE_NAME="${first_frpc%-frpc}"
                log_info "Detected instance name from running container: ${INSTANCE_NAME}"
            else
                log_error "INSTANCE_NAME is not set and no FRP container detected."
                log_error "Please ensure your .env.rvs-prxy file contains the reverse proxy configuration."
                exit 1
            fi
        fi
        THIS_INSTANCE="$INSTANCE_NAME"
        log_info "Using instance name: ${THIS_INSTANCE}"

        # Check if FRP client container is running
        local frpc_container="${THIS_INSTANCE}-frpc"
        if ! docker ps --filter name=^${frpc_container}$ --format '{{.Names}}' | grep -q "^${frpc_container}$"; then
            log_error "FRP client container '${frpc_container}' is not running!"
            log_error "Please ensure the reverse proxy is setup first"
            exit 1
        fi

        # Check if Application Service exists locally
        if ! check_app_service_local; then
            log_error "Application Service not found locally. Please ensure the service '$LOCAL_SERVICE_NAME' is running."
            exit 1
        fi

        # Generate a new remote port for this domain
        local new_remote_port
        new_remote_port=$(generate_remote_port)
        log_info "Generated remote port ${new_remote_port} for domain ${DOMAIN_NAME}"

        # Update frpc.toml with the new proxy configuration
        update_frpc_config_for_domain "$new_remote_port"

        # Update proxy server configuration
        local server_addr
        server_addr=$(extract_frp_server_address)

        if [ -z "$server_addr" ]; then
            log_error "Failed to extract FRP server address from configuration"
            exit 1
        fi

        log_info "Updating proxy server configuration at ${server_addr}..."
        update_proxy_server_config "$server_addr" "true" "$new_remote_port"

        log_info "New domain ${DOMAIN_NAME} has been configured in FRP tunnel"
        log_info "Service will be accessible at: https://${DOMAIN_NAME}"
    fi
}

# Function to setup FRP with Traefik
setup_frp_with_traefik() {
    log_info "Setting up FRP client for instance: ${THIS_INSTANCE}"
    
    # Pre-request checks
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure your current user has permission to execute docker command." >&2
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure you have key pairs ~/.ssh/authorized_keys and ~/.ssh/devs1" >&2
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure you have key pair ~/.ssh/itl-devs1 for proxy server access" >&2
    echo "" >&2
    
    # Auto-proceed with deployment (smooth operation)
    echo "Auto-proceeding with deployment..." >&2
    echo "" >&2
    
    # Use Docker network from environment variable if available, otherwise use default
    if [ -n "$DOCKER_NETWORK" ]; then
        SELECTED_NETWORK="$DOCKER_NETWORK"
        log_info "Using Docker network from environment variable: ${SELECTED_NETWORK}"
    else
        SELECTED_NETWORK=""
        log_info "Using default Docker network"
    fi
    
    # Save the network selection
    local network_to_save="$SELECTED_NETWORK"
    if [ -z "$network_to_save" ]; then
        network_to_save="none"
    fi
    save_parameters "$network_to_save"
    
    if [ -n "$SELECTED_NETWORK" ]; then
        log_info "Selected Docker network: ${SELECTED_NETWORK}"
    else
        log_info "Using default Docker network"
    fi
    
    # Generate FRP client configuration
    generate_frpc_config
    
    # Start Docker container
    start_docker_container
    
    # Extract FRP server address and remote ports
    local server_addr
    server_addr=$(extract_frp_server_address)
    
    if [ -z "$server_addr" ]; then
        log_error "Failed to extract FRP server address from configuration"
        log_info "Skipping proxy server configuration update"
    else
        log_info "Extracted FRP server address: $server_addr"
        
        # Extract all remote ports dynamically from configuration
        local all_ports=()
        local ports_string=$(extract_all_remote_ports)
        if [ -n "$ports_string" ]; then
            all_ports=($ports_string)
        fi
        
        # Check if we have at least one port to configure
        if [ ${#all_ports[@]} -ge 1 ]; then
            log_info "Extracted ${#all_ports[@]} remote port(s) for proxy server update: ${all_ports[*]}"
            
            # Check if we have any web proxies
            local has_web=$(has_web_proxy "${proxy_configs[@]}")
            
            # Update proxy server configuration with all extracted ports
            update_proxy_server_config "$server_addr" "$has_web" "${all_ports[@]}"
        else
            log_error "Failed to extract any remote ports from configuration"
            log_info "Skipping proxy server configuration update"
        fi
    fi
    
    # Display container operation commands
    display_container_commands
    
    log_info "FRP client instance ${THIS_INSTANCE} deployment completed"
}

# Setup rvs-prxy function (main function that decides FRP vs Traefik)
setup_rvs_prxy() {
    if [ "$FRP" = false ]; then
        # Run Traefik-only setup
        log_info "Running Traefik-only setup as FRP=false"
        setup_traefik_only
        return 0
    fi
    
    log_info "Setting up FRP client for instance: ${THIS_INSTANCE}"
    
    # Pre-request checks
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure your current user has permission to execute docker command." >&2
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure you have key pairs ~/.ssh/authorized_keys and ~/.ssh/devs1" >&2
    echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure you have key pair ~/.ssh/itl-devs1 for proxy server access" >&2
    echo "" >&2
    
    # Auto-proceed with deployment (smooth operation)
    echo "Auto-proceeding with deployment..." >&2
    echo "" >&2
    
    # Use Docker network from environment variable if available, otherwise use default
    if [ -n "$DOCKER_NETWORK" ]; then
        SELECTED_NETWORK="$DOCKER_NETWORK"
        log_info "Using Docker network from environment variable: ${SELECTED_NETWORK}"
    else
        SELECTED_NETWORK=""
        log_info "Using default Docker network"
    fi
    
    # Save the network selection
    local network_to_save="$SELECTED_NETWORK"
    if [ -z "$network_to_save" ]; then
        network_to_save="none"
    fi
    save_parameters "$network_to_save"
    
    if [ -n "$SELECTED_NETWORK" ]; then
        log_info "Selected Docker network: ${SELECTED_NETWORK}"
    else
        log_info "Using default Docker network"
    fi
    
    # Generate FRP client configuration
    generate_frpc_config
    
    # Start Docker container
    start_docker_container
    
    # Extract FRP server address and remote ports
    local server_addr
    server_addr=$(extract_frp_server_address)
    
    if [ -z "$server_addr" ]; then
        log_error "Failed to extract FRP server address from configuration"
        log_info "Skipping proxy server configuration update"
    else
        log_info "Extracted FRP server address: $server_addr"
        
        # Extract all remote ports dynamically from configuration
        local all_ports=()
        local ports_string=$(extract_all_remote_ports)
        if [ -n "$ports_string" ]; then
            all_ports=($ports_string)
        fi
        
        # Check if we have at least one port to configure
        if [ ${#all_ports[@]} -ge 1 ]; then
            log_info "Extracted ${#all_ports[@]} remote port(s) for proxy server update: ${all_ports[*]}"
            
            # Check if we have any web proxies
            local has_web=$(has_web_proxy "${proxy_configs[@]}")
            
            # Update proxy server configuration with all extracted ports
            update_proxy_server_config "$server_addr" "$has_web" "${all_ports[@]}"
        else
            log_error "Failed to extract any remote ports from configuration"
            log_info "Skipping proxy server configuration update"
        fi
    fi
    
    # Display container operation commands
    display_container_commands
    
    log_info "FRP client instance ${THIS_INSTANCE} deployment completed"
}

# List available systems
list_systems() {
    echo -e "${GREEN}Available deployment systems:${NC}" >&2
    echo -e "${YELLOW}============================${NC}" >&2
    echo -e "${BLUE}•${NC} setup-rvs-prxy" >&2
    echo -e "${YELLOW}============================${NC}" >&2
    echo -e "Use: ${GREEN}$0 <system-name>${NC} to deploy a system" >&2
}

# Usage function
usage() {
    cat << EOF
Usage: curl -s "<url>/deploy-rvs-prxy.sh" | bash

Automated deployment script for Reverse Proxy setup.

Modes:
  1) Setup Reverse Proxy    - Deploys Traefik or FRP Tunnel with Traefik
  2) Setup New Domain       - Adds a new domain to existing reverse proxy
  3) Setup Port Exposure    - Exposes a host port to connect to a container port
  4) Manage Configurations - List and remove existing domains and ports

The script automatically detects the mode based on your configuration:
  - If no reverse proxy exists: Setup Reverse Proxy mode
  - If reverse proxy exists and DOMAIN_NAME is set: Setup New Domain mode
  - If reverse proxy exists and HOST_PORT is set: Setup Port Exposure mode

Environment Variables (in .env.rvs-prxy file):
  FRP=true|false        Set to 'false' for Traefik mode, 'true' for FRP mode
  INSTANCE_NAME         Container name for Traefik (default: traefik)
  DOCKER_NETWORK        Docker network name (default: traefik-public)
  DASHBOARD_PORT        Traefik dashboard port on localhost (default: 18080)

Traefik Mode Variables (FRP=false):
  LOCAL_SERVICE_NAME    Name of the local Docker application container
  LOCAL_SERVICE_PORT    Port(s) of the local app service, e.g. 80 or 80,4443/udp
  DOMAIN_NAME           Domain name for reverse proxy routing
  TRAEFIK_VER           Traefik version (default: v3.6.9)
  LETSENCRYPT_EMAIL     Email for Let's Encrypt certificates

FRP Mode Variables (FRP=true):
  FRP_SERVER_ADDR       FRP server address (IP or hostname)
  FRP_SERVER_PORT       FRP server port (default: 16999)
  INTERNAL_SERVICE_PORT Internal service port (default: 3000)

Port Exposure Variables (in .env-port-<port> file):
  HOST_PORT             Port number to expose on the host (e.g., 18081)
  CONTAINER_NAME        Name of the target Docker container
  CONTAINER_PORT        Port number inside the container to connect to

Examples:
  # First run - will trigger interactive wizard
  curl -s "<url>/deploy-rvs-prxy.sh" | bash

  # With existing .env.rvs-prxy file
  curl -s "<url>/deploy-rvs-prxy.sh" | bash

  # Setup port exposure - create .env-port-18081 file and run
  curl -s "<url>/deploy-rvs-prxy.sh" | bash

  # Manage existing domains and ports (interactive menu)
  curl -s "<url>/deploy-rvs-prxy.sh" | bash -s -- --manage

Note: This script runs on the same computer as the service it connects to.
IMPORTANT: Configuration is primarily handled through .env.rvs-prxy file.
The .env.rvs-prxy file contains all parameters for your setup and is essential for operation.

Port Exposure:
  Create a file named .env-port-<port> (e.g., .env-port-18081) with:
    HOST_PORT="18081"
    CONTAINER_NAME="your-container-name"
    CONTAINER_PORT="80"
  Then run the script to expose host port 18081 -> container port 80
EOF
}

# Function to manage existing domain and port configurations
manage_configurations() {
    echo -e "${BLUE}=============================================${NC}" >&2
    echo -e "${GREEN}Reverse Proxy Configuration Manager${NC}" >&2
    echo -e "${BLUE}=============================================${NC}" >&2
    echo "" >&2

    # Collect domain files
    local domain_files=()
    for f in .env-*; do
        [ -f "$f" ] || continue
        [[ "$f" == .env.rvs-prxy* ]] && continue
        [[ "$f" == .env-port-* ]] && continue
        domain_files+=("$f")
    done

    # Collect port files
    local port_files=()
    for f in .env-port-*; do
        [ -f "$f" ] && port_files+=("$f")
    done

    local total_configs=$((${#domain_files[@]} + ${#port_files[@]}))

    if [ "$total_configs" -eq 0 ]; then
        log_info "No domain or port configurations found."
        return 0
    fi

    # Display configurations
    echo -e "${YELLOW}Current Configurations:${NC}" >&2
    echo "" >&2

    local idx=1

    if [ ${#domain_files[@]} -gt 0 ]; then
        echo -e "${BLUE}Domains:${NC}" >&2
        for f in "${domain_files[@]}"; do
            local domain_name
            domain_name=$(grep -o 'DOMAIN_NAME="[^"]*"' "$f" 2>/dev/null | sed 's/DOMAIN_NAME="//;s/"//' || echo "unknown")
            local service_name
            service_name=$(grep -o 'LOCAL_SERVICE_NAME="[^"]*"' "$f" 2>/dev/null | sed 's/LOCAL_SERVICE_NAME="//;s/"//' || echo "unknown")
            echo -e "  ${GREEN}${idx}.${NC} ${f} - ${domain_name} (${service_name})" >&2
            idx=$((idx + 1))
        done
        echo "" >&2
    fi

    if [ ${#port_files[@]} -gt 0 ]; then
        echo -e "${BLUE}Ports:${NC}" >&2
        for f in "${port_files[@]}"; do
            local host_port
            host_port=$(grep -o 'HOST_PORT="[^"]*"' "$f" 2>/dev/null | sed 's/HOST_PORT="//;s/"//' || echo "unknown")
            local container_name
            container_name=$(grep -o 'CONTAINER_NAME="[^"]*"' "$f" 2>/dev/null | sed 's/CONTAINER_NAME="//;s/"//' || echo "unknown")
            echo -e "  ${GREEN}${idx}.${NC} ${f} - Host port ${host_port} -> ${container_name}" >&2
            idx=$((idx + 1))
        done
        echo "" >&2
    fi

    # Prompt for removal
    echo -e "${YELLOW}Select configurations to remove (enter numbers separated by space, or 'q' to quit):${NC}" >&2
    read -r selection </dev/tty

    if [ "$selection" = "q" ] || [ -z "$selection" ]; then
        log_info "No changes made."
        return 0
    fi

    # Build array of all config files
    local all_files=("${domain_files[@]}" "${port_files[@]}")

    # Track if Traefik needs restart (for port removals)
    local traefik_restart_needed=false
    local ports_to_remove=()

    # Process selection
    for num in $selection; do
        if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le "$total_configs" ]; then
            local file_to_remove="${all_files[$((num-1))]}"
            if [ -f "$file_to_remove" ]; then
                log_info "Removing: ${file_to_remove}"

                # Check if it's a domain config
                if [[ "$file_to_remove" == .env-* ]] && [[ "$file_to_remove" != .env-port-* ]]; then
                    # Remove domain config from Traefik volume
                    local domain_name
                    domain_name=$(grep -o 'DOMAIN_NAME="[^"]*"' "$file_to_remove" 2>/dev/null | sed 's/DOMAIN_NAME="//;s/"//' || echo "")
                    local service_name
                    service_name=$(grep -o 'LOCAL_SERVICE_NAME="[^"]*"' "$file_to_remove" 2>/dev/null | sed 's/LOCAL_SERVICE_NAME="//;s/"//' || echo "")

                    if [ -n "$domain_name" ] && [ -n "$service_name" ]; then
                        log_info "Removing domain config from Traefik: ${domain_name}"
                        # Remove the dynamic config file from Traefik volume
                        # Must use same domain_base logic as configure_traefik_local():
                        # cut -d'/' -f1 (not dots-to-dashes) to match actual filename
                        local domain_base
                        domain_base=$(echo "$domain_name" | cut -d'/' -f1)
                        docker run --rm -v "traefik_data:/etc/traefik" alpine:latest sh -c "rm -f /etc/traefik/config/app-service-${domain_base}-${service_name}.yml 2>/dev/null" || true
                    fi

                elif [[ "$file_to_remove" == .env-port-* ]]; then
                    # Remove port config from Traefik
                    local host_port
                    host_port=$(grep -o 'HOST_PORT="[^"]*"' "$file_to_remove" 2>/dev/null | sed 's/HOST_PORT="//;s/"//' || echo "")
                    local container_name
                    container_name=$(grep -o 'CONTAINER_NAME="[^"]*"' "$file_to_remove" 2>/dev/null | sed 's/CONTAINER_NAME="//;s/"//' || echo "")

                    if [ -n "$host_port" ]; then
                        log_info "Removing port config from Traefik: port ${host_port}"
                        # Remove the dynamic config file from Traefik volume
                        # Use exact filename: port-{host_port}-{container_name}.yml
                        docker run --rm -v "traefik_data:/etc/traefik" alpine:latest sh -c "rm -f /etc/traefik/config/port-${host_port}-${container_name}.yml 2>/dev/null" || true

                        # Mark for entry point removal
                        ports_to_remove+=("$host_port")
                        traefik_restart_needed=true
                    fi

                fi

                # Remove the config file
                rm -f "$file_to_remove"
                log_info "Removed: ${file_to_remove}"
            fi
        else
            log_warn "Invalid selection: ${num}"
        fi
    done

    # If ports were removed, we need to recreate Traefik container without those entry points
    if [ "$traefik_restart_needed" = true ]; then
        log_info "Updating Traefik container to remove entry points..."

        # Get current Traefik configuration
        local current_image
        current_image=$(docker inspect traefik --format='{{.Config.Image}}' 2>/dev/null || echo "traefik:latest")
        local current_cmd
        current_cmd=$(docker inspect traefik --format='{{json .Config.Cmd}}' 2>/dev/null || echo "[]")

        # Extract Let's Encrypt email from existing config
        local letsencrypt_email
        letsencrypt_email=$(echo "$current_cmd" | grep -o 'acme\.email=[^",]*' | sed 's/acme\.email=//' | head -1)
        if [ -z "$letsencrypt_email" ]; then
            letsencrypt_email="admin@example.com"
        fi

        # Extract existing entry points (excluding the ones we're removing)
        local existing_entrypoints
        existing_entrypoints=$(echo "$current_cmd" | grep -o 'entrypoints\.[a-zA-Z0-9_-]*\.address=[^"]*' | tr -d '"' || echo "")

        # Also include extra entrypoints from EXTRA_ENTRYPOINTS config
        parse_extra_entrypoints
        local extra_ep_args=""
        local extra_port_maps=""
        for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
            extra_ep_args="${extra_ep_args} ${ep_arg}"
        done
        for pm in "${EXTRA_PORT_MAPPINGS[@]}"; do
            extra_port_maps="${extra_port_maps} -p ${pm}"
        done

        # Build new entry point list excluding removed ports
        local new_entrypoint_args="--entrypoints.web.address=:80 --entrypoints.websecure.address=:443 ${extra_ep_args}"
        local new_port_mappings="-p 80:80 -p 443:443 -p 127.0.0.1:${DASHBOARD_PORT}:8080 ${extra_port_maps}"

        # Parse existing entry points (skip web/websecure and extra entries already covered)
        while IFS= read -r ep; do
            if [ -n "$ep" ]; then
                local ep_name
                ep_name=$(echo "$ep" | sed 's/entrypoints\.\(.*\)\.address=.*/\1/')
                local ep_address
                ep_address=$(echo "$ep" | sed 's/entrypoints\.[a-zA-Z0-9_-]*\.address=//')
                local ep_port
                ep_port=$(echo "$ep_address" | sed 's/^://' | sed 's/\/.*//')

                # Check if this is already covered by extra entrypoints
                local already_covered=false
                for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
                    if echo "$ep_arg" | grep -q "entrypoints.${ep_name}.address"; then
                        already_covered=true
                        break
                    fi
                done

                # Check if this port is being removed
                local should_skip=false
                for remove_port in "${ports_to_remove[@]}"; do
                    if [ "$ep_port" = "$remove_port" ]; then
                        should_skip=true
                        break
                    fi
                done

                if [ "$should_skip" = false ] && [ "$ep_name" != "web" ] && [ "$ep_name" != "websecure" ] && [ "$already_covered" = false ]; then
                    new_entrypoint_args="${new_entrypoint_args} --entrypoints.${ep_name}.address=${ep_address}"
                    # Handle protocol suffix in port mapping
                    if [[ "$ep_address" == *"/udp" ]]; then
                        new_port_mappings="${new_port_mappings} -p ${ep_port}:${ep_port}/udp"
                    else
                        new_port_mappings="${new_port_mappings} -p ${ep_port}:${ep_port}"
                    fi
                fi
            fi
        done <<< "$existing_entrypoints"

        # Stop and remove current container
        log_info "Stopping Traefik container..."
        docker stop traefik 2>/dev/null || true
        sleep 2
        docker rm traefik 2>/dev/null || true

        # Recreate container with updated entry points
        eval docker run -d \
            --name traefik \
            --restart=unless-stopped \
            --network ${DOCKER_NETWORK} \
            ${new_port_mappings} \
            -v /var/run/docker.sock:/var/run/docker.sock:ro \
            -v traefik_data:/etc/traefik \
            "\"${current_image}\"" \
            --api.insecure=true \
            --providers.docker=true \
            --providers.docker.exposedbydefault=false \
            ${new_entrypoint_args} \
            --certificatesresolvers.letsencrypt.acme.httpchallenge=true \
            --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
            --certificatesresolvers.letsencrypt.acme.email="\"${letsencrypt_email}\"" \
            --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
            --providers.file.directory=/etc/traefik/config \
            --providers.file.watch=true \
            2>/dev/null

        if [ $? -eq 0 ]; then
            log_info "Traefik container recreated without removed entry points"
            sleep 3
        else
            log_error "Failed to recreate Traefik container"
        fi
    fi

    echo "" >&2
    log_info "Configuration cleanup complete."
}

# Main execution
main() {
    # Check for help options first (before loading .env)
    if [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "help" ]; then
        usage
        exit 0
    fi

    # Check for manage option
    if [ "$1" = "--manage" ] || [ "$1" = "-m" ]; then
        manage_configurations
        exit 0
    fi

    # Load environment variables from .env.rvs-prxy files (or trigger interactive wizard)
    load_env

    # Execute based on detected deployment mode
    if [ "$DEPLOY_MODE" = "port" ]; then
        log_info "Setting up port exposure in existing Traefik proxy..."

        # Process all port config files in batch mode
        if [ ${#PORT_ENV_FILES[@]} -gt 0 ]; then
            local total=${#PORT_ENV_FILES[@]}
            local count=0

            for env_file in "${PORT_ENV_FILES[@]}"; do
                count=$((count + 1))

                echo "" >&2
                log_info "========================================="
                log_info "Processing port $count of $total: $env_file"
                log_info "========================================="
                echo "" >&2

                # Source the port config file
                source "$env_file"
                PORT_ENV_FILE="$env_file"

                # Validate port config
                if [ -z "$HOST_PORT" ]; then
                    log_error "HOST_PORT is required in $env_file"
                    continue
                fi
                if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_PORT" ]; then
                    log_error "CONTAINER_NAME and CONTAINER_PORT are required in $env_file"
                    continue
                fi

                # Setup the port exposure
                setup_port_exposure
            done

            echo "" >&2
            log_info "========================================="
            log_info "Batch processing completed: $count port(s) processed"
            log_info "========================================="
        else
            # Fallback for single port config
            setup_port_exposure
        fi
    elif [ "$DEPLOY_MODE" = "domain" ]; then
        log_info "Setting up new domain in existing reverse proxy..."
        
        # Process all domain config files in batch mode
        if [ ${#DOMAIN_ENV_FILES[@]} -gt 0 ]; then
            local total=${#DOMAIN_ENV_FILES[@]}
            local count=0
            
            for env_file in "${DOMAIN_ENV_FILES[@]}"; do
                count=$((count + 1))
                
                # Skip .env.rvs-prxy.development and .env.rvs-prxy.production
                if [ "$env_file" = ".env.rvs-prxy.development" ] || [ "$env_file" = ".env.rvs-prxy.production" ]; then
                    continue
                fi
                
                echo "" >&2
                log_info "========================================="
                log_info "Processing domain $count of $total: $env_file"
                log_info "========================================="
                echo "" >&2
                
                # Source the domain config file
                source "$env_file"
                DOMAIN_ENV_FILE="$env_file"
                
                # Validate domain config
                if [ -z "$DOMAIN_NAME" ]; then
                    log_error "DOMAIN_NAME is required in $env_file"
                    continue
                fi
                if [ -z "$LOCAL_SERVICE_NAME" ] || [ -z "$LOCAL_SERVICE_PORT" ]; then
                    log_error "LOCAL_SERVICE_NAME and LOCAL_SERVICE_PORT are required in $env_file"
                    continue
                fi
                
                # Setup the domain
                setup_new_domain
            done
            
            echo "" >&2
            log_info "========================================="
            log_info "Batch processing completed: $count domain(s) processed"
            log_info "========================================="
        else
            # Fallback for backward compatibility
            setup_new_domain
        fi
    elif [ "$DEPLOY_MODE" = "domain_wizard" ]; then
        log_info "Starting interactive wizard for domain setup..."
        interactive_setup_wizard
        # Source the newly created domain config
        if [ -n "$DOMAIN_ENV_FILE" ]; then
            source "$DOMAIN_ENV_FILE"
        fi
        setup_new_domain
    else
        log_info "Setting up reverse proxy..."
        setup_reverse_proxy
    fi
}

# Function to prompt for domain name
prompt_domain_name() {
    # Check if DOMAIN_NAME is set from environment
    if [ -n "$DOMAIN_NAME" ]; then
        log_info "Using environment variable for domain name: ${DOMAIN_NAME}"
        echo "$DOMAIN_NAME"
        return
    fi
    
    echo -e "${BLUE}=== Domain Name Configuration ===${NC}" >&2
    echo -e "${YELLOW}Please enter the domain name for this instance${NC}" >&2
    echo -e "${YELLOW}(e.g., cmty-repos2.ivantito.com)${NC}" >&2
    echo "" >&2
    
    local domain_name=""
    while [ -z "$domain_name" ]; do
        domain_name=$(read_terminal "Enter domain name: " "")
        if [ -z "$domain_name" ]; then
            log_error "Domain name cannot be empty" >&2
        fi
    done
    
    echo "$domain_name"
}

# Function to display post-execution command reference for Traefik
show_traefik_commands() {
    local domain_name="${DOMAIN_NAME:-your-domain.com}"
    
    echo ""
    echo "============================================="
    echo "Container Operation Commands (Traefik Instance)"
    echo ""
    echo "Configuration Note:"
    echo "  By default, Traefik does not restart after configuration (RESTART_TRAEFIK=false)"
    echo "  Traefik will dynamically load new configurations automatically."
    echo "  Set RESTART_TRAEFIK=true in .env to force restart after configuration."
    echo ""
    echo "Run Container locally:"
    echo "  # Use the stored docker-compose.yml from volume"
    echo "  # First try to copy with 1000:1000 ownership, fallback to default if chown fails"
    echo "  docker run -d --name ${INSTANCE_NAME} --restart=unless-stopped \\"
    echo "    -p 80:80 -p 443:443 -p 127.0.0.1:${DASHBOARD_PORT}:8080 \\"
    echo "    -v /var/run/docker.sock:/var/run/docker.sock:ro \\"
    echo "    -v traefik_data:/etc/traefik traefik:latest"
    echo ""
    echo "Restart Container: docker restart traefik"
    echo "------------------------------------------------------------"
    echo "Container Accessing by ubuntu: docker exec -it traefik /bin/sh"
    echo "Container Accessing by root: docker exec -it -u root traefik /bin/sh"
    echo "------------------------------------------------------------"
    echo "View the Logs: docker logs --tail 50 traefik"
    echo "------------------------------------------------------------"
    echo "Inspect: docker inspect traefik"
    echo "------------------------------------------------------------"
    echo "Remove Container: docker rm -f traefik"
    echo "------------------------------------------------------------"
    echo "View Traefik Dashboard: http://localhost:8080 (only accessible locally for security)"
    echo "------------------------------------------------------------"
    echo "Your application is accessible via: https://$domain_name"
    echo "------------------------------------------------------------"
    echo "Totally Remove (Pay Attention):"
    echo "  docker rm -f traefik"
    echo "  docker volume rm traefik_data"
    echo "  docker rmi traefik:$TRAEFIK_VER"
    echo "============================================="
}

# Execute main function with arguments
main "$@"