#!/bin/bash
# =============================================================================
# WireGuard 综合一键部署
# 1=RDP加速  2=全局翻墙  3=中转(星链→港/新)  4=落地(日/国内)  5=出海
# 6=查看管理(备注)  7=卸载
# 接口: wg0/wg1/wg2/wg3/wg4 网段隔离；中转38472 出海38473 写死密钥自动对接
# 用法: bash wgk.sh
# =============================================================================
set -e
set -u
set -o pipefail

# ---------- RDP 模式默认值 (wg0) ----------
PUBLIC_IP="${PUBLIC_IP:-}"
WG_PORT="${WG_PORT:-}"
WG_NET="${WG_NET:-10.77.77.0/24}"
SERVER_IP="${SERVER_IP:-10.77.77.1}"
ADMIN_START_IP="${ADMIN_START_IP:-10.77.77.2}"
USER_START_IP="${USER_START_IP:-10.77.77.202}"
IFACE="${IFACE:-}"
OUT="${OUT:-/root/wg-clients}"
KEY_DIR="${KEY_DIR:-/etc/wireguard/clients-keys}"

# ---------- 全局模式默认值 (wg1) ----------
WG1_PORT="${WG1_PORT:-}"
WG1_NET="${WG1_NET:-10.88.88.0/24}"
WG1_SERVER_IP="${WG1_SERVER_IP:-10.88.88.1}"
WG1_START_IP="${WG1_START_IP:-10.88.88.2}"
OUT_GLOBAL="${OUT_GLOBAL:-/root/wg-global-clients}"
KEY_DIR_GLOBAL="${KEY_DIR_GLOBAL:-/etc/wireguard/global-keys}"
GLOBAL_DNS="${GLOBAL_DNS:-1.1.1.1,8.8.8.8}"
SERVER_CONF_RDP="/etc/wireguard/server.conf"
SERVER_CONF_GLOBAL="/etc/wireguard/server-global.conf"
GLOBAL_PEERS_FILE="/etc/wireguard/global-peers.conf"

# 已知特征端口黑名单（避免被识别和 QoS）
PORT_BLACKLIST="53 80 443 1194 1701 4500 500 51820 51821 8080 8443 3389 22 25 110 143 993 995 465 587 3306 5432 6379 27017"

# --- 菜单配色（ANSI 1-6 + 指定 HEX）---
C_BORDER='\033[1;36m'                    # 6 青：大标题框
C_TITLE='\033[38;2;253;238;0m'           # 小组标题/请选择 #FDEE00
C_TIP='\033[38;2;253;238;0m'             # 标题说明同 #FDEE00
C_NUM='\033[1;32m'                       # 2 绿：序号
C_TEXT='\033[1;32m'                      # 2 绿：主内容 / 查看与管理
C_DESC='\033[38;2;230;143;172m'          # 组内 mstsc…类 #E68FAC
C_WARN='\033[1;31m'                      # 1 红：卸载
C_PROMPT='\033[38;2;253;238;0m'          # 请选择 #FDEE00
NC='\033[0m'
RED="$C_WARN"; GREEN="$C_NUM"; CYAN="$C_BORDER"; YELLOW="$C_PROMPT"
BLUE='\033[0;34m'; PURPLE="$C_DESC"; WHITE='\033[1;37m'; BOLD='\033[1m'; DIM="$C_TIP"
BRED="$C_WARN"; BGREEN="$C_NUM"; BCYAN="$C_BORDER"; BBLUE='\033[1;34m'; BPURPLE="$C_DESC"
info()  { echo -e "${C_NUM}[*]${NC} ${C_DESC}$*${NC}" >&2; }
ok()    { echo -e "${C_NUM}[OK]${NC} ${C_TEXT}$*${NC}" >&2; }
warn()  { echo -e "${C_PROMPT}[提示]${NC} ${C_PROMPT}$*${NC}" >&2; }
die()   { echo -e "${C_WARN}[错误]${NC} ${C_WARN}$*${NC}" >&2; exit 1; }

# 检查是否为root
[[ $EUID -eq 0 ]] || die "请用 root 运行"

# 检查输入是否为正整数
is_positive_integer() {
  [[ "$1" =~ ^[1-9][0-9]*$ ]]
}

# 检查输入是否为非负整数
is_non_negative_integer() {
  [[ "$1" =~ ^[0-9]+$ ]]
}

# 已占用/已保留的 UDP 端口（本机监听 + 已配置的 wg0/wg1）
reserved_udp_ports() {
  local ports="" p
  if [[ -f "$SERVER_CONF_RDP" ]]; then
    p=$(grep -E '^WG_PORT=' "$SERVER_CONF_RDP" 2>/dev/null | head -1 | cut -d= -f2)
    [[ -n "$p" ]] && ports="$ports $p"
  fi
  if [[ -f "$SERVER_CONF_GLOBAL" ]]; then
    p=$(grep -E '^WG1_PORT=' "$SERVER_CONF_GLOBAL" 2>/dev/null | head -1 | cut -d= -f2)
    [[ -n "$p" ]] && ports="$ports $p"
  fi
  ports="$ports ${WG_RELAY_PORT:-38472} ${WG_EXIT_PORT:-38473}"
  echo "$ports"
}

# 生成随机非常用端口（20000-65535）
generate_random_port() {
  local port reserved
  reserved=$(reserved_udp_ports)
  while true; do
    port=$((RANDOM % 45536 + 20000))
    if echo "$PORT_BLACKLIST $reserved" | grep -qw "$port"; then
      continue
    fi
    if ss -uln 2>/dev/null | grep -qE "[:.]${port}[[:space:]]"; then
      continue
    fi
    echo "$port"
    return
  done
}

# 开放 UDP 端口（iptables + firewalld + ufw）
open_udp_port() {
  local port="$1"
  [[ -n "$port" ]] || return 0
  iptables -C INPUT -p udp --dport "$port" -j ACCEPT 2>/dev/null \
    || iptables -I INPUT 1 -p udp --dport "$port" -j ACCEPT 2>/dev/null || true
  if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
    firewall-cmd --permanent --add-port="${port}/udp" >/dev/null 2>&1 || true
    firewall-cmd --reload >/dev/null 2>&1 || true
  fi
  if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi active; then
    ufw allow "${port}/udp" >/dev/null 2>&1 || true
  fi
}

close_udp_port() {
  local port="$1"
  [[ -n "$port" ]] || return 0
  iptables -D INPUT -p udp --dport "$port" -j ACCEPT 2>/dev/null || true
  if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
    firewall-cmd --permanent --remove-port="${port}/udp" >/dev/null 2>&1 || true
    firewall-cmd --reload >/dev/null 2>&1 || true
  fi
  if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi active; then
    ufw delete allow "${port}/udp" >/dev/null 2>&1 || true
  fi
}

# 清理 WG 相关 iptables 规则（旧版宽松规则 + NAT）
cleanup_wg_iptables() {
  local iface="${1:-}"
  while iptables -C FORWARD -i wg0 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg0 -j ACCEPT; done
  while iptables -C FORWARD -o wg0 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -o wg0 -j ACCEPT; done
  while iptables -C FORWARD -i wg0 -o wg0 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg0 -o wg0 -j ACCEPT; done
  while iptables -C FORWARD -i wg0 -o wg0 -j DROP 2>/dev/null; do iptables -D FORWARD -i wg0 -o wg0 -j DROP; done
  while iptables -C FORWARD -i wg0 ! -o wg0 -j DROP 2>/dev/null; do iptables -D FORWARD -i wg0 ! -o wg0 -j DROP; done
  while iptables -C FORWARD ! -i wg0 -o wg0 -j DROP 2>/dev/null; do iptables -D FORWARD ! -i wg0 -o wg0 -j DROP; done
  [[ -n "$iface" ]] || return 0
  while iptables -t nat -C POSTROUTING -o "$iface" -j MASQUERADE 2>/dev/null; do
    iptables -t nat -D POSTROUTING -o "$iface" -j MASQUERADE
  done
}

cleanup_wg1_iptables() {
  local iface="${1:-}" net="${2:-$WG1_NET}"
  while iptables -C FORWARD -i wg1 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg1 -j ACCEPT; done
  while iptables -C FORWARD -o wg1 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -o wg1 -j ACCEPT; done
  while iptables -C FORWARD -i wg1 -o wg1 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg1 -o wg1 -j ACCEPT; done
  [[ -n "$iface" ]] || return 0
  while iptables -t nat -C POSTROUTING -s "$net" -o "$iface" -j MASQUERADE 2>/dev/null; do
    iptables -t nat -D POSTROUTING -s "$net" -o "$iface" -j MASQUERADE
  done
}

# 刷新组内 mstsc 转发规则
refresh_mstsc_forward() {
  [[ -x /etc/wireguard/wg-mstsc-forward.sh ]] || write_forward_helper
  /etc/wireguard/wg-mstsc-forward.sh up
}

# 安全启动 wg0（先确认内核模块可用）
start_wg0() {
  wg_kernel_ready || die "WireGuard 内核模块未就绪，无法启动 wg0"
  wg-quick up wg0
}

start_wg1() {
  wg_kernel_ready || die "WireGuard 内核模块未就绪，无法启动 wg1"
  wg-quick up wg1
}

ensure_ip_forward() {
  sysctl -w net.ipv4.ip_forward=1 >/dev/null
  if ! grep -qE '^[[:space:]]*net\.ipv4\.ip_forward[[:space:]]*=' /etc/sysctl.conf 2>/dev/null; then
    echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
  else
    sed -i 's/^[[:space:]]*net\.ipv4\.ip_forward.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf
  fi
}

# 从客户端 conf 读取 Address IP（兼容无 Perl 的系统）
conf_get_address_ip() {
  local conf="$1" ip=""
  [[ -f "$conf" ]] || return 0
  ip=$(grep -oP 'Address = \K[0-9.]+' "$conf" 2>/dev/null | head -1 || true)
  [[ -n "$ip" ]] || ip=$(sed -n 's/^Address = \([0-9.]*\)\/.*/\1/p' "$conf" 2>/dev/null | head -1)
  [[ -n "$ip" ]] && echo "$ip"
}

# 校验组数不超过 IP 池（user 段 202-254 最多 53 组）
validate_group_num() {
  local n=$1
  local admin_last user_last
  admin_last=$(echo "$ADMIN_START_IP" | awk -F. '{print $4}')
  user_last=$(echo "$USER_START_IP" | awk -F. '{print $4}')
  [[ $((admin_last + n - 1)) -le 254 ]] || die "组数过多：admin IP 将超出 10.77.77.254"
  [[ $((user_last + n - 1)) -le 254 ]] || die "组数过多：user IP 将超出 10.77.77.254（user 段最多 $((254 - user_last + 1)) 组）"
}

# 获取下一个可用IP（add_group 等扩展场景用）
get_next_ip() {
  local base_ip=$1
  local prefix octet
  prefix=$(echo "$base_ip" | sed 's/\.[0-9]*$//')
  octet=$(echo "$base_ip" | awk -F. '{print $4}')
  
  local current_ip
  while true; do
    current_ip="${prefix}.$octet"
    # 检查 wg0.conf 中是否已存在此 IP（精确匹配 AllowedIPs 字段）
    local in_wg0=false
    if [[ -f /etc/wireguard/wg0.conf ]]; then
      if grep -qE "AllowedIPs = ${current_ip}/32" /etc/wireguard/wg0.conf; then
        in_wg0=true
      fi
    fi
    # 检查客户端配置文件中是否已存在此 IP（精确匹配 Address 字段）
    local in_clients=false
    if ls "$OUT"/*.conf 1>/dev/null 2>&1; then
      if grep -rqE "Address = ${current_ip}/" "$OUT"/*.conf; then
        in_clients=true
      fi
    fi
    if [[ "$in_wg0" == "false" && "$in_clients" == "false" ]]; then
      echo "$current_ip"
      return
    fi
    octet=$((octet + 1))
    [[ $octet -le 254 ]] || die "IP地址池已用完"
  done
}

# 自动检测公网IP
detect_public_ip() {
  if [[ -z "$PUBLIC_IP" ]]; then
    PUBLIC_IP=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || \
                curl -s --max-time 5 icanhazip.com 2>/dev/null || \
                curl -s --max-time 5 ip.sb 2>/dev/null || true)
    PUBLIC_IP=$(echo "$PUBLIC_IP" | tr -d '[:space:]')
  fi
  echo "$PUBLIC_IP"
}

# 自动检测网卡
detect_iface() {
  if [[ -z "$IFACE" ]]; then
    IFACE=$(ip -4 route show default 2>/dev/null | awk '{print $5}' | head -1)
  fi
  [[ -n "$IFACE" ]] || IFACE=eth0
  echo "$IFACE"
}

# 测试镜像源连通性，返回可用镜像域名
select_mirror() {
  local mirrors=("mirrors.aliyun.com" "mirrors.tuna.tsinghua.edu.cn" "mirrors.volces.com")
  for m in "${mirrors[@]}"; do
    if curl -s --max-time 3 -o /dev/null "https://${m}" 2>/dev/null; then
      echo "$m"
      return 0
    fi
  done
  return 1
}

# 配置国内镜像源（仅 debian/ubuntu/centos；OpenCloudOS 等跳过以免改坏源）
setup_cn_mirror() {
  local mirror
  mirror=$(select_mirror) || { warn "国内镜像源均不可达，沿用系统默认源"; return 0; }

  local os_id=""
  if [[ -f /etc/os-release ]]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    os_id="${ID:-}"
  fi

  case "$os_id" in
    debian|ubuntu) ;;
    centos|rhel|rocky|almalinux) ;;
    *)
      info "系统 ${os_id:-unknown} 跳过镜像源改写，使用系统默认源"
      return 0
      ;;
  esac

  ok "使用镜像源: $mirror"
  export USE_MIRROR="$mirror"

  # ---------- Debian/Ubuntu (apt) ----------
  if command -v apt-get >/dev/null 2>&1; then
    [[ -f /etc/os-release ]] || return 0
    . /etc/os-release
    local codename="${VERSION_CODENAME:-}"
    [[ -n "$codename" ]] || codename=$(lsb_release -cs 2>/dev/null)
    [[ -n "$codename" ]] || { warn "无法识别发行版代号，跳过镜像源配置"; return 0; }

    # 备份原配置
    [[ -f /etc/apt/sources.list && ! -f /etc/apt/sources.list.bak.wg ]] && \
      cp -a /etc/apt/sources.list /etc/apt/sources.list.bak.wg

    if [[ "$ID" == "debian" ]]; then
      cat > /etc/apt/sources.list <<EOF
deb https://${mirror}/debian/ ${codename} main contrib non-free non-free-firmware
deb https://${mirror}/debian/ ${codename}-updates main contrib non-free non-free-firmware
deb https://${mirror}/debian-backports main contrib non-free non-free-firmware
deb https://${mirror}/debian-security/ ${codename}-security main contrib non-free non-free-firmware
EOF
    elif [[ "$ID" == "ubuntu" ]]; then
      cat > /etc/apt/sources.list <<EOF
deb https://${mirror}/ubuntu/ ${codename} main restricted universe multiverse
deb https://${mirror}/ubuntu/ ${codename}-updates main restricted universe multiverse
deb https://${mirror}/ubuntu/ ${codename}-backports main restricted universe multiverse
deb https://${mirror}/ubuntu/ ${codename}-security main restricted universe multiverse
EOF
    fi
    return 0
  fi

  # ---------- CentOS/RHEL 系 (yum/dnf) —— 仅上述白名单发行版 ----------
  if command -v yum >/dev/null 2>&1 || command -v dnf >/dev/null 2>&1; then
    local repodir=/etc/yum.repos.d
    [[ -d "$repodir" ]] || return 0

    # 备份原配置
    [[ -d "$repodir" && ! -d "${repodir}.bak.wg" ]] && cp -a "$repodir" "${repodir}.bak.wg"

    local relver
    relver=$(rpm -E %{rhel} 2>/dev/null)
    [[ -n "$relver" ]] || return 0

    # CentOS 7
    if [[ "$relver" == "7" ]]; then
      cat > "$repodir/CentOS-Base.repo" <<EOF
[base]
name=CentOS-\$releasever - Base - ${mirror}
baseurl=https://${mirror}/centos/\$releasever/os/\$basearch/
gpgcheck=1
gpgkey=https://${mirror}/centos/RPM-GPG-KEY-CentOS-7

[updates]
name=CentOS-\$releasever - Updates - ${mirror}
baseurl=https://${mirror}/centos/\$releasever/updates/\$basearch/
gpgcheck=1
gpgkey=https://${mirror}/centos/RPM-GPG-KEY-CentOS-7

[extras]
name=CentOS-\$releasever - Extras - ${mirror}
baseurl=https://${mirror}/centos/\$releasever/extras/\$basearch/
gpgcheck=1
gpgkey=https://${mirror}/centos/RPM-GPG-KEY-CentOS-7
EOF
    else
      # CentOS 8 / Stream 8+
      cat > "$repodir/CentOS-Stream.repo" <<EOF
[baseos]
name=CentOS Stream \$releasever - BaseOS - ${mirror}
baseurl=https://${mirror}/centos-stream/\$releasever-stream/BaseOS/\$basearch/os/
gpgcheck=0

[appstream]
name=CentOS Stream \$releasever - AppStream - ${mirror}
baseurl=https://${mirror}/centos-stream/\$releasever-stream/AppStream/\$basearch/os/
gpgcheck=0

[extras]
name=CentOS Stream \$releasever - Extras - ${mirror}
baseurl=https://${mirror}/centos-stream/\$releasever-stream/extras/\$basearch/os/
gpgcheck=0
EOF
    fi

    # EPEL 源
    cat > "$repodir/epel.repo" <<EOF
[epel]
name=Extra Packages for Enterprise Linux \$releasever - ${mirror}
baseurl=https://${mirror}/epel/\$releasever/\$basearch/
gpgcheck=0
enabled=1
EOF
    rm -f "$repodir/epel-testing.repo" 2>/dev/null || true
    return 0
  fi
}

# 检测 WireGuard 内核模块是否可用
wg_kernel_ready() {
  modprobe wireguard 2>/dev/null || true
  lsmod | grep -q '^wireguard[[:space:]]' && return 0
  ip link add dev wg-test type wireguard 2>/dev/null && {
    ip link del wg-test 2>/dev/null || true
    return 0
  }
  return 1
}

# 是否 CentOS/RHEL 7（内核 3.10，无内置 WireGuard）
is_el7() {
  command -v rpm >/dev/null 2>&1 || return 1
  [[ "$(rpm -E %rhel 2>/dev/null)" == "7" ]]
}

# 安装 ELRepo 源
ensure_elrepo() {
  rpm -q elrepo-release >/dev/null 2>&1 && return 0
  local relver elrepo_rpm=""
  relver=$(rpm -E %rhel 2>/dev/null || echo "7")
  elrepo_rpm="https://www.elrepo.org/elrepo-release-${relver}.el${relver}.elrepo.noarch.rpm"
  if command -v dnf >/dev/null 2>&1; then
    dnf install -y -q "$elrepo_rpm" 2>/dev/null || return 1
  else
    yum install -y -q "$elrepo_rpm" 2>/dev/null || return 1
  fi
}

# CentOS7：安装 ELRepo 新内核（kernel-lt 5.x），需重启后生效
# 环境变量 WG_EL7_KERNEL_UPGRADE: ask(默认) | auto | skip
install_el7_kernel_lt() {
  local mode="${WG_EL7_KERNEL_UPGRADE:-ask}"
  [[ "$mode" == "skip" ]] && return 1

  warn "CentOS/RHEL 7 当前内核 $(uname -r) 无 WireGuard 模块"
  info "方案A（推荐）: 仅装 kmod-wireguard，不升级内核"
  info "方案B: 安装 ELRepo kernel-lt (5.x) 新内核，需重启服务器"

  if [[ "$mode" == "ask" ]]; then
    menu_confirm "kmod 安装失败，是否安装 ELRepo 新内核 (kernel-lt)? 安装后需重启" "1" || return 1
  fi

  ensure_elrepo || die "ELRepo 源安装失败"
  info "安装 kernel-lt 及 WireGuard 内核模块（可能需要几分钟）..."
  if command -v yum >/dev/null 2>&1; then
    yum --enablerepo=elrepo-kernel install -y -q kernel-lt kernel-lt-devel 2>/dev/null || \
      yum --enablerepo=elrepo-kernel install -y kernel-lt kernel-lt-devel || return 1
    yum install -y -q kmod-wireguard 2>/dev/null || yum install -y kmod-wireguard || return 1
  fi

  # 若新内核已安装但未启动，必须重启
  local running_kver installed_lt
  running_kver=$(uname -r)
  installed_lt=$(rpm -q kernel-lt --last 2>/dev/null | head -1 | awk '{print $1}' | sed 's/kernel-lt-//' | sed 's/\.x86_64//')
  if [[ -n "$installed_lt" && "$running_kver" != *"$installed_lt"* ]]; then
    warn "新内核 kernel-lt 已安装，但当前仍在运行旧内核: $running_kver"
    echo ""
    echo "  请执行: reboot"
    echo "  重启后运行: bash wgk.sh → 5. 修复配置"
    echo ""
    exit 0
  fi

  modprobe wireguard 2>/dev/null || true
  if wg_kernel_ready; then
    ok "新内核 WireGuard 模块已就绪"
    return 0
  fi
  return 1
}

# 安装 WireGuard 内核模块（CentOS/RHEL 老内核等）
install_wg_kernel() {
  if wg_kernel_ready; then
    ok "WireGuard 内核模块已就绪 ($(uname -r))"
    return 0
  fi

  info "WireGuard 内核模块未加载，尝试安装..."
  is_el7 && info "检测到 CentOS/RHEL 7：缺的是 WireGuard 内核模块，不是 wireguard-tools"

  if command -v apt-get >/dev/null 2>&1; then
    apt-get install -y -qq wireguard-dkms linux-headers-"$(uname -r)" 2>/dev/null \
      || apt-get install -y -qq wireguard-dkms linux-headers-generic 2>/dev/null \
      || apt-get install -y -qq wireguard 2>/dev/null || true
  elif command -v dnf >/dev/null 2>&1; then
    dnf install -y -q epel-release 2>/dev/null || true
    ensure_elrepo || true
    dnf install -y -q kmod-wireguard 2>/dev/null \
      || dnf install -y -q "kernel-modules-extra-$(uname -r)" 2>/dev/null || true
  elif command -v yum >/dev/null 2>&1; then
    yum install -y -q epel-release 2>/dev/null || true
    ensure_elrepo || die "ELRepo 源安装失败，CentOS7 需要 ELRepo 提供 kmod-wireguard"
    info "安装 kmod-wireguard（匹配当前内核，无需升级系统）..."
    yum install -y -q kmod-wireguard 2>/dev/null || yum install -y -q kmod-wireguard || true
    # DKMS 兜底
    if ! wg_kernel_ready; then
      info "kmod-wireguard 未匹配当前内核，尝试 wireguard-dkms..."
      yum install -y -q dkms kernel-devel-"$(uname -r)" 2>/dev/null || \
        yum install -y -q dkms kernel-devel 2>/dev/null || true
      yum install -y -q wireguard-dkms 2>/dev/null || true
    fi
    # CentOS7 最后手段：可选升级 kernel-lt
    if ! wg_kernel_ready && is_el7; then
      install_el7_kernel_lt || true
    fi
  fi

  modprobe wireguard 2>/dev/null || true
  if wg_kernel_ready; then
    ok "WireGuard 内核模块安装成功"
    return 0
  fi

  die "WireGuard 内核模块不可用（Protocol not supported）。
当前内核: $(uname -r)
CentOS7 缺的是内核模块，不是 wireguard-tools。可手动执行:
  yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
  yum install -y kmod-wireguard && modprobe wireguard
若仍失败，安装新内核后重启:
  yum --enablerepo=elrepo-kernel install -y kernel-lt
  reboot
重启后: bash wgk.sh → 5.修复配置
或自动升级内核: WG_EL7_KERNEL_UPGRADE=auto bash wgk.sh"
}

# 安装依赖（已装 wireguard-tools 则跳过重装包）
install_dependencies() {
  if command -v wg >/dev/null 2>&1 && wg_kernel_ready; then
    ok "WireGuard 已就绪，跳过依赖重装"
    return 0
  fi

  info "安装依赖..."
  export DEBIAN_FRONTEND=noninteractive

  # 配置国内镜像源（仅白名单发行版）
  setup_cn_mirror

  if command -v apt-get >/dev/null 2>&1; then
    info "更新软件源索引（可能需要1-3分钟）..."
    apt-get -o Acquire::http::Timeout=10 -o Acquire::ftp::Timeout=10 update -qq || true
    ok "软件源索引更新完成"
    info "安装 WireGuard 及依赖包（首次安装可能需要2-5分钟，请耐心等待）..."
    apt-get install -y -qq wireguard wireguard-tools qrencode iptables iproute2 curl
    install_wg_kernel
  elif command -v dnf >/dev/null 2>&1; then
    info "安装 EPEL 源..."
    dnf install -y -q epel-release 2>/dev/null || true
    info "安装 WireGuard 及依赖包..."
    dnf install -y -q wireguard-tools qrencode iptables iproute curl
    install_wg_kernel
  elif command -v yum >/dev/null 2>&1; then
    info "安装 EPEL 源..."
    yum install -y -q epel-release 2>/dev/null || true
    info "安装 WireGuard 及依赖包..."
    yum install -y -q wireguard-tools qrencode iptables iproute curl || true
    install_wg_kernel
  else
    die "不支持的系统：未找到 apt-get/yum/dnf"
  fi
  command -v wg >/dev/null || die "wireguard-tools 安装失败"
  wg_kernel_ready || die "WireGuard 内核模块未就绪，无法创建 WG 接口"
  ok "依赖安装完成"
}

# ---------- 交互函数（纯 bash）----------
# 按选项文案里的序号选择（支持 0 返回），例如 "1. xxx" / "0. 返回"
menu_choose() {
  local opts=("$@") sel="" opt num color idx=0 rest
  for opt in "${opts[@]}"; do
    num="${opt%%.*}"
    num="${num%%)*}"
    num="${num#"${num%%[![:space:]]*}"}"
    num="${num%"${num##*[![:space:]]}"}"
    rest="${opt#*.}"
    rest="${rest# }"
    rest="${rest#)}"
    rest="${rest# }"
    if [[ "$num" == "7" && "$opt" == *卸载* ]]; then
      color="$BRED"
    elif (( idx % 2 == 0 )); then
      color="$BCYAN"
    else
      color="$BBLUE"
    fi
    echo -e "  ${BGREEN}${num})${NC} ${color}${rest}${NC}" >&2
    idx=$((idx + 1))
  done
  printf "%b" "${BPURPLE}请选择：${NC}" >&2
  read -r sel
  [[ -z "$sel" ]] && return 1
  for opt in "${opts[@]}"; do
    num="${opt%%.*}"
    num="${num%%)*}"
    num="${num#"${num%%[![:space:]]*}"}"
    num="${num%"${num##*[![:space:]]}"}"
    if [[ "$sel" == "$num" ]]; then
      printf '%s\n' "$opt"
      return 0
    fi
  done
  return 1
}

# 中转/落地共用：1=回国方向 2=出海方向 0=返回 → 输出 cn|jp|空
choose_traffic_profile() {
  local sel
  echo "" >&2
  echo -e "${C_TITLE}选择流量方向${NC}" >&2
  echo -e " ${C_NUM}1)${NC} ${C_TEXT}回国方向${NC}   ${C_DESC}国内网段走隧道，DNS ${DNS_CN}${NC}" >&2
  echo -e " ${C_NUM}2)${NC} ${C_TEXT}出海方向${NC}   ${C_DESC}经落地出国，DNS ${DNS_JP}${NC}" >&2
  echo -e " ${C_NUM}0)${NC} ${C_TEXT}返回${NC}" >&2
  printf "%b" "${C_PROMPT}请选择 [1/2/0]: ${NC}" >&2
  read -r sel
  case "$sel" in
    1) echo "cn" ;;
    2) echo "jp" ;;
    *) echo "" ;;
  esac
}

menu_input() {
  local prompt="$1" default="${2:-}" val=""
  if [[ -n "$default" ]]; then
    printf "%b" "${GREEN}${prompt}${NC} ${DIM}[默认: ${default}]${NC}：" >&2
    read -r val
    echo "${val:-$default}"
  else
    printf "%b" "${GREEN}${prompt}${NC}：" >&2
    read -r val
    echo "$val"
  fi
}

menu_confirm() {
  local prompt="$1" default_no="${2:-0}" ans=""
  if [[ "$default_no" == "1" ]]; then
    printf "%b" "${YELLOW}${prompt}${NC} ${DIM}[y/N]${NC}：" >&2
    read -r ans
    [[ "$ans" == "y" || "$ans" == "Y" ]]
  else
    printf "%b" "${YELLOW}${prompt}${NC} ${DIM}[y/N]${NC}：" >&2
    read -r ans
    [[ "$ans" == "y" || "$ans" == "Y" ]]
  fi
}

show_title() {
  echo ""
  echo -e "${BCYAN}════════ $* ════════${NC}"
}

# 生成服务器密钥
generate_server_keys() {
  if [[ ! -f /etc/wireguard/server_private.key ]]; then
    info "生成服务器密钥..."
    wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
    chmod 600 /etc/wireguard/server_private.key
    ok "服务器密钥生成完成"
  else
    ok "沿用已有服务器密钥"
  fi
  SPRIV=$(cat /etc/wireguard/server_private.key)
  SPUB=$(cat /etc/wireguard/server_public.key)
}

# 生成客户端配置（AllowedIPs 仅对端 IP，组内 mstsc，不转发其他流量）
gen_client() {
  local name=$1 ip=$2 peer_ip=$3
  local priv pub conf="$OUT/${name}.conf"
  local priv_file="$KEY_DIR/${name}_private.key"
  local pub_file="$KEY_DIR/${name}_public.key"

  if [[ -f "$priv_file" && -f "$pub_file" ]]; then
    local old_ip=""
    [[ -f "$conf" ]] && old_ip=$(conf_get_address_ip "$conf")
    priv=$(cat "$priv_file")
    pub=$(cat "$pub_file")
    if [[ "$old_ip" == "$ip" ]]; then
      info "客户端已存在，刷新配置: $name ($ip → peer ${peer_ip})"
    else
      info "客户端配置更新: $name (${old_ip:-new} → $ip, peer ${peer_ip})"
    fi
  else
    priv=$(wg genkey)
    pub=$(echo "$priv" | wg pubkey)
    umask 077
    echo "$priv" > "$priv_file"
    echo "$pub"  > "$pub_file"
  fi

  cat > "$conf" <<EOF
[Interface]
PrivateKey = ${priv}
Address = ${ip}/32
# 分流隧道：仅对端 IP 走 WG，不改本地默认路由/DNS

[Peer]
PublicKey = ${SPUB}
Endpoint = ${PUBLIC_IP}:${WG_PORT}
AllowedIPs = ${peer_ip}/32
PersistentKeepalive = 25
EOF
  chmod 600 "$conf"
  ok "已生成客户端: $name ($ip ↔ ${peer_ip}) → 配置: $conf"
  echo "$pub"
}

# 写入 PostUp/PostDown 辅助脚本
write_forward_helper() {
  cat > /etc/wireguard/wg-mstsc-forward.sh <<'EOS'
#!/bin/bash
# WireGuard mstsc 组内转发：仅 admin↔user，禁止组间与上网
set -e
ACTION="${1:-up}"
# 注意：不能用 GROUPS（bash 只读变量）
WG_GROUPS_FILE=/etc/wireguard/groups.conf

clear_rules() {
  while iptables -C FORWARD -i wg0 -o wg0 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg0 -o wg0 -j ACCEPT; done
  while iptables -C FORWARD -i wg0 -o wg0 -j DROP 2>/dev/null; do iptables -D FORWARD -i wg0 -o wg0 -j DROP; done
  while iptables -C FORWARD -i wg0 ! -o wg0 -j DROP 2>/dev/null; do iptables -D FORWARD -i wg0 ! -o wg0 -j DROP; done
  while iptables -C FORWARD ! -i wg0 -o wg0 -j DROP 2>/dev/null; do iptables -D FORWARD ! -i wg0 -o wg0 -j DROP; done
  while iptables -C FORWARD -i wg0 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg0 -j ACCEPT; done
  while iptables -C FORWARD -o wg0 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -o wg0 -j ACCEPT; done
  [[ -f "$WG_GROUPS_FILE" ]] || return 0
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
    read -r _g _an admin_ip _un user_ip <<< "$line"
    [[ -n "$admin_ip" && -n "$user_ip" ]] || continue
    while iptables -C FORWARD -i wg0 -o wg0 -s "${admin_ip}/32" -d "${user_ip}/32" -j ACCEPT 2>/dev/null; do
      iptables -D FORWARD -i wg0 -o wg0 -s "${admin_ip}/32" -d "${user_ip}/32" -j ACCEPT
    done
    while iptables -C FORWARD -i wg0 -o wg0 -s "${user_ip}/32" -d "${admin_ip}/32" -j ACCEPT 2>/dev/null; do
      iptables -D FORWARD -i wg0 -o wg0 -s "${user_ip}/32" -d "${admin_ip}/32" -j ACCEPT
    done
  done < "$WG_GROUPS_FILE"
}

apply_rules() {
  clear_rules
  [[ -f "$WG_GROUPS_FILE" ]] || return 0
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
    read -r _g _an admin_ip _un user_ip <<< "$line"
    [[ -n "$admin_ip" && -n "$user_ip" ]] || continue
    iptables -I FORWARD 1 -i wg0 -o wg0 -s "${admin_ip}/32" -d "${user_ip}/32" -j ACCEPT
    iptables -I FORWARD 1 -i wg0 -o wg0 -s "${user_ip}/32" -d "${admin_ip}/32" -j ACCEPT
  done < "$WG_GROUPS_FILE"
  iptables -A FORWARD -i wg0 -o wg0 -j DROP
  iptables -A FORWARD -i wg0 ! -o wg0 -j DROP
  iptables -A FORWARD ! -i wg0 -o wg0 -j DROP
}

case "$ACTION" in
  up) apply_rules ;;
  down) clear_rules ;;
  *) exit 1 ;;
esac
EOS
  chmod 700 /etc/wireguard/wg-mstsc-forward.sh
}

# 添加peer到wg0.conf
add_peer_to_config() {
  local name=$1 ip=$2 pub=$3
  cat >> /etc/wireguard/wg0.conf <<EOF
[Peer]
# ${name}
PublicKey = ${pub}
AllowedIPs = ${ip}/32
EOF
}

# 安装WireGuard服务器（RDP 加速版 wg0）
install_server() {
  info "=== 安装 RDP 加速版 (wg0) ==="

  if [[ -f /etc/wireguard/wg0.conf && -f "$SERVER_CONF_RDP" ]]; then
    warn "检测到已有 RDP 模式 (wg0) 安装"
    menu_confirm "继续安装将覆盖 RDP 配置（不影响全局 wg1），是否继续?" "1" || { info "已取消"; return; }
  fi

  # 公网IP：自动检测，无需交互
  PUBLIC_IP=$(detect_public_ip)
  [[ -n "$PUBLIC_IP" ]] || die "无法自动检测公网IP，请手动设置 PUBLIC_IP 环境变量"
  ok "公网IP: $PUBLIC_IP"

  # 网卡：自动检测，无需交互
  IFACE=$(detect_iface)
  ok "出口网卡: $IFACE"

  # 端口：随机生成非常用端口，无需交互
  WG_PORT=$(generate_random_port)
  ok "WireGuard端口: $WG_PORT (UDP, 随机非常用端口)"

  # 获取组数
  local input_count
  input_count=$(menu_input "请输入组数(每组1个admin+1个user)" "1")
  local group_count="${input_count:-1}"
  is_positive_integer "$group_count" || die "组数必须是正整数"
  validate_group_num "$group_count"

  # 配置确认
  info "配置确认：公网IP=$PUBLIC_IP, 网卡=$IFACE, 端口=$WG_PORT, 组数=$group_count"
  menu_confirm "开始安装?" || { info "安装已取消"; return; }

  # 安装依赖
  install_dependencies
  
  # 创建目录（配置文件与密钥文件分开存放）
  mkdir -p /etc/wireguard "$OUT" "$KEY_DIR"
  chmod 700 /etc/wireguard "$OUT" "$KEY_DIR"
  
  # 生成服务器密钥
  generate_server_keys
  
  # 生成转发辅助脚本 + wg0.conf（仅组内转发，无 MASQUERADE）
  write_forward_helper
  cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
Address = ${SERVER_IP}/24
ListenPort = ${WG_PORT}
PrivateKey = ${SPRIV}
PostUp = /etc/wireguard/wg-mstsc-forward.sh up
PostDown = /etc/wireguard/wg-mstsc-forward.sh down
EOF
  chmod 600 /etc/wireguard/wg0.conf
  
  # 创建组配置文件
  echo "# 组名 admin_name admin_ip user_name user_ip" > /etc/wireguard/groups.conf
  
  # 持久化服务器配置
  cat > "$SERVER_CONF_RDP" <<EOF
PUBLIC_IP=${PUBLIC_IP}
WG_PORT=${WG_PORT}
IFACE=${IFACE}
WG_NET=${WG_NET}
SERVER_IP=${SERVER_IP}
ADMIN_START_IP=${ADMIN_START_IP}
USER_START_IP=${USER_START_IP}
OUT=${OUT}
KEY_DIR=${KEY_DIR}
EOF
  chmod 600 "$SERVER_CONF_RDP"
  
  # 创建客户端（admin从ADMIN_START_IP开始，user从USER_START_IP开始，一一对应）
  local admin_prefix admin_start_octet
  admin_prefix=$(echo "$ADMIN_START_IP" | sed 's/\.[0-9]*$//')
  admin_start_octet=$(echo "$ADMIN_START_IP" | awk -F. '{print $4}')
  local user_prefix user_start_octet
  user_prefix=$(echo "$USER_START_IP" | sed 's/\.[0-9]*$//')
  user_start_octet=$(echo "$USER_START_IP" | awk -F. '{print $4}')
  
  for i in $(seq 1 "$group_count"); do
    local group_name="group${i}"
    local admin_name="admin${i}"
    local user_name="user${i}"
    
    # 根据组号计算IP
    local admin_ip="${admin_prefix}.$((admin_start_octet + i - 1))"
    local user_ip="${user_prefix}.$((user_start_octet + i - 1))"

    # 生成admin客户端（仅通同组 user）
    local admin_pub=$(gen_client "$admin_name" "$admin_ip" "$user_ip")
    # 生成user客户端（仅通同组 admin）
    local user_pub=$(gen_client "$user_name" "$user_ip" "$admin_ip")
    
    # 添加到wg0.conf
    add_peer_to_config "$admin_name" "$admin_ip" "$admin_pub"
    add_peer_to_config "$user_name" "$user_ip" "$user_pub"
    
    # 记录到groups.conf
    echo "${group_name} ${admin_name} ${admin_ip} ${user_name} ${user_ip}" >> /etc/wireguard/groups.conf
    
    ok "组 ${group_name} 创建完成：admin${i}=${admin_ip} ↔ user${i}=${user_ip}"
  done
  
  # 内核转发
  info "配置内核转发..."
  ensure_ip_forward
  
  # 防火墙
  info "配置防火墙..."
  open_udp_port "$WG_PORT"
  
  # 启动服务
  info "启动WireGuard服务..."
  wg-quick down wg0 2>/dev/null || true
  ip link del wg0 2>/dev/null || true
  cleanup_wg_iptables "$IFACE"
  
  sleep 1
  start_wg0
  systemctl enable wg-quick@wg0 2>/dev/null || true
  
  ok "=== RDP 加速版安装完成 ==="
  echo "  服务器VPN地址: ${SERVER_IP}"
  echo "  Endpoint: ${PUBLIC_IP}:${WG_PORT} (UDP)"
  echo "  隧道模式: 组内 mstsc（admin↔user 互通，组间隔离，不走 WG 上网）"
  echo "  mstsc 连接: admin 连 user 的 WG IP，user 连 admin 的 WG IP"
  echo ""
  wg show wg0
  echo ""
  info "客户端配置文件目录: ${OUT}/"
  info "客户端密钥文件目录: ${KEY_DIR}/"
}

# 查看状态
view_status() {
  show_title "WireGuard 双模式状态"
  PUBLIC_IP=$(detect_public_ip)

  echo -e "${BOLD}--- RDP 加速版 (wg0) ---${NC}"
  if [[ -f /etc/wireguard/wg0.conf && -f "$SERVER_CONF_RDP" ]]; then
    # shellcheck disable=SC1090
    source "$SERVER_CONF_RDP"
    echo "  服务器VPN: ${SERVER_IP}"
    echo "  Endpoint: ${PUBLIC_IP}:${WG_PORT} (UDP)"
    echo "  模式: 组内 mstsc（admin↔user），不走 WG 上网"
    if [[ -f /etc/wireguard/groups.conf ]]; then
      while read -r line || [[ -n "$line" ]]; do
        [[ "$line" =~ ^# ]] && continue
        [[ -z "$line" ]] && continue
        local group_name admin_name admin_ip user_name user_ip
        read -r group_name admin_name admin_ip user_name user_ip <<< "$line"
        echo "  ${group_name}: ${admin_name}(${admin_ip}) ↔ ${user_name}(${user_ip})"
        echo "    admin: ${OUT}/${admin_name}.conf"
        echo "    user:  ${OUT}/${user_name}.conf"
      done < /etc/wireguard/groups.conf
    fi
    echo ""
    wg show wg0 2>/dev/null || echo "  接口未启动"
    ss -ulnp 2>/dev/null | grep ":${WG_PORT}" || echo "  端口未监听"
  else
    echo "  未安装"
  fi

  echo ""
  echo -e "${BOLD}--- 全局流量版 (wg1) ---${NC}"
  if [[ -f /etc/wireguard/wg1.conf && -f "$SERVER_CONF_GLOBAL" ]]; then
    # shellcheck disable=SC1090
    source "$SERVER_CONF_GLOBAL"
    echo "  服务器VPN: ${WG1_SERVER_IP}"
    echo "  Endpoint: ${PUBLIC_IP}:${WG1_PORT} (UDP)"
    echo "  模式: 全局转发 AllowedIPs=0.0.0.0/0"
    echo "  DNS: ${GLOBAL_DNS:-1.1.1.1,8.8.8.8}"
    if [[ -f "$GLOBAL_PEERS_FILE" ]]; then
      while read -r line || [[ -n "$line" ]]; do
        [[ "$line" =~ ^# ]] && continue
        [[ -z "$line" ]] && continue
        local pname pip
        read -r pname pip <<< "$line"
        echo "  ${pname}: ${pip}  → ${OUT_GLOBAL}/${pname}.conf"
      done < "$GLOBAL_PEERS_FILE"
    fi
    echo ""
    wg show wg1 2>/dev/null || echo "  接口未启动"
    ss -ulnp 2>/dev/null | grep ":${WG1_PORT}" || echo "  端口未监听"
  else
    echo "  未安装"
  fi
}

# 添加组
add_group() {
  if [[ ! -f /etc/wireguard/wg0.conf ]]; then
    warn "WireGuard尚未安装，请先选择安装"
    return
  fi
  
  info "=== 添加新组 ==="
  
  # 获取最大组号
  local max_group_num=0
  if [[ -f /etc/wireguard/groups.conf ]]; then
    while read -r line || [[ -n "$line" ]]; do
      [[ "$line" =~ ^# ]] && continue
      [[ -z "$line" ]] && continue
      local g_name
      read -r g_name _ <<< "$line"
      local num="${g_name#group}"
      if [[ "$num" =~ ^[0-9]+$ ]] && [[ $num -gt $max_group_num ]]; then
        max_group_num=$num
      fi
    done < /etc/wireguard/groups.conf
  fi
  local new_group_num=$((max_group_num + 1))
  local group_name="group${new_group_num}"
  
  # 获取服务器信息
  SPRIV=$(cat /etc/wireguard/server_private.key)
  SPUB=$(cat /etc/wireguard/server_public.key)
  
  # 从持久化配置读取
  source /etc/wireguard/server.conf
  
  # 获取IP（根据组号计算，admin从ADMIN_START_IP开始，user从USER_START_IP开始）
  local admin_name="admin${new_group_num}"
  local user_name="user${new_group_num}"
  
  local admin_prefix admin_start_octet
  admin_prefix=$(echo "$ADMIN_START_IP" | sed 's/\.[0-9]*$//')
  admin_start_octet=$(echo "$ADMIN_START_IP" | awk -F. '{print $4}')
  local admin_ip="${admin_prefix}.$((admin_start_octet + new_group_num - 1))"
  
  local user_prefix user_start_octet
  user_prefix=$(echo "$USER_START_IP" | sed 's/\.[0-9]*$//')
  user_start_octet=$(echo "$USER_START_IP" | awk -F. '{print $4}')
  local user_ip="${user_prefix}.$((user_start_octet + new_group_num - 1))"
  validate_group_num "$new_group_num"
  
  # 生成admin客户端（仅通同组 user）
  local admin_pub=$(gen_client "$admin_name" "$admin_ip" "$user_ip")
  # 生成user客户端（仅通同组 admin）
  local user_pub=$(gen_client "$user_name" "$user_ip" "$admin_ip")
  
  # 添加到wg0.conf
  add_peer_to_config "$admin_name" "$admin_ip" "$admin_pub"
  add_peer_to_config "$user_name" "$user_ip" "$user_pub"
  
  # 记录到groups.conf
  echo "${group_name} ${admin_name} ${admin_ip} ${user_name} ${user_ip}" >> /etc/wireguard/groups.conf
  
  # 动态添加peer（无需重启，更平滑）
  info "更新WireGuard配置..."
  wg set wg0 peer "$admin_pub" allowed-ips "$admin_ip/32"
  wg set wg0 peer "$user_pub" allowed-ips "$user_ip/32"
  refresh_mstsc_forward
  
  ok "组 ${group_name} 添加完成：admin${new_group_num}=${admin_ip} ↔ user${new_group_num}=${user_ip}"
}

# 删除组
delete_group() {
  if [[ ! -f /etc/wireguard/wg0.conf ]]; then
    warn "WireGuard尚未安装，请先选择安装"
    return
  fi
  
  if [[ ! -f /etc/wireguard/groups.conf ]]; then
    warn "暂无组可删除"
    return
  fi
  
  show_title "删除组"

  # 收集组列表
  local groups=()
  local group_labels=()
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
    local group_name admin_name admin_ip user_name user_ip
    read -r group_name admin_name admin_ip user_name user_ip <<< "$line"
    groups+=("$line")
    group_labels+=("${group_name} (admin: ${admin_name} ${admin_ip}, user: ${user_name} ${user_ip})")
  done < /etc/wireguard/groups.conf

  local selected_label
  selected_label=$(menu_choose "${group_labels[@]}")
  [[ -z "$selected_label" ]] && { info "已取消"; return; }

  # 根据 label 找回原始记录
  local selected_index
  for i in "${!group_labels[@]}"; do
    [[ "${group_labels[$i]}" == "$selected_label" ]] && selected_index=$i && break
  done
  local selected_group="${groups[$selected_index]}"
  local group_name admin_name admin_ip user_name user_ip
  read -r group_name admin_name admin_ip user_name user_ip <<< "$selected_group"

  menu_confirm "确定删除组 ${group_name}?" "1" || { info "删除已取消"; return; }
  
  # 从持久化配置读取KEY_DIR
  source /etc/wireguard/server.conf

  # 先读取public key（在删除文件之前）
  local admin_pub="" user_pub=""
  [[ -f "$KEY_DIR/${admin_name}_public.key" ]] && admin_pub=$(cat "$KEY_DIR/${admin_name}_public.key")
  [[ -f "$KEY_DIR/${user_name}_public.key" ]] && user_pub=$(cat "$KEY_DIR/${user_name}_public.key")

  # 从wg0删除peer
  [[ -n "$admin_pub" ]] && wg set wg0 peer "$admin_pub" remove 2>/dev/null || true
  [[ -n "$user_pub" ]] && wg set wg0 peer "$user_pub" remove 2>/dev/null || true

  # 删除客户端配置文件和密钥文件（分开存放）
  rm -f "$OUT/${admin_name}.conf" "$OUT/${user_name}.conf"
  rm -f "$KEY_DIR/${admin_name}_private.key" "$KEY_DIR/${admin_name}_public.key"
  rm -f "$KEY_DIR/${user_name}_private.key" "$KEY_DIR/${user_name}_public.key"
  
  # 从groups.conf删除记录
  sed -i "/^${group_name} /d" /etc/wireguard/groups.conf
  
  # 重新生成wg0.conf
  info "更新配置文件..."
  cp -a /etc/wireguard/wg0.conf "/etc/wireguard/wg0.conf.bak.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
  
  # 获取[Interface]部分（用sed更健壮）
  sed '/^\[Peer\]/,$d' /etc/wireguard/wg0.conf > /tmp/wg0_interface.tmp
  
  # 重建wg0.conf
  cat /tmp/wg0_interface.tmp > /etc/wireguard/wg0.conf
  
  # 重新添加剩余peer
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
    local g_name g_admin_name g_admin_ip g_user_name g_user_ip
    read -r g_name g_admin_name g_admin_ip g_user_name g_user_ip <<< "$line"

    if [[ -f "$KEY_DIR/${g_admin_name}_public.key" ]]; then
      local pub=$(cat "$KEY_DIR/${g_admin_name}_public.key")
      add_peer_to_config "$g_admin_name" "$g_admin_ip" "$pub"
    fi
    if [[ -f "$KEY_DIR/${g_user_name}_public.key" ]]; then
      local pub=$(cat "$KEY_DIR/${g_user_name}_public.key")
      add_peer_to_config "$g_user_name" "$g_user_ip" "$pub"
    fi
  done < /etc/wireguard/groups.conf
  
  rm -f /tmp/wg0_interface.tmp
  
  # 重启wg0
  wg-quick down wg0 2>/dev/null || true
  sleep 1
  start_wg0
  refresh_mstsc_forward
  
  ok "组 ${group_name} 删除完成"
}

# 修复现有配置（IP 冲突 / 组内隔离 / 去掉上网转发）
repair_config() {
  if [[ ! -f /etc/wireguard/wg0.conf ]]; then
    warn "WireGuard尚未安装，请先选择安装"
    return
  fi

  show_title "修复现有配置"
  info "将重建唯一 IP、组内 AllowedIPs、去掉 MASQUERADE，并重载 wg0"
  menu_confirm "开始修复?" || { info "已取消"; return; }

  # 确保内核模块可用（CentOS7 半安装场景）
  install_wg_kernel

  source /etc/wireguard/server.conf
  SPRIV=$(cat /etc/wireguard/server_private.key)
  SPUB=$(cat /etc/wireguard/server_public.key)

  # 兼容旧版 START_IP
  ADMIN_START_IP="${ADMIN_START_IP:-${START_IP:-10.77.77.2}}"
  USER_START_IP="${USER_START_IP:-10.77.77.202}"

  local admin_prefix admin_start_octet user_prefix user_start_octet
  admin_prefix=$(echo "$ADMIN_START_IP" | sed 's/\.[0-9]*$//')
  admin_start_octet=$(echo "$ADMIN_START_IP" | awk -F. '{print $4}')
  user_prefix=$(echo "$USER_START_IP" | sed 's/\.[0-9]*$//')
  user_start_octet=$(echo "$USER_START_IP" | awk -F. '{print $4}')

  # 备份
  local ts
  ts=$(date +%Y%m%d%H%M%S)
  cp -a /etc/wireguard/wg0.conf "/etc/wireguard/wg0.conf.bak.${ts}"
  cp -a /etc/wireguard/groups.conf "/etc/wireguard/groups.conf.bak.${ts}" 2>/dev/null || true
  mkdir -p "${OUT}.bak.${ts}"
  cp -a "$OUT"/*.conf "${OUT}.bak.${ts}/" 2>/dev/null || true
  ok "已备份到 *.bak.${ts}"

  # 收集现有组号
  local group_nums=()
  if [[ -f /etc/wireguard/groups.conf ]]; then
    while read -r line || [[ -n "$line" ]]; do
      [[ "$line" =~ ^# ]] && continue
      [[ -z "$line" ]] && continue
      local g_name
      read -r g_name _ <<< "$line"
      local num="${g_name#group}"
      [[ "$num" =~ ^[0-9]+$ ]] && group_nums+=("$num")
    done < /etc/wireguard/groups.conf
  fi
  # 若 groups.conf 损坏，从客户端文件推断
  if [[ ${#group_nums[@]} -eq 0 ]]; then
    for f in "$OUT"/admin*.conf; do
      [[ -f "$f" ]] || continue
      local n
      n=$(basename "$f" .conf)
      n="${n#admin}"
      [[ "$n" =~ ^[0-9]+$ ]] && group_nums+=("$n")
    done
  fi
  [[ ${#group_nums[@]} -gt 0 ]] || die "未找到任何组，无法修复"

  # 重建 groups.conf
  echo "# 组名 admin_name admin_ip user_name user_ip" > /etc/wireguard/groups.conf

  # 重建转发脚本 + wg0 Interface
  write_forward_helper
  cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
Address = ${SERVER_IP}/24
ListenPort = ${WG_PORT}
PrivateKey = ${SPRIV}
PostUp = /etc/wireguard/wg-mstsc-forward.sh up
PostDown = /etc/wireguard/wg-mstsc-forward.sh down
EOF

  for i in "${group_nums[@]}"; do
    local group_name="group${i}"
    local admin_name="admin${i}"
    local user_name="user${i}"
    local admin_ip="${admin_prefix}.$((admin_start_octet + i - 1))"
    local user_ip="${user_prefix}.$((user_start_octet + i - 1))"

    [[ -f "$KEY_DIR/${admin_name}_public.key" ]] || die "缺少密钥: $KEY_DIR/${admin_name}_public.key"
    [[ -f "$KEY_DIR/${user_name}_public.key" ]] || die "缺少密钥: $KEY_DIR/${user_name}_public.key"

    local admin_pub user_pub
    admin_pub=$(gen_client "$admin_name" "$admin_ip" "$user_ip")
    user_pub=$(gen_client "$user_name" "$user_ip" "$admin_ip")

    add_peer_to_config "$admin_name" "$admin_ip" "$admin_pub"
    add_peer_to_config "$user_name" "$user_ip" "$user_pub"
    echo "${group_name} ${admin_name} ${admin_ip} ${user_name} ${user_ip}" >> /etc/wireguard/groups.conf
    ok "修复 ${group_name}: ${admin_ip} ↔ ${user_ip}"
  done

  # 更新 server.conf (RDP)
  cat > "$SERVER_CONF_RDP" <<EOF
PUBLIC_IP=${PUBLIC_IP}
WG_PORT=${WG_PORT}
IFACE=${IFACE}
WG_NET=${WG_NET}
SERVER_IP=${SERVER_IP}
ADMIN_START_IP=${ADMIN_START_IP}
USER_START_IP=${USER_START_IP}
OUT=${OUT}
KEY_DIR=${KEY_DIR}
EOF
  chmod 600 "$SERVER_CONF_RDP" /etc/wireguard/wg0.conf

  # 清理旧防火墙规则后重载
  info "重载 WireGuard..."
  wg-quick down wg0 2>/dev/null || true
  /etc/wireguard/wg-mstsc-forward.sh down 2>/dev/null || true
  cleanup_wg_iptables "$IFACE"

  sleep 1
  start_wg0

  ok "=== 修复完成 ==="
  echo "  Endpoint: ${PUBLIC_IP}:${WG_PORT}"
  echo "  模式: 组内 mstsc（admin↔user），不走 WG 上网"
  echo "  请重新下发客户端配置: ${OUT}/"
  echo ""
  wg show wg0
}

# ======================== 全局流量版 (wg1) ========================
generate_global_server_keys() {
  if [[ ! -f /etc/wireguard/server_global_private.key ]]; then
    info "生成全局模式服务器密钥..."
    wg genkey | tee /etc/wireguard/server_global_private.key | wg pubkey > /etc/wireguard/server_global_public.key
    chmod 600 /etc/wireguard/server_global_private.key
    ok "全局模式服务器密钥生成完成"
  else
    ok "沿用已有全局模式服务器密钥"
  fi
  GSPRIV=$(cat /etc/wireguard/server_global_private.key)
  GSPUB=$(cat /etc/wireguard/server_global_public.key)
}

write_global_forward_helper() {
  local iface="$1" net="$2"
  cat > /etc/wireguard/wg-global-forward.sh <<EOS
#!/bin/bash
# WireGuard 全局转发：wg1 ↔ 公网 MASQUERADE
set -e
ACTION="\${1:-up}"
IFACE="${iface}"
WG_NET="${net}"

clear_rules() {
  while iptables -C FORWARD -i wg1 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg1 -j ACCEPT; done
  while iptables -C FORWARD -o wg1 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -o wg1 -j ACCEPT; done
  while iptables -t nat -C POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE 2>/dev/null; do
    iptables -t nat -D POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE
  done
}

apply_rules() {
  clear_rules
  iptables -I FORWARD 1 -i wg1 -j ACCEPT
  iptables -I FORWARD 1 -o wg1 -j ACCEPT
  iptables -t nat -A POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE
}

case "\$ACTION" in
  up) apply_rules ;;
  down) clear_rules ;;
  *) exit 1 ;;
esac
EOS
  chmod 700 /etc/wireguard/wg-global-forward.sh
}

# 生成全局客户端配置（AllowedIPs = 0.0.0.0/0）
gen_global_client() {
  local name=$1 ip=$2
  local priv pub conf="${OUT_GLOBAL}/${name}.conf"
  local priv_file="${KEY_DIR_GLOBAL}/${name}_private.key"
  local pub_file="${KEY_DIR_GLOBAL}/${name}_public.key"

  if [[ -f "$priv_file" && -f "$pub_file" ]]; then
    priv=$(cat "$priv_file")
    pub=$(cat "$pub_file")
    info "刷新全局客户端: $name ($ip)"
  else
    priv=$(wg genkey)
    pub=$(echo "$priv" | wg pubkey)
    umask 077
    echo "$priv" > "$priv_file"
    echo "$pub"  > "$pub_file"
  fi

  cat > "$conf" <<EOF
[Interface]
PrivateKey = ${priv}
Address = ${ip}/32
DNS = ${GLOBAL_DNS}
# 全局隧道：全部流量走 WG

[Peer]
PublicKey = ${GSPUB}
Endpoint = ${PUBLIC_IP}:${WG1_PORT}
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF
  chmod 600 "$conf"
  ok "已生成全局客户端: $name ($ip) → $conf"
  echo "$pub"
}

add_peer_to_wg1() {
  local name=$1 ip=$2 pub=$3
  cat >> /etc/wireguard/wg1.conf <<EOF
[Peer]
# ${name}
PublicKey = ${pub}
AllowedIPs = ${ip}/32
EOF
}

get_next_global_ip() {
  local prefix octet current_ip
  prefix=$(echo "$WG1_START_IP" | sed 's/\.[0-9]*$//')
  octet=$(echo "$WG1_START_IP" | awk -F. '{print $4}')
  while true; do
    current_ip="${prefix}.${octet}"
    local used=false
    if [[ -f /etc/wireguard/wg1.conf ]] && grep -qE "AllowedIPs = ${current_ip}/32" /etc/wireguard/wg1.conf; then
      used=true
    fi
    if [[ -f "$GLOBAL_PEERS_FILE" ]] && grep -qE " ${current_ip}\$" "$GLOBAL_PEERS_FILE"; then
      used=true
    fi
    if [[ "$used" == "false" ]]; then
      echo "$current_ip"
      return
    fi
    octet=$((octet + 1))
    [[ $octet -le 254 ]] || die "全局模式 IP 地址池已用完"
  done
}

install_global() {
  info "=== 安装 全局流量版 (wg1) ==="

  if [[ -f /etc/wireguard/wg1.conf && -f "$SERVER_CONF_GLOBAL" ]]; then
    warn "检测到已有全局模式 (wg1) 安装"
    menu_confirm "继续安装将覆盖全局配置（不影响 RDP wg0），是否继续?" "1" || { info "已取消"; return; }
  fi

  PUBLIC_IP=$(detect_public_ip)
  [[ -n "$PUBLIC_IP" ]] || die "无法自动检测公网IP，请手动设置 PUBLIC_IP 环境变量"
  ok "公网IP: $PUBLIC_IP"

  IFACE=$(detect_iface)
  ok "出口网卡: $IFACE"

  WG1_PORT=$(generate_random_port)
  ok "全局模式端口: $WG1_PORT (UDP, 随机非常用端口，避开 QoS 常用口)"

  local input_count client_count
  input_count=$(menu_input "请输入客户端数量" "1")
  client_count="${input_count:-1}"
  is_positive_integer "$client_count" || die "客户端数量必须是正整数"
  [[ "$client_count" -le 50 ]] || die "单次最多 50 个客户端"

  info "配置确认：公网IP=$PUBLIC_IP, 网卡=$IFACE, 端口=$WG1_PORT, 客户端=$client_count, 网段=$WG1_NET"
  menu_confirm "开始安装?" || { info "安装已取消"; return; }

  install_dependencies
  mkdir -p /etc/wireguard "$OUT_GLOBAL" "$KEY_DIR_GLOBAL"
  chmod 700 /etc/wireguard "$OUT_GLOBAL" "$KEY_DIR_GLOBAL"

  generate_global_server_keys
  write_global_forward_helper "$IFACE" "$WG1_NET"

  cat > /etc/wireguard/wg1.conf <<EOF
[Interface]
Address = ${WG1_SERVER_IP}/24
ListenPort = ${WG1_PORT}
PrivateKey = ${GSPRIV}
PostUp = /etc/wireguard/wg-global-forward.sh up
PostDown = /etc/wireguard/wg-global-forward.sh down
EOF
  chmod 600 /etc/wireguard/wg1.conf

  echo "# name ip" > "$GLOBAL_PEERS_FILE"

  cat > "$SERVER_CONF_GLOBAL" <<EOF
PUBLIC_IP=${PUBLIC_IP}
WG1_PORT=${WG1_PORT}
IFACE=${IFACE}
WG1_NET=${WG1_NET}
WG1_SERVER_IP=${WG1_SERVER_IP}
WG1_START_IP=${WG1_START_IP}
OUT_GLOBAL=${OUT_GLOBAL}
KEY_DIR_GLOBAL=${KEY_DIR_GLOBAL}
GLOBAL_DNS=${GLOBAL_DNS}
EOF
  chmod 600 "$SERVER_CONF_GLOBAL"

  local i name ip pub
  for i in $(seq 1 "$client_count"); do
    name="client${i}"
    ip=$(get_next_global_ip)
    pub=$(gen_global_client "$name" "$ip")
    add_peer_to_wg1 "$name" "$ip" "$pub"
    echo "${name} ${ip}" >> "$GLOBAL_PEERS_FILE"
  done

  ensure_ip_forward
  open_udp_port "$WG1_PORT"

  info "启动全局 WireGuard (wg1)..."
  wg-quick down wg1 2>/dev/null || true
  ip link del wg1 2>/dev/null || true
  cleanup_wg1_iptables "$IFACE" "$WG1_NET"
  sleep 1
  start_wg1
  systemctl enable wg-quick@wg1 2>/dev/null || true

  ok "=== 全局流量版安装完成 ==="
  echo "  服务器VPN地址: ${WG1_SERVER_IP}"
  echo "  Endpoint: ${PUBLIC_IP}:${WG1_PORT} (UDP)"
  echo "  模式: 全局转发（AllowedIPs=0.0.0.0/0）"
  echo "  客户端目录: ${OUT_GLOBAL}/"
  echo ""
  wg show wg1
}

add_global_peer() {
  if [[ ! -f /etc/wireguard/wg1.conf || ! -f "$SERVER_CONF_GLOBAL" ]]; then
    warn "全局模式尚未安装，请先选择 2. 安装全局流量版"
    return
  fi
  # shellcheck disable=SC1090
  source "$SERVER_CONF_GLOBAL"
  generate_global_server_keys
  PUBLIC_IP=$(detect_public_ip)

  local max_n=0 n name
  if [[ -f "$GLOBAL_PEERS_FILE" ]]; then
    while read -r line || [[ -n "$line" ]]; do
      [[ "$line" =~ ^# ]] && continue
      [[ -z "$line" ]] && continue
      read -r name _ <<< "$line"
      n="${name#client}"
      if [[ "$n" =~ ^[0-9]+$ ]] && [[ $n -gt $max_n ]]; then
        max_n=$n
      fi
    done < "$GLOBAL_PEERS_FILE"
  fi
  local new_n=$((max_n + 1))
  name="client${new_n}"
  local ip pub
  ip=$(get_next_global_ip)
  pub=$(gen_global_client "$name" "$ip")
  add_peer_to_wg1 "$name" "$ip" "$pub"
  echo "${name} ${ip}" >> "$GLOBAL_PEERS_FILE"
  wg set wg1 peer "$pub" allowed-ips "${ip}/32" 2>/dev/null || {
    wg-quick down wg1 2>/dev/null || true
    start_wg1
  }
  ok "已添加全局客户端 ${name} (${ip})"
}

delete_global_peer() {
  if [[ ! -f /etc/wireguard/wg1.conf || ! -f "$SERVER_CONF_GLOBAL" ]]; then
    warn "全局模式尚未安装"
    return
  fi
  # shellcheck disable=SC1090
  source "$SERVER_CONF_GLOBAL"
  [[ -f "$GLOBAL_PEERS_FILE" ]] || { warn "暂无全局客户端"; return; }

  local peers=() labels=()
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
    local pname pip
    read -r pname pip <<< "$line"
    peers+=("$line")
    labels+=("${pname} (${pip})")
  done < "$GLOBAL_PEERS_FILE"
  [[ ${#peers[@]} -gt 0 ]] || { warn "暂无全局客户端"; return; }

  local selected_label selected_index=0
  selected_label=$(menu_choose "${labels[@]}")
  [[ -z "$selected_label" ]] && { info "已取消"; return; }
  local i
  for i in "${!labels[@]}"; do
    [[ "${labels[$i]}" == "$selected_label" ]] && selected_index=$i && break
  done
  local selected="${peers[$selected_index]}"
  local name ip
  read -r name ip <<< "$selected"
  menu_confirm "确定删除 ${name}?" "1" || { info "已取消"; return; }

  local pub=""
  [[ -f "${KEY_DIR_GLOBAL}/${name}_public.key" ]] && pub=$(cat "${KEY_DIR_GLOBAL}/${name}_public.key")
  [[ -n "$pub" ]] && wg set wg1 peer "$pub" remove 2>/dev/null || true

  rm -f "${OUT_GLOBAL}/${name}.conf"
  rm -f "${KEY_DIR_GLOBAL}/${name}_private.key" "${KEY_DIR_GLOBAL}/${name}_public.key"
  sed -i "/^${name} /d" "$GLOBAL_PEERS_FILE"

  generate_global_server_keys
  write_global_forward_helper "${IFACE}" "${WG1_NET}"
  cat > /etc/wireguard/wg1.conf <<EOF
[Interface]
Address = ${WG1_SERVER_IP}/24
ListenPort = ${WG1_PORT}
PrivateKey = ${GSPRIV}
PostUp = /etc/wireguard/wg-global-forward.sh up
PostDown = /etc/wireguard/wg-global-forward.sh down
EOF
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^# ]] && continue
    [[ -z "$line" ]] && continue
    local pn pi pp
    read -r pn pi <<< "$line"
    [[ -f "${KEY_DIR_GLOBAL}/${pn}_public.key" ]] || continue
    pp=$(cat "${KEY_DIR_GLOBAL}/${pn}_public.key")
    add_peer_to_wg1 "$pn" "$pi" "$pp"
  done < "$GLOBAL_PEERS_FILE"

  wg-quick down wg1 2>/dev/null || true
  sleep 1
  start_wg1
  ok "已删除全局客户端 ${name}"
}

rdp_manage_menu() {
  local choice
  choice=$(menu_choose "1. 添加组" "2. 删除组" "3. 修复配置" "0. 返回")
  [[ -z "$choice" ]] && return
  choice="${choice%%.*}"
  case "$choice" in
    1) add_group ;;
    2) delete_group ;;
    3) repair_config ;;
    *) return ;;
  esac
}

global_manage_menu() {
  local choice
  choice=$(menu_choose "1. 添加客户端" "2. 删除客户端" "0. 返回")
  [[ -z "$choice" ]] && return
  choice="${choice%%.*}"
  case "$choice" in
    1) add_global_peer ;;
    2) delete_global_peer ;;
    *) return ;;
  esac
}


# =============================================================================
# 写死密钥与端口（方案B：中转↔落地自动对接；星链/出海客户端亦用固定密钥池）
# =============================================================================
WG_RELAY_PORT="${WG_RELAY_PORT:-38472}"
WG_EXIT_PORT="${WG_EXIT_PORT:-38473}"

WG2_NET="${WG2_NET:-10.66.66.0/24}"
WG2_SERVER_IP="${WG2_SERVER_IP:-10.66.66.1}"
WG2_LANDING_IP="${WG2_LANDING_IP:-10.66.66.254}"
WG2_CLIENT_START="${WG2_CLIENT_START:-10.66.66.2}"
OUT_RELAY="${OUT_RELAY:-/root/wg-relay-clients}"
KEY_DIR_RELAY="${KEY_DIR_RELAY:-/etc/wireguard/relay-keys}"
SERVER_CONF_RELAY="/etc/wireguard/server-relay.conf"
RELAY_PEERS_FILE="/etc/wireguard/relay-peers.conf"
CN_ROUTES_FILE="/etc/wireguard/cn-routes.txt"

WG3_NET="${WG3_NET:-10.55.55.0/24}"
WG3_SERVER_IP="${WG3_SERVER_IP:-10.55.55.1}"
WG3_LANDING_IP="${WG3_LANDING_IP:-10.55.55.2}"
SERVER_CONF_LANDING="/etc/wireguard/server-landing.conf"

WG4_NET="${WG4_NET:-10.44.44.0/24}"
WG4_SERVER_IP="${WG4_SERVER_IP:-10.44.44.1}"
WG4_CLIENT_START="${WG4_CLIENT_START:-10.44.44.2}"
OUT_EXIT="${OUT_EXIT:-/root/wg-exit-clients}"
KEY_DIR_EXIT="${KEY_DIR_EXIT:-/etc/wireguard/exit-keys}"
SERVER_CONF_EXIT="/etc/wireguard/server-exit.conf"
EXIT_PEERS_FILE="/etc/wireguard/exit-peers.conf"

DNS_CN="223.5.5.5"
DNS_JP="1.1.1.1"

# 固定密钥（Curve25519 / WireGuard）
FIXED_RELAY_PRIV="0GgMdFnsbZXaNYXgzmPojeObR69A3j+vXDq/FeL9gkM="
FIXED_RELAY_PUB="wyC8oQc3FnG+n0Wt/RSeIGmmXDw2Uzj1deld56YG6H8="
FIXED_LANDING_PRIV="OK/hWqDOI04Rg+8Lk0eSG6kHpyLysuogZeB86D5I50U="
FIXED_LANDING_PUB="3lTN7e90pfCfWxatUQLXGJ57+/jmVOsginaMYynYzhc="
FIXED_EXIT_PRIV="kDICx8ojKNRUtytlGqtLTVLuE7StHsWwt5cYvsc75ns="
FIXED_EXIT_PUB="pEapqkLC2pMJUzGsXAgVtUEGfc/HSKzpuB3h5qipVnQ="
FIXED_RELAY_LANDING_PSK="lm/FgzF4IWMFu5NgvbGu5IDqp+mkGxJhnVn1fvPcQD8="

# 星链中转客户端固定密钥池 client1..5
FIXED_RELAY_CLIENT_PRIV=(
  "aK/aSrG3P00x0uvHlzDxP4sJG7QSHetgzsQQPVfOEFc="
  "WBjkktkyF7VNdQw7cLEWj1xp9joOd7D7CtIP0j95SmQ="
  "kI+M1rDzQUk5mi0a33QHo2ULivMpHVAK3ud58v0ihEw="
  "iGNkM6Vbl3vsKMVzsrc2kE4j9pUd+TvKYmHGsrhuEHU="
  "8K2JB+/dCB0WcrM815Ugo2pt8hCzbrrBL6jgJv0wBk8="
)
FIXED_RELAY_CLIENT_PUB=(
  "wF9tMoTth/gHsQVSUF0EeCHY4qol6oMkT/pb6OmZ1gk="
  "kPivn5y5Kkiq/G/qJ/YAuaVExN3tGSmm7w6m3KGiXkE="
  "OnY1FEAxcldoq75X8uHaNXL4SJxn3Hh5B50aJRTg51I="
  "v3HCFmVy5YnCkFQW1IAsLVs13XiJ+d/GPiR5jFfi504="
  "YH/ALcWj197fMuQZVowfSrSPRdGAYqj1EeZ7gDpf1z4="
)

# 出海客户端固定密钥池 exit1..5（与中转池分离）
FIXED_EXIT_CLIENT_PRIV=(
  "2FcZb6cHZ294gucC2hSe2BR0RFs1Mn18K+h1F/bMPHc="
  "uJ5KIguf1Ct7d/82OPTs0DRx6MK08N9PlRwsPTR7aHI="
  "iIqyANIBBjxSoL8Jz0H/zyT2NtkEEzcbsrbDVEqcHFY="
  "CBJZ0qOdl2oJKHZJprS7BdK1KWRY4ZYC5Eq30+m4Bkk="
  "uOwxpenqCYZlbOY9R69vc0YzsKEXooGkzHgTwTeeyUE="
)
FIXED_EXIT_CLIENT_PUB=(
  "Vh4u/ap/BerRDQGTyfVmKh4fiCYirGADZUAiSYnhzQ4="
  "dk3EVF+Tv6tmx4K8bRoF2CZMRBk3mgDbhhJPMbG/SC4="
  "p/JnHkN8kZprHkqfDjfvNTQN4AB45kMFT2NoGRo64Vs="
  "YFUYVnmMXYE8ovGn8905mHFY8w9O2o/UvjWgvC0sJX4="
  "QLHy9T6aSpM5CTDF47gL2zZirGqCQYd7iM8XIh3ujTE="
)

start_wg2() { wg_kernel_ready || die "WireGuard 内核模块未就绪"; wg-quick up wg2; }
start_wg3() { wg_kernel_ready || die "WireGuard 内核模块未就绪"; wg-quick up wg3; }
start_wg4() { wg_kernel_ready || die "WireGuard 内核模块未就绪"; wg-quick up wg4; }

notes_file_for() {
  case "$1" in
    rdp) echo "/etc/wireguard/notes-rdp.tsv" ;;
    global) echo "/etc/wireguard/notes-global.tsv" ;;
    relay) echo "/etc/wireguard/notes-relay.tsv" ;;
    landing) echo "/etc/wireguard/notes-landing.tsv" ;;
    exit) echo "/etc/wireguard/notes-exit.tsv" ;;
    *) echo "/etc/wireguard/notes-misc.tsv" ;;
  esac
}

note_add() {
  local mode="$1" id="$2" remark="$3" path="${4:-}"
  local f
  f=$(notes_file_for "$mode")
  mkdir -p /etc/wireguard
  touch "$f"
  # 同 id 则更新备注
  if grep -qE "^${id}\t" "$f" 2>/dev/null; then
    awk -F '\t' -v id="$id" -v ts="$(date +%s)" -v r="$remark" -v p="$path" 'BEGIN{OFS="\t"} {
      if ($1==id) print id, ts, r, (p!=""?p:$4);
      else print $0
    }' "$f" > "${f}.tmp" && mv -f "${f}.tmp" "$f"
  else
    printf '%s\t%s\t%s\t%s\n' "$id" "$(date +%s)" "$remark" "$path" >> "$f"
  fi
}

note_list() {
  local mode="$1" f
  f=$(notes_file_for "$mode")
  [[ -f "$f" ]] || { echo -e "  ${DIM}(无备注)${NC}"; return 0; }
  awk -F '\t' -v g="\033[1;32m" -v a="\033[1;36m" -v b="\033[1;34m" -v n="\033[0m" 'NF>=3 {
    i++;
    c = (i % 2 ? a : b);
    printf "  %s%2d)%s %s%s | %s | %s%s\n", g, i, n, c, $1, $3, $4, n
  }' "$f"
}

note_get_id_by_index() {
  local mode="$1" idx="$2" f
  f=$(notes_file_for "$mode")
  [[ -f "$f" ]] || return 1
  awk -F '\t' -v want="$idx" 'NF>=3 { i++; if (i==want) { print $1; exit } }' "$f"
}

note_delete_by_index() {
  local mode="$1" idx="$2" id f
  id=$(note_get_id_by_index "$mode" "$idx") || return 1
  [[ -n "$id" ]] || return 1
  f=$(notes_file_for "$mode")
  awk -F '\t' -v id="$id" 'NF>=1 && $1!=id { print $0 }' "$f" > "${f}.tmp" && mv -f "${f}.tmp" "$f"
}

note_update_remark_by_index() {
  local mode="$1" idx="$2" remark="$3" id f
  id=$(note_get_id_by_index "$mode" "$idx") || return 1
  [[ -n "$id" ]] || return 1
  f=$(notes_file_for "$mode")
  awk -F '\t' -v id="$id" -v ts="$(date +%s)" -v r="$remark" 'BEGIN{OFS="\t"} {
    if ($1==id) print $1, ts, r, $4;
    else print $0
  }' "$f" > "${f}.tmp" && mv -f "${f}.tmp" "$f"
}

ensure_cn_routes() {
  mkdir -p /etc/wireguard
  if [[ -f "$CN_ROUTES_FILE" ]] && [[ $(wc -l < "$CN_ROUTES_FILE") -gt 100 ]]; then
    ok "使用已缓存中国路由: $CN_ROUTES_FILE ($(wc -l < "$CN_ROUTES_FILE") 条)"
    return 0
  fi
  info "下载中国 IP 路由表..."
  local urls=(
    "https://raw.githubusercontent.com/fernvenue/chn-cidr-list/master/CN-ip-cidr.txt"
    "https://ispip.clang.cn/all_cn.txt"
    "https://cdn.jsdelivr.net/gh/fernvenue/chn-cidr-list@master/CN-ip-cidr.txt"
  )
  local u okdl=0
  for u in "${urls[@]}"; do
    if curl -fsSL --connect-timeout 8 --max-time 60 "$u" 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+' > "${CN_ROUTES_FILE}.tmp"; then
      if [[ $(wc -l < "${CN_ROUTES_FILE}.tmp") -gt 100 ]]; then
        mv -f "${CN_ROUTES_FILE}.tmp" "$CN_ROUTES_FILE"
        okdl=1
        break
      fi
    fi
  done
  [[ "$okdl" == "1" ]] || die "中国路由表下载失败，请检查网络后重试"
  ok "中国路由已缓存: $(wc -l < "$CN_ROUTES_FILE") 条"
}

# 将 CN 路由格式化为 WireGuard AllowedIPs（过长则截断并警告）
cn_routes_as_allowed_ips() {
  ensure_cn_routes
  local max="${1:-3500}"
  local lines total
  total=$(wc -l < "$CN_ROUTES_FILE")
  if [[ "$total" -gt "$max" ]]; then
    warn "CN 路由 ${total} 条，客户端 AllowedIPs 仅写入前 ${max} 条（大列表可能不被部分客户端接受）"
  fi
  head -n "$max" "$CN_ROUTES_FILE" | tr '\n' ',' | sed 's/,$/\n/'
}

jp_allowed_ips() {
  echo "0.0.0.0/0"
}

exit_overseas_allowed_ips() {
  # 补集计算过重；出海默认全量经隧道（国内站也会走代理），保证「海外都能代理」
  warn "出海客户端 AllowedIPs=0.0.0.0/1, 128.0.0.0/1（等效全局；国内直连需自行在系统排除）"
  echo "0.0.0.0/1, 128.0.0.0/1"
}

write_relay_forward_helper() {
  local profile="$1"
  cat > /etc/wireguard/wg-relay-forward.sh <<EOS
#!/bin/bash
# 中转转发：星链客户端 <-> 落地，不在中转做上网 NAT
set -e
ACTION="\${1:-up}"
PROFILE="${profile}"

clear_rules() {
  while iptables -C FORWARD -i wg2 -o wg2 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg2 -o wg2 -j ACCEPT; done
  while iptables -C FORWARD -i wg2 ! -o wg2 -j DROP 2>/dev/null; do iptables -D FORWARD -i wg2 ! -o wg2 -j DROP; done
  while iptables -C FORWARD ! -i wg2 -o wg2 -j DROP 2>/dev/null; do iptables -D FORWARD ! -i wg2 -o wg2 -j DROP; done
}

apply_rules() {
  clear_rules
  iptables -I FORWARD 1 -i wg2 -o wg2 -j ACCEPT
  # 禁止中转本机把隧道流量直接送公网（必须经落地）
  iptables -A FORWARD -i wg2 ! -o wg2 -j DROP
  iptables -A FORWARD ! -i wg2 -o wg2 -j DROP
}

case "\$ACTION" in
  up) apply_rules ;;
  down) clear_rules ;;
  *) exit 1 ;;
esac
EOS
  chmod 700 /etc/wireguard/wg-relay-forward.sh
}

write_landing_forward_helper() {
  local iface="$1" net="$2"
  cat > /etc/wireguard/wg-landing-forward.sh <<EOS
#!/bin/bash
set -e
ACTION="\${1:-up}"
IFACE="${iface}"
WG_NET="${net}"
clear_rules() {
  while iptables -C FORWARD -i wg3 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg3 -j ACCEPT; done
  while iptables -C FORWARD -o wg3 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -o wg3 -j ACCEPT; done
  while iptables -t nat -C POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE 2>/dev/null; do
    iptables -t nat -D POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE
  done
  # 亦清理中转网段经落地出网
  while iptables -t nat -C POSTROUTING -s "${WG2_NET}" -o "\$IFACE" -j MASQUERADE 2>/dev/null; do
    iptables -t nat -D POSTROUTING -s "${WG2_NET}" -o "\$IFACE" -j MASQUERADE
  done
}
apply_rules() {
  clear_rules
  iptables -I FORWARD 1 -i wg3 -j ACCEPT
  iptables -I FORWARD 1 -o wg3 -j ACCEPT
  iptables -t nat -A POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE
  iptables -t nat -A POSTROUTING -s "${WG2_NET}" -o "\$IFACE" -j MASQUERADE
}
case "\$ACTION" in
  up) apply_rules ;;
  down) clear_rules ;;
  *) exit 1 ;;
esac
EOS
  chmod 700 /etc/wireguard/wg-landing-forward.sh
}

write_exit_forward_helper() {
  local iface="$1" net="$2"
  cat > /etc/wireguard/wg-exit-forward.sh <<EOS
#!/bin/bash
set -e
ACTION="\${1:-up}"
IFACE="${iface}"
WG_NET="${net}"
clear_rules() {
  while iptables -C FORWARD -i wg4 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -i wg4 -j ACCEPT; done
  while iptables -C FORWARD -o wg4 -j ACCEPT 2>/dev/null; do iptables -D FORWARD -o wg4 -j ACCEPT; done
  while iptables -t nat -C POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE 2>/dev/null; do
    iptables -t nat -D POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE
  done
}
apply_rules() {
  clear_rules
  iptables -I FORWARD 1 -i wg4 -j ACCEPT
  iptables -I FORWARD 1 -o wg4 -j ACCEPT
  iptables -t nat -A POSTROUTING -s "\$WG_NET" -o "\$IFACE" -j MASQUERADE
}
case "\$ACTION" in
  up) apply_rules ;;
  down) clear_rules ;;
  *) exit 1 ;;
esac
EOS
  chmod 700 /etc/wireguard/wg-exit-forward.sh
}

gen_relay_client_conf() {
  local idx="$1" profile="$2" pub_ip="$3"
  local name="client${idx}"
  local ip priv pub allowed dns conf
  local octet=$((idx + 1))  # client1 -> .2
  ip="10.66.66.${octet}"
  priv="${FIXED_RELAY_CLIENT_PRIV[$((idx - 1))]}"
  pub="${FIXED_RELAY_CLIENT_PUB[$((idx - 1))]}"
  if [[ "$profile" == "cn" ]]; then
    allowed=$(cn_routes_as_allowed_ips)
    dns="$DNS_CN"
  else
    allowed=$(jp_allowed_ips)
    dns="$DNS_JP"
  fi
  conf="${OUT_RELAY}/${name}.conf"
  mkdir -p "$OUT_RELAY" "$KEY_DIR_RELAY"
  echo "$priv" > "${KEY_DIR_RELAY}/${name}_private.key"
  echo "$pub" > "${KEY_DIR_RELAY}/${name}_public.key"
  cat > "$conf" <<EOF
[Interface]
PrivateKey = ${priv}
Address = ${ip}/32
DNS = ${dns}
# 星链 → 中转分流（profile=${profile}）

[Peer]
PublicKey = ${FIXED_RELAY_PUB}
PresharedKey = ${FIXED_RELAY_LANDING_PSK}
Endpoint = ${pub_ip}:${WG_RELAY_PORT}
AllowedIPs = ${allowed}
PersistentKeepalive = 25
EOF
  chmod 600 "$conf" "${KEY_DIR_RELAY}/${name}_private.key"
  echo "${name} ${ip} ${pub}" 
}

install_relay() {
  info "=== 安装 中转站 (wg2) — 星链 → 港/新 ==="
  info "提示: 香港线路易被干扰，中转优先新加坡；IP 被墙需换机，不是脚本故障"

  if [[ -f /etc/wireguard/wg2.conf && -f "$SERVER_CONF_RELAY" ]]; then
    warn "检测到已有中转配置"
    menu_confirm "覆盖中转配置（不影响其它模式）?" "1" || { info "已取消"; return; }
  fi

  PUBLIC_IP=$(detect_public_ip)
  [[ -n "$PUBLIC_IP" ]] || die "无法检测公网 IP"
  IFACE=$(detect_iface)
  ok "公网IP=$PUBLIC_IP 网卡=$IFACE 端口=${WG_RELAY_PORT}"

  local profile
  profile=$(choose_traffic_profile)
  [[ -n "$profile" ]] || { info "已返回"; return; }

  local input_count client_count
  input_count=$(menu_input "星链客户端数量(1-5，固定密钥池)" "1")
  client_count="${input_count:-1}"
  is_positive_integer "$client_count" || die "数量无效"
  [[ "$client_count" -le 5 ]] || die "固定密钥池最多 5 个客户端"

  local remark
  remark=$(menu_input "中转备注(支持中文，必填)" "")
  [[ -n "$remark" ]] || die "备注不能为空"

  local profile_name="出海方向"
  [[ "$profile" == "cn" ]] && profile_name="回国方向"
  menu_confirm "开始安装中转（${profile_name}）?" || return

  install_dependencies
  [[ "$profile" == "cn" ]] && ensure_cn_routes

  mkdir -p /etc/wireguard "$OUT_RELAY" "$KEY_DIR_RELAY"
  write_relay_forward_helper "$profile"

  # CN/海外网段仍写入落地 Peer AllowedIPs（内核 cryptokey 转发需要），
  # 但 Table=off 禁止 wg-quick 往系统路由表插入上千条路由（否则 up 极慢/卡死）。
  local landing_allowed
  if [[ "$profile" == "cn" ]]; then
    landing_allowed=$(cn_routes_as_allowed_ips)
  else
    landing_allowed="0.0.0.0/0"
  fi

  cat > /etc/wireguard/wg2.conf <<EOF
[Interface]
Address = ${WG2_SERVER_IP}/24
ListenPort = ${WG_RELAY_PORT}
PrivateKey = ${FIXED_RELAY_PRIV}
Table = off
PostUp = /etc/wireguard/wg-relay-forward.sh up
PostDown = /etc/wireguard/wg-relay-forward.sh down

# 固定落地节点
[Peer]
# landing
PublicKey = ${FIXED_LANDING_PUB}
PresharedKey = ${FIXED_RELAY_LANDING_PSK}
AllowedIPs = ${WG2_LANDING_IP}/32, ${landing_allowed}
EOF

  echo "# name ip pubkey" > "$RELAY_PEERS_FILE"
  local i line name ip pub
  for i in $(seq 1 "$client_count"); do
    line=$(gen_relay_client_conf "$i" "$profile" "$PUBLIC_IP")
    read -r name ip pub <<< "$line"
    cat >> /etc/wireguard/wg2.conf <<EOF

[Peer]
# ${name}
PublicKey = ${pub}
PresharedKey = ${FIXED_RELAY_LANDING_PSK}
AllowedIPs = ${ip}/32
EOF
    echo "${name} ${ip} ${pub}" >> "$RELAY_PEERS_FILE"
    note_add "relay" "$name" "$remark" "${OUT_RELAY}/${name}.conf"
    ok "已生成 ${OUT_RELAY}/${name}.conf"
  done

  cat > "$SERVER_CONF_RELAY" <<EOF
PUBLIC_IP=${PUBLIC_IP}
IFACE=${IFACE}
WG_RELAY_PORT=${WG_RELAY_PORT}
PROFILE=${profile}
WG2_NET=${WG2_NET}
WG2_SERVER_IP=${WG2_SERVER_IP}
WG2_LANDING_IP=${WG2_LANDING_IP}
OUT_RELAY=${OUT_RELAY}
REMARK=${remark}
EOF
  chmod 600 /etc/wireguard/wg2.conf "$SERVER_CONF_RELAY"
  note_add "relay" "relay-server" "$remark" "/etc/wireguard/wg2.conf"

  ensure_ip_forward
  open_udp_port "$WG_RELAY_PORT"
  wg-quick down wg2 2>/dev/null || true
  ip link del wg2 2>/dev/null || true
  sleep 1
  start_wg2
  systemctl enable wg-quick@wg2 2>/dev/null || true

  ok "=== 中转站安装完成 ==="
  echo "  Endpoint: ${PUBLIC_IP}:${WG_RELAY_PORT}"
  echo "  方向: ${profile_name} (PROFILE=${profile})  — 落地机选项4须选同一方向"
  echo "  落地隧道 IP: ${WG2_LANDING_IP}"
  echo "  客户端目录: ${OUT_RELAY}/"
  echo "  下一步: 到落地机运行选项4，输入本机公网IP自动对接"
  wg show wg2
}

install_landing() {
  info "=== 安装 终点站/落地 (wg3) — 日本或国内 ==="

  if [[ -f /etc/wireguard/wg3.conf && -f "$SERVER_CONF_LANDING" ]]; then
    warn "检测到已有落地配置"
    menu_confirm "覆盖落地配置?" "1" || return
  fi

  local upstream port_in profile remark
  upstream=$(menu_input "上一级中转公网IP" "")
  [[ -n "$upstream" ]] || die "中转 IP 不能为空"
  upstream=$(echo "$upstream" | sed -e 's|^https\?://||' -e 's|/.*||' -e 's|:.*||' | tr -d '[:space:]')

  port_in=$(menu_input "中转 UDP 端口" "$WG_RELAY_PORT")
  WG_RELAY_PORT="${port_in:-$WG_RELAY_PORT}"

  local profile
  profile=$(choose_traffic_profile)
  [[ -n "$profile" ]] || { info "已返回"; return; }

  remark=$(menu_input "落地备注(支持中文，必填)" "")
  [[ -n "$remark" ]] || die "备注不能为空"

  IFACE=$(detect_iface)
  PUBLIC_IP=$(detect_public_ip)
  local profile_name="出海方向"
  [[ "$profile" == "cn" ]] && profile_name="回国方向"
  ok "上级=${upstream}:${WG_RELAY_PORT} 本机出网卡=${IFACE} ${profile_name}"
  menu_confirm "开始安装落地（${profile_name}）?" || return

  install_dependencies
  mkdir -p /etc/wireguard
  write_landing_forward_helper "$IFACE" "$WG3_NET"

  # 落地作为客户端连中转，同时用 wg3 地址 10.66.66.254 以匹配中转 AllowedIPs
  cat > /etc/wireguard/wg3.conf <<EOF
[Interface]
Address = ${WG2_LANDING_IP}/32
PrivateKey = ${FIXED_LANDING_PRIV}
PostUp = /etc/wireguard/wg-landing-forward.sh up
PostDown = /etc/wireguard/wg-landing-forward.sh down

[Peer]
PublicKey = ${FIXED_RELAY_PUB}
PresharedKey = ${FIXED_RELAY_LANDING_PSK}
Endpoint = ${upstream}:${WG_RELAY_PORT}
AllowedIPs = ${WG2_NET}
PersistentKeepalive = 25
EOF
  chmod 600 /etc/wireguard/wg3.conf

  cat > "$SERVER_CONF_LANDING" <<EOF
UPSTREAM=${upstream}
WG_RELAY_PORT=${WG_RELAY_PORT}
PROFILE=${profile}
IFACE=${IFACE}
PUBLIC_IP=${PUBLIC_IP:-}
WG2_LANDING_IP=${WG2_LANDING_IP}
REMARK=${remark}
EOF
  note_add "landing" "landing" "$remark" "/etc/wireguard/wg3.conf"

  ensure_ip_forward
  wg-quick down wg3 2>/dev/null || true
  ip link del wg3 2>/dev/null || true
  sleep 1
  start_wg3
  systemctl enable wg-quick@wg3 2>/dev/null || true

  ok "=== 落地安装完成 ==="
  echo "  已连接中转: ${upstream}:${WG_RELAY_PORT}"
  echo "  PROFILE: ${profile} （须与中转选项3一致）"
  echo "  本机隧道 IP: ${WG2_LANDING_IP}"
  if [[ "$profile" == "cn" ]]; then
    echo "  出口 DNS 建议客户端: ${DNS_CN}"
  else
    echo "  出口 DNS 建议客户端: ${DNS_JP}"
  fi
  wg show wg3
}

gen_exit_client_conf() {
  local idx="$1" pub_ip="$2"
  local name="exit${idx}"
  local ip priv pub allowed conf
  local octet=$((idx + 1))
  ip="10.44.44.${octet}"
  priv="${FIXED_EXIT_CLIENT_PRIV[$((idx - 1))]}"
  pub="${FIXED_EXIT_CLIENT_PUB[$((idx - 1))]}"
  allowed=$(exit_overseas_allowed_ips)
  conf="${OUT_EXIT}/${name}.conf"
  mkdir -p "$OUT_EXIT" "$KEY_DIR_EXIT"
  echo "$priv" > "${KEY_DIR_EXIT}/${name}_private.key"
  echo "$pub" > "${KEY_DIR_EXIT}/${name}_public.key"
  cat > "$conf" <<EOF
[Interface]
PrivateKey = ${priv}
Address = ${ip}/32
DNS = ${DNS_JP}
# 国内出海分流

[Peer]
PublicKey = ${FIXED_EXIT_PUB}
PresharedKey = ${FIXED_RELAY_LANDING_PSK}
Endpoint = ${pub_ip}:${WG_EXIT_PORT}
AllowedIPs = ${allowed}
PersistentKeepalive = 25
EOF
  chmod 600 "$conf"
  echo "${name} ${ip} ${pub}"
}

install_exit() {
  info "=== 安装 出海 (wg4) — 国内 → 港/日，无再中转 ==="
  warn "建议装在香港或日本。香港易被墙，优先日本/新加坡同类出口。"

  if [[ -f /etc/wireguard/wg4.conf && -f "$SERVER_CONF_EXIT" ]]; then
    menu_confirm "覆盖出海配置?" "1" || return
  fi

  PUBLIC_IP=$(detect_public_ip)
  [[ -n "$PUBLIC_IP" ]] || die "无法检测公网 IP"
  IFACE=$(detect_iface)

  local input_count client_count remark
  input_count=$(menu_input "客户端数量(1-5)" "1")
  client_count="${input_count:-1}"
  is_positive_integer "$client_count" || die "数量无效"
  [[ "$client_count" -le 5 ]] || die "最多 5 个"
  remark=$(menu_input "出海备注(支持中文，必填)" "")
  [[ -n "$remark" ]] || die "备注不能为空"

  menu_confirm "开始安装出海 ${PUBLIC_IP}:${WG_EXIT_PORT}?" || return
  install_dependencies
  mkdir -p /etc/wireguard "$OUT_EXIT" "$KEY_DIR_EXIT"
  write_exit_forward_helper "$IFACE" "$WG4_NET"

  cat > /etc/wireguard/wg4.conf <<EOF
[Interface]
Address = ${WG4_SERVER_IP}/24
ListenPort = ${WG_EXIT_PORT}
PrivateKey = ${FIXED_EXIT_PRIV}
PostUp = /etc/wireguard/wg-exit-forward.sh up
PostDown = /etc/wireguard/wg-exit-forward.sh down
EOF

  echo "# name ip pubkey" > "$EXIT_PEERS_FILE"
  local i line name ip pub
  for i in $(seq 1 "$client_count"); do
    line=$(gen_exit_client_conf "$i" "$PUBLIC_IP")
    read -r name ip pub <<< "$line"
    cat >> /etc/wireguard/wg4.conf <<EOF

[Peer]
# ${name}
PublicKey = ${pub}
PresharedKey = ${FIXED_RELAY_LANDING_PSK}
AllowedIPs = ${ip}/32
EOF
    echo "${name} ${ip} ${pub}" >> "$EXIT_PEERS_FILE"
    note_add "exit" "$name" "$remark" "${OUT_EXIT}/${name}.conf"
    ok "已生成 ${OUT_EXIT}/${name}.conf"
  done

  cat > "$SERVER_CONF_EXIT" <<EOF
PUBLIC_IP=${PUBLIC_IP}
IFACE=${IFACE}
WG_EXIT_PORT=${WG_EXIT_PORT}
WG4_NET=${WG4_NET}
WG4_SERVER_IP=${WG4_SERVER_IP}
OUT_EXIT=${OUT_EXIT}
REMARK=${remark}
EOF
  chmod 600 /etc/wireguard/wg4.conf "$SERVER_CONF_EXIT"
  note_add "exit" "exit-server" "$remark" "/etc/wireguard/wg4.conf"

  ensure_ip_forward
  open_udp_port "$WG_EXIT_PORT"
  wg-quick down wg4 2>/dev/null || true
  ip link del wg4 2>/dev/null || true
  sleep 1
  start_wg4
  systemctl enable wg-quick@wg4 2>/dev/null || true

  ok "=== 出海部署完成 ==="
  echo "  Endpoint: ${PUBLIC_IP}:${WG_EXIT_PORT}"
  echo "  客户端配置存放于: ${OUT_EXIT}/"
  wg show wg4
}

# ======================== 备注与列表管理 (选项 6) ========================
manage_notes_menu() {
  show_title "查看管理与备注"
  view_status

  echo ""
  echo -e "${C_TITLE}--- 备注管理分类 ---${NC}"
  local sub_choice
  sub_choice=$(menu_choose "1. RDP加速 (rdp)" "2. 全局流量 (global)" "3. 中转站 (relay)" "4. 落地站 (landing)" "5. 出海站 (exit)" "0. 返回")
  [[ -z "$sub_choice" ]] && return
  sub_choice="${sub_choice%%.*}"

  local mode=""
  case "$sub_choice" in
    1) mode="rdp" ;;
    2) mode="global" ;;
    3) mode="relay" ;;
    4) mode="landing" ;;
    5) mode="exit" ;;
    *) return ;;
  esac

  echo ""
  show_title "模式 [${mode}] 当前备注列表"
  note_list "$mode"

  echo ""
  local op
  op=$(menu_choose "1. 修改备注" "2. 删除备注" "3. 手动新增/追加备注" "0. 返回")
  [[ -z "$op" ]] && return
  op="${op%%.*}"

  case "$op" in
    1)
      local idx r
      idx=$(menu_input "请输入要修改的序号" "")
      [[ -n "$idx" ]] || return
      r=$(menu_input "请输入新备注" "")
      [[ -n "$r" ]] || return
      note_update_remark_by_index "$mode" "$idx" "$r" && ok "备注已更新"
      ;;
    2)
      local idx
      idx=$(menu_input "请输入要删除的序号" "")
      [[ -n "$idx" ]] || return
      note_delete_by_index "$mode" "$idx" && ok "备注已删除"
      ;;
    3)
      local id r p
      id=$(menu_input "请输入标识(如 client1 / group1)" "")
      [[ -n "$id" ]] || return
      r=$(menu_input "请输入备注说明" "")
      [[ -n "$r" ]] || return
      p=$(menu_input "配置文件路径(可选)" "")
      note_add "$mode" "$id" "$r" "$p" && ok "备注添加成功"
      ;;
    *) return ;;
  esac
}

# ======================== 全量卸载逻辑 (选项 7) ========================
uninstall_all() {
  show_title "卸载 WireGuard 部署"
  warn "此操作将彻底停止所有 WG 接口，清除 iptables 规则，并删除配置与密钥目录！"
  menu_confirm "确定全量卸载 WireGuard 部署?" "1" || { info "已取消卸载"; return; }

  info "正在停止并禁用 WireGuard 服务..."
  for dev in wg0 wg1 wg2 wg3 wg4; do
    systemctl disable --now wg-quick@${dev} 2>/dev/null || true
    wg-quick down ${dev} 2>/dev/null || true
    ip link del ${dev} 2>/dev/null || true
  done

  info "正在清理 iptables 转发与 NAT 规则..."
  cleanup_wg_iptables ""
  cleanup_wg1_iptables "" ""
  /etc/wireguard/wg-relay-forward.sh down 2>/dev/null || true
  /etc/wireguard/wg-landing-forward.sh down 2>/dev/null || true
  /etc/wireguard/wg-exit-forward.sh down 2>/dev/null || true

  # 关闭已开放的固定端口
  close_udp_port "$WG_RELAY_PORT"
  close_udp_port "$WG_EXIT_PORT"
  if [[ -f "$SERVER_CONF_RDP" ]]; then
    local p
    p=$(grep -E '^WG_PORT=' "$SERVER_CONF_RDP" 2>/dev/null | cut -d= -f2)
    [[ -n "$p" ]] && close_udp_port "$p"
  fi
  if [[ -f "$SERVER_CONF_GLOBAL" ]]; then
    local p
    p=$(grep -E '^WG1_PORT=' "$SERVER_CONF_GLOBAL" 2>/dev/null | cut -d= -f2)
    [[ -n "$p" ]] && close_udp_port "$p"
  fi

  info "清理配置文件与生成的客户端数据..."
  rm -rf /etc/wireguard
  rm -rf "$OUT" "$KEY_DIR"
  rm -rf "$OUT_GLOBAL" "$KEY_DIR_GLOBAL"
  rm -rf "$OUT_RELAY" "$KEY_DIR_RELAY"
  rm -rf "$OUT_EXIT" "$KEY_DIR_EXIT"

  ok "WireGuard 综合部署已全量清理完毕！"
}

# ======================== 主菜单入口 ========================
main_menu() {
  while true; do
    echo -e "${C_BORDER}┌─────────────────────────────────────────────────────────────┐${NC}"
    echo -e "${C_BORDER}│               WireGuard 综合一键部署控制台                 │${NC}"
    echo -e "${C_BORDER}└─────────────────────────────────────────────────────────────┘${NC}"
    echo -e "${C_TITLE}请选择需要执行的操作：${NC}"
    
    local choice
    choice=$(menu_choose \
      "1. RDP加速模式 (wg0 组内互通/隔离)" \
      "2. 全局翻墙模式 (wg1 全局代理)" \
      "3. 中转站部署 (wg2 星链/跨境中转)" \
      "4. 终点站部署 (wg3 落地出口节点)" \
      "5. 直连出海部署 (wg4 国内直连海外)" \
      "6. 查看管理状态与备注信息" \
      "7. 卸载 WireGuard 综合环境" \
      "0. 退出脚本")

    [[ -z "$choice" ]] && exit 0
    choice="${choice%%.*}"

    case "$choice" in
      1)
        if [[ -f /etc/wireguard/wg0.conf ]]; then
          rdp_manage_menu
        else
          install_server
        fi
        ;;
      2)
        if [[ -f /etc/wireguard/wg1.conf ]]; then
          global_manage_menu
        else
          install_global
        fi
        ;;
      3) install_relay ;;
      4) install_landing ;;
      5) install_exit ;;
      6) manage_notes_menu ;;
      7) uninstall_all ;;
      0) exit 0 ;;
      *) warn "无效选项，请重新选择" ;;
    esac
    echo ""
  done
}

# 执行主流程
main_menu