#!/bin/bash
set -o pipefail
umask 077

readonly R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[1;33m' B=$'\033[0;34m'
readonly P=$'\033[0;35m' C=$'\033[0;36m' N=$'\033[0m'
readonly PSK="hm123456" DNS_CN="223.5.5.5" DNS_OW="8.8.8.8"
readonly POOL_NET="192.168.18"

abort() { echo -e "${R}${1}${N}" 1>&2; exit 1; }
info()  { echo -e "${G}${1}${N}"; }
warn()  { echo -e "${Y}${1}${N}"; }

[[ $EUID -eq 0 ]] || abort "必须使用 root 账号运行！"

setup_terminal() {
  if [[ -t 0 ]]; then
    command -v stty &>/dev/null  && stty erase '^?' 2>/dev/null || true
    command -v bind &>/dev/null  && {
      bind '"\C-h": backward-delete-char' 2>/dev/null
      bind '"\e[3~": delete-char' 2>/dev/null
    } || true
  fi
}

read_input() {
  local p="$1" v="$2" sp="$1"
  if [[ -t 0 ]]; then
    sp=$(printf '%s' "$p" | sed $'s/\033\\[[0-9;]*m/\001&\002/g')
    IFS= read -e -r -p "$sp" "$v"
  else
    IFS= read -r -p "$p" "$v"
  fi
}

# ─── 网络检测 ──────────────────────────────────────

is_public_ipv4() {
  local ip=$1 a b c d
  [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
  IFS=. read -r a b c d <<< "$ip"
  for i in a b c d; do ((10#${!i} <= 255)) || return 1; done
  ((a==0||a==10||a==127||a>=224||a==100&&b>=64&&b<=127||a==169&&b==254||a==172&&b>=16&&b<=31||a==192&&b==168||a==198&&(b==18||b==19))) && return 1
  return 0
}

get_public_ip() {
  IP=""
  local u r c
  for u in "https://myip.ipip.net" "https://api.ipify.org" "https://ipv4.icanhazip.com" "https://ifconfig.me/ip"; do
    r=$(curl -4fsS --connect-timeout 5 --max-time 10 "$u" 2>/dev/null) || r=$(wget -4qO- --timeout=10 "$u" 2>/dev/null) || r=""
    c=$(printf '%s' "$r" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1)
    is_public_ipv4 "$c" && { IP="$c"; return 0; }
  done
  c=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '/src/ {for(i=1;i<=NF;i++) if($i=="src") {print $(i+1); exit}}')
  is_public_ipv4 "$c" && IP="$c"
}

is_mainland_china() {
  local country
  for u in "https://ipapi.co/country/" "https://ifconfig.co/country-iso"; do
    country=$(curl -4fsS --connect-timeout 3 --max-time 6 "$u" 2>/dev/null || true)
    country=$(printf '%s' "$country" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')
    [[ $country == "CN" ]] && return 0
    [[ $country =~ ^[A-Z]{2}$ ]] && return 1
  done
  return 1
}

# ─── 内核 / 系统检测 ─────────────────────────────────

get_os_info() {
  OS_ID=""
  . /etc/os-release  && { OS=$NAME; OS_ID=${ID:-}; VERSION_ID=${VERSION_ID:-unknown}; } || {
    [[ -f /etc/redhat-release ]] && { OS="CentOS"; VERSION_ID=$(grep -oE '[0-9]+\.' /etc/redhat-release | cut -d. -f1 | head -1); }
    [[ -f /etc/alpine-release ]] && { OS="Alpine"; VERSION_ID=$(cat /etc/alpine-release); }
    [[ -z "${OS:-}" ]] && {
      command -v apt-get &>/dev/null  && { OS="Debian"; VERSION_ID=$(lsb_release -r 2>/dev/null | awk '{print $2}' || echo "12"); }
      command -v apk &>/dev/null      && { OS="Alpine"; VERSION_ID=$(cat /etc/alpine-release  || echo "3.19"); }
      command -v zypper &>/dev/null   && { OS="SUSE"; VERSION_ID=$(rpm -q --queryformat '%{VERSION}' sles-release 2>/dev/null || echo "15"); }
      command -v pacman &>/dev/null   && { OS="Arch"; VERSION_ID="rolling"; }
      (command -v dnf &>/dev/null || command -v yum) &>/dev/null  && { OS="CentOS"; for rel in centos-release rocky-release almalinux-release redhat-release; do VERSION_ID=$(rpm -q --queryformat '%{VERSION_ID}' "$rel" 2>/dev/null | cut -d. -f1) && break; done; [[ -z $VERSION_ID ]] && VERSION_ID=$(rpm -E "%{rhel}" 2>/dev/null || echo "7"); }
      [[ -z "${OS:-}" ]] && abort "不支持的操作系统！"
    }
  }
  # 统一别名
  local REDHAT_DERIV="AlmaLinux|Rocky|Alibaba Cloud Linux|Anolis|TencentOS|Tencent Linux|CTyunOS|EulerOS|openEuler|BCE Linux|OpenCloudOS"
  [[ $OS == *$REDHAT_DERIV* ]] && OS="CentOS"
  [[ $OS == *"Kylin"* ]] && { command -v apt-get &>/dev/null  && OS="Debian" || OS="CentOS"; }
  case ${OS_ID,,} in opencloudos|tencentos|tlinux|anolis|alinux|ctyunos|euleros|openeuler) OS="CentOS"; esac
  local upper; upper=$(echo "${ID_LIKE:-}" | tr '[:upper:]' '[:lower:]')
  case ${OS_ID,,} in
    ubuntu) OS="Ubuntu" ;; debian) OS="Debian" ;; alpine) OS="Alpine" ;; arch) OS="Arch" ;; fedora) OS="Fedora" ;;
    *) [[ $upper == *"suse"*   ]] && OS="SUSE"
       [[ $upper == *"debian"* ]] && OS="Debian"
       [[ $upper == *"rhel"* || $upper == *"fedora"* ]] && OS="CentOS" ;;
  esac
  get_public_ip
  [[ -z $IP ]] && warn "暂时无法获取公网 IP，安装依赖后将再次检测"
  echo -e "${Y}检测到系统: $OS $VERSION_ID${N}"
}

detect_ipsec_stack() {
  local v
  v=$(ipsec --version 2>/dev/null)
  [[ $v == *Libreswan* || $v == *libreswan* ]] && { echo "libreswan"; return; }
  [[ $v == *strongSwan* || $v == *strongswan* ]] && { echo "strongswan"; return; }
  (command -v swanctl &>/dev/null  || command -v strongswan &>/dev/null ) && { echo "strongswan"; return; }
  [[ -f /usr/libexec/ipsec/libreswan ]] && { echo "libreswan"; return; }
  command -v ipsec &>/dev/null  && { echo "strongswan"; return; }
  echo "unknown"
}

check_kernel_ppp() {
  modprobe ppp_generic 2>/dev/null && return 0
  [[ -c /dev/ppp ]] && lsmod  | grep -q '^ppp_generic' && return 0
  local cfg v
  for cfg in /proc/config.gz /boot/config-$(uname -r); do
    v=$(zcat "$cfg" 2>/dev/null | grep -c '^CONFIG_PPP=[ym]' || grep -c '^CONFIG_PPP=[ym]' "$cfg" 2>/dev/null) || continue
    (( v > 0 )) && return 0
  done
  return 1
}

install_xanmod_kernel() {
  local codename
  codename=$(awk -F= '/^VERSION_CODENAME/{print $2}' /etc/os-release | tr -d '"')
  [[ -z $codename ]] && codename=$(awk -F= '/^VERSION=/{print $2}' /etc/os-release | sed -n 's/.*(\(.*\)).*/\1/p')
  [[ -z $codename ]] && abort "无法获取系统代号"
  apt-get update  && apt-get install -y wget gnupg
  rm -f /etc/apt/sources.list.d/xanmod*.list /etc/apt/sources.list.d/xanmod*.sources
  sed -i '/deb.xanmod.org/d' /etc/apt/sources.list 
  wget -qO - https://dl.xanmod.org/gpg.key | gpg --dearmor --yes -o /usr/share/keyrings/xanmod-archive-keyring.gpg 
  echo "deb [signed-by=/usr/share/keyrings/xanmod-archive-keyring.gpg] http://deb.xanmod.org ${codename} main" > /etc/apt/sources.list.d/xanmod-kernel.list
  apt-get update 
  apt-get install -y linux-xanmod-x64v3  || apt-get install -y linux-xanmod-x64v2  || apt-get install -y linux-xanmod-x64v1  || abort "XanMod 内核安装失败"
  command -v update-grub &>/dev/null  && update-grub >/dev/null 2>&1 || grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1
  info "XanMod 内核安装完成，请重启后再次运行本脚本"
  exit 0
}

install_backports_kernel() {
  local codename suite uri cfg
  codename=$(awk -F= '/^VERSION_CODENAME/{print $2}' /etc/os-release | tr -d '"')
  [[ -z $codename ]] && abort "无法获取系统代号"
  suite="${codename}-backports"
  cfg="/etc/apt/sources.list.d/debian-backports.sources"
  if ! grep -rqE "^[^#]*(Suites:.*${suite}|[[:space:]]${suite}[[:space:]])" /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null; then
    uri=$(grep -rshE '^[[:space:]]*URIs:[[:space:]]*http' /etc/apt/sources.list.d/ 2>/dev/null | head -n1 | awk '{print $2}' | sed 's#/$##')
    [[ -z $uri ]] && uri="http://deb.debian.org/debian"
    cat > "$cfg" << EOF
Types: deb
URIs: ${uri}
Suites: ${suite}
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
    info "已添加 ${suite} 官方 backports 源"
  fi
  apt-get update 
  apt-get install -y linux-image-6.16.12+deb13-amd64 2>/dev/null || \
  apt-get install -y -t "${suite}" linux-image-amd64 || abort "backports 内核安装失败"
  if grep -q -E '^\s*net\.(ipv4\.tcp_congestion_control|core\.default_qdisc)\s*=' /etc/sysctl.conf /etc/sysctl.d/99-*.conf 2>/dev/null; then
    info "检测到系统已配置 Qdisc/Congestion，BBR 沿用系统设置"
  else
    cat > /etc/sysctl.d/99-bbr3-pie.conf << EOF
net.core.default_qdisc = fq_pie
net.ipv4.tcp_congestion_control = bbr3
EOF
    warn "系统未配置 Qdisc/Congestion，已写入 BBR3+FQ_PIE 作为默认"
    sysctl --system  >/dev/null 2>&1
  fi
  command -v update-grub &>/dev/null  && update-grub >/dev/null 2>&1 || grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1
  info "已安装官方 backports 内核（含 PPP 与 BBR3），请重启后再次运行本脚本"
  exit 0
}

# ─── PPP / 设备 ─────────────────────────────────────

ensure_ppp_dev() {
  modprobe ppp_generic 2>/dev/null ; modprobe pppox 2>/dev/null ; modprobe l2tp_ppp 
  [[ -c /dev/ppp ]] && return 0
  local min
  min=$(awk '$2=="ppp"{print $1}' /proc/misc 2>/dev/null)
  mkdir -p /dev
  if [[ -n $min ]]; then
    mknod /dev/ppp c 10 "$min" &>/dev/null
  else
    mknod /dev/ppp c 108 0 &>/dev/null
  fi
}

# ─── IPsec 配置 ─────────────────────────────────────

select_ipsec_proposals() {
  local s=$1
  if [[ $s == "libreswan" ]]; then
    IKE_PROPOSALS="aes128-sha1-modp1024,aes128-sha1-modp1536,aes128-sha1-modp2048,3des-sha1-modp1024,3des-sha1-modp1536,3des-sha1-modp2048,aes256-sha2_256-modp2048"
    ESP_PROPOSALS="aes128-sha1,3des-sha1,aes256-sha2_256"
  else
    IKE_PROPOSALS="aes128-sha1-modp1024,aes128-sha1-modp1536,aes128-sha1-modp2048,3des-sha1-modp1024,3des-sha1-modp1536,3des-sha1-modp2048,aes256-sha256-modp2048"
    ESP_PROPOSALS="aes128-sha1,3des-sha1,aes256-sha256"
  fi
}

config_ipsec() {
  local s d="/etc"
  s=$(detect_ipsec_stack)
  select_ipsec_proposals "$s"
  [[ $s == "strongswan" && -d /etc/strongswan && ( -x /usr/sbin/strongswan || -f /etc/strongswan/strongswan.conf ) ]] && d="/etc/strongswan"
  mkdir -p "$d" /etc/ipsec.d/{cacerts,aacerts,ocspcerts,acerts,crls,private,certs,reqs}
  local lifetime="keylife=24h"
  [[ $s == "libreswan" ]] && lifetime="salifetime=24h"
  local mi left_addr
  mi=$(detect_main_if)
  left_addr=$(ip -4 addr show dev "$mi" 2>/dev/null | grep -oE 'inet [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1 | awk '{print $2}')
  [[ -z $left_addr ]] && left_addr="%defaultroute"
  if [[ $s == "libreswan" ]]; then
    cat > "${d}/ipsec.conf" << EOF
version 2.0
config setup
    uniqueids=no
    ikev1-policy=accept
conn %default
    keyexchange=ikev1
    ike=${IKE_PROPOSALS}
    esp=${ESP_PROPOSALS}
    ikelifetime=24h
    ${lifetime}
    rekey=no
    dpddelay=10
    dpdtimeout=30
    dpdaction=hold
conn l2tp-psk
    rightsubnet=vhost:%priv
    also=l2tp-psk-default
conn l2tp-psk-default
    authby=secret
    pfs=no
    type=transport
    left=${left_addr}
    leftprotoport=17/1701
    right=%any
    rightprotoport=17/%any
    encapsulation=yes
    fragmentation=yes
    keyingtries=%forever
    auto=start
EOF
  else
    cat > "${d}/ipsec.conf" << EOF
config setup
    uniqueids=no
conn l2tp-psk
    keyexchange=ikev1
    authby=secret
    type=transport
    left=${left_addr}
    leftprotoport=17/1701
    right=%any
    rightprotoport=17/%any
    forceencaps=yes
    ike=${IKE_PROPOSALS}!
    esp=${ESP_PROPOSALS}!
    auto=add
    dpddelay=10
    dpdtimeout=30
    dpdaction=hold
    ikelifetime=24h
    lifetime=24h
    rekey=no
    keyingtries=%forever
EOF
  fi
  cat > "${d}/ipsec.secrets" << EOF
%any %any : PSK "${PSK}"
EOF
  chmod 600 "${d}/ipsec.secrets"
  [[ $d != "/etc" && -f "${d}/ipsec.conf"   ]] && ln -sfn "${d}/ipsec.conf"   /etc/ipsec.conf    || true
  [[ $d != "/etc" && -f "${d}/ipsec.secrets" ]] && ln -sfn "${d}/ipsec.secrets" /etc/ipsec.secrets  || true
}

# ─── xl2tpd / PPP ──────────────────────────────────

config_xl2tpd() {
  mkdir -p /etc/xl2tpd /etc/ppp
  is_mainland_china && local dns=$DNS_CN || local dns=$DNS_OW
  local dns2
  [[ $dns == "$DNS_CN" ]] && dns2="119.29.29.29" || dns2="8.8.4.4"
  local pppv echo_adaptive=""
  pppv=$(pppd --version 2>&1 | sed -n 's/pppd version 2\.\([0-9][0-9]*\).*/\1/p')
  [[ -n $pppv && $pppv -ge 8 ]] && echo_adaptive="lcp-echo-adaptive"
  cat > /etc/xl2tpd/xl2tpd.conf << EOF
[global]
listen-addr = 0.0.0.0
port = 1701
access control = no
[lns default]
ip range = ${POOL_NET}.2-${POOL_NET}.254
local ip = ${POOL_NET}.1
require chap = yes
refuse pap = yes
require authentication = yes
name = l2tpd
exclusive = no
assign ip = yes
length bit = yes
ppp debug = no
pppoptfile = /etc/ppp/options.xl2tpd
EOF
  cat > /etc/ppp/options.xl2tpd << EOF
ipcp-accept-local
ipcp-accept-remote
require-mschap-v2
ms-dns ${dns}
ms-dns ${dns2}
noccp
auth
hide-password
idle 0
mtu 1320
mru 1320
nodefaultroute
persist
maxfail 0
holdoff 1
connect-delay 1000
ipcp-max-configure 30
ipcp-max-failure 30
lcp-echo-interval 30
lcp-echo-failure 5
${echo_adaptive}
noipx
novj
novjccomp
nopcomp
noaccomp
nobsdcomp
nodeflate
asyncmap 0
receive-all
EOF
  touch /etc/ppp/chap-secrets
  chmod 600 /etc/ppp/chap-secrets
  mkdir -p /etc/logrotate.d
  cat > /etc/logrotate.d/xl2tpd-l2tp << EOF
/var/log/xl2tpd.log /var/log/strongswan.log /var/log/libreswan.log {
  weekly
  rotate 4
  compress
  missingok
  notifempty
  copytruncate
}
EOF
}

# ─── 系统 sysctl / 卸载 ─────────────────────────────

detect_main_if() {
  local iface
  iface=$(ip -4 route show default  | awk '/default/ {print $5}' | head -1)
  [[ -n $iface ]] || iface=$(ip route show default  | awk '/default/{print $5; exit}')
  [[ -n $iface ]] || iface=$(ls /sys/class/net  | grep -vE '^lo$|^sit|^ppp|^tap|^docker|^veth' | head -1)
  echo "${iface:-eth0}"
}

config_system() {
  local selinux_orig
  : > /etc/l2tp-sysctl-backup.conf
  if [[ $OS == CentOS* && -s /etc/selinux/config ]]; then
    selinux_orig=$(grep '^SELINUX=' /etc/selinux/config | head -1)
    [[ -n $selinux_orig ]] && echo "SELINUX_ORIG=${selinux_orig}" >> /etc/l2tp-sysctl-backup.conf
    grep -q 'SELINUX=enforcing' /etc/selinux/config && {
      sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
      setenforce 0 2>/dev/null || true
    }
  fi
  MAIN_IF=$(detect_main_if)
  local ctmax key val
  ctmax=$(awk '/MemTotal/{print int($2/16)}' /proc/meminfo)
  (( ctmax < 32768 )) && ctmax=32768
  (( ctmax > 1048576 )) && ctmax=1048576
  modprobe nf_conntrack 2>/dev/null || true
  for key in net.ipv4.ip_forward net.ipv4.conf.all.rp_filter net.ipv4.conf.default.rp_filter \
             net.ipv4.conf.${MAIN_IF}.rp_filter net.ipv4.conf.all.forwarding \
             net.ipv4.udp_rmem_min net.ipv4.udp_wmem_min \
             net.core.rmem_default net.core.wmem_default \
             net.netfilter.nf_conntrack_max \
             net.netfilter.nf_conntrack_udp_timeout \
             net.netfilter.nf_conntrack_udp_timeout_stream; do
    val=$(sysctl -n "$key" 2>/dev/null) || continue
    echo "${key}=${val}" >> /etc/l2tp-sysctl-backup.conf
  done
  cat > /etc/sysctl.d/99-l2tp.conf << EOF
net.ipv4.ip_forward=1
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.icmp_ignore_bogus_error_responses=1
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.${MAIN_IF}.rp_filter=0
net.ipv4.conf.all.accept_source_route=0
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.accept_source_route=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.default.send_redirects=0
net.ipv4.conf.all.forwarding=1
net.ipv4.conf.default.forwarding=1
net.ipv4.conf.${MAIN_IF}.forwarding=1
net.ipv4.udp_rmem_min = 65536
net.ipv4.udp_wmem_min = 65536
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.netfilter.nf_conntrack_max = ${ctmax}
net.netfilter.nf_conntrack_udp_timeout = 60
net.netfilter.nf_conntrack_udp_timeout_stream = 180
EOF
  sysctl --system  || warn "部分内核参数不受当前系统支持，已跳过"
  [[ $(sysctl -n net.ipv4.ip_forward) != "1" ]] && {
    grep -q '^net\.ipv4\.ip_forward\s*=\s*1' /etc/sysctl.conf  || echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
    sysctl -p 
  }
}

# ─── iptables ──────────────────────────────────────

remove_firewall_rules() {
  command -v iptables &>/dev/null  || return 0
  local t
  for t in "" "-t nat" "-t mangle"; do
    eval iptables "$t" -S  | grep -q '^-A.*L2TP_MGR_' || continue
    eval iptables "$t" -S  | grep '^-A.*L2TP_MGR_' | while read -r rule; do
      eval iptables "$t" "$(echo "$rule" | sed 's/^-A/-D/')"  || true
    done
  done
  for c in INPUT FORWARD; do
    iptables -D "$c" -j L2TP_MGR_IN &>/dev/null
    iptables -D "$c" -j L2TP_MGR_FWD &>/dev/null
  done
  iptables -F L2TP_MGR_IN 2>/dev/null ; iptables -X L2TP_MGR_IN 2>/dev/null
  iptables -F L2TP_MGR_FWD 2>/dev/null ; iptables -X L2TP_MGR_FWD 2>/dev/null
  iptables -t nat -F L2TP_MGR_NAT 2>/dev/null ; iptables -t nat -X L2TP_MGR_NAT 2>/dev/null
  iptables -t mangle -F L2TP_MGR_MSS 2>/dev/null ; iptables -t mangle -X L2TP_MGR_MSS 2>/dev/null 
}

config_firewall() {
  local mi="${MAIN_IF:-$(detect_main_if)}"
  command -v iptables &>/dev/null  || abort "iptables 不可用！"
  modprobe nf_conntrack &>/dev/null || true
  remove_firewall_rules
  local p
  for p in 500 4500 1701; do
    while iptables -C INPUT -p udp --dport "$p" -j REJECT --reject-with icmp-port-unreachable &>/dev/null; do
      iptables -D INPUT -p udp --dport "$p" -j REJECT --reject-with icmp-port-unreachable 2>/dev/null || break
    done
  done
  iptables -N L2TP_MGR_IN
  for p in 500 4500 1701; do iptables -A L2TP_MGR_IN -p udp --dport "$p" -j ACCEPT; done
  iptables -A L2TP_MGR_IN -p esp -j ACCEPT
  iptables -I INPUT 1 -j L2TP_MGR_IN
  iptables -N L2TP_MGR_FWD
  iptables -A L2TP_MGR_FWD -s ${POOL_NET}.0/24 -j ACCEPT
  iptables -A L2TP_MGR_FWD -d ${POOL_NET}.0/24 -j ACCEPT
  iptables -I FORWARD 1 -j L2TP_MGR_FWD
  iptables -t nat -N L2TP_MGR_NAT
  iptables -t nat -A L2TP_MGR_NAT -s ${POOL_NET}.0/24 -o "$mi" -j MASQUERADE
  iptables -t nat -I POSTROUTING 1 -j L2TP_MGR_NAT
  iptables -t mangle -N L2TP_MGR_MSS
  iptables -t mangle -A L2TP_MGR_MSS -s ${POOL_NET}.0/24 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
  iptables -t mangle -I FORWARD 1 -j L2TP_MGR_MSS
  if command -v firewall-cmd &>/dev/null  && firewall-cmd --state &>/dev/null; then
    firewall-cmd --permanent --add-port=500/udp --add-port=4500/udp --add-port=1701/udp 
    firewall-cmd --permanent --add-service=ipsec 
    firewall-cmd --reload 
    info "firewalld 已放行 UDP 500/4500/1701"
  fi
  if [[ $OS == CentOS* || $OS == Fedora* ]]; then
    service iptables save  || { mkdir -p /etc/sysconfig; iptables-save > /etc/sysconfig/iptables; }
    systemctl list-unit-files iptables.service  | grep -q iptables.service || {
      [[ -f /etc/sysconfig/iptables ]] && command -v iptables-restore &>/dev/null  && cat > /etc/systemd/system/l2tp-iptables-restore.service << UNIT
[Unit]
Description=Restore L2TP iptables rules
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/iptables-restore /etc/sysconfig/iptables
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
UNIT
    }
    systemctl daemon-reload 
    systemctl enable l2tp-iptables-restore.service 2>/dev/null || true
  else
    netfilter-persistent save  || { mkdir -p /etc/iptables; iptables-save > /etc/iptables/rules.v4; }
  fi
  info "防火墙规则保存成功！"
}

# ─── 用户管理 ──────────────────────────────────────

get_next_ip() {
  local i
  for ((i=2; i<=254; i++)); do
    awk -v ip="${POOL_NET}.$i" '$1!~/^#/ && $4==ip{f=1}END{exit !f}' /etc/ppp/chap-secrets  || { echo "${POOL_NET}.$i"; return; }
  done
  abort "没有可用的 IP 地址！"
}

add_user() {
  local username=$1 multi_dial=$2
  grep -q "^${username}[[:space:]]" /etc/ppp/chap-secrets && abort "用户名 $username 已存在！"
  local ip_addr="*"
  [[ $multi_dial != [yY] ]] && ip_addr=$(get_next_ip)
  echo "$username    l2tpd    ${PSK}    $ip_addr" >> /etc/ppp/chap-secrets
  chmod 600 /etc/ppp/chap-secrets
  systemctl restart xl2tpd  || true
  local old_ip=${IP:-}
  get_public_ip
  [[ -z $IP ]] && IP=$old_ip
  info "用户添加成功！"
  echo -e "${C}----------------------------------------${N}"
  printf "%-15s %-20s\n" "用户名: $username" "IP: $( [[ "$multi_dial" == [yY] ]] && echo '*' || echo "$ip_addr")"
  printf "%-15s %-20s\n" "密码: $PSK" "PSK: $PSK"
  printf "%-15s %-20s\n" "服务器IP: $IP" "端口: 1701"
  echo -e "${C}----------------------------------------${N}"
  echo -e "${C}----------------------------------------${N}"
}

delete_user() {
  local username=$1
  grep -q "^${username}[[:space:]]" /etc/ppp/chap-secrets || abort "用户名 $username 不存在！"
  sed -i "/^${username}[[:space:]]/d" /etc/ppp/chap-secrets
  systemctl restart xl2tpd  || true
  info "用户删除成功！"
}

show_users() {
  echo -e "${B}当前用户列表:${N}\n${C}----------------------------------------${N}"
  grep -v '^#' /etc/ppp/chap-secrets | awk '$1!=""&&$4!=""{printf "%-12s %s\n","用户: "$1,"IP: "$4}'
  echo -e "${C}----------------------------------------${N}"
}

show_online() {
  local n=0
  echo -e "${B}当前在线连接:${N}"
  while read -r pppid rest; do
    [[ -z $pppid ]] && continue
    n=$((n+1))
    local peer iface tid real_ip
    peer=$(grep -oE '[0-9]+(\.[0-9]+){3}:[0-9]+(\.[0-9]+){3}' <<< "$rest" | head -1 | cut -d: -f2)
    [[ -n $peer ]] && iface=$(ip -o -4 addr show | awk -v p="$peer" '$0 ~ "peer " p "[/ ]" {print $2; exit}')
    tid=$(grep -oE 'pppol2tp_tunnel_id [0-9]+' <<< "$rest" | awk '{print $2}')
    [[ -n $tid ]] && real_ip=$(ip l2tp show tunnel | awk -v t="$tid" '$0 ~ "^Tunnel " t "," {f=1} f && $1=="From" {print $4; exit}')
    echo -e "${C}  PID ${pppid}  接口 ${iface:-?}  对端IP ${peer:--}  真实IP ${real_ip:--}${N}"
  done < <(ps -eo pid,args | grep '[p]ppd.*options.xl2tpd' 2>/dev/null)
  [[ $n -gt 0 ]] && echo -e "${C}  共 ${n} 个会话${N}" || echo -e "${Y}  当前无在线连接${N}"
}

# ─── 安装 / 卸载 ───────────────────────────────────

ensure_xl2tpd_service() {
  [[ -f /usr/lib/systemd/system/xl2tpd.service || -f /etc/systemd/system/xl2tpd.service ]] && return 0
  command -v xl2tpd &>/dev/null  || return 1
  local b; b=$(command -v xl2tpd) &>/dev/null
  cat > /etc/systemd/system/xl2tpd.service << EOF
[Unit]
Description=L2TP Daemon
After=network-online.target iptables.service netfilter-persistent.service
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/xl2tpd
RuntimeDirectory=xl2tpd
ExecStart=${b} -D
Restart=always
RestartSec=5
TasksMax=256
LimitNPROC=256
[Install]
WantedBy=multi-user.target
EOF
  systemctl daemon-reload
}

install_xl2tpd_fallback() {
  command -v xl2tpd &>/dev/null  && return 0
  local elver=9 a um
  um=$(uname -m)
  case $um in x86_64|amd64) a="x86_64" ;; aarch64|arm64) a="aarch64" ;; *) warn "当前架构 $um 无预置 xl2tpd RPM"; return 1 ;; esac
  local mv; mv=$(echo "$VERSION_ID" | cut -d. -f1)
  [[ $mv == "7" || ${PLATFORM_ID:-} == *el7* || ${PLATFORM_ID:-} == *oc7* ]] && elver=7
  [[ $mv == "8" || ${PLATFORM_ID:-} == *el8* || ${PLATFORM_ID:-} == *oc8* ]] && elver=8
  local vs url
  for vs in "1.3.18-1" "1.3.17-1" "1.3.16-1"; do
    for url in "https://mirrors.aliyun.com/epel/${elver}/Everything/${a}/Packages/x/xl2tpd-${vs}.el${elver}.${a}.rpm" "https://dl.fedoraproject.org/pub/epel/${elver}/Everything/${a}/Packages/x/xl2tpd-${vs}.el${elver}.${a}.rpm"; do
      curl -fsSL --connect-timeout 8 --max-time 60 "$url" -o /tmp/xl2tpd.rpm  || continue
      (dnf -y install /tmp/xl2tpd.rpm || yum -y install /tmp/xl2tpd.rpm || rpm -Uvh --nodeps /tmp/xl2tpd.rpm || rpm -ivh --nodeps /tmp/xl2tpd.rpm)  && { rm -f /tmp/xl2tpd.rpm; info "xl2tpd 安装成功"; return 0; }
      rm -f /tmp/xl2tpd.rpm
    done
  done
  warn "无法安装 xl2tpd，请检查网络或手动安装"; return 1
}

install_rpm_ipsec() {
  local m=$1
  local cur; cur=$(detect_ipsec_stack)
  [[ $cur == "strongswan" ]] && return 0
  [[ $cur == "libreswan" ]] && "$m" -y remove libreswan 
  "$m" -y install strongswan  && [[ $(detect_ipsec_stack) == "strongswan" ]] && return 0
  "$m" -y install libreswan 
  [[ $(detect_ipsec_stack) == "libreswan" ]]
}

install_dependencies() {
  if [[ $OS == Rhel* || $OS == CentOS* ]]; then
    local mgr; command -v dnf &>/dev/null  && mgr=dnf || mgr=yum
    local mv; mv=$(echo "$VERSION_ID" | cut -d. -f1)
    [[ $mv == "8"* ]] && {
      sed -i 's|mirror.centos.org|vault.centos.org|g' /etc/yum.repos.d/*.repo 
      sed -i 's|^#baseurl|baseurl|g;s|^mirrorlist|#mirrorlist|g' /etc/yum.repos.d/*.repo /etc/yum.repos.d/epel*.repo 
    }
    "$mgr" -y makecache  || true
    rpm -q epel-release  || "$mgr" -y install epel-release  || true
    "$mgr" -y install curl iproute ppp ethtool  || true
    "$mgr" -y install iptables-services  || "$mgr" -y install iptables iptables-nft  || true
    "$mgr" -y install kernel-modules-extra  || true
    install_rpm_ipsec "$mgr"
    "$mgr" -y install xl2tpd  || install_xl2tpd_fallback
    ensure_xl2tpd_service
    [[ $mv -ge 10 ]] && "$mgr" -y install nftables  || true
  elif [[ $OS == Ubuntu* || $OS == Debian* ]]; then
    apt-get update 
    echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections 
    echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections 
    DEBIAN_FRONTEND=noninteractive apt-get -y install curl iproute2 ppp xl2tpd iptables iptables-persistent ethtool strongswan-starter strongswan-charon libstrongswan-standard-plugins 
    DEBIAN_FRONTEND=noninteractive apt-get -y install libcharon-extauth-plugins  || true
    DEBIAN_FRONTEND=noninteractive apt-get -y install libstrongswan-extra-plugins  || DEBIAN_FRONTEND=noninteractive apt-get -y install libcharon-extra-plugins  || true
    ensure_xl2tpd_service
  elif [[ $OS == Fedora* ]]; then
    dnf -y makecache && dnf -y install curl iproute ppp strongswan xl2tpd iptables iptables-services ethtool
  elif [[ $OS == SUSE* ]]; then
    zypper --non-interactive refresh && zypper --non-interactive install curl iproute2 ppp strongswan xl2tpd iptables ethtool
  elif [[ $OS == Arch* ]]; then
    pacman -Sy --noconfirm --needed curl iproute2 ppp strongswan xl2tpd iptables ethtool
  elif [[ $OS == Alpine* ]]; then
    setup-devd udev  || true
    apk update && apk add curl iproute2 ppp strongswan xl2tpd iptables iptables-legacy ethtool
  else
    abort "不支持的操作系统！"
  fi
  command -v xl2tpd &>/dev/null  || abort "xl2tpd 安装失败！"
  [[ $(detect_ipsec_stack) != "unknown" ]] || abort "IPSec（strongswan/libreswan）安装失败！"
}

enable_service_restart() {
  local ipsec_svc=""
  if [[ $OS == Alpine* ]]; then
    for s in strongswan xl2tpd; do rc-update add "$s" default ; rc-service "$s" restart  || warn "$s 启动失败"; done
    sleep 2
    local i=false l=false
    rc-service strongswan status  | grep -q started && i=true
    rc-service xl2tpd status  | grep -q started && l=true
    $i && $l && info "IPSec 和 xl2tpd 服务已成功启动" || {
      $i || warn "警告: IPSec 服务未运行"
      $l || warn "警告: xl2tpd 服务未运行"
    }
    return
  fi
  mkdir -p /etc/systemd/system/xl2tpd.service.d
  cat > /etc/systemd/system/xl2tpd.service.d/override.conf << EOF
[Service]
Restart=always
RestartSec=5
TasksMax=256
LimitNPROC=256
RuntimeDirectory=xl2tpd
RuntimeDirectoryMode=0755
EOF
  for s in strongswan-starter strongswan ipsec; do
    systemctl list-unit-files "${s}.service" | grep -q "${s}.service" || continue
    mkdir -p "/etc/systemd/system/${s}.service.d"
    cat > "/etc/systemd/system/${s}.service.d/restart.conf" << EOF
[Unit]
After=iptables.service netfilter-persistent.service
[Service]
Restart=always
RestartSec=5
EOF
  done
  systemctl daemon-reload
  systemctl enable xl2tpd 
  for s in strongswan-starter strongswan ipsec; do
    systemctl list-unit-files "${s}.service" | grep -q "${s}.service" || continue
    ipsec_svc=$s
    systemctl enable "$s" 
    systemctl restart "$s"  || warn "$s 启动失败，请重启服务器后检查"
    break
  done
  [[ -n $ipsec_svc ]] || warn "未找到 strongswan/ipsec 服务单元，请手动启动"
  systemctl restart xl2tpd  || warn "xl2tpd 启动失败，请重启服务器后检查"
  sleep 2
  local i=false l=false
  for s in strongswan-starter strongswan ipsec; do systemctl is-active --quiet "$s"  && i=true && break; done
  systemctl is-active --quiet xl2tpd  && l=true
  $i && $l && info "IPSec 和 xl2tpd 服务已成功启动" || {
    $i || warn "警告: IPSec 服务未运行，可能需要重启后生效"
    $l || warn "警告: xl2tpd 服务未运行，可能需要重启后生效"
  }
}

check_l2tp_installed() {
  [[ -f /etc/ipsec.conf && -f /etc/xl2tpd/xl2tpd.conf && -f /etc/ppp/options.xl2tpd ]]
}

uninstall_l2tp() {
  warn "开始彻底卸载 L2TP..."
  for s in xl2tpd ipsec strongswan strongswan-starter; do systemctl stop "$s" &>/dev/null; systemctl disable "$s" &>/dev/null; done
  command -v rc-service &>/dev/null  && for s in xl2tpd ipsec strongswan; do rc-service "$s" stop &>/dev/null; rc-update del "$s" default &>/dev/null; done
  pkill -x xl2tpd 2>/dev/null; pkill -f 'pppd.*pppol2tp' 2>/dev/null
  sleep 1
  pkill -9 -x xl2tpd 2>/dev/null; pkill -9 -f 'pppd.*pppol2tp' 2>/dev/null
  local mgr=""
  for m in dnf yum apt zypper pacman apk; do command -v "$m" &>/dev/null  && { mgr=$m; break; }; done
  if [[ -n $mgr ]]; then
    warn "正在自动卸载系统软件包 (xl2tpd libreswan strongswan ppp) ..."
    case $mgr in
      dnf|yum) $mgr remove -y xl2tpd libreswan strongswan ppp; $mgr autoremove -y ;;
      apt)     apt purge -y xl2tpd libreswan strongswan ppp; apt autoremove -y ;;
      zypper)  zypper remove -y xl2tpd libreswan strongswan ppp ;;
      pacman)  pacman -Rns --noconfirm xl2tpd libreswan strongswan ppp ;;
      apk)     apk del xl2tpd libreswan strongswan ppp ;;
    esac
    info "系统软件包已卸载"
  else
    warn "未检测到可用的包管理器，跳过系统软件包卸载"
  fi
  rm -rf /etc/systemd/system/xl2tpd.service.d &>/dev/null
  for s in ipsec strongswan strongswan-starter; do
    rm -f "/etc/systemd/system/$s.service.d/restart.conf" &>/dev/null
    rmdir "/etc/systemd/system/$s.service.d" &>/dev/null
  done
  systemctl disable l2tp-iptables-restore.service &>/dev/null
  rm -f /etc/systemd/system/l2tp-iptables-restore.service &>/dev/null
  rm -rf /etc/xl2tpd /var/run/xl2tpd /var/log/xl2tpd 
  rm -f /etc/ppp/options.xl2tpd /etc/ppp/chap-secrets 
  rm -f /etc/ppp/options.l2tpd.hairpin /tmp/xl2tpd_hairpin.conf /tmp/xl2tpd.pid /tmp/l2tp-control
  rm -f /etc/ipsec.conf /etc/ipsec.secrets 
  rm -f /etc/strongswan/ipsec.conf /etc/strongswan/ipsec.secrets 
  rm -rf /var/lib/strongswan /var/lib/libreswan /var/log/strongswan /var/log/libreswan 
  rm -f /etc/firewalld/services/xl2tpd.xml 
  rm -f /etc/ipsec.conf /etc/ipsec.secrets /etc/sysctl.d/99-l2tp.conf /etc/sysctl.d/99-tcp-keepalive.conf /etc/nftables.d/l2tp.nft /etc/logrotate.d/xl2tpd-l2tp 
  remove_firewall_rules
  command -v firewall-cmd &>/dev/null  && {
    firewall-cmd --permanent --remove-port=500/udp --remove-port=4500/udp --remove-port=1701/udp 
    firewall-cmd --permanent --remove-service=ipsec 
    firewall-cmd --reload 
    info "firewalld 规则已清理"
  }
  command -v iptables-save &>/dev/null  && {
    command -v netfilter-persistent &>/dev/null  && netfilter-persistent save 
    iptables-save > /etc/iptables/rules.v4  || true
    mkdir -p /etc/sysconfig; iptables-save > /etc/sysconfig/iptables  || true
  }
  if [[ -f /etc/l2tp-sysctl-backup.conf ]]; then
    while IFS= read -r line; do
      [[ -z $line || $line == SELINUX_ORIG=* ]] && continue
      sysctl -w "${line// /}" &>/dev/null || true
    done < /etc/l2tp-sysctl-backup.conf
    local selinux_orig
    selinux_orig=$(grep '^SELINUX_ORIG=' /etc/l2tp-sysctl-backup.conf | cut -d= -f2-)
    if [[ -n $selinux_orig && -s /etc/selinux/config ]]; then
      sed -i "s/^SELINUX=.*/${selinux_orig}/g" /etc/selinux/config
    fi
    rm -f /etc/l2tp-sysctl-backup.conf
  else
    sysctl -w net.ipv4.ip_forward=0 &>/dev/null || true
  fi
  sysctl --system ; systemctl daemon-reload 
  rm -f /var/log/xl2tpd.log /var/log/strongswan.log /var/log/libreswan.log 
  info "L2TP 配置已清理完毕！"
}

# ─── 主流程 ────────────────────────────────────────

main() {
  echo -e "${P}L2TP VPN 管理脚本${N}"
  setup_terminal
  ensure_ppp_dev
  get_os_info

  if ! check_kernel_ppp; then
    echo -e "${R}当前内核不支持 PPP！$(uname -r) 为精简内核，缺少 ppp_generic 模块${N}"
    [[ $OS == Debian* && ${VERSION_ID%%.*} == 13 ]] && {
      read_input "${Y}需要安装 Debian 官方 backports 内核（含 PPP 与 BBR3），是否安装? (y/n): ${N}" ans
      [[ $ans == [yY] ]] && install_backports_kernel
      abort "缺少 PPP 内核支持，L2TP 无法正常工作"
    }
    [[ $OS == Debian* || $OS == Ubuntu* ]] && {
      read_input "${Y}需要安装 XanMod 内核（含 PPP 支持），是否安装? (y/n): ${N}" ans
      [[ $ans == [yY] ]] && install_xanmod_kernel
      abort "缺少 PPP 内核支持，L2TP 无法正常工作"
    } || abort "请安装支持 PPP 的内核后重试"
  fi

  if ! check_l2tp_installed; then
    warn "未检测到 L2TP 安装，开始安装..."
    read_input "${C}请输入 VPN 用户名: ${N}" VPN_USER
    read_input "${C}是否启用多拨? (y/n): ${N}" MULTI_DIAL
    install_dependencies
    [[ -z $IP ]] && get_public_ip
    config_ipsec
    config_xl2tpd
    config_system
    config_firewall
    enable_service_restart
    add_user "$VPN_USER" "$MULTI_DIAL"
    info "安装完成！已启用自动重拨/服务自恢复功能"
  else
    echo -e "${B}检测到L2TP已安装，请选择操作:${N}"
    echo -e "${C}1. 添加用户${N}"
    echo -e "${C}2. 删除用户${N}"
    echo -e "${C}3. 显示所有用户${N}"
    echo -e "${C}4. 查看在线连接${N}"
    echo -e "${R}5. 卸载L2TP${N}"
    read_input "${Y}请选择 (1-5): ${N}" ch
    case $ch in
      1) read_input "${C}请输入新用户名: ${N}" u; read_input "${C}是否启用多拨? (y/n): ${N}" m; add_user "$u" "$m" ;;
      2) read_input "${C}请输入要删除的用户名: ${N}" u; delete_user "$u" ;;
      3) show_users ;;
      4) show_online ;;
      5) read_input "${R}确定要卸载L2TP吗？这将删除所有配置和用户数据！(y/n): ${N}" c; [[ $c == [yY] ]] && uninstall_l2tp || warn "已取消卸载操作" ;;
      *) abort "无效的选择！" ;;
    esac
  fi
}

main
