#!/bin/bash
# Envoy v1.38.0 安装与配置脚本
# 核心特性：gRPC/WS/CONNECT 长连接优先、BDP 动态窗口、STRICT_DNS 双栈解析、LEAST_REQUEST 负载均衡、主动健康检查与被动摘除
# 当前取向：优先不断流与多 IP/单 IP 双栈适配，兼顾 CLI 自动化部署与交互式运维
# 防护特性：路径规范化、TLS 版本收敛、请求头超时防护、可选普通流量反代

# ===== 全局配置 =====
DOWNLOAD_SERVER="666ATM666.x9y.club"
# 伪装反代连接地址与路径需单独指定；不与下载站复用。
CAMOUFLAGE_HOST="www.netlify.com"
# 留空时默认复用 CAMOUFLAGE_HOST；若反代站套了 CDN，可单独指定回源 Host/SNI。
CAMOUFLAGE_ORIGIN_HOST="www.netlify.com"
# 留空表示保留原始请求路径；仅在确需固定落地页时设置形如 /new
CAMOUFLAGE_PATH=""
ENVOY_BIN="/usr/sbin/envoy"
ENVOY_CONFIG="/etc/envoy/envoy.yaml"
SYSCTL_CONFIG="/etc/sysctl.d/99-envoy-optimized.conf"
TOOL_PATH="/usr/local/bin/envoy_tool"
LIMITS_DROPIN="/etc/security/limits.d/99-envoy-optimized.conf"
MODULES_LOAD_CONFIG="/etc/modules-load.d/bbr.conf"
STATE_DIR="/var/lib/envoy-tool"
THP_STATE_FILE="${STATE_DIR}/transparent_hugepage.mode"
PROFILE_MARKER_BEGIN="# >>> envoy-tool managed block >>>"
PROFILE_MARKER_END="# <<< envoy-tool managed block <<<"
PAM_MARKER_BEGIN="# >>> envoy-tool pam_limits >>>"
PAM_MARKER_END="# <<< envoy-tool pam_limits <<<"

# 默认值
DEFAULT_BW_MBPS=1000
# 以跨境高时延场景为默认档位，面向约 350ms / 1Gbps 的目标进行调优
DEFAULT_RTT_MS=350

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

# ===== 辅助函数 =====
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_err() { echo -e "${RED}[ERROR]${NC} $1" >&2; }

remove_managed_block() {
    local file_path="$1"
    local begin_marker="$2"
    local end_marker="$3"
    local tmp_file="${file_path}.tmp.$$"

    [ -f "$file_path" ] || return 0

    awk -v begin="$begin_marker" -v end="$end_marker" '
    $0 == begin { skip = 1; next }
    $0 == end { skip = 0; next }
    !skip { print }
    ' "$file_path" > "$tmp_file" && mv -f "$tmp_file" "$file_path"
}

write_managed_block() {
    local file_path="$1"
    local begin_marker="$2"
    local end_marker="$3"
    local block_content="$4"
    local tmp_file="${file_path}.tmp.$$"
    local file_dir

    file_dir=$(dirname "$file_path")
    mkdir -p "$file_dir"
    touch "$file_path"

    remove_managed_block "$file_path" "$begin_marker" "$end_marker"
    cp -f "$file_path" "$tmp_file"
    {
        cat "$tmp_file"
        printf "\n%s\n%s\n%s\n" "$begin_marker" "$block_content" "$end_marker"
    } > "$file_path"
    rm -f "$tmp_file"
}

save_thp_mode() {
    local current_mode=""

    [ -r /sys/kernel/mm/transparent_hugepage/enabled ] || return 0
    mkdir -p "$STATE_DIR"

    if [ ! -f "$THP_STATE_FILE" ]; then
        current_mode=$(grep -o '\[[^]]*\]' /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null | tr -d '[]')
        case "$current_mode" in
            always|madvise|never) printf "%s\n" "$current_mode" > "$THP_STATE_FILE" ;;
        esac
    fi
}

restore_thp_mode() {
    local saved_mode=""

    [ -r "$THP_STATE_FILE" ] || return 0
    [ -w /sys/kernel/mm/transparent_hugepage/enabled ] || return 0

    saved_mode=$(tr -d '[:space:]' < "$THP_STATE_FILE")
    case "$saved_mode" in
        always|madvise|never)
            echo "$saved_mode" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null || true
            ;;
    esac
    rm -f "$THP_STATE_FILE"
}

check_root() {
    if [ "$EUID" -ne 0 ]; then
        log_err "请使用 root 运行"
        exit 1
    fi
}

download_file() {
    local url="$1"
    local dest="$2"
    log_info "正在下载 $url -> $dest"
    if command -v curl >/dev/null 2>&1; then
        curl -fsSL "$url" -o "$dest"
    elif command -v wget >/dev/null 2>&1; then
        wget -qO "$dest" "$url"
    else
        log_err "未找到 curl 或 wget"
        return 1
    fi
    if [ ! -s "$dest" ]; then
        log_err "下载失败或文件为空: $dest"
        rm -f "$dest"
        return 1
    fi
}

normalize_optional_path() {
    local raw_path="$1"
    if [ -z "$raw_path" ] || [ "$raw_path" = "/" ]; then
        echo ""
        return
    fi

    case "$raw_path" in
        /*) echo "$raw_path" ;;
        *) echo "/$raw_path" ;;
    esac
}

get_effective_camouflage_origin_host() {
    if [ -n "$CAMOUFLAGE_ORIGIN_HOST" ]; then
        echo "$CAMOUFLAGE_ORIGIN_HOST"
    else
        echo "$CAMOUFLAGE_HOST"
    fi
}

validate_envoy_txt() {
    local file_path="$1"

    if [ ! -s "$file_path" ]; then
        log_err "envoy.txt 不存在或为空: $file_path"
        return 1
    fi

    awk '
    BEGIN { ok = 1 }
    /^[[:space:]]*($|#)/ { next }
    NF != 4 {
        printf("[ERROR] envoy.txt 第 %d 行字段数错误，期望 4 列，实际 %d 列\n", NR, NF) > "/dev/stderr"
        ok = 0
        next
    }
    $2 !~ /^[0-9]+$/ || $4 !~ /^[0-9]+$/ {
        printf("[ERROR] envoy.txt 第 %d 行端口不是数字: %s %s %s %s\n", NR, $1, $2, $3, $4) > "/dev/stderr"
        ok = 0
        next
    }
    $2 < 1 || $2 > 65535 || $4 < 1 || $4 > 65535 {
        printf("[ERROR] envoy.txt 第 %d 行端口超出范围: %s %s %s %s\n", NR, $1, $2, $3, $4) > "/dev/stderr"
        ok = 0
        next
    }
    seen_port[$2]++ {
        if (seen_port[$2] > 1) {
            printf("[ERROR] envoy.txt 第 %d 行监听端口重复: %s；当前脚本会以本地端口生成 listener/cluster 名称，端口必须唯一\n", NR, $2) > "/dev/stderr"
            ok = 0
        }
    }
    END { exit(ok ? 0 : 1) }
    ' "$file_path"
}

check_install_curl() {
    if ! command -v curl >/dev/null 2>&1; then
        log_info "安装依赖: curl"
        if command -v apt-get >/dev/null 2>&1; then
            apt-get update && apt-get install -y curl
        elif command -v yum >/dev/null 2>&1; then
            yum install -y curl
        fi
    fi
}

check_install_cron() {
    # 检查并安装依赖 (cron)
    local install_cmd=""
    local pkg_manager=""
    
    if command -v apt-get >/dev/null 2>&1; then
        pkg_manager="apt-get"
    elif command -v yum >/dev/null 2>&1; then
        pkg_manager="yum"
    fi
    
    if ! command -v crontab >/dev/null 2>&1; then
        if [ "$pkg_manager" = "apt-get" ]; then
            install_cmd="$install_cmd cron"
        elif [ "$pkg_manager" = "yum" ]; then
            install_cmd="$install_cmd crontabs"
        fi
    fi
    
    if [ -n "$install_cmd" ]; then
        log_info "安装依赖: $install_cmd"
        if [ "$pkg_manager" = "apt-get" ]; then
            apt-get update && apt-get install -y $install_cmd
        elif [ "$pkg_manager" = "yum" ]; then
            yum install -y $install_cmd
        fi
    fi

    # 无论是否刚安装，都尽量确保 cron 服务已运行。
    if command -v systemctl >/dev/null 2>&1; then
        systemctl enable cron 2>/dev/null || systemctl enable crond 2>/dev/null
        systemctl start cron 2>/dev/null || systemctl start crond 2>/dev/null
    fi
}

# 智能更新函数：仅当配置内容(忽略版本号)变更时才覆盖文件，实现精准热加载
safe_update_config() {
    local new_file="$1"
    local target_file="$2"
    local resource_name="$3"
    
    if [ ! -f "$target_file" ]; then
        mv -f "$new_file" "$target_file"
        log_info "${resource_name} 初始化完成"
        return
    fi

    # 忽略第一行 (version_info) 进行内容比对
    # 如果内容一致，则不更新文件，避免触发 Envoy 不必要的重载
    if cmp -s <(tail -n +2 "$new_file") <(tail -n +2 "$target_file"); then
        rm -f "$new_file"
        # log_info "${resource_name} 无变更，跳过重载"
    else
        mv -f "$new_file" "$target_file"
        log_info "${resource_name} 已更新，触发热加载"
    fi
}

# ===== 核心配置应用逻辑 (复用) =====
_apply_envoy_config() {
    local USER_BW="$1"
    local tunnel_num="$2"
    local config_type="$3"
    local pass_real_ip="$4"
    local USER_RTT="${5:-$DEFAULT_RTT_MS}"

    local mode="" envoy_txt_url="" crt_url="" key_url=""
    case $config_type in
        1) mode="in"
           envoy_txt_url="https://${DOWNLOAD_SERVER}/tunnel/tunnel${tunnel_num}/in/envoy.txt"
           crt_url="https://${DOWNLOAD_SERVER}/tunnel/ljfxz/server.crt"
           key_url="https://${DOWNLOAD_SERVER}/tunnel/ljfxz/server.key" ;;
        2) mode="out"
           envoy_txt_url="https://${DOWNLOAD_SERVER}/tunnel/tunnel${tunnel_num}/out/envoy.txt"
           crt_url="https://${DOWNLOAD_SERVER}/tunnel/ljfxz/server.crt"
           key_url="https://${DOWNLOAD_SERVER}/tunnel/ljfxz/server.key" ;;
        3) mode="single"
           envoy_txt_url="https://${DOWNLOAD_SERVER}/tunnel/tunnel${tunnel_num}/out/envoy.txt"
           crt_url="https://${DOWNLOAD_SERVER}/tunnel/ljfxz/server.crt"
           key_url="https://${DOWNLOAD_SERVER}/tunnel/ljfxz/server.key" ;;
        *) log_err "无效的配置类型"; return 1 ;;
    esac

    # 0. 环境自检与依赖修复
    # 确保管理脚本是最新的 (用于热更新回调)
    if [ -f "$0" ]; then
        if [ ! -f "$TOOL_PATH" ] || ! cmp -s "$0" "$TOOL_PATH"; then
            cp -f "$0" "$TOOL_PATH"
            chmod +x "$TOOL_PATH"
        fi
    fi
    # 确保 cron 服务已安装 (用于热更新定时任务)
    check_install_cron

    # 下载配置文件
    download_file "$envoy_txt_url" "/etc/envoy/envoy.txt" || return 1
    # 转换格式 (DOS to Unix)
    sed -i 's/\r$//' /etc/envoy/envoy.txt 2>/dev/null
    validate_envoy_txt "/etc/envoy/envoy.txt" || return 1
    
    # 证书下载
    mkdir -p /etc/envoy/ssl
    download_file "$crt_url" "/etc/envoy/ssl/server.crt" || return 1
    download_file "$key_url" "/etc/envoy/ssl/server.key" || return 1
    chmod 644 /etc/envoy/ssl/server.crt
    chmod 600 /etc/envoy/ssl/server.key
    
    # Session Ticket
    if [ ! -f /etc/envoy/ssl/ticket.key ]; then
        log_info "生成 TLS 会话复用密钥..."
        dd if=/dev/urandom of=/etc/envoy/ssl/ticket.key bs=80 count=1 2>/dev/null
        chmod 600 /etc/envoy/ssl/ticket.key
    fi
    
    calculate_params "$USER_BW" "$USER_RTT"
    
    # 生成配置 (File-based XDS 模式)
    generate_envoy_yaml "$mode" "/etc/envoy/envoy.txt" "$pass_real_ip"
    
    # 智能启动/重载
    if command -v systemctl >/dev/null 2>&1; then
        if systemctl is-active --quiet envoy; then
            log_info "Envoy 服务正在运行，配置已通过 File-based XDS 自动热加载。"
        else
            log_info "Envoy 服务未运行，正在启动..."
            systemctl start envoy
            sleep 2
            systemctl is-active --quiet envoy && log_info "Envoy 启动成功！"
        fi
    fi
}

get_effective_nofile_limit() {
    local nofile_limit=""
    local nr_open=""

    nr_open=$(cat /proc/sys/fs/nr_open 2>/dev/null)
    if ! [[ "$nr_open" =~ ^[0-9]+$ ]] || [ "$nr_open" -le 0 ]; then
        nr_open=1048576
    fi

    # 优先读取 systemd 中 envoy 服务的真实 LimitNOFILE，避免只看当前 shell 的 ulimit。
    if command -v systemctl >/dev/null 2>&1; then
        nofile_limit=$(systemctl show -p LimitNOFILE --value envoy 2>/dev/null | tr -d '[:space:]')
    fi

    # 若 envoy 服务尚未安装或 systemd 尚未生效，则回退到 manager 默认值。
    if ! [[ "$nofile_limit" =~ ^[0-9]+$ ]] || [ "$nofile_limit" -le 0 ]; then
        nofile_limit=$(awk -F= '/^LimitNOFILE=/ {gsub(/[[:space:]]/, "", $2); print $2; exit}' /etc/systemd/system/envoy.service 2>/dev/null)
    fi

    # 再回退到发行版默认的 unit 安装路径，兼容“直接下拉 envoy.service 使用”的模式。
    if ! [[ "$nofile_limit" =~ ^[0-9]+$ ]] || [ "$nofile_limit" -le 0 ]; then
        nofile_limit=$(awk -F= '/^LimitNOFILE=/ {gsub(/[[:space:]]/, "", $2); print $2; exit}' /usr/lib/systemd/system/envoy.service 2>/dev/null)
    fi

    # 最后才回退到当前 shell。
    if ! [[ "$nofile_limit" =~ ^[0-9]+$ ]] || [ "$nofile_limit" -le 0 ]; then
        nofile_limit=$(ulimit -n 2>/dev/null)
    fi

    # infinity 或超出内核上限时，按 nr_open 收敛。
    if ! [[ "$nofile_limit" =~ ^[0-9]+$ ]] || [ "$nofile_limit" -le 0 ] || [ "$nofile_limit" -gt "$nr_open" ]; then
        nofile_limit="$nr_open"
    fi

    echo "$nofile_limit"
}

# ===== 1. 智能 BDP 算法 (带宽延迟积) =====
calculate_params() {
    local bw_mbps="$1"
    local rtt_ms="${2:-$DEFAULT_RTT_MS}"

    local MiB=$((1024 * 1024))
    local KiB=1024
    local mem_total_bytes
    local nofile_limit

    mem_total_bytes=$(awk '/MemTotal/ {printf "%.0f", $2 * 1024}' /proc/meminfo 2>/dev/null)
    if ! [[ "$mem_total_bytes" =~ ^[0-9]+$ ]] || [ "$mem_total_bytes" -le 0 ]; then
        mem_total_bytes=$((2 * 1024 * MiB))
    fi

    nofile_limit=$(get_effective_nofile_limit)
    if ! [[ "$nofile_limit" =~ ^[0-9]+$ ]] || [ "$nofile_limit" -le 0 ]; then
        nofile_limit=1048576
    fi

    # 原始 BDP = 带宽(bps) * RTT(s) / 8
    local raw_bdp_bytes
    raw_bdp_bytes=$(echo "$bw_mbps $rtt_ms" | awk '{printf "%.0f", $1 * 1000000 * $2 / 1000 / 8}')
    local raw_bdp_mib=$(( (raw_bdp_bytes + MiB - 1) / MiB ))

    # 以实际内存为上限，给窗口做“够用但不过肥”的裁剪。
    local stream_cap_mem=$((mem_total_bytes / 200)) # 0.5%
    local conn_cap_mem=$((mem_total_bytes / 100))   # 1.0%
    [ "$stream_cap_mem" -lt $((16 * MiB)) ] && stream_cap_mem=$((16 * MiB))
    [ "$stream_cap_mem" -gt $((64 * MiB)) ] && stream_cap_mem=$((64 * MiB))
    [ "$conn_cap_mem" -lt $((32 * MiB)) ] && conn_cap_mem=$((32 * MiB))
    [ "$conn_cap_mem" -gt $((128 * MiB)) ] && conn_cap_mem=$((128 * MiB))

    ENV_STREAM_WINDOW="$raw_bdp_bytes"
    [ "$ENV_STREAM_WINDOW" -gt "$stream_cap_mem" ] && ENV_STREAM_WINDOW="$stream_cap_mem"
    ENV_STREAM_WINDOW=$((((ENV_STREAM_WINDOW + MiB - 1) / MiB) * MiB))

    local two_bdp=$((raw_bdp_bytes * 2))
    local conn_target="$two_bdp"
    [ "$conn_target" -gt "$conn_cap_mem" ] && conn_target="$conn_cap_mem"

    ENV_CONN_WINDOW=$((ENV_STREAM_WINDOW * 2))
    [ "$ENV_CONN_WINDOW" -lt "$conn_target" ] && ENV_CONN_WINDOW="$conn_target"
    ENV_CONN_WINDOW=$((((ENV_CONN_WINDOW + MiB - 1) / MiB) * MiB))

    # Envoy 的 per_connection_buffer_limit_bytes 需要不小于连接窗口，否则会先被软缓冲限制卡住。
    ENV_PER_CONN_BUFFER="$ENV_CONN_WINDOW"

    # 用实际内存和 fd 推导平衡档的总连接能力。
    local reserve_os=$((mem_total_bytes / 5))      # 20%
    local reserve_envoy=$((mem_total_bytes / 10))  # 10%
    [ "$reserve_os" -lt $((2 * 1024 * MiB)) ] && reserve_os=$((2 * 1024 * MiB))
    [ "$reserve_envoy" -lt $((512 * MiB)) ] && reserve_envoy=$((512 * MiB))

    # 小内存主机上不能把可用内存强行钳回 512MiB，否则会高估承载能力。
    if [ $((reserve_os + reserve_envoy)) -ge "$mem_total_bytes" ]; then
        reserve_os=$((mem_total_bytes / 5))
        reserve_envoy=$((mem_total_bytes / 10))
    fi

    local usable_mem=$((mem_total_bytes - reserve_os - reserve_envoy))
    if [ "$usable_mem" -le 0 ]; then
        usable_mem=$((mem_total_bytes / 10))
    fi
    [ "$usable_mem" -lt $((128 * MiB)) ] && usable_mem=$((128 * MiB))

    local idle_cost=$((96 * KiB))
    local hot_fixed_cost=$((1 * MiB))
    local hot_cost=$((hot_fixed_cost + ENV_PER_CONN_BUFFER + ENV_STREAM_WINDOW / 2))
    local effective_conn_cost=$((idle_cost + (hot_cost - idle_cost) / 100)) # active_ratio=1%
    [ "$effective_conn_cost" -lt "$idle_cost" ] && effective_conn_cost="$idle_cost"

    ENV_EST_CONN_LIMIT_MEM=$((usable_mem / effective_conn_cost))
    ENV_EST_CONN_LIMIT_FD=$((nofile_limit * 80 / 100))
    [ "$ENV_EST_CONN_LIMIT_FD" -lt 1024 ] && ENV_EST_CONN_LIMIT_FD=1024

    # 不主动限制 H2 并发流，尽量只受协议栈和系统资源本身约束。
    ENV_H2_MAX_CONCURRENT_STREAMS=2147483647
    ENV_EFFECTIVE_NOFILE="$nofile_limit"
    ENV_REAL_RTT_MS="$rtt_ms"
    ENV_HEAP_BUDGET_BYTES=$((reserve_envoy + usable_mem))
    [ "$ENV_HEAP_BUDGET_BYTES" -gt "$mem_total_bytes" ] && ENV_HEAP_BUDGET_BYTES="$mem_total_bytes"

    local est_single_stream_mbps
    est_single_stream_mbps=$(echo "$ENV_STREAM_WINDOW $rtt_ms" | awk '{printf "%.0f", ($1 * 8) / ($2 / 1000) / 1000000}')

    log_info "平衡优化参数 (带宽: ${bw_mbps}Mbps, RTT: ${rtt_ms}ms):"
    log_info "  - Raw BDP:         ${raw_bdp_mib} MiB"
    log_info "  - Stream Window:   $((ENV_STREAM_WINDOW / MiB)) MiB"
    log_info "  - Conn Window:     $((ENV_CONN_WINDOW / MiB)) MiB"
    log_info "  - Buffer Limit:    $((ENV_PER_CONN_BUFFER / MiB)) MiB"
    log_info "  - H2并发流上限:   ${ENV_H2_MAX_CONCURRENT_STREAMS}"
    log_info "  - 内存估算连接上限: ${ENV_EST_CONN_LIMIT_MEM}"
    log_info "  - FD估算连接上限:  ${ENV_EST_CONN_LIMIT_FD}"
    log_info "  - Effective NOFILE: ${ENV_EFFECTIVE_NOFILE}"
    log_info "  - Envoy堆预算:     $((ENV_HEAP_BUDGET_BYTES / MiB)) MiB"
    log_info "  - 单流理论上限:   ${est_single_stream_mbps} Mbps"
}

# ===== 2. 配置生成器 (Envoy v1.38.0 标准) =====

get_socket_options() {
    cat <<EOF
  socket_options:
  - level: 6 # IPPROTO_TCP
    name: 1  # TCP_NODELAY
    int_value: 1
    state: STATE_PREBIND
  - level: 1 # SOL_SOCKET
    name: 9  # SO_KEEPALIVE
    int_value: 1
    state: STATE_PREBIND
  - level: 6 # IPPROTO_TCP
    name: 4  # TCP_KEEPIDLE
    int_value: 60
    state: STATE_PREBIND
  - level: 6 # IPPROTO_TCP
    name: 5  # TCP_KEEPINTVL
    int_value: 10
    state: STATE_PREBIND
  - level: 6 # IPPROTO_TCP
    name: 6  # TCP_KEEPCNT
    int_value: 5
    state: STATE_PREBIND
  - level: 6 # IPPROTO_TCP
    name: 23 # TCP_FASTOPEN
    int_value: 4096
    state: STATE_PREBIND
  - level: 6 # IPPROTO_TCP
    name: 18 # TCP_USER_TIMEOUT
    int_value: 120000 # 120s，降低高 RTT/抖动链路误判断流
    state: STATE_PREBIND
EOF
}

get_downstream_tls() {
    # 使用 SDS (Secret Discovery Service) 配置
    # 引用名为 "server_cert" 的 Secret，而不是直接指定文件路径
    cat <<EOF
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
        common_tls_context:
          tls_certificate_sds_secret_configs:
          - name: "server_cert"
            sds_config:
              path_config_source:
                path: "/etc/envoy/sds.yaml"
          alpn_protocols: ["h2", "http/1.1"]
          tls_params:
            tls_minimum_protocol_version: TLSv1_2
            tls_maximum_protocol_version: TLSv1_3
        session_ticket_keys:
          keys:
          - filename: "/etc/envoy/ssl/ticket.key"
EOF
}

get_upstream_tls() {
    local sni="$1"
    local alpn="${2:-[\"h2\", \"http/1.1\"]}"
    cat <<EOF
  transport_socket:
    name: envoy.transport_sockets.tls
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
      sni: "${sni}"
      common_tls_context:
        # 允许上游在 HTTP/2 与 HTTP/1.1 之间协商，避免 WebSocket 与强制 H2 冲突
        alpn_protocols: ${alpn}
        tls_params:
            tls_minimum_protocol_version: TLSv1_2
            tls_maximum_protocol_version: TLSv1_3
EOF
}

get_real_ip_request_headers() {
    cat <<EOF
              request_headers_to_add:
              - header:
                  key: "X-Real-IP"
                  value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
                append_action: OVERWRITE_IF_EXISTS_OR_ADD
              - header:
                  key: "X-Forwarded-For"
                  value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
                append_action: OVERWRITE_IF_EXISTS_OR_ADD
EOF
}

get_tcp_connect_health_check() {
    cat <<EOF
  health_checks:
  - timeout: 2s
    interval: 10s
    interval_jitter: 1s
    unhealthy_threshold: 2
    healthy_threshold: 2
    no_traffic_interval: 15s
    no_traffic_healthy_interval: 60s
    unhealthy_interval: 5s
    tcp_health_check:
      receive: []
EOF
}

get_outlier_detection_config() {
    cat <<EOF
  outlier_detection:
    split_external_local_origin_errors: true
    consecutive_local_origin_failure: 3
    consecutive_gateway_failure: 3
    enforcing_consecutive_local_origin_failure: 100
    enforcing_consecutive_gateway_failure: 100
    interval: 10s
    base_ejection_time: 30s
    max_ejection_time: 300s
    max_ejection_percent: 80
EOF
}

get_camouflage_route() {
    if [ -n "$CAMOUFLAGE_HOST" ]; then
        local normalized_path
        local path_rewrite_block=""
        local upstream_host_header
        upstream_host_header=$(get_effective_camouflage_origin_host)
        normalized_path=$(normalize_optional_path "$CAMOUFLAGE_PATH")
        if [ -n "$normalized_path" ]; then
            path_rewrite_block="                path_rewrite: \"${normalized_path}\""
        fi
        cat <<EOF
            - match: { prefix: "/" }
              route:
                cluster: cluster_camouflage
                timeout: 30s
                idle_timeout: 300s
                host_rewrite_literal: "${upstream_host_header}"
${path_rewrite_block}
                retry_policy:
                  retry_on: connect-failure,reset,gateway-error
                  num_retries: 2
                  per_try_timeout: 10s
EOF
    else
        cat <<EOF
            - match: { prefix: "/" }
              direct_response:
                status: 404
                body:
                  inline_string: '<html><head><title>404</title></head><body><center><h1>404</h1></center><hr><center></center></body></html>'
EOF
    fi
}

generate_envoy_yaml() {
    local mode="$1" # in, out, single
    local listeners_file="$2"
    local pass_real_ip="${3:-y}"
    
    log_info "正在生成 Envoy 配置 (Mode: $mode, RealIP: $pass_real_ip)..."
    validate_envoy_txt "$listeners_file" || return 1

    # 动态获取当前机器的物理内存总大小 (单位: 字节)
    local SYS_MEM_BYTES=$(awk '/MemTotal/ {printf "%.0f", $2 * 1024}' /proc/meminfo 2>/dev/null)
    
    # 容错：如果获取失败或结果包含非数字字符，保底设置为 2GB (2147483648 字节)
    if ! [[ "$SYS_MEM_BYTES" =~ ^[0-9]+$ ]] || [ "$SYS_MEM_BYTES" -le 0 ]; then
        SYS_MEM_BYTES=2147483648
        log_info "未能获取有效的系统物理内存，已启用保底策略：2GB (2048 MB)"
    else
        local mem_mb=$((SYS_MEM_BYTES / 1024 / 1024))
        log_info "智能过载保护：检测到系统物理内存 ${mem_mb} MB，已动态绑定至 Envoy 熔断引擎"
    fi

    local ENVOY_HEAP_BUDGET_BYTES="${ENV_HEAP_BUDGET_BYTES:-$SYS_MEM_BYTES}"
    if ! [[ "$ENVOY_HEAP_BUDGET_BYTES" =~ ^[0-9]+$ ]] || [ "$ENVOY_HEAP_BUDGET_BYTES" -le 0 ]; then
        ENVOY_HEAP_BUDGET_BYTES="$SYS_MEM_BYTES"
    fi

    local listener_count=0
    while read -r l_ip l_port t_host t_port; do
        [[ -z "$l_ip" || "$l_ip" =~ ^# ]] && continue
        listener_count=$((listener_count + 1))
    done < "$listeners_file"
    [ "$listener_count" -lt 1 ] && listener_count=1
    # 当前脚本模型是一行监听映射生成一个 listener 和一个隧道 cluster。
    # 若显式配置了伪装站点，则额外生成一个普通网页流量用的伪装 cluster。
    local cluster_count="$listener_count"
    [ -n "$CAMOUFLAGE_HOST" ] && cluster_count=$((cluster_count + 1))

    local ENV_UPSTREAM_MAX_CONNECTIONS=4294967295
    local ENV_UPSTREAM_MAX_PENDING_REQUESTS=4294967295
    local ENV_UPSTREAM_MAX_REQUESTS=4294967295
    local ENV_UPSTREAM_MAX_RETRIES=4294967295
    local ENV_UPSTREAM_MAX_CONNECTION_POOLS=4294967295

    log_info "连接限制策略:"
    log_info "  - Downstream Global: disabled"
    log_info "  - Listener Limit:    disabled"
    log_info "  - Upstream Conn:     unlimited"
    log_info "  - Upstream Pending:  unlimited"
    log_info "  - Upstream Requests: unlimited"
    log_info "  - Listener Count:    ${listener_count}"
    log_info "  - Cluster Count:     ${cluster_count}"

    # 生成主配置文件 envoy.yaml (只需生成一次)
    local MAIN_CONFIG="/etc/envoy/envoy.yaml"
    local MAIN_CONFIG_TMP="/etc/envoy/envoy.yaml.tmp"
    cat > "$MAIN_CONFIG_TMP" <<EOF
node:
  cluster: envoy_cluster
  id: envoy_node_1

overload_manager:
  refresh_interval: 0.25s
  resource_monitors:
  - name: "envoy.resource_monitors.fixed_heap"
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.resource_monitors.fixed_heap.v3.FixedHeapConfig
      max_heap_size_bytes: ${ENVOY_HEAP_BUDGET_BYTES}
  actions:
  - name: "envoy.overload_actions.shrink_heap"
    triggers:
    - name: "envoy.resource_monitors.fixed_heap"
      threshold:
        value: 0.80
  - name: "envoy.overload_actions.disable_http_keepalive"
    triggers:
    - name: "envoy.resource_monitors.fixed_heap"
      threshold:
        value: 0.85
dynamic_resources:
  lds_config:
    resource_api_version: V3
    initial_fetch_timeout: 0s
    path_config_source:
      path: "/etc/envoy/lds.yaml"
  cds_config:
    resource_api_version: V3
    initial_fetch_timeout: 0s
    path_config_source:
      path: "/etc/envoy/cds.yaml"
EOF
    mv -f "$MAIN_CONFIG_TMP" "$MAIN_CONFIG"

    # 生成 SDS 配置文件 (sds.yaml) - 原子写入防半读取
    local SDS_CONFIG="/etc/envoy/sds.yaml"
    local SDS_CONFIG_TMP="/etc/envoy/sds.yaml.tmp"
    local sds_timestamp=$(date +%s)
    # 确保证书文件存在，避免 sed 报错
    touch /etc/envoy/ssl/server.crt /etc/envoy/ssl/server.key
    local cert_content=$(sed 's/^/        /' /etc/envoy/ssl/server.crt)
    local key_content=$(sed 's/^/        /' /etc/envoy/ssl/server.key)

    cat > "$SDS_CONFIG_TMP" <<EOF
version_info: "${sds_timestamp}"
resources:
- "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
  name: "server_cert"
  tls_certificate:
    certificate_chain:
      inline_string: |
${cert_content}
    private_key:
      inline_string: |
${key_content}
EOF
    chmod 600 "$SDS_CONFIG_TMP"
    mv -f "$SDS_CONFIG_TMP" "$SDS_CONFIG"

    # 生成 LDS 配置文件 (lds.yaml)
    local LDS_CONFIG="/etc/envoy/lds.yaml"
    local LDS_TMP="/etc/envoy/lds.yaml.tmp"
    local lds_timestamp=$(date +%s)
    
    cat > "$LDS_TMP" <<EOF
version_info: "${lds_timestamp}"
resources:
EOF

    while read -r l_ip l_port t_host t_port; do
        [[ -z "$l_ip" || "$l_ip" =~ ^# ]] && continue
        
        # 监听地址逻辑
        local current_listen_ip="${l_ip}"
        local ipv4_compat=""
        
        # 特殊标记 "ljfxz" 表示双栈监听 (v4+v6)
        # 其他情况保持原样，由 envoy.txt 决定监听 IPv4 或 IPv6
        if [ "$current_listen_ip" == "ljfxz" ]; then
            current_listen_ip="::"
            ipv4_compat="ipv4_compat: true"
        fi

        local cluster_name="cluster_${l_port}"
        local use_remote_address="false"
        local xff_hops="0"
        local skip_xff_append="false"
        local request_headers_to_add=""
        
    if [ "$mode" == "in" ]; then
        # 入口模式: 使用远端地址，不信任来路 XFF
        use_remote_address="true"
        xff_hops="0"
        skip_xff_append="false"
        
        # 添加入口特定的请求头 (传递真实 IP 给后端)
        request_headers_to_add=$(get_real_ip_request_headers)
    elif [ "$mode" == "single" ]; then
        if [ "$pass_real_ip" == "y" ]; then
            # 单层且允许透传真实 IP 时，按边缘代理语义工作。
            use_remote_address="true"
            xff_hops="0"
            skip_xff_append="false"
            request_headers_to_add=$(get_real_ip_request_headers)
        else
            # 单层且显式不透传真实 IP 时，禁止 HCM 自动追加 XFF。
            use_remote_address="false"
            xff_hops="0"
            skip_xff_append="true"
            request_headers_to_add=""
        fi
    elif [ "$mode" == "out" ]; then
        # 出口: 信任入口层追加的 XFF，不再把入口 Envoy 的地址当作客户端地址。
        # 同时禁止再追加一跳 XFF，保持两层代理间的真实客户端 IP 语义稳定。
        use_remote_address="false"
        xff_hops="1"
        skip_xff_append="true"
        request_headers_to_add=""
    fi

        cat >> "$LDS_TMP" <<EOF
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_${l_port}
  address:
    socket_address:
      address: "${current_listen_ip}"
      port_value: ${l_port}
      ${ipv4_compat}
  per_connection_buffer_limit_bytes: ${ENV_PER_CONN_BUFFER}
  enable_reuse_port: true
$(get_socket_options)

  filter_chains:
  - filters:
    - name: envoy.filters.network.http_connection_manager
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
        stat_prefix: "ingress_http_${l_port}"
        
        # 关闭主动生成 x-request-id，减少额外代理标识
        generate_request_id: false
        # 开启路径规范化保护
        normalize_path: true
        merge_slashes: true
        path_with_escaped_slashes_action: REJECT_REQUEST

        codec_type: AUTO
        stream_error_on_invalid_http_message: false
        http_protocol_options:
          enable_trailers: true
          accept_http_10: false
        server_header_transformation: PASS_THROUGH
        
        # HTTP 错误页伪装
        local_reply_config:
          body_format:
            text_format_source:
              inline_string: '<html><head><title>%RESPONSE_CODE%</title></head><body><center><h1>%RESPONSE_CODE%</h1></center><hr><center></center></body></html>'
            content_type: "text/html"

        use_remote_address: ${use_remote_address}
        xff_num_trusted_hops: ${xff_hops}
        skip_xff_append: ${skip_xff_append}
          
        # 防主动探测核心：必须限制请求头超时！
        # 真实的 Web 服务器绝不会允许客户端建立连接后永远不发送完整的 HTTP 头 (Slowloris 攻击)。
        # XHTTP/gRPC/WS 在发送握手头时只需几十毫秒。这里设置为 60s 足够宽容。
        request_headers_timeout: 60s
          
        stream_idle_timeout: 0s
        request_timeout: 0s
          
        common_http_protocol_options:
          idle_timeout: 3600s
          max_headers_count: 200
          headers_with_underscores_action: REJECT_REQUEST

        http2_protocol_options:
          initial_stream_window_size: ${ENV_STREAM_WINDOW}
          initial_connection_window_size: ${ENV_CONN_WINDOW}
          max_concurrent_streams: ${ENV_H2_MAX_CONCURRENT_STREAMS}
          # 启用 CONNECT 隧道支持，适配隧道与长连接场景
          allow_connect: true
          connection_keepalive:
            interval: 30s
            timeout: 10s
            connection_idle_interval: 30s
            
        route_config:
          name: local_route
          virtual_hosts:
          - name: local_service
            domains: ["*"]
            routes:
            - match:
                connect_matcher: {}
              route:
                cluster: ${cluster_name}
                timeout: 0s
                max_stream_duration:
                  max_stream_duration: 0s
                  grpc_timeout_header_max: 0s
                upgrade_configs:
                - upgrade_type: CONNECT
                  enabled: true
            - match: { prefix: "/ljfxz" }
${request_headers_to_add}
              route:
                cluster: ${cluster_name}
                timeout: 0s
                max_stream_duration:
                  max_stream_duration: 0s
                  grpc_timeout_header_max: 0s
                upgrade_configs:
                - upgrade_type: websocket
                  enabled: true
$(get_camouflage_route)
                  
        http_filters:
        - name: envoy.filters.http.router
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
            suppress_envoy_headers: true
              
EOF
        # 所有模式强制 TLS 监听
        get_downstream_tls >> "$LDS_TMP"
        
    done < "$listeners_file"
    


    # 生成 CDS 配置文件 (cds.yaml)
    local CDS_CONFIG="/etc/envoy/cds.yaml"
    local CDS_TMP="/etc/envoy/cds.yaml.tmp"
    local cds_timestamp=$(date +%s)
    
    cat > "$CDS_TMP" <<EOF
version_info: "${cds_timestamp}"
resources:
EOF

    while read -r l_ip l_port t_host t_port; do
        [[ -z "$l_ip" || "$l_ip" =~ ^# ]] && continue
        local cluster_name="cluster_${l_port}"
        
        cat >> "$CDS_TMP" <<EOF
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: ${cluster_name}
  connect_timeout: 20s
  
  type: STRICT_DNS
  dns_lookup_family: ALL
  dns_refresh_rate: 5s
  dns_jitter: 1s
  dns_failure_refresh_rate:
    base_interval: 2s
    max_interval: 10s
  respect_dns_ttl: true
  wait_for_warm_on_init: true
      
  per_connection_buffer_limit_bytes: ${ENV_PER_CONN_BUFFER}
  # 多地址场景下避免新连接粘在少量节点；单地址场景会自然退化为唯一上游。
  lb_policy: LEAST_REQUEST
  least_request_lb_config:
    choice_count: 2
    slow_start_config:
      slow_start_window: 30s
      aggression:
        default_value: 1.0
      min_weight_percent:
        value: 10
    
  common_lb_config:
    close_connections_on_host_set_change: false
    # 对单目标/少目标长连接隧道而言，DNS 新地址首检前不接流量会明显放大切换抖动。
    ignore_new_hosts_until_first_hc: false
    # 保持 Envoy 默认 panic 语义，避免单目标场景因短抖动直接演变为 no healthy upstream。
    healthy_panic_threshold:
      value: 50
$(get_tcp_connect_health_check)
$(get_outlier_detection_config)
    
  upstream_connection_options:
    tcp_keepalive:
      keepalive_probes: 5
      keepalive_time: 60
      keepalive_interval: 10
    
  typed_extension_protocol_options:
    envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
      "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
      # 让上游协议跟随下游：WebSocket 走 HTTP/1.1，CONNECT/gRPC 可继续走 HTTP/2
      use_downstream_protocol_config:
        http_protocol_options: {}
        http2_protocol_options:
          initial_stream_window_size: ${ENV_STREAM_WINDOW}
          initial_connection_window_size: ${ENV_CONN_WINDOW}
          max_concurrent_streams: ${ENV_H2_MAX_CONCURRENT_STREAMS}
          allow_connect: true
          connection_keepalive:
            interval: 30s
            timeout: 10s
            connection_idle_interval: 30s
      common_http_protocol_options:
        idle_timeout: 3600s
EOF
    if [ "$mode" == "in" ]; then
         get_upstream_tls "$t_host" >> "$CDS_TMP"
    fi
    
    cat >> "$CDS_TMP" <<EOF
  circuit_breakers:
    thresholds:
    - priority: DEFAULT
      max_connections: ${ENV_UPSTREAM_MAX_CONNECTIONS}
      max_connection_pools: ${ENV_UPSTREAM_MAX_CONNECTION_POOLS}
      max_pending_requests: ${ENV_UPSTREAM_MAX_PENDING_REQUESTS}
      max_requests: ${ENV_UPSTREAM_MAX_REQUESTS}
      max_retries: ${ENV_UPSTREAM_MAX_RETRIES}
    - priority: HIGH
      max_connections: ${ENV_UPSTREAM_MAX_CONNECTIONS}
      max_connection_pools: ${ENV_UPSTREAM_MAX_CONNECTION_POOLS}
      max_pending_requests: ${ENV_UPSTREAM_MAX_PENDING_REQUESTS}
      max_requests: ${ENV_UPSTREAM_MAX_REQUESTS}
      max_retries: ${ENV_UPSTREAM_MAX_RETRIES}
  load_assignment:
    cluster_name: ${cluster_name}
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: ${t_host}
              port_value: ${t_port}
EOF
    done < "$listeners_file"

    if [ -n "$CAMOUFLAGE_HOST" ]; then
        local camouflage_origin_host
        camouflage_origin_host=$(get_effective_camouflage_origin_host)
        cat >> "$CDS_TMP" <<EOF
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: cluster_camouflage
  connect_timeout: 10s
  # 伪装站对于 CDN 或多 IP 外部服务，使用 LOGICAL_DNS 更稳定，避免对 CDN 节点进行无效的健康检查导致被封锁。
  type: LOGICAL_DNS
  # 伪装反代单独固定为 IPv4，避免 CDN 域名在坏 IPv6/半通 IPv6 环境下触发 502。
  dns_lookup_family: V4_ONLY
  dns_failure_refresh_rate:
    base_interval: 2s
    max_interval: 10s
  dns_refresh_rate: 30s
  dns_jitter: 5s
  respect_dns_ttl: true
  wait_for_warm_on_init: true
  per_connection_buffer_limit_bytes: 1048576
  lb_policy: ROUND_ROBIN
  common_lb_config:
    close_connections_on_host_set_change: false
    ignore_new_hosts_until_first_hc: false
    healthy_panic_threshold:
      value: 50
  typed_extension_protocol_options:
    envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
      "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
      explicit_http_config:
        http_protocol_options: {}
      common_http_protocol_options:
        idle_timeout: 300s
EOF
    # 伪装站强制使用 HTTP/1.1 时，必须避免 TLS ALPN 协商出 h2，否则 Envoy 会在 H2 连接上发 HTTP/1.1 请求导致 502
    get_upstream_tls "$camouflage_origin_host" "[\"http/1.1\"]" >> "$CDS_TMP"
    cat >> "$CDS_TMP" <<EOF
  circuit_breakers:
    thresholds:
    - priority: DEFAULT
      max_connections: 1024
      max_pending_requests: 1024
      max_requests: 2048
      max_retries: 3
    - priority: HIGH
      max_connections: 1024
      max_pending_requests: 1024
      max_requests: 2048
      max_retries: 3
  load_assignment:
    cluster_name: cluster_camouflage
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: ${CAMOUFLAGE_HOST}
              port_value: 443
EOF
    fi
    
    # 智能更新 CDS (仅当 Clusters 实际变更时重载)
    # 关键优化: 必须先更新 CDS，再更新 LDS。
    # 因为 LDS 引用了 Cluster，如果先更新 LDS 而 CDS 还没更新，
    # Envoy 可能会报错 "unknown cluster" 并拒绝 LDS 更新。
    safe_update_config "$CDS_TMP" "$CDS_CONFIG" "CDS(集群)"
    safe_update_config "$LDS_TMP" "$LDS_CONFIG" "LDS(监听器)"

    # 生成热更新脚本 (仅当变量存在时，避免递归调用时覆盖)
    if [ -n "$envoy_txt_url" ]; then
        cat > "/usr/local/bin/update_envoy_config.sh" <<EOF
#!/bin/bash
# Envoy 自动热更新脚本 (File-based Dynamic Configuration)

ENVOY_TXT_URL="$envoy_txt_url"
CRT_URL="$crt_url"
KEY_URL="$key_url"
PASS_REAL_IP="$pass_real_ip"
MODE="$mode"
USER_BW="$USER_BW"
USER_RTT="$USER_RTT"

download_with_fallback() {
    local url="\$1"
    local dest="\$2"

    if command -v curl >/dev/null 2>&1; then
        curl -m 10 -fsSL "\$url" -o "\$dest"
    elif command -v wget >/dev/null 2>&1; then
        wget -T 10 -qO "\$dest" "\$url"
    else
        return 1
    fi

    [ -s "\$dest" ]
}

validate_envoy_txt() {
    local file_path="\$1"

    if [ ! -s "\$file_path" ]; then
        echo "[ERROR] envoy.txt 不存在或为空: \$file_path" >&2
        return 1
    fi

    awk '
    BEGIN { ok = 1 }
    /^[[:space:]]*($|#)/ { next }
    NF != 4 {
        printf("[ERROR] envoy.txt 第 %d 行字段数错误，期望 4 列，实际 %d 列\n", NR, NF) > "/dev/stderr"
        ok = 0
        next
    }
    \$2 !~ /^[0-9]+$/ || \$4 !~ /^[0-9]+$/ {
        printf("[ERROR] envoy.txt 第 %d 行端口不是数字: %s %s %s %s\n", NR, \$1, \$2, \$3, \$4) > "/dev/stderr"
        ok = 0
        next
    }
    \$2 < 1 || \$2 > 65535 || \$4 < 1 || \$4 > 65535 {
        printf("[ERROR] envoy.txt 第 %d 行端口超出范围: %s %s %s %s\n", NR, \$1, \$2, \$3, \$4) > "/dev/stderr"
        ok = 0
        next
    }
    seen_port[\$2]++ {
        if (seen_port[\$2] > 1) {
            printf("[ERROR] envoy.txt 第 %d 行监听端口重复: %s；当前脚本会以本地端口生成 listener/cluster 名称，端口必须唯一\n", NR, \$2) > "/dev/stderr"
            ok = 0
        }
    }
    END { exit(ok ? 0 : 1) }
    ' "\$file_path"
}

# 下载新配置到临时文件
if ! download_with_fallback "\$ENVOY_TXT_URL" /tmp/envoy_new.txt; then
    exit 1
fi
sed -i 's/\r$//' /tmp/envoy_new.txt
if ! validate_envoy_txt /tmp/envoy_new.txt; then
    rm -f /tmp/envoy_new.txt
    exit 1
fi

# 标记配置是否变更
CONFIG_CHANGED=0
if ! cmp -s /tmp/envoy_new.txt /etc/envoy/envoy.txt; then
    CONFIG_CHANGED=1
fi

# 1. 始终检查证书更新 (防止证书过期但配置未变的情况)
# 下载新证书到临时文件
if ! download_with_fallback "\$CRT_URL" /tmp/server.crt.new || ! download_with_fallback "\$KEY_URL" /tmp/server.key.new; then
    # 如果证书下载失败但配置变了，依然继续更新配置，不强制退出
    :
else
    # 对比证书差异 (如果新证书与旧证书不同，则更新)
    if ! cmp -s /tmp/server.crt.new /etc/envoy/ssl/server.crt || ! cmp -s /tmp/server.key.new /etc/envoy/ssl/server.key; then
        mv /tmp/server.crt.new /etc/envoy/ssl/server.crt
        mv /tmp/server.key.new /etc/envoy/ssl/server.key
        chmod 644 /etc/envoy/ssl/server.crt
        chmod 600 /etc/envoy/ssl/server.key
        
        # 证书更新后，即使配置没变，也需要触发 Envoy 热加载
        # SDS 模式：将新证书内容写入 sds.yaml (inline_string) 以触发热更新
        # 注意：使用 inline_string 确保内容变更能被 Envoy 捕捉
        CERT_CONTENT=\$(sed 's/^/        /' /etc/envoy/ssl/server.crt)
        KEY_CONTENT=\$(sed 's/^/        /' /etc/envoy/ssl/server.key)
        
        # 1. 写入临时文件，避免写入过程中被 Envoy 读取导致配置不完整
        TIMESTAMP=\$(date +%s)
        cat > /etc/envoy/sds.yaml.tmp <<INNEREOF
version_info: "\${TIMESTAMP}"
resources:
- "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
  name: "server_cert"
  tls_certificate:
    certificate_chain:
      inline_string: |
\${CERT_CONTENT}
    private_key:
      inline_string: |
\${KEY_CONTENT}
INNEREOF
        # 2. 设置权限 (与私钥保持一致，因含敏感信息)
        chmod 600 /etc/envoy/sds.yaml.tmp
        # 3. 原子移动 (Atomic Move) 触发热加载
        mv -f /etc/envoy/sds.yaml.tmp /etc/envoy/sds.yaml
        
        # SDS 机制下不需要重载主配置，CONFIG_CHANGED=0 即可
        # 但为了日志记录或其他依赖，我们可以记录一下
        echo "证书已更新并触发 SDS 热加载"
    else
        rm -f /tmp/server.crt.new /tmp/server.key.new
    fi
fi

# 如果配置没变，则退出 (证书更新由 SDS 处理，无需重载 LDS)
if [ "\$CONFIG_CHANGED" -eq 0 ]; then
    rm -f /tmp/envoy_new.txt
    exit 0
fi

# 2. 更新 envoy.txt (如果有变)
mv -f /tmp/envoy_new.txt /etc/envoy/envoy.txt

# 3. 重新生成 LDS/CDS 配置文件 (原子替换触发热重载)
# 调用主脚本中的生成函数 (需要主脚本支持 export 或在此处内联逻辑，为简化直接调用主脚本的 generate 功能)

$TOOL_PATH generate_yaml "\$MODE" "/etc/envoy/envoy.txt" "\$PASS_REAL_IP" "\$USER_BW" "\$USER_RTT" >/dev/null 2>&1

EOF
        chmod +x "/usr/local/bin/update_envoy_config.sh"

        if command -v systemctl >/dev/null 2>&1; then
            systemctl enable cron 2>/dev/null || systemctl enable crond 2>/dev/null
            systemctl start cron 2>/dev/null || systemctl start crond 2>/dev/null
            
            # 自动添加定时任务 (每分钟执行一次)
            local cron_job="* * * * * /usr/local/bin/update_envoy_config.sh >/dev/null 2>&1"
            if ! crontab -l 2>/dev/null | grep -q "update_envoy_config.sh"; then
                (crontab -l 2>/dev/null; echo "$cron_job") | crontab -
                log_info "已添加证书自动热更新定时任务"
            fi
        fi
    fi
}

# 新增一个隐藏的 CLI 命令，用于脚本内部调用生成 YAML
# 在 main 函数中处理



# ===== 功能菜单函数 =====

envoy_install() {
    log_info "安装 Envoy..."
    
    # 安装前先停止服务，防止文件占用或冲突
    if command -v systemctl >/dev/null 2>&1; then
        systemctl stop envoy 2>/dev/null
    fi

    # 强制下载最新版 Envoy (移除条件判断)
    mkdir -p /etc/envoy/ssl
    download_file "https://${DOWNLOAD_SERVER}/tools/envoy" "$ENVOY_BIN" || return 1
    chmod +x "$ENVOY_BIN"
    
    # 远程下载 service 文件
    download_file "https://${DOWNLOAD_SERVER}/tools/envoy.service" "/usr/lib/systemd/system/envoy.service" || return 1
    if command -v systemctl >/dev/null 2>&1; then
        systemctl daemon-reload
        systemctl enable envoy
    else
        log_info "未检测到 systemctl，已跳过 Envoy service 注册，请手动接管服务启动"
    fi
    
    # 安装时自动执行系统优化
    system_optimize
    
    # 安装管理脚本
    if [ -f "$0" ]; then
        cp "$0" "$TOOL_PATH"
        chmod +x "$TOOL_PATH"
        log_info "管理脚本已安装到 $TOOL_PATH"
    fi
    
    log_info "Envoy 安装完成"
    
    # 更新或安装后自动尝试重启服务
    if [ -f "$ENVOY_CONFIG" ]; then
        if command -v systemctl >/dev/null 2>&1; then
            log_info "检测到已有配置文件，正在启动 Envoy..."
            systemctl restart envoy
            if systemctl is-active --quiet envoy; then
                echo -e "${GREEN}[INFO] Envoy 服务已成功启动并加载最新版本！${NC}"
            else
                echo -e "${RED}[ERROR] Envoy 启动失败，请检查配置或日志。${NC}"
            fi
        else
            echo -e "${YELLOW}[WARN] 已生成配置，但当前环境无 systemctl，请手动启动 Envoy。${NC}"
        fi
    else
        echo -e "${YELLOW}[WARN] Envoy 安装完成，但尚未配置。请运行配置菜单生成配置文件。${NC}"
    fi
}

envoy_manage() {
    echo "1.启动 2.停止 3.重启 4.状态"
    read -p "选择: " c
    case $c in
        1) systemctl start envoy ;;
        2) systemctl stop envoy ;;
        3) systemctl restart envoy ;;
        4) systemctl status envoy --no-pager ;;
    esac
}

firewall_remove() {
    systemctl stop firewalld 2>/dev/null
    systemctl disable firewalld 2>/dev/null
    ufw disable 2>/dev/null
    log_info "防火墙已禁用"
}

envoy_monitor() {
    check_install_cron
    cat > /usr/local/bin/checkenvoy.sh <<'EOF'
#!/bin/bash
# 仅做 Envoy 服务级保活；上游节点健康由 Envoy 自身 health_checks/outlier_detection 负责。
if command -v systemctl >/dev/null 2>&1; then
    if systemctl is-active --quiet envoy; then
        exit 0
    fi
    systemctl restart envoy
    exit $?
fi

if pgrep -x envoy >/dev/null 2>&1; then
    exit 0
fi

exit 1
EOF
    chmod +x /usr/local/bin/checkenvoy.sh
    (crontab -l 2>/dev/null | grep -v checkenvoy; echo "* * * * * /usr/local/bin/checkenvoy.sh >/dev/null 2>&1") | crontab -
    log_info "已设置 Envoy 服务级保活巡检"
}

soga_setup() {
    local node_id="$1"
    local soga_key="$2"
    local webapi_domain="$3"
    local webapi_key="$4"

    # 检查并安装 curl 依赖
    check_install_curl

    # 如果没有传参，进入交互模式
    if [ -z "$node_id" ]; then
        read -r -p "节点ID: " node_id
        read -r -p "授权码: " soga_key
        read -r -p "域名: " webapi_domain
        read -r -p "webapi_key: " webapi_key
    fi

    log_info "正在下载并安装 soga..."
    local soga_install="/tmp/soga_install.sh"
    if ! curl -Ls https://raw.githubusercontent.com/vaxilu/soga/master/install.sh -o "$soga_install"; then
        log_err "下载 soga 安装脚本失败，请检查网络"
        return 1
    fi
    
    bash "$soga_install" || { log_err "soga 安装失败"; rm -f "$soga_install"; return 1; }
    rm -f "$soga_install"

    # 确保配置目录存在
    mkdir -p /etc/soga
    
    cat > /etc/soga/soga.conf <<EOF
type=xboard
server_type=vless
node_id=${node_id}
soga_key=${soga_key}
api=webapi
webapi_url=https://${webapi_domain}
webapi_key=${webapi_key}
proxy_protocol=false
user_conn_limit=0
user_speed_limit=0
user_tcp_limit=0
node_speed_limit=0
check_interval=60
submit_interval=60
forbidden_bit_torrent=true
log_level=info
auto_update=true
force_close_ssl=true
EOF
    soga restart && log_info "soga 配置完成并重启" || log_err "soga 重启失败"
}

setup_ddns() {
    local cf_key="$1"
    local cf_user="$2"
    local cf_zone="$3"
    local cf_record="$4"
    local record_type="$5"

    log_info "正在配置 Cloudflare DDNS..."
    
    # 检查并安装 cron 依赖
    check_install_cron
    
    # 检查并安装 curl (ddns 还需要 curl)
    check_install_curl

    # 如果没有传参，进入交互模式
    if [ -z "$cf_key" ]; then
        read -p "请输入 Cloudflare API Key (Global Key): " cf_key
        read -p "请输入 Cloudflare 邮箱: " cf_user
        read -p "请输入主域名 (例如 example.com): " cf_zone
        read -p "请输入解析记录名 (例如 ddns): " cf_record
        echo "解析类型: 1)IPv4(A) 2)IPv6(AAAA)"
        read -p "选择 [1-2]: " record_type_choice
        record_type="A"
        [ "$record_type_choice" = "2" ] && record_type="AAAA"
    else
        # CLI 模式下的类型转换
        if [ "$record_type" = "1" ]; then
            record_type="A"
        elif [ "$record_type" = "2" ]; then
            record_type="AAAA"
        fi
        # 如果用户直接传了 A 或 AAAA，则保持原样
    fi

    # 下载并配置 DDNS 脚本
    local ddns_script="/usr/local/bin/cf-ddns.sh"
    download_file "https://raw.githubusercontent.com/yulewang/cloudflare-api-v4-ddns/master/cf-v4-ddns.sh" "$ddns_script" || return 1
    chmod +x "$ddns_script"

    # 修复环境变量缺失问题 (如 HOME)
    # 在脚本顶部添加 export HOME=/root 如果未定义
    if ! grep -q "export HOME=" "$ddns_script"; then
        sed -i '2i export HOME=${HOME:-/root}' "$ddns_script"
    fi

    # 创建定时任务
    local cron_cmd="$ddns_script -k $cf_key -u $cf_user -h $cf_record -z $cf_zone -t $record_type >/dev/null 2>&1"
    (crontab -l 2>/dev/null | grep -v "cf-ddns.sh"; echo "* * * * * $cron_cmd") | crontab -
    
    # 立即执行一次
    log_info "正在执行第一次同步..."
    # 手动设置 HOME 环境变量以防万一
    export HOME=${HOME:-/root}
    $ddns_script -k "$cf_key" -u "$cf_user" -h "$cf_record" -z "$cf_zone" -t "$record_type" -f true >/dev/null 2>&1
    
    if [ $? -eq 0 ]; then
        log_info "DDNS 配置成功！每分钟自动更新。"
    else
        log_err "DDNS 同步失败，请检查 API Key 和网络。"
    fi
}

system_optimize() {
    log_info "正在应用极致系统优化 (BBR + Kernel)..."
    
    # 确保 BBR 模块已预先加载，防止 sysctl 设置失败
    modprobe tcp_bbr 2>/dev/null || true
    mkdir -p /etc/modules-load.d
    echo "tcp_bbr" > "$MODULES_LOAD_CONFIG" 2>/dev/null || true

    # 直接写入专用配置文件
    cat > "$SYSCTL_CONFIG" <<EOF
# --- 系统级限制 ---
fs.file-max = 2097152              # 系统最大文件打开数 (200w)
net.core.somaxconn = 65535         # 监听队列长度 (全连接队列)
net.ipv4.tcp_max_syn_backlog = 819200 # SYN 队列长度 (半连接队列)

# --- TCP 缓冲区 (适配大带宽) ---
net.core.rmem_max = 134217728      # 接收缓冲区最大值 (128MB)
net.core.wmem_max = 134217728      # 发送缓冲区最大值 (128MB)
net.ipv4.tcp_rmem = 4096 87380 134217728 # 自动调整范围
net.ipv4.tcp_wmem = 4096 65536 134217728 # 自动调整范围

# --- TCP 连接管理 ---
net.ipv4.tcp_max_tw_buckets = 2000000 # 最大 TIME_WAIT 数量 (防报错)
net.ipv4.tcp_tw_reuse = 1             # 允许重用 TIME_WAIT socket
net.ipv4.tcp_fin_timeout = 15         # 缩短 FIN_WAIT_2 时间 (加快回收)
net.ipv4.tcp_slow_start_after_idle = 0 # 禁用空闲后慢启动 (防降速)

# --- Keepalive (防死连接) ---
net.ipv4.tcp_keepalive_time = 60      # 60秒无数据开始探测
net.ipv4.tcp_keepalive_intvl = 10     # 探测间隔 10秒
net.ipv4.tcp_keepalive_probes = 6     # 探测 6次无果即断开

# --- 协议栈优化 ---
net.ipv4.tcp_fastopen = 3             # 开启 TFO (Client+Server)
net.ipv4.tcp_mtu_probing = 1          # 开启 MTU 探测 (防黑洞)
net.ipv4.tcp_window_scaling = 1       # 开启窗口扩大因子
net.ipv4.ip_local_port_range = 1024 65535 # 扩大本地端口范围
net.ipv4.tcp_notsent_lowat = 16384    # 解决 Bufferbloat，防抖动断流

# --- BBR 核心 ---
net.core.default_qdisc = fq           # 必须使用 fq 队列
net.ipv4.tcp_congestion_control = bbr # 开启 BBR 拥塞控制

# === 增强优化参数 (From tcp.sh Option 22) ===
# --- 网卡队列与调度 ---
net.core.netdev_max_backlog = 100000  # 网卡接收队列 (防丢包)
net.core.netdev_budget = 50000        # 软中断一次处理包数量
net.core.netdev_budget_usecs = 5000   # 软中断处理时间限制
net.core.rmem_default = 67108864      # 默认接收缓冲
net.core.wmem_default = 67108864      # 默认发送缓冲
net.core.optmem_max = 65536           # Socket 选项内存限制

# --- 安全与 ICMP 设置 ---
net.ipv4.icmp_echo_ignore_all = 0     # 允许 Ping
net.ipv4.icmp_echo_ignore_broadcasts = 1 # 禁止广播 Ping (防风暴)
net.ipv4.icmp_ignore_bogus_error_responses = 1 # 忽略错误 ICMP
net.ipv4.conf.all.accept_redirects = 0 # 禁用 ICMP 重定向 (安全)
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.rp_filter = 0   # 宽松的反向路径过滤
net.ipv4.conf.all.rp_filter = 0

# --- IPv6 设置 ---
net.ipv6.conf.all.disable_ipv6 = 0    # 开启 IPv6
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0
net.ipv6.conf.all.accept_ra = 2       # 接受路由广播
net.ipv6.conf.default.accept_ra = 2
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# --- 邻居表优化 (抗丢包关键) ---
net.ipv4.neigh.default.gc_thresh1 = 2048
net.ipv4.neigh.default.gc_thresh2 = 4096
net.ipv4.neigh.default.gc_thresh3 = 8192
net.ipv6.neigh.default.gc_thresh1 = 2048
net.ipv6.neigh.default.gc_thresh2 = 4096
net.ipv6.neigh.default.gc_thresh3 = 8192
net.ipv4.neigh.default.unres_qlen = 10000  # 未解析队列长度 (防突发流量丢包)
net.ipv6.neigh.default.unres_qlen = 10000

# --- 其他 TCP 细节 ---
net.ipv4.tcp_synack_retries = 1       # SYN-ACK 重试次数 (快速失败)
net.ipv4.tcp_syncookies = 1           # 开启 SYN Cookies (防 SYN Flood)
net.ipv4.tcp_rfc1337 = 0              # 禁用 TIME-WAIT Assassination 保护
net.ipv4.tcp_timestamps = 1           # 开启时间戳 (BBR 需要)
net.ipv4.udp_rmem_min = 8192          # UDP 缓冲区下限
net.ipv4.udp_wmem_min = 8192
net.ipv4.tcp_autocorking = 0          # 禁用自动粘包 (降低延迟)
net.ipv4.tcp_max_orphans = 262144     # 最大孤儿连接数 (防DDoS)
net.ipv4.tcp_orphan_retries = 0       # 加快回收孤儿连接 (防断流)
net.ipv4.tcp_retries2 = 5             # 已建立连接的重试次数
net.ipv4.tcp_early_retrans = 3        # 开启早期重传
net.ipv4.tcp_no_metrics_save = 0      # 缓存 TCP 指标
net.ipv4.tcp_ecn = 1                  # 开启 ECN (显式拥塞通知)
net.ipv4.tcp_ecn_fallback = 1
net.ipv4.tcp_frto = 0                 # 禁用 F-RTO

# --- 内存管理 ---
vm.swappiness = 1                     # 尽量不使用 Swap
vm.overcommit_memory = 1              # 允许内存超卖
kernel.pid_max = 64000                # 最大进程 ID
net.netfilter.nf_conntrack_max = 262144 # 连接追踪表大小
EOF

    sysctl --system
    save_thp_mode
    echo always >/sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null || true

    mkdir -p /etc/security/limits.d
    cat > "$LIMITS_DROPIN" <<EOF
root     soft   nofile    1000000
root     hard   nofile    1000000
root     soft   nproc     unlimited
root     hard   nproc     unlimited
root     soft   core      unlimited
root     hard   core      unlimited
root     hard   memlock   unlimited
root     soft   memlock   unlimited
*     soft   nofile    1000000
*     hard   nofile    1000000
*     soft   nproc     unlimited
*     hard   nproc     unlimited
*     soft   core      unlimited
*     hard   core      unlimited
*     hard   memlock   unlimited
*     soft   memlock   unlimited
EOF

    write_managed_block "/etc/profile" "$PROFILE_MARKER_BEGIN" "$PROFILE_MARKER_END" "ulimit -SHn 1000000"

    if [ -f /etc/pam.d/common-session ] && ! grep -q '^[[:space:]]*session[[:space:]]\+required[[:space:]]\+pam_limits\.so' /etc/pam.d/common-session; then
        write_managed_block "/etc/pam.d/common-session" "$PAM_MARKER_BEGIN" "$PAM_MARKER_END" "session required pam_limits.so"
    fi

    if command -v systemctl >/dev/null 2>&1; then
        systemctl daemon-reload
    fi
    log_info "系统优化和 BBR 配置完成！"
}

envoy_uninstall() {
    local force="$1"
    if [ "$force" != "-y" ] && [ "$force" != "force" ]; then
        read -p "确定要卸载 Envoy 吗? [y/N]: " confirm
        [[ "$confirm" != "y" && "$confirm" != "Y" ]] && return
    fi

    log_info "正在停止 Envoy 服务..."
    if command -v systemctl >/dev/null 2>&1; then
        systemctl stop envoy 2>/dev/null
        systemctl disable envoy 2>/dev/null
    fi
    
    log_info "正在删除文件..."
    rm -f "$ENVOY_BIN"
    rm -f "/usr/lib/systemd/system/envoy.service"
    rm -rf "/etc/envoy"
    rm -f "$SYSCTL_CONFIG"
    rm -f "$LIMITS_DROPIN"
    rm -f "$MODULES_LOAD_CONFIG"
    rm -f "/usr/local/bin/update_envoy_config.sh"
    rm -f "/usr/local/bin/checkenvoy.sh"
    rm -f "$TOOL_PATH"
    remove_managed_block "/etc/profile" "$PROFILE_MARKER_BEGIN" "$PROFILE_MARKER_END"
    remove_managed_block "/etc/pam.d/common-session" "$PAM_MARKER_BEGIN" "$PAM_MARKER_END"
    restore_thp_mode
    crontab -l 2>/dev/null | grep -v "update_envoy_config.sh" | grep -v "checkenvoy.sh" | crontab -
    
    sysctl --system >/dev/null 2>&1 || true
    if command -v systemctl >/dev/null 2>&1; then
        systemctl daemon-reload
    fi
    log_info "Envoy 已卸载完成，并已回滚本脚本写入的系统优化项"
}

# ===== 核心配置流程 =====
envoy_tcp_config() {
    read -p "请输入带宽 (Mbps) [${DEFAULT_BW_MBPS}]: " USER_BW
    USER_BW=${USER_BW:-$DEFAULT_BW_MBPS}
    local USER_RTT="$DEFAULT_RTT_MS"
    
    read -p "隧道编号: " tunnel_num
    if [[ ! "$tunnel_num" =~ ^[0-9]+$ ]]; then
        log_err "必须是数字"
        return 1
    fi
    
    echo "配置类型: 1)入口 2)出口 3)单层"
    read -p "选择 [1-3]: " config_type
    
    local pass_real_ip="y"
    if [ "$config_type" == "3" ]; then
        read -p "是否传递真实IP给后端? [Y/n]: " user_pass_ip
        [[ "$user_pass_ip" == "n" || "$user_pass_ip" == "N" ]] && pass_real_ip="n"
    fi

    # 调用核心函数 (无需 IP 版本参数)
    _apply_envoy_config "$USER_BW" "$tunnel_num" "$config_type" "$pass_real_ip" "$USER_RTT"
}

show_menu() {
    clear
    echo "====================================="
    echo "  Envoy 极致性能代理 v3.0"
    echo "  大带宽|不断流|长连接|BBR"
    echo "====================================="
    echo "1. 安装 Envoy"
    echo "2. 生成配置"
    echo "3. 管理服务"
    echo "4. 删除防火墙"
    echo "5. 定时检测"
    echo "6. 对接 soga"
    echo "7. 设置 DDNS"
    echo "8. 系统优化 (BBR + 内核)"
    echo "9. 卸载 Envoy"
    echo "0. 退出"
    echo "====================================="
}

show_cli_help() {
    cat <<'EOF'
用法:
  ./envoy.sh
  ./envoy.sh <命令> [参数]

可用命令:
  install
      安装 Envoy、service 文件和管理脚本。

  config <带宽Mbps> <隧道号> <配置类型1-3> [是否传IP y/n] [RTT毫秒]
      配置类型:
        1 = 入口
        2 = 出口
        3 = 单层
      示例:
        ./envoy.sh config 1000 1 1
        ./envoy.sh config 1000 1 3 n 350

  optimize
      应用系统级 BBR/sysctl/limits 优化。

  ddns [API_KEY EMAIL DOMAIN RECORD TYPE]
      不带参数时进入交互模式；TYPE 支持 1/A 或 2/AAAA。

  soga [NodeID SogaKey Domain WebApiKey]
      不带参数时进入交互模式。

  uninstall [force|-y]
      卸载 Envoy 及相关脚本，并回滚本脚本写入的系统优化项。

  help
      显示本帮助。

内部命令:
  generate_yaml <mode> <txt_file> <pass_real_ip> <user_bw> [user_rtt]
EOF
}


# ===== 主逻辑 =====
main() {
    # 支持命令行参数直接调用
    if [ $# -gt 0 ]; then
        case "$1" in
            help|-h|--help)
                show_cli_help
                exit 0
                ;;
        esac
    fi

    check_root
    
    # 支持命令行参数直接调用
    if [ $# -gt 0 ]; then
        case "$1" in
            install)
                envoy_install
                exit $?
                ;;
            config)
                # 参数: config <带宽Mbps> <隧道号> <类型1-3> [是否传IP y/n] [RTT毫秒]
                # 参数校验逻辑: 至少需要 3 个参数 (config, bandwidth, tunnel_num, type)
                if [ $# -lt 4 ]; then
                    echo "用法: $0 config <带宽Mbps> <隧道号> <配置类型1-3> [是否传IP y/n] [RTT毫秒]"
                    echo "配置类型: 1:入口, 2:出口, 3:单层"
                    exit 1
                fi
                # 获取参数
                local user_bw="$2"
                local tunnel_num="$3"
                local config_type="$4"
                local pass_real_ip="${5:-y}" # 默认为 y
                local user_rtt="${6:-$DEFAULT_RTT_MS}"
                if ! [[ "$user_rtt" =~ ^[0-9]+$ ]]; then
                    # 兼容旧参数顺序: 第五个参数若是 RTT，则视作 RTT；第六个参数为 pass_real_ip
                    if [[ "$pass_real_ip" =~ ^[0-9]+$ ]]; then
                        user_rtt="$pass_real_ip"
                        pass_real_ip="${6:-y}"
                    else
                        user_rtt="$DEFAULT_RTT_MS"
                    fi
                fi
                
                # 调用核心配置函数
                _apply_envoy_config "$user_bw" "$tunnel_num" "$config_type" "$pass_real_ip" "$user_rtt"
                exit $?
                ;;
            optimize)
                system_optimize
                exit $?
                ;;
            ddns)
                # 参数: ddns <Key> <Email> <Zone> <Record> <Type>
                # 如果只有 "./envoy.sh ddns" (参数个数=1)，进入交互模式
                if [ $# -eq 1 ]; then
                    setup_ddns
                else
                    # 否则将参数传给函数
                    setup_ddns "$2" "$3" "$4" "$5" "$6"
                fi
                exit $?
                ;;
            soga)
                # 参数: soga <NodeID> <SogaKey> <Domain> <WebApiKey>
                if [ $# -eq 1 ]; then
                    soga_setup
                else
                    soga_setup "$2" "$3" "$4" "$5"
                fi
                exit $?
                ;;
            generate_yaml)
                # 隐藏命令，供热更新脚本内部调用
                # 参数: generate_yaml <mode> <txt_file> <pass_real_ip> <user_bw> [user_rtt]
                # 参数校验: 需要至少 4 个参数 (generate_yaml, mode, txt_file, pass_real_ip)
                if [ $# -lt 4 ]; then
                     echo "用法: $0 generate_yaml <mode> <txt_file> <pass_real_ip> <user_bw> [user_rtt]"
                     exit 1
                fi
                local mode="$2"
                local listeners_file="$3"
                local pass_real_ip="$4"
                local user_bw="${5:-1000}"
                local user_rtt="${6:-$DEFAULT_RTT_MS}"
                
                # 重新计算 BDP 参数
                calculate_params "$user_bw" "$user_rtt"
                # 生成配置
                generate_envoy_yaml "$mode" "$listeners_file" "$pass_real_ip"
                exit 0
                ;;
            uninstall)
                envoy_uninstall "$2"
                exit $?
                ;;
            *)
                echo "未知命令: $1"
                show_cli_help
                exit 1
                ;;
        esac
    fi

    # 无参数则进入交互式菜单
    while true; do
        show_menu
        read -p "选择 [0-9]: " choice
        case $choice in
            1) envoy_install ;;
            2) envoy_tcp_config ;;
            3) envoy_manage ;;
            4) firewall_remove ;;
            5) envoy_monitor ;;
            6) soga_setup ;;
            7) setup_ddns ;;
            8) system_optimize ;;
            9) envoy_uninstall ;;
            0) exit 0 ;;
            *) echo "无效选择" ;;
        esac
        read -p "按回车继续..."
    done
}

main "$@"
