#!/bin/bash

# --- [0. 初始化] 权限与环境检测 ---
# 必须先确保是 Root 用户，否则后续操作无法执行
if [ "$(id -u)" != "0" ]; then
  echo -e "\033[31m[×] 错误：此脚本必须以 root 权限运行\033[0m"
  exit 1
fi

# --- [0. 初始化] 字符集修复 ---
# 强制使用 UTF-8，防止中文显示乱码或对齐错误
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
export LVM_SUPPRESS_FD_WARNINGS=1

# --- [1. 核心] 系统与包管理器检测 ---
check_os() {
  if [ -f /etc/os-release ]; then
    . /etc/os-release
    OS_ID=$ID
    OS_VERSION_ID=$VERSION_ID
    OS_CODENAME=${VERSION_CODENAME:-${UBUNTU_CODENAME:-""}}
    OS_ID_LIKE=${ID_LIKE:-""}
  elif [ -f /etc/redhat-release ]; then
    OS_ID="centos" # 兼容性回退
    OS_ID_LIKE="rhel"
  else
    OS_ID="unknown"
    OS_ID_LIKE=""
  fi

  # 检测是否为 Proxmox VE (PVE) 环境 (必须检查 ii 正常安装状态，杜绝 rc 卸载残留误报)
  IS_PVE=false
  if command -v pveversion >/dev/null 2>&1 || [ -d /etc/pve ]; then
    IS_PVE=true
  elif dpkg -l proxmox-ve 2>/dev/null | grep -qE '^ii\s+proxmox-ve' || dpkg -l pve-manager 2>/dev/null | grep -qE '^ii\s+pve-manager'; then
    IS_PVE=true
  fi

  # 归一化发行版名称与包管理器配置 (支持 Debian系与现代 RHEL全系)
  case "$OS_ID" in
  ubuntu | debian | kali | raspbian)
    PKG_MANAGER="apt"
    PKG_UPDATE="apt-get update"
    PKG_INSTALL="apt-get install -y"
    PKG_REMOVE="apt-get purge -y"
    OS_FAMILY="debian"
    ;;
  centos | rhel | fedora | almalinux | alma | rocky | rockylinux | anolis | ol | amzn | openEuler | openeuler | alinux | tencentos)
    if command -v dnf5 >/dev/null 2>&1; then
      PKG_MANAGER="dnf5"
      PKG_UPDATE="dnf5 makecache"
      PKG_INSTALL="dnf5 install -y"
      PKG_REMOVE="dnf5 remove -y"
    elif command -v dnf >/dev/null 2>&1; then
      PKG_MANAGER="dnf"
      PKG_UPDATE="dnf makecache"
      PKG_INSTALL="dnf install -y"
      PKG_REMOVE="dnf remove -y"
    else
      PKG_MANAGER="yum"
      PKG_UPDATE="yum makecache"
      PKG_INSTALL="yum install -y"
      PKG_REMOVE="yum remove -y"
    fi
    OS_FAMILY="rhel"
    ;;
  *)
    # 尝试基于 ID_LIKE 回退兼容
    if [[ "$OS_ID_LIKE" =~ (debian|ubuntu) ]]; then
      PKG_MANAGER="apt"
      PKG_UPDATE="apt-get update"
      PKG_INSTALL="apt-get install -y"
      PKG_REMOVE="apt-get purge -y"
      OS_FAMILY="debian"
    elif [[ "$OS_ID_LIKE" =~ (rhel|centos|fedora) ]]; then
      if command -v dnf5 >/dev/null 2>&1; then
        PKG_MANAGER="dnf5"
        PKG_UPDATE="dnf5 makecache"
        PKG_INSTALL="dnf5 install -y"
        PKG_REMOVE="dnf5 remove -y"
      elif command -v dnf >/dev/null 2>&1; then
        PKG_MANAGER="dnf"
        PKG_UPDATE="dnf makecache"
        PKG_INSTALL="dnf install -y"
        PKG_REMOVE="dnf remove -y"
      else
        PKG_MANAGER="yum"
        PKG_UPDATE="yum makecache"
        PKG_INSTALL="yum install -y"
        PKG_REMOVE="yum remove -y"
      fi
      OS_FAMILY="rhel"
    else
      echo -e "\033[31m[×] 错误：暂不支持的操作系统发行版: $OS_ID ($OS_ID_LIKE)\033[0m"
      exit 1
    fi
    ;;
  esac

  # 定义跨发行版通用服务名称
  case "$OS_FAMILY" in
  debian)
    SVC_CRON="cron"
    SVC_SSH="ssh"
    SVC_HTTP="apache2"
    ;;
  rhel)
    SVC_CRON="crond"
    SVC_SSH="sshd"
    SVC_HTTP="httpd"
    ;;
  esac
}

# 规范化包清理函数 (彻底避免在变量中嵌入 && 导致的参数解析错误，并严格实施 PVE autoremove 豁免保护)
do_pkg_clean() {
  case "$PKG_MANAGER" in
    apt)
      apt-get clean
      if [ "$IS_PVE" = true ]; then
        echo -e "\033[33m[!] 检测到 PVE 宿主机环境，跳过 autoremove 保护核心虚拟化元包\033[0m"
      else
        apt-get autoremove -y
      fi
      ;;
    dnf5)
      dnf5 autoremove -y && dnf5 clean all
      ;;
    dnf)
      dnf autoremove -y && dnf clean all
      ;;
    yum)
      yum autoremove -y && yum clean all
      ;;
  esac
}

check_os

# --- [2. 配置] 终端配色 ---
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
PURPLE='\033[35m'
CYAN='\033[36m'
WHITE='\033[37m'
BOLD='\033[1m'
RESET='\033[0m'
GRAY='\033[90m'
BOLD_WHITE='\033[1;37m'
BOLD_GREEN='\033[1;32m'
BOLD_YELLOW='\033[1;33m'
BOLD_PURPLE='\033[1;35m'
BOLD_CYAN='\033[1;36m'
BOLD_RED='\033[1;31m'

# --- [2. 配置] 信号捕获 (Ctrl+C) ---
SKIP_PAUSE=false
CTRL_C_PRESSED=false

trap_ctrl_c() {
  echo -e "\n${YELLOW}[!] 用户触发中断 (Ctrl+C)，正在返回主菜单...${RESET}"
  SKIP_PAUSE=true
  CTRL_C_PRESSED=true
}
trap trap_ctrl_c SIGINT


# --- 统一交互：菜单 0/q 返回，参数 q 取消，确认 y/N ---
UI_PAUSED=false

ui_pause() {
  local ui_pause_reply
  IFS= read -r -p "${1:-按 Enter 继续...}" ui_pause_reply || true
  UI_PAUSED=true
}

ui_confirm() {
  local ui_target="$1" ui_prompt="$2" ui_default="${3:-n}" ui_answer
  while true; do
    if ! IFS= read -r -p "$ui_prompt" ui_answer; then ui_answer=n; fi
    case "${ui_answer,,}" in
      '') ui_answer="$ui_default" ;;
      y|yes) ui_answer=y ;;
      n|no|0|q) ui_answer=n ;;
      *) echo -e "${YELLOW}[!] 请输入 y 确认、n 拒绝，或 q 取消。${RESET}" >&2; continue ;;
    esac
    printf -v "$ui_target" '%s' "$ui_answer"
    return 0
  done
}

ui_menu_read() {
  local ui_target="$1" ui_prompt="$2" ui_allowed="$3" ui_default="${4:-}" ui_answer
  while true; do
    if ! IFS= read -r -p "${ui_prompt} [0/q 返回]: " ui_answer; then
      printf -v "$ui_target" '%s' 0
      return 1
    fi
    case "$ui_answer" in q|Q) ui_answer=0 ;; '') ui_answer="$ui_default" ;; esac
    if [[ "$ui_answer" =~ ^($ui_allowed)$ ]]; then
      printf -v "$ui_target" '%s' "$ui_answer"
      return 0
    fi
    echo -e "${YELLOW}[!] 无效选项，请重新输入；0/q 返回。${RESET}" >&2
  done
}

# 验证失败只重读当前字段；0 仅在调用者明确允许时作为旧版取消别名。
ui_read_field() {
  local ui_target="$1" ui_prompt="$2" ui_validator="${3:-ui_any_value}"
  local ui_default="${4:-}" ui_zero_cancel="${5:-false}" ui_answer
  while true; do
    if ! IFS= read -r -p "${ui_prompt} [q 取消]: " ui_answer; then
      return 1
    fi
    if [[ "$ui_answer" =~ ^[qQ]$ ]] || { [ "$ui_zero_cancel" = true ] && [ "$ui_answer" = 0 ]; }; then
      return 1
    fi
    [ -z "$ui_answer" ] && ui_answer="$ui_default"
    if "$ui_validator" "$ui_answer"; then
      printf -v "$ui_target" '%s' "$ui_answer"
      return 0
    fi
    echo -e "${YELLOW}[!] 输入无效，请重新填写当前项；q 取消。${RESET}" >&2
  done
}

ui_any_value() { return 0; }
ui_confirm_explicit() {
  local ui_target="$1" ui_prompt="$2" ui_answer
  while true; do
    IFS= read -r -p "${ui_prompt} [yes 确认 / q 取消]: " ui_answer || ui_answer=q
    case "${ui_answer,,}" in
      yes) printf -v "$ui_target" '%s' yes; return 0 ;;
      ''|n|no|0|q) printf -v "$ui_target" '%s' ''; return 0 ;;
      *) echo -e "${YELLOW}[!] 请输入完整的 yes 确认，或 q 取消。${RESET}" >&2 ;;
    esac
  done
}
ui_nonempty() { [ -n "$1" ]; }
ui_hostname_confirm() { [[ "${1,,}" =~ ^(y|yes|n|no)$ ]]; }
ui_unsigned() { [[ "$1" =~ ^(0|[1-9][0-9]{0,8})$ ]]; }
ui_positive() { [[ "$1" =~ ^[1-9][0-9]{0,8}$ ]]; }
ui_bandwidth() { [[ "$1" =~ ^[1-9][0-9]{0,5}$ ]]; }
ui_rtt() { [[ "$1" =~ ^[1-9][0-9]{0,4}$ ]]; }
ui_bdp_mult() { [[ "$1" =~ ^[1-5]$ ]]; }
ui_enum2() { [[ "$1" =~ ^[0-2]$ ]]; }
ui_enum3() { [[ "$1" =~ ^[0-3]$ ]]; }
ui_bool() { [[ "$1" =~ ^[01]$ ]]; }
ui_swappiness() { ui_unsigned "$1" && (( $1 <= 100 )); }
ui_f2b_duration() {
  local ui_duration
  ui_duration=$(parse_f2b_duration "$1")
  [ -n "$ui_duration" ] && { [ "$ui_duration" = -1 ] || [ "$ui_duration" -gt 0 ]; }
}
ui_probe_host() { [[ "$1" =~ ^[A-Za-z0-9.:_-]+$ ]] && [ ${#1} -le 255 ]; }
ui_probe_count() { [[ "$1" =~ ^[0-9]{1,2}$ ]] && (( 10#$1 >= 1 && 10#$1 <= 20 )); }
ui_probe_ports() {
  local ui_port
  local ui_ports=()
  [[ "$1" =~ ^[0-9]+(,[0-9]+)*$ ]] || return 1
  IFS=',' read -ra ui_ports <<<"$1"
  for ui_port in "${ui_ports[@]}"; do
    [[ "$ui_port" =~ ^[0-9]{1,5}$ ]] && (( 10#$ui_port >= 1 && 10#$ui_port <= 65535 )) || return 1
  done
}
ui_ipv4_list() {
  local ui_ip
  [ -n "$1" ] || return 1
  for ui_ip in $1; do validate_ipv4 "$ui_ip" || return 1; done
}
ui_ip_list() {
  local ui_ip
  [ -n "$1" ] || return 1
  for ui_ip in $1; do validate_ip "$ui_ip" || return 1; done
}

# 循环子菜单自行展示结果并暂停，退出时不再要求主菜单重复暂停。
ui_run_menu() {
  local ui_status=0
  "$@" || ui_status=$?
  if [ "$ui_status" -eq 0 ]; then SKIP_PAUSE=true; fi
  return "$ui_status"
}

# --- [3. 核心] 包管理器锁检测 (通用版) ---
check_pm_lock() {
  local lock_files=()
  
  if [[ "$PKG_MANAGER" == "apt" ]]; then
    lock_files=("/var/lib/dpkg/lock" "/var/lib/dpkg/lock-frontend" "/var/lib/apt/lists/lock")
  elif [[ "$PKG_MANAGER" == "dnf5" ]]; then
    lock_files=("/run/dnf5.pid" "/var/run/dnf5.pid" "/var/lib/rpm/.dbenv.lock")
  elif [[ "$PKG_MANAGER" == "yum" || "$PKG_MANAGER" == "dnf" ]]; then
    lock_files=("/var/run/yum.pid" "/var/run/dnf.pid" "/var/lib/rpm/.dbenv.lock")
  fi

  # 辅助函数：跨平台检测锁文件的持有进程 PID 清单
  get_lock_pids() {
    local f="$1"
    [ -f "$f" ] || return 0
    if command -v fuser >/dev/null 2>&1; then
      fuser "$f" 2>/dev/null
    elif command -v lsof >/dev/null 2>&1; then
      lsof -t "$f" 2>/dev/null
    fi
  }

  local locked=false
  local pids=""

  for lock in "${lock_files[@]}"; do
    local p
    p=$(get_lock_pids "$lock")
    if [ -n "$p" ]; then
      locked=true
      pids="$pids $p"
    fi
  done
  pids=$(echo "$pids" | tr ' ' '\n' | sort -u | xargs)

  # 若无 fuser/lsof 且锁文件存在，检测是否正在运行包管理器进程
  if [ "$locked" = false ]; then
    local pm_procs=""
    if [[ "$PKG_MANAGER" == "apt" ]]; then
      pm_procs=$(pgrep -x "apt|apt-get|dpkg" 2>/dev/null | xargs)
    else
      pm_procs=$(pgrep -x "dnf|dnf5|yum|rpm" 2>/dev/null | xargs)
    fi
    if [ -n "$pm_procs" ]; then
      locked=true
      pids="$pm_procs"
    fi
  fi

  if [ "$locked" = true ]; then
    echo -e "${RED}[!] 检测到包管理器锁被占用 (PID: $pids)${RESET}"
    echo -e "${YELLOW}系统可能有后台更新服务 (如 unattended-upgrades / dnf-makecache) 正在运行。${RESET}"
    echo -e "请选择操作："
    echo -e "  1. 等待后台事务完成并自动释放锁 (推荐)"
    echo -e "  2. 发送 TERM 信号请求进程优雅中止 (安全停止，保留数据库完整性)"
    echo -e "  3. 取消当前操作"

    ui_menu_read lock_choice "请输入选项 [1-3]" "[0-3]" || return 1
    case $lock_choice in
    1)
      echo -e "${CYAN}>>> 正在等待锁释放 (按 Ctrl+C 可取消)...${RESET}"
      CTRL_C_PRESSED=false

      while true; do
        local still_locked=false
        for lock in "${lock_files[@]}"; do
           local p
           p=$(get_lock_pids "$lock")
           if [ -n "$p" ]; then
             still_locked=true
             break
           fi
        done
        
        if [ "$still_locked" = false ]; then
          local waiting_procs=""
          if [[ "$PKG_MANAGER" == apt ]]; then
            waiting_procs=$(pgrep -x 'apt|apt-get|dpkg' 2>/dev/null)
          else
            waiting_procs=$(pgrep -x 'dnf|dnf5|yum|rpm' 2>/dev/null)
          fi
          [ -n "$waiting_procs" ] && still_locked=true
        fi
        if [ "$still_locked" = false ]; then break; fi

        if [ "$CTRL_C_PRESSED" = true ]; then
          echo -e "\n${YELLOW}>>> 用户取消等待，操作终止。${RESET}"
          return 1
        fi
        echo -n "."
        sleep 2
      done

      if [ "$CTRL_C_PRESSED" = true ]; then return 1; fi
      echo -e "\n${GREEN}[√] 锁已释放${RESET}"
      ;;
    2)
      echo -e "${YELLOW}>>> 正在向持锁进程发送 SIGTERM 信号请求安全终止...${RESET}"
      for pid in $pids; do
        kill -15 "$pid" 2>/dev/null
      done
      sleep 3
      local remaining=""
      for pid in $pids; do
        kill -0 "$pid" 2>/dev/null && remaining="$remaining $pid"
      done
      if [ -z "$remaining" ]; then
        echo -e "${GREEN}[√] 持锁进程已安全退出，事务未损坏${RESET}"
        if [[ "$PKG_MANAGER" == "apt" ]]; then
          dpkg --configure -a >/dev/null 2>&1 || true
        fi
      else
        echo -e "${RED}[!] 进程 ($remaining) 仍在运行未释放锁。${RESET}"
        echo -e "${YELLOW}为防止数据库状态损坏，脚本绝不强制删除系统锁文件。请等待或手动排查。${RESET}"
        return 1
      fi
      ;;
    *)
      echo -e "${YELLOW}操作已取消${RESET}"
      return 1
      ;;
    esac
  fi
  return 0
}

# --- [3. 核心] 基础依赖检查与自动安装 ---
check_dependencies() {
  local commands=("curl" "wget" "grep" "awk" "sed" "ip" "ss" "lsof" "fuser")
  local install_needed=false
  local missing_cmds=""

  for cmd in "${commands[@]}"; do
    if ! command -v "$cmd" &>/dev/null; then
      install_needed=true
      missing_cmds="$missing_cmds $cmd"
    fi
  done

  if [ "$install_needed" = true ]; then
    echo -e "\033[36m>>> 检测到缺失工具 ($missing_cmds)，正在自动安装...\033[0m"
    
    # 检查包管理器锁
    check_pm_lock || exit 1

    # 兼容性包名映射 (覆盖 Debian 体系与包含 DNF5 的全部 RHEL 体系)
    local pkg_curl="curl"
    local pkg_wget="wget"
    local pkg_grep="grep"
    local pkg_awk="gawk"
    local pkg_sed="sed"
    local pkg_lsof="lsof"
    local pkg_psmisc="psmisc"
    local pkg_iproute="iproute2"

    if [[ "$OS_FAMILY" == "rhel" ]]; then
       pkg_iproute="iproute" # RHEL/Fedora/CentOS/Rocky/Alma/DNF5 统一为 iproute
    fi

    # [优化] 先更新包索引（精简系统可能没有缓存）
    echo -e "\033[36m>>> 更新软件源缓存...\033[0m"
    if [[ "$PKG_MANAGER" == "apt" ]]; then
       export DEBIAN_FRONTEND=noninteractive
       apt-get update -qq 2>/dev/null || apt-get update 2>&1 | grep -v "^W:" || true
    else
       $PKG_UPDATE >/dev/null 2>&1
    fi

    echo -e "\033[36m>>> 安装依赖包...\033[0m"
    if [[ "$PKG_MANAGER" == "apt" ]]; then
       apt-get install -y --fix-broken $pkg_curl $pkg_wget $pkg_grep $pkg_awk $pkg_sed $pkg_iproute $pkg_lsof $pkg_psmisc 2>&1 || {
         echo -e "\033[33m>>> 首次安装失败，尝试修复依赖后重试...\033[0m"
         apt-get --fix-broken install -y 2>/dev/null
         apt-get install -y $pkg_curl $pkg_wget $pkg_grep $pkg_awk $pkg_sed $pkg_iproute $pkg_lsof $pkg_psmisc
       }
    else
       $PKG_INSTALL $pkg_curl $pkg_wget $pkg_grep $pkg_awk $pkg_sed $pkg_iproute $pkg_lsof $pkg_psmisc
    fi

    # 二次验证
    local still_missing=""
    for cmd in "${commands[@]}"; do
      if ! command -v "$cmd" &>/dev/null; then
        still_missing="$still_missing $cmd"
      fi
    done
    
    if [ -n "$still_missing" ]; then
      echo -e "\033[31m[×] 错误：以下命令依然无法找到:$still_missing\033[0m"
      echo -e "\033[33m请尝试手动安装缺失的软件包后重新运行脚本。\033[0m"
      echo -e "\033[33m例如: apt-get update && apt-get install -y curl wget\033[0m"
      exit 1
    fi
    echo -e "\033[32m[√] 依赖安装完成\033[0m"
  fi
}

check_dependencies

# --- [4. 机制] 退出前自动清理 ---
# 创建临时文件目录
TEMP_DIR=$(mktemp -d)
CACHE_FILE="${TEMP_DIR}/sys_info_cache"

on_exit() {
  # 脚本退出时自动清理临时目录
  if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
    rm -rf "$TEMP_DIR"
  fi
}

# 注册 EXIT 信号，Ctrl+C (SIGINT) 已单独处理
trap on_exit EXIT

# --- [5. 配置] 远程脚本地址 ---
COUNT=$(curl -s -m 5 https://sh.cici.one/count.php 2>/dev/null || echo "N/A")
SCRIPT_URL="bash <(curl -sL sh.cici.one)"

# --- [工具] 辅助函数 ---
get_system_version() {
  # --- 策略说明 ---
  # 1. 优先从 /etc/os-release 读取 VERSION_ID
  # 2. 对于 Debian 系，如果 VERSION_ID 只有主版本号，尝试从 /etc/debian_version 获取更详细版本
  # 3. 对于 Kali 等滚动发行版，使用 VERSION 字段
  # 4. 兜底读取 /etc/debian_version 或 /etc/redhat-release
  
  local version_id=""
  local os_id=""
  
  if [ -f /etc/os-release ]; then
    version_id=$(grep "^VERSION_ID" /etc/os-release | cut -d'=' -f2 | tr -d '"')
    os_id=$(grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
  fi
  
  # 对于 Debian，尝试获取更详细的版本号
  if [ "$os_id" = "debian" ] && [ -f /etc/debian_version ]; then
    local deb_ver
    deb_ver=$(cat /etc/debian_version)
    # 如果 debian_version 是数字版本格式（如 13.0），优先使用
    if [[ "$deb_ver" =~ ^[0-9]+\.[0-9]+ ]]; then
      echo "$deb_ver"
      return
    fi
    # 否则使用 VERSION_ID（避免显示 trixie/sid 等代号）
    if [ -n "$version_id" ]; then
      echo "$version_id"
      return
    fi
  fi
  
  # 对于其他发行版，直接使用 VERSION_ID
  if [ -n "$version_id" ]; then
    echo "$version_id"
    return
  fi
  
  # 尝试 VERSION 字段 (如 Kali 2024.1)
  if [ -f /etc/os-release ]; then
    local version
    version=$(grep "^VERSION=" /etc/os-release | cut -d'=' -f2 | tr -d '"')
    if [ -n "$version" ]; then
      echo "$version"
      return
    fi
  fi
  
  # RHEL/CentOS/Rocky/Alma 兜底
  if [ -f /etc/redhat-release ]; then
    grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | head -1
    return
  fi
  
  # Debian 系滚动发行版兜底 (如 Sid)
  if [ -f /etc/debian_version ]; then
    cat /etc/debian_version
    return
  fi
  
  echo "未知"
}

get_debian_major_version() {
  [ -f /etc/debian_version ] && cut -d'.' -f1 </etc/debian_version || echo "0"
}

# --- [工具] 容器环境检测 (全局公共函数) ---
# 用于检测是否运行在容器化环境中 (OpenVZ/LXC/Docker 等)
# 容器环境通常无法修改内核参数、加载模块、创建 SWAP 等
is_container_env() {
  # OpenVZ 容器 (兼容 v6/v7)
  if [ -f /proc/vz/veinfo ] && [ ! -f /proc/vz/version ]; then return 0; fi
  if [ -f /proc/user_beancounters ]; then return 0; fi
  # 现代 systemd 容器标志 (适用于 Debian 13/Ubuntu 24.04/26.04 cgroup v2)
  if [ -f /run/systemd/container ]; then return 0; fi
  # 环境变量与进程检测
  if grep -qaE "container=(lxc|docker|podman|containerd)" /proc/1/environ 2>/dev/null; then return 0; fi
  # 传统 cgroup v1 检测
  if grep -qE "(lxc|docker|kubepods)" /proc/1/cgroup 2>/dev/null; then return 0; fi
  # Docker / Podman 容器文件系统标识
  if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then return 0; fi
  # systemd-detect-virt 检测容器类型
  if command -v systemd-detect-virt >/dev/null 2>&1; then
    local virt_type=$(systemd-detect-virt --container 2>/dev/null)
    if [ -n "$virt_type" ] && [ "$virt_type" != "none" ]; then
      return 0
    fi
  fi
  return 1
}

# --- [工具] systemd 运行时状态检测 ---
# 确认 PID 1 是否为 systemd，防御精简容器或非 systemd 环境下调用 systemctl 引发崩溃
has_systemd() {
  [ -d /run/systemd/system ] && [ "$(ps -p 1 -o comm= 2>/dev/null)" = "systemd" ]
}

# --- [工具] sysctl 参数可写性检测 ---
# 检测指定的 sysctl 参数是否可写 (容器环境常为只读)
is_sysctl_writable() {
  local param="$1"
  local current_val
  current_val=$(sysctl -n "$param" 2>/dev/null)
  [ -z "$current_val" ] && return 1
  
  # 尝试写入相同值测试可写性
  sysctl -w "${param}=${current_val}" >/dev/null 2>&1
  return $?
}

# --- [工具] 信息展示 (自动对齐) ---
display_info() {
  local title="$1"
  local items=("${@:2}")

  echo -e "${BOLD}${CYAN}${title}${RESET}"

  # ---- 计算最长标签显示宽度（中文=2宽度，英文=1宽度）----
  local max_label_width=0
  for item in "${items[@]}"; do
    IFS='|' read -r label _ <<<"$item"
    # 去除颜色码
    local clean_label
    clean_label=$(echo -e "$label" | sed -E "s/\x1B\[[0-9;]*[mK]//g")
    # 计算宽度（中文算2）
    local width=0
    for ((i = 0; i < ${#clean_label}; i++)); do
      local c="${clean_label:i:1}"
      [[ "$c" =~ [\x00-\x7F] ]] && ((width += 1)) || ((width += 2))
    done
    ((width > max_label_width)) && max_label_width=$width
  done

  # ---- 输出对齐 ----
  for item in "${items[@]}"; do
    IFS='|' read -r label value <<<"$item"

    # 去除颜色计算实际宽度
    local clean_label
    clean_label=$(echo -e "$label" | sed -E "s/\x1B\[[0-9;]*[mK]//g")
    local width=0
    for ((i = 0; i < ${#clean_label}; i++)); do
      local c="${clean_label:i:1}"
      [[ "$c" =~ [\x00-\x7F] ]] && ((width += 1)) || ((width += 2))
    done

    local padding=$((max_label_width - width))
    local spaces=""
    for ((i = 0; i < padding; i++)); do
      spaces+=" "
    done

    echo -e "  ${YELLOW}${label}:${RESET}${spaces} ${value}"
  done
  echo -e "${CYAN}--------------------------------------------------${RESET}"
}

# ---------- 虚拟化检测函数 ----------
detect_virtualization() {
  local virt_type="未知"
  # 检测容器环境
  if [ -f /.dockerenv ]; then
    virt_type="Docker容器"
  elif grep -q "docker" /proc/1/cgroup 2>/dev/null; then
    virt_type="Docker容器"
  elif grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
    virt_type="LXC容器"
  elif command -v systemd-detect-virt >/dev/null 2>&1; then
    local sdv
    sdv=$(systemd-detect-virt 2>/dev/null)
    [ -z "$sdv" ] && sdv="none"
    case $sdv in
    kvm) virt_type="KVM虚拟机" ;;
    qemu) virt_type="QEMU虚拟机" ;;
    vmware) virt_type="VMware虚拟机" ;;
    microsoft) virt_type="Hyper-V虚拟机" ;;
    oracle) virt_type="VirtualBox虚拟机" ;;
    xen) virt_type="Xen虚拟机" ;;
    lxc | lxc-libvirt) virt_type="LXC容器" ;;
    docker) virt_type="Docker容器" ;;
    podman) virt_type="Podman容器" ;;
    none) virt_type="物理机/未知" ;;
    *) virt_type="$sdv" ;;
    esac
  elif [ -f /proc/cpuinfo ] && grep -q "hypervisor" /proc/cpuinfo; then
    virt_type="虚拟化环境"
  elif command -v dmidecode >/dev/null 2>&1; then
    local bios_vendor
    bios_vendor=$(dmidecode -s system-manufacturer 2>/dev/null | head -1)
    case $bios_vendor in
    *[Vv][Mm][Ww]are*) virt_type="VMware虚拟机" ;;
    *[Qq][Ee][Mm][Uu]*) virt_type="QEMU虚拟机" ;;
    *[Mm]icrosoft*) virt_type="Hyper-V虚拟机" ;;
    *[Oo]racle*) virt_type="VirtualBox虚拟机" ;;
    *[Xx]en*) virt_type="Xen虚拟机" ;;
    *[Kk][Vv][Mm]*) virt_type="KVM虚拟机" ;;
    esac
  fi

  echo "$virt_type"
}

# ---------- 并行执行函数 ----------
run_parallel() {
  local funcs=("$@")
  local pids=()
  local outfiles=()

  for i in "${!funcs[@]}"; do
    outfiles[$i]="${TEMP_DIR}/parallel_result_$i"
    ${funcs[$i]} >"${outfiles[$i]}" 2>/dev/null &
    pids[$i]=$!
  done

  # 等待并读结果
  for i in "${!pids[@]}"; do
    wait "${pids[$i]}" 2>/dev/null
  done

  for i in "${!outfiles[@]}"; do
    if [ -f "${outfiles[$i]}" ]; then
      cat "${outfiles[$i]}"
      rm -f "${outfiles[$i]}"
    fi
  done
}

# ---------- 网络检测与安全数据清洗函数 ----------
get_ipv4_public() {
  local ip
  ip=$(curl -s -4 --connect-timeout 2 https://api64.ipify.org 2>/dev/null || curl -s -4 --connect-timeout 2 https://ifconfig.me/ip 2>/dev/null)
  if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
    echo "$ip"
  else
    echo "N/A"
  fi
}

get_ipv6_public() {
  local ip
  ip=$(curl -s -6 --connect-timeout 2 https://api64.ipify.org 2>/dev/null || curl -s -6 --connect-timeout 2 https://ifconfig.me/ip 2>/dev/null)
  if [[ "$ip" =~ ^[0-9a-fA-F:]+$ ]]; then
    echo "$ip"
  else
    echo "N/A"
  fi
}

get_isp_info() {
  local ip=$1
  if [ -n "$ip" ] && [ "$ip" != "N/A" ]; then
    local result
    # 支持 IPv4/IPv6 双栈查询，设置 2 秒连接超时与 4 秒总超时上限，防止挂起
    result=$(curl -s --connect-timeout 2 -m 4 "https://ipinfo.io/$ip/org" 2>/dev/null | head -1 | tr -cd 'a-zA-Z0-9.,_ -' | head -c 80)
    [ -z "$result" ] && result="未知"
    echo "$result"
  else
    echo "未知"
  fi
}

get_location_info() {
  local ip=$1
  if [ -n "$ip" ] && [ "$ip" != "N/A" ]; then
    local city country
    city=$(curl -s --connect-timeout 2 -m 4 "https://ipinfo.io/$ip/city" 2>/dev/null | tr -cd 'a-zA-Z0-9.,_ -' | head -c 40)
    [ -z "$city" ] && city="未知"
    country=$(curl -s --connect-timeout 2 -m 4 "https://ipinfo.io/$ip/country" 2>/dev/null | tr -cd 'a-zA-Z0-9.,_ -' | head -c 20)
    [ -z "$country" ] && country="未知"
    echo "$city, $country"
  else
    echo "未知"
  fi
}

# 安全加载键值对缓存 (彻底废除 source，消除命令注入风险)
load_kv_cache() {
  local f="$1"
  [ -f "$f" ] || return 1
  while IFS='=' read -r key val || [ -n "$key" ]; do
    [[ "$key" =~ ^[[:space:]]*# ]] && continue
    [ -z "$key" ] && continue
    val="${val%\"}"
    val="${val#\"}"
    case "$key" in
      ipv4_public) ipv4_public="$val" ;;
      ipv6_public) ipv6_public="$val" ;;
      isp_info) isp_info="$val" ;;
      location_info) location_info="$val" ;;
      os_name) os_name="$val" ;;
      sys_ver) sys_ver="$val" ;;
      kernel_ver) kernel_ver="$val" ;;
      arch_info) arch_info="$val" ;;
      virt_type) virt_type="$val" ;;
      cpu_model) cpu_model="$val" ;;
      cpu_count) cpu_count="$val" ;;
      cpu_mhz) cpu_mhz="$val" ;;
      virt_support) virt_support="$val" ;;
      aes_status) aes_status="$val" ;;
    esac
  done < "$f"
}

# --- CPU 统计采集辅助函数 ---
get_cpu_stat() {
  local line
  read -r line </proc/stat
  # 注意: 内核计时中 guest 已计入 user($2)，guest_nice 已计入 nice($3)；总时间仅取前 8 项之和，避免重复统计
  echo "$line" | awk '{print $2+$3+$4+$5+$6+$7+$8+$9, $5+$6, $9}'
}

# --- 只读内存虚拟化检测：驱动绑定不等于正在回收，来宾 KSM 不代表宿主机 ---
detect_memory_sharing_status() {
  local memory_sys_root="${1:-/sys}" memory_is_container="${2:-false}"
  local memory_driver_dir memory_device memory_module memory_ksm_run
  balloon_status="${YELLOW}未知 (未检测到驱动)${RESET}"
  ksm_status="${YELLOW}未知${RESET}"

  # 容器可能透出宿主机 sysfs，不能把这些信息当成容器自己的设置。
  if [ "$memory_is_container" = true ]; then
    balloon_status="${GRAY}不适用 (容器)${RESET}"
    ksm_status="${YELLOW}未知${RESET}"
    return 0
  fi

  # 优先检查实际绑定的设备，兼容内核内置驱动；仅有 module 链接不算绑定。
  for memory_driver_dir in \
    "$memory_sys_root/bus/virtio/drivers/virtio_balloon" \
    "$memory_sys_root/bus/vmbus/drivers/hv_balloon" \
    "$memory_sys_root/bus/platform/drivers/vmw-balloon" \
    "$memory_sys_root/bus/platform/drivers/vmw_balloon"; do
    [ -d "$memory_driver_dir" ] || continue
    for memory_device in "$memory_driver_dir"/*; do
      [ -L "$memory_device" ] && [ -d "$memory_device" ] || continue
      case "${memory_device##*/}" in module|subsystem) continue ;; esac
      balloon_status="${GREEN}已开启 (驱动已绑定)${RESET}"
      break 2
    done
  done

  # 已加载但未发现绑定，不能据此判定回收机制开启或关闭。
  if [[ "$balloon_status" == *"未检测到驱动"* ]]; then
    for memory_module in virtio_balloon hv_balloon vmw_balloon xen_balloon; do
      if [ -d "$memory_sys_root/module/$memory_module" ]; then
        balloon_status="${YELLOW}未知 (仅加载驱动)${RESET}"
        break
      fi
    done
  fi

  # run=0 停止扫描但可能保留已合并页；run=2 停止扫描并解除合并。
  if [ -r "$memory_sys_root/kernel/mm/ksm/run" ] &&
     memory_ksm_run=$(cat "$memory_sys_root/kernel/mm/ksm/run" 2>/dev/null); then
    case "$memory_ksm_run" in
      1) ksm_status="${GREEN}已开启${RESET}" ;;
      0|2) ksm_status="${GRAY}已关闭${RESET}" ;;
    esac
  fi
  return 0
}

# ---------- 系统信息显示 (最终版：虚拟化支持检测+完美对齐) ----------
display_system_info() {
  clear
  # --- 0. 标题头 ---
  echo -e "${CYAN}=============================================================================${RESET}"
  echo -e "${WHITE}                             极光VPS系统管理工具${RESET}"
  echo -e "${CYAN}=============================================================================${RESET}"
  echo -e "${CYAN}作者: ${WHITE}FMSO${RESET} ${GRAY}|${RESET} ${CYAN}版本: ${WHITE}v2026-10-05${RESET} ${GRAY}|${RESET} ${CYAN}调用: ${YELLOW}${COUNT}次${RESET}"
  echo -e "${CYAN}地址: ${WHITE}${SCRIPT_URL}${RESET}"
  echo -e "${CYAN}=============================================================================${RESET}"

  # --- 1. 缓存与数据采集 ---
  local HW_CACHE="${TEMP_DIR}/hardware_cache"
  local CACHE_HIT=false

  if [ -f "$CACHE_FILE" ]; then
    load_kv_cache "$CACHE_FILE" && CACHE_HIT=true
  else
    echo -e "${CYAN}正在初始化系统信息 (首次运行需检测网络)...${RESET}"
  fi

  if [ "$CACHE_HIT" = false ]; then
    tmp4="${TEMP_DIR}/get_ipv4"
    tmp6="${TEMP_DIR}/get_ipv6"
    tmp_isp="${TEMP_DIR}/isp"
    tmp_loc="${TEMP_DIR}/loc"
    get_ipv4_public >"$tmp4" 2>/dev/null &
    pid4=$!
    get_ipv6_public >"$tmp6" 2>/dev/null &
    pid6=$!
  fi

  if [ -f "$HW_CACHE" ]; then
    load_kv_cache "$HW_CACHE"
  else
    if command -v lsb_release >/dev/null 2>&1; then
      os_name=$(lsb_release -d | cut -f2-)
    else
      os_name=$(grep "PRETTY_NAME" /etc/os-release | cut -d'"' -f2 2>/dev/null || uname -s)
    fi
    sys_ver=$(get_system_version)
    kernel_ver=$(uname -r)
    arch_info=$(uname -m)
    virt_type=$(detect_virtualization)

    # CPU 基础信息
    cpu_model=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d':' -f2 | xargs || echo "未知")
    [ -z "$cpu_model" ] && cpu_model="未知"
    cpu_count=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo "1")

    # CPU 主频
    cpu_mhz=$(grep -m1 'cpu MHz' /proc/cpuinfo 2>/dev/null | cut -d':' -f2 | xargs | awk '{printf "%.0f MHz", $1}')
    [ -z "$cpu_mhz" ] && cpu_mhz="未知"

    # [修改] 虚拟化支持检测 (替换了 CPU缓存)
    if grep -qE 'vmx|svm' /proc/cpuinfo 2>/dev/null; then
      virt_support="${GREEN}支持 (Nested)${RESET}"
    else
      virt_support="${RED}不支持${RESET}"
    fi

    # AES 指令集
    if grep -q aes /proc/cpuinfo 2>/dev/null; then
      aes_status="${GREEN}支持${RESET}"
    else
      aes_status="${RED}不支持${RESET}"
    fi

    cat >"$HW_CACHE" <<EOF
os_name="$os_name"
sys_ver="$sys_ver"
kernel_ver="$kernel_ver"
arch_info="$arch_info"
virt_type="$virt_type"
cpu_model="$cpu_model"
cpu_count="$cpu_count"
cpu_mhz="$cpu_mhz"
virt_support="$virt_support"
aes_status="$aes_status"
EOF
  fi

  if uptime -p >/dev/null 2>&1; then uptime_info=$(uptime -p | sed 's/up //'); else uptime_info=$(uptime | sed -E 's/^.* up +//; s/, *[0-9]+ users.*//; s/, *load average.*//'); fi
  boot_time=$(who -b | awk '{print $3 " " $4}')
  load_info=$(uptime | awk -F'load average:' '{print $2}' | xargs)
  current_user=$(whoami)
  hostname_info=$(hostname)

  # --- CPU 计算 (区间统计: 首次启动采样 0.5s，后续为菜单驻留区间平均值) ---
  read -r cur_total cur_idle cur_steal <<<$(get_cpu_stat)
  if [ -z "$PREV_CPU_TOTAL" ]; then
    sleep 0.5
    read -r next_total next_idle next_steal <<<$(get_cpu_stat)
    diff_total=$((next_total - cur_total))
    diff_idle=$((next_idle - cur_idle))
    diff_steal=$((next_steal - cur_steal))
    cur_total=$next_total
    cur_idle=$next_idle
    cur_steal=$next_steal
  else
    diff_total=$((cur_total - PREV_CPU_TOTAL))
    diff_idle=$((cur_idle - PREV_CPU_IDLE))
    diff_steal=$((cur_steal - PREV_CPU_STEAL))
  fi
  PREV_CPU_TOTAL=$cur_total
  PREV_CPU_IDLE=$cur_idle
  PREV_CPU_STEAL=$cur_steal

  if [ "$diff_total" -gt 0 ]; then
    cpu_usage=$(awk -v i="$diff_idle" -v t="$diff_total" 'BEGIN {printf "%.1f%%", 100 - (i/t)*100}')
    cpu_st=$(awk -v s="$diff_steal" -v t="$diff_total" 'BEGIN {printf "%.1f", (s/t)*100}')
  else
    cpu_usage="0.0%"
    cpu_st="0.0"
  fi

  # Steal 颜色
  st_int=$(echo "$cpu_st" | awk -F. '{print $1}')
  if [ "$st_int" -ge 10 ]; then st_display="${RED}${cpu_st}% (严重抢占)${RESET}"; elif [ "$st_int" -gt 0 ]; then st_display="${YELLOW}${cpu_st}% (轻微争抢)${RESET}"; else st_display="${GREEN}${cpu_st}% (良好)${RESET}"; fi

  # 内存/Swap
  mem_info=$(free -h 2>/dev/null | awk '/Mem:/ {print $2, $3}')
  mem_total=$(echo "$mem_info" | awk '{print $1}')
  mem_used=$(echo "$mem_info" | awk '{print $2}')
  
  # [修复] 如果 free -h 获取失败 (为空)，尝试读取 /proc/meminfo
  if [ -z "$mem_total" ] || [ -z "$mem_used" ]; then
      if [ -f /proc/meminfo ]; then
          # 单位 kB
          total_kb=$(grep -i "MemTotal" /proc/meminfo | awk '{print $2}')
          avail_kb=$(grep -i "MemAvailable" /proc/meminfo | awk '{print $2}')
          # 若无 MemAvailable (老内核), 尝试 MemFree + Buffers + Cached
          if [ -z "$avail_kb" ]; then
             free_kb=$(grep -i "MemFree" /proc/meminfo | awk '{print $2}')
             buf_kb=$(grep -i "Buffers" /proc/meminfo | awk '{print $2}')
             cache_kb=$(grep -i "^Cached" /proc/meminfo | awk '{print $2}')
             avail_kb=$((free_kb + buf_kb + cache_kb))
          fi
          
          used_kb=$((total_kb - avail_kb))
          
          # 格式化函数
          calc_size() {
             local k=$1
             if [ "$k" -lt 1024 ]; then echo "${k}K";
             elif [ "$k" -lt 1048576 ]; then awk -v k="$k" 'BEGIN{printf "%.1fM", k/1024}';
             else awk -v k="$k" 'BEGIN{printf "%.1fG", k/1024/1024}'; fi
          }
          
          mem_total=$(calc_size "$total_kb")
          mem_used=$(calc_size "$used_kb")
          mem_percent=$(awk -v u="$used_kb" -v t="$total_kb" 'BEGIN{if(t>0) printf "%.1f%%", u/t*100; else print "0.0%"}')
      else
          mem_total="N/A"
          mem_used="N/A"
          mem_percent="0.0%"
      fi
  else
      mem_percent=$(free 2>/dev/null | awk '/Mem:/ {used=$3; total=$2; if(total>0) printf "%.1f%%", used/total*100}')
  fi

  swap_info=$(free -h 2>/dev/null | awk '/Swap:/ {print $2, $3}')
  swap_total=$(echo "$swap_info" | awk '{print $1}')
  swap_used=$(echo "$swap_info" | awk '{print $2}')
  
  # [修复] Swap 兜底检测
  if [ -z "$swap_total" ] || [ -z "$swap_used" ]; then
    if [ -f /proc/meminfo ]; then
      swap_total_kb=$(grep -i "SwapTotal" /proc/meminfo | awk '{print $2}')
      swap_free_kb=$(grep -i "SwapFree" /proc/meminfo | awk '{print $2}')
      
      if [ -n "$swap_total_kb" ] && [ "$swap_total_kb" -gt 0 ]; then
        swap_used_kb=$((swap_total_kb - swap_free_kb))
        
        # 复用 calc_size
        swap_total=$(calc_size "$swap_total_kb")
        swap_used=$(calc_size "$swap_used_kb")
        swap_percent=$(awk -v u="$swap_used_kb" -v t="$swap_total_kb" 'BEGIN{if(t>0) printf "%.1f%%", u/t*100; else print "0.0%"}')
      else
        # 确实没有 Swap
        swap_total="0B"
      fi
    else
      swap_total="0B"
    fi
  else
    swap_percent=$(free 2>/dev/null | awk '/Swap:/ {used=$3; total=$2; if(total>0) printf "%.1f%%", used/total*100}')
  fi

  if [ -z "$swap_total" ] || [ "$swap_total" = "0B" ]; then
    swap_display="${RED}未检测到SWAP分区${RESET}"
  else
    swap_display="$swap_used / $swap_total ($swap_percent)"
  fi

  # 网络处理
  if [ "$CACHE_HIT" = false ]; then
    wait $pid4 2>/dev/null
    wait $pid6 2>/dev/null
    ipv4_public=$(cat "$tmp4" 2>/dev/null || echo "N/A")
    ipv6_public=$(cat "$tmp6" 2>/dev/null || echo "N/A")
    # 纯 IPv6 VPS 适配：当无公网 IPv4 时，回退使用公网 IPv6 地址查询地理与运营商信息
    local query_ip="$ipv4_public"
    if [ "$query_ip" == "N/A" ] || [ -z "$query_ip" ]; then
      query_ip="$ipv6_public"
    fi
    get_isp_info "$query_ip" >"$tmp_isp" 2>/dev/null &
    pid_isp=$!
    get_location_info "$query_ip" >"$tmp_loc" 2>/dev/null &
    pid_loc=$!
    wait $pid_isp 2>/dev/null
    wait $pid_loc 2>/dev/null
    isp_info=$(cat "$tmp_isp" 2>/dev/null || echo "未知")
    location_info=$(cat "$tmp_loc" 2>/dev/null || echo "未知")
    cat >"$CACHE_FILE" <<EOF
ipv4_public="$ipv4_public"
ipv6_public="$ipv6_public"
isp_info="$isp_info"
location_info="$location_info"
EOF
  fi

  ipv4_local=$(hostname -I 2>/dev/null | awk '{print $1}')
  ipv6_local=$(ip -6 addr show 2>/dev/null | grep "inet6" | grep -v "::1" | awk '{print $2}' | cut -d'/' -f1 | head -1)
  current_time=$(date '+%Y-%m-%d %H:%M:%S')
  local tz_name tz_abbr tz_offset
  tz_name=$(timedatectl show --property=Timezone --value 2>/dev/null)
  [ -z "$tz_name" ] && [ -L /etc/localtime ] && tz_name=$(readlink /etc/localtime 2>/dev/null | sed -E 's|.*/zoneinfo/||')
  [ -z "$tz_name" ] && [ -f /etc/timezone ] && tz_name=$(head -n 1 /etc/timezone 2>/dev/null | tr -d ' \r\n')
  tz_abbr=$(date +%Z 2>/dev/null | tr -d ' \r\n')
  tz_offset=$(date +%:z 2>/dev/null | tr -d ' \r\n')
  if [ -n "$tz_name" ] && [ -n "$tz_offset" ]; then
    timezone="$tz_name (UTC$tz_offset)"
  elif [ -n "$tz_name" ]; then
    timezone="$tz_name"
  elif [ -n "$tz_offset" ]; then
    timezone="${tz_abbr:-UTC} (UTC$tz_offset)"
  elif [ -n "$tz_abbr" ]; then
    timezone="$tz_abbr"
  else
    timezone="未知"
  fi
  default_interface=$(ip route get 8.8.8.8 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}')
  if [ -z "$default_interface" ]; then
    # 回退到 IPv6 默认路由或系统首选默认路由 (纯 IPv6 VPS 适配)
    default_interface=$(ip -6 route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}' | head -1)
    [ -z "$default_interface" ] && default_interface=$(ip route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}' | head -1)
  fi
  if [ -n "$default_interface" ]; then
    mac_address=$(ip link show "$default_interface" 2>/dev/null | awk '/link\/ether/ {print $2; exit}' || echo "未知")
    rx=$(cat /sys/class/net/"$default_interface"/statistics/rx_bytes 2>/dev/null || echo 0)
    tx=$(cat /sys/class/net/"$default_interface"/statistics/tx_bytes 2>/dev/null || echo 0)
    rx_h=$(awk -v b=$rx 'BEGIN{printf "%.2f GB", b/1024/1024/1024}')
    tx_h=$(awk -v b=$tx 'BEGIN{printf "%.2f GB", b/1024/1024/1024}')
  else
    default_interface="未知"
    mac_address="未知"
    rx_h="0 GB"
    tx_h="0 GB"
  fi
  # DNS 检测 (增强：处理 systemd-resolved stub resolver)
  dns_servers=$(grep -oP 'nameserver\s+\K\S+' /etc/resolv.conf 2>/dev/null | head -3 | tr '\n' ',' | sed 's/,$//')
  
  # 如果只有 127.0.0.53，尝试从 resolvectl 获取真实 DNS
  if [ "$dns_servers" = "127.0.0.53" ] && command -v resolvectl >/dev/null 2>&1; then
    local real_dns
    real_dns=$(resolvectl status 2>/dev/null | grep -oP 'DNS Servers:\s+\K\S+' | head -3 | tr '\n' ',' | sed 's/,$//')
    [ -n "$real_dns" ] && dns_servers="$real_dns"
  fi
  
  [ -z "$dns_servers" ] && dns_servers="未配置"

  display_ipv4_pub=$ipv4_public
  [ "$ipv4_public" = "N/A" ] && display_ipv4_pub="${RED}无法获取${RESET}"
  display_ipv6_pub=$ipv6_public
  [ "$ipv6_public" = "N/A" ] && display_ipv6_pub="${RED}无法获取${RESET}"

  # ==================== 全局对齐配置 ====================
  ALIGN_WIDTH=53
  LABEL_WIDTH=10

  # --- 打印函数：双列 (自动对齐) ---
  print_row() {
    local l1=$1
    local v1=$2
    local l2=$3
    local v2=$4

    # --- 左侧处理 ---
    local clean_l1=$(echo -e "$l1" | sed -r 's/\x1B\[[0-9;]*[mK]//g')
    local l1_len=$(echo -n "$clean_l1" | wc -L)
    local l1_pad_len=$((LABEL_WIDTH - l1_len))
    [ $l1_pad_len -lt 0 ] && l1_pad_len=0
    local l1_padding=$(printf "%${l1_pad_len}s" "")

    # 组合左侧: "Label:      Value"
    local left_full="${CYAN}${l1}:${RESET}${l1_padding} ${v1}"

    # 计算左侧总视觉长度 (用于分隔符对齐)
    local clean_left="${clean_l1}:${l1_padding} $(echo -e "$v1" | sed -r 's/\x1B\[[0-9;]*[mK]//g')"
    local left_visual_len=$(echo -n "$clean_left" | wc -L)

    # 计算分隔符填充
    local sep_pad_len=$((ALIGN_WIDTH - left_visual_len))
    [ $sep_pad_len -lt 1 ] && sep_pad_len=1
    local sep_padding=$(printf "%${sep_pad_len}s" "")

    # --- 右侧处理 ---
    # 右侧也同样对齐值
    local clean_l2=$(echo -e "$l2" | sed -r 's/\x1B\[[0-9;]*[mK]//g')
    local l2_len=$(echo -n "$clean_l2" | wc -L)
    local l2_pad_len=$((LABEL_WIDTH - l2_len))
    [ $l2_pad_len -lt 0 ] && l2_pad_len=0
    local l2_padding=$(printf "%${l2_pad_len}s" "")

    # 输出完整行
    printf "  %b%s${GRAY}|${RESET} ${CYAN}%s:${RESET}%s %b\n" "$left_full" "$sep_padding" "$l2" "$l2_padding" "$v2"
  }

  # --- 打印函数：单列 (逻辑与 print_row 左侧一致) ---
  print_full() {
    local l1=$1
    local v1=$2
    
    local clean_l1=$(echo -e "$l1" | sed -r 's/\x1B\[[0-9;]*[mK]//g')
    local l1_len=$(echo -n "$clean_l1" | wc -L)
    local l1_pad_len=$((LABEL_WIDTH - l1_len))
    [ $l1_pad_len -lt 0 ] && l1_pad_len=0
    local l1_padding=$(printf "%${l1_pad_len}s" "")
    
    printf "  ${CYAN}%s:${RESET}%s %b\n" "$l1" "$l1_padding" "$v1"
  }

  # --- 1. 系统信息 ---
  echo -e "${GREEN}一、系统信息${RESET}"
  print_row "操作系统" "$os_name" "系统版本" "$sys_ver"
  print_row "内核版本" "$kernel_ver" "系统架构" "$arch_info"
  print_row "虚拟化类型" "$virt_type" "登录用户" "$current_user"
  print_row "主机名" "$hostname_info" "运行时间" "$uptime_info"
  print_row "启动时间" "$boot_time" "系统负载" "$load_info"
  echo -e "${GRAY}-----------------------------------------------------------------------------${RESET}"

  # --- 2. 硬件资源 (已更新) ---
  echo -e "${GREEN}二、硬件资源${RESET}"
  print_full "CPU型号" "$cpu_model"
  print_row "CPU核心" "${cpu_count} 核心" "CPU频率" "$cpu_mhz"
  # [修改] 此处将 CPU缓存 替换为 虚拟化支持
  print_row "CPU使用率" "$cpu_usage" "VM-x/AMD-V" "$virt_support"
  print_row "内存使用" "${mem_used} / ${mem_total} (${mem_percent})" "AES指令集" "$aes_status"
  print_row "SWAP使用" "$swap_display" "CPU窃取" "$st_display"
  local memory_is_container=false
  is_container_env && memory_is_container=true
  detect_memory_sharing_status /sys "$memory_is_container"
  print_row "气球回收" "$balloon_status" "KSM复用" "$ksm_status"
  echo -e "${GRAY}-----------------------------------------------------------------------------${RESET}"

  # --- 3. 网络信息 ---
  echo -e "${GREEN}三、网络信息${RESET}"
  print_full "运营商" "$isp_info"
  print_row "地理位置" "$location_info" "公网IPv4" "$display_ipv4_pub"
  print_row "默认网卡" "${default_interface:-未知}" "内网IPv4" "${ipv4_local:-未检测到}"
  print_row "MAC地址" "$mac_address" "公网IPv6" "$display_ipv6_pub"
  print_row "入站流量" "$rx_h" "内网IPv6" "${ipv6_local:-未检测到}"
  print_row "出站流量" "$tx_h" "DNS服务器" "$dns_servers"
  print_row "系统时间" "$current_time" "时区信息" "$timezone"
  echo -e "${GRAY}-----------------------------------------------------------------------------${RESET}"

  # --- 4. 磁盘信息 ---
  echo -e "${GREEN}四、磁盘使用情况${RESET}"
  local disk_rows
  disk_rows=$(df -hP 2>/dev/null | grep -vE 'overlay|tmpfs|udev|loop' | sed 1d | wc -l)
  [ "$disk_rows" -lt 0 ] 2>/dev/null && disk_rows=0
  # 向上回退行数: 内存检测1 + 网络10 + 磁盘头尾3 + 服务8 + 加速4 + 菜单38 = 64 行固定基准
  MENU_STEAL_OFFSET=$(( 64 + disk_rows ))

  df -hP 2>/dev/null | grep -vE 'overlay|tmpfs|udev|loop' | awk -v C="${CYAN}" -v R="${RESET}" '
  BEGIN {
      h1="文件系统"; h2="容量"; h3="已用"; h4="可用"; h5="使用%"; h6="挂载点"
  }
  NR==1 { next }
  {
      fs=$1; size=$2; used=$3; avail=$4; use=$5; mount=$6
      len=length(fs)
      if(len>max) max=len
      
      # 保存数据
      lines[NR] = fs "|" size "|" used "|" avail "|" use "|" mount
  }

  END {
      if(max<20) max=20
      
      # 打印表头 (手动计算空格填充，规避 awk 版本差异)
      # 格式: "  " + Col1 + " " + Col2 ...
      
      # Col 1: 文件系统 (视宽8), 目标 max
      printf "  %s文件系统%s", C, R
      for(i=0;i<max-8;i++) printf " "
      
      # Col 2: 容量 (视宽4), 目标 10
      printf " %s容量      %s", C, R
      
      # Col 3: 已用 (视宽4), 目标 10
      printf " %s已用      %s", C, R
      
      # Col 4: 可用 (视宽4), 目标 10
      printf " %s可用      %s", C, R
      
      # Col 5: 使用% (视宽5), 目标 10
      printf " %s使用%%     %s", C, R
      
      # Col 6: 挂载点
      printf " %s挂载点%s\n", C, R
      
      # 打印数据
      data_fmt="  %-" max "s %-10s %-10s %-10s %-10s %-15s\n"
      for(i=2; i<=NR; i++) {
          if(i in lines) {
              split(lines[i], f, "|")
              printf data_fmt, f[1], f[2], f[3], f[4], f[5], f[6]
          }
      }
  }'
  echo -e "${GRAY}-----------------------------------------------------------------------------${RESET}"

  # --- 5. 服务状态 (颜色与对齐修复版) ---
  echo -e "${GREEN}五、系统服务状态${RESET}"
  # 定义要检测的服务列表 (使用变量名或通用名)
  # 注意: 数组中存放的是 "显示标签|服务名变量/真实名"
  local services_config=(
    "SSH|$SVC_SSH"
    "Nginx|nginx"
    "HTTP|$SVC_HTTP"
    "Caddy|caddy"
    "MySQL|mysql"
    "MariaDB|mariadb"
    "PostgreSQL|postgresql"
    "Redis|redis"
    "Docker|docker"
    "X-UI|x-ui"
    "UFW|ufw"
    "Fail2Ban|fail2ban"
  )

  # 临时目录
  local svc_tmp_dir="${TEMP_DIR}/svc_check"
  mkdir -p "$svc_tmp_dir"
  rm -f "$svc_tmp_dir"/*

  local svc_pids=()

  # 并行检测
  for i in "${!services_config[@]}"; do
    (
      IFS='|' read -r label svc_name <<<"${services_config[$i]}"
      local service="$svc_name"
      local value=""
      local version=""
      local status=""
      local is_installed=false

      # --- 1. 服务名修正与存在性检测 ---
      # 尝试直接检测
      if systemctl cat "$service" >/dev/null 2>&1; then
        is_installed=true
      else
        # 常见别名回退
        case "$service" in
          ssh)
            # [修复] SSH 服务在不同发行版有多种名称
            if systemctl cat "sshd" >/dev/null 2>&1; then
               service="sshd"; is_installed=true
            elif systemctl cat "ssh.service" >/dev/null 2>&1; then
               service="ssh.service"; is_installed=true
            elif systemctl cat "openssh-server" >/dev/null 2>&1; then
               service="openssh-server"; is_installed=true
            fi
            ;;
          redis)
            if systemctl cat "redis-server" >/dev/null 2>&1; then
               service="redis-server"; is_installed=true
            fi
            ;;
          mysql)
            if systemctl cat "mysqld" >/dev/null 2>&1; then
               service="mysqld"; is_installed=true
            fi
            ;;
          mariadb)
            if systemctl cat "mariadb.service" >/dev/null 2>&1; then
               service="mariadb.service"; is_installed=true
            elif systemctl cat "mariadb-server" >/dev/null 2>&1; then
               service="mariadb-server"; is_installed=true
            fi
            ;;
          postgresql)
            # PostgreSQL 在某些发行版使用带版本号的服务名
            local pg_svc
            pg_svc=$(systemctl list-unit-files 2>/dev/null | grep -E "postgresql.*\.service" | awk '{print $1}' | head -1)
            if [ -n "$pg_svc" ]; then
               service="$pg_svc"; is_installed=true
            fi
            ;;
          x-ui)
            if systemctl cat "3x-ui" >/dev/null 2>&1; then
               service="3x-ui"; label="3X-UI"; is_installed=true
            fi
            ;;
          nginx)
            if [ -f /etc/init.d/nginx ]; then
               # 非 systemd 场景或源码编译场景检测
               is_installed=true
            fi
            ;;
        esac
      fi
      
      # [修复] 命令存在性兜底检测 (适用于非 systemd 管理但已安装的情况)
      if [ "$is_installed" = false ]; then
          case "$label" in
            UFW)      command -v ufw >/dev/null 2>&1 && is_installed=true ;;
            Docker)   command -v docker >/dev/null 2>&1 && is_installed=true ;;
            Fail2Ban)
              # 使用包管理器判断，避免卸载后命令残留导致误判
              if [[ "$PKG_MANAGER" == "apt" ]]; then
                dpkg -l fail2ban 2>/dev/null | grep -q "^ii" && is_installed=true
              elif [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
                rpm -q fail2ban >/dev/null 2>&1 && is_installed=true
              fi
              ;;
          esac
      fi

      if [ "$is_installed" = true ]; then
        status=$(systemctl is-active "$service" 2>/dev/null || echo "inactive")
        
        # UFW 特殊检测 (命令存在且 status active)
        if [ "$label" == "UFW" ]; then
          if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then status="active"; else status="inactive"; fi
        fi

        # --- 2. 版本获取 (部分命令耗时) ---
        # 仅当服务安装时尝试获取
        case $label in
          SSH)  version=$(ssh -V 2>&1 | awk '{print $1}' | sed 's/^OpenSSH_//');;
          Nginx) version=$(nginx -v 2>&1 | awk -F'/' '{print $2}' | awk '{print $1}');;
          HTTP) 
             # 区分 apache2ctl 或 httpd
             if command -v apache2ctl >/dev/null 2>&1; then
                version=$(apache2ctl -v 2>&1 | grep 'Server version' | awk -F'/' '{print $2}' | awk '{print $1}')
             elif command -v httpd >/dev/null 2>&1; then
                version=$(httpd -v 2>&1 | grep 'Server version' | awk -F'/' '{print $2}' | awk '{print $1}')
             fi
             ;;
          Caddy) version=$(caddy version 2>&1 | awk '{print $1}');;
          MySQL)
             # [修复] MySQL 版本获取 - 区分真正的 MySQL 和 MariaDB 的兼容命令
             if command -v mysqld >/dev/null 2>&1; then
                local ver_out=$(mysqld --version 2>&1)
                if echo "$ver_out" | grep -qi "mariadb"; then
                   version=""  # 实际是 MariaDB，跳过
                else
                   version=$(echo "$ver_out" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
                fi
             elif command -v mysql >/dev/null 2>&1; then
                local ver_out=$(mysql --version 2>&1)
                if echo "$ver_out" | grep -qi "mariadb"; then
                   version=""  # 实际是 MariaDB 的 mysql 兼容命令
                else
                   version=$(echo "$ver_out" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
                fi
             fi
             ;;
          MariaDB)
             # [修复] MariaDB 版本获取
             if command -v mariadbd >/dev/null 2>&1; then
                version=$(mariadbd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
             elif command -v mariadb >/dev/null 2>&1; then
                version=$(mariadb --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
             elif command -v mysqld >/dev/null 2>&1; then
                local ver_out=$(mysqld --version 2>&1)
                if echo "$ver_out" | grep -qi "mariadb"; then
                   version=$(echo "$ver_out" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
                fi
             fi
             ;;
          PostgreSQL)
             # [修复] PostgreSQL 版本 - 优先用服务端命令
             if command -v postgres >/dev/null 2>&1; then
                version=$(postgres --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)
             elif command -v psql >/dev/null 2>&1; then
                version=$(psql --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)
             fi
             ;;
          Redis) 
              if command -v redis-server >/dev/null 2>&1; then
                 version=$(redis-server --version 2>&1 | grep -oE 'v=[0-9]+\.[0-9]+\.[0-9]+' | cut -d'=' -f2)
              fi
              ;;
          Docker) version=$(docker --version 2>&1 | awk '{print $3}' | sed 's/,//');;
          UFW) version=$(ufw --version 2>&1 | awk '{print $2}');;
          Fail2Ban) version=$(fail2ban-client --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1);;
        esac
        
        version=$(echo "$version" | tr -d '\n')
        [ -n "$version" ] && version="(v$version)"

        # 端口信息 (仅SSH展示)
        local port_info=""
        if [ "$label" = "SSH" ] && [ "$status" = "active" ]; then
          local cp=""
          # 尝试从 sshd_config 读取 (兼容 RHEL/Debian 路径)
          if [ -f /etc/ssh/sshd_config ]; then
              cp=$(grep -E "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' | head -1)
          fi
          port_info="(端口:${cp:-22})"
        fi

        if [ "$status" = "active" ]; then
          value="${GREEN}运行中${RESET} $version $port_info"
        else
          value="${YELLOW}未运行${RESET} $version"
        fi
      else
        value="${GRAY}未安装${RESET}"
      fi
      
      # 写入结果文件: label|value
      echo "$label|$value" > "${svc_tmp_dir}/${i}.res"
    ) &
    svc_pids+=($!)
  done

  # 等待所有子进程
  for pid in "${svc_pids[@]}"; do
    wait "$pid" 2>/dev/null
  done

  # 收集结果
  declare -a svc_labels
  declare -a svc_values
  for i in "${!services_config[@]}"; do
    if [ -f "${svc_tmp_dir}/${i}.res" ]; then
       IFS='|' read -r l v < "${svc_tmp_dir}/${i}.res"
       svc_labels+=("$l")
       svc_values+=("$v")
    else
       IFS='|' read -r l _ <<<"${services_config[$i]}"
       svc_labels+=("$l")
       svc_values+=("${GRAY}检测超时${RESET}")
    fi
  done
  
  # 清理
  rm -rf "$svc_tmp_dir"

  # 循环调用 print_row 进行双列打印 (完全继承上方的对齐逻辑)
  len=${#svc_labels[@]}
  for ((i=0; i<len; i+=2)); do
    l1="${svc_labels[i]}"
    v1="${svc_values[i]}"
    l2="${svc_labels[i+1]}"
    v2="${svc_values[i+1]}"
    
    if [ -n "$l2" ]; then
        # 双列显示
        print_row "$l1" "$v1" "$l2" "$v2"
    else
        # 最后一个单列显示
        print_full "$l1" "$v1"
    fi
  done
  echo -e "${GRAY}-----------------------------------------------------------------------------${RESET}"

# --- 6. BBR状态 ---
  # 检测 TCP 拥塞控制
  if sysctl net.ipv4.tcp_congestion_control 2>/dev/null | grep -q bbr; then
      bbr_base="${GREEN}已启用${RESET}"
  else
      bbr_base="${RED}未启用${RESET}"
  fi

  # 检测 BBR 模块加载情况 (lsmod 在精简系统/容器中可能不存在)
  if command -v lsmod >/dev/null 2>&1 && lsmod 2>/dev/null | grep -q tcp_bbr; then
      bbr_extra="(模块已加载)"
  elif grep -wq "bbr" /proc/sys/net/ipv4/tcp_available_congestion_control 2>/dev/null; then
      bbr_extra="(内核内置)"
  else
      bbr_extra="(模块未加载)"
  fi
  
  # 组合 BBR 显示内容
  bbr_display="${bbr_base} ${bbr_extra}"

  # 检测 Qdisc 调度算法
  qdisc_raw=$(sysctl net.core.default_qdisc 2>/dev/null | awk -F'= ' '{print $2}')
  if [[ "$qdisc_raw" == "fq" || "$qdisc_raw" == "cake" ]]; then
      qdisc_display="${GREEN}${qdisc_raw}${RESET}"
  elif [ -n "$qdisc_raw" ]; then
      qdisc_display="${YELLOW}${qdisc_raw}${RESET}"
  else
      qdisc_display="${RED}未设置${RESET}"
  fi
  
  echo -e "${GREEN}六、网络加速状态${RESET}"
  print_row "BBR状态" "$bbr_display" "BBR调度算法" "$qdisc_display"
  
  echo -e "${CYAN}=============================================================================${RESET}\n"
}

# ---------- [最终版] 菜单显示 (单列布局) ----------
show_menu() {
  display_system_info
  echo -e "${GREEN}七、系统管理工具菜单${RESET}"
  echo -e "${CYAN}=============================================================================${RESET}"

  # --- 板块 1: 系统运维 ---
  echo -e "${YELLOW}(一) 系统运维${RESET}"
  echo -e "  ${YELLOW} 1.${RESET} 系统升级与缓存清理"
  echo -e "  ${YELLOW} 2.${RESET} 系统清理 (日志/旧文件/Docker)"
  echo -e "  ${YELLOW} 3.${RESET} 系统内核管理"
  echo -e "  ${YELLOW} 4.${RESET} Swap 交换分区管理"
  echo -e "  ${YELLOW} 5.${RESET} 修改主机名 (Hostname)"
  echo -e "  ${YELLOW} 6.${RESET} 系统时区设置"
  echo -e "  ${YELLOW} 7.${RESET} 系统国内源切换 (Apt/Docker)"

  # --- 板块 2: 安全防护 ---
  echo -e "${YELLOW}(二) 安全防护${RESET}"
  echo -e "  ${YELLOW} 8.${RESET} 防火墙管理 (UFW)"
  echo -e "  ${YELLOW} 9.${RESET} Fail2Ban 防爆破管理"
  echo -e "  ${YELLOW}10.${RESET} SSH端口检测与修改"

  # --- 板块 3: 网络优化 ---
  echo -e "${YELLOW}(三) 网络优化${RESET}"
  echo -e "  ${YELLOW}11.${RESET} 静态IP网络配置 (PVE/自建)"
  echo -e "  ${YELLOW}12.${RESET} BBR 加速管理"
  echo -e "  ${YELLOW}13.${RESET} TCP 网络调优 (BDP智能计算)"
  echo -e "  ${YELLOW}14.${RESET} IPv6 开启与关闭"
  echo -e "  ${YELLOW}15.${RESET} DNS 设置与优化"

  # --- 板块 4: 工具应用 ---
  echo -e "${YELLOW}(四) 工具应用${RESET}"
  echo -e "  ${YELLOW}16.${RESET} 常用软件中心 (Docker)"
  echo -e "  ${YELLOW}17.${RESET} 计划任务管理 (Crontab)"
  echo -e "  ${YELLOW}18.${RESET} 系统监控中心 (端口/进程/审计)"

  # --- 板块 5: 性能测试 ---
  echo -e "${YELLOW}(五) 性能测试${RESET}"
  echo -e "  ${YELLOW}19.${RESET} 流媒体解锁测试"
  echo -e "  ${YELLOW}20.${RESET} 网络质量测试"
  echo -e "  ${YELLOW}21.${RESET} 常用端口与连通性检测 (出站/监听/审计/测速)"
  echo -e "  ${YELLOW}22.${RESET} 融合怪全面测试"
  echo -e "  ${YELLOW}23.${RESET} 服务器性能测试 (YABS)"

  # --- 板块 6: 系统控制 ---
  echo -e "${YELLOW}(六) 系统控制${RESET}"
  echo -e "  ${YELLOW}24.${RESET} 虚拟机磁盘瘦身 (VMware/PVE/TRIM)"
  echo -e "  ${YELLOW}25.${RESET} 重启系统 (Reboot)"
  echo -e "  ${YELLOW}26.${RESET} 关机 (Shutdown)"

  echo -e "${CYAN}=============================================================================${RESET}"
  echo -e "  ${RED}0. 退出脚本${RESET}"
  echo -e "${CYAN}=============================================================================${RESET}"
}

# ---------- 通用依赖安装 (支持多发行版与命令/包名智能映射) ----------
install_deps() {
  for item in "$@"; do
    local check_cmd="$item"
    local pkg_name="$item"

    case "$item" in
    iproute2 | iproute)
      check_cmd="ip"
      if [[ "$OS_FAMILY" == "rhel" ]]; then pkg_name="iproute"; else pkg_name="iproute2"; fi
      ;;
    gawk | awk)
      check_cmd="awk"
      pkg_name="gawk"
      ;;
    cron | crond | cronie)
      check_cmd="crontab"
      if [[ "$OS_FAMILY" == "rhel" ]]; then pkg_name="cronie"; else pkg_name="cron"; fi
      ;;
    dnsutils | bind-utils)
      check_cmd="nslookup"
      if [[ "$OS_FAMILY" == "rhel" ]]; then pkg_name="bind-utils"; else pkg_name="dnsutils"; fi
      ;;
    net-tools)
      check_cmd="ifconfig"
      pkg_name="net-tools"
      ;;
    lsof)
      check_cmd="lsof"
      pkg_name="lsof"
      ;;
    curl)
      check_cmd="curl"
      pkg_name="curl"
      ;;
    wget)
      check_cmd="wget"
      pkg_name="wget"
      ;;
    esac

    if ! command -v "$check_cmd" &>/dev/null; then
      echo -e "${BLUE}[→] 安装依赖: ${pkg_name}${RESET}"
      check_pm_lock || return 1
      $PKG_INSTALL "$pkg_name" >/dev/null 2>&1
    fi
  done
}

# ---------- 通用清理 ----------
cleanup() {
  [ -d "$1" ] && rm -rf "$1" && echo -e "${GREEN}[√] 清理临时目录: $1${RESET}"
}

# ---------- [全局公共辅助] 获取所有 SSH 端口 (优先当前连接端口，兼容多端口监听与配置) ----------
get_all_ssh_ports() {
  local ports=()
  # 1. 优先获取当前活跃 SSH 连接的服务器监听端口
  if [ -n "$SSH_CONNECTION" ]; then
    local cur_conn_port
    cur_conn_port=$(echo "$SSH_CONNECTION" | awk '{print $4}')
    [[ "$cur_conn_port" =~ ^[0-9]+$ ]] && ports+=("$cur_conn_port")
  fi

  # 2. 从运行中 sshd 或 socket 监听获取所有端口
  local listen_ports
  listen_ports=$(ss -tlnp 2>/dev/null | grep -E '("sshd"|sshd\.service|ssh\.socket)' | awk '{print $4}' | awk -F: '{print $NF}')
  for p in $listen_ports; do
    [[ "$p" =~ ^[0-9]+$ ]] && ports+=("$p")
  done

  # 3. 检查 sshd_config 及 sshd_config.d 子配置文件中的所有 Port 定义
  local cfg_ports
  cfg_ports=$(grep -rhE "^[[:space:]]*Port[[:space:]]+[0-9]+" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ 2>/dev/null | awk '{print $2}')
  for p in $cfg_ports; do
    [[ "$p" =~ ^[0-9]+$ ]] && ports+=("$p")
  done

  # 4. 检查 systemd ssh.socket
  if systemctl cat ssh.socket >/dev/null 2>&1; then
    local sock_ports
    sock_ports=$(systemctl cat ssh.socket 2>/dev/null | grep -E "^ListenStream=" | awk -F= '{print $2}' | awk -F: '{print $NF}')
    for p in $sock_ports; do
      [[ "$p" =~ ^[0-9]+$ ]] && ports+=("$p")
    done
  fi

  # 兜底默认 22
  [ ${#ports[@]} -eq 0 ] && ports=(22)

  # 去重并输出，以空格分隔
  printf "%s\n" "${ports[@]}" | sort -nu | tr '\n' ' ' | sed 's/[[:space:]]*$//'
}

get_actual_ssh_port() {
  local all_p
  all_p=($(get_all_ssh_ports))
  if [ -n "$SSH_CONNECTION" ]; then
    local cur_p
    cur_p=$(echo "$SSH_CONNECTION" | awk '{print $4}')
    for p in "${all_p[@]}"; do
      if [ "$p" == "$cur_p" ]; then
        echo "$p"
        return 0
      fi
    done
  fi
  echo "${all_p[0]:-22}"
}

# ---------- UFW 防火墙管理 (深度修复: 状态全显 + 禁Ping智能兜底 + 交互优化) ----------
manage_ufw() {
  # [辅助] 检测安装
  check_ufw_installed() {
    if ! command -v ufw >/dev/null 2>&1; then return 1; else return 0; fi
  }

  # [辅助] 检查 Limit
  check_port_limit() {
    local port=$1
    if ufw status | grep -E "^$port(/tcp)? +LIMIT" >/dev/null 2>&1; then return 0; else return 1; fi
  }

  # [辅助] Ping 状态检测 (基于 DROP 关键字)
  check_ping_status() {
    # 只要检测到 explicit DROP 规则，就视为已禁用 (返回 1/False)
    # 没有 DROP 规则，视为允许 (返回 0/True)
    if grep -q "icmp-type.*echo-request.*DROP" /etc/ufw/before.rules 2>/dev/null; then
      return 1
    else
      return 0
    fi
  }

  # [辅助] 智能修改 Ping 规则 (核心修复逻辑)
  modify_ping_rule() {
    local action=$1
    local file=$2
    local proto=$3
    local type_flag="icmp-type"
    [ "$proto" == "icmpv6" ] && type_flag="icmpv6-type"

    local target="ACCEPT"
    [ "$action" == "drop" ] && target="DROP"

    if grep -q "$type_flag.*echo-request" "$file"; then
      sed -i -E "s/($type_flag.*echo-request.*-j) [A-Z]+/\1 $target/g" "$file"
      return 0
    else
      if [ "$action" == "drop" ]; then
        local chain="ufw-before-input"
        [ "$proto" == "icmpv6" ] && chain="ufw6-before-input"
        local new_rule="-A $chain -p $proto --$type_flag echo-request -j DROP"
        if ! grep -q "$new_rule" "$file"; then
          # [修复] 添加插入失败检测
          if grep -q "^# ok icmp codes" "$file"; then
            sed -i "/^# ok icmp codes/i $new_rule" "$file"
          elif grep -q "^COMMIT" "$file"; then
            sed -i "/^COMMIT/i $new_rule" "$file"
          else
            echo -e "${YELLOW}[!] 警告: 无法在 $file 中找到插入点，请手动添加规则${RESET}" >&2
            return 1
          fi
        fi
      else
        return 0
      fi
    fi
  }

  # --- 入口安装检测 ---
  if ! check_ufw_installed; then
    echo -e "${YELLOW}[!] 未检测到 UFW 防火墙。${RESET}"
    ui_confirm install_choice "是否立即安装 UFW? [y/N] (q 取消): "
    if [[ "$install_choice" =~ ^[Yy]$ ]]; then
      check_pm_lock || return 1
      # 对于 RHEL 系，EPEL 通常在这个阶段可能还未安装 (如果 ufw 在 EPEL 中)
      # 不过之前的 check_dependencies 可能已经处理了基础环境。
      # 这里使用通用变量
      $PKG_UPDATE
      $PKG_INSTALL ufw
      hash -r 2>/dev/null
    else
      return 0
    fi
  fi

  while true; do
    if ! check_ufw_installed; then
      echo -e "${RED}UFW 丢失${RESET}"
      sleep 2
      return 0
    fi

    # --- 状态检测逻辑 ---
    if ufw status 2>/dev/null | grep -q "Status: active"; then
      is_active=true
      ufw_status="${GREEN}运行中 (Active)${RESET}"
      # 获取默认策略
      default_incoming=$(ufw status verbose | grep "Default:" | awk '{print $2}')
      if [ "$default_incoming" == "deny" ]; then
        policy_display="${GREEN}拒绝入站 (Safe)${RESET}"
      else
        policy_display="${RED}允许入站 (Risk)${RESET}"
      fi
    else
      is_active=false
      ufw_status="${RED}已关闭 (Inactive)${RESET}"
      policy_display="${GRAY}未生效${RESET}"
    fi

    ssh_port=$(get_actual_ssh_port)
    if [ "$is_active" = true ]; then
      if check_port_limit "$ssh_port"; then
        limit_display="${GREEN}[已开启]${RESET} 关闭 SSH 智能防爆破 (当前: Limit模式)"
        limit_is_on=true
      else
        limit_display="${GRAY}[已关闭]${RESET} 开启 SSH 智能防爆破 (当前: Allow模式)"
        limit_is_on=false
      fi
    else
      limit_display="${GRAY}[已关闭]${RESET} 开启 SSH 智能防爆破 (需先开启防火墙)"
      limit_is_on=false
    fi

    # Ping 状态显示逻辑
    if check_ping_status; then
      ping_display="${GREEN}已允许${RESET}"
    else
      ping_display="${RED}已禁止${RESET}"
    fi

    if [ -f /usr/local/bin/ufw-docker ]; then
      docker_tool_display="${GREEN}已安装${RESET}"
    else
      docker_tool_display="${GRAY}未安装${RESET}"
    fi

    # --- 菜单显示 ---
    clear
    echo -e "${BOLD}${CYAN}UFW 防火墙高级管理${RESET}"
    echo -e "${CYAN}==================================================${RESET}"
    echo -e "当前状态: $ufw_status  |  默认策略: $policy_display"
    echo -e "SSH 端口: ${YELLOW}$ssh_port${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    if [ "$is_active" = true ]; then
      echo -e "${YELLOW}  1.${RESET} 重载配置 (Reload) ${GREEN}[自动保活SSH]${RESET}"
      echo -e "${YELLOW}  2.${RESET} 关闭防火墙 (Disable)"
      echo -e "${YELLOW}  3.${RESET} 查看规则列表 (含编号)"
    else
      echo -e "${YELLOW}  1.${RESET} 开启防火墙 (Enable)"
      echo -e "${GRAY}  2. 关闭防火墙 (当前已关闭)${RESET}"
      echo -e "${GRAY}  3. 查看规则列表 (需开启)${RESET}"
    fi

    echo -e "${CYAN}--- 规则管理 ---${RESET}"
    echo -e "${YELLOW}  4.${RESET} 放行端口 (Allow Port)"
    echo -e "${YELLOW}  5.${RESET} 放行应用 (Allow App)"
    if [ "$is_active" = true ]; then
      echo -e "${YELLOW}  6.${RESET} ${RED}删除规则 (Delete Rule)${RESET} ${GREEN}[安全交互]${RESET}"
    else
      echo -e "${GRAY}  6. 删除规则 (需开启)${RESET}"
    fi

    echo -e "${CYAN}--- 安全策略 ---${RESET}"
    echo -e "${YELLOW}  7.${RESET} 封禁 IP (Deny IP)"
    echo -e "${YELLOW}  8.${RESET} 信任 IP (Allow IP)"
    if [ "$is_active" = true ]; then
      echo -e "${YELLOW}  9.${RESET} $limit_display"
    else
      echo -e "${GRAY}  9. $limit_display${RESET}"
    fi

    echo -e "${CYAN}--- 高级功能 ---${RESET}"
    echo -e "${YELLOW} 12.${RESET} Docker 防火墙修复 (UFW-Docker) [${docker_tool_display}]"
    echo -e "${YELLOW} 13.${RESET} ICMP(Ping) 控制 (当前: ${ping_display})"
    echo -e "${YELLOW} 14.${RESET} 调整日志级别 (Log Level)"
    echo -e "${YELLOW} 15.${RESET} 备份与恢复配置 (Backup/Restore)"

    echo -e "${CYAN}--- 系统 ---${RESET}"
    echo -e "${YELLOW} 10.${RESET} ${RED}重置防火墙 (Reset) [慎用]${RESET}"
    echo -e "${YELLOW} 11.${RESET} ${RED}卸载 UFW 防火墙${RESET}"
    echo -e "${YELLOW}  0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"

    ui_menu_read u_choice "请输入选项" "[0-9]|1[0-5]" || return 0

    case $u_choice in
    1)
      if [ "$IS_PVE" = true ] && [ "$is_active" != true ]; then
        echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e "${RED}[!] 严正警告: 检测到当前系统为 Proxmox VE (PVE) 宿主机！${RESET}"
        echo -e "${YELLOW}Proxmox VE 依赖底层虚拟网桥(vmbr)及内置集群防火墙(pve-firewall)。${RESET}"
        echo -e "${YELLOW}启用外部 UFW 会重置默认 FORWARD/INPUT 策略，极易导致虚拟机/容器断网、Corosync 集群失效或 Web 面板 (8006) 无法访问！${RESET}"
        echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        ui_confirm pve_ufw_confirm "是否仍然确认执意开启 UFW 防火墙? [y/N] (q 取消): "
        if [[ ! "$pve_ufw_confirm" =~ ^[Yy]$ ]]; then
          echo -e "${GREEN}[√] 操作已取消，保持 PVE 原生网络环境。${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
      fi
      local ssh_ports_to_allow
      ssh_ports_to_allow=($(get_all_ssh_ports))
      local allow_failed=false
      for sp in "${ssh_ports_to_allow[@]}"; do
        if ! ufw allow "$sp/tcp" comment "SSH-Anti-Lockout" >/dev/null 2>&1; then
          echo -e "${RED}[!] 警告: 放行 SSH 端口 $sp 失败！${RESET}"
          allow_failed=true
        else
          echo -e "${GREEN}[√] 已放行 SSH 防失联端口: ${sp}/tcp${RESET}"
        fi
      done
      if [ "$allow_failed" = true ]; then
        echo -e "${RED}[×] SSH 防失联端口未能完全放行，为防止断联已放弃启用防火墙！${RESET}"
        ui_pause "按 Enter 继续..."
        continue
      fi
      if [ "$is_active" = true ]; then
        ufw reload
        echo -e "${GREEN}[√] 重载完成${RESET}"
      else
        ufw --force enable
        echo -e "${GREEN}[√] 防火墙已开启${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    2)
      if [ "$is_active" = false ]; then echo -e "${YELLOW}已关闭${RESET}"; else
        ufw disable
        echo -e "${YELLOW}[!] 已关闭${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    3)
      if [ "$is_active" = false ]; then echo -e "${RED}需开启${RESET}"; else ufw status numbered; fi
      ui_pause "按 Enter 继续..."
      ;;
    4)
      # [UX优化] 端口智能输入
      ui_read_field port "端口/范围 (例如 80 或 80/tcp) [0 返回]: " ui_any_value "" true || { port=0; }
      [ "$port" == "0" ] || [ -z "$port" ] && continue

      # 检测用户是否已经输入了协议 (如 80/tcp)
      if [[ "$port" == *"/"* ]]; then
        arg="" # 如果带斜杠，直接作为参数
        echo -e "${GRAY}检测到已包含协议，跳过协议选择...${RESET}"
      else
        # [修复] 添加默认值提示
        ui_menu_read pidx "协议 (1:TCP [默认] 2:UDP 3:All)" "[0-3]" 1 || return 0
        [ "$pidx" = 0 ] && continue
        arg="/tcp"
        [ "$pidx" == "2" ] && arg="/udp"
        [ "$pidx" == "3" ] && arg=""
        # 空输入时明确提示使用默认值
        [ -z "$pidx" ] && echo -e "${GRAY}使用默认: TCP${RESET}"
        port="${port}${arg}"
      fi

      ui_read_field cmt "备注（留空使用默认）" ui_any_value || continue
      [ -z "$cmt" ] && cmt="Manual"
      ufw allow "$port" comment "$cmt"
      echo -e "${GREEN}[√] 添加成功: $port${RESET}"
      ui_pause "按 Enter 继续..."
      ;;
    5)
      ufw app list
      ui_read_field app "应用名" ui_nonempty "" true || continue
      [ "$app" != "0" ] && [ -n "$app" ] && ufw allow "$app"
      ui_pause "按 Enter 继续..."
      ;;
    6)
      [ "$is_active" = false ] && {
        echo -e "${RED}需开启${RESET}"
        ui_pause "按 Enter 继续..."
        continue
      }
      # [UX优化] 先展示列表
      echo -e "${CYAN}>>> 当前规则列表:${RESET}"
      ufw status numbered
      echo -e "${CYAN}--------------------------${RESET}"

      ui_read_field n "请输入要删除的规则编号" ui_positive "" true || continue
      if [[ "$n" =~ ^[0-9]+$ ]] && [ "$n" -ne 0 ]; then
        # [UX优化] 获取规则内容用于回显，防止误删
        # [修复] 使用更精确的正则匹配，避免 1 匹配到 11、21 等
        rule_content=$(ufw status numbered | grep -E "^\[\s*$n\]")

        if [ -n "$rule_content" ]; then
          echo -e "\n${YELLOW}即将删除以下规则:${RESET}"
          echo -e "${CYAN}${rule_content}${RESET}"
          ui_confirm confirm_del "请再次确认删除? [y/N] (q 取消): "
          if [[ "$confirm_del" =~ ^[Yy]$ ]]; then
            echo "y" | ufw delete "$n" >/dev/null 2>&1
            echo -e "${GREEN}[√] 规则已删除${RESET}"
            echo -e "${GRAY}提示: 后续规则编号已自动前移${RESET}"
          else
            echo -e "${YELLOW}已取消删除${RESET}"
          fi
        else
          echo -e "${RED}[!] 找不到编号为 $n 的规则${RESET}"
        fi
      fi
      ui_pause "按 Enter 继续..."
      ;;
    7)
      ui_read_field ip "封禁 IP" validate_ip "" true || continue
      [ "$ip" != "0" ] && [ -n "$ip" ] && ufw deny from "$ip" && echo -e "${GREEN}[√] 已封禁${RESET}"
      ui_pause "按 Enter 继续..."
      ;;
    8)
      ui_read_field ip "信任 IP" validate_ip "" true || continue
      [ "$ip" != "0" ] && [ -n "$ip" ] && ufw allow from "$ip" && echo -e "${GREEN}[√] 已信任${RESET}"
      ui_pause "按 Enter 继续..."
      ;;
    9)
      [ "$is_active" = false ] && {
        echo -e "${RED}需开启${RESET}"
        ui_pause "按 Enter 继续..."
        continue
      }
      if [ "$limit_is_on" = true ]; then
        ui_confirm c "确认关闭 Limit (回退为 Allow)? [y/N] (q 取消): "
        if [[ "$c" =~ ^[Yy]$ ]]; then
          ufw delete limit "$ssh_port/tcp" >/dev/null 2>&1
          ufw allow "$ssh_port/tcp" comment "SSH-Allow" >/dev/null 2>&1
          echo -e "${GREEN}[√] 已恢复普通放行${RESET}"
        fi
      else
        ui_confirm c "确认开启 Limit (30s/6次)? [y/N] (q 取消): "
        if [[ "$c" =~ ^[Yy]$ ]]; then
          ufw delete allow "$ssh_port/tcp" >/dev/null 2>&1
          ufw limit "$ssh_port/tcp" comment "SSH-Limit"
          echo -e "${GREEN}[√] Limit 已开启${RESET}"
        fi
      fi
      ui_pause "按 Enter 继续..."
      ;;
    10)
      # [UX优化] 增加对 Docker/自定义规则丢失的明确警告
      echo -e "${RED}>>> 危险警告：重置将删除所有规则！${RESET}"
      echo -e "${YELLOW}注意: 这将清除所有 UFW-Docker 规则和自定义端口，仅自动尝试放行 SSH。${RESET}"
      ui_confirm c "确认执行重置? [y/N] (q 取消): "
      if [[ "$c" =~ ^[Yy]$ ]]; then
        ufw --force reset
        ufw default deny incoming
        ufw default allow outgoing
        local reset_ports
        reset_ports=($(get_all_ssh_ports))
        local reset_allow_fail=false
        for rp in "${reset_ports[@]}"; do
          if ! ufw allow "$rp/tcp" comment "SSH-Anti-Lockout" >/dev/null 2>&1; then
            echo -e "${RED}[!] 警告: 放行 SSH 端口 $rp 失败！${RESET}"
            reset_allow_fail=true
          else
            echo -e "${GREEN}[√] 已放行 SSH 防失联端口: ${rp}/tcp${RESET}"
          fi
        done
        if [ "$reset_allow_fail" = true ]; then
          echo -e "${RED}[×] SSH 防失联端口未能完全放行，未启用防火墙以防失联！${RESET}"
        else
          ufw --force enable
          echo -e "${GREEN}[√] 重置完成${RESET}"
        fi
      fi
      ui_pause "按 Enter 继续..."
      ;;
    11)
      ui_confirm c "确认卸载 UFW? [y/N] (q 取消): "
      if [[ "$c" =~ ^[Yy]$ ]]; then
        ufw disable
        $PKG_REMOVE ufw
        rm -rf /etc/ufw
        hash -r
        # 清理 docker 工具
        [ -f /usr/local/bin/ufw-docker ] && rm -f /usr/local/bin/ufw-docker
        echo -e "${GREEN}[√] 已卸载${RESET}"
        SKIP_PAUSE=true
        return 0
      fi
      ;;
    12)
      echo -e "${CYAN}>>> Docker 防火墙修复 (UFW-Docker)${RESET}"
      if [ ! -f /usr/local/bin/ufw-docker ]; then
        echo -e "${YELLOW}正在下载工具...${RESET}"
        wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
        chmod +x /usr/local/bin/ufw-docker
      fi
      echo -e "\n  1. 自动修复 (Install & Reload)"
      echo -e "  2. 检查状态"
      ui_menu_read d_opt "选项" "[0-2]" || return 0
      case $d_opt in
      1)
        ufw-docker install
        ufw reload
        echo -e "${GREEN}[√] 修复完成${RESET}"
        ;;
      2) ufw-docker check ;;
      esac
      ui_pause "按 Enter 继续..."
      ;;
    13)
      if check_ping_status; then
        # 当前状态: 允许 -> 设为禁止
        echo -e "${CYAN}当前状态: ${GREEN}允许 Ping${RESET}"
        ui_confirm c "是否【禁止】 Ping (隐藏服务器)? [y/N] (q 取消): "
        if [[ "$c" =~ ^[Yy]$ ]]; then
          # [修复] 同时备份 IPv4 和 IPv6 规则文件
          cp /etc/ufw/before.rules /etc/ufw/before.rules.bak 2>/dev/null
          cp /etc/ufw/before6.rules /etc/ufw/before6.rules.bak 2>/dev/null
          if [ -f /etc/ufw/before.rules ]; then modify_ping_rule "drop" "/etc/ufw/before.rules" "icmp"; fi
          if [ -f /etc/ufw/before6.rules ]; then modify_ping_rule "drop" "/etc/ufw/before6.rules" "icmpv6"; fi
          ufw reload >/dev/null
          echo -e "${YELLOW}[!] 已禁止 Ping (双栈生效)${RESET}"
        fi
      else
        # 当前状态: 禁止 -> 设为允许
        echo -e "${CYAN}当前状态: ${YELLOW}已禁止 Ping${RESET}"
        ui_confirm c "是否【允许】 Ping (恢复默认)? [y/N] (q 取消): "
        if [[ "$c" =~ ^[Yy]$ ]]; then
          if [ -f /etc/ufw/before.rules ]; then modify_ping_rule "accept" "/etc/ufw/before.rules" "icmp"; fi
          if [ -f /etc/ufw/before6.rules ]; then modify_ping_rule "accept" "/etc/ufw/before6.rules" "icmpv6"; fi
          ufw reload >/dev/null
          echo -e "${GREEN}[√] 已允许 Ping (恢复默认)${RESET}"
        fi
      fi
      ui_pause "按 Enter 继续..."
      ;;
    14)
      echo -e "当前: $(grep "LOGLEVEL" /etc/ufw/ufw.conf | cut -d= -f2)"
      echo -e "可选: off, low, medium, high"
      ui_read_field lvl "输入级别 [0 返回]: " ui_any_value "" true || { lvl=0; }
      if [ "$lvl" != "0" ] && [ -n "$lvl" ]; then
        ufw logging "$lvl"
        echo -e "${GREEN}[√] 已设置${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    15)
      echo -e "  1. 备份配置\n  2. 恢复配置"
      ui_menu_read bk_opt "选项" "[0-2]" || return 0
      case $bk_opt in
      1)
        bk_file="/root/ufw_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
        tar -czf "$bk_file" /etc/ufw /lib/ufw/user* 2>/dev/null
        echo -e "${GREEN}[√] 备份至: $bk_file${RESET}"
        ;;
      2)
        ui_read_field r_file "备份路径" ui_nonempty || continue
        if [ -f "$r_file" ]; then
          echo -e "${CYAN}正在严格验证备份文件安全性...${RESET}"
          local file_list
          file_list=$(tar -tzf "$r_file" 2>/dev/null)
          if [ -z "$file_list" ]; then
            echo -e "${RED}[×] 备份文件损坏或非有效 tar.gz 归档${RESET}"
          else
            # 检查是否有越界路径或非法目标 (严格白名单: 仅允许 etc/ufw/ 与 lib/ufw/user*)
            if echo "$file_list" | grep -qE '(\.\./|/\.\.)'; then
              echo -e "${RED}[!] 安全拦截: 归档包含危险的相对路径分量 (..)，已阻止解压！${RESET}"
            else
              local non_ufw_files
              non_ufw_files=$(echo "$file_list" | sed -E 's@^(\./|/)+@@' | grep -vE '^$' | grep -vE '^(etc/?|etc/ufw(/.*)?|lib/?|lib/ufw/?|lib/ufw/user.*)$'); [ -n "$r_file" ] && [ -f "$r_file" ] && bad_links=$(tar -tvf "$r_file" 2>/dev/null | grep -E '(link to|->)' | while read -r l_entry; do l_tgt=$(echo "$l_entry" | sed -E 's@^.*(link to|->)[[:space:]]+@@'); [ -n "$l_tgt" ] || continue; if echo "$l_tgt" | grep -qE '(\.\./|/\.\.|\.\.$|^\.\.$)'; then echo "$l_tgt"; else l_norm=$(echo "$l_tgt" | sed -E 's@^(\./|/)+@@'); if ! echo "$l_norm" | grep -qE '^(etc/ufw(/.*)?|lib/ufw/user.*)$'; then echo "$l_tgt"; fi; fi; done) && [ -n "$bad_links" ] && non_ufw_files="${non_ufw_files}${non_ufw_files:+$'\n'}${bad_links}"
              if [ -n "$non_ufw_files" ]; then
                echo -e "${RED}[!] 安全拦截: 归档包含非 UFW 配置文件或目录，已阻止解压！${RESET}"
                echo -e "${GRAY}违规成员样例: $(echo "$non_ufw_files" | head -3 | tr '\n' ' ')${RESET}"
              elif ! echo "$file_list" | sed -E 's@^(\./|/)+@@' | grep -qE '^etc/ufw/'; then
                echo -e "${RED}[!] 归档中未找到任何 etc/ufw/ 配置条目${RESET}"
              else
                # 恢复前先快照当前配置并记录文件清单 (用于在失败时彻底清理新引入的文件)
                local cur_ufw_snap="/tmp/ufw_cur_snap.$$.tar.gz"
                local pre_files_list="/tmp/ufw_pre_files.$$.list"
                find /etc/ufw /lib/ufw -type f 2>/dev/null > "$pre_files_list"
                local snap_items=()
                [ -d /etc/ufw ] && snap_items+=("etc/ufw")
                [ -d /lib/ufw ] && snap_items+=("lib/ufw")
                if [ ${#snap_items[@]} -gt 0 ] && ! tar -czf "$cur_ufw_snap" -C / "${snap_items[@]}" 2>/dev/null; then
                  echo -e "${RED}[×] 创建 UFW 当前配置快照失败，已中止恢复以防失联${RESET}"
                  rm -f "$pre_files_list" 2>/dev/null
                  continue
                fi
                
                # 整个归档已严格核验全部成员均为合规 UFW 路径，直接整包解压 (兼容 ./ 前缀与特殊字符)
                if tar -xzf "$r_file" -C / 2>/dev/null; then
                  if ufw reload >/dev/null 2>&1; then
                    echo -e "${GREEN}[√] UFW 配置安全恢复成功${RESET}"
                    rm -f "$cur_ufw_snap" "$pre_files_list" 2>/dev/null
                  else
                    echo -e "${RED}[!] 规则重载失败，正在自动还原当前配置快照并清理新引入的文件...${RESET}"
                    local rollback_ok=true
                    if [ -f "$cur_ufw_snap" ]; then
                      tar -xzf "$cur_ufw_snap" -C / 2>/dev/null || rollback_ok=false
                    fi
                    if [ -f "$pre_files_list" ]; then
                      while read -r curr_f; do
                        [ -n "$curr_f" ] || continue
                        if ! grep -Fqx "$curr_f" "$pre_files_list"; then
                          rm -f "$curr_f" || rollback_ok=false
                        fi
                      done < <(find /etc/ufw /lib/ufw -type f 2>/dev/null)
                    fi
                    ufw reload >/dev/null 2>&1 || rollback_ok=false
                    if [ "$rollback_ok" = true ]; then
                      echo -e "${YELLOW}[!] 已成功回滚至原配置并清理残留文件${RESET}"
                      rm -f "$cur_ufw_snap" "$pre_files_list" 2>/dev/null
                    else
                      echo -e "${RED}[×] 回滚恢复或重载未能完全成功，保留快照备份: $cur_ufw_snap${RESET}"
                      rm -f "$pre_files_list" 2>/dev/null
                    fi
                  fi
                else
                  echo -e "${RED}[×] 解压恢复失败，正在自动还原...${RESET}"
                  local rollback_ok=true
                  if [ -f "$cur_ufw_snap" ]; then
                    tar -xzf "$cur_ufw_snap" -C / 2>/dev/null || rollback_ok=false
                  fi
                  if [ -f "$pre_files_list" ]; then
                    while read -r curr_f; do
                      [ -n "$curr_f" ] || continue
                      if ! grep -Fqx "$curr_f" "$pre_files_list"; then
                        rm -f "$curr_f" || rollback_ok=false
                      fi
                    done < <(find /etc/ufw /lib/ufw -type f 2>/dev/null)
                  fi
                  ufw reload >/dev/null 2>&1 || rollback_ok=false
                  if [ "$rollback_ok" = true ]; then
                    echo -e "${YELLOW}[!] 已成功回滚至原配置并清理残留文件${RESET}"
                    rm -f "$cur_ufw_snap" "$pre_files_list" 2>/dev/null
                  else
                    echo -e "${RED}[×] 回滚恢复或重载未能完全成功，保留快照备份: $cur_ufw_snap${RESET}"
                    rm -f "$pre_files_list" 2>/dev/null
                  fi
                fi
              fi
            fi
          fi
        else
          echo -e "${RED}文件不存在${RESET}"
        fi
        ;;
      esac
      ui_pause "按 Enter 继续..."
      ;;
    0)
      SKIP_PAUSE=true
      break
      ;;
    *)
      echo -e "${RED}无效选项${RESET}"
      sleep 1
      ;;
    esac
  done
}

# ---------- [PVE 辅助] 安全处理 Proxmox VE 企业订阅源与 Ceph 源 (严格遵循 DEB822 Stanza 规范与通道隔离) ----------
disable_pve_enterprise_sources() {
  [ "$IS_PVE" != true ] && return 0
  
  # 1. 传统 .list 格式：仅注释明确包含 enterprise.proxmox.com 的行，绝不误杀 ceph.list 中的 no-subscription 免费通道
  for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
    [ -f "$f" ] || continue
    if grep -qE "^[[:space:]]*deb[[:space:]]+.*enterprise\.proxmox\.com" "$f" 2>/dev/null; then
      sed -i -E '/enterprise[.]proxmox[.]com\/debian\/ceph/! s|^[[:space:]]*deb[[:space:]]+(.*enterprise\.proxmox\.com.*)|# deb \1|g' "$f" || return 1
      echo -e "${YELLOW}[!] 已自动注释未订阅的企业源条目: $f${RESET}"
    fi
  done

  # 2. 现代 DEB822 .sources 格式 (PVE 8/9)：逐 Stanza 状态机解析，只对包含 enterprise.proxmox.com 的 Stanza 设置 Enabled: no
  # 杜绝粗暴在文件首行盲目插入 Enabled: no 破坏多 Stanza (如同时包含 PVE 与 Ceph no-sub 的文件)
  for f in /etc/apt/sources.list.d/*.sources; do
    [ -f "$f" ] || continue
    if grep -qi "enterprise\.proxmox\.com" "$f" 2>/dev/null; then
      local tmp_src="${f}.tmp.$$"
      awk '
      function flush_stanza() {
        if (n > 0) {
          if (is_enterprise && !is_disabled) {
            if (has_enabled) {
              lines[enabled_idx] = "Enabled: no"
            } else {
              print "Enabled: no"
            }
          }
          for (i = 1; i <= n; i++) {
            print lines[i]
          }
        }
        n = 0
        is_enterprise = 0
        has_enabled = 0
        enabled_idx = 0
        is_disabled = 0
      }
      /^[[:space:]]*$/ {
        flush_stanza()
        print ""
        next
      }
      {
        n++
        lines[n] = $0
        line_str = $0
        sub(/^[[:space:]]+/, "", line_str)
        if (line_str ~ /^#/) next
        if (line_str ~ /^[uU][rR][iI][sS]:.*enterprise\.proxmox\.com/ && line_str !~ /\/debian\/ceph/) is_enterprise = 1
        if (line_str ~ /^[eE][nN][aA][bB][lL][eE][dD]:/) {
          has_enabled = 1
          enabled_idx = n
          if (line_str ~ /^[eE][nN][aA][bB][lL][eE][dD]:[[:space:]]*no/) is_disabled = 1
        }
      }
      END {
        flush_stanza()
      }
      ' "$f" > "$tmp_src" 2>/dev/null

      if [ -s "$tmp_src" ]; then
        mv -f "$tmp_src" "$f" || return 1
        echo -e "${YELLOW}[!] 已对 DEB822 源文件精准禁用企业订阅 Stanza (非企业通道保持原样): $f${RESET}"
      else
        rm -f "$tmp_src" 2>/dev/null
      fi
    fi
  done
}

# 准确检测系统是否已存在处于激活状态的 pve-no-subscription 源 (支持注释识别与 DEB822 多 Stanza 判定)
has_active_pve_no_subscription() {
  # 1. 传统 .list 文件
  for sf in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
    [ -f "$sf" ] || continue
    if grep -qE "^[[:space:]]*deb[[:space:]]+.*pve-no-subscription" "$sf" 2>/dev/null; then
      return 0
    fi
  done

  # 2. 现代 DEB822 .sources 文件 (逐 Stanza 解析，必须包含 pve-no-subscription 且未标注 Enabled: no)
  for sf in /etc/apt/sources.list.d/*.sources; do
    [ -f "$sf" ] || continue
    if awk '
      function check_stanza() {
        if (has_repo && !is_disabled) { found = 1 }
        has_repo = 0
        is_disabled = 0
      }
      /^[[:space:]]*$/ { check_stanza(); next }
      {
        line = $0; sub(/^[[:space:]]+/, "", line)
        if (line ~ /^#/) next
        if (line ~ /pve-no-subscription/) has_repo = 1
        if (line ~ /^[eE][nN][aA][bB][lL][eE][dD]:[[:space:]]*no/) is_disabled = 1
      }
      END { check_stanza(); if (found) exit 0; else exit 1 }
    ' "$sf" 2>/dev/null; then
      return 0
    fi
  done

  return 1
}

# 安全配置 PVE 替代无订阅源 (规范附带 Signed-By 密钥路径与镜像源支持)
setup_pve_no_subscription_source() {
  [ "$IS_PVE" != true ] && return 0
  local cur_codename="$1"
  [ -z "$cur_codename" ] && cur_codename="${OS_CODENAME:-}"
  case "$cur_codename" in
    stretch|buster|bullseye|bookworm|trixie) ;;
    *) echo -e "${RED}[×] 无法确认 PVE 对应 Debian 代号，停止补源，避免跨版本混源${RESET}"; return 1 ;;
  esac
  local mirror_opt="$2"

  if has_active_pve_no_subscription; then
    echo -e "${GRAY}[i] 系统已存在有效启用的 pve-no-subscription 源，跳过重复配置${RESET}"
    return 0
  fi

  # 定位系统中的 Proxmox 官方签名 Key
  local key_path=""
  for k in "/usr/share/keyrings/proxmox-archive-keyring.gpg" \
           "/etc/apt/trusted.gpg.d/proxmox-release-${cur_codename}.gpg" \
           "/etc/apt/trusted.gpg.d/proxmox-ve-release-${cur_codename}.gpg"; do
    if [ -f "$k" ]; then
      key_path="$k"
      break
    fi
  done

  local pve_base_url="http://download.proxmox.com/debian/pve"
  [ "$mirror_opt" != "4" ] && pve_base_url="https://mirrors.ustc.edu.cn/proxmox/debian/pve"

  local target_list="/etc/apt/sources.list.d/pve-no-subscription.list"
  local signed_by_attr=""
  [ -n "$key_path" ] && signed_by_attr="[signed-by=$key_path] "
  echo "deb ${signed_by_attr}${pve_base_url} ${cur_codename} pve-no-subscription" > "$target_list"
  echo -e "${GREEN}[√] 已安全配置 PVE 替代无订阅源 (Signed-By: ${key_path:-系统默认信任环}): $target_list${RESET}"
}

# --- [6. 系统] 软件包更新与清理 ---
system_upgrade() {
  # 检测是否为已停更 (EOL) 发行版或 ESM 扩展支持阶段
  local is_eol=false is_esm=false
  local eol_reason="" esm_reason=""
  if [ "$OS_ID" == "debian" ] && [ -n "$OS_VERSION_ID" ]; then
    local deb_major="${OS_VERSION_ID%%.*}"
    if [ "$deb_major" -le 10 ] 2>/dev/null; then
      is_eol=true
      eol_reason="Debian ${OS_VERSION_ID} (LTS维护期已过，官方源已归档)"
    fi
  elif [ "$OS_ID" == "ubuntu" ] && [ -n "$OS_VERSION_ID" ]; then
    case "$OS_VERSION_ID" in
      14.04|16.04)
        is_eol=true
        eol_reason="Ubuntu ${OS_VERSION_ID} (标准支持终止，官方源已移至 old-releases)"
        ;;
      18.04)
        is_esm=true
        esm_reason="Ubuntu 18.04 (标准支持已结束，当前处于 ESM 扩展安全维护阶段，主源仍在 archive.ubuntu.com)"
        ;;
    esac
  elif [ "$OS_ID" == "centos" ]; then
    local is_centos_stream=false
    grep -qi "stream" /etc/os-release 2>/dev/null && is_centos_stream=true
    if [ "$is_centos_stream" = false ]; then
      is_eol=true
      eol_reason="CentOS Linux 7/8 (上游已全面 EOL 停更，官方源已归档至 Vault)"
    elif [[ "$OS_VERSION_ID" =~ ^8 ]]; then
      is_eol=true
      eol_reason="CentOS Stream 8 (已于 2024-05-31 正式 EOL 停更，官方源已移至 Vault 归档)"
    fi
  fi

  if [ "$is_esm" = true ]; then
    echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${CYAN}[i] 发行版生命周期提示: 当前系统处于 ESM (扩展安全维护) 阶段${RESET}"
    echo -e "${GRAY}说明: ${esm_reason}${RESET}"
    echo -e "${GRAY}官方主源保持可用；未绑定 Ubuntu Pro 订阅的主机仍可获取基础版本库更新。${RESET}"
    echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  fi

  if [ "$is_eol" = true ]; then
    echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${YELLOW}[!] 生命周期警示: 当前系统属于已停止上游安全维护 (EOL) 的发行版！${RESET}"
    echo -e "${GRAY}原因: ${eol_reason}${RESET}"
    echo -e "${YELLOW}普通的软件包更新仅能安装归档中的最后版本，无法获得新的漏洞安全修复。${RESET}"
    echo -e "${CYAN}说明: 如需大版本升级，请使用各发行版专用迁移工具或重装受支持的现代系统。${RESET}"
    echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    ui_confirm eol_confirm "是否仍然继续更新当前归档包? [y/N] (q 取消): "
    if [[ ! "$eol_confirm" =~ ^[Yy]$ ]]; then
      echo -e "${YELLOW}操作已取消${RESET}"
      SKIP_PAUSE=true
      return 0
    fi
  fi

  echo -e "${CYAN}>>> 准备进行系统升级与缓存清理${RESET}"
  echo -e "${YELLOW}注意: 这将更新所有软件包，可能需要一些时间。${RESET}"
  ui_confirm confirm "确认继续吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}操作已取消${RESET}"
    SKIP_PAUSE=true
    return 0
  fi
  
  echo -e "${CYAN}>>> 系统升级与清理开始...${RESET}"
  check_pm_lock || return 1

  local upgrade_ok=false

  if [[ "$PKG_MANAGER" == "apt" ]]; then
    export DEBIAN_FRONTEND=noninteractive
    if [ "$IS_PVE" = true ]; then
      echo -e "${CYAN}>>> 检测到 Proxmox VE (PVE) 环境，正在执行安全兼容升级...${RESET}"
      # 升级入口保持订阅与 Ceph 通道；切换仓库只在换源菜单显式执行。
      echo -e "${GRAY}[i] 保留现有 PVE/Ceph 仓库通道；401 错误请在换源菜单确认处理${RESET}"
    fi
    # [修复] 添加升级失败的错误处理
    if ! apt-get update -y; then
      echo -e "${RED}[!] 软件源更新失败${RESET}"
      return 1
    fi
    if [ "$IS_PVE" = true ]; then
      # PVE 官方建议 dist-upgrade，但不执行盲目 autoremove 以免卸载 proxmox-ve
      apt-get dist-upgrade -y && apt-get autoclean -y && upgrade_ok=true
    else
      apt-get dist-upgrade -y && apt-get autoremove -y && apt-get autoclean -y && upgrade_ok=true
    fi
  elif [[ "$PKG_MANAGER" == "dnf5" ]]; then
    dnf5 upgrade -y && dnf5 autoremove -y && dnf5 clean all && upgrade_ok=true
  elif [[ "$PKG_MANAGER" == "dnf" ]]; then
    dnf update -y && dnf autoremove -y && dnf clean all && upgrade_ok=true
  elif [[ "$PKG_MANAGER" == "yum" ]]; then
    yum update -y && yum autoremove -y && yum clean all && upgrade_ok=true
  fi

  # [修复] 升级后清理硬件信息缓存，确保系统版本号即时更新
  rm -f "${TEMP_DIR}/hardware_cache"

  if [ "$upgrade_ok" = true ]; then
    echo -e "${GREEN}[√] 系统升级与清理完成${RESET}"
  else
    echo -e "${RED}[×] 系统升级过程中发生错误，请检查上方日志${RESET}"
    return 1
  fi
}

# --- [6. 系统] BBR内核加速管理 ---
# 前置依赖: 必须先完成 check_dependencies
manage_bbr() {
  # 注: is_container_env() 已在脚本顶部定义为全局函数

  # [辅助函数] 检测 BBR 模块是否可用
  is_bbr_available() {
    # 检查内核是否内置或可加载 BBR 模块
    if grep -wq "bbr" /proc/sys/net/ipv4/tcp_available_congestion_control 2>/dev/null; then
      return 0
    fi
    # 尝试加载模块 (仅在非容器环境有效)
    modprobe tcp_bbr 2>/dev/null
    if grep -wq "bbr" /proc/sys/net/ipv4/tcp_available_congestion_control 2>/dev/null; then
      return 0
    fi
    return 1
  }

  # [辅助函数] 检测 default_qdisc 参数是否可用 (容器环境通常不可用)
  is_qdisc_configurable() {
    [ -f /proc/sys/net/core/default_qdisc ]
  }

  while true; do
    clear
    echo -e "${CYAN}>>> BBR 内核加速管理${RESET}"
    
    # --- 环境检测与警告 ---
    local is_container=false
    local container_warning=""
    if is_container_env; then
      is_container=true
      container_warning="${RED}[!] 检测到容器环境 (OpenVZ/LXC/Docker)，可能无法修改内核参数${RESET}"
    fi
    
    # 检测 BBR 模块状态
    local bbr_available=false
    local bbr_status="不可用"
    if is_bbr_available; then
      bbr_available=true
      # 进一步检测是否已加载
      if command -v lsmod >/dev/null 2>&1 && lsmod 2>/dev/null | grep -wq "tcp_bbr"; then
        bbr_status="${GREEN}已加载${RESET}"
      elif grep -wq "bbr" /proc/sys/net/ipv4/tcp_available_congestion_control 2>/dev/null; then
        bbr_status="${YELLOW}内核内置${RESET}"
      fi
    else
      bbr_status="${RED}不可用 (内核未编译/无法加载模块)${RESET}"
    fi
    
    # 检测 qdisc 状态
    local qdisc_available=false
    local qdisc_status="${RED}不可配置${RESET}"
    if is_qdisc_configurable; then
      qdisc_available=true
      qdisc_status="${GREEN}可配置${RESET}"
    fi
    
    # 检测当前状态
    local current_algo=$(sysctl net.ipv4.tcp_congestion_control 2>/dev/null | awk '{print $3}')
    local current_qdisc=$(sysctl net.core.default_qdisc 2>/dev/null | awk '{print $3}')
    
    echo -e "当前拥塞控制算法: ${GREEN}${current_algo:-未知}${RESET}"
    echo -e "当前队列管理算法: ${GREEN}${current_qdisc:-未知}${RESET}"
    echo -e "BBR 模块状态:     $bbr_status"
    echo -e "队列调度器:       $qdisc_status"
    [ -n "$container_warning" ] && echo -e "$container_warning"
    echo -e "${CYAN}--------------------------------------------------${RESET}"
    
    echo -e "  1. 开启 BBR + FQ"
    echo -e "  2. 开启 BBR + CAKE (如果不支会自动回退)"
    echo -e "  3. 关闭 BBR (恢复默认)"
    echo -e "  0. 返回上一级"
    
    ui_menu_read choice "请输入选项" "[0-3]" || return 0
    case $choice in
      1|2)
        # --- 前置检查: 容器环境警告 ---
        if [ "$is_container" = true ]; then
          echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
          echo -e "${RED}[!] 警告: 检测到容器化虚拟环境 (如 NAT VPS)${RESET}"
          echo -e "${YELLOW}容器共享宿主机内核，通常无法:${RESET}"
          echo -e "${GRAY}  • 加载内核模块 (tcp_bbr)${RESET}"
          echo -e "${GRAY}  • 修改 net.core.default_qdisc 参数${RESET}"
          echo -e "${YELLOW}BBR 需要宿主机内核已启用支持才能生效。${RESET}"
          echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
          ui_confirm force_continue "仍要继续尝试? [y/N] (q 取消): "
          if [[ ! "$force_continue" =~ ^[Yy]$ ]]; then
            continue
          fi
        fi
        
        # --- 前置检查: BBR 模块可用性 ---
        if [ "$bbr_available" = false ]; then
          echo -e "${RED}[!] BBR 模块不可用，请检查:${RESET}"
          echo -e "${GRAY}  • 内核版本是否 >= 4.9${RESET}"
          echo -e "${GRAY}  • 内核是否编译了 tcp_bbr 模块${RESET}"
          echo -e "${GRAY}  • 是否有权限加载内核模块 (非容器环境)${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
        
        # 检查内核版本
        local kernel_ver=$(uname -r | cut -d- -f1)
        # 简单比对主版本 >= 4.9
        if [[ "$(echo "$kernel_ver" | cut -d. -f1)" -lt 4 ]] || \
           ([[ "$(echo "$kernel_ver" | cut -d. -f1)" -eq 4 ]] && [[ "$(echo "$kernel_ver" | cut -d. -f2)" -lt 9 ]]); then
           echo -e "${RED}[!] 内核版本过低 ($kernel_ver)，BBR 需要 Linux Kernel 4.9+${RESET}"
           ui_pause "按 Enter 继续..."
           continue
        fi

        echo -e "${CYAN}>>> 正在配置系统参数...${RESET}"
        
        # [兼容性修复] 使用 /etc/sysctl.d/ 目录存储配置
        # Debian 13+ 的 systemd-sysctl 默认不读取 /etc/sysctl.conf
        local BBR_CONF="/etc/sysctl.d/99-bbr.conf"
        
        # 1. 运行态与配置快照备份 (用于失败时精准回滚)
        local orig_algo
        orig_algo=$(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null)
        local orig_qdisc
        orig_qdisc=$(sysctl -n net.core.default_qdisc 2>/dev/null)
        local bbr_snap_conf="${TEMP_DIR}/bbr_sysctl_conf.$$.bak"
        local bbr_snap_d="${TEMP_DIR}/bbr_dropin.$$.bak"
        if [ -f /etc/sysctl.conf ] && ! cp -p /etc/sysctl.conf "$bbr_snap_conf" 2>/dev/null; then
          echo -e "${RED}[×] 备份 /etc/sysctl.conf 失败，已中止操作${RESET}"
          continue
        fi
        if [ -f "$BBR_CONF" ] && ! cp -p "$BBR_CONF" "$bbr_snap_d" 2>/dev/null; then
          echo -e "${RED}[×] 备份 $BBR_CONF 失败，已中止操作${RESET}"
          rm -f "$bbr_snap_conf" 2>/dev/null
          continue
        fi

        # 清理旧配置 (兼容性: 同时清理 sysctl.conf 中的遗留配置)
        if [ -f /etc/sysctl.conf ]; then
          sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf
          sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf
        fi
        
        local qdisc="fq"
        if [ "$choice" == "2" ]; then qdisc="cake"; fi
        
        # 写入新配置文件
        echo "# BBR Congestion Control - Auto generated by script" > "$BBR_CONF"
        if [ "$qdisc_available" = true ]; then
          echo "net.core.default_qdisc = $qdisc" >> "$BBR_CONF"
        fi
        echo "net.ipv4.tcp_congestion_control = bbr" >> "$BBR_CONF"
        
        # 使用 sysctl --system 加载所有配置
        sysctl --system >/dev/null 2>&1
        
        # 验证
        local verify_algo=$(sysctl net.ipv4.tcp_congestion_control 2>/dev/null | awk '{print $3}')
        local verify_qdisc=$(sysctl net.core.default_qdisc 2>/dev/null | awk '{print $3}')
        local bbr_loaded=false
        
        # 检查 BBR 是否真正生效 (模块加载或内核内置)
        if (command -v lsmod >/dev/null 2>&1 && lsmod 2>/dev/null | grep -wq "tcp_bbr") || \
           (grep -wq "bbr" /proc/sys/net/ipv4/tcp_available_congestion_control 2>/dev/null && [ "$verify_algo" = "bbr" ]); then
          bbr_loaded=true
        fi
        
        # --- 综合判断结果 ---
        if [[ "$verify_algo" == "bbr" ]] && [ "$bbr_loaded" = true ]; then
           # 检查 CAKE 是否生效，否则回退到 FQ
           if [ "$choice" == "2" ] && [ "$verify_qdisc" != "cake" ]; then
              echo -e "${YELLOW}[!] CAKE 队列不支持，已自动回退到 FQ${RESET}"
              sed -i 's/net.core.default_qdisc = cake/net.core.default_qdisc = fq/' "$BBR_CONF"
              sysctl -w net.core.default_qdisc=fq >/dev/null 2>&1
              qdisc="fq"
           fi
           
           # 检查 qdisc 是否生效
           if [ "$qdisc_available" = false ]; then
              echo -e "${YELLOW}[!] 队列调度器 (default_qdisc) 无法配置 (容器环境限制)${RESET}"
              echo -e "${GREEN}[√] BBR 拥塞控制已启用${RESET}"
           else
              echo -e "${GREEN}[√] BBR 已成功开启 ($qdisc)${RESET}"
           fi
           rm -f "$bbr_snap_conf" "$bbr_snap_d" 2>/dev/null
        else
           echo -e "${RED}[!] 开启失败${RESET}"
           if [ "$is_container" = true ]; then
              echo -e "${YELLOW}原因: 容器环境无法加载 BBR 模块，需要宿主机内核支持。${RESET}"
              echo -e "${GRAY}提示: 联系您的 VPS 服务商确认宿主机是否已启用 BBR。${RESET}"
           else
              echo -e "${YELLOW}请检查内核日志: dmesg | grep -i bbr${RESET}"
           fi
           # 精准回滚配置与运行时状态并核验返回值
           echo -e "${YELLOW}正在自动回滚原配置与运行时参数...${RESET}"
           local bbr_rb_ok=true
           rm -f "$BBR_CONF"
           if [ -f "$bbr_snap_d" ]; then
             cp -pf "$bbr_snap_d" "$BBR_CONF" || bbr_rb_ok=false
           fi
           if [ -f "$bbr_snap_conf" ]; then
             cp -pf "$bbr_snap_conf" /etc/sysctl.conf || bbr_rb_ok=false
           fi
           if [ -n "$orig_algo" ]; then
             sysctl -w net.ipv4.tcp_congestion_control="$orig_algo" >/dev/null 2>&1 || bbr_rb_ok=false
           fi
           if [ -n "$orig_qdisc" ]; then
             sysctl -w net.core.default_qdisc="$orig_qdisc" >/dev/null 2>&1 || bbr_rb_ok=false
           fi
           if [ "$bbr_rb_ok" = true ]; then
             rm -f "$bbr_snap_conf" "$bbr_snap_d" 2>/dev/null
             echo -e "${GREEN}[√] 原系统网络与 BBR 状态已成功恢复${RESET}"
           else
             echo -e "${RED}[×] BBR 参数自动回滚未完全成功，快照保留在: ${TEMP_DIR}${RESET}"
           fi
        fi
        ui_pause "按 Enter 继续..."
        ;;
      3)
        echo -e "${CYAN}>>> 正在移除 BBR 配置...${RESET}"
        local BBR_CONF="/etc/sysctl.d/99-bbr.conf"
        
        # 1. 删除配置文件
        rm -f "$BBR_CONF"
        
        # 2. 清理旧位置的遗留配置 (兼容性)
        if [ -f /etc/sysctl.conf ]; then
          sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf
          sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf
        fi
        
        # 3. 强制应用默认值 (因为 sysctl --system 无法重置已生效的参数)
        # 通用默认值: cubic + fq_codel
        echo -e "${YELLOW}正在重置运行状态为默认 (cubic + fq_codel)...${RESET}"
        sysctl -w net.ipv4.tcp_congestion_control=cubic 2>/dev/null
        if is_qdisc_configurable; then
          sysctl -w net.core.default_qdisc=fq_codel 2>/dev/null
        fi
        
        echo -e "${GREEN}[√] BBR 配置已移除并重置运行状态${RESET}"
        ui_pause "按 Enter 继续..."
        ;;
      0) return 0 ;;
      *) ;;
    esac
  done
}

# --- [6. 系统] TCP 网络调优 ---
# 基于 BDP (带宽延迟积) 理论自动计算最优 TCP 参数
manage_tcp_tuning() {
  # TCP 调优参数配置
  # 智能检测配置文件路径，确保跨发行版兼容性:
  #   - 现代 systemd 系统 (Debian 8+, RHEL 7+, Ubuntu 16+): 使用 /etc/sysctl.d/
  #   - 老旧系统或特殊环境: 回退到 /etc/sysctl.conf
  local TCP_BACKUP_TAG="# TCP_TUNING_MANAGED"
  local SYSCTL_D_CONF="/etc/sysctl.d/99-tcp-tuning.conf"
  local SYSCTL_CONF="/etc/sysctl.conf"
  local TCP_RUNTIME_BACKUP="/etc/sysctl.d/.aurora-tcp-runtime.backup"
  
  # [智能检测] 选择配置文件路径
  local USE_SYSCTL_D=false
  local TARGET_CONF=""
  if [ -d "/etc/sysctl.d" ]; then
    # 现代 systemd 系统: 使用 sysctl.d 目录
    USE_SYSCTL_D=true
    TARGET_CONF="$SYSCTL_D_CONF"
  else
    # 老旧系统: 回退到 sysctl.conf
    echo -e "${RED}[×] 未检测到 sysctl.d，停止调优以避免覆盖整个 sysctl.conf${RESET}"
    return 1
  fi
  
  # [辅助函数] 获取当前 TCP 参数值
  get_tcp_param() {
    sysctl -n "$1" 2>/dev/null || echo "N/A"
  }
  
  # [辅助函数] 计算 BDP 并生成参数
  # 参数: $1=带宽(Mbps), $2=RTT(ms), $3=倍数(可选, 默认3)
  calculate_bdp_params() {
    local bandwidth_mbps=$1
    local rtt_ms=$2
    local mult=${3:-3}
    
    # BDP = 带宽(Bytes/s) × RTT(s)
    # 带宽: Mbps → Bytes/s = Mbps × 1000000 / 8
    # RTT: ms → s = ms / 1000
    local bandwidth_bytes_per_sec=$(( bandwidth_mbps * 1000000 / 8 ))
    local bdp_bytes=$(( bandwidth_bytes_per_sec * rtt_ms / 1000 ))
    
    # 理论目标最大缓冲区 = BDP × 倍数 (经验基准上限)
    local raw_buffer_max=$(( bdp_bytes * mult ))
    local buffer_max=$raw_buffer_max
    local clamp_flag="none"
    
    # 系统安全边界限制: 保底 4MB, 上限 128MB
    if [ $buffer_max -lt 4194304 ]; then
      buffer_max=4194304
      clamp_flag="min_clamped"
    elif [ $buffer_max -gt 134217728 ]; then
      buffer_max=134217728
      clamp_flag="max_clamped"
    fi
    
    # 默认缓冲区 (default，新建连接初始窗口):
    # 依托 Linux 内核的 TCP 窗口自动调节机制 (DRS)，新建连接初始无需盲目占用数兆内存。
    # 保持稳健合理的默认初始值 (128KB~256KB)，避免短连接资源膨胀；内核按需自动动态扩容至 buffer_max。
    local buffer_default=262144
    [ $buffer_default -gt $(( buffer_max / 2 )) ] && buffer_default=$(( buffer_max / 2 ))
    [ $buffer_default -lt 65536 ] && buffer_default=65536
    
    # 返回: buffer_max buffer_default clamp_flag raw_buffer_max bdp_bytes
    echo "$buffer_max $buffer_default $clamp_flag $raw_buffer_max $bdp_bytes"
  }
  
  # [辅助函数] 获取内核版本号 (用于参数兼容性检测)
  # 返回格式: MAJOR.MINOR (如 5.15)
  get_kernel_version() {
    local kver=$(uname -r | cut -d- -f1)
    local major=$(echo "$kver" | cut -d. -f1)
    local minor=$(echo "$kver" | cut -d. -f2)
    echo "${major}.${minor}"
  }
  
  # [辅助函数] 比较内核版本
  # 参数: $1=当前版本, $2=要求的最低版本
  # 返回: 0=满足要求, 1=不满足
  kernel_version_ge() {
    local current=$1
    local required=$2
    local cur_major=$(echo "$current" | cut -d. -f1)
    local cur_minor=$(echo "$current" | cut -d. -f2)
    local req_major=$(echo "$required" | cut -d. -f1)
    local req_minor=$(echo "$required" | cut -d. -f2)
    
    if [ "$cur_major" -gt "$req_major" ]; then
      return 0
    elif [ "$cur_major" -eq "$req_major" ] && [ "$cur_minor" -ge "$req_minor" ]; then
      return 0
    fi
    return 1
  }
  
  # [辅助函数] 基于系统内存的保守基准预设
  # 说明: 内存大小无法推导网络线路带宽与往返延迟，此处仅按内存规格提供安全的保底配置
  # 返回: buffer_max buffer_default estimated_bandwidth mem_mb estimated_rtt
  auto_detect_system_params() {
    # 获取系统内存 (KB)，防御受限容器或 proc 文件缺失
    local mem_kb=$(awk '/MemTotal/{print $2}' /proc/meminfo 2>/dev/null)
    [[ ! "$mem_kb" =~ ^[0-9]+$ ]] && mem_kb=1048576
    local mem_mb=$(( 10#$mem_kb / 1024 ))
    
    # 针对不同内存规格的服务器，设定保守稳妥的吞吐基准（仅作无输入时的安全默认参考）
    local base_bw=100
    local base_rtt=50
    
    if [ "$mem_mb" -lt 1024 ]; then
      base_bw=50; base_rtt=80
    elif [ "$mem_mb" -lt 2048 ]; then
      base_bw=100; base_rtt=60
    elif [ "$mem_mb" -lt 4096 ]; then
      base_bw=200; base_rtt=50
    elif [ "$mem_mb" -lt 8192 ]; then
      base_bw=500; base_rtt=40
    else
      base_bw=1000; base_rtt=50
    fi
    
    local calc_result
    calc_result=$(calculate_bdp_params "$base_bw" "$base_rtt" 3)
    local buffer_max=$(echo "$calc_result" | awk '{print $1}')
    local buffer_default=$(echo "$calc_result" | awk '{print $2}')
    
    # 单连接单个缓冲区上限安全约束：不超过系统内存的 2% (小内存VPS兜底保底 4MB)
    local mem_limit=$(( mem_kb * 1024 * 2 / 100 ))
    if [ "$buffer_max" -gt "$mem_limit" ]; then
      buffer_max=$mem_limit
      [ $buffer_max -lt 4194304 ] && buffer_max=4194304
    fi
    
    echo "$buffer_max $buffer_default $base_bw $mem_mb $base_rtt"
  }


  
  # [辅助函数] 清理旧的 TCP 调优参数
  # 同时清理 /etc/sysctl.d/99-tcp-tuning.conf 和 /etc/sysctl.conf 中的遗留配置
  clean_tcp_params() {
    local params=(
      "net.core.rmem_max"
      "net.core.wmem_max"
      "net.core.rmem_default"
      "net.core.wmem_default"
      "net.core.optmem_max"
      "net.ipv4.tcp_rmem"
      "net.ipv4.tcp_wmem"
      "net.core.somaxconn"
      "net.core.netdev_max_backlog"
      "net.ipv4.tcp_max_syn_backlog"
      "net.ipv4.tcp_fin_timeout"
      "net.ipv4.tcp_tw_reuse"
      "net.ipv4.tcp_keepalive_time"
      "net.ipv4.tcp_keepalive_probes"
      "net.ipv4.tcp_keepalive_intvl"
      "net.ipv4.tcp_max_tw_buckets"
      "net.ipv4.ip_local_port_range"
      "net.ipv4.tcp_fastopen"
      "net.ipv4.tcp_mtu_probing"
      "net.ipv4.tcp_slow_start_after_idle"
      "net.ipv4.tcp_timestamps"
      "net.ipv4.tcp_window_scaling"
      "net.ipv4.tcp_sack"
      "net.ipv4.tcp_ecn"
      "net.ipv4.tcp_notsent_lowat"
    )
    
    # 只清理脚本管理文件中的目标键；不删除用户 sysctl.conf 中的同名配置。
    if [ -f "$SYSCTL_D_CONF" ]; then
      if ! grep -qF "$TCP_BACKUP_TAG" "$SYSCTL_D_CONF"; then
        echo -e "${RED}[×] TCP 配置文件不是本脚本生成，未清理或覆盖${RESET}"
        return 1
      fi
      local temp_conf="${SYSCTL_D_CONF}.tmp.$$"
      awk -v keys="${params[*]}" '
        BEGIN {n=split(keys,a," "); for(i=1;i<=n;i++) managed[a[i]]=1}
        {key=$0; sub(/^[[:space:]]*/,"",key); sub(/[[:space:]]*=.*/,"",key); if(!(key in managed)) print}
      ' "$SYSCTL_D_CONF" > "$temp_conf" && mv -f "$temp_conf" "$SYSCTL_D_CONF" || return 1
    fi
  }
  
  # [辅助函数] 模式预览: 显示当前值 vs 新值对比
  # 参数: $1=buffer_max, $2=buffer_default, $3=scenario (balanced/throughput/latency)
  preview_tcp_changes() {
    local buffer_max=$1
    local buffer_default=$2
    local scenario=${3:-balanced}
    
    echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${BOLD}参数预览: 当前值 → 新值${RESET}"
    echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    
    # 根据场景计算目标值
    local new_somaxconn=4096
    local new_syn_backlog=8192
    local new_netdev_backlog=5000
    local new_fin_timeout=15
    local new_keepalive_time=600
    local new_notsent_lowat=16384
    local new_optmem=81920
    local new_max_tw=262144
    
    case $scenario in
      throughput)
        new_netdev_backlog=65535
        new_fin_timeout=30
        new_optmem=262144
        new_notsent_lowat=131072
        new_max_tw=400000
        ;;
      latency)
        new_fin_timeout=10
        new_keepalive_time=300
        new_notsent_lowat=4096
        ;;
    esac
    
    # 格式化函数: 字节转人类可读
    format_bytes() {
      local bytes=$1
      if [ "$bytes" -ge 1048576 ]; then
        echo "$(( bytes / 1048576 )) MB"
      elif [ "$bytes" -ge 1024 ]; then
        echo "$(( bytes / 1024 )) KB"
      else
        echo "$bytes B"
      fi
    }
    
    # 比较并显示
    show_diff() {
      local label=$1
      local param=$2
      local new_val=$3
      local is_bytes=${4:-false}
      
      local old_val=$(sysctl -n "$param" 2>/dev/null || echo "N/A")
      
      # 格式化显示
      local old_display="$old_val"
      local new_display="$new_val"
      if [ "$is_bytes" = true ] && [ "$old_val" != "N/A" ]; then
        old_display="$(format_bytes "$old_val")"
        new_display="$(format_bytes "$new_val")"
      fi
      
      # 判断是否变化
      if [ "$old_val" = "$new_val" ]; then
        printf "  ${GRAY}%-24s${RESET} %s ${GRAY}(无变化)${RESET}\n" "$label" "$old_display"
      else
        printf "  ${YELLOW}%-24s${RESET} %s → ${GREEN}%s${RESET}\n" "$label" "$old_display" "$new_display"
      fi
    }
    
    echo -e "${YELLOW}【TCP 缓冲区】${RESET} ${GRAY}(max为内核动态调节上限，default为初始窗口)${RESET}"
    show_diff "rmem_max" "net.core.rmem_max" "$buffer_max" true
    show_diff "wmem_max" "net.core.wmem_max" "$buffer_max" true
    show_diff "optmem_max" "net.core.optmem_max" "$new_optmem" true
    
    echo -e "${YELLOW}【连接队列】${RESET} ${GRAY}(影响高并发处理能力)${RESET}"
    show_diff "somaxconn" "net.core.somaxconn" "$new_somaxconn"
    show_diff "syn_backlog" "net.ipv4.tcp_max_syn_backlog" "$new_syn_backlog"
    show_diff "netdev_backlog" "net.core.netdev_max_backlog" "$new_netdev_backlog"
    
    echo -e "${YELLOW}【连接管理】${RESET} ${GRAY}(控制空闲/孤儿连接释放与保活，不改变网络传输延迟)${RESET}"
    show_diff "fin_timeout" "net.ipv4.tcp_fin_timeout" "$new_fin_timeout"
    show_diff "keepalive_time" "net.ipv4.tcp_keepalive_time" "$new_keepalive_time"
    show_diff "max_tw_buckets" "net.ipv4.tcp_max_tw_buckets" "$new_max_tw"
    
    echo -e "${YELLOW}【队列积压控制】${RESET} ${GRAY}(限制待发数据积压，抑制缓冲膨胀)${RESET}"
    show_diff "tcp_notsent_lowat" "net.ipv4.tcp_notsent_lowat" "$new_notsent_lowat" true
    
    echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  }
  # [辅助函数] 写入 TCP 调优参数
  # 参数: $1=模式名称, $2=buffer_max, $3=buffer_default, $4=场景或自定义参数字符串
  # 自定义参数格式: "key1=val1|key2=val2|..." 支持的 key:
  #   somaxconn, syn_backlog, netdev_backlog, fin_timeout, tw_reuse,
  #   keepalive_time, keepalive_intvl, keepalive_probes, fastopen, mtu_probing, slow_start, timestamps
  #   [新增] window_scaling, sack, ecn, notsent_lowat, optmem_max, port_range_start, port_range_end, max_tw_buckets
  write_tcp_params() {
    if [ -f "$TARGET_CONF" ] && ! grep -qF "$TCP_BACKUP_TAG" "$TARGET_CONF"; then
      echo -e "${RED}[×] 目标 TCP 配置不是本脚本管理，未覆盖${RESET}"
      return 1
    fi
    if [ ! -f "$TCP_RUNTIME_BACKUP" ]; then
      local runtime_key runtime_value runtime_tmp="${TCP_RUNTIME_BACKUP}.tmp.$$"
      : > "$runtime_tmp" || return 1
      chmod 600 "$runtime_tmp" || return 1
      for runtime_key in net.core.rmem_max net.core.wmem_max net.core.rmem_default net.core.wmem_default net.core.optmem_max net.core.somaxconn net.core.netdev_max_backlog net.ipv4.tcp_rmem net.ipv4.tcp_wmem net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_fin_timeout net.ipv4.tcp_tw_reuse net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_probes net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_max_tw_buckets net.ipv4.ip_local_port_range net.ipv4.tcp_fastopen net.ipv4.tcp_mtu_probing net.ipv4.tcp_slow_start_after_idle net.ipv4.tcp_timestamps net.ipv4.tcp_window_scaling net.ipv4.tcp_sack net.ipv4.tcp_ecn net.ipv4.tcp_notsent_lowat; do
        runtime_value=$(sysctl -n "$runtime_key" 2>/dev/null) || continue
        printf '%s=%s\n' "$runtime_key" "$runtime_value" >> "$runtime_tmp" || return 1
      done
      [ -s "$runtime_tmp" ] && mv -f "$runtime_tmp" "$TCP_RUNTIME_BACKUP" || return 1
    fi
    local mode_name=$1
    local buffer_max=$2
    local buffer_default=$3
    local params_or_scenario=${4:-balanced}
    
    # 获取内核版本用于兼容性检测
    local kver=$(get_kernel_version)
    
    # 默认参数值 (连接队列)
    local somaxconn=4096
    local syn_backlog=8192
    local netdev_backlog=5000
    
    # 默认参数值 (连接管理)
    local fin_timeout=15
    local tw_reuse=2
    local keepalive_time=600
    local keepalive_intvl=15
    local keepalive_probes=5
    local max_tw_buckets=262144
    local port_range_start=1024
    local port_range_end=65535
    
    # 默认参数值 (性能增强)
    local fastopen=3
    local mtu_probing=1
    local slow_start=0
    local timestamps=1
    local window_scaling=1
    local sack=1
    local ecn=1
    local notsent_lowat=16384
    local optmem_max=81920
    
    # 解析参数: 检查是否是自定义参数字符串 (包含 =)
    if [[ "$params_or_scenario" == *"="* ]]; then
      # 自定义参数模式
      IFS='|' read -ra param_pairs <<< "$params_or_scenario"
      for pair in "${param_pairs[@]}"; do
        local key=$(echo "$pair" | cut -d'=' -f1)
        local val=$(echo "$pair" | cut -d'=' -f2)
        case "$key" in
          somaxconn) somaxconn=$val ;;
          syn_backlog) syn_backlog=$val ;;
          netdev_backlog) netdev_backlog=$val ;;
          fin_timeout) fin_timeout=$val ;;
          tw_reuse) tw_reuse=$val ;;
          keepalive_time) keepalive_time=$val ;;
          keepalive_intvl) keepalive_intvl=$val ;;
          keepalive_probes) keepalive_probes=$val ;;
          max_tw_buckets) max_tw_buckets=$val ;;
          port_range_start) port_range_start=$val ;;
          port_range_end) port_range_end=$val ;;
          fastopen) fastopen=$val ;;
          mtu_probing) mtu_probing=$val ;;
          slow_start) slow_start=$val ;;
          timestamps) timestamps=$val ;;
          window_scaling) window_scaling=$val ;;
          sack) sack=$val ;;
          ecn) ecn=$val ;;
          notsent_lowat) notsent_lowat=$val ;;
          optmem_max) optmem_max=$val ;;
        esac
      done
    else
      # 场景模式
      local scenario=$params_or_scenario
      case $scenario in
        throughput)
          # 高吞吐: 大队列，大缓冲，慢超时
          netdev_backlog=65535
          fin_timeout=30
          optmem_max=262144
          notsent_lowat=131072
          max_tw_buckets=400000
          ;;
        latency)
          # 低延迟: 快超时，小缓冲低水位
          fin_timeout=10
          keepalive_time=300
          keepalive_intvl=10
          notsent_lowat=4096
          ;;
      esac
    fi
    
    # 参数范围限制
    [ $somaxconn -lt 128 ] && somaxconn=128
    [ $somaxconn -gt 65535 ] && somaxconn=65535
    [ $syn_backlog -gt 65535 ] && syn_backlog=65535
    [ $port_range_start -lt 1024 ] && port_range_start=1024
    [ $port_range_end -gt 65535 ] && port_range_end=65535
    
    # [兼容] 写入到检测到的配置文件路径
    # 旧管理键由 clean_tcp_params 移除；追加保留该文件中的无关内容。
    cat >> "$TARGET_CONF" << EOF
${TCP_BACKUP_TAG} - ${mode_name} - $(date +%Y%m%d_%H%M%S)
# TCP 网络调优参数 - 由脚本自动生成
# 配置文件: $TARGET_CONF
# 重启后自动生效

# TCP 缓冲区 (基于 BDP 计算)
net.core.rmem_max = ${buffer_max}
net.core.wmem_max = ${buffer_max}
net.core.rmem_default = ${buffer_default}
net.core.wmem_default = ${buffer_default}
net.core.optmem_max = ${optmem_max}
net.ipv4.tcp_rmem = 4096 ${buffer_default} ${buffer_max}
net.ipv4.tcp_wmem = 4096 ${buffer_default} ${buffer_max}

# 连接队列
net.core.somaxconn = ${somaxconn}
net.core.netdev_max_backlog = ${netdev_backlog}
net.ipv4.tcp_max_syn_backlog = ${syn_backlog}

# 连接管理
net.ipv4.tcp_fin_timeout = ${fin_timeout}
net.ipv4.tcp_keepalive_time = ${keepalive_time}
net.ipv4.tcp_keepalive_probes = ${keepalive_probes}
net.ipv4.tcp_keepalive_intvl = ${keepalive_intvl}
net.ipv4.tcp_max_tw_buckets = ${max_tw_buckets}
net.ipv4.ip_local_port_range = ${port_range_start} ${port_range_end}
EOF

    # 保守使用 5.10+ 的已确认语义；更老内核不推断厂商回移能力。
    if kernel_version_ge "$kver" "5.10"; then
      echo "net.ipv4.tcp_tw_reuse = ${tw_reuse}" >> "$TARGET_CONF"
      echo -e "${GRAY}  [√] tcp_tw_reuse=${tw_reuse} (2 仅用于 loopback)${RESET}"
    else
      local tw_compat=$tw_reuse
      [ $tw_compat -gt 1 ] && tw_compat=1
      echo "net.ipv4.tcp_tw_reuse = ${tw_compat}" >> "$TARGET_CONF"
      echo -e "${GRAY}  [!] tcp_tw_reuse=${tw_compat} (老内核使用保守兼容值)${RESET}"
    fi

    # 写入性能增强参数
    echo "" >> "$TARGET_CONF"
    echo "# 性能增强" >> "$TARGET_CONF"
    
    # tcp_fastopen: 内核 3.7+ 支持
    if kernel_version_ge "$kver" "3.7"; then
      echo "net.ipv4.tcp_fastopen = ${fastopen}" >> "$TARGET_CONF"
      echo -e "${GRAY}  [√] tcp_fastopen=${fastopen} (内核 $kver >= 3.7)${RESET}"
    else
      echo -e "${YELLOW}  [!] 跳过 tcp_fastopen (需要内核 3.7+，当前 $kver)${RESET}"
    fi
    
    echo "net.ipv4.tcp_mtu_probing = ${mtu_probing}" >> "$TARGET_CONF"
    echo "net.ipv4.tcp_slow_start_after_idle = ${slow_start}" >> "$TARGET_CONF"
    echo "net.ipv4.tcp_timestamps = ${timestamps}" >> "$TARGET_CONF"
    echo "net.ipv4.tcp_window_scaling = ${window_scaling}" >> "$TARGET_CONF"
    echo "net.ipv4.tcp_sack = ${sack}" >> "$TARGET_CONF"
    
    # tcp_ecn: 内核 2.4+ 支持，但 ECN_ENABLED (值1) 可能在部分老网络有兼容问题
    echo "net.ipv4.tcp_ecn = ${ecn}" >> "$TARGET_CONF"
    
    # tcp_notsent_lowat: 内核 3.12+ 支持
    if kernel_version_ge "$kver" "3.12"; then
      echo "net.ipv4.tcp_notsent_lowat = ${notsent_lowat}" >> "$TARGET_CONF"
      echo -e "${GRAY}  [√] tcp_notsent_lowat=${notsent_lowat} (内核 $kver >= 3.12)${RESET}"
    else
      echo -e "${YELLOW}  [!] 跳过 tcp_notsent_lowat (需要内核 3.12+，当前 $kver)${RESET}"
    fi
  }

  
  # [事务快照] 捕获当前文件状态与内存运行值快照
  backup_tcp_transaction() {
    local snap_dir
    snap_dir=$(mktemp -d "${TEMP_DIR}/tcp-snap.XXXXXX") || return 1
    # 记录 TARGET_CONF 是否存在及其原内容
    if [ -f "$TARGET_CONF" ]; then
      cp -p "$TARGET_CONF" "$snap_dir/target_conf" 2>/dev/null || { rm -rf "$snap_dir"; return 1; }
      echo "existing" > "$snap_dir/target_state"
    else
      echo "absent" > "$snap_dir/target_state"
    fi
    # 记录 SYSCTL_CONF 原内容
    if [ -f "$SYSCTL_CONF" ]; then
      cp -p "$SYSCTL_CONF" "$snap_dir/sysctl_conf" 2>/dev/null || { rm -rf "$snap_dir"; return 1; }
    fi
    # 记录当前核心运行值 (覆盖调优实际修改的所有参数)
    local tcp_keys=(
      "net.core.rmem_max" "net.core.wmem_max" "net.core.rmem_default" "net.core.wmem_default"
      "net.core.optmem_max" "net.core.somaxconn" "net.core.netdev_max_backlog"
      "net.ipv4.tcp_rmem" "net.ipv4.tcp_wmem" "net.ipv4.tcp_max_syn_backlog"
      "net.ipv4.tcp_fin_timeout" "net.ipv4.tcp_tw_reuse" "net.ipv4.tcp_keepalive_time"
      "net.ipv4.tcp_keepalive_intvl" "net.ipv4.tcp_keepalive_probes" "net.ipv4.tcp_fastopen"
      "net.ipv4.tcp_mtu_probing" "net.ipv4.tcp_slow_start_after_idle" "net.ipv4.tcp_timestamps"
      "net.ipv4.tcp_window_scaling" "net.ipv4.tcp_sack" "net.ipv4.tcp_ecn"
      "net.ipv4.tcp_notsent_lowat" "net.ipv4.tcp_max_tw_buckets" "net.ipv4.ip_local_port_range"
    )
    for p in "${tcp_keys[@]}"; do
      local val
      val=$(sysctl -n "$p" 2>/dev/null)
      [ -n "$val" ] && echo "$p=$val" >> "$snap_dir/runtime_values"
    done
    echo "$snap_dir"
  }

  # [事务回滚] 恢复修改前的配置文件、移除新建 drop-in 并重置已生效的运行时参数
  rollback_tcp_transaction() {
    local snap_dir="$1"
    [ -n "$snap_dir" ] && [ -d "$snap_dir" ] && [ -f "$snap_dir/target_state" ] || return 1
    echo -e "${YELLOW}正在自动回滚原配置与运行时参数...${RESET}"
    local rb_failed=false
    # 1. 还原 TARGET_CONF (如果原本不存在则直接删除，若原本存在且备份有效则安全恢复)
    local t_state
    t_state=$(cat "$snap_dir/target_state" 2>/dev/null)
    if [ "$t_state" = "existing" ]; then
      if [ -f "$snap_dir/target_conf" ]; then
        cp -pf "$snap_dir/target_conf" "$TARGET_CONF" || rb_failed=true
      else
        rb_failed=true
      fi
    else
      if [ -f "$TARGET_CONF" ]; then
        rm -f "$TARGET_CONF" || rb_failed=true
      fi
    fi
    # 2. 还原 SYSCTL_CONF 原文件
    if [ -f "$snap_dir/sysctl_conf" ]; then
      cp -pf "$snap_dir/sysctl_conf" "$SYSCTL_CONF" || rb_failed=true
    fi
    # 3. 必须先重载系统配置文件，再最后写回操作前捕获的内存运行值以防被系统默认配置覆盖
    sysctl --system >/dev/null 2>&1 || true
    if [ -f "$snap_dir/runtime_values" ]; then
      while IFS='=' read -r param val; do
        if [ -n "$param" ] && [ -n "$val" ]; then
          sysctl -w "$param=$val" >/dev/null 2>&1 || rb_failed=true
        fi
      done < "$snap_dir/runtime_values"
    fi
    if [ "$rb_failed" = true ]; then
      echo -e "${RED}[×] 网络配置自动回滚未完全成功，快照保留在: $snap_dir${RESET}"
      return 1
    fi
    rm -rf "$snap_dir" 2>/dev/null
    echo -e "${GREEN}[√] 已安全恢复修改前的网络配置文件与系统运行值${RESET}"
    return 0
  }

  # [辅助函数] 应用并验证参数 (支持失败时自动安全回滚)
  apply_and_verify() {
    local snap_dir="$1"
    echo -e "${CYAN}正在应用参数...${RESET}"
    
    # 执行 sysctl -p 加载新配置文件
    local sysctl_output
    sysctl_output=$(sysctl -p "$TARGET_CONF" 2>&1)
    local sysctl_ret=$?
    
    # 定义需要验证的关键参数及其预期最小值
    local verify_params=(
      "net.core.rmem_max"
      "net.core.wmem_max"
      "net.core.somaxconn"
    )
    
    local verify_failed=0
    local verify_success=0
    
    echo -e "${GRAY}正在验证关键参数...${RESET}"
    
    for param in "${verify_params[@]}"; do
      local current_val=$(sysctl -n "$param" 2>/dev/null)
      local config_val=$(grep "^${param}" "$TARGET_CONF" 2>/dev/null | tail -1 | awk -F'=' '{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2}')
      
      if [ -n "$config_val" ]; then
        # 对于多值参数 (如 tcp_rmem)，只比较第一个有效数字
        local config_first=$(echo "$config_val" | awk '{print $1}')
        local current_first=$(echo "$current_val" | awk '{print $1}')
        
        if [ "$current_first" == "$config_first" ] || [ "$current_val" == "$config_val" ]; then
          echo -e "  ${GREEN}✓${RESET} $param = $current_val"
          ((verify_success++))
        else
          echo -e "  ${RED}✗${RESET} $param: 期望 $config_val, 实际 $current_val"
          ((verify_failed++))
        fi
      fi
    done
    
    echo -e ""
    
    if [ $verify_failed -eq 0 ] && [ $sysctl_ret -eq 0 ]; then
      echo -e "${GREEN}[√] TCP 调优参数已成功应用 (${verify_success} 项验证通过)${RESET}"
      echo -e "${GRAY}当前生效的关键参数:${RESET}"
      echo -e "  接收缓冲区 max: $(get_tcp_param net.core.rmem_max) bytes"
      echo -e "  发送缓冲区 max: $(get_tcp_param net.core.wmem_max) bytes"
      echo -e "  连接队列: $(get_tcp_param net.core.somaxconn)"
      [ -n "$snap_dir" ] && rm -rf "$snap_dir" 2>/dev/null
      return 0
    else
      echo -e "${RED}[!] 部分参数应用失败 (${verify_failed} 项未生效)${RESET}"
      if [ -n "$sysctl_output" ]; then
        echo -e "${GRAY}sysctl 输出:${RESET}"
        echo "$sysctl_output" | head -5
      fi
      echo -e "${YELLOW}可能原因: 容器环境限制、内核不支持、或权限不足${RESET}"
      rollback_tcp_transaction "$snap_dir"
      return 1
    fi
  }
  
  while true; do
    clear
    echo -e "${BOLD}${CYAN}TCP 网络调优${RESET}"
    echo -e "${CYAN}==================================================${RESET}"
    
    # --- 环境检测与警告 ---
    local is_container=false
    if is_container_env; then
      is_container=true
      echo -e "${RED}[!] 检测到容器环境，部分参数可能无法修改${RESET}"
    fi
    
    # 显示当前使用的配置文件路径
    echo -e "${GRAY}配置文件: ${TARGET_CONF}${RESET}"
    
    # --- 显示当前参数 (全面显示 + 关键参数高亮) ---
    echo -e "${BOLD}${CYAN}当前 TCP 配置:${RESET}"
    
    # 获取系统内存用于参考
    local sys_mem_kb=$(awk '/MemTotal/{print $2}' /proc/meminfo 2>/dev/null)
    local sys_mem_mb=$(( sys_mem_kb / 1024 ))
    echo -e "${GRAY}系统内存: ${sys_mem_mb} MB | 内核: $(uname -r)${RESET}"
    echo -e ""
    
    # === 缓冲区参数 (关键) ===
    echo -e "${YELLOW}【缓冲区参数】${RESET} ${GRAY}(影响吞吐量)${RESET}"
    local rmem_max=$(get_tcp_param net.core.rmem_max)
    local wmem_max=$(get_tcp_param net.core.wmem_max)
    local optmem_max=$(get_tcp_param net.core.optmem_max)
    local tcp_rmem=$(get_tcp_param net.ipv4.tcp_rmem)
    local tcp_wmem=$(get_tcp_param net.ipv4.tcp_wmem)
    # 转换为人类可读格式 (防御 N/A 与前导零)
    local rmem_max_mb="N/A"
    [[ "$rmem_max" =~ ^[0-9]+$ ]] && rmem_max_mb="$(( 10#$rmem_max / 1048576 )) MB"
    local wmem_max_mb="N/A"
    [[ "$wmem_max" =~ ^[0-9]+$ ]] && wmem_max_mb="$(( 10#$wmem_max / 1048576 )) MB"
    local optmem_kb="N/A"
    [[ "$optmem_max" =~ ^[0-9]+$ ]] && optmem_kb="$(( 10#$optmem_max / 1024 )) KB"
    echo -e "  ${GREEN}★${RESET} rmem_max (全局接收): ${GREEN}${rmem_max_mb}${RESET} (${rmem_max} bytes)"
    echo -e "  ${GREEN}★${RESET} wmem_max (全局发送): ${GREEN}${wmem_max_mb}${RESET} (${wmem_max} bytes)"
    echo -e "    optmem_max (辅助缓冲): ${GREEN}${optmem_kb} KB${RESET}"
    echo -e "    tcp_rmem (TCP接收): ${GREEN}${tcp_rmem}${RESET}"
    echo -e "    tcp_wmem (TCP发送): ${GREEN}${tcp_wmem}${RESET}"
    
    # === 连接队列参数 ===
    echo -e "${YELLOW}【连接队列】${RESET} ${GRAY}(影响并发)${RESET}"
    echo -e "  ${GREEN}★${RESET} somaxconn (完成队列):     ${GREEN}$(get_tcp_param net.core.somaxconn)${RESET}"
    echo -e "    max_syn_backlog (SYN队列): ${GREEN}$(get_tcp_param net.ipv4.tcp_max_syn_backlog)${RESET}"
    echo -e "    netdev_max_backlog (网卡): ${GREEN}$(get_tcp_param net.core.netdev_max_backlog)${RESET}"
    
    # === 连接管理参数 ===
    echo -e "${YELLOW}【连接管理】${RESET} ${GRAY}(影响资源释放)${RESET}"
    echo -e "    fin_timeout:     ${GREEN}$(get_tcp_param net.ipv4.tcp_fin_timeout)s${RESET}"
    echo -e "    tw_reuse:        ${GREEN}$(get_tcp_param net.ipv4.tcp_tw_reuse)${RESET}"
    echo -e "    max_tw_buckets:  ${GREEN}$(get_tcp_param net.ipv4.tcp_max_tw_buckets)${RESET}"
    echo -e "    keepalive_time:  ${GREEN}$(get_tcp_param net.ipv4.tcp_keepalive_time)s${RESET}"
    echo -e "    keepalive_intvl: ${GREEN}$(get_tcp_param net.ipv4.tcp_keepalive_intvl)s${RESET}"
    echo -e "    port_range:      ${GREEN}$(get_tcp_param net.ipv4.ip_local_port_range)${RESET}"
    
    # === 性能增强参数 ===
    echo -e "${YELLOW}【性能增强】${RESET}"
    echo -e "    tcp_fastopen:    ${GREEN}$(get_tcp_param net.ipv4.tcp_fastopen)${RESET}"
    echo -e "    mtu_probing:     ${GREEN}$(get_tcp_param net.ipv4.tcp_mtu_probing)${RESET}"
    echo -e "    slow_start_idle: ${GREEN}$(get_tcp_param net.ipv4.tcp_slow_start_after_idle)${RESET}"
    echo -e "    window_scaling:  ${GREEN}$(get_tcp_param net.ipv4.tcp_window_scaling)${RESET}"
    echo -e "    sack:            ${GREEN}$(get_tcp_param net.ipv4.tcp_sack)${RESET}"
    echo -e "    ecn:             ${GREEN}$(get_tcp_param net.ipv4.tcp_ecn)${RESET}"
    echo -e "    notsent_lowat:   ${GREEN}$(get_tcp_param net.ipv4.tcp_notsent_lowat)${RESET}"
    
    # 检查是否已有调优配置
    if grep -q "$TCP_BACKUP_TAG" "$SYSCTL_CONF" 2>/dev/null; then
      echo -e ""
      echo -e "${YELLOW}[*] 已存在调优配置 (由本脚本管理)${RESET}"
    fi

    
    echo -e "${CYAN}--------------------------------------------------${RESET}"
    echo -e ""
    echo -e "${CYAN}理论依据: BDP (带宽延迟积) = 带宽 × RTT${RESET}"
    echo -e "${GRAY}缓冲区上限 (max) 作为滑动窗口上限，由 Linux 内核自动调节 (Autotuning) 动态伸缩${RESET}"
    echo -e "${GRAY}注: 选项 1-3 仅按系统内存提供安全基准，无法测量真实网络；若需突破大带宽瓶颈，推荐使用选项 4${RESET}"
    echo -e ""
    echo -e "${YELLOW}  1.${RESET} 通用保守预设 (平衡模式 - 基于内存规格)"
    echo -e "${YELLOW}  2.${RESET} 通用保守预设 (高吞吐 - 放大缓冲区上限)"
    echo -e "${YELLOW}  3.${RESET} 通用保守预设 (低资源 - 紧凑缓冲区上限)"
    echo -e "${YELLOW}  4.${RESET} 自定义链路参数 (输入真实带宽/RTT精确计算)"
    echo -e "${YELLOW}  5.${RESET} ${RED}恢复默认配置${RESET}"
    echo -e "${YELLOW}  ?.${RESET} 参数技术说明"
    echo -e "${YELLOW}  0.${RESET} 返回上一级"
    echo -e "${CYAN}==================================================${RESET}"
    
    ui_menu_read choice "请输入选项" "[0-5]|\?" || return 0
    case $choice in
      1|2|3)
        # --- 容器环境警告 ---
        if [ "$is_container" = true ]; then
          echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
          echo -e "${RED}[!] 警告: 容器环境可能无法修改部分内核参数${RESET}"
          echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
          ui_confirm force_continue "仍要继续? [y/N] (q 取消): "
          if [[ ! "$force_continue" =~ ^[Yy]$ ]]; then
            continue
          fi
        fi
        
        local mode_name=""
        local buffer_max=0
        local buffer_default=0
        local scenario=""
        
        # 基于系统内存的保守基准预设
        local sys_params=$(auto_detect_system_params)
        local auto_buffer_max=$(echo "$sys_params" | awk '{print $1}')
        local auto_buffer_default=$(echo "$sys_params" | awk '{print $2}')
        local auto_estimated_bw=$(echo "$sys_params" | awk '{print $3}')
        local sys_mem_mb=$(echo "$sys_params" | awk '{print $4}')
        local auto_estimated_rtt=$(echo "$sys_params" | awk '{print $5}')
        
        # 计算 BDP 用于显示
        local bdp_bytes=$(( auto_estimated_bw * 1000000 / 8 * auto_estimated_rtt / 1000 ))
        local bdp_kb=$(( bdp_bytes / 1024 ))
        
        echo -e "${CYAN}>>> 系统内存规格与参考基准:${RESET}"
        echo -e "  系统内存:     ${GREEN}${sys_mem_mb} MB${RESET}"
        echo -e "  参考基准带宽: ${GREEN}${auto_estimated_bw} Mbps${RESET} ${GRAY}(内存分档安全参考，非真实测量值)${RESET}"
        echo -e "  参考基准 RTT: ${GREEN}${auto_estimated_rtt} ms${RESET}"
        echo -e "  理论 BDP 参考: ${GREEN}${bdp_kb} KB${RESET}"
        echo -e "  内核版本:     ${GREEN}$(get_kernel_version)${RESET}"
        echo -e ""
        
        case $choice in
          1)
            mode_name="通用保守预设 (平衡模式)"
            buffer_max=$auto_buffer_max
            buffer_default=$auto_buffer_default
            scenario="balanced"
            ;;
          2)
            mode_name="通用保守预设 (高吞吐模式)"
            buffer_max=$(( auto_buffer_max * 3 / 2 ))
            [ $buffer_max -gt 134217728 ] && buffer_max=134217728
            buffer_default=$(( auto_buffer_default * 3 / 2 ))
            [ $buffer_default -gt 524288 ] && buffer_default=524288
            scenario="throughput"
            ;;
          3)
            mode_name="通用保守预设 (低资源模式)"
            buffer_max=$(( auto_buffer_max / 2 ))
            [ $buffer_max -lt 4194304 ] && buffer_max=4194304
            buffer_default=$(( auto_buffer_default / 2 ))
            [ $buffer_default -lt 65536 ] && buffer_default=65536
            scenario="latency"
            ;;
        esac
        
        # 显示参数预览对比
        echo -e ""
        preview_tcp_changes "$buffer_max" "$buffer_default" "$scenario"
        echo -e ""
        
        ui_confirm confirm "确认应用以上配置? [y/N] (q 取消): "
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
          echo -e "${YELLOW}已取消，当前系统网络配置保持不变。${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
        
        # 用户确认后捕获完整事务快照并清理旧配置
        local cur_tcp_snap
        cur_tcp_snap=$(backup_tcp_transaction) || continue
        clean_tcp_params || { rollback_tcp_transaction "$cur_tcp_snap"; continue; }

        echo -e "${CYAN}>>> 正在应用 ${mode_name}...${RESET}"
        if ! write_tcp_params "$mode_name" "$buffer_max" "$buffer_default" "$scenario"; then
          rollback_tcp_transaction "$cur_tcp_snap"
          continue
        fi
        apply_and_verify "$cur_tcp_snap"
        ui_pause "按 Enter 继续..."
        ;;
        
      4)
        # --- 自定义参数: 完全自定义所有 TCP 参数 ---
        echo -e "\n${CYAN}>>> 自定义参数模式 (完全控制)${RESET}"
        echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e "${CYAN}此模式允许您精确控制所有 TCP 调优参数${RESET}"
        echo -e "${GRAY}输入 q 可随时取消并返回上一级菜单；0 是合法参数值${RESET}"
        echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e ""
        
        # ====== 第1步: 基础网络参数 ======
        echo -e "${YELLOW}【第1步】基础网络参数${RESET}"
        
        echo -e "${GRAY}  带宽: 服务商承诺的网络速度，影响缓冲区计算${RESET}"
        echo -e "${GRAY}  建议: 从 speedtest 或服务商面板获取实际值${RESET}"
        ui_read_field input_bandwidth "VPS 带宽 (Mbps) [100]: " ui_bandwidth 100 || continue
        input_bandwidth=${input_bandwidth:-100}
        if [[ ! "$input_bandwidth" =~ ^[1-9][0-9]{0,5}$ ]]; then
          echo -e "${RED}[!] 带宽必须为正整数${RESET}"; ui_pause "按 Enter 继续..."; continue
        fi
        input_bandwidth=$(( 10#$input_bandwidth ))
        
        echo -e ""
        echo -e "${GRAY}  RTT: 往返延迟，影响 BDP 和缓冲区大小${RESET}"
        echo -e "${GRAY}  建议: ping 主要用户节点，取平均值${RESET}"
        ui_read_field input_rtt "网络延迟 RTT (ms) [50]: " ui_rtt 50 || continue
        input_rtt=${input_rtt:-50}
        if [[ ! "$input_rtt" =~ ^[1-9][0-9]{0,4}$ ]]; then
          echo -e "${RED}[!] 延迟必须为正整数${RESET}"; ui_pause "按 Enter 继续..."; continue
        fi
        input_rtt=$(( 10#$input_rtt ))
        
        echo -e ""
        echo -e "${GRAY}  BDP倍数: 缓冲区上限 = BDP × 倍数。仅作为内核自动调节的经验上限起点${RESET}"
        echo -e "${GRAY}  说明: 实际吞吐与延迟受拥塞控制、丢包及对端影响，需通过同一路径测试确认效果 (默认 2)${RESET}"
        ui_read_field input_bdp_mult "BDP 倍数 (1-5) [2]: " ui_bdp_mult 2 || continue
        input_bdp_mult=${input_bdp_mult:-2}
        if [[ ! "$input_bdp_mult" =~ ^[1-5]$ ]]; then
          input_bdp_mult=2
        fi
        input_bdp_mult=$(( 10#$input_bdp_mult ))
        
        # 计算 BDP 和缓冲区
        local calc_res
        calc_res=$(calculate_bdp_params "$input_bandwidth" "$input_rtt" "$input_bdp_mult")
        local calc_buffer_max=$(echo "$calc_res" | awk '{print $1}')
        local calc_buffer_default=$(echo "$calc_res" | awk '{print $2}')
        local clamp_flag=$(echo "$calc_res" | awk '{print $3}')
        local raw_buffer_max=$(echo "$calc_res" | awk '{print $4}')
        local bdp_bytes=$(echo "$calc_res" | awk '{print $5}')
        
        echo -e "${GREEN}  → 计算完成:${RESET}"
        echo -e "    理论单连接 BDP:  ${GREEN}$(( bdp_bytes / 1024 )) KB${RESET}"
        echo -e "    建议缓冲区上限:  ${GREEN}$(( calc_buffer_max / 1048576 )) MB${RESET} (${calc_buffer_max} bytes)"
        echo -e "    建议初始缓冲区:  ${GREEN}$(( calc_buffer_default / 1024 )) KB${RESET} (依托内核自动调节)"
        
        if [ "$clamp_flag" = "max_clamped" ]; then
          local raw_mb=$(( raw_buffer_max / 1048576 ))
          echo -e "${YELLOW}  [!] 提示: 根据输入链路参数，理论所需缓冲区上限为 ${raw_mb} MB，已触发系统安全保护上限 (128 MB) 并被截断。若在超高速、大延迟网络下，单连接最大吞吐可能会受到此上限钳位。${RESET}"
        elif [ "$clamp_flag" = "min_clamped" ]; then
          echo -e "${YELLOW}  [!] 提示: 计算所得缓冲区较小，已安全提升至系统保底 4 MB 上限，以确保突发传输流畅。${RESET}"
        fi
        
        local mem_kb=$(awk '/MemTotal/{print $2}' /proc/meminfo 2>/dev/null)
        if [[ "$mem_kb" =~ ^[0-9]+$ ]]; then
          local mem_total_bytes=$(( mem_kb * 1024 ))
          local pct=$(( calc_buffer_max * 100 / mem_total_bytes ))
          if [ "$pct" -gt 5 ]; then
            echo -e "${YELLOW}  [!] 提示: 当前单个 socket 缓冲区上限占系统物理总内存的 ${pct}%。若存在海量高吞吐并发连接，请注意监控系统内存使用情况。${RESET}"
          fi
        fi
        
        # ====== 第2步: 连接队列参数 ======
        echo -e ""
        echo -e "${YELLOW}【第2步】连接队列参数 (影响高并发能力)${RESET}"
        
        echo -e "${GRAY}  somaxconn: 完成三次握手的连接队列长度${RESET}"
        echo -e "${GRAY}  建议: Web服务=4096~65535, 普通VPS=1024~4096${RESET}"
        ui_read_field input_somaxconn "somaxconn [4096]: " ui_unsigned 4096 || continue
        input_somaxconn=${input_somaxconn:-4096}
        
        echo -e "${GRAY}  syn_backlog: 半连接(SYN_RECV)队列，防SYN洪水攻击${RESET}"
        echo -e "${GRAY}  建议: 设为 somaxconn 的 1~2 倍${RESET}"
        ui_read_field input_syn_backlog "syn_backlog [8192]: " ui_unsigned 8192 || continue
        input_syn_backlog=${input_syn_backlog:-8192}
        
        echo -e "${GRAY}  netdev_backlog: 网卡接收队列，高带宽需增大${RESET}"
        echo -e "${GRAY}  建议: 100M=1000, 1G=5000, 10G=30000+${RESET}"
        ui_read_field input_netdev_backlog "netdev_max_backlog [5000]: " ui_unsigned 5000 || continue
        input_netdev_backlog=${input_netdev_backlog:-5000}
        
        # ====== 第3步: 连接管理参数 ======
        echo -e ""
        echo -e "${YELLOW}【第3步】连接管理参数 (影响资源释放速度)${RESET}"
        
        echo -e "${GRAY}  fin_timeout: FIN_WAIT_2 超时，越小越快回收端口${RESET}"
        echo -e "${GRAY}  建议: 默认60过长，推荐10~30秒${RESET}"
        ui_read_field input_fin_timeout "fin_timeout 秒 [10]: " ui_positive 10 || continue
        input_fin_timeout=${input_fin_timeout:-10}
        
        echo -e "${GRAY}  tw_reuse: TIME_WAIT复用 (0=禁用, 1=全局启用, 2=仅loopback)${RESET}"
        echo -e "${GRAY}  值 2 仅在已确认的现代内核使用；老内核采用兼容值${RESET}"
        ui_read_field input_tw_reuse "tw_reuse (0/1/2) [2]: " ui_enum2 2 || continue
        input_tw_reuse=${input_tw_reuse:-2}
        
        echo -e "${GRAY}  keepalive_time: 空闲多久后发送探活包${RESET}"
        echo -e "${GRAY}  建议: 默认7200太长，推荐300~600秒${RESET}"
        ui_read_field input_keepalive_time "keepalive_time 秒 [300]: " ui_positive 300 || continue
        input_keepalive_time=${input_keepalive_time:-300}
        
        echo -e "${GRAY}  keepalive_intvl: 探活包发送间隔${RESET}"
        echo -e "${GRAY}  建议: 推荐10~30秒${RESET}"
        ui_read_field input_keepalive_intvl "keepalive_intvl 秒 [10]: " ui_positive 10 || continue
        input_keepalive_intvl=${input_keepalive_intvl:-10}
        
        echo -e "${GRAY}  keepalive_probes: 探活失败多少次后断开${RESET}"
        echo -e "${GRAY}  建议: 推荐3~5次${RESET}"
        ui_read_field input_keepalive_probes "keepalive_probes [5]: " ui_positive 5 || continue
        input_keepalive_probes=${input_keepalive_probes:-5}
        
        # ====== 第4步: 性能增强参数 ======
        echo -e ""
        echo -e "${YELLOW}【第4步】性能增强参数${RESET}"
        
        echo -e "${GRAY}  tcp_fastopen: TFO 减少握手延迟 (需内核3.7+)${RESET}"
        echo -e "${GRAY}  建议: 3=客户端+服务端都启用${RESET}"
        ui_read_field input_fastopen "tcp_fastopen (0/1/2/3) [3]: " ui_enum3 3 || continue
        input_fastopen=${input_fastopen:-3}
        
        echo -e "${GRAY}  mtu_probing: 自动探测最佳MTU，避免分片${RESET}"
        echo -e "${GRAY}  建议: 1=检测到黑洞时启用${RESET}"
        ui_read_field input_mtu_probing "mtu_probing (0/1/2) [1]: " ui_enum2 1 || continue
        input_mtu_probing=${input_mtu_probing:-1}
        
        echo -e "${GRAY}  slow_start_after_idle: 空闲后是否重新慢启动${RESET}"
        echo -e "${GRAY}  建议: 0=禁用，长连接更快恢复速度${RESET}"
        ui_read_field input_slow_start "slow_start_after_idle (0/1) [0]: " ui_bool 0 || continue
        input_slow_start=${input_slow_start:-0}
        
        echo -e "${GRAY}  tcp_timestamps: 时间戳，用于RTT计算和PAWS${RESET}"
        echo -e "${GRAY}  建议: 1=启用 (禁用可能影响性能)${RESET}"
        ui_read_field input_timestamps "tcp_timestamps (0/1) [1]: " ui_enum2 1 || continue
        input_timestamps=${input_timestamps:-1}
        
        local custom_valid=true custom_var custom_value
        for custom_var in input_somaxconn input_syn_backlog input_netdev_backlog input_fin_timeout input_tw_reuse input_keepalive_time input_keepalive_intvl input_keepalive_probes input_fastopen input_mtu_probing input_slow_start input_timestamps; do
          custom_value=${!custom_var}
          if [[ ! "$custom_value" =~ ^(0|[1-9][0-9]{0,8})$ ]]; then custom_valid=false; fi
        done
        if [ "$custom_valid" = true ]; then
          ((input_tw_reuse <= 2 && input_fastopen <= 3 && input_mtu_probing <= 2 && input_slow_start <= 1 && input_timestamps <= 2 && input_keepalive_time > 0 && input_keepalive_intvl > 0 && input_keepalive_probes > 0 && input_fin_timeout > 0)) || custom_valid=false
        fi
        if [ "$custom_valid" = false ]; then
          echo -e "${RED}[×] 自定义 TCP 参数含非法数字或超出枚举范围，未写入配置${RESET}"
          continue
        fi
        # ====== 显示配置摘要 ======
        echo -e ""
        echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e "${GREEN}配置摘要:${RESET}"
        echo -e "  ${YELLOW}【缓冲区】${RESET} (BDP×${input_bdp_mult})"
        echo -e "    buffer_max:     $(( calc_buffer_max / 1048576 )) MB"
        echo -e "    buffer_default: $(( calc_buffer_default / 1024 )) KB"
        echo -e "  ${YELLOW}【连接队列】${RESET}"
        echo -e "    somaxconn:      $input_somaxconn"
        echo -e "    syn_backlog:    $input_syn_backlog"
        echo -e "    netdev_backlog: $input_netdev_backlog"
        echo -e "  ${YELLOW}【连接管理】${RESET}"
        echo -e "    fin_timeout:    ${input_fin_timeout}s"
        echo -e "    tw_reuse:       $input_tw_reuse"
        echo -e "    keepalive:      ${input_keepalive_time}s / ${input_keepalive_intvl}s × ${input_keepalive_probes}"
        echo -e "  ${YELLOW}【性能增强】${RESET}"
        echo -e "    fastopen:       $input_fastopen"
        echo -e "    mtu_probing:    $input_mtu_probing"
        echo -e "    slow_start:     $input_slow_start"
        echo -e "    timestamps:     $input_timestamps"
        echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        
        ui_confirm confirm "确认应用以上配置? [y/N] (q 取消): "
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
          echo -e "${YELLOW}已取消${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
        
        # 容器环境警告
        if [ "$is_container" = true ]; then
          echo -e "${YELLOW}[!] 容器环境，部分参数可能不生效${RESET}"
        fi
        
        # 捕获事务快照
        local cur_custom_snap
        cur_custom_snap=$(backup_tcp_transaction) || continue
        
        # 清理旧配置
        clean_tcp_params || { rollback_tcp_transaction "$cur_custom_snap"; continue; }
        
        # 构建自定义参数字符串
        local custom_params="somaxconn=${input_somaxconn}|syn_backlog=${input_syn_backlog}|netdev_backlog=${input_netdev_backlog}"
        custom_params="${custom_params}|fin_timeout=${input_fin_timeout}|tw_reuse=${input_tw_reuse}"
        custom_params="${custom_params}|keepalive_time=${input_keepalive_time}|keepalive_intvl=${input_keepalive_intvl}|keepalive_probes=${input_keepalive_probes}"
        custom_params="${custom_params}|fastopen=${input_fastopen}|mtu_probing=${input_mtu_probing}|slow_start=${input_slow_start}|timestamps=${input_timestamps}"
        
        # 写入配置并在失败时自动回滚
        local mode_name="自定义 (${input_bandwidth}Mbps/${input_rtt}ms/×${input_bdp_mult})"
        if ! write_tcp_params "$mode_name" "$calc_buffer_max" "$calc_buffer_default" "$custom_params"; then
          rollback_tcp_transaction "$cur_custom_snap"
          continue
        fi
        apply_and_verify "$cur_custom_snap"
        ui_pause "按 Enter 继续..."
        ;;
        
      5)
        # --- 恢复默认配置 ---
        echo -e "\n${CYAN}>>> 恢复默认 TCP 配置${RESET}"
        echo -e "${YELLOW}将移除所有由本脚本添加的 TCP 调优参数${RESET}"
        ui_confirm confirm "确认恢复默认? [y/N] (q 取消): "
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
          continue
        fi
        
        # 备份当前配置文件 (如果存在)
        if [ -f "$TARGET_CONF" ]; then
          cp "$TARGET_CONF" "${TARGET_CONF}.bak.$(date +%Y%m%d_%H%M%S)"
          echo -e "${GRAY}  已备份 $TARGET_CONF${RESET}"
        fi
        
        # 清理调优参数 (同时清理新旧配置文件)
        clean_tcp_params || continue
        
        if [ -f "$TCP_RUNTIME_BACKUP" ]; then
          local runtime_key runtime_value runtime_restore_ok=true
          while IFS='=' read -r runtime_key runtime_value; do
            [[ "$runtime_key" =~ ^net\.(core|ipv4)\.[a-z_]+$ && "$runtime_value" =~ ^[0-9[:space:]]+$ ]] || { runtime_restore_ok=false; continue; }
            sysctl -w "$runtime_key=$runtime_value" >/dev/null 2>&1 || runtime_restore_ok=false
          done < "$TCP_RUNTIME_BACKUP"
          if [ "$runtime_restore_ok" = false ]; then
            echo -e "${RED}[×] 运行时恢复不完整，请检查保存的参数和内核权限${RESET}"
            continue
          fi
        else
          echo -e "${YELLOW}[!] 无首次调优前快照，不能重建内核默认运行值；需重启后核验${RESET}"
        fi
        # 恢复快照后，再加载管理员的持久配置。
        echo -e "${CYAN}正在重置运行时参数...${RESET}"
        
        # 根据系统类型选择重载命令
        if [ "$USE_SYSCTL_D" = true ]; then
          # systemd 系统: 使用 --system 重载所有配置
          sysctl --system >/dev/null 2>&1
        else
          # 老旧系统: 使用 -p 重载 sysctl.conf
          sysctl -p >/dev/null 2>&1
        fi
        
        # 显示恢复后的关键参数值
        echo -e "${GRAY}恢复后的参数值 (内核当前值):${RESET}"
        echo -e "  rmem_max:  $(sysctl -n net.core.rmem_max 2>/dev/null) bytes"
        echo -e "  wmem_max:  $(sysctl -n net.core.wmem_max 2>/dev/null) bytes"
        echo -e "  somaxconn: $(sysctl -n net.core.somaxconn 2>/dev/null)"
        echo -e ""
        
        echo -e "${GREEN}[√] 已清理脚本管理参数；运行值按可用快照和管理员配置恢复${RESET}"
        echo -e "${GRAY}注: 如需完全恢复编译时默认值，可重启系统${RESET}"
        ui_pause "按 Enter 继续..."
        ;;
        
      "?"|"？")
        # --- 参数帮助说明 ---
        clear
        echo -e "${BOLD}${CYAN}TCP 调优参数帮助说明${RESET}"
        echo -e "${CYAN}==================================================${RESET}"
        echo -e ""
        echo -e "${YELLOW}【缓冲区参数】${RESET}"
        echo -e "  ${GREEN}rmem_max/wmem_max${RESET}"
        echo -e "    操作系统层面的单连接最大接收/发送缓冲区上限 (ceiling)。"
        echo -e "    计算依据: 基于 BDP (带宽×RTT) 经验估算，作为内核窗口调节的伸展天花板。"
        echo -e ""
        echo -e "  ${GREEN}tcp_rmem/tcp_wmem${RESET}"
        echo -e "    格式: 最小值 默认值 最大值"
        echo -e "    Linux 内核据此自动调节滑动窗口。默认值(default)供新建连接初始分配，"
        echo -e "    最大值(max)决定大流量并发时的单连接吞吐极值。"
        echo -e ""
        echo -e "  ${GREEN}optmem_max${RESET}"
        echo -e "    辅助缓冲区上限，用于 IP 选项等 socket 辅助控制内存。"
        echo -e ""
        echo -e "${YELLOW}【连接队列参数】${RESET}"
        echo -e "  ${GREEN}somaxconn${RESET}"
        echo -e "    完成三次握手但尚未被 accept 的已完成连接队列上限。高并发 Web 服务建议适当增大。"
        echo -e ""
        echo -e "  ${GREEN}tcp_max_syn_backlog${RESET}"
        echo -e "    半连接队列 (SYN_RECV 状态) 上限。应对突发并发握手与 SYN 洪水。"
        echo -e ""
        echo -e "${YELLOW}【连接管理参数】${RESET}"
        echo -e "  ${GREEN}tcp_fin_timeout${RESET}"
        echo -e "    孤儿 socket 保持在 FIN_WAIT_2 状态的超时时间 (秒)。仅加快断开连接的资源回收，不改变网络传输延迟。"
        echo -e ""
        echo -e "  ${GREEN}tcp_tw_reuse${RESET}"
        echo -e "    安全复用 TIME_WAIT 状态套接字: 0=禁用, 1=全局启用, 2=仅适用于 loopback 本地回路。"
        echo -e ""
        echo -e "  ${GREEN}tcp_max_tw_buckets${RESET}"
        echo -e "    系统同时保持 TIME_WAIT 套接字的最大数量，超出后直接销毁以防内存耗尽。"
        echo -e ""
        echo -e "  ${GREEN}ip_local_port_range${RESET}"
        echo -e "    本地出站临时端口范围 (起始 结束)。高并发主动外连场景推荐扩展为 10240 65535。"
        echo -e ""
        echo -e "  ${GREEN}tcp_keepalive_time/intvl/probes${RESET}"
        echo -e "    TCP 保活探测心跳配置: 空闲探测时间/重试间隔/最大探测次数，用于发现对端死连接。"
        echo -e ""
        echo -e "${YELLOW}【队列积压与性能参数】${RESET}"
        echo -e "  ${GREEN}tcp_notsent_lowat${RESET}"
        echo -e "    限制发送端未发送排队数据的低水位线。显著减少发送队列积压，抑制缓冲膨胀 (Bufferbloat)。"
        echo -e ""
        echo -e "  ${GREEN}tcp_fastopen${RESET}"
        echo -e "    TFO 快速打开: 0=禁用, 1=客户端, 2=服务端, 3=双向启用 (需双方及应用层支持)。"
        echo -e ""
        echo -e "  ${GREEN}tcp_mtu_probing${RESET}"
        echo -e "    路径 MTU 黑洞自动探测: 0=禁用, 1=检测到黑洞时启用, 2=始终启用。"
        echo -e ""
        echo -e "  ${GREEN}tcp_slow_start_after_idle${RESET}"
        echo -e "    连接空闲后是否重置慢启动: 0=禁用 (长连接推荐保持已探测窗口), 1=启用。"
        echo -e ""
        echo -e "  ${GREEN}tcp_window_scaling${RESET}"
        echo -e "    RFC 1323 窗口缩放支持: 允许 TCP 接收窗口突破 64KB，高速大延迟链路必备 (建议=1)。"
        echo -e ""
        echo -e "  ${GREEN}tcp_sack${RESET}"
        echo -e "    选择性确认 (SACK): 允许只重传丢失的报文段，降低丢包恢复惩罚 (建议=1)。"
        echo -e ""
        echo -e "  ${GREEN}tcp_ecn${RESET}"
        echo -e "    显式拥塞通知: 0=禁用, 1=按需启用, 2=强制启用。"
        echo -e "${CYAN}==================================================${RESET}"
        ui_pause "按 Enter 返回..."
        ;;
        
      0)
        return 0
        ;;
      *)
        echo -e "${RED}无效选项${RESET}"
        sleep 1
        ;;
    esac
  done
}

# --- [6. 系统] 系统内核管理 ---

manage_kernels() {
  while true; do
    clear
    echo -e "${BOLD}${CYAN}系统内核管理${RESET}"
    echo -e "${CYAN}==================================================${RESET}"
    echo -e "当前运行内核: ${GREEN}$(uname -r)${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"
    echo -e "  ${YELLOW}1.${RESET} 显示所有已安装内核"
    echo -e "  ${YELLOW}2.${RESET} 清理多余内核 (可选择保留 1 或 2 个)"
    echo -e "  ${YELLOW}3.${RESET} ${RED}重启系统 (Reboot)${RESET}"
    echo -e "  ${YELLOW}0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"
    ui_menu_read choice "请输入选项" "[0-3]" || return 0
    case $choice in
      1)
         echo -e "${CYAN}>>> 系统已安装的内核列表:${RESET}"
         if [[ "$PKG_MANAGER" == "apt" ]]; then
             local cur_k=$(uname -r)
             dpkg -l | awk -v cur="$cur_k" '$1=="ii" && $2 ~ /^(linux-image|pve-kernel|proxmox-kernel)-[0-9]+\.[0-9]+\.[0-9]+/ {
                 if ($2 ~ cur) {
                     printf "%-42s %-25s \033[32m[当前运行]\033[0m\n", $2, $3
                 } else {
                     printf "%-42s %-25s\n", $2, $3
                 }
             }'
         elif [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "dnf5" || "$PKG_MANAGER" == "yum" ]]; then
             rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' | grep -E "^kernel(-core)?-[0-9]" | sort -V
         else
             echo -e "${YELLOW}[?] 包管理器未知，尝试读取 /boot 目录:${RESET}"
             ls -1 /boot/vmlinuz-* 2>/dev/null | sed 's|/boot/vmlinuz-||' || echo -e "${RED}[!] 未找到内核镜像${RESET}"
         fi
         ui_pause "按 Enter 继续..."
         ;;
      2)
         SKIP_PAUSE=false
         clean_kernels
         if [ "$SKIP_PAUSE" = false ]; then
           ui_pause "按 Enter 继续..."
         fi
         SKIP_PAUSE=false
         ;;
      3)
         echo -e "${YELLOW}[!] 警告: 即将重启系统，所有未保存的数据将丢失！${RESET}"
         ui_confirm reboot_confirm "确认重启? [y/N] (q 取消): "
         if [[ "$reboot_confirm" =~ ^[Yy]$ ]]; then
           echo -e "${CYAN}>>> 系统将在 3 秒后重启...${RESET}"
           sleep 3
           reboot
         else
           echo -e "${YELLOW}已取消重启${RESET}"
           sleep 1
         fi
         ;;
      0) return 0 ;;
      *) echo -e "${RED}无效选项${RESET}"; sleep 1 ;;
    esac
  done
}

# --- [子功能] 旧内核清理 ---

clean_kernels() {
  # [容器适配] 容器共享宿主机内核，无独立安装的内核包
  if is_container_env; then
    echo -e "${YELLOW}[!] 检测到容器环境 (OpenVZ/LXC/Docker)${RESET}"
    echo -e "${GRAY}容器共享宿主机内核，无需清理独立内核包。${RESET}"
    return 0
  fi
  
  if [[ "$PKG_MANAGER" == "apt" || "$PKG_MANAGER" == "dnf5" || "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
    local count_limit=2 keep_choice
    echo -e "${CYAN}请选择内核保留策略:${RESET}"
    echo -e "  1. 只保留最新一个内核 (将失去旧内核故障回退能力)"
    echo -e "  2. 保留两个内核 (默认，建议保留旧内核用于故障回退)"
    echo -e "${YELLOW}当前运行内核及 PVE 固定/手动保留内核始终受保护，实际保留数量可能更多。${RESET}"
    while true; do
      ui_menu_read keep_choice "请输入保留数量 [1/2，默认 2]" "[0-2]" 2 || return 0
      case "$keep_choice" in
        ""|2) count_limit=2; break ;;
        1) count_limit=1; break ;;
        0) echo -e "${YELLOW}已取消清理操作${RESET}"; SKIP_PAUSE=true; return 0 ;;
        *) echo -e "${YELLOW}请输入 1、2 或 0。${RESET}" ;;
      esac
    done
    if [ "$count_limit" -eq 1 ]; then
      echo -e "${YELLOW}[!] 已选择只保留最新内核；若该内核无法启动，将没有旧内核可供回退。${RESET}"
    fi
  fi

  echo -e "${CYAN}>>> 扫描可清理内核...${RESET}"
  
  if [[ "$PKG_MANAGER" == "apt" ]]; then
      # --- Debian/Ubuntu/PVE 清理逻辑 ---
      # 1. 优先清理残留配置 (rc 状态)
      local rc_kernels=$(dpkg -l | grep "^rc" | awk '{print $2}' | grep -E "^(linux-(image|headers|modules)|pve-(kernel|headers)|proxmox-(kernel|headers))")
      if [ -n "$rc_kernels" ]; then
        echo -e "${YELLOW}发现已卸载内核的残留配置，正在自动清理...${RESET}"
        check_pm_lock || return 1
        echo "$rc_kernels" | xargs apt-get -y purge
      fi

      # 2. 扫描已安装的内核并分类
      local current_ver=$(uname -r)
      local current_base_ver=$(echo "$current_ver" | grep -oE '^[0-9]+\.[0-9]+(\.[0-9]+)?-[0-9]+')
      echo -e "当前运行内核: ${GREEN}$current_ver${RESET}"

      # 扫描 PVE 固定的内核 (Pinned Kernels) 与手动保留内核 (Manual Keep / Next-Boot)
      local pve_pinned_kernels=""
      if [ "$IS_PVE" = true ]; then
        local pve_detected_raw=""
        if command -v proxmox-boot-tool >/dev/null 2>&1; then
          # 状态机精准解析 section-style 输出 (支持标题与值分行、Manually selected 区块以及行内 (pinned) 标注)
          pve_detected_raw=$(proxmox-boot-tool kernel list 2>/dev/null | awk '
            /Manually selected kernels:/ { in_manual=1; in_pinned=0; next }
            /Automatically selected kernels:/ { in_manual=0; in_pinned=0 }
            /^[[:space:]]*(Pinned( kernel)?|Kernel pinned on next-boot):/ {
              in_pinned=1; in_manual=0
              for(i=1; i<=NF; i++) if($i ~ /^[0-9]+\.[0-9]+/) print $i
              next
            }
            (in_manual || in_pinned) && /^[[:space:]]*[0-9]/ { print $1 }
            /\(pinned\)/ { for(i=1; i<=NF; i++) if($i ~ /^[0-9]+\.[0-9]+/) print $i }
          ')
        fi
        # 直接读取 PVE 官方引导底层手动保留与下次启动配置文件
        [ -f /etc/kernel/pve-efiboot-manual-kernels ] && pve_detected_raw="$pve_detected_raw $(cat /etc/kernel/pve-efiboot-manual-kernels 2>/dev/null)"
        [ -f /etc/kernel/next-boot ] && pve_detected_raw="$pve_detected_raw $(cat /etc/kernel/next-boot 2>/dev/null)"
        local pve_keep_file
        for pve_keep_file in /etc/kernel/proxmox-boot-manual-kernels /etc/kernel/proxmox-boot-pin /etc/kernel/next-boot-pin; do
          [ -f "$pve_keep_file" ] && pve_detected_raw="$pve_detected_raw $(cat "$pve_keep_file" 2>/dev/null)"
        done
        pve_pinned_kernels=$(echo "$pve_detected_raw" | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sort -u)
        if [ -n "$pve_pinned_kernels" ]; then
          echo -e "${CYAN}检测到 PVE 固定与手动保留内核 (Pinned/Manual Keep):${RESET}"
          for pk in $pve_pinned_kernels; do echo -e "  • ${GREEN}$pk${RESET}"; done
        fi
      fi

      # 获取所有已安装的内核包 (包括镜像、头文件与模块，排除元包、树莓派/Armbian引导固件与内核)
      local all_kernels=$(dpkg -l | grep "^ii" | awk '{print $2}' | grep -E "^(linux-(image|headers|modules(-extra)?)|pve-(kernel|headers)|proxmox-(kernel|headers))-[0-9]+\.[0-9]+\.[0-9]+" | grep -vE "(proxmox-default-kernel|proxmox-ve|linux-image-generic|linux-image-amd64|raspberrypi-.*|rpi-.*|raspi-.*|armbian-.*|linux-dtb-.*)")
      
      # 仅从真正可启动的内核镜像提取内核版本集合 (严防 linux-headers-*-common 等公共包被误判为独立新内核)
      local bootable_images=$(echo "$all_kernels" | tr ' ' '\n' | grep -E "^(linux-image|pve-kernel|proxmox-kernel)-[0-9]+\.[0-9]+\.[0-9]+" | grep -vE -- "-common($|-)")
      local all_versions=$(for k in $bootable_images; do
          echo "$k" | sed -E 's/^(linux-image|pve-kernel|proxmox-kernel)(-signed)?-//' | sed -E 's/-signed$//'
      done | sort -uV)

      local newer_kernels=""
      local older_versions_list=()

      for v in $all_versions; do
          # 跳过当前内核及其基础版本
          if [[ "$v" == "$current_ver" ]] || [[ -n "$current_base_ver" && "$v" == "$current_base_ver" ]]; then
              continue
          fi
          # 跳过 PVE 固定内核
          if [[ -n "$pve_pinned_kernels" ]] && echo "$pve_pinned_kernels" | grep -Fxq "$v"; then
              echo -e "${CYAN}检测到 PVE 固定内核 (Pinned): $v，自动列入保护名单${RESET}"
              continue
          fi

          if [[ $(echo -e "$v\n$current_ver" | sort -V | tail -n1) == "$v" ]]; then
              newer_kernels="$newer_kernels $v"
          else
              older_versions_list+=("$v")
          fi
      done

      if [ -n "$newer_kernels" ]; then
          echo -e "${YELLOW}警告: 发现比当前运行版本更${RED}新${YELLOW}的内核 (可能刚安装未重启):${RESET}"
          echo "$newer_kernels"
          echo -e "${CYAN}建议: 若要应用这些新版本，请先重启系统再进行清理。${RESET}\n"
          if [ "$count_limit" -eq 1 ]; then
              echo -e "${YELLOW}[!] 当前尚未运行最新内核，请重启并确认最新内核可用后，再选择只保留一个。${RESET}"
              return 0
          fi
      fi

      # 由操作者决定是否保留额外的旧内核用于回退。
      local purge_versions=()
      local fallback_kernel="" fallback_base_ver=""
      local total_old=${#older_versions_list[@]}

      if [ "$total_old" -eq 0 ]; then
          echo -e "${GREEN}[√] 未发现任何多余的旧内核。${RESET}"
          return 0
      fi

      if [ "$count_limit" -eq 2 ]; then
        # 回退候选必须是已安装且具有 /boot 镜像的内核，不能把 common headers 当作可启动内核。
        local fallback_versions="" candidate_pkg candidate_ver
        for candidate_pkg in $all_kernels; do
          [[ "$candidate_pkg" =~ ^(linux-image|pve-kernel|proxmox-kernel)- ]] || continue
          candidate_ver=$(echo "$candidate_pkg" | sed -E 's/^(linux-image|pve-kernel|proxmox-kernel)-//;s/-signed$//')
          [ "$candidate_ver" = "$current_ver" ] && continue
          [ -f "/boot/vmlinuz-$candidate_ver" ] || continue
          if [ "$(printf '%s\n' "$candidate_ver" "$current_ver" | sort -V | tail -n1)" = "$current_ver" ]; then
            fallback_versions="$fallback_versions $candidate_ver"
          fi
        done
        fallback_kernel=$(printf '%s\n' $fallback_versions | sort -uV | tail -n1)
        if [ -z "$fallback_kernel" ]; then
          echo -e "${YELLOW}[!] 无法确认可启动的旧内核回退，停止清理${RESET}"
          return 1
        fi
        fallback_base_ver=$(echo "$fallback_kernel" | grep -oE '^[0-9]+\.[0-9]+(\.[0-9]+)?-[0-9]+')
        echo -e "${CYAN}系统应急回退内核 (按所选策略保留): ${GREEN}$fallback_kernel${RESET}"
  
      else
          echo -e "${YELLOW}按所选策略，不额外保留旧内核作为故障回退。${RESET}"
      fi

      for ((idx=0; idx<total_old; idx++)); do
          [ "${older_versions_list[$idx]}" = "$fallback_kernel" ] && continue
          purge_versions+=("${older_versions_list[$idx]}")
      done

      # 组装待清理的包列表 (严格版本边界匹配，坚决杜绝子串误匹配)
      local old_kernels_list=()
      for pv in "${purge_versions[@]}"; do
          [ -z "$pv" ] && continue
          local escaped_pv=$(echo "$pv" | sed 's/[.+-]/\\&/g')
          # 精确匹配版本边界 (-pv 或 -pv-)，避免 6.8.0-31 误匹配 6.8.0-311
          local matches=$(echo "$all_kernels" | tr ' ' '\n' | grep -E -- "-${escaped_pv}($|-)")
          for pkg in $matches; do
              # 核心白名单防御：绝对排除当前运行内核、回退内核及 PVE 固定内核！
              if [[ -n "$current_ver" ]] && echo "$pkg" | grep -qE -- "-$(echo "$current_ver" | sed 's/[.+-]/\\&/g')($|-)"; then
                  continue
              fi
              if [[ -n "$current_base_ver" ]] && echo "$pkg" | grep -qE -- "-$(echo "$current_base_ver" | sed 's/[.+-]/\\&/g')($|-)"; then
                  continue
              fi
              if [[ -n "$fallback_kernel" ]] && echo "$pkg" | grep -qE -- "-$(echo "$fallback_kernel" | sed 's/[.+-]/\\&/g')($|-)"; then
                  continue
              fi
              if [[ -n "$fallback_base_ver" ]] && echo "$pkg" | grep -qE -- "-$(echo "$fallback_base_ver" | sed 's/[.+-]/\\&/g')($|-)"; then
                  continue
              fi
              if [[ -n "$pve_pinned_kernels" ]]; then
                  local is_pinned=false
                  for pk in $pve_pinned_kernels; do
                      local pk_base
                      pk_base=$(echo "$pk" | grep -oE '^[0-9]+\.[0-9]+(\.[0-9]+)?-[0-9]+')
                      if [[ -n "$pk_base" ]] && echo "$pkg" | grep -qE -- "-$(echo "$pk_base" | sed 's/[.+-]/\\&/g')($|-)"; then
                          is_pinned=true; break
                      fi
                      if echo "$pkg" | grep -qE -- "-$(echo "$pk" | sed 's/[.+-]/\\&/g')($|-)"; then
                          is_pinned=true; break
                      fi
                  done
                  [ "$is_pinned" = true ] && continue
              fi
              old_kernels_list+=("$pkg")
          done
      done
      # 去重
      local old_kernels=$(printf "%s\n" "${old_kernels_list[@]}" | sort -u | xargs)

      if [ -z "$old_kernels" ]; then
        echo -e "${GREEN}[√] 经安全白名单严格校验，无需清理任何内核。${RESET}"
        return 0
      fi

      if [ -n "$fallback_kernel" ]; then
          echo -e "${CYAN}已保留回退内核: $fallback_kernel${RESET}"
      else
          echo -e "${YELLOW}未额外保留回退内核；当前运行内核及 PVE 保护内核仍保留。${RESET}"
      fi
      echo -e "${YELLOW}发现以下${RED}待清理旧内核${YELLOW}版本:${RESET}"
      for pv in "${purge_versions[@]}"; do echo -e "  - $pv"; done
      echo -e "${CYAN}涉及的软件包清单:${RESET}\n$old_kernels\n"

      ui_confirm k_choice "确认安全清理上述冗余旧内核? [y/N] (q 取消): "
      if [[ "$k_choice" =~ ^[Yy]$ ]]; then
         check_pm_lock || return 1
         echo -e "${CYAN}正在清理旧内核...${RESET}"
         echo "$old_kernels" | xargs apt-get -y purge || return 1
         
         # [PVE保护] 严禁在 PVE 下无确认执行 autoremove，避免误删 proxmox-ve 元包
         if [ "$IS_PVE" = true ]; then
             echo -e "${YELLOW}[!] 检测到 PVE 宿主机环境，跳过 autoremove 以确保 PVE 核心元包安全${RESET}"
         else
             echo -e "${YELLOW}[!] 内核清理不执行全局 autoremove，避免删除已保护的内核依赖${RESET}"
         fi

         # 刷新引导配置 (支持 PVE systemd-boot 及传统 GRUB)
         if [ "$IS_PVE" = true ] && command -v proxmox-boot-tool >/dev/null 2>&1; then
             echo -e "${CYAN}>>> 正在同步 Proxmox systemd-boot 引导分区...${RESET}"
             proxmox-boot-tool refresh 2>&1 | grep -v "File descriptor.*leaked"
         fi

         if command -v update-grub >/dev/null 2>&1; then
             update-grub 2>&1 | grep -v "File descriptor.*leaked"
         elif [ -f /boot/grub2/grub.cfg ]; then
             grub2-mkconfig -o /boot/grub2/grub.cfg 2>&1 | grep -v "File descriptor.*leaked"
         elif [ -f /boot/grub/grub.cfg ] && command -v grub-mkconfig >/dev/null 2>&1; then
             grub-mkconfig -o /boot/grub/grub.cfg || return 1
         else
             echo -e "${YELLOW}[!] 未找到已确认的 GRUB 主配置，未写入 EFI 厂商引导文件${RESET}"
         fi
         echo -e "${GREEN}[√] 内核清理完成 (已按所选策略保留内核并刷新引导)${RESET}"
      else
         echo -e "${YELLOW}已取消清理操作${RESET}"
         SKIP_PAUSE=true
      fi

  elif [[ "$PKG_MANAGER" == "dnf5" || "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
      # --- CentOS/RHEL/Rocky/Alma 清理逻辑 ---
      echo -e "${CYAN}正在检查旧内核...${RESET}"
      local running_k=$(uname -r)
      
      if [[ "$PKG_MANAGER" == "dnf5" ]]; then
          echo -e "${YELLOW}使用 DNF 5 清理多余旧内核 (保留最近 $count_limit 个且绝对保护运行内核)${RESET}"
          ui_confirm k_choice "确认清理? [y/N] (q 取消): "
          if [[ "$k_choice" =~ ^[Yy]$ ]]; then
             check_pm_lock || return 1
             local old_k
             if ! old_k=$(dnf5 repoquery --installonly --latest-limit=-$count_limit 2>/dev/null); then
                echo -e "${RED}[×] 查询旧内核失败，未执行清理以保护系统安全${RESET}"
                return 1
             fi
             # 关键防御：过滤排除当前运行内核，杜绝误卸载正在运行的旧内核
             local safe_old_k=""
             for pkg in $old_k; do
                 if [[ "$pkg" == *"$running_k"* ]]; then
                     echo -e "${CYAN}检测到运行内核包 $pkg ，予以绝对豁免保护${RESET}"
                 else
                     safe_old_k="$safe_old_k $pkg"
                 fi
             done
             if [ -n "$safe_old_k" ]; then
                if echo "$safe_old_k" | xargs dnf5 remove -y; then
                   echo -e "${GREEN}[√] DNF 5 旧内核安全清理完成${RESET}"
                else
                   echo -e "${RED}[×] DNF 5 旧内核清理失败${RESET}"
                   return 1
                fi
             else
                echo -e "${GREEN}[√] 没有需要清理的旧内核${RESET}"
             fi
          else
             SKIP_PAUSE=true
          fi
      elif [[ "$PKG_MANAGER" == "dnf" ]]; then
          echo -e "${YELLOW}将使用 DNF 清理旧内核 (保留最近 $count_limit 个并保护运行内核)${RESET}"
          ui_confirm k_choice "确认清理? [y/N] (q 取消): "
          if [[ "$k_choice" =~ ^[Yy]$ ]]; then
             check_pm_lock || return 1
             if [ "$count_limit" -eq 1 ]; then
                # DNF 不允许 installonly_limit=1，查询旧包后显式移除。
                local old_k safe_old_k="" pkg
                old_k=$(dnf repoquery --installed --installonly --latest-limit=-1 2>/dev/null) || {
                   echo -e "${RED}[!] 查询旧内核失败，未执行清理${RESET}"
                   return 1
                }
                for pkg in $old_k; do
                   if [[ "$pkg" == *"$running_k"* ]]; then
                      echo -e "${CYAN}检测到运行内核包 $pkg，予以保护；请启动最新内核后再清理${RESET}"
                   else
                      safe_old_k="$safe_old_k $pkg"
                   fi
                done
                if [ -n "$safe_old_k" ]; then
                   echo "$safe_old_k" | xargs dnf remove -y || return 1
                else
                   echo -e "${GREEN}[√] 没有需要清理的旧内核${RESET}"
                fi
             else
                check_pm_lock || return 1
                dnf remove --oldinstallonly --setopt installonly_limit=$count_limit -y || { echo -e "${RED}[×] DNF 内核清理失败${RESET}"; return 1; }
                echo -e "${GREEN}[√] 清理完成${RESET}"
             fi
          else
             SKIP_PAUSE=true
          fi
      else
          check_pm_lock || return 1
          if ! command -v package-cleanup &>/dev/null; then
             $PKG_INSTALL yum-utils || { echo -e "${RED}[×] 安装 yum-utils 失败${RESET}"; return 1; }
          fi
          echo -e "${YELLOW}将使用 package-cleanup --oldkernels (保留最近 $count_limit 个)${RESET}"
          ui_confirm k_choice "确认清理? [y/N] (q 取消): "
          if [[ "$k_choice" =~ ^[Yy]$ ]]; then
             check_pm_lock || return 1
             package-cleanup --oldkernels --count=$count_limit -y || { echo -e "${RED}[×] YUM 内核清理失败${RESET}"; return 1; }
             echo -e "${GREEN}[√] 清理完成${RESET}"
          else
             SKIP_PAUSE=true
          fi
      fi
  else
      echo -e "${RED}[!] 当前系统不支持此脚本的内核清理功能${RESET}"
  fi
}

# --- [6. 系统] SWAP 虚拟内存管理 ---
manage_swap() {
  while true; do
    clear
    echo -e "${BOLD}${CYAN}SWAP 虚拟内存管理${RESET}"
    echo -e "${CYAN}==================================================${RESET}"

    # 获取当前 Swap 信息
    local swap_total=$(free -m | awk '/Swap:/ {print $2}')
    local swap_used=$(free -m | awk '/Swap:/ {print $3}')
    local swap_file_exists=false
    if [ -f /swapfile ] || grep -q "/swapfile" /etc/fstab; then
      swap_file_exists=true
    fi

    echo -e "当前 Swap 总量: ${GREEN}${swap_total} MB${RESET}"
    echo -e "当前 Swap 已用: ${YELLOW}${swap_used} MB${RESET}"
    
    if [ "$swap_file_exists" = true ]; then
      echo -e "检测到交换文件: ${GREEN}/swapfile (由脚本管理)${RESET}"
    else
      echo -e "检测到交换文件: ${GRAY}无 (或非标准路径)${RESET}"
    fi
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    echo -e "${YELLOW}  1.${RESET} 添加/扩容 Swap (自动创建 /swapfile)"
    echo -e "${YELLOW}  2.${RESET} 调整 Swappiness (优化小内存)"
    echo -e "${YELLOW}  3.${RESET} ${RED}删除/关闭 Swap (清理 /swapfile)${RESET}"
    echo -e "${YELLOW}  0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"

    ui_menu_read choice "请输入选项" "[0-3]" || return 0
    case $choice in
    1)
      echo -e "\n${CYAN}>>> 设置 Swap 大小${RESET}"
      echo -e "${GRAY}提示: 输入 0 可返回上一级菜单${RESET}"
      echo -e "请输入需要的大小 (单位: MB)，例如 1024, 2048, 4096"
      ui_read_field swap_size "大小 (MB)" ui_positive "" true || continue

      # --- [安全防御 0] 容器环境拦截 (LXC/Docker/OpenVZ 共享内核无 swapon 权限) ---
      if is_container_env; then
        echo -e "\n${RED}[!] 警告：检测到当前系统运行在容器环境中 (LXC/Docker/OpenVZ)！${RESET}"
        echo -e "${YELLOW}容器环境与宿主机共享 Linux 内核，通常不具备独立 swapon 权限。${RESET}"
        echo -e "${GRAY}建议在宿主机 (如 PVE 节点配置或云控制台) 中为该容器直接分配 Swap 配额。${RESET}"
        ui_confirm force_swap "是否仍尝试强制创建? [y/N] (q 取消): "
        if [[ ! "$force_swap" =~ ^[Yy]$ ]]; then
          continue
        fi
      fi

      # --- [安全防御 1] ZFS 文件系统与文件式 Swap 冲突拦截 ---
      local root_fstype=$(df -T / 2>/dev/null | awk 'NR==2 {print $2}')
      if [[ "$root_fstype" == "zfs" ]]; then
        echo -e "\n${RED}[!] 错误：检测到当前根分区为 ZFS 文件系统！${RESET}"
        echo -e "${YELLOW}Linux 内核在 ZFS 普通文件上启用 Swap 会导致系统死锁与不可预期的内核崩溃。${RESET}"
        echo -e "${CYAN}在 ZFS 架构 (如 Proxmox VE ZFS 安装) 下，应使用 ZFS Volume (ZVOL) 创建 Swap：${RESET}"
        echo -e "${GRAY}例如: zfs create -V ${swap_size}M -b \$(getconf PAGESIZE) -o compression=zle -o sync=always -o primarycache=metadata rpool/swap${RESET}"
        ui_pause "按 Enter 返回..."
        continue
      fi

      # --- [安全防御 2] 磁盘可用空间预检 ---
      local avail_mb=$(df -m / 2>/dev/null | awk 'NR==2 {print $4}')
      if [ -n "$avail_mb" ] && [ "$avail_mb" -lt "$((swap_size + 256))" ]; then
        echo -e "\n${RED}[!] 磁盘剩余空间不足！当前可用: ${avail_mb}MB，所需: ${swap_size}MB (+256MB安全余量)${RESET}"
        ui_pause "按 Enter 继续..."
        continue
      fi

      # --- [安全防御 3] 原子创建新 Swap 文件，验证可用后再切换，杜绝先删后崩 ---
      local tmp_swap="/swapfile.tmp.$$"
      echo -e "${CYAN}正在创建 ${swap_size}MB 的临时 Swap 文件 ($tmp_swap)...${RESET}"
      
      local dd_success=false
      local dd_progress_opt=""
      if dd if=/dev/null of=/dev/null status=progress >/dev/null 2>&1; then
        dd_progress_opt="status=progress"
      fi
      if [[ "$root_fstype" == "btrfs" ]]; then
        truncate -s 0 "$tmp_swap" 2>/dev/null || touch "$tmp_swap"
        chattr +C "$tmp_swap" 2>/dev/null
        dd if=/dev/zero of="$tmp_swap" bs=1M count="$swap_size" conv=notrunc,fsync $dd_progress_opt
        [ $? -eq 0 ] && dd_success=true
      elif [[ "$root_fstype" == "xfs" ]]; then
        if command -v fallocate >/dev/null 2>&1; then
          fallocate -l "${swap_size}M" "$tmp_swap" 2>/dev/null && dd_success=true
        fi
        if [ "$dd_success" = false ]; then
          dd if=/dev/zero of="$tmp_swap" bs=1M count="$swap_size" conv=fsync $dd_progress_opt
          [ $? -eq 0 ] && dd_success=true
        fi
      else
        dd if=/dev/zero of="$tmp_swap" bs=1M count="$swap_size" conv=fsync $dd_progress_opt
        [ $? -eq 0 ] && dd_success=true
      fi

      if [ "$dd_success" = false ]; then
        echo -e "${RED}[!] 磁盘写入失败，无法创建临时 Swap 文件${RESET}"
        rm -f "$tmp_swap" 2>/dev/null
        ui_pause "按 Enter 继续..."
        continue
      fi

      chmod 600 "$tmp_swap"
      if ! mkswap "$tmp_swap" >/dev/null 2>&1; then
        echo -e "${RED}[!] mkswap 格式化失败${RESET}"
        rm -f "$tmp_swap" 2>/dev/null
        ui_pause "按 Enter 继续..."
        continue
      fi

      # 验证新 Swap 是否可以被当前内核安全加载
      local test_swapon_out
      test_swapon_out=$(swapon "$tmp_swap" 2>&1)
      local test_ret=$?
      if [ $test_ret -ne 0 ]; then
        echo -e "${RED}[!] 新 Swap 试激活失败: $test_swapon_out${RESET}"
        rm -f "$tmp_swap" 2>/dev/null
        if echo "$test_swapon_out" | grep -qi "permission"; then
          echo -e "${YELLOW}检测到内核权限拦截，很可能是无特权容器 (LXC/OpenVZ) 环境限制。${RESET}"
        fi
        ui_pause "按 Enter 继续..."
        continue
      fi

      # 记录原 /swapfile 是否处于激活状态
      local old_swap_was_active=false
      if [ -f /swapfile ] && swapon --show=NAME --noheadings 2>/dev/null | awk '$1 == "/swapfile" { found=1 } END { exit !found }'; then
        old_swap_was_active=true
      fi

      # 新 Swap 验证可行，卸载临时挂载以完成交接 (严格校验 swapoff 结果)
      if ! swapoff "$tmp_swap" 2>/dev/null; then
        echo -e "${RED}[!] 临时 Swap 停用失败，中止交接以防内核挂载泄漏${RESET}"
        echo -e "${YELLOW}[!] 保留仍活动的临时 Swap: $tmp_swap，请手动检查${RESET}"
        ui_pause "按 Enter 继续..."
        continue
      fi

      # 停用旧的 /swapfile (如有且处于激活中)，必须核查 swapoff 结果
      if [ "$old_swap_was_active" = true ]; then
        echo -e "${CYAN}正在卸载旧 Swap...${RESET}"
        if ! swapoff /swapfile 2>/dev/null; then
          echo -e "${RED}[!] 错误：旧 Swap 卸载失败 (可能是当前物理内存吃紧，无法转存已占用的 Swap 数据)${RESET}"
          echo -e "${YELLOW}为保护现有系统稳定性，已自动中止扩容操作并清理临时文件。${RESET}"
          rm -f "$tmp_swap" 2>/dev/null
          ui_pause "按 Enter 继续..."
          continue
        fi
      fi

      # 原子安全交接与故障回滚 (使用带时间戳与PID的唯一备份路径，杜绝覆盖历史残留备份)
      local swap_bak="/swapfile.orig_bak.$$.$(date +%s)"
      local has_old_swap=false
      if [ -f /swapfile ]; then
        has_old_swap=true
        if ! mv -f /swapfile "$swap_bak" 2>/dev/null; then
          echo -e "${RED}[!] 备份原 /swapfile 失败，中止交接操作${RESET}"
          rm -f "$tmp_swap" 2>/dev/null
          [ "$old_swap_was_active" = true ] && swapon /swapfile 2>/dev/null || true
          ui_pause "按 Enter 继续..."
          continue
        fi
      fi

      # 移动新文件到目标路径并检查返回值
      if ! mv -f "$tmp_swap" /swapfile 2>/dev/null; then
        echo -e "${RED}[!] 部署新 Swap 文件到 /swapfile 失败，正在回滚原 Swap...${RESET}"
        rm -f "$tmp_swap" 2>/dev/null
        if [ "$has_old_swap" = true ]; then
          mv -f "$swap_bak" /swapfile || { echo -e "${RED}[×] 原 Swap 文件恢复失败，备份保留: $swap_bak${RESET}"; return 1; }
          if [ "$old_swap_was_active" = true ]; then
            swapon /swapfile || { echo -e "${RED}[×] 原 Swap 重新激活失败，请检查内存和权限${RESET}"; return 1; }
          fi
        fi
        ui_pause "按 Enter 继续..."
        continue
      fi

      # 设置权限并检查返回值
      if ! chmod 600 /swapfile 2>/dev/null; then
        echo -e "${RED}[!] 设置 /swapfile 权限 600 失败，正在紧急回滚...${RESET}"
        rm -f /swapfile 2>/dev/null
        if [ "$has_old_swap" = true ]; then
          mv -f "$swap_bak" /swapfile || { echo -e "${RED}[×] 原 Swap 文件恢复失败，备份保留: $swap_bak${RESET}"; return 1; }
          if [ "$old_swap_was_active" = true ]; then
            swapon /swapfile || { echo -e "${RED}[×] 原 Swap 重新激活失败，请检查内存和权限${RESET}"; return 1; }
          fi
        fi
        ui_pause "按 Enter 继续..."
        continue
      fi
      
      # 激活新 Swap
      local swapon_err
      if ! swapon_err=$(swapon /swapfile 2>&1); then
        echo -e "${RED}[×] 激活新 /swapfile 失败: $swapon_err${RESET}"
        echo -e "${YELLOW}正在紧急回滚恢复原 Swap 配置与运行状态...${RESET}"
        rm -f /swapfile 2>/dev/null
        if [ "$has_old_swap" = true ]; then
          mv -f "$swap_bak" /swapfile || { echo -e "${RED}[×] 原 Swap 文件恢复失败，备份保留: $swap_bak${RESET}"; return 1; }
          if [ "$old_swap_was_active" = true ]; then
            swapon /swapfile 2>/dev/null && echo -e "${GREEN}[√] 已成功恢复原 Swap 文件与激活状态${RESET}" || echo -e "${RED}[!] 原 Swap 恢复激活失败，请检查系统内存与日志${RESET}"
          fi
        fi
        ui_pause "按 Enter 继续..."
        continue
      fi

      # 验证 /etc/fstab 持久化写入
      local fstab_ok=true
      if awk '$1 == "/swapfile" {found=1} END {exit !found}' /etc/fstab 2>/dev/null; then
        if ! awk '$1 == "/swapfile" {total++; if($3 == "swap" && $4 !~ /(^|,)noauto(,|$)/) valid++} END {exit !(total == 1 && valid == 1)}' /etc/fstab; then
          fstab_ok=false
          echo -e "${RED}[!] 现有 /swapfile fstab 条目类型/选项错误或重复，未覆盖管理员条目，备份保留${RESET}"
        fi
      else
        if ! echo "/swapfile none swap sw 0 0" >>/etc/fstab 2>/dev/null; then
          fstab_ok=false
          echo -e "${RED}[!] 写入 /etc/fstab 失败 (可能文件系统受保护或只读)${RESET}"
        fi
      fi

      if [ "$fstab_ok" = true ]; then
        # 仅在所有交接、激活、持久化流程全部无误后，才清理旧备份
        [ "$has_old_swap" = true ] && rm -f "$swap_bak" 2>/dev/null
        echo -e "${GREEN}[√] Swap 设置/扩容成功并已持久化到 /etc/fstab！当前状态：${RESET}"
        free -h
      else
        echo -e "${YELLOW}[!] Swap 已在当前运行中成功激活，但 /etc/fstab 未能写入持久化配置。原备份保留在: $swap_bak${RESET}"
        free -h
      fi
      ui_pause "按 Enter 继续..."
      ;;
    2)
      echo -e "\n${CYAN}>>> 调整 vm.swappiness (虚拟内存积极度)${RESET}"
      
      # 智能检测与推荐逻辑
      local current_swap=$(cat /proc/sys/vm/swappiness 2>/dev/null)
      local mem_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
      local mem_gb=$(awk "BEGIN {printf \"%.1f\", $mem_kb/1024/1024}")
      
      local rec_val=60
      local rec_msg="内存充裕 (>8GB)，保持默认平衡策略 (60)"
      
      if [ "$mem_kb" -lt 2097152 ]; then # < 2GB
          rec_val=1
          rec_msg="小内存 (<2GB)，建议设为 1 以最大程度避免 IO 卡顿"
      elif [ "$mem_kb" -lt 8388608 ]; then # < 8GB
          rec_val=10
          rec_msg="中等内存 (<8GB)，建议设为 10 以优先使用物理内存"
      fi

      echo -e "当前系统内存: ${YELLOW}${mem_gb} GB${RESET}"
      echo -e "当前 Swappiness: ${GREEN}$current_swap${RESET}"
      echo -e "推荐设置值: ${GREEN}$rec_val${RESET} (${GRAY}$rec_msg${RESET})"
      
      echo -e "${GRAY}--------------------------------------------------${RESET}"
      echo -e "输入 q 返回，直接回车使用推荐值 ($rec_val)"
      ui_read_field new_swap_val "请输入新值 (0-100，回车使用推荐值)" ui_swappiness "$rec_val" || continue
      
      if [[ "$new_swap_val" =~ ^[qQ]$ ]]; then continue; fi
      if [ -z "$new_swap_val" ]; then new_swap_val=$rec_val; fi

      if [[ ! "$new_swap_val" =~ ^[0-9]+$ ]] || [ "$new_swap_val" -gt 100 ]; then
         echo -e "${RED}[!] 输入无效${RESET}"
      else
         # [修复] 检查 sysctl 写入是否成功 (容器环境可能只读)
         if ! sysctl -w vm.swappiness="$new_swap_val" >/dev/null 2>&1; then
           echo -e "${RED}[!] 无法修改 swappiness${RESET}"
           if is_container_env; then
             echo -e "${YELLOW}原因: 容器环境可能限制了内核参数修改${RESET}"
           else
             echo -e "${YELLOW}请检查是否有足够权限${RESET}"
           fi
         else
           # 永久生效 (独立 drop-in 配置文件)
           mkdir -p /etc/sysctl.d
           local swap_conf="/etc/sysctl.d/99-aurora-swappiness.conf"
           echo "vm.swappiness = $new_swap_val" > "$swap_conf"
           sysctl -p "$swap_conf" >/dev/null 2>&1
           for old_f in /etc/sysctl.conf /etc/sysctl.d/sysctl.conf; do
             [ -f "$old_f" ] && sed -i '/^[[:space:]]*vm\.swappiness/d' "$old_f"
           done
           echo -e "${GREEN}[√] Swappiness 已写入 $swap_conf 并生效: $new_swap_val${RESET}"
         fi
      fi
      ui_pause "按 Enter 继续..."
      ;;
    3)
      echo -e "\n${CYAN}>>> 正在安全卸载与删除 Swap...${RESET}"
      if [ -f /swapfile ] || swapon --show 2>/dev/null | grep -q "/swapfile"; then
        echo -e "${CYAN}正在执行 swapoff /swapfile (数据将释放回物理内存)...${RESET}"
        if ! swapoff /swapfile 2>/dev/null; then
          echo -e "${RED}[×] 卸载失败！当前物理内存可能不足以回收全部 Swap 数据。${RESET}"
          echo -e "${YELLOW}为防止系统因内存耗尽而瞬间崩溃，原 Swap 文件与 fstab 配置已完整保留。${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
        if ! sed -i -E '\#^[[:space:]]*/swapfile([[:space:]]|$)#d' /etc/fstab; then
          echo -e "${RED}[×] 清理 /etc/fstab 中的 Swap 配置失败，已中止物理文件删除${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
        rm -f /swapfile
        rm -f /etc/sysctl.d/99-aurora-swappiness.conf
        echo -e "${GREEN}[√] Swap 已安全停用并彻底清理 /swapfile${RESET}"
      else
        echo -e "${YELLOW}未检测到活动的 /swapfile，无需清理。${RESET}"
      fi
      free -h
      ui_pause "按 Enter 继续..."
      ;;
    0)
      SKIP_PAUSE=true
      return 0
      ;;
    *)
      echo -e "${RED}无效选项${RESET}"
      sleep 1
      ;;
    esac
  done
}

# ---------- 修改 SSH 端口 (增强版: 增加恢复默认提示) ----------
change_ssh_port() {
  echo -e "${CYAN}>>> 正在修改 SSH 端口...${RESET}"

  local CURRENT_PORT=$(grep -E "^Port" /etc/ssh/sshd_config | awk '{print $2}' | head -n 1)
  if [[ -z "$CURRENT_PORT" ]]; then CURRENT_PORT="22"; fi
  echo -e "${CYAN}当前SSH端口: ${YELLOW}$CURRENT_PORT${RESET}"

  while true; do
    while true; do
      # --- [增强] 提示文本优化 ---
      echo -ne "${CYAN}请输入新的SSH端口号 (输入 22 恢复默认, 0 退出): ${RESET}"
      ui_read_field NEW_PORT "新 SSH 端口" ui_any_value "" true || { NEW_PORT=0; }

      if [ "$NEW_PORT" == "0" ]; then
        SKIP_PAUSE=true
        return 0
      fi

      if [[ ! $NEW_PORT =~ ^[0-9]{1,5}$ ]] || ((10#$NEW_PORT < 1 || 10#$NEW_PORT > 65535)); then
        echo -e "${RED}错误：端口号必须是数字${RESET}"
        continue
      fi

      NEW_PORT=$((10#$NEW_PORT))
      if [[ $NEW_PORT -eq $CURRENT_PORT ]]; then
        echo -e "${YELLOW}[!] 新端口与当前端口相同，无需修改${RESET}"
        return 1
      fi

      if ss -tuln | grep -q ":${NEW_PORT} "; then
        echo -e "${YELLOW}[!] 警告: 端口 $NEW_PORT 已被其他服务使用${RESET}"
        ui_confirm FORCE_CONTINUE "是否继续? [y/N，q 取消]: "
        if [[ ! $FORCE_CONTINUE =~ ^[Yy]$ ]]; then continue; fi
      fi
      break
    done

    # --- [增强] 确认逻辑区分 ---
    if [ "$NEW_PORT" == "22" ]; then
      echo -e "\n${CYAN}即将恢复默认 SSH 端口 (22)${RESET}"
    else
      echo -e "\n${CYAN}即将修改SSH端口: ${YELLOW}$CURRENT_PORT -> $NEW_PORT${RESET}"
    fi
    ui_confirm CONFIRM "确认修改? [y/N，q 取消]: "
    if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
      echo -e "${YELLOW}[!] 操作已取消${RESET}"
      SKIP_PAUSE=true
      return 0
    else
      break
    fi
  done

  local backup_file="/etc/ssh/sshd_config.backup.$(date +%Y%m%d%H%M%S)"
  cp /etc/ssh/sshd_config "$backup_file"
  echo -e "${GREEN}[√] 已备份SSH配置文件: $backup_file${RESET}"

  if grep -E "^#? *Port " /etc/ssh/sshd_config >/dev/null; then
    sed -i -E "s/^#? *Port [0-9]+/Port $NEW_PORT/" /etc/ssh/sshd_config
  else
    echo "Port $NEW_PORT" >>/etc/ssh/sshd_config
  fi
  echo -e "${GREEN}[√] SSH配置已更新${RESET}"

  # --- [1] SELinux 策略与上下文安全校验 (Enforcing 阻断保护) ---
  local selinux_mode=""
  if command -v getenforce >/dev/null 2>&1; then selinux_mode=$(getenforce 2>/dev/null); fi
  if [ "$selinux_mode" == "Enforcing" ]; then
    echo -e "${CYAN}检测到 SELinux 处于 Enforcing 强制模式，正在校验并更新端口上下文...${RESET}"
    if ! command -v semanage >/dev/null 2>&1; then
      echo -e "${YELLOW}缺少 semanage 工具，尝试自动安装...${RESET}"
      if [ "$OS_ID" == "centos" ] && [[ "$OS_VERSION_ID" =~ ^7 ]]; then
        yum install -y policycoreutils-python >/dev/null 2>&1
      else
        $PKG_INSTALL policycoreutils-python-utils >/dev/null 2>&1
      fi
    fi

    if command -v semanage >/dev/null 2>&1; then
      if semanage port -a -t ssh_port_t -p tcp "$NEW_PORT" 2>/dev/null || \
         semanage port -m -t ssh_port_t -p tcp "$NEW_PORT" 2>/dev/null; then
        echo -e "${GREEN}[√] SELinux 端口上下文配置成功${RESET}"
      else
        echo -e "${RED}[×] 错误: semanage 为端口 $NEW_PORT 分配 ssh_port_t 上下文失败！${RESET}"
        echo -e "${RED}为防止 SSH 无法 bind 导致失联，操作已紧急中止并还原配置。${RESET}"
        cp "$backup_file" /etc/ssh/sshd_config
        rm -f "$backup_file"
        return 1
      fi
    else
      echo -e "${RED}[×] 严重错误: 系统处于 SELinux Enforcing 模式，但无法安装 semanage 工具！${RESET}"
      echo -e "${RED}强行修改端口将导致 sshd 无法绑定端口并断开连接。操作已中止。${RESET}"
      cp "$backup_file" /etc/ssh/sshd_config
      rm -f "$backup_file"
      return 1
    fi
  fi

  # --- [2] 防火墙放行与规则持久化 (精确状态与真实 Zone 匹配) ---
  local firewall_configured=false
  # 1. UFW 严格匹配 Status: active (杜绝 Status: inactive 误判)
  if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qE "^Status:[[:space:]]*active"; then
    echo -e "${CYAN}检测到 UFW 防火墙处于活动状态，正在放行端口...${RESET}"
    if ufw allow "$NEW_PORT/tcp" >/dev/null 2>&1; then
      echo -e "${GREEN}[√] 已成功添加 UFW 规则: allow $NEW_PORT/tcp${RESET}"
      firewall_configured=true
    fi
  fi

  # 2. Firewalld (多活动 Zone 遍历与持久化)
  if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
    echo -e "${CYAN}检测到 Firewalld 处于活动状态，正在更新规则...${RESET}"
    local active_zones
    active_zones=$(firewall-cmd --get-active-zones 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+' | awk '{print $1}')
    [ -z "$active_zones" ] && active_zones=$(firewall-cmd --get-default-zone 2>/dev/null || echo "public")
    local fwd_all_ok=true newly_added_zones=""
    for z in $active_zones; do
      local had_port=false
      firewall-cmd --zone="$z" --query-port="$NEW_PORT/tcp" --permanent >/dev/null 2>&1 && had_port=true
      if firewall-cmd --zone="$z" --add-port="$NEW_PORT/tcp" --permanent >/dev/null 2>&1; then
        [ "$had_port" = false ] && newly_added_zones="$newly_added_zones $z"
      else
        fwd_all_ok=false
        echo -e "${YELLOW}[!] Firewalld 区域 [$z] 放行端口 $NEW_PORT 失败${RESET}"
      fi
    done
    if [ "$fwd_all_ok" = true ] && firewall-cmd --reload >/dev/null 2>&1; then
      echo -e "${GREEN}[√] 已成功向 Firewalld 所有活动区域 ($active_zones) 放行端口 $NEW_PORT 并完成重载${RESET}"
      firewall_configured=true
    else
      echo -e "${RED}[×] Firewalld 端口放行失败或部分活动区域未能放行！${RESET}"
      echo -e "${RED}[!] 为防止修改 SSH 端口后被防火墙阻断失联，正在回滚防火墙规则与 SSH 配置...${RESET}"
      for z in $newly_added_zones; do
        firewall-cmd --zone="$z" --remove-port="$NEW_PORT/tcp" --permanent >/dev/null 2>&1 || true
      done
      firewall-cmd --reload >/dev/null 2>&1 || true
      [ -f "$backup_file" ] && cp -f "$backup_file" /etc/ssh/sshd_config
      ui_pause "按 Enter 返回..."; return 1
    fi
  fi

  # 3. nftables (精确判断与持久化)
  if [ "$firewall_configured" = false ] && command -v nft >/dev/null 2>&1; then
    if nft list chains 2>/dev/null | grep -q "input"; then
      local nft_target_table="inet"
      nft list tables 2>/dev/null | grep -q "inet" || nft_target_table="ip"
      if nft insert rule $nft_target_table filter input tcp dport "$NEW_PORT" accept 2>/dev/null || \
         nft add rule $nft_target_table filter input tcp dport "$NEW_PORT" accept 2>/dev/null; then
        echo -e "${GREEN}[√] 已添加 nftables 放行规则 (优先放行)${RESET}"
        echo -e "${YELLOW}[!] SSH 放行仅写入运行规则，未覆盖管理员 nftables 配置；请确认现有加载机制${RESET}"
        firewall_configured=true
      fi
    fi
  fi

  # 4. iptables (前置插入与持久化)
  if [ "$firewall_configured" = false ] && command -v iptables >/dev/null 2>&1; then
    if iptables -S INPUT 2>/dev/null | grep -qE "DROP|REJECT"; then
      if iptables -I INPUT 1 -p tcp --dport "$NEW_PORT" -j ACCEPT 2>/dev/null; then
        echo -e "${GREEN}[√] 已在 iptables INPUT 链首插入放行规则${RESET}"
        netfilter-persistent save >/dev/null 2>&1 || iptables-save > /etc/sysconfig/iptables 2>/dev/null || true
        firewall_configured=true
      fi
    fi
  fi

  if [ "$firewall_configured" = false ]; then
    echo -e "${YELLOW}======================================================${RESET}"
    echo -e "${YELLOW}[!] 注意：未检测到生效中的本地拦截防火墙，或端口已默认畅通${RESET}"
    echo -e "${YELLOW}若服务商提供外部安全组 (如阿里云/腾讯云)，请务必放行端口 ${NEW_PORT}${RESET}"
    echo -e "${YELLOW}======================================================${RESET}"
    read -p "我已知晓外部防火墙要求 [按回车继续]" _tmp_f
  fi

  echo -e "\n${CYAN}是否立即重启SSH服务以使更改生效？${RESET}"
  ui_confirm RESTART_SSH "立即重启 SSH? [Y/n，q 取消]: " y

  if [[ $RESTART_SSH =~ ^[Nn]$ ]]; then
    echo -e "${YELLOW}[!] 请稍后手动执行重启服务以应用端口修改${RESET}"
    rm -f "$backup_file"
    return 0
  else
    echo -e "${CYAN}正在重启 SSH 服务并验证实际端口监听...${RESET}"
    if sshd -t 2>/dev/null; then
      # --- [3] Socket 激活模式精确判断与备份 ---
      local is_socket_active=false
      local socket_override_file="/etc/systemd/system/ssh.socket.d/listen.conf"
      local socket_backup_file="/tmp/listen.conf.backup.$$"
      
      if systemctl is-active --quiet ssh.socket 2>/dev/null; then
        is_socket_active=true
        echo -e "${CYAN}检测到 SSH 当前由 systemd socket (ssh.socket) 接管，正在配置 override...${RESET}"
        if [ -f "$socket_override_file" ]; then
          cp "$socket_override_file" "$socket_backup_file"
        fi
        mkdir -p /etc/systemd/system/ssh.socket.d
        cat >"$socket_override_file" <<EOF
[Socket]
ListenStream=
ListenStream=$NEW_PORT
EOF
        systemctl daemon-reload
        systemctl restart ssh.socket 2>/dev/null
      fi

      # 重启 SSH 主服务
      systemctl restart $SVC_SSH 2>/dev/null || service $SVC_SSH restart 2>/dev/null
      sleep 2

      # --- [4] 基于 ss 真实监听的严格判定与事务回滚 ---
      local listen_verified=false
      if ss -tlnp 2>/dev/null | grep -E "(:${NEW_PORT}[[:space:]]|,${NEW_PORT}[[:space:]])" | grep -q '"sshd"'; then
        listen_verified=true
      elif systemctl is-active --quiet ssh.socket 2>/dev/null &&
           systemctl show ssh.socket -p Listen --value 2>/dev/null | grep -qE ":${NEW_PORT}[[:space:]]+\(Stream\)" &&
           ss -tlnp 2>/dev/null | grep -E ":${NEW_PORT}[[:space:]]" | grep -q '"systemd"'; then
        listen_verified=true
      fi

      if [ "$listen_verified" = true ]; then
        echo -e "${GREEN}[√] 端口验证通过！新端口 $NEW_PORT 已正常处于 LISTEN 监听状态${RESET}"
        echo -e "\n${BOLD}${GREEN}SSH 端口已成功切换为 $NEW_PORT${RESET}"

        # 联动更新 Fail2Ban (若已安装)
        local f2b_sshd_conf="/etc/fail2ban/jail.d/00-aurora-sshd.local"
        if [ -f "$f2b_sshd_conf" ] || [ -f /etc/fail2ban/jail.local ]; then
          echo -e "${CYAN}检测到 Fail2Ban，正在同步更新其 SSH 监听端口为 $NEW_PORT...${RESET}"
          local f2b_target="$f2b_sshd_conf"
          [ ! -f "$f2b_target" ] && f2b_target="/etc/fail2ban/jail.local"
          cp -p "$f2b_target" "${f2b_target}.bak.$$" || return 1
          awk -v port="$NEW_PORT" '
            /^[[:space:]]*\[/ { ssh=($0 ~ /^[[:space:]]*\[sshd\][[:space:]]*$/); print; if(ssh) print "port = " port; next }
            ssh && /^[[:space:]]*port[[:space:]]*=/ {next}
            {print}
          ' "$f2b_target" > "${f2b_target}.tmp.$$" &&
            mv -f "${f2b_target}.tmp.$$" "$f2b_target" || return 1
          if systemctl is-active --quiet fail2ban 2>/dev/null; then
            if fail2ban-client -t && systemctl restart fail2ban && fail2ban-client status sshd >/dev/null; then
              echo -e "${GREEN}[√] Fail2Ban SSH jail 已更新并通过运行验证${RESET}"
            else
              cp -p "${f2b_target}.bak.$$" "$f2b_target"
              systemctl restart fail2ban 2>/dev/null
              echo -e "${RED}[×] Fail2Ban 更新失败，已恢复配置；请检查 SSH 防护${RESET}"
            fi
          fi
        fi

        echo -e "${YELLOW}请切勿关闭当前终端窗口！请新开一个终端窗口测试: ssh -p $NEW_PORT root@<服务器IP>${RESET}"
      else
        echo -e "${RED}[!] 严重错误：SSH 服务未能成功监听新端口 $NEW_PORT ！${RESET}"
        echo -e "${YELLOW}正在执行事务性安全回滚，恢复修改前的系统状态...${RESET}"
        cp "$backup_file" /etc/ssh/sshd_config
        rm -f "$backup_file"
        if [ "$is_socket_active" = true ]; then
          if [ -f "$socket_backup_file" ]; then
            mv -f "$socket_backup_file" "$socket_override_file"
          else
            rm -f "$socket_override_file"
          fi
          systemctl daemon-reload 2>/dev/null
          systemctl restart ssh.socket 2>/dev/null
        fi
        systemctl restart $SVC_SSH 2>/dev/null || service $SVC_SSH restart 2>/dev/null
        sleep 1
        if ss -tln 2>/dev/null | grep -qE "(:${CURRENT_PORT}[[:space:]]|,${CURRENT_PORT}[[:space:]])"; then
          echo -e "${GREEN}[√] 已安全恢复至原端口 $CURRENT_PORT 且原端口运行正常。${RESET}"
        else
          echo -e "${RED}[!] 警告：原端口恢复可能需要排查，请保持当前会话检查 sshd 状态。${RESET}"
        fi
        return 1
      fi
    else
      echo -e "${RED}[!] 错误：sshd -t 语法检查未通过，正在自动还原配置...${RESET}"
      cp "$backup_file" /etc/ssh/sshd_config
      rm -f "$backup_file"
      return 1
    fi
  fi
}

modify_dns() {
  echo -e "${CYAN}>>> 修改系统DNS地址...${RESET}"

  # 检查权限
  if [ $EUID -ne 0 ]; then
    echo -e "${RED}错误: 此功能需要root权限执行${RESET}"
    return 1
  fi

  # 常用DNS服务器列表 (保持原样)
  common_dns=(
    # IPv4
    "8.8.8.8|Google Public DNS (IPv4)"
    "8.8.4.4|Google Public DNS 备用 (IPv4)"
    "1.1.1.1|Cloudflare DNS (IPv4)"
    "1.0.0.1|Cloudflare DNS 备用 (IPv4)"
    "208.67.222.222|OpenDNS (IPv4)"
    "208.67.220.220|OpenDNS 备用 (IPv4)"
    "9.9.9.9|Quad9 DNS (IPv4)"
    "149.112.112.112|Quad9 DNS 备用 (IPv4)"
    "94.140.14.14|AdGuard DNS (IPv4)"
    "94.140.15.15|AdGuard DNS 备用 (IPv4)"
    "223.5.5.5|阿里 AliDNS (IPv4)"
    "223.6.6.6|阿里 AliDNS 备用 (IPv4)"
    "119.29.29.29|腾讯 DNSPod (IPv4)"
    "180.76.76.76|百度 BaiduDNS (IPv4)"
    # IPv6
    "2001:4860:4860::8888|Google Public DNS (IPv6)"
    "2001:4860:4860::8844|Google Public DNS 备用 (IPv6)"
    "2606:4700:4700::1111|Cloudflare DNS (IPv6)"
    "2606:4700:4700::1001|Cloudflare DNS 备用 (IPv6)"
    "2620:119:35::35|OpenDNS (IPv6)"
    "2620:119:53::53|OpenDNS 备用 (IPv6)"
    "2620:fe::fe|Quad9 DNS (IPv6)"
    "2a10:50c0::ad1:ff|AdGuard DNS (IPv6)"
    "2400:3200::1|阿里 AliDNS (IPv6)"
    "2400:da00::6666|百度 BaiduDNS (IPv6)"
  )

  # 全局变量，用于接收子函数返回的 IP 列表
  SELECTED_IPS=()

  # 显示当前DNS配置
  echo -e "${YELLOW}当前DNS配置:${RESET}"
  if [ -f /etc/resolv.conf ]; then
    grep -E '^nameserver' /etc/resolv.conf | while read line; do
      echo -e "  ${GREEN}✓${RESET} $line"
    done
  fi

  # 使用循环包裹菜单
  while true; do
    # 每次循环清空选择
    SELECTED_IPS=()

    echo -e "\n${CYAN}请选择操作方式:${RESET}"
    echo -e "  ${GREEN}1${RESET}) 自动测试并手动选择 (支持多选，含IPv6)"
    echo -e "  ${GREEN}2${RESET}) 手动输入DNS地址 (支持连续输入，含IPv6)"
    echo -e "  ${GREEN}3${RESET}) 从常用DNS列表选择 (支持多选，含IPv6)"
    # --- 新增选项 ---
    echo -e "  ${RED}4${RESET}) 恢复 DHCP/系统默认 (清除静态配置)"
    echo -e "  ${YELLOW}0.${RESET} 取消操作/返回"

    ui_menu_read choice "请输入选择 [0-4]" "[0-4]" || return 0

    case $choice in
    1)
      auto_test_dns
      ;;
    2)
      manual_input_dns
      ;;
    3)
      select_from_list
      ;;
    4)
      # --- 恢复默认逻辑 ---
      echo -e "${CYAN}>>> 正在恢复系统默认 DNS...${RESET}"
      local default_dns_owner=""
      if systemctl is-active --quiet systemd-resolved 2>/dev/null; then default_dns_owner=resolved
      elif systemctl is-active --quiet NetworkManager 2>/dev/null && command -v nmcli >/dev/null 2>&1; then default_dns_owner=nm
      elif command -v resolvconf >/dev/null 2>&1; then default_dns_owner=resolvconf
      else
        echo -e "${YELLOW}[!] 没有活动的自动 DNS 管理器 (常见于静态 PVE)，保留现有配置；请通过手动 DNS 菜单设置${RESET}"
        return 1
      fi

      # 1. 解锁文件并备份现有配置
      chattr -i /etc/resolv.conf 2>/dev/null
      local cur_resolv_bak="/tmp/resolv.conf.restore_bak.$$.$(date +%s)"
      local default_resolv_target=""
      if [ -L /etc/resolv.conf ] && [ -e /etc/resolv.conf ]; then
        default_resolv_target=$(readlink -f /etc/resolv.conf) || return 1
        cp -p "$default_resolv_target" "${cur_resolv_bak}.target" || return 1
      fi
      if [ -e /etc/resolv.conf ] || [ -L /etc/resolv.conf ]; then
        cp -P /etc/resolv.conf "$cur_resolv_bak" || return 1
      fi

      local default_nm_backup="${cur_resolv_bak}.nm" default_nm_uuid default_nm_dev
      : > "$default_nm_backup" || return 1
      if systemctl is-active --quiet NetworkManager 2>/dev/null && command -v nmcli >/dev/null 2>&1; then
        local default_nm_list default_prop default_row
        default_nm_list=$(nmcli -g UUID connection show --active) || return 1
        for default_nm_uuid in $default_nm_list; do
          default_row="$default_nm_uuid"
          for default_prop in ipv4.dns ipv6.dns ipv4.ignore-auto-dns ipv6.ignore-auto-dns; do
            default_row="${default_row}|$(nmcli -g "$default_prop" connection show "$default_nm_uuid")" || return 1
          done
          printf '%s\n' "$default_row" >> "$default_nm_backup" || return 1
        done
      fi
      restore_default_nm_dns() {
        local uuid d4 d6 a4 a6 dev
        while IFS='|' read -r uuid d4 d6 a4 a6; do
          [ -n "$uuid" ] || continue
          nmcli connection modify "$uuid" ipv4.dns "$d4" ipv6.dns "$d6" ipv4.ignore-auto-dns "$a4" ipv6.ignore-auto-dns "$a6" || return 1
          dev=$(nmcli -g GENERAL.DEVICES connection show "$uuid") || return 1
          [ -z "$dev" ] || nmcli device reapply "$dev" || return 1
        done < "$default_nm_backup"
      }
      while IFS='|' read -r default_nm_uuid _; do
        [ -n "$default_nm_uuid" ] || continue
        if ! nmcli connection modify "$default_nm_uuid" ipv4.dns "" ipv6.dns "" ipv4.ignore-auto-dns no ipv6.ignore-auto-dns no; then
          restore_default_nm_dns
          return 1
        fi
        default_nm_dev=$(nmcli -g GENERAL.DEVICES connection show "$default_nm_uuid")
        if [ -n "$default_nm_dev" ] && ! nmcli device reapply "$default_nm_dev"; then
          restore_default_nm_dns
          return 1
        fi
      done < "$default_nm_backup"
      # 只清理脚本的 drop-in，保留管理员 resolved.conf。
      local default_dropin_bak="${cur_resolv_bak}.dropin"
      if [ -f /etc/systemd/resolved.conf.d/99-aurora-dns.conf ]; then
        cp -p /etc/systemd/resolved.conf.d/99-aurora-dns.conf "$default_dropin_bak" || { restore_default_nm_dns; return 1; }
      fi
      rm -f /etc/systemd/resolved.conf.d/99-aurora-dns.conf || { restore_default_nm_dns; return 1; }

      # 3. 恢复系统默认行为
      local default_dns_apply_ok=true
      if [ "$default_dns_owner" = resolved ]; then
        rm -f /etc/resolv.conf
        ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
        systemctl restart systemd-resolved 2>/dev/null || default_dns_apply_ok=false
      else
        # 仅重新生成 DNS，不重启整个网络。
        if [ "$default_dns_owner" = nm ]; then
          nmcli general reload dns-rc || default_dns_apply_ok=false
        else
          resolvconf -u || default_dns_apply_ok=false
        fi
      fi

      # 4. [加固] 验证重建：静态网络 (如 PVE) 无 DHCP，删除后不会自动生成 resolv.conf，必须检验重建
      sleep 1
      local restore_ok=false
      if [ "$default_dns_apply_ok" = false ]; then
        restore_default_nm_dns
        [ ! -f "$default_dropin_bak" ] || cp -p "$default_dropin_bak" /etc/systemd/resolved.conf.d/99-aurora-dns.conf
        if [ -e "$cur_resolv_bak" ] || [ -L "$cur_resolv_bak" ]; then
          rm -f /etc/resolv.conf
          cp -P "$cur_resolv_bak" /etc/resolv.conf
        fi
        [ -z "$default_resolv_target" ] || cp -p "${cur_resolv_bak}.target" "$default_resolv_target"
        systemctl is-active --quiet systemd-resolved 2>/dev/null && systemctl restart systemd-resolved 2>/dev/null
        echo -e "${RED}[×] 默认 DNS 应用失败，已尝试还原，备份保留: $cur_resolv_bak${RESET}"
        return 1
      fi
      if [ -s /etc/resolv.conf ] && grep -qE "^[[:space:]]*nameserver" /etc/resolv.conf 2>/dev/null; then
        restore_ok=true
      fi

      if [ "$restore_ok" = false ] || ! verify_dns_config; then
        restore_default_nm_dns
        [ ! -f "$default_dropin_bak" ] || cp -p "$default_dropin_bak" /etc/systemd/resolved.conf.d/99-aurora-dns.conf
        if [ -e "$cur_resolv_bak" ] || [ -L "$cur_resolv_bak" ]; then
          rm -f /etc/resolv.conf
          cp -P "$cur_resolv_bak" /etc/resolv.conf
        fi
        [ -z "$default_resolv_target" ] || cp -p "${cur_resolv_bak}.target" "$default_resolv_target"
        systemctl is-active --quiet systemd-resolved 2>/dev/null && systemctl restart systemd-resolved 2>/dev/null
        echo -e "${RED}[×] 默认 DNS 未重建或解析验证失败，已尝试回滚；备份保留: $cur_resolv_bak${RESET}"
        return 1
      fi

      echo -e "${GREEN}[√] 已完成 DNS 恢复与连通性验证${RESET}"
      echo -e "${YELLOW}当前配置状态:${RESET}"
      ls -l /etc/resolv.conf 2>/dev/null
      grep -E '^[[:space:]]*nameserver' /etc/resolv.conf 2>/dev/null || true

      return 0
      ;;
    0)
      echo -e "${YELLOW}已取消DNS修改操作${RESET}"
      SKIP_PAUSE=true
      return 0
      ;;
    *)
      echo -e "${RED}无效选择，请重新输入${RESET}"
      continue
      ;;
    esac

    # 检查是否有选中的 IP (仅针对 1-3 选项)
    if [ ${#SELECTED_IPS[@]} -eq 0 ]; then
      echo -e "${YELLOW}未选择任何 DNS，返回上一级菜单...${RESET}"
      continue # 继续循环
    fi

    # 如果选择了IP，则跳出循环，继续执行应用逻辑
    break
  done

  # === 下方为应用新DNS的逻辑 (仅针对 1-3 选项) ===

  # 数组去重
  local unique_ips=""
  for ip in "${SELECTED_IPS[@]}"; do
    if [[ ! " $unique_ips " =~ " $ip " ]]; then
      unique_ips="$unique_ips $ip"
    fi
  done
  SELECTED_IPS=($unique_ips)

  echo -e "\n${CYAN}准备应用新的 DNS 配置: ${SELECTED_IPS[*]}${RESET}"

  # --- 1. 备份配置 (完整备份 resolv.conf、resolved.conf 以及已有 99-aurora-dns.conf 片段) ---
  local backup_file="/etc/resolv.conf.backup.$(date +%Y%m%d_%H%M%S)"
  local backup_systemd=""
  local backup_aurora_dropin=""
  local resolv_target="" backup_target=""
  local nm_uuids=() nm_dns4=() nm_dns6=() nm_auto4=() nm_auto6=()
  local uuid
  if [ -L /etc/resolv.conf ] && [ -e /etc/resolv.conf ]; then
    resolv_target=$(readlink -f /etc/resolv.conf) || return 1
    backup_target="${backup_file}.target"
    cp -p "$resolv_target" "$backup_target" || return 1
  fi
  if systemctl is-active --quiet NetworkManager 2>/dev/null && command -v nmcli >/dev/null 2>&1; then
    local active_uuid_list
    active_uuid_list=$(nmcli -g UUID connection show --active) || return 1
    for uuid in $active_uuid_list; do
      nm_uuids+=("$uuid")
      nm_dns4+=("$(nmcli -g ipv4.dns connection show "$uuid")")
      nm_dns6+=("$(nmcli -g ipv6.dns connection show "$uuid")")
      nm_auto4+=("$(nmcli -g ipv4.ignore-auto-dns connection show "$uuid")")
      nm_auto6+=("$(nmcli -g ipv6.ignore-auto-dns connection show "$uuid")")
      if [ -z "${nm_auto4[-1]}" ] || [ -z "${nm_auto6[-1]}" ]; then
        echo -e "${RED}[×] 无法备份连接 DNS 属性，未修改 DNS${RESET}"
        return 1
      fi
    done
  fi

  # 尝试备份 resolv.conf
  if cp -P /etc/resolv.conf "$backup_file" 2>/dev/null; then
    echo -e "${GREEN}[√] 已备份原配置到: $backup_file${RESET}"
  else
    if [ -e /etc/resolv.conf ] || [ -L /etc/resolv.conf ]; then
      echo -e "${RED}[×] 原 DNS 配置备份失败，停止修改${RESET}"
      return 1
    fi
    touch "$backup_file" || return 1
  fi

  # 如果存在 systemd-resolved，也备份它的主配置
  if [ -f /etc/systemd/resolved.conf ]; then
    backup_systemd="/etc/systemd/resolved.conf.backup.$(date +%Y%m%d_%H%M%S)"
    cp /etc/systemd/resolved.conf "$backup_systemd" || return 1
  fi

  # 备份此前已有的 99-aurora-dns.conf 片段 (二次修改失败时精准恢复而非盲目删除)
  if [ -f /etc/systemd/resolved.conf.d/99-aurora-dns.conf ]; then
    backup_aurora_dropin="/etc/systemd/resolved.conf.d/99-aurora-dns.conf.bak.$$.$(date +%s)"
    cp -P /etc/systemd/resolved.conf.d/99-aurora-dns.conf "$backup_aurora_dropin" || return 1
  fi

  # --- 2. 写入新配置 (先清理后应用) ---
  # --- 3. 验证与回滚 ---
  if write_dns_config "${SELECTED_IPS[@]}" && verify_dns_config; then
    echo -e "${GREEN}[√] DNS修改成功且验证通过${RESET}"
    echo -e "${YELLOW}新的DNS配置:${RESET}"
    grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | while read line; do
      echo -e "  ${GREEN}✓${RESET} $line"
    done

    # 验证成功，安全清理临时备份文件
    echo -e "${CYAN}>>> 正在清理备份文件...${RESET}"
    rm -f "$backup_file"
    [ -n "$backup_target" ] && rm -f "$backup_target"
    [ -n "$backup_systemd" ] && [ -f "$backup_systemd" ] && rm -f "$backup_systemd"
    [ -n "$backup_aurora_dropin" ] && [ -f "$backup_aurora_dropin" ] && rm -f "$backup_aurora_dropin"
    echo -e "${GREEN}[√] 备份清理完成${RESET}"

  else
    echo -e "${RED}[×] DNS配置验证失败，正在还原配置...${RESET}"

    # 还原 resolv.conf
    if [ -e "$backup_file" ] || [ -L "$backup_file" ]; then
      chattr -i /etc/resolv.conf 2>/dev/null
      rm -f /etc/resolv.conf
      cp -P "$backup_file" /etc/resolv.conf || echo -e "${RED}[×] resolv.conf 恢复失败，备份保留: $backup_file${RESET}"
      if [ -n "$backup_target" ]; then
        chattr -i "$resolv_target" 2>/dev/null || true
        cp -p "$backup_target" "$resolv_target" || echo -e "${RED}[×] DNS 链接目标恢复失败，备份保留: $backup_target${RESET}"
      fi
    fi

    # 还原或清理 99-aurora-dns.conf (恢复到修改前状态)
    if [ -n "$backup_aurora_dropin" ] && [ -f "$backup_aurora_dropin" ]; then
      mv -f "$backup_aurora_dropin" /etc/systemd/resolved.conf.d/99-aurora-dns.conf
      echo -e "${YELLOW}[!] 已恢复修改前的 99-aurora-dns.conf 配置片段${RESET}"
    else
      rm -f /etc/systemd/resolved.conf.d/99-aurora-dns.conf 2>/dev/null
    fi

    if [ -n "$backup_systemd" ] && [ -f "$backup_systemd" ]; then
      cp "$backup_systemd" /etc/systemd/resolved.conf
      echo -e "${YELLOW}[!] 已还原 /etc/systemd/resolved.conf${RESET}"
    fi
    systemctl is-active --quiet systemd-resolved 2>/dev/null && systemctl restart systemd-resolved 2>/dev/null
    local nm_index nm_dev
    for nm_index in "${!nm_uuids[@]}"; do
      uuid="${nm_uuids[$nm_index]}"
      if ! nmcli connection modify "$uuid" ipv4.dns "${nm_dns4[$nm_index]}" ipv6.dns "${nm_dns6[$nm_index]}" \
          ipv4.ignore-auto-dns "${nm_auto4[$nm_index]}" ipv6.ignore-auto-dns "${nm_auto6[$nm_index]}"; then
        echo -e "${RED}[×] NetworkManager DNS 回滚失败: $uuid${RESET}"
      fi
      nm_dev=$(nmcli -g GENERAL.DEVICES connection show "$uuid" 2>/dev/null)
      [ -n "$nm_dev" ] && nmcli device reapply "$nm_dev" 2>/dev/null
    done

    return 1
  fi
}

auto_test_dns() {
  echo -e "${CYAN}>>> 正在测试常用DNS速度 (IPv4/IPv6 并行测试)...${RESET}"

  # 测试的DNS服务器 (混合v4和v6)
  local test_dns=(
    "8.8.8.8|Google IPv4"
    "1.1.1.1|Cloudflare IPv4"
    "208.67.222.222|OpenDNS IPv4"
    "9.9.9.9|Quad9 IPv4"
    "223.5.5.5|AliDNS IPv4"
    "119.29.29.29|DNSPod IPv4"
    "180.76.76.76|Baidu IPv4"
    "2001:4860:4860::8888|Google IPv6"
    "2606:4700:4700::1111|Cloudflare IPv6"
    "2400:3200::1|AliDNS IPv6"
    "2400:da00::6666|Baidu IPv6"
  )

  # 临时目录存放结果
  local dns_tmp_dir="${TEMP_DIR}/dns_test"
  mkdir -p "$dns_tmp_dir"
  rm -f "$dns_tmp_dir"/*

  # 并行测试 (真实 DNS 53 服务响应测试 + ICMP Ping 辅助回退)
  local pids=()
  for i in "${!test_dns[@]}"; do
    (
      local info="${test_dns[$i]}"
      IFS='|' read -r dns_ip dns_name <<<"$info"
      
      local lat="9999"

      # 1. 优先使用 dig 发送实际 DNS 解析请求 (测试 UDP 53 服务本身)
      if command -v dig >/dev/null 2>&1; then
        local dig_out
        dig_out=$(dig +time=2 +tries=1 "@$dns_ip" www.qq.com A 2>/dev/null)
        if echo "$dig_out" | grep -q "status: NOERROR"; then
          lat=$(echo "$dig_out" | grep -oP 'Query time:\s*\K[0-9]+' | head -1)
          : ${lat:=9999}
        fi
      fi

      # 2. 若 dig 不可用或超时，尝试使用 nslookup 测试服务有效性
      if [ "$lat" == "9999" ] && command -v nslookup >/dev/null 2>&1; then
        local t_start t_end
        t_start=$(date +%s%3N 2>/dev/null || echo "")
        if nslookup -timeout=2 www.qq.com "$dns_ip" >/dev/null 2>&1; then
          if [ -n "$t_start" ]; then
            t_end=$(date +%s%3N 2>/dev/null || echo "")
            if [ -n "$t_end" ] && [ "$t_end" -ge "$t_start" ]; then
              lat=$((t_end - t_start))
            else
              lat="50"
            fi
          else
            lat="50"
          fi
        fi
      fi

      # ICMP 响应不能证明 DNS 服务可用，查询失败保留不可用状态。
      
      echo "$lat" > "${dns_tmp_dir}/${i}.res"
    ) &
    pids+=($!)
  done

  # 显示进度条动画 (简单版)
  local spin='-\|/'
  local active=1
  while [ $active -gt 0 ]; do
    active=0
    for pid in "${pids[@]}"; do
      if kill -0 "$pid" 2>/dev/null; then
        ((active++))
      fi
    done
    if [ $active -gt 0 ]; then
       # 打印旋转动画
       local i=$(( (i+1) %4 ))
       printf "\r${CYAN}>>> 正在测试中... [%c] ${RESET}" "${spin:$i:1}"
       sleep 0.1
    fi
  done
  printf "\r${CYAN}>>> 测试完成！                   ${RESET}\n"

  # 收集结果
  declare -a dns_results
  for i in "${!test_dns[@]}"; do
    local latency="9999"
    if [ -f "${dns_tmp_dir}/${i}.res" ]; then
      latency=$(cat "${dns_tmp_dir}/${i}.res")
    fi
    local info="${test_dns[$i]}"
    IFS='|' read -r dns_ip dns_name <<<"$info"
    # 存入格式： latency|ip|name
    dns_results[$i]="$latency|$dns_ip|$dns_name"
  done

  # 清理
  rm -rf "$dns_tmp_dir"

  # 分离 v4 / v6
  local v4_list=()
  local v6_list=()
  for res in "${dns_results[@]}"; do
    IFS='|' read -r lat ip nm <<<"$res"
    if [[ "$ip" == *":"* ]]; then
      v6_list+=("$res")
    else
      v4_list+=("$res")
    fi
  done

  # 排序
  local sorted_v4=()
  local sorted_v6=()
  if [ ${#v4_list[@]} -gt 0 ]; then
    IFS=$'\n' sorted_v4=($(printf "%s\n" "${v4_list[@]}" | sort -n -t'|' -k1))
    unset IFS
  fi
  if [ ${#v6_list[@]} -gt 0 ]; then
    IFS=$'\n' sorted_v6=($(printf "%s\n" "${v6_list[@]}" | sort -n -t'|' -k1))
    unset IFS
  fi

  local valid_options=()
  local display_index=1

  # Display IPv4
  echo -e "\n${CYAN}IPv4 DNS 延迟排名:${RESET}"
  local v4_count=0
  for item in "${sorted_v4[@]}"; do
    IFS='|' read -r latency ip name <<<"$item"
    if [ "$latency" != "9999" ] && [ -n "$latency" ]; then
      echo -e "  ${GREEN}${display_index}${RESET}. ${BOLD}$name${RESET} ($ip) - ${YELLOW}${latency}ms${RESET}"
      valid_options[$display_index]="$ip"
      display_index=$((display_index + 1))
      ((v4_count++))
    fi
  done
  [ $v4_count -eq 0 ] && echo -e "  ${GRAY}无可用 IPv4 结果${RESET}"

  # Display IPv6
  echo -e "\n${CYAN}IPv6 DNS 延迟排名:${RESET}"
  local v6_count=0
  for item in "${sorted_v6[@]}"; do
    IFS='|' read -r latency ip name <<<"$item"
    if [ "$latency" != "9999" ] && [ -n "$latency" ]; then
      echo -e "  ${GREEN}${display_index}${RESET}. ${BOLD}$name${RESET} ($ip) - ${YELLOW}${latency}ms${RESET}"
      valid_options[$display_index]="$ip"
      display_index=$((display_index + 1))
      ((v6_count++))
    fi
  done
  [ $v6_count -eq 0 ] && echo -e "  ${GRAY}无可用 IPv6 结果${RESET}"

  # Check validation
  if [ ${#valid_options[@]} -eq 0 ]; then
    echo -e "${RED}所有DNS测试均超时，请检查网络连接${RESET}"
    return 1
  fi

  echo -e "\n${YELLOW}提示：可以输入多个编号进行组合（例如：1 3）(输入 0 退出)${RESET}"
  while true; do
    ui_read_field user_choices "请输入要使用的DNS编号 (空格分隔)" ui_nonempty "" true || return 0
    local dns_choices_valid=true
    local dns_pending=()
    for choice in $user_choices; do
      if [[ "$choice" =~ ^[1-9][0-9]{0,2}$ ]] && [ -n "${valid_options[$choice]}" ]; then
        dns_pending+=("${valid_options[$choice]}")
      else
        dns_choices_valid=false
      fi
    done
    if [ "$dns_choices_valid" = true ]; then SELECTED_IPS=("${dns_pending[@]}"); break; fi
    echo -e "${YELLOW}[!] 编号无效，请按本次测速列表重新选择；q 取消。${RESET}"
  done
}

manual_input_dns() {
  echo -e "${CYAN}>>> 手动输入DNS地址${RESET}"
  echo -e "${YELLOW}提示：支持输入多个IP地址(IPv4/IPv6)，用空格分隔；q 取消 (兼容 0)${RESET}"

  ui_read_field input_dns "请输入DNS服务器地址 (空格分隔)" ui_ip_list "" true || return 0
  SELECTED_IPS=()
  for ip in $input_dns; do SELECTED_IPS+=("$ip"); done
}

select_from_list() {
  echo -e "${CYAN}>>> 从常用DNS列表选择${RESET}"

  echo -e "${YELLOW}常用DNS服务器列表:${RESET}"
  for i in "${!common_dns[@]}"; do
    IFS='|' read -r ip name <<<"${common_dns[$i]}"
    echo -e "  ${GREEN}$((i + 1))${RESET}) $name - ${YELLOW}$ip${RESET}"
  done

  echo -e "\n${YELLOW}提示：可以输入多个编号进行组合（例如：1 2）；q 取消 (兼容 0)${RESET}"
  while true; do
    ui_read_field user_choices "请选择DNS服务器编号 (空格分隔)" ui_nonempty "" true || return 0
    local dns_choices_valid=true
    local dns_pending=()
    for choice in $user_choices; do
      if [[ "$choice" =~ ^[1-9][0-9]{0,2}$ ]] && (( choice <= ${#common_dns[@]} )); then
        index=$((choice - 1))
        IFS='|' read -r selected_ip selected_name <<<"${common_dns[$index]}"
        dns_pending+=("$selected_ip")
      else
        dns_choices_valid=false
      fi
    done
    if [ "$dns_choices_valid" = true ]; then SELECTED_IPS=("${dns_pending[@]}"); break; fi
    echo -e "${YELLOW}[!] 编号无效，请重新选择；q 取消。${RESET}"
  done
}

# 辅助函数 (支持IPv4和IPv6)
# 严格 IPv4 格式与数值校验 (0-255 无前导零)
validate_ipv4() {
  local ip="$1"
  [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && return 1
  local IFS='.'
  local -a octets=($ip)
  [[ ${#octets[@]} -ne 4 ]] && return 1
  for o in "${octets[@]}"; do
    [[ ${#o} -gt 1 && ${o:0:1} == '0' ]] && return 1
    ((10#$o > 255)) && return 1
  done
  return 0
}

# 严格 IPv6 校验 (杜绝 : 或 :::，遵循 RFC 4291)
validate_ipv6() {
  local ip="$1"
  if [[ "$ip" == *.* ]]; then
    local tail="${ip##*:}"
    validate_ipv4 "$tail" || return 1
    ip="${ip%:*}:0:0"
  fi
  [[ -z "$ip" ]] && return 1
  [[ "$ip" == ":" || "$ip" =~ ::: ]] && return 1
  [[ "$ip" =~ ::.*:: ]] && return 1
  # 全写规范 8 段
  [[ "$ip" =~ ^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$ ]] && return 0
  # 包含 :: 的压缩写法
  if [[ "$ip" =~ ^(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?$ ]]; then
    [ "$ip" = "::" ] && return 0
    local groups="${ip//::/:}" group count=0
    local IFS=':'
    for group in $groups; do
      [ -n "$group" ] && ((count++))
    done
    [ "$count" -lt 8 ] && return 0
  fi
  return 1
}

# 综合 IP 校验
validate_ip() {
  validate_ipv4 "$1" || validate_ipv6 "$1"
}

# 连续子网掩码校验并转换为 CIDR 前缀 (支持 /0 到 /32，拒绝非连续掩码)
mask_to_cidr() {
  local mask="$1"
  validate_ipv4 "$mask" || return 1
  local IFS='.'
  local -a octets=($mask)
  local bin=""
  for o in "${octets[@]}"; do
    local b=""
    for ((i=7; i>=0; i--)); do
      b+=$(( (o >> i) & 1 ))
    done
    bin+="$b"
  done
  if [[ ! "$bin" =~ ^1*0*$ ]]; then
    return 1 # 非连续掩码
  fi
  local ones="${bin//0/}"
  echo "${#ones}"
  return 0
}

write_dns_config() {
  local dns_list=("$@")
  local dns4=() dns6=() ip
  for ip in "${dns_list[@]}"; do
    if validate_ipv4 "$ip"; then dns4+=("$ip")
    elif validate_ipv6 "$ip"; then dns6+=("$ip")
    else return 1
    fi
  done

  echo -e "${CYAN}正在配置系统 DNS...${RESET}"

  # 0. 悬空软链接检测与修复 (杜绝 [ -f ] 遗漏悬空链接 BUG)
  if [ -L /etc/resolv.conf ] && [ ! -e /etc/resolv.conf ]; then
    echo -e "${YELLOW}[!] 检测到 /etc/resolv.conf 为悬空软链接 (目标不存在)，正在清理并转为实体配置...${RESET}"
    rm -f /etc/resolv.conf
  fi

  # 1. 提取并保留原有 resolv.conf 中的 search / domain / options / sortlist 指令
  local preserved_options=""
  if [ -e /etc/resolv.conf ]; then
    preserved_options=$(grep -E '^[[:space:]]*(search|domain|options|sortlist)[[:space:]]' /etc/resolv.conf 2>/dev/null)
  fi

  # 2. systemd-resolved 激活时，优先更新 resolved 配置以保留符号链接与系统生命周期管理
  if systemctl is-active --quiet systemd-resolved 2>/dev/null; then
    echo -e "${CYAN}检测到 systemd-resolved 正在运行，通过全局配置与 drop-in 更新...${RESET}"
    mkdir -p /etc/systemd/resolved.conf.d || return 1
    local dns_string="${dns_list[*]}"
    cat >/etc/systemd/resolved.conf.d/99-aurora-dns.conf <<EOF || return 1
[Resolve]
DNS=$dns_string
FallbackDNS=8.8.8.8 1.1.1.1
EOF
    # drop-in 覆盖 DNS，无需改写管理员的 resolved.conf。
    systemctl restart systemd-resolved 2>/dev/null || return 1
    echo -e "${GREEN}[√] systemd-resolved 配置已生效${RESET}"
  fi

  # 3. 处理 NetworkManager (若处于活跃状态且接管网络)
  if systemctl is-active --quiet NetworkManager 2>/dev/null && command -v nmcli >/dev/null 2>&1; then
    echo -e "${CYAN}检测到 NetworkManager 正在运行，尝试同步活动连接 DNS...${RESET}"
    local active_conns
    active_conns=$(nmcli -g UUID connection show --active 2>/dev/null) || return 1
    for uuid in $active_conns; do
      [ -z "$uuid" ] && continue
      nmcli connection modify "$uuid" ipv4.dns "${dns4[*]}" ipv6.dns "${dns6[*]}" \
        ipv4.ignore-auto-dns yes ipv6.ignore-auto-dns yes || return 1
      local nm_dev
      nm_dev=$(nmcli -g GENERAL.DEVICES connection show "$uuid" 2>/dev/null) || return 1
      [ -z "$nm_dev" ] || nmcli device reapply "$nm_dev" || return 1
    done
  fi

  # 4. 写入 /etc/resolv.conf 或其真实指向文件 (安全保留 options/search 与符号链接)
  local target_file="/etc/resolv.conf"
  if [ -L /etc/resolv.conf ]; then
    local resolved_target
    resolved_target=$(readlink -f /etc/resolv.conf 2>/dev/null)
    # 如果指向 resolved stub，则在上面步骤中已通过 resolved 处理，保持软链接即可
    if [[ "$resolved_target" =~ (resolved|systemd) ]]; then
      systemctl is-active --quiet systemd-resolved 2>/dev/null || return 1
      echo -e "${CYAN}/etc/resolv.conf 指向 systemd-resolved 存根，保持其服务符号链接。${RESET}"
      target_file=""
    elif [ -n "$resolved_target" ] && [ -w "$resolved_target" ]; then
      target_file="$resolved_target"
      echo -e "${CYAN}/etc/resolv.conf 为服务符号链接 -> 目标指向: $target_file${RESET}"
    fi
  fi

  if [ -n "$target_file" ]; then
    chattr -i "$target_file" 2>/dev/null || true
    local tmp_resolv="${target_file}.tmp.$$"
    cat >"$tmp_resolv" <<EOF
# Generated by Aurora VPS Script
# Last update: $(date)
EOF
    for ip in "${dns_list[@]}"; do
      echo "nameserver $ip" >>"$tmp_resolv"
    done
    if [ -n "$preserved_options" ]; then
      echo "" >>"$tmp_resolv"
      echo "# Preserved domain/search/options configuration:" >>"$tmp_resolv"
      echo "$preserved_options" >>"$tmp_resolv"
    fi
    mv -f "$tmp_resolv" "$target_file" || return 1
    chmod 644 "$target_file" 2>/dev/null || true
    echo -e "${GREEN}[√] $target_file 已成功更新静态条目 (原 search/options 选项已完整保留)${RESET}"
  fi

  # 5. 询问锁定 (仅当非 systemd-resolved 软链接时建议锁定)
  if [ ! -L /etc/resolv.conf ]; then
    echo -e "${YELLOW}是否锁定 DNS 配置文件以防止系统重启或 DHCP 再次修改?${RESET}"
    ui_confirm lock_choice "锁定 /etc/resolv.conf? [y/N] (q 取消): "
    if [[ "$lock_choice" =~ ^[Yy]$ ]]; then
      if command -v chattr >/dev/null 2>&1; then
        chattr +i /etc/resolv.conf
        echo -e "${GREEN}[√] 文件已锁定 (+i)${RESET}"
      else
        echo -e "${RED}[!] 错误：未找到 chattr 命令，无法锁定${RESET}"
      fi
    fi
  fi
  return 0
}

verify_dns_config() {
  echo -e "\n${CYAN}>>> 验证DNS配置...${RESET}"

  if [ ! -f /etc/resolv.conf ]; then
    echo -e "${RED}错误: /etc/resolv.conf 文件不存在${RESET}"
    return 1
  fi

  local dns_servers=$(grep -E '^nameserver' /etc/resolv.conf | awk '{print $2}')
  if [ -z "$dns_servers" ]; then
    # 如果是 systemd-resolved，resolv.conf 可能是存根，需要检查 resolvectl
    if systemctl is-active systemd-resolved >/dev/null 2>&1; then
      echo -e "${GRAY}使用 systemd-resolved，尝试解析验证...${RESET}"
    else
      echo -e "${RED}错误: 未找到有效的DNS服务器配置${RESET}"
      return 1
    fi
  fi

  # 进行实际解析测试 (双栈容错: 支持国内与海外高可用域名解析)
  echo -ne "  正在测试网络域名解析 (baidu.com / qq.com / google.com) ... "
  local resolved=false
  for test_host in "baidu.com" "qq.com" "aliyun.com" "google.com" "cloudflare.com"; do
    # 优先使用 libc 级别的系统解析检测 (不依赖 ping/ICMP，真正验证 DNS 解析)
    if command -v getent >/dev/null 2>&1; then
      if getent ahosts "$test_host" >/dev/null 2>&1; then
        resolved=true
        break
      fi
    elif command -v nslookup >/dev/null 2>&1; then
      if nslookup -timeout=3 "$test_host" >/dev/null 2>&1; then
        resolved=true
        break
      fi
    elif command -v curl >/dev/null 2>&1; then
      if curl -s --connect-timeout 3 "http://$test_host" >/dev/null 2>&1; then
        resolved=true
        break
      fi
    elif command -v ping >/dev/null 2>&1; then
      if ping -c 1 -W 2 "$test_host" >/dev/null 2>&1; then
        resolved=true
        break
      fi
    fi
  done

  if [ "$resolved" = true ]; then
    echo -e "${GREEN}成功${RESET}"
    return 0
  else
    echo -e "${RED}失败${RESET}"
    return 1
  fi
}

manage_ipv6() {
  echo -e "${CYAN}>>> 管理 IPv6 配置...${RESET}"

  # [容器适配] 检测并警告容器环境限制
  local is_container=false
  local ipv6_sysctl_writable=true
  
  if is_container_env; then
    is_container=true
    echo -e "${YELLOW}[!] 检测到容器环境，IPv6 配置可能受限${RESET}"
  fi
  
  # 检测 sysctl 是否可写
  if ! is_sysctl_writable "net.ipv6.conf.all.disable_ipv6"; then
    ipv6_sysctl_writable=false
    echo -e "${YELLOW}[!] IPv6 sysctl 参数不可修改 (只读)${RESET}"
  fi

  # 检测当前内核 IPv6 状态
  local ipv6_disabled_status=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)
  local has_ipv6_addr=$(ip -6 addr show | grep -v "::1" | grep -q "inet6" && echo "yes" || echo "no")

  echo -ne "当前状态: "
  if [ "$ipv6_disabled_status" = "1" ]; then
    echo -e "${RED}已禁用${RESET}"
  else
    if [ "$has_ipv6_addr" = "yes" ]; then
      echo -e "${GREEN}已开启 (且检测到IPv6地址)${RESET}"
    else
      echo -e "${YELLOW}已开启 (但未检测到IPv6地址，可能网络不支持)${RESET}"
    fi
  fi

  echo -e "\n${CYAN}请选择操作:${RESET}"
  echo -e "  ${GREEN}1${RESET}) 开启 IPv6"
  echo -e "  ${GREEN}2${RESET}) 关闭 IPv6 (永久生效)"
  echo -e "  ${YELLOW}0.${RESET} 取消/返回"

  ui_menu_read ipv6_choice "请输入选项 [0-2]" "[0-2]" || return 0
  if [ "$ipv6_choice" == "2" ] && [ "$ipv6_sysctl_writable" = false ] && [ "$is_container" = true ]; then
    echo -e "${RED}[×] 当前容器环境无法修改 IPv6 sysctl，未改动配置${RESET}"
    return 1
  fi

  local disable_conf="/etc/sysctl.d/99-ipv6-disable.conf"
  local grub_file="/etc/default/grub"

  case $ipv6_choice in
  1)
    echo -e "${CYAN}>>> 正在开启 IPv6...${RESET}"

    local reboot_required=false

    # 1. 引导参数清理 (适配 PVE systemd-boot 与 GRUB)
    # (A) 处理 PVE systemd-boot
    if [ -f /etc/kernel/cmdline ]; then
      local cur_cmdline
      cur_cmdline=$(cat /etc/kernel/cmdline 2>/dev/null)
      if echo "$cur_cmdline" | grep -qw "ipv6.disable=1"; then
        echo -e "${CYAN}检测到 PVE systemd-boot，正在从 /etc/kernel/cmdline 移除 ipv6.disable=1...${RESET}"
        echo "$cur_cmdline" | sed 's/\bipv6\.disable=1\b//g' | sed 's/[[:space:]]\+/ /g' | sed 's/^ //;s/ $//' > /etc/kernel/cmdline
        if command -v proxmox-boot-tool >/dev/null 2>&1; then
          proxmox-boot-tool refresh >/dev/null 2>&1
        fi
        reboot_required=true
      fi
    fi

    # (B) 处理 GRUB
    if [ -f "$grub_file" ]; then
      if grep -qw "ipv6.disable=1" "$grub_file"; then
        echo -e "${CYAN}发现 GRUB 内核禁用参数，正在移除...${RESET}"
        sed -i 's/\bipv6\.disable=1\b//g' "$grub_file"
        if command -v update-grub >/dev/null 2>&1; then
          update-grub >/dev/null 2>&1
        elif command -v grub2-mkconfig >/dev/null 2>&1; then
          if [ -f /boot/grub2/grub.cfg ]; then
            grub2-mkconfig -o /boot/grub2/grub.cfg >/dev/null 2>&1
          elif [ -f /boot/grub/grub.cfg ]; then
            grub2-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1
          fi
        elif command -v grub-mkconfig >/dev/null 2>&1; then
          grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1
        fi
        echo -e "${GREEN}[√] GRUB 配置已更新${RESET}"
        reboot_required=true
      fi
    fi

    # 2. 删除专门的禁用配置文件
    if [ -f "$disable_conf" ]; then
      rm -f "$disable_conf"
      echo -e "${YELLOW}[-] 已删除禁用配置文件: $disable_conf${RESET}"
    fi

    # 3. 清理各历史配置文件中的禁用项 (若文件存在)
    for conf_candidate in /etc/sysctl.conf /etc/sysctl.d/sysctl.conf /etc/sysctl.d/99-sysctl.conf; do
      if [ -f "$conf_candidate" ]; then
        sed -i '/net.ipv6.conf.all.disable_ipv6/d' "$conf_candidate"
        sed -i '/net.ipv6.conf.default.disable_ipv6/d' "$conf_candidate"
        sed -i '/net.ipv6.conf.lo.disable_ipv6/d' "$conf_candidate"
      fi
    done

    # 4. 尝试动态启用
    if [ -d "/proc/sys/net/ipv6" ]; then
      sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1
      sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1
      sysctl -w net.ipv6.conf.lo.disable_ipv6=0 >/dev/null 2>&1
      sysctl --system >/dev/null 2>&1 || sysctl -p >/dev/null 2>&1
      if [ "$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)" != 0 ] ||
         [ "$(sysctl -n net.ipv6.conf.default.disable_ipv6 2>/dev/null)" != 0 ]; then
        echo -e "${RED}[×] IPv6 动态启用未生效，请检查内核权限和其他 sysctl 配置${RESET}"
        return 1
      fi
      echo -e "${GREEN}[√] IPv6 参数已动态启用${RESET}"
    else
      echo -e "${YELLOW}[!] 检测到 IPv6 内核模块未加载${RESET}"
      reboot_required=true
    fi

    if [ "$reboot_required" = true ]; then
      echo -e "${RED}[!] 必须重启系统才能重新加载 IPv6 模块！${RESET}"
      ui_confirm restart_choice "是否立即重启? [y/N] (q 取消): "
      if [[ "$restart_choice" =~ ^[Yy]$ ]]; then
        reboot
      fi
    else
      echo -e "${GREEN}[√] IPv6 已开启${RESET}"
    fi
    ;;

  2)
    echo -e "${CYAN}>>> 正在关闭 IPv6...${RESET}"

    # 写入禁用配置到独立文件
    mkdir -p /etc/sysctl.d
    cat >"$disable_conf" <<EOF
# Disable IPv6 (Aurora System Manager)
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
    echo -e "${GREEN}[+] 已创建禁用配置: $disable_conf${RESET}"
    if ! sysctl -p "$disable_conf" >/dev/null 2>&1 || [ "$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)" != 1 ]; then
      echo -e "${RED}[×] IPv6 动态禁用失败，未继续修改引导；请检查 $disable_conf${RESET}"
      return 1
    fi

    # 引导内核禁用
    # (A) 处理 PVE systemd-boot
    if [ -f /etc/kernel/cmdline ]; then
      local cur_cmdline
      cur_cmdline=$(cat /etc/kernel/cmdline 2>/dev/null)
      if ! echo "$cur_cmdline" | grep -qw "ipv6.disable=1"; then
        echo -e "${CYAN}检测到 PVE systemd-boot，正在更新 /etc/kernel/cmdline...${RESET}"
        echo "$cur_cmdline ipv6.disable=1" | sed 's/[[:space:]]\+/ /g' | sed 's/^ //;s/ $//' > /etc/kernel/cmdline
        if command -v proxmox-boot-tool >/dev/null 2>&1; then
          proxmox-boot-tool refresh >/dev/null 2>&1
        fi
        echo -e "${GREEN}[√] PVE systemd-boot 配置已更新${RESET}"
      fi
    fi

    # (B) 处理 GRUB
    if [ -f "$grub_file" ]; then
      echo -e "${CYAN}正在修改 GRUB 配置以彻底禁用 IPv6...${RESET}"
      if ! grep -qw "ipv6.disable=1" "$grub_file"; then
        if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=' "$grub_file"; then
          sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"/GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 \1"/' "$grub_file"
        fi
        if grep -q '^GRUB_CMDLINE_LINUX=' "$grub_file"; then
          sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="ipv6.disable=1 \1"/' "$grub_file"
        fi
        if command -v update-grub >/dev/null 2>&1; then
          update-grub >/dev/null 2>&1
        elif command -v grub2-mkconfig >/dev/null 2>&1; then
          if [ -f /boot/grub2/grub.cfg ]; then
            grub2-mkconfig -o /boot/grub2/grub.cfg >/dev/null 2>&1
          elif [ -f /boot/grub/grub.cfg ]; then
            grub2-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1
          fi
        elif command -v grub-mkconfig >/dev/null 2>&1; then
          grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1
        fi
        echo -e "${GREEN}[√] GRUB 配置已更新${RESET}"
      fi
    fi

    echo -e "${GREEN}[√] IPv6 已永久关闭${RESET}"
    echo -e "${YELLOW}注意: 建议重启系统以确保内核级禁用配置生效${RESET}"
    ;;

  0)
    SKIP_PAUSE=true
    return 0
    ;;

  *)
    echo -e "${RED}无效选项${RESET}"
    ;;
  esac
}

# ---------- [新增] 静态网络配置 (PVE/自建) ----------
# ---------- [新增] 静态网络配置 (支持 Debian系 与 RHEL系) ----------
configure_network_static() {
  install_deps "iproute2" "grep" "awk"

  while true; do
    clear
    echo -e "${CYAN}>>> 静态IP网络配置${RESET}"
    echo -e "${RED}警告：修改网络配置有风险，可能导致 SSH 断连！${RESET}"
    
    # [容器适配] 检测并警告容器环境
    if is_container_env; then
      echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
      echo -e "${YELLOW}[!] 检测到容器环境 (NAT VPS/LXC/Docker)${RESET}"
      echo -e "${GRAY}  • 容器网络通常由宿主机或编排系统管理${RESET}"
      echo -e "${GRAY}  • 手动修改可能被覆盖或导致网络中断${RESET}"
      echo -e "${GRAY}  • 建议通过 VPS 控制面板配置网络${RESET}"
      echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    fi
    
    echo -e "${YELLOW}支持: Debian(interfaces) / RHEL(ifcfg)${RESET}"
    echo -e "${GRAY}对于 Netplan (Ubuntu 18.04+)，仅提供有限支持或警告。${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    # 1. 检测网卡 (允许显示 PVE 虚拟网桥 vmbr 等，但排除内部虚拟接口)
    local interfaces
    interfaces=$(ip -o link show | awk -F': ' '{print $2}' | cut -d'@' -f1 | grep -vE "^(lo|docker|veth|cali|tun|wg|virbr)")

    if [ -z "$interfaces" ]; then
      echo -e "${RED}未检测到可用网卡，请尝试手动编辑。${RESET}"
      ui_pause "按 Enter 返回..."
      return 1
    fi

    local i=1
    local iface_list=()
    echo -e "请选择要配置的网卡:"
    for iface in $interfaces; do
      local extra_tag=""
      if [[ "$iface" =~ ^vmbr[0-9]+ ]]; then
        extra_tag=" ${GREEN}(PVE虚拟网桥)${RESET}"
      fi
      echo -e "  ${GREEN}$i${RESET}) $iface$extra_tag"
      iface_list+=("$iface")
      ((i++))
    done
    echo -e "  ${YELLOW}0.${RESET} 返回主菜单"

    echo -e "${CYAN}==================================================${RESET}"
    ui_read_field if_idx "请输入选项: " ui_any_value "" true || { if_idx=0; }

    if [[ "$if_idx" == "0" ]]; then return 0; fi

    if [[ ! "$if_idx" =~ ^[0-9]+$ ]] || [ "$if_idx" -lt 1 ] || [ "$if_idx" -gt "${#iface_list[@]}" ]; then
      echo -e "${RED}无效选择${RESET}"
      sleep 1
      continue
    fi

    local target_iface="${iface_list[$((if_idx - 1))]}"

    # --- PVE 专属安全检查：防止误将 IP 配在已桥接的物理网卡上导致 PVE 断网 ---
    if [ "$IS_PVE" = true ] && [ -f /etc/network/interfaces ]; then
      if grep -E "bridge-ports.*\b${target_iface}\b" /etc/network/interfaces >/dev/null 2>&1; then
        local bridge_name=$(grep -B 2 -E "bridge-ports.*\b${target_iface}\b" /etc/network/interfaces | grep -oE "iface vmbr[0-9]+" | awk '{print $2}' | head -1)
        echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        echo -e "${RED}[!] 严重警告: 网卡 [${target_iface}] 已被网桥 [${bridge_name:-vmbr0}] 桥接！${RESET}"
        echo -e "${YELLOW}在 PVE 架构中，管理 IP 必须配置在 ${bridge_name:-vmbr0} 网桥上，而不是底层物理网卡！${RESET}"
        echo -e "${YELLOW}若将静态 IP 直接绑定到该网卡，PVE 宿主机和所有虚拟机/LXC 容器将立即全部失联！${RESET}"
        echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
        ui_confirm pve_bridge_continue "建议返回并选择 ${bridge_name:-vmbr0} 进行配置。仍要继续破坏性操作吗? [y/N] (q 取消): "
        if [[ ! "$pve_bridge_continue" =~ ^[Yy]$ ]]; then
          continue
        fi
      fi
    fi

    # --- 2. 获取信息 ---
    local curr_ip_cidr
    curr_ip_cidr=$(ip -4 addr show "$target_iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
    local curr_ip="${curr_ip_cidr%/*}"
    local curr_gw
    curr_gw=$(ip route show 2>/dev/null | grep default | grep "$target_iface" | awk '{print $3}' | head -1)
    local curr_dns
    curr_dns=$(grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | awk '{print $2}' | tr '\n' ' ')

    echo -e "\n${BOLD}${CYAN}网卡 [${target_iface}] 当前状态:${RESET}"
    echo -e "  IP地址:   ${GREEN}${curr_ip:-未配置}${RESET} (CIDR: ${curr_ip_cidr:-未知})"
    echo -e "  默认网关: ${GREEN}${curr_gw:-未知}${RESET}"
    echo -e "  DNS服务器:${GREEN}${curr_dns:-未知}${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    echo -e "请选择操作:"
    echo -e "  ${GREEN}1${RESET}) 修改静态 IP 配置"
    echo -e "  ${YELLOW}0${RESET}) 返回网卡列表"
    ui_menu_read sub_choice "选项" "[01]" || return 0

    if [[ "$sub_choice" == "0" ]]; then continue; fi
    if [[ "$sub_choice" != "1" ]]; then continue; fi

    # --- 3. 输入新配置 ---
    echo -e "\n${CYAN}>>> 请输入新的配置 (输入 0 返回):${RESET}"

    # IP (严格 IPv4 0-255 格式)
    while true; do
      ui_read_field new_ip "IP 地址 (例如 192.168.1.10): " ui_any_value "" true || { new_ip=0; }
      if [[ "$new_ip" == "0" ]]; then continue 2; fi
      if validate_ipv4 "$new_ip"; then break; else echo -e "${RED}格式错误 (必须为 0-255 规范 IPv4)${RESET}"; fi
    done

    # 子网掩码 (连续掩码算法计算 CIDR)
    local cidr_prefix="24"
    while true; do
      ui_read_field new_mask "子网掩码 (默认 255.255.255.0): " ui_any_value "" true || { new_mask=0; }
      if [[ "$new_mask" == "0" ]]; then continue 2; fi
      [ -z "$new_mask" ] && new_mask="255.255.255.0"
      local calc_pfx
      calc_pfx=$(mask_to_cidr "$new_mask")
      if [ $? -eq 0 ] && [ -n "$calc_pfx" ]; then
        cidr_prefix="$calc_pfx"
        break
      else
        echo -e "${RED}格式错误 (子网掩码必须为合法且连续的掩码，如 255.255.255.0 或 255.255.224.0)${RESET}"
      fi
    done

    # Gateway (严格 IPv4)
    while true; do
      ui_read_field new_gw "网关地址 (例如 192.168.1.1): " ui_any_value "" true || { new_gw=0; }
      if [[ "$new_gw" == "0" ]]; then continue 2; fi
      if validate_ipv4 "$new_gw"; then break; else echo -e "${RED}格式错误 (必须为 0-255 规范 IPv4)${RESET}"; fi
    done

    # DNS 验证失败只重读 DNS，不丢失 IP、掩码和网关。
    ui_read_field new_dns "DNS服务器 (空格分隔，回车使用默认值)" ui_ipv4_list "223.5.5.5 8.8.8.8" true || continue

    # --- 4. 确认写入 ---
    echo -e "\n${YELLOW}即将写入以下配置:${RESET}"
    echo -e "Interface: $target_iface"
    echo -e "Address:   $new_ip/$cidr_prefix"
    echo -e "Netmask:   $new_mask"
    echo -e "Gateway:   $new_gw"
    echo -e "DNS:       $new_dns"

    ui_confirm confirm_write "确认写入? [y/N] (q 取消): "
    if [[ ! "$confirm_write" =~ ^[Yy]$ ]]; then continue; fi

    # -------------------------------------------------------------
    # 动态探测目标网卡当前活动管理器 (Netplan vs NetworkManager vs ifupdown vs systemd-networkd)
    # -------------------------------------------------------------
    local active_manager="unknown"
    if command -v nmcli >/dev/null 2>&1 && systemctl is-active NetworkManager >/dev/null 2>&1; then
      local nm_dev_state
      nm_dev_state=$(nmcli -t -f DEVICE,STATE dev status 2>/dev/null | grep -E "^${target_iface}:" | cut -d: -f2)
      if [ -n "$nm_dev_state" ] && [ "$nm_dev_state" != "unmanaged" ]; then
        active_manager="nmcli"
      fi
    fi

    if [ "$active_manager" = "unknown" ]; then
      local has_netplan_yaml=false
      if command -v netplan >/dev/null 2>&1; then
        for np_dir in /etc/netplan /run/netplan /lib/netplan; do
          if [ -d "$np_dir" ] && compgen -G "${np_dir}/*.yaml" >/dev/null 2>&1; then
            has_netplan_yaml=true
            break
          fi
        done
      fi

      if command -v networkctl >/dev/null 2>&1 && systemctl is-active systemd-networkd >/dev/null 2>&1; then
        local netd_out netd_state netd_file
        netd_out=$(networkctl status "$target_iface" 2>/dev/null)
        netd_state=$(echo "$netd_out" | grep -iE "Administrative State|Setup State" | head -1)
        netd_file=$(echo "$netd_out" | grep -i "Network File:" | head -1)
        if [ -n "$netd_state" ] && ! echo "$netd_state" | grep -qi "unmanaged"; then
          if [ "$has_netplan_yaml" = true ] && echo "$netd_file" | grep -qi "netplan"; then
            active_manager="netplan"
          elif [ "$has_netplan_yaml" = true ] && [ -z "$netd_file" ] && grep -rqE "\b${target_iface}\b" /etc/netplan /run/netplan /lib/netplan 2>/dev/null; then
            active_manager="netplan"
          else
            active_manager="networkd"
          fi
        fi
      fi

      if [ "$active_manager" = "unknown" ] && [ "$has_netplan_yaml" = true ]; then
        active_manager="netplan"
      fi
    fi

    if [ "$active_manager" = "unknown" ] && [ -f /etc/network/interfaces ]; then
      if grep -rqE "iface[[:space:]]+${target_iface}\b" /etc/network/interfaces /etc/network/interfaces.d 2>/dev/null || command -v ifup >/dev/null 2>&1; then
        active_manager="interfaces"
      elif ! command -v nmcli >/dev/null 2>&1; then
        active_manager="interfaces"
      fi
    fi

    if [ "$active_manager" = "unknown" ] && [ -f "/etc/sysconfig/network-scripts/ifcfg-$target_iface" ]; then
      active_manager="ifcfg"
    fi

    echo -e "识别到活动网络管理器: ${GREEN}$active_manager${RESET}"

    if [ "$active_manager" = "networkd" ]; then
       echo -e "${YELLOW}[!] 检测到接口 $target_iface 当前由原生 systemd-networkd 服务纳管。${RESET}"
       echo -e "${GRAY}[i] systemd-networkd 采用 .network 策略规则（位于 /etc/systemd/network/）。${RESET}"
       echo -e "${GRAY}[i] 为避免覆盖系统高级策略，脚本暂不提供自动化改写，请手动配置 /etc/systemd/network/*.network。${RESET}"
       ui_pause "按 Enter 继续..."
       continue
    fi

    if [ "$active_manager" = "unknown" ]; then
       echo -e "${RED}[!] 未能确认接口 $target_iface 的有效网络配置后端，操作中止以保护现有连接。${RESET}"
       ui_pause "按 Enter 继续..."
       continue
    fi
    
    # 1. Netplan 分支
    if [ "$active_manager" = "netplan" ]; then
       echo -e "${CYAN}>>> 检测到 Netplan 网络体系，正在分析接口拓扑与现有配置...${RESET}"
       # 精准探测接口的拓扑类型 (杜绝将 bridge/bond 误判为 ethernets)
       local iface_topology="ethernets"
       if [ -d "/sys/class/net/${target_iface}/bridge" ]; then
         iface_topology="bridges"
       elif [ -d "/sys/class/net/${target_iface}/bonding" ] || [ -f "/sys/class/net/${target_iface}/bonding/slaves" ]; then
         iface_topology="bonds"
       elif ip -d link show "$target_iface" 2>/dev/null | grep -q "vlan id"; then
         iface_topology="vlans"
       fi
       echo -e "接口拓扑类型: ${GREEN}$iface_topology${RESET}"

       if ! python3 -c 'import yaml' >/dev/null 2>&1; then
         check_pm_lock || continue
         $PKG_INSTALL python3-yaml || continue
       fi
       local netplan_work
       netplan_work=$(mktemp -d /etc/netplan/aurora-work.XXXXXX) || continue
       # 使用 YAML 节点位置进行局部编辑，避免重新序列化无关配置与注释。
       if ! python3 - /etc/netplan "$netplan_work" "$target_iface" "$iface_topology" "$new_ip/$cidr_prefix" "$new_gw" "$new_dns" <<'PY_NETPLAN'
import fnmatch, ipaddress, os, pathlib, sys, yaml
root, work, iface, topology, address, gateway, dns = sys.argv[1:]
root, work = pathlib.Path(root), pathlib.Path(work)

def read_text_compat(p):
    with open(str(p), 'r', encoding='utf-8', errors='ignore') as f:
        return f.read()

def write_text_compat(p, s):
    with open(str(p), 'w', encoding='utf-8') as f:
        f.write(s)

def dump_yaml_compat(d):
    try:
        return yaml.safe_dump(d, sort_keys=False)
    except TypeError:
        return yaml.safe_dump(d)

def fields(node):
    if not isinstance(node, yaml.MappingNode) or node.flow_style:
        raise ValueError('Unsupported flow mapping or YAML alias')
    return {k.value: (k, v) for k, v in node.value}

mac_path = pathlib.Path('/sys/class/net') / iface / 'address'
mac = read_text_compat(mac_path).strip().lower() if mac_path.exists() else ''
documents, matches = [], []

# 支持 Netplan 多目录层级合并与遮蔽规则 (/lib/netplan < /etc/netplan < /run/netplan)
# 低优先级被高优先级同名文件完全遮蔽；幸存文件按全局字典序加载合并
file_map = {}
for s_dir in [pathlib.Path('/lib/netplan'), root, pathlib.Path('/run/netplan')]:
    if s_dir.is_dir():
        for p in s_dir.glob('*.yaml'):
            file_map[p.name] = p

effective_paths = [file_map[name] for name in sorted(file_map.keys())]

for path in effective_paths:
    text = read_text_compat(path)
    try:
        parsed = yaml.safe_load(text)
        tree = yaml.compose(text)
    except Exception:
        continue
    if not parsed or not isinstance(parsed, dict):
        continue
    network = fields(tree).get('network')
    if not network:
        continue
    # 持久化目标：若源来自 /lib/netplan，持久化应写到 /etc/netplan/同名文件 以覆盖默认配置；其他保持原路径
    target_write = root / path.name if path.parent == pathlib.Path('/lib/netplan') else path
    documents.append({'source_path': path, 'target_path': target_write, 'text': text, 'edits': []})
    doc_idx = len(documents) - 1
    for kind, (key, group) in fields(network[1]).items():
        if kind not in ('ethernets', 'bridges', 'bonds', 'vlans', 'wifis'):
            continue
        for identifier, (id_key, node) in fields(group).items():
            config = parsed['network'][kind][identifier]
            match = config.get('match', {})
            selected = identifier == iface or config.get('set-name') == iface
            if match:
                if 'driver' in match:
                    raise ValueError('Driver matching requires manual review; no files changed')
                name_ok = 'name' not in match or fnmatch.fnmatchcase(iface, str(match['name']))
                mac_ok = 'macaddress' not in match or str(match['macaddress']).lower() == mac
                selected = config.get('set-name') == iface or (name_ok and mac_ok)
            if selected:
                matches.append((doc_idx, kind, identifier, node, config, id_key.start_mark.column+2))

if not matches:
    if topology in ('bridges', 'bonds', 'vlans'):
        raise ValueError('Missing topology definition; no files changed')
    data = {'network': {'version': 2, topology: {iface: {'dhcp4': False, 'addresses': [address],
            'routes': [{'to': 'default', 'via': gateway}], 'nameservers': {'addresses': dns.split()}}}}}
    name = '99-aurora-static-' + iface + '.yaml'
    target_out = root / name
    if target_out.exists():
        raise ValueError('Existing unrelated Aurora file; no files changed')
    documents.append({'source_path': target_out, 'target_path': target_out, 'text': dump_yaml_compat(data), 'edits': []})
else:
    if len({(m[1], m[2]) for m in matches}) != 1:
        raise ValueError('Ambiguous interface matching; no files changed')
    addresses, routes, searches = [address], [], []
    for index, kind, identifier, node, config, indent in matches:
        text = documents[index]['text']
        mapping = fields(node)
        if '<<' in mapping:
            raise ValueError('YAML merge key requires manual review')
        for old in config.get('addresses', []):
            if not isinstance(old, str):
                raise ValueError('Address lifetime mapping requires manual review')
            if ipaddress.ip_interface(old).version == 6 and old not in addresses:
                addresses.append(old)
        for route in config.get('routes', []):
            is_default4 = route.get('to') in ('default', '0.0.0.0/0') and ':' not in str(route.get('via', ''))
            if not is_default4 and route not in routes:
                routes.append(route)
        nameservers = config.get('nameservers', {})
        for search in nameservers.get('search', []):
            if search not in searches:
                searches.append(search)
        for key in ('dhcp4', 'addresses', 'gateway4', 'routes', 'nameservers'):
            if key in mapping:
                k, v = mapping[key]
                if k.start_mark.column != indent:
                    raise ValueError('YAML alias requires manual review')
                start = text.rfind('\n', 0, k.start_mark.index) + 1
                end = v.end_mark.index
                line_start = text.rfind('\n', 0, end) + 1
                if v.end_mark.line > k.start_mark.line and not text[line_start:end].strip():
                    end = line_start
                elif end and text[end-1:end] != '\n':
                    end = text.find('\n', end)
                    end = len(text) if end < 0 else end + 1
                documents[index]['edits'].append((start, end, ''))
    index, kind, identifier, node, config, indent = matches[-1]
    routes.append({'to': 'default', 'via': gateway})
    settings = {'dhcp4': False, 'addresses': addresses, 'routes': routes,
                'nameservers': {'addresses': dns.split()}}
    if searches:
        settings['nameservers']['search'] = searches
    fragment = dump_yaml_compat(settings)
    fragment = ''.join(' '*indent + line + '\n' for line in fragment.splitlines())
    end = node.end_mark.index
    text = documents[index]['text']
    line_start = text.rfind('\n', 0, end) + 1
    if not text[line_start:end].strip():
        end = line_start
    elif end and text[end-1:end] != '\n':
        fragment = '\n' + fragment
    documents[index]['edits'].append((end, end, fragment))

manifest = []
for idx, doc in enumerate(documents):
    if matches and not doc['edits']:
        continue
    updated = doc['text']
    for start, end, replacement in sorted(doc['edits'], reverse=True):
        updated = updated[:start] + replacement + updated[end:]
    yaml.safe_load(updated)
    draft_name = 'draft_%d.yaml' % idx
    backup_name = 'backup_%d.yaml' % idx
    write_text_compat(work / draft_name, updated)
    target = doc['target_path']
    exists = target.exists()
    if exists:
        write_text_compat(work / backup_name, read_text_compat(target))
    manifest.append((draft_name, str(target), '1' if exists else '0'))
    if doc['source_path'].parent == pathlib.Path('/run/netplan') and doc['target_path'] != (root / doc['source_path'].name):
        p_target = root / doc['source_path'].name
        p_draft = 'draft_%d_persist.yaml' % idx
        p_backup = 'backup_%d_persist.yaml' % idx
        write_text_compat(work / p_draft, updated)
        p_exists = p_target.exists()
        if p_exists:
            write_text_compat(work / p_backup, read_text_compat(p_target))
        manifest.append((p_draft, str(p_target), '1' if p_exists else '0'))

write_text_compat(work / 'manifest', ''.join('|'.join(row) + '\n' for row in manifest))
PY_NETPLAN
       then
         echo -e "${RED}[×] 无法安全解析现有 Netplan 配置，未修改活动 YAML；草稿保留: $netplan_work${RESET}"
         continue
       fi
       echo -e "${GREEN}[√] Netplan 局部修改草稿已生成，其他接口和高级参数保持不变${RESET}"
       ui_confirm r_net "立即应用网络配置 (附带 30 秒断网自动回滚保护)? [y/N] (q 取消): "
       if [[ "$r_net" =~ ^[Yy]$ ]]; then
          local netplan_commit_ok=true draft_file target_abs was_existed
          while IFS='|' read -r draft_file target_abs was_existed; do
            [ -n "$draft_file" ] || continue
            mkdir -p "$(dirname "$target_abs")"
            install -m 600 "$netplan_work/$draft_file" "$target_abs" || netplan_commit_ok=false
          done < "$netplan_work/manifest"
          echo -e "${CYAN}正在调用 netplan try 进行带回滚测试 (30s 内回车确认)...${RESET}"
          if [ "$netplan_commit_ok" = true ] && netplan generate && netplan try --timeout 30; then
             echo -e "${GREEN}[√] Netplan 网络配置应用并确认成功${RESET}"
          else
             echo -e "${RED}[!] Netplan 应用超时或失败，正在执行完整现场还原...${RESET}"
             local netplan_restore_ok=true
             while IFS='|' read -r draft_file target_abs was_existed; do
               [ -n "$draft_file" ] || continue
               local bak_file="${draft_file/draft_/backup_}"
               if [ "$was_existed" = "1" ] && [ -f "$netplan_work/$bak_file" ]; then
                 cp -f "$netplan_work/$bak_file" "$target_abs" || netplan_restore_ok=false
               else
                 rm -f "$target_abs" || netplan_restore_ok=false
               fi
             done < "$netplan_work/manifest"
             if [ "$netplan_restore_ok" = true ] && netplan apply; then
               echo -e "${YELLOW}[!] 原 Netplan 配置已完整还原${RESET}"
             else
               echo -e "${RED}[×] Netplan 恢复失败，请使用保留的备份检查: $netplan_work${RESET}"
             fi
          fi
       else
          echo -e "${YELLOW}[!] 已取消应用，未改动活动 YAML。草稿保留: $netplan_work${RESET}"
       fi

    # 2. 传统 Debian / PVE (/etc/network/interfaces)
    elif [ "$active_manager" = "interfaces" ]; then
       local if_file="/etc/network/interfaces"
       # 检查是否定义在 interfaces.d 下
       local d_file=$(grep -rlE "iface[[:space:]]+${target_iface}\b" /etc/network/interfaces.d 2>/dev/null | head -1)
       [ -n "$d_file" ] && [ -f "$d_file" ] && if_file="$d_file"

       local timestamp=$(date +%Y%m%d%H%M%S)
       local net_bak="${if_file}.bak.${timestamp}"
       local hosts_bak="/etc/hosts.bak.${timestamp}"
       cp -p "$if_file" "$net_bak" || continue
       if [ -f /etc/hosts ]; then
         cp -p /etc/hosts "$hosts_bak" || continue
       fi

       # 确保 python3 环境可用
       if ! command -v python3 >/dev/null 2>&1; then
         check_pm_lock || continue
         $PKG_INSTALL python3 || continue
       fi

       echo -e "${CYAN}正在解析并安全更新 $if_file (保留网桥/VLAN/bond所有高级参数)...${RESET}"

       # 使用 Python 安全重写对应 stanza，正确覆盖 source-directory / allow-hotplug 结束边界
       if ! python3 -c "
import sys, re

target = '$target_iface'
new_ip = '$new_ip'
new_mask = '$new_mask'
new_gw = '$new_gw'
new_dns = '$new_dns'
filepath = '$if_file'

with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
    lines = f.readlines()

out = []
in_target_stanza = False
target_stanza_done = False
has_auto = False
preserved_attrs = []

for line in lines:
    stripped = line.strip()
    if re.match(r'^auto\s+.*\b' + re.escape(target) + r'\b', stripped):
        has_auto = True
    
    # 遇到新的 iface 或 mapping
    if re.match(r'^(iface|mapping)\s+', stripped):
        if in_target_stanza:
            in_target_stanza = False
        if re.match(r'^iface\s+' + re.escape(target) + r'\s+inet\b', stripped):
            in_target_stanza = True
            target_stanza_done = True
            continue
    # 扩展匹配：精准覆盖 source-directory, allow-hotplug 等非缩进顶级指令
    elif in_target_stanza and re.match(r'^(auto|source|source-directory|allow-hotplug|allow-)\s+', stripped):
        in_target_stanza = False

    if in_target_stanza:
        # 收集非 IP/掩码/网关/DNS 的高级选项 (如 bridge-*, bond-*, mtu, hwaddress 等)
        if not re.match(r'^(address|netmask|gateway|dns-nameservers)\b', stripped):
            preserved_attrs.append(line)
    else:
        out.append(line)

# 重构该 interface 的完整 stanza (采用向下兼容的字符串拼接，不依赖 Python 3.6+ 的 f-string)
stanza = []
if not has_auto:
    stanza.append('auto ' + target + '\n')
stanza.append('iface ' + target + ' inet static\n')
stanza.append('    address ' + new_ip + '\n')
stanza.append('    netmask ' + new_mask + '\n')
stanza.append('    gateway ' + new_gw + '\n')
stanza.append('    dns-nameservers ' + new_dns + '\n')
for attr in preserved_attrs:
    stanza.append(attr)

out.append('\n# Generated by Aurora VPS Script\n')
out.extend(stanza)

with open(filepath, 'w', encoding='utf-8') as f:
    f.writelines(out)
sys.exit(0)
"; then
          echo -e "${RED}[×] 重构网络接口配置失败！已中止修改并还原备份。${RESET}"
          cp -f "$net_bak" "$if_file"
          ui_pause "按 Enter 继续..."
          continue
       fi

       echo -e "${GREEN}[√] $if_file 已精准更新 (完整保留网桥参数)${RESET}"

       # --- [PVE 宿主机 /etc/hosts 联动检查 (严格转义与限定当前节点名)] ---
       local hosts_modified=false
       if [ "$IS_PVE" = true ] && [ -n "$curr_ip" ] && [ "$curr_ip" != "未配置" ]; then
          local cur_hostname=$(hostname 2>/dev/null)
          local escaped_curr_ip=$(echo "$curr_ip" | sed 's/[.]/\\./g')
          if grep -qE "^[[:space:]]*${escaped_curr_ip}\b.*${cur_hostname}\b" /etc/hosts 2>/dev/null; then
             echo -e "${YELLOW}检测到 /etc/hosts 中绑定了原 PVE 节点 IP ($curr_ip $cur_hostname)${RESET}"
             ui_confirm sync_hosts "是否同步更新 /etc/hosts 中的 PVE 节点 IP 映射? [Y/n] (q 取消): " y
             if [[ ! "$sync_hosts" =~ ^[Nn]$ ]]; then
                # 严格限定在当前主机名的映射行中替换，绝不破坏其他节点或子串
                if ! awk -v old="$curr_ip" -v new="$new_ip" -v host="$cur_hostname" '
                  $1 == old { for(i=2; i<=NF && $i !~ /^#/; i++) if($i == host) {
                    sub(/^[[:space:]]*[^[:space:]]+/, new); break
                  } }
                  { print }
                ' /etc/hosts > "${hosts_bak}.new" || ! cp -f "${hosts_bak}.new" /etc/hosts; then
                  cp -f "$net_bak" "$if_file"
                  cp -f "$hosts_bak" /etc/hosts
                  echo -e "${RED}[×] hosts 更新失败，已停止网络修改${RESET}"
                  continue
                fi
                rm -f "${hosts_bak}.new"
                hosts_modified=true
                echo -e "${GREEN}[√] /etc/hosts 节点映射已安全同步更新为 $new_ip${RESET}"
             fi
          fi
       fi

       # 重启网络 (带失联看门狗自动回滚保护)
       ui_confirm r_net "立即重载网络 (附带 35 秒失联自动回滚看门狗)? [y/N] (q 取消): "
       if [[ "$r_net" =~ ^[Yy]$ ]]; then
          local watchdog_flag
          watchdog_flag=$(mktemp /tmp/aurora-net-watchdog.XXXXXX) || continue
          # 启动后台看门狗保护：若重载网络导致远程失联无法完成确认，35 秒后自动还原 interfaces 和 hosts
          (
            trap '' HUP
            sleep 35
            if [ -f "$watchdog_flag" ]; then
              cp -f "$net_bak" "$if_file" || exit 1
              [ ! -f "$hosts_bak" ] || cp -f "$hosts_bak" /etc/hosts || exit 1
              ifreload -a 2>/dev/null || systemctl restart networking 2>/dev/null || exit 1
              rm -f "$watchdog_flag"
            fi
          ) >"${watchdog_flag}.log" 2>&1 &
          local watchdog_pid=$!

          local reload_ok=false
          if [ "$IS_PVE" = true ] && command -v ifreload >/dev/null 2>&1; then
             echo -e "${CYAN}正在执行 ifreload -a (Proxmox 安全重载)...${RESET}"
             ifreload -a && reload_ok=true
          elif systemctl restart networking 2>/dev/null; then
             reload_ok=true
          fi

          if [ "$reload_ok" = true ]; then
             local network_confirm=""
             read -r -t 25 -p "请另开 SSH 连接确认新地址可登录，再输入 yes 保留配置 (超时回滚): " network_confirm
             if [ "$network_confirm" = yes ] && [ -f "$watchdog_flag" ]; then
               rm -f "$watchdog_flag"
               kill "$watchdog_pid" 2>/dev/null || true
               wait "$watchdog_pid" 2>/dev/null || true
               echo -e "${GREEN}[√] 已确认保留新网络配置${RESET}"
             else
               echo -e "${YELLOW}[!] 未确认新连接，等待看门狗恢复原配置${RESET}"
               wait "$watchdog_pid" || echo -e "${RED}[×] 看门狗恢复失败，请检查 $net_bak 与 ${watchdog_flag}.log${RESET}"
             fi
          else
             echo -e "${RED}[!] 网络重载失败，正在执行应急双向回滚 (interfaces 与 /etc/hosts)...${RESET}"
             cp -f "$net_bak" "$if_file"
             [ -f "$hosts_bak" ] && cp -f "$hosts_bak" /etc/hosts
             ifreload -a 2>/dev/null || systemctl restart networking 2>/dev/null
             rm -f "$watchdog_flag" 2>/dev/null
             kill -9 "$watchdog_pid" 2>/dev/null || true
             echo -e "${YELLOW}[!] 原网络配置与 hosts 映射已全部复原${RESET}"
          fi
       fi

    # 3. 通用 NetworkManager (nmcli) 分支 (全发行版支持)
    elif [ "$active_manager" = "nmcli" ]; then
        echo -e "${CYAN}>>> 使用 NetworkManager (nmcli) 写入静态连接配置...${RESET}"
           local con_name
           con_name=$(nmcli -g GENERAL.CON-UUID dev show "$target_iface" 2>/dev/null | head -1)

           # 处理未管理接口 (-- 标识)
           local is_new_conn=false
           if [ -z "$con_name" ] || [ "$con_name" == "--" ]; then
             con_name="static-${target_iface}-$$-${RANDOM}"
             is_new_conn=true
             echo -e "${YELLOW}接口当前未被 NetworkManager 活动连接托管，将创建新连接 profile: $con_name${RESET}"
           fi

           # 备份旧连接参数以备回滚
           local old_ipv4_addr="" old_ipv4_gw="" old_ipv4_dns="" old_ipv4_method="auto"
           if [ "$is_new_conn" = false ]; then
             old_ipv4_addr=$(nmcli -g ipv4.addresses con show "$con_name" 2>/dev/null) || continue
             old_ipv4_gw=$(nmcli -g ipv4.gateway con show "$con_name" 2>/dev/null) || continue
             old_ipv4_dns=$(nmcli -g ipv4.dns con show "$con_name" 2>/dev/null) || continue
             old_ipv4_method=$(nmcli -g ipv4.method con show "$con_name" 2>/dev/null) || continue
           fi

           local cfg_ok=false
           if [ "$is_new_conn" = false ]; then
             if nmcli con mod "$con_name" ipv4.addresses "$new_ip/$cidr_prefix" ipv4.gateway "$new_gw" ipv4.dns "${new_dns// /,}" ipv4.method manual 2>/dev/null; then
               cfg_ok=true
             fi
           fi

           if [ "$cfg_ok" = false ] && [ "$is_new_conn" = true ]; then
             if [ "$(nmcli -g GENERAL.TYPE dev show "$target_iface" 2>/dev/null)" != ethernet ]; then
               echo -e "${RED}[×] 未托管的非 Ethernet 接口需要专用拓扑配置，未创建错误 profile${RESET}"
               continue
             fi
             if nmcli con add type ethernet ifname "$target_iface" con-name "$con_name" ipv4.addresses "$new_ip/$cidr_prefix" ipv4.gateway "$new_gw" ipv4.dns "${new_dns// /,}" ipv4.method manual 2>/dev/null; then
               cfg_ok=true
               is_new_conn=true
             fi
           fi

           if [ "$cfg_ok" = false ]; then
             echo -e "${RED}[×] NetworkManager 配置应用失败，未变更原网络连接。${RESET}"
             ui_pause "按 Enter 继续..."
             continue
           fi
           
           echo -e "${GREEN}[√] NetworkManager 配置已更新 (连接: $con_name)${RESET}"
           ui_confirm r_net "立即激活网络连接? [y/N] (q 取消): "
           if [[ "$r_net" =~ ^[Yy]$ ]]; then
              local nm_flag nm_watchdog_pid nm_confirm=""
              restore_nm_network() {
                if [ "$is_new_conn" = true ]; then
                  nmcli con delete "$con_name"
                else
                  nmcli con mod "$con_name" ipv4.addresses "$old_ipv4_addr" ipv4.gateway "$old_ipv4_gw" ipv4.dns "$old_ipv4_dns" ipv4.method "$old_ipv4_method" &&
                    nmcli con up "$con_name"
                fi
              }
              nm_flag=$(mktemp /tmp/aurora-nm-watchdog.XXXXXX) || { restore_nm_network; continue; }
              (
                trap '' HUP
                sleep 35
                if [ -f "$nm_flag" ]; then
                  restore_nm_network || exit 1
                  rm -f "$nm_flag"
                fi
              ) >"${nm_flag}.log" 2>&1 &
              nm_watchdog_pid=$!
              if nmcli con up "$con_name"; then
                read -r -t 25 -p "请另开 SSH 连接验证新地址，再输入 yes 保留配置 (超时回滚): " nm_confirm
              fi
              if [ "$nm_confirm" = yes ] && [ -f "$nm_flag" ]; then
                rm -f "$nm_flag"
                kill "$nm_watchdog_pid" 2>/dev/null || true
                wait "$nm_watchdog_pid" 2>/dev/null || true
                echo -e "${GREEN}[√] 已确认新 NetworkManager 连接${RESET}"
              else
                wait "$nm_watchdog_pid" || echo -e "${RED}[×] NM 看门狗恢复失败，请检查 ${nm_flag}.log${RESET}"
                echo -e "${YELLOW}[!] 未确认连接，已触发旧配置恢复；请检查管理连接${RESET}"
              fi
           fi

    # 4. 传统 RHEL / CentOS ifcfg 分支
    elif [ "$active_manager" = "ifcfg" ]; then
        local ifcfg="/etc/sysconfig/network-scripts/ifcfg-$target_iface"
        local ifcfg_bak="${ifcfg}.bak.$(date +%Y%m%d%H%M%S)"
        cp "$ifcfg" "$ifcfg_bak"
        set_kv() {
          local k=$1; local v=$2
          if grep -q "^$k=" "$ifcfg"; then
             sed -i "s|^$k=.*|$k=\"$v\"|g" "$ifcfg"
          else
             echo "$k=\"$v\"" >>"$ifcfg"
          fi
        }
        set_kv "BOOTPROTO" "static"
        set_kv "ONBOOT" "yes"
        set_kv "IPADDR" "$new_ip"
        set_kv "NETMASK" "$new_mask"
        set_kv "GATEWAY" "$new_gw"
        local dns_idx=1
        for d in $new_dns; do
          set_kv "DNS$dns_idx" "$d"
          ((dns_idx++))
        done
        echo -e "${GREEN}[√] 配置已写入 $ifcfg${RESET}"
        ui_confirm r_net "立即重启网络? [y/N] (q 取消): "
        if [[ "$r_net" =~ ^[Yy]$ ]]; then
           if systemctl restart network 2>/dev/null || systemctl restart NetworkManager 2>/dev/null; then
             echo -e "${GREEN}[√] 网络已成功重启${RESET}"
           else
             echo -e "${RED}[!] 网络重启失败，正在还原 $ifcfg 并重新启动网络...${RESET}"
             cp -f "$ifcfg_bak" "$ifcfg"
             systemctl restart network 2>/dev/null || systemctl restart NetworkManager 2>/dev/null
             echo -e "${YELLOW}[!] 已回滚恢复原 ifcfg 配置${RESET}"
           fi
        fi
    else
        echo -e "${RED}[!] 当前系统未发现受支持的网络管理服务 (无 netplan/interfaces/nmcli/ifcfg)${RESET}"
    fi
    
    ui_pause "按 Enter 返回网卡列表..."
  done
}


# --- [8. 应用] 软件安装中心 (Docker/Shell) ---
# --- [8.1] Docker 基础环境管理 ---
has_docker_compose() {
  docker compose version >/dev/null 2>&1 || docker-compose version >/dev/null 2>&1
}

run_docker_compose() {
  if docker compose version >/dev/null 2>&1; then
    docker compose "$@"
  elif docker-compose version >/dev/null 2>&1; then
    docker-compose "$@"
  else
    echo -e "${RED}[×] 未找到可正常运行的 docker compose 或 docker-compose 命令${RESET}"
    return 127
  fi
}

ensure_docker() {
  if ! command -v docker >/dev/null 2>&1; then
    echo -e "${YELLOW}[!] 检测到系统未安装 Docker 环境。${RESET}"
    ui_confirm install_choice "是否立即安装 Docker? [y/N] (q 取消): "
    if [[ "$install_choice" =~ ^[Yy]$ ]]; then
      do_install_docker
      # 安装后再次检测
      if ! command -v docker >/dev/null 2>&1; then
        echo -e "${RED}[×] Docker 安装失败或已取消，无法继续部署容器。${RESET}"
        return 1
      fi
    else
      echo -e "${YELLOW}[!] 已取消操作，需要 Docker 环境才能继续。${RESET}"
      return 1
    fi
  fi
  # 确保 docker 服务运行
  if ! systemctl start docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
    echo -e "${RED}[×] Docker daemon 不可用，停止部署${RESET}"
    return 1
  fi
  if ! has_docker_compose; then
    check_pm_lock || return 1
    if ! $PKG_INSTALL docker-compose-plugin 2>/dev/null; then
      [ "$PKG_MANAGER" = apt ] && $PKG_INSTALL docker-compose-v2 2>/dev/null || true
    fi
  fi
  if ! has_docker_compose; then
    local compose_bin="/usr/local/bin/docker-compose"
    local compose_url="https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)"
    echo -e "${CYAN}尝试下载独立版 Docker Compose 二进制...${RESET}"
    if curl -fsSL --retry 3 --connect-timeout 15 "$compose_url" -o "$compose_bin" 2>/dev/null; then
      chmod +x "$compose_bin" 2>/dev/null
    fi
  fi
  if ! has_docker_compose; then
    echo -e "${RED}[×] Docker Compose 插件及独立二进制均不可用，停止依赖 Compose 的部署${RESET}"
    return 1
  fi
  # 建立双向快捷命令，确保命令行体验一致
  if ! command -v docker-compose >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
    cat >/usr/local/bin/docker-compose <<'EOF'
#!/bin/sh
exec docker compose "$@"
EOF
    chmod +x /usr/local/bin/docker-compose 2>/dev/null || true
  fi
  return 0
}

# 独立安装 Docker 函数
do_install_docker() {
  if command -v docker >/dev/null 2>&1; then
    echo -e "${YELLOW}[!] Docker 已安装，无需重复操作。${RESET}"
    return 0
  fi

  # PVE 宿主机冲突风险警示
  if [ "$IS_PVE" = true ]; then
    echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${RED}[!] 架构警告: 当前宿主机为 Proxmox VE (PVE)！${RESET}"
    echo -e "${YELLOW}Proxmox 官方极不建议在 PVE 宿主机直接安装运行 Docker。${RESET}"
    echo -e "${YELLOW}Docker 会重写宿主机的 iptables/nftables 链与默认转发策略，极易导致虚拟机/容器网桥(vmbr)通信故障或 Corosync 失联！${RESET}"
    echo -e "${CYAN}建议做法: 创建专门的 Debian/Alpine LXC 容器或虚拟机来运行 Docker 业务。${RESET}"
    echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    ui_confirm pve_docker_confirm "是否仍然执意在 PVE 宿主机安装 Docker? [y/N] (q 取消): "
    if [[ ! "$pve_docker_confirm" =~ ^[Yy]$ ]]; then
      echo -e "${GREEN}[√] 操作已取消，保持 PVE 宿主机环境纯净。${RESET}"
      return 0
    fi
  fi

  echo -e "${CYAN}>>> 准备安装 Docker 环境 (官方源)...${RESET}"
  ui_confirm confirm "确认开始安装吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  install_deps "curl"
  check_pm_lock || return 1

  local docker_script="${TEMP_DIR}/get_docker.sh"
  echo -e "${CYAN}正在下载 Docker 官方安装脚本...${RESET}"
  if ! curl -fsSL --retry 3 --connect-timeout 10 https://get.docker.com -o "$docker_script"; then
    echo -e "${YELLOW}官方 get.docker.com 下载超时，尝试通过镜像加速下载...${RESET}"
    curl -fsSL --retry 2 --connect-timeout 10 https://mirror.azure.cn/docker-ce/linux/static/stable/ || true
    curl -fsSL --retry 2 --connect-timeout 10 https://get.docker.com -o "$docker_script" || {
      echo -e "${RED}[×] 无法下载 Docker 安装脚本，请检查网络或 DNS 解析${RESET}"
      return 1
    }
  fi

  echo -e "${CYAN}正在执行 Docker 自动化部署...${RESET}"
  if sh "$docker_script" && command -v docker >/dev/null 2>&1; then
    rm -f "$docker_script"
    echo -e "${GREEN}[√] Docker 引擎安装完成${RESET}"
    systemctl enable --now docker >/dev/null 2>&1

    # 处理 Docker Compose (优先使用已集成的 docker-compose-plugin)
    echo -e "${CYAN}>>> 正在检测与配置 Docker Compose...${RESET}"
    if docker compose version >/dev/null 2>&1; then
      echo -e "${GREEN}[√] 检测到系统已集成现代版 docker compose plugin${RESET}"
      # 为传统命令建立包装脚本，兼容习惯
      if [ ! -f /usr/local/bin/docker-compose ]; then
        cat >/usr/local/bin/docker-compose <<'EOF'
#!/bin/sh
exec docker compose "$@"
EOF
        chmod +x /usr/local/bin/docker-compose
      fi
    else
      echo -e "${CYAN}正在下载独立版 Docker Compose 二进制...${RESET}"
      local compose_bin="/usr/local/bin/docker-compose"
      local compose_url="https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)"
      if curl -fsSL --retry 3 --connect-timeout 15 "$compose_url" -o "$compose_bin"; then
        if [ -s "$compose_bin" ] && ! head -n 1 "$compose_bin" | grep -qiE "<!doctype|<html>"; then
          chmod +x "$compose_bin"
          echo -e "${GREEN}[√] Docker Compose 安装完成${RESET}"
        else
          rm -f "$compose_bin"
          echo -e "${RED}[!] 下载的文件无效或为错误页面，正在回退通过包管理器安装 compose 插件...${RESET}"
          $PKG_INSTALL docker-compose-plugin 2>/dev/null || true
        fi
      else
        echo -e "${YELLOW}[!] GitHub 直连下载失败，正在通过包管理器补装 docker-compose-plugin...${RESET}"
        $PKG_INSTALL docker-compose-plugin 2>/dev/null || true
      fi
    fi
  else
    rm -f "$docker_script"
    echo -e "${RED}[×] Docker 安装脚本执行失败，请检查系统依赖或包管理器锁状态${RESET}"
    return 1
  fi
}

# 独立卸载 Docker 函数
do_uninstall_docker() {
  if ! command -v docker >/dev/null 2>&1 && ! dpkg -l docker-ce >/dev/null 2>&1 && ! rpm -q docker-ce >/dev/null 2>&1; then
    echo -e "${YELLOW}[!] 系统未检测到已安装的 Docker。${RESET}"
    return 0
  fi

  # 前置检测共享 containerd (K3s/K8s/CNI 环境)
  local is_shared_containerd=false
  if command -v k3s >/dev/null 2>&1 || command -v crictl >/dev/null 2>&1 || [ -f /etc/rancher/k3s/k3s.yaml ] || [ -d /etc/cni ]; then
    is_shared_containerd=true
    echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${YELLOW}[!] 提示: 检测到外部 Kubernetes/K3s 运行时环境！${RESET}"
    echo -e "${YELLOW}containerd 将作为共享底层容器运行时予以严格保留，不会停止或删除 containerd 组件与数据。${RESET}"
    echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
  fi

  echo -e "${RED}>>> 警告: 即将卸载 Docker 引擎及所有容器镜像与本地数据!${RESET}"
  ui_confirm confirm "确认彻底卸载吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  echo -e "${CYAN}>>> 正在停止 Docker 服务并核查进程退出状态...${RESET}"
  systemctl stop docker.socket docker 2>/dev/null || true
  if [ "$is_shared_containerd" = false ]; then
    systemctl stop containerd.service 2>/dev/null || true
  fi

  # 确认 dockerd 进程已完全退出
  if pgrep -x dockerd >/dev/null 2>&1; then
    sleep 2
    if pgrep -x dockerd >/dev/null 2>&1; then
      echo -e "${RED}[×] dockerd 进程仍在运行未能正常停止，为保护数据一致性，已中止卸载。${RESET}"
      return 1
    fi
  fi

  echo -e "${CYAN}>>> 正在扫描并过滤实际已安装的 Docker 组件...${RESET}"
  check_pm_lock || return 1
  
  local candidate_pkgs="docker-ce docker-ce-cli docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras docker.io docker"
  [ "$is_shared_containerd" = false ] && candidate_pkgs="$candidate_pkgs containerd.io"

  local installed_to_remove=()
  if [[ "$PKG_MANAGER" == "apt" ]]; then
     for p in $candidate_pkgs; do
        dpkg -l "$p" 2>/dev/null | grep -qE "^(ii|rc)" && installed_to_remove+=("$p")
     done
  elif [[ "$PKG_MANAGER" == "dnf5" || "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
     for p in $candidate_pkgs; do
        rpm -q "$p" >/dev/null 2>&1 && installed_to_remove+=("$p")
     done
  fi

  if [ ${#installed_to_remove[@]} -gt 0 ]; then
     echo -e "${CYAN}待卸载软件包: ${installed_to_remove[*]}${RESET}"
     local remove_failed=false
     $PKG_REMOVE "${installed_to_remove[@]}" || remove_failed=true

     # 坚决杜绝任何绕过条件：只要包卸载失败，不论 CLI 是否存在，无条件阻断数据删除！
     if [ "$remove_failed" = true ]; then
        echo -e "${RED}[×] 软件包卸载过程中报告失败！为防数据丢失与依赖冲突，已无条件中止数据删除。${RESET}"
        return 1
     fi
  else
     echo -e "${GRAY}系统无已安装的已知 Docker 软件包${RESET}"
  fi
  
  # 清理 Docker 自有数据与二进制
  echo -e "${CYAN}>>> 正在清理 Docker 本地数据目录与二进制文件...${RESET}"
  rm -rf /var/lib/docker /etc/docker
  rm -f /usr/local/bin/docker-compose /usr/bin/docker-compose

  # 保护共享 containerd 数据
  if [ "$is_shared_containerd" = false ]; then
     rm -rf /var/lib/containerd
  else
     echo -e "${GREEN}[√] /var/lib/containerd 已完整保留 (保护外部 K3s/K8s 运行时)${RESET}"
  fi

  echo -e "${GREEN}[√] Docker 卸载与清理完成${RESET}"
}

# --- [8.2] 面板类应用安装 ---
install_xui() {
  echo -e "${CYAN}>>> 准备安装 X-UI 面板 (官方脚本)${RESET}"
  ui_confirm confirm "确认开始安装吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  install_deps "curl"
  local tmp_installer
  tmp_installer=$(mktemp /tmp/xui-install.XXXXXX.sh) || return 1
  echo -e "${CYAN}正在下载官方安装脚本...${RESET}"
  if curl -fsSL "https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh" -o "$tmp_installer" && [ -s "$tmp_installer" ]; then
    local run_ret=0
    bash "$tmp_installer" || run_ret=$?
    rm -f "$tmp_installer"
    if [ $run_ret -ne 0 ]; then
      echo -e "${RED}[×] X-UI 安装失败 (退出码: $run_ret)${RESET}"
      return $run_ret
    fi
    echo -e "${GREEN}[√] X-UI 安装成功完成${RESET}"
  else
    rm -f "$tmp_installer"
    echo -e "${RED}[×] 安装脚本下载失败，请检查网络连接（如 GitHub 访问）${RESET}"
    return 1
  fi
}

install_3xui() {
  echo -e "${CYAN}>>> 准备安装 3X-UI 面板 (官方脚本)${RESET}"
  ui_confirm confirm "确认开始安装吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  install_deps "curl"
  local tmp_installer
  tmp_installer=$(mktemp /tmp/3xui-install.XXXXXX.sh) || return 1
  echo -e "${CYAN}正在下载官方安装脚本...${RESET}"
  if curl -fsSL "https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh" -o "$tmp_installer" && [ -s "$tmp_installer" ]; then
    local run_ret=0
    bash "$tmp_installer" || run_ret=$?
    rm -f "$tmp_installer"
    if [ $run_ret -ne 0 ]; then
      echo -e "${RED}[×] 3X-UI 安装失败 (退出码: $run_ret)${RESET}"
      return $run_ret
    fi
    echo -e "${GREEN}[√] 3X-UI 安装成功完成${RESET}"
  else
    rm -f "$tmp_installer"
    echo -e "${RED}[×] 安装脚本下载失败，请检查网络连接（如 GitHub 访问）${RESET}"
    return 1
  fi
}

install_portainer() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 Portainer CE (可视化管理)${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  echo -e "${CYAN}>>> 正在拉取并启动 Portainer...${RESET}"
  docker volume create portainer_data >/dev/null 2>&1
  if docker run -d \
    -p 8000:8000 \
    -p 9443:9443 \
    -p 9000:9000 \
    --name portainer \
    --restart=always \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    portainer/portainer-ce:latest; then
    echo -e "${GREEN}[√] Portainer 已成功部署。${RESET}"
    echo -e "${YELLOW}访问地址 (HTTPS - 推荐): https://<IP>:9443${RESET}"
    echo -e "${YELLOW}访问地址 (HTTP): http://<IP>:9000${RESET}"
  else
    echo -e "${RED}[×] Portainer 启动失败，请检查端口占用 (8000/9443/9000) 或镜像拉取状态${RESET}"
    return 1
  fi
}

install_npm() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 Nginx Proxy Manager${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  local install_dir="/root/npm"
  mkdir -p "$install_dir"

  cat >"$install_dir/docker-compose.yml" <<EOF
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
EOF

  echo -e "${CYAN}正在启动容器...${RESET}"
  if cd "$install_dir" && run_docker_compose up -d; then
    echo -e "${GREEN}[√] NPM 已部署。${RESET}"
    echo -e "${YELLOW}管理后台: http://<IP>:81 (admin@example.com / changeme)${RESET}"
  else
    echo -e "${RED}[×] 启动失败。${RESET}"
    return 1
  fi
}

install_filecodebox() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 FileCodeBox (文件快递柜)${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  local data_dir="/opt/filecodebox"
  mkdir -p "$data_dir"

  if docker run -d \
    --name filecodebox \
    --restart=always \
    -p 12345:12345 \
    -v "$data_dir":/app/data \
    lanol/filecodebox:latest; then
    echo -e "${GREEN}[√] FileCodeBox 已成功部署。${RESET}"
    echo -e "${YELLOW}访问地址: http://<IP>:12345${RESET}"
  else
    echo -e "${RED}[×] FileCodeBox 启动失败，请检查端口占用 (12345) 或 Docker 日志${RESET}"
    return 1
  fi
}

install_1panel() {
  echo -e "${CYAN}>>> 准备安装 1Panel (现代化运维面板)${RESET}"
  echo -e "${GRAY}注意: 1Panel 安装脚本会自动管理 Docker 环境。${RESET}"
  ui_confirm confirm "确认开始安装吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  install_deps "curl"
  check_pm_lock || return 1
  local tmp_1p
  tmp_1p=$(mktemp /tmp/1panel-quick-start.XXXXXX.sh) || return 1
  echo -e "${CYAN}正在下载 1Panel 官方安装脚本...${RESET}"
  if curl -fsSL "https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh" -o "$tmp_1p" && [ -s "$tmp_1p" ]; then
    local run_ret=0
    bash "$tmp_1p" || run_ret=$?
    rm -f "$tmp_1p" quick_start.sh 2>/dev/null
    if [ $run_ret -ne 0 ]; then
      echo -e "${RED}[×] 1Panel 安装失败 (退出码: $run_ret)${RESET}"
      return $run_ret
    fi
    echo -e "${GREEN}[√] 1Panel 安装流程已完成${RESET}"
  else
    rm -f "$tmp_1p"
    echo -e "${RED}[×] 1Panel 安装脚本下载失败，请检查网络连接${RESET}"
    return 1
  fi
}

install_n8n() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 n8n (自动化工作流)${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  docker volume create n8n_data >/dev/null 2>&1
  if docker run -d \
    --name n8n \
    --restart unless-stopped \
    --shm-size 2g \
    -p 5678:5678 \
    -e N8N_SECURE_COOKIE=false \
    -v n8n_data:/home/node/.n8n \
    n8nio/n8n:latest; then
    echo -e "${GREEN}[√] n8n 已成功部署。${RESET}"
    echo -e "${YELLOW}访问地址: http://<IP>:5678${RESET}"
  else
    echo -e "${RED}[×] n8n 启动失败，请检查端口占用 (5678) 或 Docker 日志${RESET}"
    return 1
  fi
}

install_uptime_kuma() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 Uptime Kuma (在线状态监控)${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  if docker run -d \
    --restart=always \
    -p 3001:3001 \
    -v uptime-kuma:/app/data \
    --name uptime-kuma \
    louislam/uptime-kuma:1; then
    echo -e "${GREEN}[√] Uptime Kuma 已成功部署。${RESET}"
    echo -e "${YELLOW}访问地址: http://<IP>:3001${RESET}"
  else
    echo -e "${RED}[×] Uptime Kuma 启动失败，请检查端口占用 (3001) 或 Docker 日志${RESET}"
    return 1
  fi
}

install_netdata() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 Netdata (实时性能监控)${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  local netdata_err
  if ! netdata_err=$(docker run -d --name=netdata \
    -p 19999:19999 \
    -v netdataconfig:/etc/netdata \
    -v netdatalib:/var/lib/netdata \
    -v netdatacache:/var/cache/netdata \
    -v /etc/passwd:/host/etc/passwd:ro \
    -v /etc/group:/host/etc/group:ro \
    -v /proc:/host/proc:ro \
    -v /sys:/host/sys:ro \
    -v /etc/os-release:/host/etc/os-release:ro \
    --restart unless-stopped \
    --cap-add SYS_PTRACE \
    --security-opt apparmor=unconfined \
    netdata/netdata 2>&1); then
    echo -e "${RED}[×] Netdata 启动失败: $netdata_err${RESET}"
    return 1
  fi

  echo -e "${GREEN}[√] Netdata 已部署。${RESET}"
  echo -e "${YELLOW}访问地址: http://<IP>:19999${RESET}"
}

install_prom_grafana() {
  ensure_docker || return 1

  echo -e "${CYAN}>>> 准备安装 Prometheus + Grafana${RESET}"
  ui_confirm confirm "确认开始部署吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}已取消${RESET}"
    return 0
  fi

  local install_dir="/opt/monitoring"
  mkdir -p "$install_dir/prometheus"
  mkdir -p "$install_dir/grafana/provisioning/datasources"

  # 配置文件生成逻辑保持不变，此处省略详细生成过程以节省篇幅，实际运行时会执行
  cat >"$install_dir/prometheus/prometheus.yml" <<EOF
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
EOF

  cat >"$install_dir/grafana/provisioning/datasources/datasource.yml" <<EOF
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
EOF

  cat >"$install_dir/docker-compose.yml" <<EOF
services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - 9090:9090
    networks:
      - monitoring
  grafana:
    image: grafana/grafana
    container_name: grafana
    restart: unless-stopped
    ports:
      - 3000:3000
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    depends_on:
      - prometheus
    networks:
      - monitoring
volumes:
  grafana_data:
networks:
  monitoring:
EOF

  echo -e "${CYAN}正在启动监控容器...${RESET}"
  if cd "$install_dir" && run_docker_compose up -d; then
    echo -e "${GREEN}[√] 监控平台已部署。${RESET}"
    echo -e "${YELLOW}Grafana: http://<IP>:3000 (admin/admin)${RESET}"
  else
    echo -e "${RED}[×] 启动失败。${RESET}"
    return 1
  fi
}

software_hub() {
  while true; do
    clear
    echo -e "${BOLD}${GREEN}常用软件安装中心 (Docker/Panel)${RESET}"
    echo -e "${CYAN}==================================================${RESET}"

    # 简易检测 Docker 状态
    if command -v docker >/dev/null 2>&1; then
      echo -e "Docker状态: ${GREEN}已安装 $(docker --version | awk '{print $3}' | sed 's/,//')${RESET}"
    else
      echo -e "Docker状态: ${RED}未安装${RESET}"
    fi

    # 检测 Docker Compose 状态 (插件与独立版双向识别)
    if has_docker_compose; then
      local comp_ver
      comp_ver=$(run_docker_compose version 2>/dev/null | head -1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -1)
      echo -e "Compose状态: ${GREEN}已安装 (${comp_ver:-已就绪})${RESET}"
    else
      echo -e "Compose状态: ${RED}未安装${RESET}"
    fi
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    echo -e "${CYAN}--- 环境管理 ---${RESET}"
    echo -e "${YELLOW}  1.${RESET} 安装 Docker 环境"
    echo -e "${YELLOW}  2.${RESET} 卸载 Docker 环境"
    echo -e "${CYAN}--- 面板类 (Script) ---${RESET}"
    echo -e "${YELLOW}  3.${RESET} 安装 X-UI 面板"
    echo -e "${YELLOW}  4.${RESET} 安装 3X-UI 面板"
    echo -e "${CYAN}--- 容器类 (Docker) ---${RESET}"
    echo -e "${YELLOW}  5.${RESET} 安装 Portainer (可视化容器管理)"
    echo -e "${YELLOW}  6.${RESET} 安装 Nginx Proxy Manager (反代神器)"
    echo -e "${YELLOW}  7.${RESET} 安装 FileCodeBox (文件快递柜)"
    echo -e "${YELLOW}  8.${RESET} 安装 1Panel (现代化运维面板)"
    echo -e "${YELLOW}  9.${RESET} 安装 N8n (工作流自动化)"
    echo -e "${CYAN}--- 监控类 (Monitoring) ---${RESET}"
    echo -e "${YELLOW} 10.${RESET} 安装 Uptime Kuma (在线状态监控)"
    echo -e "${YELLOW} 11.${RESET} 安装 Netdata (实时性能监控)"
    echo -e "${YELLOW} 12.${RESET} 安装 Prometheus + Grafana (可视化平台)"
    echo -e "${YELLOW}  0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"

    ui_menu_read sw_choice "请输入选项" "[0-9]|1[0-2]" || return 0
    case $sw_choice in
    1) do_install_docker ;;
    2) do_uninstall_docker ;;
    3) install_xui ;;
    4) install_3xui ;;
    5) install_portainer ;;
    6) install_npm ;;
    7) install_filecodebox ;;
    8) install_1panel ;;
    9) install_n8n ;;
    10) install_uptime_kuma ;;
    11) install_netdata ;;
    12) install_prom_grafana ;;
    0)
      SKIP_PAUSE=true
      return 0
      ;;
    *) echo -e "${RED}无效选项${RESET}" ;;
    esac

    # 子菜单操作完后暂停，方便看日志
    if [ "$SKIP_PAUSE" = false ]; then
      echo -e ""
      ui_pause "按 Enter 继续..."
    else
      SKIP_PAUSE=false
    fi
  done
}

stream_test() {
  echo -e "${CYAN}>>> 准备运行流媒体解锁测试${RESET}"
  ui_confirm confirm "确认开始测试吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}操作已取消${RESET}"
    SKIP_PAUSE=true
    return 0
  fi
  install_deps "curl"
  local temp
  temp=$(mktemp -d) || return 1
  local test_ret=1
  echo -e "${CYAN}>>> 开始流媒体解锁测试...${RESET}"
  if cd "$temp"; then
    if curl -fsSL https://Check.Place -o check.sh && [ -s check.sh ]; then
      bash check.sh -I && test_ret=0 || test_ret=$?
    else
      echo -e "${RED}[×] 测试脚本下载失败，请检查网络连接${RESET}"
    fi
    cd - >/dev/null
  fi
  rm -rf "$temp" 2>/dev/null
  if [ $test_ret -eq 0 ]; then
    echo -e "${GREEN}[√] 流媒体测试完成${RESET}"
    return 0
  else
    echo -e "${RED}[×] 流媒体测试未完成或发生错误 (退出码: $test_ret)${RESET}"
    return $test_ret
  fi
}

net_test() {
  echo -e "${CYAN}>>> 准备运行网络质量测试${RESET}"
  ui_confirm confirm "确认开始测试吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}操作已取消${RESET}"
    SKIP_PAUSE=true
    return 0
  fi
  install_deps "curl"
  local temp
  temp=$(mktemp -d) || return 1
  local test_ret=1
  echo -e "${CYAN}>>> 开始网络质量测试...${RESET}"
  if cd "$temp"; then
    if curl -fsSL https://Check.Place -o check.sh && [ -s check.sh ]; then
      bash check.sh -N && test_ret=0 || test_ret=$?
    else
      echo -e "${RED}[×] 测试脚本下载失败，请检查网络连接${RESET}"
    fi
    cd - >/dev/null
  fi
  rm -rf "$temp" 2>/dev/null
  if [ $test_ret -eq 0 ]; then
    echo -e "${GREEN}[√] 网络质量测试完成${RESET}"
    return 0
  else
    echo -e "${RED}[×] 网络质量测试未完成或发生错误 (退出码: $test_ret)${RESET}"
    return $test_ret
  fi
}

full_test() {
  echo -e "${CYAN}>>> 准备运行融合怪全面测试${RESET}"
  ui_confirm confirm "确认开始测试吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}操作已取消${RESET}"
    SKIP_PAUSE=true
    return 0
  fi
  install_deps "curl"
  
  # [重要] 融合怪包含 fio 磁盘测试，必须在真实磁盘分区运行
  local temp
  temp=$(mktemp -d -p /root) || return 1
  local test_ret=1
  
  echo -e "${CYAN}>>> 开始融合怪全面测试...${RESET}"
  if cd "$temp"; then
    if curl -fsSL https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && [ -s ecs.sh ]; then
      chmod +x ecs.sh
      bash ecs.sh && test_ret=0 || test_ret=$?
    else
      echo -e "${RED}[×] 融合怪测试脚本下载失败，请检查网络连接${RESET}"
    fi
    cd - >/dev/null
  fi
  rm -rf "$temp" 2>/dev/null
  if [ $test_ret -eq 0 ]; then
    echo -e "${GREEN}[√] 融合怪测试完成${RESET}"
    return 0
  else
    echo -e "${RED}[×] 融合怪测试未完成或发生错误 (退出码: $test_ret)${RESET}"
    return $test_ret
  fi
}

benchmark() {
  echo -e "${CYAN}>>> 准备运行服务器性能测试${RESET}"
  ui_confirm confirm "确认开始测试吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}操作已取消${RESET}"
    SKIP_PAUSE=true
    return 0
  fi
  install_deps "curl"
  
  # [重要] yabs.sh 的 fio 测试会检测当前工作目录所在的分区
  # 必须在真实磁盘分区(如 /root)下运行，而非 /tmp (通常是 tmpfs 内存文件系统)
  # 否则 fio 测试结果将是内存速度而非磁盘速度，导致结果偏差巨大
  local temp
  temp=$(mktemp -d -p /root) || return 1
  local test_ret=1
  
  echo -e "${CYAN}>>> 开始服务器性能测试...${RESET}"
  if cd "$temp"; then
    if curl -fsSL https://yabs.sh -o yabs.sh && [ -s yabs.sh ]; then
      chmod +x yabs.sh
      bash yabs.sh && test_ret=0 || test_ret=$?
    else
      echo -e "${RED}[×] 性能测试脚本下载失败，请检查网络连接${RESET}"
    fi
    cd - >/dev/null
  fi
  
  # 清理临时目录
  rm -rf "$temp" 2>/dev/null
  
  if [ $test_ret -eq 0 ]; then
    echo -e "${GREEN}[√] 性能测试完成${RESET}"
    return 0
  else
    echo -e "${RED}[×] 性能测试未完成或发生错误 (退出码: $test_ret)${RESET}"
    return $test_ret
  fi
}

# --- [9. 系统] 深度清理与优化 ---
system_cleanup() {
  echo -e "${CYAN}>>> 准备执行深度系统清理${RESET}"
  echo -e "${YELLOW}将清理: 日志、软件包缓存、孤立包、Docker缓存、临时文件等。${RESET}"
  ui_confirm confirm "确认执行清理吗? [y/N] (q 取消): "
  if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo -e "${YELLOW}操作已取消${RESET}"
    SKIP_PAUSE=true
    return 0
  fi
  echo -e "${BOLD}${CYAN}>>> 正在执行深度系统清理...${RESET}"

  # 初始化总清理大小 (KB)
  total_freed_kb=0

  # 辅助函数：获取目录大小 (KB)
  get_dir_size_kb() {
    local path="$1"
    if [ -d "$path" ]; then
      du -sk "$path" 2>/dev/null | awk '{print $1}'
    else
      echo 0
    fi
  }

  # 辅助函数：格式化大小显示 (KB -> Human Readable)
  format_size() {
    local kb=$1
    if [ "$kb" -lt 1024 ]; then
      echo "${kb}KB"
    elif [ "$kb" -lt 1048576 ]; then
      awk -v k="$kb" 'BEGIN{printf "%.2fMB", k/1024}'
    else
      awk -v k="$kb" 'BEGIN{printf "%.2fGB", k/1024/1024}'
    fi
  }

  # --- [清理 1/7] 包管理器缓存 ---
  echo -e "${YELLOW}[1/7] 清理软件包缓存与残留...${RESET}"
  check_pm_lock || return 1
  
  # [修复] 使用 清理前-清理后 差值计算真实释放量
  local pkg_freed=0
  if [[ "$PKG_MANAGER" == "apt" ]]; then
     local cache_path="/var/cache/apt/archives"
     local size_before=$(get_dir_size_kb "$cache_path")
     do_pkg_clean >/dev/null 2>&1
     local size_after=$(get_dir_size_kb "$cache_path")
     pkg_freed=$((size_before - size_after))
     [ "$pkg_freed" -lt 0 ] && pkg_freed=0
  elif [[ "$PKG_MANAGER" == "dnf5" || "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
     local cache_path="/var/cache/yum"
     [ "$PKG_MANAGER" == "dnf" ] && cache_path="/var/cache/dnf"
     [ "$PKG_MANAGER" == "dnf5" ] && cache_path="/var/cache/libdnf5"
     [ ! -d "$cache_path" ] && [ -d "/var/cache/dnf5" ] && cache_path="/var/cache/dnf5"
     local size_before=$(get_dir_size_kb "$cache_path")
     do_pkg_clean >/dev/null 2>&1
     local size_after=$(get_dir_size_kb "$cache_path")
     pkg_freed=$((size_before - size_after))
     [ "$pkg_freed" -lt 0 ] && pkg_freed=0
  fi
  
  # 其他通用系统临时目录 (安全清理: /var/tmp 排除 systemd-private 保护目录并仅清理过期文件)
  local sys_cleaned=0
  if [ -d "/var/crash" ]; then
    local crash_before=$(get_dir_size_kb "/var/crash")
    rm -rf /var/crash/* 2>/dev/null
    local crash_after=$(get_dir_size_kb "/var/crash")
    local crash_freed=$((crash_before - crash_after))
    [ "$crash_freed" -gt 0 ] && sys_cleaned=$((sys_cleaned + crash_freed))
  fi
  if [ -d "/var/tmp" ]; then
    local vtmp_before=$(get_dir_size_kb "/var/tmp")
    # 仅清理 3 天以上的过期临时文件，使用 -prune 严格排除 systemd-private-* 等活动私有命名空间目录及其子树
    # 使用 -exec rm -f {} + 替代 -delete，避免 GNU find 内部 -delete 隐式启用 -depth 导致 -prune 失效报错
    find /var/tmp -name "systemd-private-*" -prune -o -type f -mtime +3 -exec rm -f {} + 2>/dev/null
    local vtmp_after=$(get_dir_size_kb "/var/tmp")
    local vtmp_freed=$((vtmp_before - vtmp_after))
    [ "$vtmp_freed" -gt 0 ] && sys_cleaned=$((sys_cleaned + vtmp_freed))
  fi
  
  # 合计本项
  step1_total=$((pkg_freed + sys_cleaned))
  total_freed_kb=$((total_freed_kb + step1_total))
  echo -e "  ${GREEN}✓ 包管理器及临时文件已清理 (释放: $(format_size $step1_total))${RESET}"

  # --- [清理 2/7] 残留配置 (仅Debian系) ---
  if [[ "$PKG_MANAGER" == "apt" ]]; then
      echo -e "${YELLOW}[2/7] 扫描已卸载软件的残留配置(RC)...${RESET}"
      rc_count=$(dpkg -l | grep "^rc" | wc -l)
      if [ "$rc_count" -gt 0 ]; then
        dpkg -l | grep "^rc" | awk '{print $2}' | xargs -r apt-get -y purge >/dev/null 2>&1
        echo -e "  ${GREEN}✓ 清理了 $rc_count 个残留配置${RESET}"
      else
        echo -e "  ${GRAY}✓ 无残留配置需清理${RESET}"
      fi
  else
      echo -e "${YELLOW}[2/7] 残留配置清理 (非APT系统跳过)${RESET}"
  fi

  # --- [清理 3/7] 日志文件深度清理 ---
  echo -e "${YELLOW}[3/7] 深度清理系统日志...${RESET}"
  log_freed=0

  # 3.1 截断大日志 (>50MB) - 使用 Delta 计算
  # [说明] 使用 truncate 而非删除，可保持文件句柄，避免影响正在运行的服务
  large_logs=$(find /var/log -type f -name "*.log" -size +50M 2>/dev/null)
  if [ -n "$large_logs" ]; then
    size_before=$(find /var/log -type f -name "*.log" -size +50M -print0 2>/dev/null | xargs -0 -r du -ck 2>/dev/null | tail -1 | awk '{print $1}')
    find /var/log -type f -name "*.log" -size +50M -exec truncate -s 0 {} \; 2>/dev/null
    size_after=$(find /var/log -type f -name "*.log" -size +50M -print0 2>/dev/null | xargs -0 -r du -ck 2>/dev/null | tail -1 | awk '{print $1}')
    
    # 防止空值运算
    : ${size_before:=0} ${size_after:=0}
    
    real_freed=$((size_before - size_after))
    if [ "$real_freed" -gt 0 ]; then
       log_freed=$((log_freed + real_freed))
       echo -e "  ${GREEN}✓ 已截断过大的活动日志文件 (释放: $(format_size $real_freed))${RESET}"
    else
       echo -e "  ${YELLOW}! 大日志截断失败或无需释放${RESET}"
    fi
  fi

  # 3.2 Systemd Journal 优化
  if command -v journalctl >/dev/null 2>&1; then
    # Journalctl vacuum 实际上很难精确计算释放量，因为它管理的是自己的二进制格式
    # 通常 vacuum 会提示 freed amount，但捕获解析比较繁琐。这里保留定性提示。
    journalctl --vacuum-time=1d >/dev/null 2>&1
    echo -e "  ${GREEN}✓ Systemd 日志已优化 (保留最近1天)${RESET}"
  fi

  # 3.3 归档日志清理 (*.gz, *.old) - 使用 Delta 计算
  # 先计算目标文件列表
  target_files=$(find /var/log -type f \( -name "*.gz" -o -name "*.old" -o -name "*.log.*" \))
  if [ -n "$target_files" ]; then
    size_before=$(echo "$target_files" | tr '\n' '\0' | xargs -0 du -ck 2>/dev/null | tail -1 | awk '{print $1}')
    
    # 执行删除
    find /var/log -type f \( -name "*.gz" -o -name "*.old" -o -name "*.log.*" \) -delete 2>/dev/null
    
    # 再次检查剩余文件 (理论上应为空，或者是删不掉的文件)
    files_remaining=$(find /var/log -type f \( -name "*.gz" -o -name "*.old" -o -name "*.log.*" \))
    if [ -n "$files_remaining" ]; then
        size_after=$(echo "$files_remaining" | tr '\n' '\0' | xargs -0 du -ck 2>/dev/null | tail -1 | awk '{print $1}')
    else
        size_after=0
    fi
    
    : ${size_before:=0} ${size_after:=0}
    real_freed=$((size_before - size_after))
    
    if [ "$real_freed" -gt 0 ]; then
        log_freed=$((log_freed + real_freed)) 
        echo -e "  ${GREEN}✓ 归档旧日志已清理 (释放: $(format_size $real_freed))${RESET}"
    elif [ "$size_before" -gt 0 ]; then
        echo -e "  ${RED}! 归档日志清理失败 (权限不足或文件被锁定)${RESET}"
    fi
  fi

  # 3.4 清理/tmp (24小时前) - 使用 Delta 计算 (排除 systemd 私有服务目录)
  target_tmp=$(find /tmp -name "systemd-private-*" -prune -o -type f -mtime +1 -print 2>/dev/null)
  if [ -n "$target_tmp" ]; then
      size_before=$(echo "$target_tmp" | tr '\n' '\0' | xargs -0 du -ck 2>/dev/null | tail -1 | awk '{print $1}')
      
      find /tmp -name "systemd-private-*" -prune -o -type f -mtime +1 -exec rm -f {} + 2>/dev/null
      find /tmp -name "systemd-private-*" -prune -o -type d -empty -not -path "/tmp" -exec rmdir {} + 2>/dev/null
      
      tmp_remaining=$(find /tmp -name "systemd-private-*" -prune -o -type f -mtime +1 -print 2>/dev/null)
      if [ -n "$tmp_remaining" ]; then
         size_after=$(echo "$tmp_remaining" | tr '\n' '\0' | xargs -0 du -ck 2>/dev/null | tail -1 | awk '{print $1}')
      else
         size_after=0
      fi
      
      : ${size_before:=0} ${size_after:=0}
      real_freed=$((size_before - size_after))
      
      if [ "$real_freed" -gt 0 ]; then
         log_freed=$((log_freed + real_freed))
      fi
  fi

  total_freed_kb=$((total_freed_kb + log_freed))
  echo -e "  ${GREEN}✓ 日志与临时文件清理完成 (释放: $(format_size $log_freed))${RESET}"

  # --- [清理 4/7] Snap清理  ---
  if command -v snap >/dev/null 2>&1; then
    echo -e "${YELLOW}[4/7] 正在清理 Snap 旧版本缓存...${RESET}"
    snap_before=$(get_dir_size_kb "/var/lib/snapd/snaps")
    snap set system refresh.retain=2 2>/dev/null
    snap list --all | awk '/disabled/{print $1, $3}' |
      while read snapname revision; do
        snap remove "$snapname" --revision="$revision" >/dev/null 2>&1
      done
    snap_after=$(get_dir_size_kb "/var/lib/snapd/snaps")
    snap_freed=$((snap_before - snap_after))
    if [ "$snap_freed" -lt 0 ]; then snap_freed=0; fi
    total_freed_kb=$((total_freed_kb + snap_freed))
    echo -e "  ${GREEN}✓ Snap 缓存清理完成 (释放: $(format_size $snap_freed))${RESET}"
  else
    echo -e "${YELLOW}[4/7] Snap 未安装，跳过${RESET}"
  fi

  # --- [清理 5/7] 语言环境缓存  ---
  echo -e "${YELLOW}[5/7] 检查编程语言与开发缓存...${RESET}"
  lang_freed=0

  cache_checks=(
    "NPM|npm|$HOME/.npm"
    "Yarn|yarn|$HOME/.cache/yarn"
    "Pip|pip|$HOME/.cache/pip"
    "Go|go|$HOME/go/pkg/mod"
  )

  local found_caches=0 failed_cleans=0

  for check in "${cache_checks[@]}"; do
    IFS='|' read -r name cmd path <<<"$check"
    if command -v "$cmd" >/dev/null 2>&1 && [ -d "$path" ]; then
      local size_before=$(get_dir_size_kb "$path")
      if [ "$size_before" -gt 0 ]; then
        found_caches=$((found_caches + 1))
        rm -rf "${path:?}"/* "${path:?}"/.[!.]* 2>/dev/null
        local size_after=$(get_dir_size_kb "$path")
        local actual_freed=$((size_before - size_after))
        if [ "$actual_freed" -gt 0 ]; then
          lang_freed=$((lang_freed + actual_freed))
          echo -e "  ${GREEN}✓ 清理 $name 缓存 ($(format_size $actual_freed))${RESET}"
        else
          failed_cleans=$((failed_cleans + 1))
          echo -e "  ${YELLOW}! $name 缓存清理受限或未释放空间 (占用: $(format_size $size_before))${RESET}"
        fi
      fi
    fi
  done

  total_freed_kb=$((total_freed_kb + lang_freed))
  if [ "$found_caches" -eq 0 ]; then
    echo -e "  ${GRAY}✓ 无开发环境缓存需清理${RESET}"
  elif [ "$lang_freed" -eq 0 ]; then
    echo -e "  ${YELLOW}! 检测到开发环境缓存，但未能成功释放空间 (可能受权限或进程占用限制)${RESET}"
  fi

  # --- [清理 6/7] 用户级缓存 (安全清理: 重点清理缩略图与 7 天以上过期缓存) ---
  echo -e "${YELLOW}[6/7] 清理用户缩略图与过期缓存...${RESET}"
  user_freed=0
  local all_homes=(/home/* /root)
  for user_home in "${all_homes[@]}"; do
    [ -d "$user_home" ] || continue
    local cache_dir="$user_home/.cache"
    if [ -d "$cache_dir" ]; then
      local s_before=$(get_dir_size_kb "$cache_dir")
      # 1. 彻底清理缩略图缓存
      [ -d "$cache_dir/thumbnails" ] && rm -rf "$cache_dir/thumbnails"/* 2>/dev/null
      # 2. 清理 7 天以上未访问的过期临时文件，杜绝暴力抹杀正在运行的进程 socket
      find "$cache_dir" -type f -atime +7 -delete 2>/dev/null
      local s_after=$(get_dir_size_kb "$cache_dir")
      local s_diff=$((s_before - s_after))
      [ "$s_diff" -gt 0 ] && user_freed=$((user_freed + s_diff))
    fi
  done

  total_freed_kb=$((total_freed_kb + user_freed))
  echo -e "  ${GREEN}✓ 用户缓存清理完成 (释放: $(format_size $user_freed))${RESET}"
  
  # --- [清理 7/7] Docker 构建缓存 ---
  echo -e "${YELLOW}[7/7] 检查 Docker 构建缓存...${RESET}"
  if command -v docker >/dev/null 2>&1 && systemctl is-active docker >/dev/null 2>&1; then
     # 辅助函数：将 Docker 输出的大小精确转换为 KB (严格区分十进制 SI 与二进制 IEC 单位并四舍五入)
     parse_size_to_kb() {
       local size_str="$1"
       [ -z "$size_str" ] && echo 0 && return
       local num=$(echo "$size_str" | grep -oE '[0-9]+\.?[0-9]*')
       local unit=$(echo "$size_str" | grep -oE '[A-Za-z]+')
       [ -z "$num" ] && echo 0 && return
       case "$unit" in
         B|b|Bytes)   awk -v n="$num" 'BEGIN{print int(n / 1024 + 0.5)}' ;;
         kB|KB)       awk -v n="$num" 'BEGIN{print int(n * 1000 / 1024 + 0.5)}' ;;
         KiB)         awk -v n="$num" 'BEGIN{print int(n + 0.5)}' ;;
         MB)          awk -v n="$num" 'BEGIN{print int(n * 1000 * 1000 / 1024 + 0.5)}' ;;
         MiB)         awk -v n="$num" 'BEGIN{print int(n * 1024 + 0.5)}' ;;
         GB)          awk -v n="$num" 'BEGIN{print int(n * 1000 * 1000 * 1000 / 1024 + 0.5)}' ;;
         GiB)         awk -v n="$num" 'BEGIN{print int(n * 1024 * 1024 + 0.5)}' ;;
         TB)          awk -v n="$num" 'BEGIN{print int(n * 1000 * 1000 * 1000 * 1000 / 1024 + 0.5)}' ;;
         TiB)         awk -v n="$num" 'BEGIN{print int(n * 1024 * 1024 * 1024 + 0.5)}' ;;
         *)           awk -v n="$num" 'BEGIN{print int(n + 0.5)}' ;;
       esac
     }
     
     local docker_freed=0
     
     echo -e "  ${CYAN}正在执行 docker builder prune (构建缓存)...${RESET}"
     builder_out=$(docker builder prune --all --force 2>&1)
     builder_reclaimed=$(echo "$builder_out" | grep "Total reclaimed space" | awk -F': ' '{print $2}')
     if [ -n "$builder_reclaimed" ]; then
        echo -e "    -> 释放: ${GREEN}${builder_reclaimed}${RESET}"
        docker_freed=$((docker_freed + $(parse_size_to_kb "$builder_reclaimed")))
     fi
     
     echo -e "  ${CYAN}正在执行 docker image prune (悬空镜像)...${RESET}"
     image_out=$(docker image prune --force 2>&1)
     image_reclaimed=$(echo "$image_out" | grep "Total reclaimed space" | awk -F': ' '{print $2}')
     if [ -n "$image_reclaimed" ]; then
        echo -e "    -> 释放: ${GREEN}${image_reclaimed}${RESET}"
        docker_freed=$((docker_freed + $(parse_size_to_kb "$image_reclaimed")))
     fi
     
     total_freed_kb=$((total_freed_kb + docker_freed))
     echo -e "  ${GREEN}✓ Docker 清理完成${RESET}"
  else
     echo -e "  ${GRAY}✓ Docker 未运行或未安装，跳过${RESET}"
  fi

  # --- 总结报告 ---
  echo -e "${BOLD}${CYAN}--------------------------------------------------${RESET}"
  echo -e "${BOLD}${GREEN}系统清理全部完成！${RESET}"
  echo -e "${BOLD}共计释放空间: ${YELLOW}$(format_size $total_freed_kb)${RESET}"

  df -h / | tail -1 | awk -v G="${GREEN}" -v R="${RESET}" '{printf "%s当前磁盘剩余空间: %s (使用率: %s)%s\n", G, $4, $5, R}'
  echo -e "${BOLD}${CYAN}--------------------------------------------------${RESET}"
}

# ---------- Fail2Ban 管理 (逻辑闭环: 白名单子菜单 + 状态全显 + 交互优化) ----------
backup_source_transaction() {
  local backup file
  backup=$(mktemp -d /var/tmp/aurora-source-backup.XXXXXX) || return 1
  : > "$backup/manifest" || return 1
  for file in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources /etc/apt/apt.conf.d/99-archive-valid-until.conf /etc/yum.repos.d/*.repo; do
    if [ -L "$file" ]; then
      echo "Source symlink requires manual review: $file" >&2
      return 1
    fi
    [ -f "$file" ] || continue
    cp --parents -p "$file" "$backup" || return 1
    printf 'existing|%s\n' "$file" >> "$backup/manifest" || return 1
  done
  for file in /etc/apt/sources.list /etc/apt/sources.list.d/pve-no-subscription.list /etc/apt/apt.conf.d/99-archive-valid-until.conf; do
    [ -e "$file" ] || printf 'absent|%s\n' "$file" >> "$backup/manifest" || return 1
  done
  printf '%s\n' "$backup"
}

rollback_source_transaction() {
  local backup="$1" state file failed=false
  [ -f "$backup/manifest" ] || return 1
  while IFS='|' read -r state file; do
    if [ "$state" = existing ]; then
      if ! cmp -s "$backup$file" "$file"; then
        cp -p "$backup$file" "$file" || failed=true
      fi
    elif [ "$state" = absent ]; then
      rm -f "$file" || failed=true
    fi
  done < "$backup/manifest"
  if [ "$failed" = true ]; then
    echo -e "${RED}[×] 源配置回滚不完整，备份保留: $backup${RESET}"
    return 1
  fi
  echo -e "${YELLOW}[!] 已恢复修改前的源配置，备份保留: $backup${RESET}"
}

rewrite_apt_mirror_file() {
  python3 - "$1" "$2" "$3" "$OS_ID" <<'PY_APT_MIRROR'
import os, pathlib, re, shutil, sys, urllib.parse
path, main_uri, security_uri, distribution = sys.argv[1:]
path = pathlib.Path(path)
with open(str(path), 'r', encoding='utf-8', errors='replace') as f:
    text = f.read()
known = {'deb.debian.org', 'security.debian.org', 'archive.debian.org',
         'archive.ubuntu.com', 'security.ubuntu.com', 'ports.ubuntu.com', 'old-releases.ubuntu.com',
         'mirrors.aliyun.com', 'mirrors.tuna.tsinghua.edu.cn', 'mirrors.cloud.tencent.com',
         'mirrors.ustc.edu.cn'}
count = 0
def replace_uri(uri, security_suite=False):
    global count
    parsed = urllib.parse.urlsplit(uri)
    if parsed.scheme not in ('http', 'https') or not (parsed.hostname in known or (parsed.hostname or '').endswith('.archive.ubuntu.com')):
        return uri
    base = parsed.path.rstrip('/')
    allowed = ('/debian', '/debian-security', '/debian-archive/debian', '/debian-archive/debian-security') if distribution == 'debian' else ('/ubuntu', '/ubuntu-ports')
    if base not in allowed and not (distribution == 'debian' and (base.endswith('/debian') or base.endswith('/debian-security'))):
        return uri
    count += 1
    return security_uri if (base in ('/debian-security', '/debian-archive/debian-security') or parsed.hostname == 'security.ubuntu.com' or security_suite) else main_uri
deprecated_updates = {'jessie-updates', 'stretch-updates', 'wheezy-updates', 'squeeze-updates'}
def is_debian_archive(target_uri):
    parsed = urllib.parse.urlsplit(target_uri)
    return parsed.hostname == 'archive.debian.org' or parsed.path.startswith('/debian-archive')

out = []
if path.suffix == '.sources':
    for stanza in re.split(r'(\n[ \t]*\n)', text):
        suites = re.search(r'^Suites:[ \t]*(.*)$', stanza, re.M | re.I)
        security_suite = bool(suites and all(s.endswith('-security') or s.endswith('/updates') for s in suites.group(1).split()))
        stanza = re.sub(r'^(URIs:[ \t]*)(.*)$', lambda m: m.group(1) + re.sub(r'\S+', lambda u: replace_uri(u.group(), security_suite), m.group(2)), stanza, flags=re.M | re.I)
        uris_match = re.search(r'^URIs:[ \t]*(.*)$', stanza, re.M | re.I)
        if uris_match and any(is_debian_archive(u) for u in uris_match.group(1).split()):
            stanza_disabled = False
            if suites:
                current_suites = suites.group(1).split()
                filtered_suites = [s for s in current_suites if s not in deprecated_updates]
                if filtered_suites != current_suites:
                    if filtered_suites:
                        stanza = re.sub(r'^(Suites:[ \t]*).*$', r'\1' + ' '.join(filtered_suites), stanza, flags=re.M | re.I)
                    else:
                        stanza = '\n'.join('# ' + l if l.strip() else l for l in stanza.splitlines()) + '\n'; stanza_disabled = True
            if not stanza_disabled:
                if re.search(r'^Check-Valid-Until:', stanza, re.M | re.I): stanza = re.sub(r'^Check-Valid-Until:.*$', 'Check-Valid-Until: no', stanza, flags=re.M | re.I)
                else: stanza = stanza.rstrip('\n') + '\nCheck-Valid-Until: no\n'
        out.append(stanza)
else:
    for line in text.splitlines(keepends=True):
        match = re.match(r'^(\s*deb(?:-src)?\s+(?:\[[^\]]*\]\s+)?)(\S+)(\s+)(\S+)', line)
        if match:
            suite = match.group(4)
            uri = replace_uri(match.group(2), suite.endswith('-security') or suite.endswith('/updates'))
            line = line[:match.start(2)] + uri + line[match.end(2):]
            if is_debian_archive(uri):
                if suite in deprecated_updates:
                    line = '# ' + line.lstrip()
                else:
                    if '[' in match.group(1):
                        line = re.sub(r'\[([^\]]*)\]', lambda m: '[' + re.sub(r'\bcheck-valid-until=\S+\s*', '', m.group(1)).rstrip() + ' check-valid-until=no]', line, count=1)
                    else:
                        line = re.sub(r'^(\s*deb(?:-src)?\s+)', r'\1[check-valid-until=no] ', line, count=1)
        out.append(line)
if not count:
    sys.exit(2)
updated = ''.join(out)
backup = str(path) + '.aurora-backup.' + str(os.getpid())
shutil.copy2(str(path), backup)
tmp = str(path) + '.tmp.' + str(os.getpid())
with open(tmp, 'w', encoding='utf-8') as f:
    f.write(updated)
shutil.copystat(str(path), tmp)
os.replace(tmp, str(path))
PY_APT_MIRROR
}

persist_aurora_nft_bans() {
  local config="/etc/aurora-nft-bans.nft" unit="/etc/systemd/system/aurora-nft-bans.service"
  local nft_bin
  nft_bin=$(command -v nft) || return 1
  if [ -f "$config" ] && ! grep -qF '# Aurora managed nft bans' "$config"; then return 1; fi
  if [ -f "$unit" ] && ! grep -qF '# Aurora managed nft bans' "$unit"; then return 1; fi
  printf '%s\n' '# Aurora managed nft bans' > "${config}.tmp.$$" || return 1
  nft list table inet aurora_bans >> "${config}.tmp.$$" || return 1
  chmod 600 "${config}.tmp.$$" && mv -f "${config}.tmp.$$" "$config" || return 1
  cat > "${unit}.tmp.$$" <<EOF || return 1
# Aurora managed nft bans
[Unit]
Description=Aurora owned nftables bans
After=nftables.service
PartOf=nftables.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-$nft_bin delete table inet aurora_bans
ExecStart=$nft_bin -f $config
[Install]
WantedBy=multi-user.target
EOF
  chmod 644 "${unit}.tmp.$$" && mv -f "${unit}.tmp.$$" "$unit" || return 1
  systemctl daemon-reload && systemctl enable aurora-nft-bans.service
}

add_aurora_nft_ban() {
  local ip="$1" proto=ip
  validate_ip "$ip" || return 1
  [[ "$ip" == *:* ]] && proto=ip6
  if nft list table inet aurora_bans >/dev/null 2>&1; then
    grep -qF '# Aurora managed nft bans' /etc/aurora-nft-bans.nft 2>/dev/null || {
      echo -e "${RED}[×] aurora_bans 表归属不明，未修改${RESET}"; return 1;
    }
  else
    nft add table inet aurora_bans || return 1
  fi
  if ! nft list chain inet aurora_bans input >/dev/null 2>&1; then
    nft 'add chain inet aurora_bans input { type filter hook input priority -10; policy accept; }' || return 1
  fi
  nft add rule inet aurora_bans input "$proto" saddr "$ip" drop comment '"aurora-permanent-ban"'
}

list_aurora_nft_handles() {
  command -v python3 >/dev/null 2>&1 || return 1
  nft -j list ruleset | python3 -c '
import ipaddress, json, re, sys
target = ipaddress.ip_address(sys.argv[1])
proto = "ip" if target.version == 4 else "ip6"
for entry in json.load(sys.stdin).get("nftables", []):
    rule = entry.get("rule", {})
    if not rule or not ((rule.get("family") == "inet" and rule.get("table") == "aurora_bans") or rule.get("comment") == "aurora-permanent-ban"):
        continue
    expr = rule.get("expr", [])
    if not any("drop" in part for part in expr):
        continue
    for part in expr:
        match = part.get("match", {})
        left = match.get("left", {})
        if not isinstance(left, dict):
            continue
        left = left.get("payload", {})
        if match.get("op") != "==" or left.get("protocol") != proto or left.get("field") != "saddr":
            continue
        try:
            same = ipaddress.ip_address(match.get("right")) == target
        except ValueError:
            same = False
        if same:
            names = [rule.get(k, "") for k in ("family", "table", "chain")]
            handle = str(rule.get("handle", ""))
            if all(re.fullmatch(r"[A-Za-z0-9_.-]+", name) for name in names) and handle.isdigit():
                print(*names, handle)
            break
' "$1"
}

manage_fail2ban() {
  # [逻辑] 检测 Fail2Ban 是否已安装 (使用包管理器判断避免残留误判)
  check_f2b_installed() {
    if [[ "$PKG_MANAGER" == "apt" ]]; then
      dpkg -l fail2ban 2>/dev/null | grep -q "^ii" && return 0
    elif [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "dnf5" || "$PKG_MANAGER" == "yum" ]]; then
      rpm -q fail2ban >/dev/null 2>&1 && return 0
    fi
    # 兜底：检查服务单元文件是否存在
    systemctl cat fail2ban >/dev/null 2>&1 && return 0
    return 1
  }

  # [逻辑] 检测服务守护进程状态
  check_f2b_running() {
    check_f2b_installed || return 1
    systemctl is-active fail2ban >/dev/null 2>&1
  }

  # [逻辑] 检测特定 Jail 运行状态
  check_jail_status() {
    local jail_name=$1
    if check_f2b_running; then
      if fail2ban-client status "$jail_name" >/dev/null 2>&1; then return 0; fi
    fi
    return 1
  }

  local aurora_jail_dir="/etc/fail2ban/jail.d"
  local sshd_conf="$aurora_jail_dir/00-aurora-sshd.local"
  local recidive_conf="$aurora_jail_dir/00-aurora-recidive.local"

  # [逻辑] 补全并更新参数 (独立 drop-in 配置，动态检测真实 SSH 端口，兼容 systemd-journald 与 nftables)
  ensure_jail_params() {
    local jail=$1
    mkdir -p "$aurora_jail_dir"

    # 检测系统日志源
    local use_systemd=false
    if [ ! -f /var/log/auth.log ] && [ ! -f /var/log/secure ]; then
      use_systemd=true
    fi

    # 动作适配 (优先 nftables)
    local f2b_banaction="iptables-multiport"
    if ! command -v iptables >/dev/null 2>&1 && command -v nft >/dev/null 2>&1; then
      f2b_banaction="nftables-multiport"
    fi

    # 动态获取实际监听端口
    local real_ssh_port=$(get_actual_ssh_port)
    local port_str="$real_ssh_port"
    [ "$real_ssh_port" != "22" ] && port_str="$real_ssh_port,ssh"

    case $jail in
    "sshd")
      if [ ! -f "$sshd_conf" ]; then
        if [ "$use_systemd" = true ]; then
          cat >"$sshd_conf" <<EOF
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1

[sshd]
enabled = true
port = $port_str
backend = systemd
maxretry = 5
bantime = 600
banaction = $f2b_banaction
EOF
        else
          local ssh_logpath="/var/log/auth.log"
          if [[ "$OS_FAMILY" == "rhel" ]]; then
            ssh_logpath="/var/log/secure"
          fi
          cat >"$sshd_conf" <<EOF
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1

[sshd]
enabled = true
port = $port_str
logpath = $ssh_logpath
filter = sshd
maxretry = 5
bantime = 600
banaction = $f2b_banaction
EOF
        fi
      else
        # 若配置已存在，动态校准其端口定义
        if grep -q "^port" "$sshd_conf"; then
          sed -i "s|^port[[:space:]]*=.*|port = $port_str|" "$sshd_conf"
        else
          sed -i "/^\[sshd\]/a port = $port_str" "$sshd_conf"
        fi
      fi
      ;;
    "recidive")
      if [ ! -f "$recidive_conf" ]; then
        cat >"$recidive_conf" <<EOF
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = $f2b_banaction
bantime = 604800
findtime = 86400
maxretry = 3
EOF
      fi
      ;;
    esac

    # 兼容性迁移：自动将历史遗留的带单位参数清洗为全版本通用的纯秒数
    if [ -f "$sshd_conf" ]; then
      sed -i -E 's/^[[:space:]]*bantime[[:space:]]*=[[:space:]]*10m[[:space:]]*$/bantime = 600/' "$sshd_conf"
    fi
    if [ -f "$recidive_conf" ]; then
      sed -i -E 's/^[[:space:]]*bantime[[:space:]]*=[[:space:]]*1w[[:space:]]*$/bantime = 604800/' "$recidive_conf"
      sed -i -E 's/^[[:space:]]*findtime[[:space:]]*=[[:space:]]*1d[[:space:]]*$/findtime = 86400/' "$recidive_conf"
    fi
  }

  # [逻辑] 配置文件状态写入函数
  set_jail_config() {
    local jail=$1
    local state=$2
    ensure_jail_params "$jail"

    case $jail in
    "sshd")
      if grep -q "^enabled" "$sshd_conf"; then
        sed -i "s|^enabled[[:space:]]*=.*|enabled = $state|" "$sshd_conf"
      else
        sed -i "/^\[sshd\]/a enabled = $state" "$sshd_conf"
      fi
      ;;
    "recidive")
      if grep -q "^enabled" "$recidive_conf"; then
        sed -i "s|^enabled[[:space:]]*=.*|enabled = $state|" "$recidive_conf"
      else
        sed -i "/^\[recidive\]/a enabled = $state" "$recidive_conf"
      fi
      ;;
    esac
  }

  while true; do
    clear

    # --- 状态检测逻辑 (区分: 未安装 / 未运行 / 运行中) ---
    local is_installed=true
    if ! check_f2b_installed; then
      # 未安装
      f2b_state="${GRAY}未安装${RESET}"
      is_running=false
      is_installed=false
      ssh_display="${GRAY}[未安装]${RESET} 开启 SSH 保护"
      rec_display="${GRAY}[未安装]${RESET} 开启 顽固监狱"
    elif check_f2b_running; then
      # 运行中
      f2b_state="${GREEN}运行中 (Active)${RESET}"
      is_running=true

      # SSH 保护状态
      if check_jail_status "sshd"; then
        ssh_display="${GREEN}[已开启]${RESET} 关闭 SSH 保护"
        ssh_status="on"
      else
        ssh_display="${GRAY}[已关闭]${RESET} 开启 SSH 保护"
        ssh_status="off"
      fi

      # 顽固监狱状态
      if check_jail_status "recidive"; then
        rec_display="${GREEN}[已开启]${RESET} 关闭 顽固监狱 (Recidive)"
        rec_status="on"
      else
        rec_display="${GRAY}[已关闭]${RESET} 开启 顽固监狱 (Recidive)"
        rec_status="off"
      fi
    else
      # 已安装但未运行
      f2b_state="${YELLOW}未运行${RESET}"
      is_running=false
      ssh_display="${GRAY}[未运行]${RESET} 开启 SSH 保护"
      rec_display="${GRAY}[未运行]${RESET} 开启 顽固监狱"
    fi

    db_file="/var/lib/fail2ban/fail2ban.sqlite3"
    db_size="N/A"
    [ -f "$db_file" ] && db_size=$(ls -lh "$db_file" | awk '{print $5}')

    # --- 菜单显示 ---
    echo -e "${BOLD}${CYAN}Fail2Ban 防爆破高级管理${RESET}"
    echo -e "${CYAN}==================================================${RESET}"
    echo -e "Fail2Ban状态: $f2b_state"
    echo -e "数据库占用: ${YELLOW}$db_size${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    if [ "$is_installed" = false ]; then
      echo -e "${YELLOW}  1.${RESET} 安装 Fail2Ban"
      echo -e "${GRAY}  2. 停用服务 (需先安装)${RESET}"
    elif [ "$is_running" = true ]; then
      echo -e "${GREEN}  1.${RESET} 重启 Fail2Ban 服务"
      echo -e "${YELLOW}  2.${RESET} 停用 Fail2Ban 服务"
    else
      echo -e "${YELLOW}  1.${RESET} 启动 Fail2Ban 服务"
      echo -e "${GRAY}  2. 停用服务 (已停用)${RESET}"
    fi
    echo -e "${YELLOW}  3.${RESET} ${RED}卸载 Fail2Ban${RESET}"

    echo -e "${CYAN}--- 监控与操作 ---${RESET}"
    echo -e "${YELLOW}  4.${RESET} 查看 SSH 封禁列表"
    echo -e "${YELLOW}  5.${RESET} 手动封禁 IP (Ban IP) ${GREEN}[增强版]${RESET}"
    echo -e "${YELLOW}  6.${RESET} 手动解封 IP (Unban) ${GREEN}[列表回显]${RESET}"
    echo -e "${YELLOW}  7.${RESET} 白名单管理 (Whitelist) ${GREEN}[独立子菜单]${RESET}"
    echo -e "${YELLOW}  8.${RESET} 查看详细运行日志"

    echo -e "${CYAN}--- 策略与维护 ---${RESET}"
    if [ "$is_running" = true ]; then
      echo -e "${YELLOW}  9.${RESET} 修改 SSH 封禁策略 (次数/时长)"
      echo -e "${YELLOW} 10.${RESET} $rec_display"
      echo -e "${YELLOW} 11.${RESET} $ssh_display"
      echo -e "${YELLOW} 12.${RESET} ${RED}清理数据库 (当前: $db_size)${RESET}"
    else
      echo -e "${GRAY}  9. 修改封禁策略 (需启动服务)${RESET}"
      echo -e "${GRAY} 10. $rec_display${RESET}"
      echo -e "${GRAY} 11. $ssh_display${RESET}"
      echo -e "${GRAY} 12. 清理数据库 (需启动服务)${RESET}"
    fi

    echo -e "${YELLOW}  0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"

    ui_menu_read f_choice "请输入选项" "[0-9]|1[0-2]" || return 0

    case $f_choice in
    1)
      if [ "$is_installed" = false ]; then
        # 未安装 -> 安装
        check_pm_lock || return 1
        echo -e "${CYAN}正在安装 Fail2Ban...${RESET}"
        if [[ "$OS_FAMILY" == "rhel" ]]; then
          if ! rpm -q epel-release >/dev/null 2>&1; then
            echo -e "${CYAN}检测到 RHEL 系系统，正在安装 EPEL 仓库以获取 Fail2Ban...${RESET}"
            $PKG_INSTALL epel-release 2>/dev/null || true
          fi
        fi
        $PKG_UPDATE
        if ! $PKG_INSTALL fail2ban python3-systemd; then
          echo -e "${RED}[×] Fail2Ban 或 systemd 日志依赖安装失败，停止配置${RESET}"
          continue
        fi
        ensure_jail_params "sshd"
        set_jail_config "sshd" "true"
        fail2ban-client -t && systemctl enable fail2ban && systemctl restart fail2ban &&
          fail2ban-client status sshd >/dev/null || { echo -e "${RED}[×] Fail2Ban 配置或启动验证失败${RESET}"; continue; }
        echo -e "${GREEN}[√] 安装并启动成功${RESET}"
        sleep 3
      elif [ "$is_running" = true ]; then
        # 运行中 -> 重启
        ensure_jail_params "migrate"; echo -e "${CYAN}正在重启服务...${RESET}"
        fail2ban-client -t && systemctl restart fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 重启失败${RESET}"; continue; }
        echo -e "${GREEN}[√] 服务已重启${RESET}"
        sleep 2
      else
        # 已安装未运行 -> 启动
        ensure_jail_params "migrate"; echo -e "${CYAN}正在启动服务...${RESET}"
        fail2ban-client -t && systemctl start fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 启动验证失败${RESET}"; continue; }
        echo -e "${GREEN}[√] 服务已启动${RESET}"
        sleep 2
      fi
      ;;
    2)
      # 停用服务
      if [ "$is_installed" = false ]; then
        echo -e "${YELLOW}[!] Fail2Ban 尚未安装${RESET}"
        sleep 1
        continue
      fi
      if [ "$is_running" = false ]; then
        echo -e "${YELLOW}[!] 服务已是停用状态${RESET}"
        sleep 1
        continue
      fi
      echo -e "${CYAN}正在停用服务...${RESET}"
      systemctl stop fail2ban
      echo -e "${GREEN}[√] 服务已停用 (配置保留)${RESET}"
      sleep 2
      ;;
    3)
      if [ "$is_installed" = false ]; then
        echo -e "${YELLOW}[!] Fail2Ban 尚未安装${RESET}"
        sleep 1
        continue
      fi
      echo -e "${RED}警告: 即将完全卸载 Fail2Ban (包括所有配置和数据)。${RESET}"
      ui_confirm c "确认? [y/N] (q 取消): "
      if [[ "$c" =~ ^[Yy]$ ]]; then
        echo -e "${CYAN}正在停止服务...${RESET}"
        systemctl stop fail2ban 2>/dev/null
        systemctl disable fail2ban 2>/dev/null
        check_pm_lock || return 1
        echo -e "${CYAN}正在卸载软件包...${RESET}"
        # 使用 purge 彻底清除 (apt) 或标准 remove (yum/dnf)
        if [[ "$PKG_MANAGER" == "apt" ]]; then
          apt-get purge -y fail2ban 2>/dev/null
          apt-get autoremove -y 2>/dev/null
        else
          $PKG_REMOVE fail2ban
        fi
        echo -e "${CYAN}正在清理残留文件...${RESET}"
        rm -rf /etc/fail2ban /var/lib/fail2ban /var/log/fail2ban.log*
        # 刷新命令缓存，避免 command -v 误判
        hash -r 2>/dev/null
        echo -e "${GREEN}[√] 卸载完成，已清理所有配置和数据${RESET}"
        sleep 2
      fi
      ;;
    4)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      echo -e "${CYAN}SSH 监狱:${RESET}"
      fail2ban-client status sshd 2>/dev/null || echo "未启动"
      if check_jail_status "recidive"; then
        echo -e "\n${CYAN}顽固监狱:${RESET}"
        fail2ban-client status recidive
      fi
      ui_pause "按 Enter 继续..."
      ;;
    5)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      ui_read_field ip "封禁 IP" validate_ip "" true || continue
      [ "$ip" == "0" ] || [ -z "$ip" ] && continue
      validate_ip "$ip" || { echo -e "${RED}[×] 非法 IP 地址${RESET}"; continue; }
      echo -e "  1. 临时封禁 (Fail2Ban Jail - 随预设时间自动释放)\n  2. 永久封禁 (底层防火墙持久拦截规则)"
      ui_menu_read m "封禁方式" "[0-2]" || return 0
      [ "$m" = 0 ] && continue
      local is_v6=false
      [[ "$ip" =~ : ]] && is_v6=true

      case $m in
      1) 
        fail2ban-client set sshd banip "$ip" 2>/dev/null && echo -e "${GREEN}[√] 已加入 Fail2Ban SSH 临时封禁${RESET}" || echo -e "${RED}[!] 加入 Jail 失败${RESET}"
        ;;
      2) 
        local ban_success=false
        local ban_persisted=false
        local ban_backend_info=""

        # 1. UFW 持久规则
        if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qE "^Status:[[:space:]]*active"; then
          if ufw insert 1 deny from "$ip" to any >/dev/null 2>&1; then
            echo -e "${GREEN}[√] 已添加 UFW 永久拦截规则${RESET}"
            ban_success=true
            ban_backend_info="UFW"
            ban_persisted=true
          else
            echo -e "${RED}[!] UFW 规则添加失败${RESET}"
          fi
        # 2. Firewalld 持久 Rich Rule (遍历活跃 zones 并校验 reload)
        elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
          local fam="ipv4"
          [ "$is_v6" = true ] && fam="ipv6"
          local fw_zones
          fw_zones=$(firewall-cmd --get-active-zones 2>/dev/null | grep -v '^[[:space:]]' | awk '{print $1}')
          [ -z "$fw_zones" ] && fw_zones=$(firewall-cmd --get-default-zone 2>/dev/null)
          [ -z "$fw_zones" ] && fw_zones="public"
          
          local fw_zone_added=true
          for z in $fw_zones; do
            if firewall-cmd --permanent --zone="$z" --add-rich-rule="rule family='${fam}' source address='${ip}' drop" >/dev/null 2>&1; then
              fw_zone_added=true
            else
              fw_zone_added=false
              break
            fi
          done
          if [ "$fw_zone_added" = true ]; then
            if firewall-cmd --reload >/dev/null 2>&1; then
              echo -e "${GREEN}[√] 已添加 Firewalld 永久 Rich Rule 拦截规则 (Zone: $fw_zones)${RESET}"
              ban_success=true
              ban_backend_info="Firewalld ($fw_zones)"
              ban_persisted=true
            else
              echo -e "${RED}[!] Firewalld reload 失败，规则可能未生效${RESET}"
            fi
          else
            echo -e "${RED}[!] Firewalld rich rule 写入失败${RESET}"
          fi
        # 3. nftables 真实后端与链动态探测 (支持 inet/ip/ip6 家族)
        elif command -v nft >/dev/null 2>&1; then
          if add_aurora_nft_ban "$ip"; then
            ban_success=true
            ban_backend_info="nftables (inet aurora_bans input)"
            if persist_aurora_nft_bans; then
              ban_persisted=true
            else
              echo -e "${YELLOW}[!] 运行规则已生效，但独立加载服务配置失败，不能保证重启后封禁${RESET}"
            fi
          fi
        # 4. iptables / ip6tables (严格检查插入命令退出码)
        elif command -v iptables >/dev/null 2>&1 || command -v ip6tables >/dev/null 2>&1; then
          local ipt_rc=1
          if [ "$is_v6" = true ] && command -v ip6tables >/dev/null 2>&1; then
            ip6tables -I INPUT 1 -s "$ip" -j DROP 2>/dev/null
            ipt_rc=$?
          elif [ "$is_v6" = false ] && command -v iptables >/dev/null 2>&1; then
            iptables -I INPUT 1 -s "$ip" -j DROP 2>/dev/null
            ipt_rc=$?
          fi

          if [ $ipt_rc -eq 0 ]; then
            echo -e "${GREEN}[√] 已添加 iptables 链首 DROP 拦截规则${RESET}"
            ban_success=true
            ban_backend_info="iptables"
            # 尝试持久化保存
            if command -v netfilter-persistent >/dev/null 2>&1; then
              if netfilter-persistent save && systemctl is-enabled netfilter-persistent >/dev/null 2>&1; then
                ban_persisted=true
              else
                echo -e "${YELLOW}[!] 持久化保存或加载服务不可用，重启后需核验${RESET}"
              fi
            elif command -v iptables-save >/dev/null 2>&1 && [ -d /etc/iptables ]; then
              iptables-save > /etc/iptables/rules.v4 2>/dev/null || true
              [ "$is_v6" = true ] && command -v ip6tables-save >/dev/null 2>&1 && ip6tables-save > /etc/iptables/rules.v6 2>/dev/null || true
            fi
          else
            echo -e "${RED}[!] iptables 规则插入失败${RESET}"
          fi
        fi
        
        # 兼容传统 hosts.deny
        local escaped_ip
        escaped_ip=$(echo "$ip" | sed 's/\./\\./g')
        if [ -f /etc/hosts.deny ] && ! awk -v ip="$ip" '$1 == "ALL:" && $2 == ip {found=1} END {exit !found}' /etc/hosts.deny; then
          echo "ALL: $ip # Aurora" >>/etc/hosts.deny
          echo -e "${GREEN}[√] 已将 IP 写入 /etc/hosts.deny${RESET}"
        fi

        if [ "$ban_success" = true ]; then
          if [ "$ban_persisted" = true ]; then
            echo -e "${GREEN}[√] IP $ip 已写入活动及持久封禁配置 (${ban_backend_info})，重启加载仍需验收${RESET}"
          else
            echo -e "${YELLOW}[!] IP $ip 仅确认活动规则已生效 (${ban_backend_info})，未确认永久封禁${RESET}"
          fi
        else
          echo -e "${RED}[×] 警告: 未能将 IP $ip 写入任何活动的底层防火墙 (UFW/Firewalld/nftables/iptables 未启用或写入失败)${RESET}"
        fi
        ;;
      esac
      ui_pause "按 Enter 继续..."
      ;;
    6)
      check_f2b_running || continue
      # 展示当前被封禁的 IP
      echo -e "${CYAN}>>> 当前被封禁的 IP (Fail2Ban SSH Jail):${RESET}"
      fail2ban-client status sshd 2>/dev/null | grep "Banned IP list:" | sed 's/Banned IP list://g' | xargs -n 5
      echo -e "${CYAN}--------------------------${RESET}"

      ui_read_field ip "请输入要解封的 IP" validate_ip "" true || continue
      [ "$ip" != "0" ] && [ -n "$ip" ] && {
        validate_ip "$ip" || { echo -e "${RED}[×] 非法 IP 地址${RESET}"; continue; }
        local is_v6=false
        [[ "$ip" =~ : ]] && is_v6=true
        local unban_count=0
        local unban_details=()

        # 1. 解除 Fail2Ban Jail (sshd 与 recidive)
        if fail2ban-client status sshd 2>/dev/null | grep -q "$ip"; then
          if fail2ban-client set sshd unbanip "$ip" 2>/dev/null; then
            ((unban_count++))
            unban_details+=("Fail2Ban sshd")
          fi
        fi
        if fail2ban-client status recidive 2>/dev/null | grep -q "$ip"; then
          if fail2ban-client set recidive unbanip "$ip" 2>/dev/null; then
            ((unban_count++))
            unban_details+=("Fail2Ban recidive")
          fi
        fi
        
        # 2. 对称撤销底层防火墙规则 (UFW / Firewalld / nftables / iptables)
        # UFW (循环删除直到返回非0，确保重复规则清空)
        if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qE "^Status:[[:space:]]*active"; then
          local ufw_deleted=false
          while ufw delete deny from "$ip" to any >/dev/null 2>&1; do
            ufw_deleted=true
            ((unban_count++))
          done
          [ "$ufw_deleted" = true ] && unban_details+=("UFW")
        fi

        # Firewalld (遍历活跃与默认 zones)
        if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then
          local fam="ipv4"
          [ "$is_v6" = true ] && fam="ipv6"
          local fw_zones
          fw_zones=$(firewall-cmd --get-active-zones 2>/dev/null | grep -v '^[[:space:]]' | awk '{print $1}')
          [ -z "$fw_zones" ] && fw_zones=$(firewall-cmd --get-default-zone 2>/dev/null)
          local fwd_removed=false
          for z in $fw_zones; do
            if firewall-cmd --permanent --zone="$z" --remove-rich-rule="rule family='${fam}' source address='${ip}' drop" >/dev/null 2>&1; then
              fwd_removed=true
              ((unban_count++))
            fi
          done
          if [ "$fwd_removed" = true ]; then
            firewall-cmd --reload >/dev/null 2>&1 || true
            unban_details+=("Firewalld")
          fi
        fi

        # nftables (动态解析 handle 并循环清除，支持 inet/ip/ip6 家族)
        if command -v nft >/dev/null 2>&1; then
          local nft_deleted=false
          # 只删除脚本拥有的完整源地址 DROP 规则，保留管理员与旧归属不明规则。
            while read -r fam tbl chn h; do
              [ -z "$h" ] && continue
              if nft delete rule "$fam" "$tbl" "$chn" handle "$h" 2>/dev/null; then
                nft_deleted=true
                ((unban_count++))
              fi
            done < <(list_aurora_nft_handles "$ip")
          if [ "$nft_deleted" = true ]; then
            persist_aurora_nft_bans || echo -e "${YELLOW}[!] 解封已生效，但持久化更新失败，请检查独立加载配置${RESET}"
            unban_details+=("nftables")
          fi
        fi

        # iptables / ip6tables (循环删除直至非 0，清除所有重复规则)
        if command -v iptables >/dev/null 2>&1 || command -v ip6tables >/dev/null 2>&1; then
          local ipt_deleted=false
          if [ "$is_v6" = true ] && command -v ip6tables >/dev/null 2>&1; then
            while ip6tables -D INPUT -s "$ip" -j DROP 2>/dev/null; do
              ipt_deleted=true
              ((unban_count++))
            done
          fi
          if command -v iptables >/dev/null 2>&1; then
            while iptables -D INPUT -s "$ip" -j DROP 2>/dev/null; do
              ipt_deleted=true
              ((unban_count++))
            done
          fi
          if [ "$ipt_deleted" = true ]; then
            if command -v netfilter-persistent >/dev/null 2>&1; then
              netfilter-persistent save >/dev/null 2>&1 || true
            fi
            unban_details+=("iptables")
          fi
        fi

        # 3. 对称清理 hosts.deny
        if [ -f /etc/hosts.deny ]; then
          local escaped_ip
          escaped_ip=$(echo "$ip" | sed 's/\./\\./g')
          if awk -v ip="$ip" '$1 == "ALL:" && $2 == ip && $3 == "#" && $4 == "Aurora" {found=1} END {exit !found}' /etc/hosts.deny; then
            awk -v ip="$ip" '!($1 == "ALL:" && $2 == ip && $3 == "#" && $4 == "Aurora")' /etc/hosts.deny > "/etc/hosts.deny.tmp.$$" &&
              mv -f "/etc/hosts.deny.tmp.$$" /etc/hosts.deny || { echo -e "${RED}[×] hosts.deny 修改失败${RESET}"; continue; }
            ((unban_count++))
            unban_details+=("hosts.deny")
          fi
        fi

        if [ "$unban_count" -gt 0 ]; then
          local details_str
          details_str=$(IFS=, ; echo "${unban_details[*]}")
          echo -e "${GREEN}[√] 成功解封 IP $ip！已撤销 $unban_count 处拦截条目 (${details_str})${RESET}"
        else
          echo -e "${YELLOW}[i] 未在 Fail2Ban 或系统防火墙中找到针对 IP $ip 的封禁记录${RESET}"
        fi
      }
      ui_pause "按 Enter 继续..."
      ;;
    7)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      # --- 白名单子菜单循环 ---
      while true; do
        clear
        echo -e "${BOLD}${CYAN}Fail2Ban 白名单管理${RESET}"
        echo -e "${CYAN}==================================================${RESET}"

        # 实时获取列表 (优先从运行时获取，配置文件同时检查 aurora drop-in 和 jail.local)
        cur_runtime_list=$(fail2ban-client get sshd ignoreip 2>/dev/null)
        cur_file_list=$(grep -rh "^[[:space:]]*ignoreip" "$sshd_conf" /etc/fail2ban/jail.local 2>/dev/null | cut -d= -f2 | xargs)

        echo -e "当前生效(Runtime): ${GREEN}${cur_runtime_list:-无}${RESET}"
        echo -e "配置文件(Config):  ${YELLOW}${cur_file_list:-无}${RESET}"
        echo -e "${CYAN}--------------------------------------------------${RESET}"
        echo -e "${YELLOW}  1.${RESET} 添加 IP (Add IP) ${GRAY}[智能建议]${RESET}"
        echo -e "${YELLOW}  2.${RESET} 删除 IP (Remove IP)"
        echo -e "${YELLOW}  0.${RESET} 返回上一级"
        echo -e "${CYAN}==================================================${RESET}"

        ui_menu_read wl_choice "请输入选项" "[0-2]" || return 0
        case $wl_choice in
        1)
          # 智能获取建议 IP
          suggest_ip=$(echo "${SSH_CLIENT%% *}")
          [ -z "$suggest_ip" ] && suggest_ip=$(echo "${SSH_CONNECTION%% *}")
          ui_read_field add_ip "请输入要添加的 IP (默认 $suggest_ip)" ui_any_value "$suggest_ip" || continue
          [ -z "$add_ip" ] && add_ip="$suggest_ip"

          if [ -n "$add_ip" ]; then
            # 1. 运行时添加
            fail2ban-client set sshd addignoreip "$add_ip" >/dev/null 2>&1

            # 2. 写入配置文件 (持久化到 00-aurora-sshd.local)
            ensure_jail_params "sshd"
            if grep -q "ignoreip[[:space:]]*=" "$sshd_conf"; then
              if ! grep "^[[:space:]]*ignoreip" "$sshd_conf" | grep -qw "$add_ip"; then
                sed -i "/ignoreip[[:space:]]*=/s/$/ $add_ip/" "$sshd_conf"
              fi
            else
              sed -i "/^\[DEFAULT\]/a ignoreip = 127.0.0.1/8 ::1 $add_ip" "$sshd_conf"
            fi
            echo -e "${GREEN}[√] 添加成功: $add_ip${RESET}"
            sleep 1
          fi
          ;;
        2)
          ui_read_field del_ip "请输入要删除的 IP" ui_nonempty || continue
          if [ -n "$del_ip" ]; then
            # 1. 运行时删除
            fail2ban-client set sshd delignoreip "$del_ip" >/dev/null 2>&1

            # 2. 配置文件删除 (仅对 ignoreip 行进行精准 token 匹配过滤，防止类似 1.2.3.4 误伤 1.2.3.45)
            for target_conf in "$sshd_conf" /etc/fail2ban/jail.local; do
              if [ -f "$target_conf" ] && grep -qE '^[[:space:]]*ignoreip[[:space:]]*=' "$target_conf"; then
                awk -v del="$del_ip" '
                  /^[[:space:]]*ignoreip[[:space:]]*=/ {
                    split($0, arr, "=")
                    prefix = arr[1] "="
                    val_str = substr($0, length(prefix) + 1)
                    n = split(val_str, tokens, /[[:space:],]+/)
                    new_val = ""
                    for (i = 1; i <= n; i++) {
                      if (tokens[i] != "" && tokens[i] != del) {
                        new_val = (new_val == "" ? tokens[i] : new_val " " tokens[i])
                      }
                    }
                    print prefix " " new_val
                    next
                  }
                  { print }
                ' "$target_conf" > "${target_conf}.tmp" && mv "${target_conf}.tmp" "$target_conf"
              fi
            done
            echo -e "${GREEN}[√] 删除操作已执行: $del_ip${RESET}"
            sleep 1
          fi
          ;;
        0) break ;; # 退出子菜单
        *)
          echo -e "${RED}无效选项${RESET}"
          sleep 1
          ;;
        esac
      done
      ;;
    8)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      echo -e "${CYAN}>>> 最后 20 条日志:${RESET}"
      tail -n 20 /var/log/fail2ban.log 2>/dev/null
      echo -e "${CYAN}--------------------------------------------------${RESET}"
      ui_confirm view_live "是否进入实时监控模式 (按 Ctrl+C 退出)? [y/N] (q 取消): "
      if [[ "$view_live" =~ ^[Yy]$ ]]; then
        tail -f /var/log/fail2ban.log
      fi
      ;;
    9)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      
      # 时间单位转换为秒的解析工具函数 (纯解析：强制十进制解析，支持 -1 永久封禁及任意合法时长)
      parse_f2b_duration() {
        local val=$(echo "$1" | tr -d '[:space:]')
        if [[ "$val" == "-1" ]]; then
          echo "-1"
          return 0
        fi
        if [[ "$val" =~ ^([0-9]+)([mMhHwWdDsS]?)$ ]]; then
          local num=$(( 10#${BASH_REMATCH[1]} ))
          local mult=1
          case "${BASH_REMATCH[2]}" in
            [mM]) mult=60 ;;
            [hH]) mult=3600 ;;
            [dD]) mult=86400 ;;
            [wW]) mult=604800 ;;
            *)    mult=1 ;;
          esac
          echo $(( num * mult ))
        else
          echo ""
        fi
      }

      # 获取当前配置作为默认值
      local cur_maxretry=$(sed -n '/^\[sshd\]/,/^\[/{/maxretry/p}' "$sshd_conf" /etc/fail2ban/jail.local 2>/dev/null | tail -1 | awk -F= '{print $2}' | xargs)
      local cur_bantime=$(sed -n '/^\[sshd\]/,/^\[/{/bantime/p}' "$sshd_conf" /etc/fail2ban/jail.local 2>/dev/null | tail -1 | awk -F= '{print $2}' | xargs)
      [ -z "$cur_maxretry" ] && cur_maxretry="5"
      local cur_bantime_sec=$(parse_f2b_duration "$cur_bantime")
      [ -z "$cur_bantime_sec" ] && cur_bantime_sec="600"
      
      local display_bt="${cur_bantime_sec}秒"
      [ "$cur_bantime_sec" == "-1" ] && display_bt="永久(-1)"
      echo -e "当前配置: 最大失败=${YELLOW}$cur_maxretry${RESET}, 封禁时长=${YELLOW}${display_bt}${RESET} (全版本兼容纯秒数)"
      echo -e "${GRAY}提示: 输入 0 可返回上一级菜单${RESET}"
      ui_read_field mr "最大失败次数 (留空保持 $cur_maxretry)" ui_positive "$cur_maxretry" true || continue
      if [[ "$mr" == "0" ]]; then continue; fi
      ui_read_field bt "封禁时长 (秒数或单位如 10m/1h，留空保持 ${display_bt})" ui_f2b_duration "$cur_bantime_sec" true || continue
      if [[ "$bt" == "0" ]]; then continue; fi
      
      # 使用原值作为默认
      [ -z "$mr" ] && mr="$cur_maxretry"
      local bt_sec
      if [ -z "$bt" ]; then
        bt_sec="$cur_bantime_sec"
      else
        bt_sec=$(parse_f2b_duration "$bt")
        if [ -z "$bt_sec" ] || { [ "$bt_sec" -ne -1 ] && [ "$bt_sec" -le 0 ]; }; then
          echo -e "${RED}[!] 封禁时长格式无效，请输入正整数秒或带单位格式 (如 600, 10m, 1h, 1d) 或 -1 (永久)${RESET}"
          ui_pause "按 Enter 继续..."
          continue
        fi
      fi
      
      if [[ "$mr" =~ ^[0-9]+$ ]] && [ "$mr" -gt 0 ]; then
        ensure_jail_params "sshd"
        sed -i '/^\[sshd\]/,/^\[/ { /maxretry/d; /bantime/d }' "$sshd_conf"
        sed -i "/^\[sshd\]/a maxretry = $mr\nbantime = $bt_sec" "$sshd_conf"
        echo -e "${CYAN}正在应用策略 (bantime=${bt_sec}秒)...${RESET}"
        fail2ban-client -t && systemctl restart fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 配置/重启失败${RESET}"; continue; }
        echo -e "${GREEN}[√] 策略已成功更新 (全版本兼容纯秒数生效)${RESET}"
      else
        echo -e "${RED}[!] 最大失败次数必须是正整数${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    10)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      if [ "$rec_status" == "on" ]; then
        ui_confirm c "确认关闭 顽固监狱 (Recidive)? [y/N] (q 取消): "
        if [[ "$c" =~ ^[Yy]$ ]]; then
          set_jail_config "recidive" "false"
          fail2ban-client -t && systemctl restart fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 配置/重启失败${RESET}"; continue; }
          sleep 2
          echo -e "${YELLOW}[!] 已关闭${RESET}"
        else
          echo -e "${YELLOW}操作已取消${RESET}"
        fi
      else
        set_jail_config "recidive" "true"
        ensure_jail_params "recidive"
        fail2ban-client -t && systemctl restart fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 配置/重启失败${RESET}"; continue; }
        sleep 2
        echo -e "${GREEN}[√] 已开启${RESET}"
      fi
      ;;
    11)
      # [修复] 添加服务未运行的反馈提示
      if ! check_f2b_running; then
        echo -e "${RED}[!] Fail2Ban 服务未运行，请先启动服务${RESET}"
        sleep 1
        continue
      fi
      if [ "$ssh_status" == "on" ]; then
        ui_confirm c "确认关闭 SSH 保护? [y/N] (q 取消): "
        [[ "$c" =~ ^[Yy]$ ]] && {
          set_jail_config "sshd" "false"
          fail2ban-client -t && systemctl restart fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 配置/重启失败${RESET}"; continue; }
          sleep 2
          echo -e "${YELLOW}[!] 已关闭${RESET}"
        }
      else
        set_jail_config "sshd" "true"
        ensure_jail_params "sshd"
        fail2ban-client -t && systemctl restart fail2ban && fail2ban-client ping >/dev/null || { echo -e "${RED}[×] Fail2Ban 配置/重启失败${RESET}"; continue; }
        sleep 2
        echo -e "${GREEN}[√] 已开启${RESET}"
      fi
      ;;
    12)
      if [ -f "$db_file" ]; then
        # [修复] 增强数据库清理的风险警告
        echo -e "${RED}========== 警告 ===========${RESET}"
        echo -e "${YELLOW}此操作将清除 Fail2Ban 数据库，包括:${RESET}"
        echo -e "  - 所有封禁记录和历史"
        echo -e "  - Jail 统计信息"
        echo -e "  - 当前数据库大小: ${YELLOW}$db_size${RESET}"
        echo -e "${RED}============================${RESET}"
        ui_confirm_explicit c "确认清理数据库?"
        if [ "$c" == "yes" ]; then
          systemctl stop fail2ban
          rm -f "$db_file"
          if systemctl start fail2ban; then
            echo -e "${GREEN}[√] 数据库已清理，服务已重启${RESET}"
          else
            echo -e "${RED}[!] 警告: 服务启动失败，请检查配置${RESET}"
          fi
        else
          echo -e "${YELLOW}已取消${RESET}"
        fi
      else echo -e "${YELLOW}文件不存在${RESET}"; fi
      ui_pause "按 Enter 继续..."
      ;;
    0)
      SKIP_PAUSE=true
      break
      ;;
    *)
      echo -e "${RED}无效选项${RESET}"
      sleep 1
      ;;
    esac
  done
}

change_timezone() {
  echo -e "${CYAN}>>> 修改系统时区...${RESET}"

  local target_tz=""

  while true; do
    current_tz=$(timedatectl show --property=Timezone --value 2>/dev/null || cat /etc/timezone)
    current_time=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "当前时区: ${GREEN}$current_tz${RESET}"
    echo -e "当前时间: ${GREEN}$current_time${RESET}"

    echo -e "\n${CYAN}请选择目标时区:${RESET}"
    echo -e "  ${GREEN}1${RESET}) 亚洲/上海 (Beijing/Shanghai, UTC+8)"
    echo -e "  ${GREEN}2${RESET}) 亚洲/香港 (Hong Kong, UTC+8)"
    echo -e "  ${GREEN}3${RESET}) 亚洲/台北 (Taipei, UTC+8)"
    echo -e "  ${GREEN}4${RESET}) 亚洲/东京 (Tokyo, UTC+9)"
    echo -e "  ${GREEN}5${RESET}) 美国/纽约 (New York, UTC-5/UTC-4)"
    echo -e "  ${GREEN}6${RESET}) 欧洲/伦敦 (London, UTC+0/UTC+1)"
    echo -e "  ${GREEN}7${RESET}) 交互式选择 (Region -> City)"
    echo -e "  ${GREEN}8${RESET}) 恢复默认时区 (UTC)"
    echo -e "  ${YELLOW}0.${RESET} 取消/返回"

    ui_menu_read tz_choice "请输入选项 [0-8]" "[0-8]" || return 0

    case $tz_choice in
    1)
      target_tz="Asia/Shanghai"
      break
      ;;
    2)
      target_tz="Asia/Hong_Kong"
      break
      ;;
    3)
      target_tz="Asia/Taipei"
      break
      ;;
    4)
      target_tz="Asia/Tokyo"
      break
      ;;
    5)
      target_tz="America/New_York"
      break
      ;;
    6)
      target_tz="Europe/London"
      break
      ;;
    7)
      # 交互式选择逻辑
      while true; do
        echo -e "\n${CYAN}>>> 区域选择 (输入 0 退出):${RESET}"
        regions=($(find /usr/share/zoneinfo -maxdepth 1 -type d | sed 's|/usr/share/zoneinfo/||' | grep -v "^\." | grep -v "^posix" | grep -v "^right" | grep -E "^[A-Z]" | sort))

        for i in "${!regions[@]}"; do
          printf "  ${GREEN}%-2d${RESET}) %s\n" "$((i + 1))" "${regions[$i]}"
        done
        echo -e "  ${YELLOW}0.${RESET} 返回上一级"

        ui_read_field region_idx "请输入区域编号: " ui_any_value "" true || { region_idx=0; }

        if [[ "$region_idx" == "0" ]]; then break; fi

        if [[ "$region_idx" =~ ^[0-9]+$ ]] && [ "$region_idx" -ge 1 ] && [ "$region_idx" -le "${#regions[@]}" ]; then
          selected_region="${regions[$((region_idx - 1))]}"
          echo -e "${YELLOW}已选择区域: $selected_region${RESET}"

          while true; do
            echo -e "\n${CYAN}>>> 城市选择 (输入 0 返回区域列表):${RESET}"
            cities=($(ls "/usr/share/zoneinfo/$selected_region" | grep -v "^posix" | grep -v "^right" | sort))

            local total_cities=${#cities[@]}
            local num_cols=3
            local rows=$(((total_cities + num_cols - 1) / num_cols))

            for ((r = 0; r < rows; r++)); do
              idx1=$r
              if [ $idx1 -lt $total_cities ]; then
                printf "${GREEN}%-3d${RESET}) %-20s" "$((idx1 + 1))" "${cities[$idx1]}"
              fi
              idx2=$((r + rows))
              if [ $idx2 -lt $total_cities ]; then
                printf "${GREEN}%-3d${RESET}) %-20s" "$((idx2 + 1))" "${cities[$idx2]}"
              fi
              idx3=$((r + rows * 2))
              if [ $idx3 -lt $total_cities ]; then
                printf "${GREEN}%-3d${RESET}) %-20s" "$((idx3 + 1))" "${cities[$idx3]}"
              fi
              echo ""
            done

            echo -e "  ${YELLOW}0.${RESET} 返回上一级"

            ui_read_field city_idx "请输入城市编号: " ui_any_value "" true || { city_idx=0; }

            if [[ "$city_idx" == "0" ]]; then break; fi

            if [[ "$city_idx" =~ ^[0-9]+$ ]] && [ "$city_idx" -ge 1 ] && [ "$city_idx" -le "${#cities[@]}" ]; then
              selected_city="${cities[$((city_idx - 1))]}"
              target_tz="$selected_region/$selected_city"
              # 跳出两层循环 (City 和 Region)
              break 2
            else
              echo -e "${RED}无效的城市编号${RESET}"
            fi
          done
        else
          echo -e "${RED}无效的区域编号${RESET}"
        fi
      done

      # 如果在交互模式中未选择(直接返回了)，则 continue 重新显示主菜单
      if [ -z "$target_tz" ]; then continue; fi
      # 如果选择了，则 break 跳出主菜单循环去执行应用逻辑
      break
      ;;
    8)
      target_tz="Etc/UTC"
      echo -e "${CYAN}准备将时区重置为默认值 (Etc/UTC)...${RESET}"
      break
      ;;
    0)
      # 取消操作，直接退出函数
      SKIP_PAUSE=true
      return 0
      ;;
    *)
      echo -e "${RED}无效选项${RESET}"
      sleep 1
      continue
      ;;
    esac
  done

  # ---------- 应用逻辑移至循环外 ----------
  if [ -n "$target_tz" ]; then
    echo -e "${CYAN}>>> 正在应用时区设置: $target_tz ...${RESET}"
    if [ -f "/usr/share/zoneinfo/$target_tz" ]; then
      local tz_applied=false
      if command -v timedatectl >/dev/null 2>&1; then
        if timedatectl set-timezone "$target_tz" >/dev/null 2>&1; then
          tz_applied=true
        fi
      fi
      if [ "$tz_applied" = false ]; then
        # timedatectl 不存在或执行失败（如容器/无 systemd-timedated 环境），回退到直接配置 /etc/localtime 与 /etc/timezone
        if ln -sf "/usr/share/zoneinfo/$target_tz" /etc/localtime 2>/dev/null; then
          echo "$target_tz" >/etc/timezone 2>/dev/null || true
          tz_applied=true
        fi
      fi

      # 同步硬件时间 (静默容错)
      hwclock --systohc 2>/dev/null || true

      if [ "$tz_applied" = true ]; then
        echo -e "${GREEN}[√] 时区已成功修改为: $target_tz${RESET}"
        echo -e "当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
      else
        echo -e "${RED}[×] 时区修改失败，请检查系统权限或 /etc/localtime 是否只读${RESET}"
      fi
    else
      echo -e "${RED}[!] 错误: 未找到时区文件 /usr/share/zoneinfo/$target_tz${RESET}"
    fi
  fi
}

# ---------- 修改主机名 ----------
change_hostname() {
  # --- PVE 强拦截与系统安全保护 (杜绝通用命令导致集群与节点目录瘫痪) ---
  if [ "$IS_PVE" = true ]; then
    echo -e "\n${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    echo -e "${RED}[!] 严正拦截: 检测到当前系统为 Proxmox VE (PVE) 宿主机！${RESET}"
    echo -e "${YELLOW}Proxmox VE 的集群数据库与底层配置强绑定于 /etc/pve/nodes/<节点名>/。${RESET}"
    echo -e "${YELLOW}通过通用脚本修改主机名会导致 pve-cluster 无法挂载、节点脱网、所有虚拟机与容器从面板丢失！${RESET}"
    echo -e "${CYAN}官方安全重命名标准流程:${RESET}"
    echo -e "${GRAY}  1. 备份并停止所有虚拟机和容器${RESET}"
    echo -e "${GRAY}  2. systemctl stop pve-cluster${RESET}"
    echo -e "${GRAY}  3. pmxcfs -l (本地模式启动集群文件系统)${RESET}"
    echo -e "${GRAY}  4. mv /etc/pve/nodes/<旧名称> /etc/pve/nodes/<新名称>${RESET}"
    echo -e "${GRAY}  5. 更新 /etc/hosts、/etc/hostname 与 corosync.conf 证书${RESET}"
    echo -e "${GRAY}  6. 重启系统验证${RESET}"
    echo -e "${RED}为了您的业务与数据资产安全，本脚本禁止对 PVE 宿主机执行通用重命名。${RESET}"
    echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
    ui_pause "按 Enter 键安全返回主菜单..."
    SKIP_PAUSE=true
    return 0
  fi

  while true; do
    clear
    echo -e "${CYAN}>>> 修改系统主机名 (Hostname)${RESET}"

    local current_hostname=$(hostname)
    echo -e "当前主机名: ${GREEN}${current_hostname}${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    # --- 步骤 1: 输入阶段 (支持返回) ---
    echo -e "${GRAY}提示: 建议使用英文、数字和连字符(-)，例如: vps-hk-01${RESET}"
    ui_read_field new_hostname "请输入新的主机名 (输入 0 取消): " ui_any_value "" true || { new_hostname=0; }

    # 返回主菜单逻辑
    if [ "$new_hostname" == "0" ]; then
      SKIP_PAUSE=true
      return 0
    fi

    # 空值检查 -> 返回重输
    if [ -z "$new_hostname" ]; then
      echo -e "${RED}[!] 错误：主机名不能为空，请重新输入。${RESET}"
      sleep 1
      continue
    fi

    # 格式检查 (简单正则) -> 返回重输
    if [[ ! "$new_hostname" =~ ^[a-zA-Z0-9.-]+$ ]]; then
      echo -e "${RED}[!] 错误：主机名包含非法字符，仅支持字母、数字、点和横杠。${RESET}"
      sleep 2
      continue
    fi

    # 一致性检查 -> 返回重输
    if [ "$new_hostname" == "$current_hostname" ]; then
      echo -e "${YELLOW}[!] 新主机名与当前一致，无需修改。${RESET}"
      ui_pause "按 Enter 返回..."
      return 0
    fi

    # --- 步骤 2: 确认阶段 (支持“反悔”返回上一步) ---
    echo -e "\n${CYAN}即将执行以下变更:${RESET}"
    echo -e "  旧主机名: ${RED}${current_hostname}${RESET}"
    echo -e "  新主机名: ${GREEN}${new_hostname}${RESET}"

    echo -e "\n${YELLOW}确认修改吗?${RESET}"
    echo -e "  [y] 确认修改"
    echo -e "  [n] 重新输入 (返回上一步)"
    echo -e "  [0] 取消并退出"

    ui_read_field confirm_choice "确认修改 (y 确认 / n 重新输入)" ui_hostname_confirm "n" true || { confirm_choice=0; }

    case $confirm_choice in
    [yY] | [yY][eE][sS])
      # 用户确认，跳出循环执行修改
      break
      ;;
    0)
      # 用户取消
      SKIP_PAUSE=true
      return 0
      ;;
    *)
      # 用户选 n 或其他，循环继续，回到“输入阶段”
      continue
      ;;
    esac
  done

  # --- 步骤 3: 执行阶段 ---
  echo -e "\n${CYAN}>>> 正在应用修改...${RESET}"

  # 3.1 修改主机名
  if command -v hostnamectl >/dev/null 2>&1; then
    hostnamectl set-hostname "$new_hostname"
  else
    hostname "$new_hostname"
    [ -f /etc/hostname ] && echo "$new_hostname" >/etc/hostname
  fi

  # 3.2 同步修改 /etc/hosts
  if [ -f /etc/hosts ]; then
    local escaped_old escaped_new escaped_check_new
    escaped_old=$(printf '%s\n' "$current_hostname" | sed -e 's/[][\/.^$*]/\\&/g')
    escaped_new=$(printf '%s\n' "$new_hostname" | sed -e 's/[\/&]/\\&/g')
    escaped_check_new=$(printf '%s\n' "$new_hostname" | sed -e 's/[][\/.^$*]/\\&/g')

    # 只在非注释行中查找并替换精确匹配的独立主机名
    if grep -v '^[[:space:]]*#' /etc/hosts | grep -qE "(^|[[:space:]])${escaped_old}([[:space:]]|$)"; then
      sed -i -E "/^[[:space:]]*#/!s/(^|[[:space:]])${escaped_old}([[:space:]]|$)/\1${escaped_new}\2/g" /etc/hosts
      echo -e "${GREEN}[√] 已更新 /etc/hosts 映射${RESET}"
    else
      # 如果找不到旧名，检查是否已有新名映射，无则安全追加
      if ! grep -v '^[[:space:]]*#' /etc/hosts | grep -qE "(^|[[:space:]])${escaped_check_new}([[:space:]]|$)"; then
        echo "127.0.1.1 $new_hostname" >>/etc/hosts
        echo -e "${GREEN}[√] 已添加 /etc/hosts 映射${RESET}"
      fi
    fi
  fi

  # --- 步骤 4: 结果验证 ---
  local verify_name=$(hostname)
  if [ "$verify_name" == "$new_hostname" ]; then
    echo -e "${GREEN}[√] 主机名修改成功!${RESET}"
    echo -e "${YELLOW}注意: 请重新连接 SSH (断开重连) 以使终端提示符更新。${RESET}"
  else
    echo -e "${RED}[!] 修改可能未完全生效，检测到的主机名为: $verify_name${RESET}"
  fi
}

# ---------- [增强] 端口占用速查 (内部函数) ----------
sub_port_usage() {
  while true; do
    clear
    echo -e "${BOLD}${CYAN}端口占用情况速查 (TCP/UDP)${RESET}"
    echo -e "${CYAN}========================================================================${RESET}"
    echo -e "${YELLOW}Proto  Local Address             Port    PID/Process Name${RESET}"
    echo -e "${CYAN}------------------------------------------------------------------------${RESET}"

    ss -tulnp | sed '1d' | awk '{
            proto = $1
            local_addr = $5
            process_raw = $7
            n = split(local_addr, a, ":")
            port = a[n]
            ip = ""
            for(i=1; i<n; i++){
                ip = ip a[i]
                if(i < n-1) ip = ip ":"
            }
            proc_info = "未知"
            idx = index(process_raw, "users:((\"")
            if (idx > 0) {
                raw_str = substr(process_raw, idx + 9)
                split(raw_str, p_arr, ",")
                p_name = p_arr[1]
                gsub(/"/, "", p_name)
                p_pid = ""
                for(k in p_arr) {
                    if(index(p_arr[k], "pid=") > 0) {
                        split(p_arr[k], pid_arr, "=")
                        p_pid = pid_arr[2]
                        break
                    }
                }
                proc_info = p_name "(" p_pid ")"
            }
            print proto "|" ip "|" port "|" proc_info
        }' | sort -t'|' -k3,3n | head -n 30 |
      while IFS='|' read -r proto ip port proc_info; do
        current_color=""
        if [[ "$ip" == "127.0.0.1" ]] || [[ "$ip" == "[::1]" ]]; then
          current_color="${GREEN}"
        elif [[ "$ip" == "0.0.0.0" ]] || [[ "$ip" == "*" ]] || [[ "$ip" == "[::]" ]]; then
          current_color="${RED}"
        else
          current_color="${RESET}"
        fi
        printf "%-6s ${current_color}%-25s${RESET} %-7s %s\n" "$proto" "$ip" "$port" "$proc_info"
      done

    echo -e "${CYAN}========================================================================${RESET}"
    echo -e "${YELLOW}功能菜单:${RESET}"
    echo -e "  ${GREEN}1${RESET}) 刷新列表 (Refresh)"
    echo -e "  ${GREEN}2${RESET}) 强制结束进程 (Kill PID)"
    echo -e "  ${YELLOW}0${RESET}) 返回上一级"

    ui_menu_read choice "请输入选项" "[0-2]" || return 0
    case $choice in
    1) continue ;;
    2)
      ui_read_field kill_pid "请输入要结束的 PID (数字, 输入 0 返回): " ui_any_value "" true || { kill_pid=0; }
      if [ "$kill_pid" == "0" ]; then continue; fi
      if [[ "$kill_pid" =~ ^[0-9]+$ ]]; then
        proc_name=$(ps -p "$kill_pid" -o comm= 2>/dev/null)
        if [ -z "$proc_name" ]; then
          echo -e "${RED}[!] 找不到 PID 为 $kill_pid 的进程${RESET}"
        else
          echo -e "${YELLOW}警告: 即将结束进程: $proc_name (PID: $kill_pid)${RESET}"
          ui_confirm confirm_kill "确认执行吗? [y/N] (q 取消): "
          if [[ "$confirm_kill" =~ ^[Yy]$ ]]; then
            kill -9 "$kill_pid"
            echo -e "${GREEN}[√] 进程已结束${RESET}"
          fi
        fi
      else
        echo -e "${RED}[!] PID 必须是数字${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    0) return 0 ;;
    *) echo -e "${RED}无效选项${RESET}"; sleep 1 ;;
    esac
  done
}

# --- 进程资源列表：全量采集后搜索与分页，不再限制前 5 名 ---
process_resource_rows() {
  local process_sort="$1" process_query="${2:-}" process_snapshot
  case "$process_sort" in cpu) process_sort=-%cpu ;; memory) process_sort=-%mem ;; *) return 1 ;; esac
  process_snapshot=$(ps -ww -eo pid=,user=,%mem=,%cpu=,args= --sort="$process_sort" 2>/dev/null) || return 1
  if [ -z "$process_query" ]; then
    printf '%s\n' "$process_snapshot"
  else
    # 数字按 PID 精确匹配；其他输入按完整命令行进行不区分大小写的字面匹配。
    PROCESS_RESOURCE_QUERY="$process_query" awk '
      BEGIN {query=ENVIRON["PROCESS_RESOURCE_QUERY"]; numeric=(query ~ /^[0-9]+$/); query=tolower(query)}
      numeric {if ($1 == query) print; next}
      {command=$0; sub(/^[[:space:]]*[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+/, "", command); if (index(tolower(command), query)) print}
    ' <<<"$process_snapshot"
  fi
}

process_page_size_valid() {
  [[ "$1" =~ ^[0-9]{1,3}$ ]] && ((10#$1 >= 10 && 10#$1 <= 100))
}

sub_process_top() {
  local process_sort=memory process_query="" process_page=1 process_page_size=20
  local process_rows process_total process_pages process_start process_end process_action process_new_size process_new_query
  local process_pid process_user process_mem process_cpu process_command process_sort_name
  while true; do
    clear
    echo -e "${BOLD}${CYAN}进程资源占用分析 (分页/搜索)${RESET}"
    if ! process_rows=$(process_resource_rows "$process_sort" "$process_query"); then
      echo -e "${RED}[×] 无法获取进程列表，请检查 ps 是否支持排序及字段输出${RESET}"
      ui_pause
      return 1
    fi
    process_total=$(printf '%s\n' "$process_rows" | awk 'NF {n++} END {print n+0}')
    process_pages=$(((process_total + process_page_size - 1) / process_page_size))
    [ "$process_pages" -gt 0 ] || process_pages=1
    [ "$process_page" -le "$process_pages" ] || process_page="$process_pages"
    process_sort_name="内存"
    [ "$process_sort" = cpu ] && process_sort_name="CPU"
    printf '排序: %s | 搜索: %s | 共 %s 个进程 | 第 %s/%s 页 | 每页 %s 个\n' \
      "$process_sort_name" "${process_query:-全部}" "$process_total" "$process_page" "$process_pages" "$process_page_size"
    echo -e "${GRAY}%CPU 为 ps 的进程生命周期平均值；输入 6 可刷新快照${RESET}"
    printf '%-8s %-12s %-7s %-7s %s\n' PID USER %MEM %CPU COMMAND
    process_start=$(((process_page - 1) * process_page_size + 1))
    process_end=$((process_page * process_page_size))
    if [ "$process_total" -eq 0 ]; then
      echo -e "${YELLOW}没有匹配的进程，可重新搜索或清除搜索${RESET}"
    else
      while read -r process_pid process_user process_mem process_cpu process_command; do
        printf '%b%-8s%b %-12s %-7s %-7s %s\n' "$GREEN" "$process_pid" "$RESET" \
          "$process_user" "$process_mem" "$process_cpu" "$process_command"
      done < <(printf '%s\n' "$process_rows" | sed -n "${process_start},${process_end}p")
    fi
    echo -e "${CYAN}--------------------------------------------------${RESET}"
    echo '  1. 下一页    2. 上一页    3. 搜索进程名/命令/PID'
    echo '  4. 清除搜索  5. 切换 CPU/内存排序  6. 刷新  7. 每页数量 (10-100)'
    echo '  0. 返回上一级'
    ui_menu_read process_action "请输入选项" '[0-7]' || return 0
    case "$process_action" in
      1) [ "$process_page" -ge "$process_pages" ] || process_page=$((process_page + 1)) ;;
      2) [ "$process_page" -le 1 ] || process_page=$((process_page - 1)) ;;
      3)
        ui_read_field process_new_query "输入进程名/命令关键词或 PID (留空显示全部)" || continue
        process_query="$process_new_query"; process_page=1
        ;;
      4) process_query=""; process_page=1 ;;
      5) if [ "$process_sort" = memory ]; then process_sort=cpu; else process_sort=memory; fi; process_page=1 ;;
      6) : ;;
      7)
        ui_read_field process_new_size "每页数量 (10-100，默认 $process_page_size)" process_page_size_valid "$process_page_size" || continue
        process_page_size=$((10#$process_new_size)); process_page=1
        ;;
      0) return 0 ;;
    esac
  done
}

# 仅匹配 SSH 失败事件中的来源地址，保留 IPv6 完整地址。
login_audit_parse() {
    LC_ALL=C awk '/sshd|ssh-session/ && /Failed (password|publickey|keyboard-interactive|none)|authentication failure|Invalid user/ {
        for (i=1; i<=NF; i++) {
            if ($i == "from" && i<NF) {print $(i+1); break}
            if ($i ~ /^rhost=/) {sub(/^rhost=/, "", $i); print $i; break}
        }
    }' | while IFS= read -r ip; do validate_ip "$ip" && printf '%s\n' "$ip"; done
}

login_audit_count() {
    LC_ALL=C sort | uniq -c | LC_ALL=C sort -k1,1nr -k2,2
}

# 状态文件是纯数据；不 source，避免将日志或状态作为 shell 代码执行。
login_audit_reset() {
    local state_dir="$1" log_dir="$2" tmp epoch file inode size
    epoch=$(date +%s) || return 1
    mkdir -p -- "$state_dir" || return 1
    tmp=$(mktemp "$state_dir/.start.XXXXXX") || return 1
    if ! (
        printf '%s\n' "$epoch"
        for file in "$log_dir/auth.log" "$log_dir/secure"; do
            if [ -f "$file" ]; then
                inode=$(stat -c '%d:%i' -- "$file") && size=$(stat -c %s -- "$file") || return 1
                printf '%s %s %s\n' "${file##*/}" "$inode" "$size"
            fi
        done
    ) > "$tmp"; then
        rm -f -- "$tmp"; return 1
    fi
    chmod 600 -- "$tmp" && mv -f -- "$tmp" "$state_dir/start" || { rm -f -- "$tmp"; return 1; }
}

login_audit_clear_btmp() {
    local log_dir="$1" backup_dir="$2" backup
    [ -f "$log_dir/btmp" ] && [ ! -L "$log_dir/btmp" ] && [ -r "$log_dir/btmp" ] && [ -w "$log_dir/btmp" ] || {
        echo 'btmp 不存在、不是普通文件或没有读写权限；未清理。'; return 1;
    }
    mkdir -p -- "$backup_dir" && chmod 700 -- "$backup_dir" || return 1
    backup=$(mktemp "$backup_dir/btmp-$(date +%Y%m%d-%H%M%S).XXXXXX") || return 1
    # 备份完成且可读之后才截断，保留原文件 inode、所有者和权限。
    if ! cp -- "$log_dir/btmp" "$backup" || ! chmod 600 -- "$backup"; then
        echo '备份失败，未清空 btmp。'; return 1
    fi
    truncate -s 0 -- "$log_dir/btmp" || { echo "清空失败；备份: $backup"; return 1; }
    printf 'btmp 已清空；备份: %s\n' "$backup"
}

login_audit_collect() {
    local lastb_cmd="$1" log_dir="${2:-/var/log}" state_file="$3"
    local raw rows err file inode size saved_name saved_inode saved_size offset since_text journal_since
    local -a lastb_args=(-w -i)
    audit_rows=""; audit_source='无可用失败记录'; audit_scope='见各来源状态'; audit_notes=(); audit_since=""
    if [ -r "$state_file" ]; then
        IFS= read -r audit_since < "$state_file"
        [[ "$audit_since" =~ ^[0-9]{1,12}$ ]] || audit_since=""
    fi
    err=$(mktemp) || { audit_notes+=('无法创建读取诊断临时文件。'); return 1; }
    if [ -n "$audit_since" ]; then
        since_text=$(date -d "@$audit_since" '+%F %T') || { rm -f -- "$err"; return 1; }
        lastb_args+=(-s "$since_text")
        audit_scope="从 $since_text 起；文本日志以保存的文件位置为准"
    fi
    if [ ! -f "$log_dir/btmp" ]; then audit_notes+=('btmp: 文件不存在，系统可能未配置失败登录落盘。')
    elif [ ! -r "$log_dir/btmp" ]; then audit_notes+=('btmp: 无读取权限。')
    elif [ ! -s "$log_dir/btmp" ]; then audit_notes+=('btmp: 文件为空。')
    elif [ -z "$lastb_cmd" ]; then audit_notes+=('btmp: 缺少 lastb/兼容 last 命令。')
    elif raw=$($lastb_cmd "${lastb_args[@]}" 2> "$err"); then
        rows=$(printf '%s\n' "$raw" | awk '{print $3}' | while read -r ip; do validate_ip "$ip" && printf '%s\n' "$ip"; done | login_audit_count)
        if [ -n "$rows" ]; then
            audit_rows=$rows; audit_source=btmp; audit_scope="${since_text:+从 $since_text 起；}当前 btmp 文件 (含非 SSH 失败登录)"
            rm -f -- "$err"; return 0
        fi
        audit_notes+=('btmp: 读取成功，当前范围没有可解析的来源 IP。')
    else audit_notes+=('btmp: 读取失败，可能权限不足、文件损坏或 lastb 不支持参数。'); fi
    if command -v journalctl >/dev/null 2>&1; then
        journal_since='7 days ago'; [ -z "$audit_since" ] || journal_since="@$audit_since"
        # 同字段为 OR，不同组以 + 为 OR，同时涵盖 unit 和 syslog 标识。
        if raw=$(journalctl --since "$journal_since" --no-pager -o short \
            _SYSTEMD_UNIT=ssh.service _SYSTEMD_UNIT=sshd.service + \
            SYSLOG_IDENTIFIER=sshd SYSLOG_IDENTIFIER=sshd-session + _COMM=sshd _COMM=sshd-session 2> "$err"); then
            rows=$(printf '%s\n' "$raw" | login_audit_parse | login_audit_count)
            [ ! -s "$err" ] || audit_notes+=('journal: 存在读取警告，权限限制可能导致结果不完整。')
            if [ -n "$rows" ]; then
                audit_rows=$rows; audit_source=journald; audit_scope='最近 7 天的可读取 SSH 日志'
                [ -z "$since_text" ] || audit_scope="从 $since_text 起的可读取 SSH 日志"
                rm -f -- "$err"; return 0
            fi
            audit_notes+=('journal: 命令成功，但当前范围没有可解析的 SSH 失败 IP；日志也可能未保留。')
        else audit_notes+=('journal: 读取失败，可能没有 journal、服务未运行或权限不足。'); fi
    else audit_notes+=('journal: 系统没有 journalctl。'); fi
    for file in "$log_dir/auth.log" "$log_dir/secure"; do
        if [ ! -f "$file" ]; then audit_notes+=("${file##*/}: 文件不存在。"); continue; fi
        if [ ! -r "$file" ]; then audit_notes+=("${file##*/}: 无读取权限。"); continue; fi
        offset=0
        if [ -n "$audit_since" ]; then
            inode=$(stat -c '%d:%i' -- "$file") && size=$(stat -c %s -- "$file") || { audit_notes+=("${file##*/}: 无法读取文件状态。"); continue; }
            while read -r saved_name saved_inode saved_size; do
                if [ "$saved_name" = "${file##*/}" ] && [ "$saved_inode" = "$inode" ] && [[ "$saved_size" =~ ^[0-9]+$ ]] && [ "$size" -ge "$saved_size" ]; then
                    offset=$saved_size; break
                fi
            done < "$state_file"
            [ "$offset" -gt 0 ] || audit_notes+=("${file##*/}: 新建、轮转、截断或原文件为空；读取当前文件，范围可能受轮转影响。")
        fi
        if raw=$(tail -c "+$((offset + 1))" -- "$file" 2> "$err"); then
            rows=$(printf '%s\n' "$raw" | login_audit_parse | login_audit_count)
            if [ -n "$rows" ]; then
                audit_rows=$rows; audit_source="${file##*/}"; audit_scope='当前日志文件 (不含轮转归档)'
                [ -z "$since_text" ] || audit_scope="从 $since_text 保存的文件位置起 (轮转时读取新文件)"
                rm -f -- "$err"; return 0
            fi
            audit_notes+=("${file##*/}: 读取成功，没有可解析的 SSH 失败 IP。")
        else audit_notes+=("${file##*/}: 读取失败。"); fi
    done
    rm -f -- "$err"
}

# --- [新增] 登录安全审计 ---
sub_login_audit() {
    clear
    # 依赖按需安装 (交互式)
    if { ! command -v last >/dev/null 2>&1 && ! command -v wtmpdb >/dev/null 2>&1; } ||
       { [ "$PKG_MANAGER" = apt ] && command -v wtmpdb >/dev/null 2>&1 && ! dpkg-query -W -f='${Status}' libpam-wtmpdb 2>/dev/null | grep -q 'install ok installed'; }; then
        echo -e "${YELLOW}检测到缺少审计工具 (last/lastb/wtmpdb)${RESET}"
        ui_confirm install_choice "是否尝试自动安装所需组件 (util-linux/wtmpdb)? [y/N] (q 取消): "
        if [[ "$install_choice" =~ ^[Yy]$ ]]; then
            check_pm_lock
            
            echo -e "${CYAN}正在安装审计组件...${RESET}"
            $PKG_INSTALL util-linux 2>/dev/null || true
            
            if ! command -v last >/dev/null 2>&1 && [[ "$PKG_MANAGER" == "apt" ]]; then
                 $PKG_INSTALL sysvinit-utils 2>/dev/null || true
            fi

            # 现代发行版 (Debian 12/13, Ubuntu 24.04/26.04) 适配 wtmpdb 与 PAM 模块
            if [[ "$PKG_MANAGER" == "apt" ]]; then
                 $PKG_INSTALL wtmpdb libpam-wtmpdb 2>/dev/null || $PKG_INSTALL wtmpdb 2>/dev/null || true
            fi
            
            if command -v last >/dev/null 2>&1 || command -v wtmpdb >/dev/null 2>&1; then
                echo -e "${GREEN}[√] 组件安装成功${RESET}"
            else
                echo -e "${RED}[!] 安装尝试结束，未能找回 last/wtmpdb 命令。${RESET}"
            fi
        else
            echo -e "${GRAY}已跳过安装，将尝试直接分析系统日志。${RESET}"
        fi
    fi

    # 动态检测可用命令 (增加绝对路径扫描)
    local LAST_CMD=""
    local LASTB_CMD=""

    # 1. 寻找 last / wtmpdb
    if command -v last >/dev/null 2>&1; then LAST_CMD="last";
    elif [ -x /usr/bin/last ]; then LAST_CMD="/usr/bin/last";
    elif [ -x /sbin/last ]; then LAST_CMD="/sbin/last";
    elif [ -x /usr/sbin/last ]; then LAST_CMD="/usr/sbin/last";
    elif command -v wtmpdb >/dev/null 2>&1; then LAST_CMD="wtmpdb last";
    fi

    # 2. 寻找 lastb
    if command -v lastb >/dev/null 2>&1; then LASTB_CMD="lastb";
    elif [ -x /usr/sbin/lastb ]; then LASTB_CMD="/usr/sbin/lastb";
    elif [ -x /sbin/lastb ]; then LASTB_CMD="/sbin/lastb";
    elif [ -n "$LAST_CMD" ] && [[ "$LAST_CMD" != *"wtmpdb"* ]] && [ -f /var/log/btmp ]; then
        LASTB_CMD="$LAST_CMD -f /var/log/btmp"
    fi

    local audit_rows audit_source audit_scope audit_notes audit_since audit_page=1 audit_size=20
    local audit_total audit_pages audit_action audit_query="" audit_new_query audit_new_size audit_filtered audit_count audit_ip
    local audit_state_dir=/var/lib/aurora/login-audit audit_notice=""
    while true; do
        clear
        echo -e "${BOLD}${CYAN}登录安全审计${RESET}"
        echo -e "${GREEN}最近 5 次成功登录:${RESET}"
        if [ -n "$LAST_CMD" ]; then
            $LAST_CMD -n 5 2>/dev/null | grep -vE "(reboot|wtmp begins|system boot)" | head -n 5
        else
            echo -e "${GRAY}未找到 'last' 或 'wtmpdb' 命令${RESET}"
        fi
        login_audit_collect "$LASTB_CMD" /var/log "$audit_state_dir/start"
        audit_filtered=$(printf '%s\n' "$audit_rows" | AUDIT_QUERY="$audit_query" awk 'NF && index($2, ENVIRON["AUDIT_QUERY"])')
        audit_total=$(printf '%s\n' "$audit_filtered" | awk 'NF {n++} END {print n+0}')
        audit_pages=$(((audit_total + audit_size - 1) / audit_size))
        [ "$audit_pages" -gt 0 ] || audit_pages=1
        [ "$audit_page" -le "$audit_pages" ] || audit_page=$audit_pages
        echo -e "\n${RED}失败登录来源 IP 统计:${RESET}"
        printf '来源: %s | 范围: %s\n' "$audit_source" "$audit_scope"
        printf '共 %s 个 IP | 第 %s/%s 页 | 每页 %s 个 | 搜索: %s\n' "$audit_total" "$audit_page" "$audit_pages" "$audit_size" "${audit_query:-全部}"
        printf '%s\n' "${audit_notes[@]}"
        [ -z "$audit_notice" ] || printf '%s\n' "$audit_notice"
        echo '数量为所选来源的失败记录数；不同来源可能记录同一事件，不叠加统计。'
        if [ -z "$audit_filtered" ]; then
            echo -e "${YELLOW}当前范围未提取到匹配 IP；请结合上述日志状态判断，不能据此认定没有攻击。${RESET}"
        else
            while read -r audit_count audit_ip; do
                printf '  %8s 条失败记录  %s\n' "$audit_count" "$audit_ip"
            done < <(printf '%s\n' "$audit_filtered" | sed -n "$(((audit_page - 1) * audit_size + 1)),$((audit_page * audit_size))p")
        fi
        echo '  1. 下一页  2. 上一页  3. 搜索 IP  4. 清除搜索'
        echo '  5. 刷新    6. 每页数量 (10-100)'
        echo '  7. 从现在重新统计 (保留原日志)'
        echo '  8. 备份后清空 btmp 并重新统计 (保留 auth.log/secure/journal)'
        echo '  0. 返回上一级'
        ui_menu_read audit_action "请输入选项" '[0-8]' || return 0
        case "$audit_action" in
            1) [ "$audit_page" -ge "$audit_pages" ] || audit_page=$((audit_page + 1)) ;;
            2) [ "$audit_page" -le 1 ] || audit_page=$((audit_page - 1)) ;;
            3) ui_read_field audit_new_query "输入完整 IP 或部分地址 (留空显示全部)" || continue
               audit_query=$audit_new_query; audit_page=1 ;;
            4) audit_query=""; audit_page=1 ;;
            5) : ;;
            6) ui_read_field audit_new_size "每页数量 (10-100，默认 $audit_size)" process_page_size_valid "$audit_size" || continue
               audit_size=$((10#$audit_new_size)); audit_page=1 ;;
            7|8)
                local audit_confirm audit_clear_message=""
                if [ "$audit_action" = 8 ]; then
                    ui_confirm_explicit audit_confirm "备份后清空 /var/log/btmp (所有服务的失败登录记录)，并从现在重新统计?"
                else
                    ui_confirm audit_confirm "将统计起点设为现在并保存，保留原日志? [y/N]: "
                fi
                case "$audit_confirm" in y|yes)
                    if [ "$audit_action" = 8 ] && ! audit_clear_message=$(login_audit_clear_btmp /var/log "$audit_state_dir/backups"); then
                        audit_notice="$audit_clear_message 操作失败，未更新统计起点。"
                        continue
                    fi
                    if login_audit_reset "$audit_state_dir" /var/log; then
                        audit_notice="${audit_clear_message} 统计起点已更新：$(date '+%F %T')；后续进入本页面也会使用此起点。"
                        audit_page=1; audit_query=""
                    else
                        audit_notice="${audit_clear_message} 无法保存统计起点；若 btmp 已清空，其备份仍保留，其他日志统计范围未改变。"
                    fi ;;
                esac ;;
            0) return 0 ;;
        esac
    done
}

# ---------- [重构] 系统监控中心 (入口) ----------
show_sys_monitor() {
  install_deps "ss" "awk" "ps"
  
  while true; do
    clear
    echo -e "${BOLD}${CYAN}系统监控中心${RESET}"
    echo -e "${CYAN}==================================================${RESET}"
    
    echo -e "  ${YELLOW}1.${RESET} 端口占用情况速查 (Port Usage)"
    echo -e "  ${YELLOW}2.${RESET} 进程资源占用分析 (分页/搜索/排序)"
    echo -e "  ${YELLOW}3.${RESET} 登录安全审计 (Last Login/Fail)"
    echo -e "  ${YELLOW}0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"

    ui_menu_read choice "请输入选项" "[0-3]" || return 0
    case $choice in
      1) sub_port_usage ;;
      2) sub_process_top ;;
      3) sub_login_audit ;;
      0) return 0 ;;
      *) echo -e "${RED}无效选项${RESET}"; sleep 1 ;;
    esac
  done
}

# ---------- 计划任务管理 (增强版: 增加脚本权限自动检测 + 向导退出逻辑) ----------
manage_crontab() {
  # --- 1. 入口检测 ---
  if ! command -v crontab >/dev/null 2>&1; then
    echo -e "${YELLOW}[!] 未检测到 Crontab 服务。${RESET}"
    ui_confirm install_choice "是否立即安装 Cron? [y/N] (q 取消): "
    if [[ "$install_choice" =~ ^[Yy]$ ]]; then
      check_pm_lock || return 1
      
      # 区分包名 (RHEL/Fedora/CentOS 全系包含 dnf5 均使用 cronie)
      local cron_pkg="cron"
      if [[ "$OS_FAMILY" == "rhel" ]]; then
          cron_pkg="cronie"
      fi
      
      echo -e "${CYAN}正在安装 $cron_pkg ...${RESET}"
      $PKG_UPDATE
      $PKG_INSTALL "$cron_pkg"
      
      systemctl enable $SVC_CRON
      systemctl start $SVC_CRON
      echo -e "${GREEN}[√] Cron 安装成功${RESET}"
    else
      return 0
    fi
  fi

  if ! systemctl is-active $SVC_CRON >/dev/null 2>&1; then
    systemctl start $SVC_CRON 2>/dev/null
    sleep 1
  fi

  # 提取真正的计划任务 (严格排除纯空行、任意缩进注释与环境变量设置)
  get_valid_cron_tasks() {
    crontab -l 2>/dev/null | grep -vE '^[[:space:]]*(#|$|[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=)'
  }

  while true; do
    clear
    echo -e "${BOLD}${CYAN}计划任务管理 (Crontab)${RESET}"
    echo -e "${CYAN}==================================================${RESET}"

    if command -v crontab >/dev/null 2>&1; then
      if systemctl is-active $SVC_CRON >/dev/null 2>&1; then
        cron_status="${GREEN}运行中 (Active)${RESET}"
      else
        cron_status="${RED}未运行 (Inactive)${RESET}"
      fi
      task_count=$(get_valid_cron_tasks | wc -l)
    else
      cron_status="${RED}未安装${RESET}"
      task_count="N/A"
    fi

    echo -e "服务状态: $cron_status | 当前任务数: ${GREEN}$task_count${RESET}"
    echo -e "${CYAN}--------------------------------------------------${RESET}"

    if [[ "$task_count" != "N/A" ]] && [ "$task_count" -gt 0 ]; then
      echo -e "${YELLOW}任务预览:${RESET}"
      get_valid_cron_tasks | head -n 3 | awk '{print "  " $0}'
      if [ "$task_count" -gt 3 ]; then echo -e "  ${GRAY}... (还有 $((task_count - 3)) 条)${RESET}"; fi
      echo -e "${CYAN}--------------------------------------------------${RESET}"
    fi

    echo -e "${YELLOW}  1.${RESET} 查看完整任务列表"
    echo -e "${YELLOW}  2.${RESET} 添加: 每日凌晨 3 点自动重启 (快捷)"
    echo -e "${YELLOW}  3.${RESET} 添加: 每周一凌晨 4 点清理日志 (快捷)"
    echo -e "${YELLOW}  4.${RESET} 添加: 自定义计划任务 ${GREEN}[向导模式]${RESET}"
    echo -e "${CYAN}--- 管理 ---${RESET}"
    echo -e "${YELLOW}  5.${RESET} 编辑: 手动编辑文件 (vi/nano)"
    echo -e "${YELLOW}  6.${RESET} ${RED}删除: 删除指定任务 [便捷]${RESET}"
    echo -e "${YELLOW}  7.${RESET} ${RED}清空: 删除所有任务${RESET}"
    echo -e "${CYAN}--- 维护 ---${RESET}"
    echo -e "${YELLOW}  8.${RESET} 备份: 导出当前任务列表"
    echo -e "${YELLOW}  9.${RESET} 恢复: 从备份文件导入"
    echo -e "${YELLOW} 10.${RESET} 日志: 查看 Crontab 运行日志"
    echo -e "${CYAN}--- 危险区域 ---${RESET}"
    echo -e "${YELLOW} 11.${RESET} ${RED}卸载 Cron 服务${RESET}"
    echo -e "${YELLOW}  0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"

    ui_menu_read c_choice "请输入选项" "[0-9]|1[01]" || return 0

    if [[ "$task_count" == "N/A" ]] && [[ "$c_choice" != "0" ]] && [[ "$c_choice" != "11" ]]; then
      echo -e "${RED}[!] 服务缺失，请选择卸载或返回。${RESET}"
      ui_pause "按 Enter 继续..."
      continue
    fi

    case $c_choice in
    1)
      echo -e "${CYAN}>>> 完整任务列表:${RESET}"
      [ "$task_count" -eq 0 ] && echo -e "${GRAY}(无任务)${RESET}" || get_valid_cron_tasks | nl -w2 -s'. '
      ui_pause "按 Enter 继续..."
      ;;
    2)
      local reboot_bin="/sbin/reboot"
      [ ! -x "$reboot_bin" ] && reboot_bin=$(command -v reboot 2>/dev/null || echo "reboot")
      local ret=0
      (
        crontab -l 2>/dev/null
        echo "0 3 * * * $reboot_bin"
      ) | awk '/^[[:space:]]*([#]|$)/ {print; next} /^[[:space:]]*[A-Za-z_][A-Za-z_0-9]*[[:space:]]*=/ {delete visited; print; next} !visited[$0]++' | crontab - || ret=$?
      if [ $ret -eq 0 ]; then
        echo -e "${GREEN}[√] 已添加每日重启任务${RESET}"
      else
        echo -e "${RED}[×] 添加每日重启任务失败 (错误码: $ret)${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    3)
      local clean_cmd=""
      if [ "$IS_PVE" = true ]; then
        # PVE 保护：禁止 autoremove 避免误删 PVE 内核元包
        clean_cmd="apt-get clean && journalctl --vacuum-time=3d"
      elif [[ "$OS_FAMILY" == "debian" ]]; then
        clean_cmd="apt-get autoremove -y && apt-get clean && journalctl --vacuum-time=3d"
      elif [[ "$OS_FAMILY" == "rhel" ]]; then
        if command -v dnf5 >/dev/null 2>&1; then
          clean_cmd="dnf5 clean all -y && journalctl --vacuum-time=3d"
        elif command -v dnf >/dev/null 2>&1; then
          clean_cmd="dnf autoremove -y && dnf clean all && journalctl --vacuum-time=3d"
        else
          clean_cmd="yum clean all && journalctl --vacuum-time=3d"
        fi
      else
        clean_cmd="journalctl --vacuum-time=3d"
      fi

      local new_cron="0 4 * * 1 $clean_cmd"
      local clean_ret=0
      (
        crontab -l 2>/dev/null
        echo "$new_cron"
      ) | awk '/^[[:space:]]*([#]|$)/ {print; next} /^[[:space:]]*[A-Za-z_][A-Za-z_0-9]*[[:space:]]*=/ {delete visited; print; next} !visited[$0]++' | crontab - || clean_ret=$?
      if [ $clean_ret -eq 0 ]; then
        echo -e "${GREEN}[√] 已添加系统日志与包清理任务: $new_cron${RESET}"
      else
        echo -e "${RED}[×] 添加清理任务失败 (错误码: $clean_ret)${RESET}"
      fi
      ui_pause "按 Enter 继续..."
      ;;
    4)
      echo -e "${CYAN}>>> 添加自定义任务 (向导模式)${RESET}"
      echo -e "  ${GREEN}1.${RESET} 每分钟 (* * * * *)"
      echo -e "  ${GREEN}2.${RESET} 每小时 (0 * * * *)"
      echo -e "  ${GREEN}3.${RESET} 每天 (0 0 * * *)"
      echo -e "  ${GREEN}4.${RESET} 每周 (0 0 * * 0)  ${GRAY}# 0=周日, 1=周一...${RESET}"
      echo -e "  ${GREEN}5.${RESET} 每月 (0 0 1 * *)"
      echo -e "  ${GREEN}6.${RESET} 重启时 (@reboot)"
      echo -e "  ${YELLOW}7.${RESET} 手动输入"
      echo -e "  ${YELLOW}0.${RESET} 返回"

      ui_menu_read cron_type "频率编号" "[0-7]" || return 0
      local cron_time=""

      # [修改] 重写辅助函数，支持 q 退出
      read_range() {
        local p="$1"
        local min="$2"
        local max="$3"
        local v
        while true; do
          IFS= read -r -p "$p ($min-$max) [q 取消]: " v || { echo "QUIT"; return 0; }
          # [新增] 退出检测
          if [[ "$v" =~ ^[qQ]$ ]]; then
            echo "QUIT"
            return 0
          fi

          if [[ "$v" =~ ^[0-9]{1,2}$ ]] && (( 10#$v >= min && 10#$v <= max )); then
            echo "$((10#$v))"
            return 0
          fi
          echo -e "${RED}无效输入，请输入 $min-$max 之间的数字或 q 退出${RESET}" >&2
        done
      }

      case $cron_type in
      1) cron_time="* * * * *" ;;
      2)
        m=$(read_range "第几分钟" 0 59)
        if [ "$m" == "QUIT" ]; then continue; fi # [新增] 检测退出
        cron_time="$m * * * *"
        ;;
      3)
        h=$(read_range "小时" 0 23)
        if [ "$h" == "QUIT" ]; then continue; fi
        m=$(read_range "分钟" 0 59)
        if [ "$m" == "QUIT" ]; then continue; fi
        cron_time="$m $h * * *"
        ;;
      4)
        w=$(read_range "星期(0-6)" 0 6)
        if [ "$w" == "QUIT" ]; then continue; fi
        h=$(read_range "小时" 0 23)
        if [ "$h" == "QUIT" ]; then continue; fi
        m=$(read_range "分钟" 0 59)
        if [ "$m" == "QUIT" ]; then continue; fi
        cron_time="$m $h * * $w"
        ;;
      5)
        d=$(read_range "日期(1-31)" 1 31)
        if [ "$d" == "QUIT" ]; then continue; fi
        h=$(read_range "小时" 0 23)
        if [ "$h" == "QUIT" ]; then continue; fi
        m=$(read_range "分钟" 0 59)
        if [ "$m" == "QUIT" ]; then continue; fi
        cron_time="$m $h $d * *"
        ;;
      6) cron_time="@reboot" ;;
      7)
      ui_read_field cron_time "输入表达式" ui_nonempty "" true || continue
        if [ "$cron_time" == "0" ]; then continue; fi
        ;;
      0) continue ;;
      *)
        echo -e "${RED}无效选择${RESET}"
        sleep 1
        continue
        ;;
      esac

      echo -e "时间: ${YELLOW}$cron_time${RESET}"
      ui_read_field cron_cmd "请输入命令 (绝对路径)" ui_nonempty "" true || continue
      if [ "$cron_cmd" == "0" ] || [ -z "$cron_cmd" ]; then continue; fi

      # --- [增强] 脚本权限自动检测 ---
      cmd_path=$(echo "$cron_cmd" | awk '{print $1}')
      if [ -f "$cmd_path" ]; then
        if [ ! -x "$cmd_path" ]; then
          echo -e "${YELLOW}警告: 脚本 $cmd_path 没有执行权限 (x)${RESET}"
          ui_confirm chmod_confirm "是否自动赋予执行权限? [y/N] (q 取消): "
          if [[ "$chmod_confirm" =~ ^[Yy]$ ]]; then
            chmod +x "$cmd_path"
            echo -e "${GREEN}[√] 已赋予 +x 权限${RESET}"
          else
            echo -e "${RED}[!] 任务可能无法运行，请注意。${RESET}"
          fi
        fi
      fi
      # ------------------------------

      # --- [修复] 处理 Crontab 中的 % 转义 (未转义的 % 会被 cron 解释为换行符及 stdin) ---
      local escaped_cron_cmd="$cron_cmd"
      if [[ "$cron_cmd" == *"%"* ]]; then
        escaped_cron_cmd=$(printf '%s\n' "$cron_cmd" | sed -E ':a; s/(^|[^\\])%/\1\\%/g; ta')
        if [[ "$escaped_cron_cmd" != "$cron_cmd" ]]; then
          echo -e "${YELLOW}[!] 检测到命令包含 Crontab 特殊字符 '%'，已自动转换为安全转义格式:${RESET}"
          echo -e "    ${CYAN}$escaped_cron_cmd${RESET}"
        fi
      fi
      # -----------------------------------------------------------------------------------

      echo -e "${CYAN}添加任务: ${GREEN}$cron_time $escaped_cron_cmd${RESET}"
      ui_confirm confirm_add "确认? [y/N] (q 取消): "
      if [[ "$confirm_add" =~ ^[Yy]$ ]]; then
        local add_ret=0
        (
          crontab -l 2>/dev/null
          echo "$cron_time $escaped_cron_cmd"
        ) | awk '/^[[:space:]]*([#]|$)/ {print; next} /^[[:space:]]*[A-Za-z_][A-Za-z_0-9]*[[:space:]]*=/ {delete visited; print; next} !visited[$0]++' | crontab - || add_ret=$?
        if [ $add_ret -eq 0 ]; then
          echo -e "${GREEN}[√] 计划任务添加成功${RESET}"
        else
          echo -e "${RED}[×] 计划任务写入失败 (错误码: $add_ret)${RESET}"
        fi
      fi
      ui_pause "按 Enter 继续..."
      ;;
    5)
      if command -v nano >/dev/null; then export EDITOR=nano; else export EDITOR=vi; fi
      crontab -e
      ;;
    6)
      if [ "$task_count" -gt 0 ]; then
        local tmp="${TEMP_DIR}/cron_tasks.tmp"
        get_valid_cron_tasks >"$tmp"
        nl -w2 -s'. ' "$tmp"
        ui_read_field dn "删除编号 (0返回): " ui_any_value "" true || { dn=0; }
        if [[ "$dn" =~ ^[0-9]+$ ]] && [ "$dn" -gt 0 ] && [ "$dn" -le "$(wc -l < "$tmp" 2>/dev/null)" ]; then
          local target_line=$(sed -n "${dn}p" "$tmp")
          if [ -n "$target_line" ]; then
            local full_tmp="${TEMP_DIR}/cron_full.tmp"
            crontab -l 2>/dev/null > "$full_tmp"
            awk -v t="$target_line" 'BEGIN{deleted=0} {if ($0 == t && !deleted) {deleted=1; next} print $0}' "$full_tmp" | crontab -
            rm -f "$full_tmp"
            echo -e "${GREEN}[√] 删除成功 (已保留系统环境变量与配置注释)${RESET}"
          fi
        fi
        rm -f "$tmp"
      else echo -e "${YELLOW}无任务${RESET}"; fi
      ui_pause "按 Enter 继续..."
      ;;
    7)
      ui_confirm c "确认清空? [y/N] (q 取消): "
      [[ "$c" =~ ^[Yy]$ ]] && crontab -r && echo -e "${GREEN}[√] 已清空${RESET}"
      ui_pause "按 Enter 继续..."
      ;;
    8)
      bf="/root/cron_bak_$(date +%Y%m%d).txt"
      crontab -l >"$bf" 2>/dev/null
      echo -e "${GREEN}[√] 备份至 $bf${RESET}"
      ui_pause "按 Enter 继续..."
      ;;
    9)
      ui_read_field rf "备份文件路径" ui_nonempty || continue
      [ -f "$rf" ] && crontab "$rf" && echo -e "${GREEN}[√] 恢复成功${RESET}" || echo -e "${RED}文件不存在${RESET}"
      ui_pause "按 Enter 继续..."
      ;;
    10)
      if [ -f /var/log/syslog ]; then
        grep "CRON" /var/log/syslog | tail -20
      elif [ -f /var/log/cron.log ]; then
        tail -20 /var/log/cron.log
      else journalctl -u $SVC_CRON -n 20 --no-pager 2>/dev/null; fi
      ui_pause "按 Enter 继续..."
      ;;
    11)
      ui_confirm un_c "[提示] 确定卸载 Cron 服务? [y/N] (q 取消): "
      if [[ "$un_c" =~ ^[Yy]$ ]]; then
        systemctl stop $SVC_CRON
        # 发行版适配包名 (RHEL/Fedora/CentOS 全系包含 dnf5 均使用 cronie)
        local cron_pkg="cron"
        if [[ "$OS_FAMILY" == "rhel" ]]; then
            cron_pkg="cronie"
        fi
        $PKG_REMOVE "$cron_pkg"
        rm -rf /var/spool/cron/crontabs
        echo -e "${GREEN}[√] 已卸载${RESET}"
        SKIP_PAUSE=true
        return 0
      fi
      ;;
    0)
      SKIP_PAUSE=true
      return 0
      ;;
    *)
      echo -e "${RED}无效${RESET}"
      sleep 1
      ;;
    esac
  done
}

# --- [10. 工具] 系统软件源切换 ---
change_mirror() {
  while true; do
    clear
    echo -e "${BOLD}${CYAN}系统国内源切换${RESET}"
    echo -e "${CYAN}==================================================${RESET}"
    echo -e "  ${GREEN}1${RESET}) 切换 系统软件源 (APT/YUM/DNF)"
    echo -e "  ${GREEN}2${RESET}) 切换 Docker 镜像源"
    echo -e "  ${YELLOW}0.${RESET} 返回主菜单"
    echo -e "${CYAN}==================================================${RESET}"
    ui_menu_read choice "请输入选项" "[0-2]" || return 0
    case "$choice" in
    1)
      while true; do
        clear
        echo -e "${CYAN}>>> 切换系统软件源 (System Repos)${RESET}"
        echo -e "检测到系统: ${GREEN}$OS_ID${RESET} (包管理器: $PKG_MANAGER, PVE: $IS_PVE)"
        
        # --- 检测当前源 ---
        current_source="未知/混合"
        local deb822_file=""
        if [ -d /etc/apt/sources.list.d ]; then
          for sf in /etc/apt/sources.list.d/*.sources; do
            [ -f "$sf" ] || continue
            if grep -qE "URIs:[[:space:]]*https?://.*(ubuntu|debian|aliyun|tsinghua|tencent|ustc)" "$sf" 2>/dev/null; then
              deb822_file="$sf"
              break
            fi
          done
        fi
        [ -z "$deb822_file" ] && [ -f /etc/apt/sources.list.d/ubuntu.sources ] && deb822_file="/etc/apt/sources.list.d/ubuntu.sources"
        [ -z "$deb822_file" ] && [ -f /etc/apt/sources.list.d/debian.sources ] && deb822_file="/etc/apt/sources.list.d/debian.sources"

        if [[ "$PKG_MANAGER" == "apt" ]]; then
           local check_targets=("/etc/apt/sources.list")
           [ -n "$deb822_file" ] && check_targets=("$deb822_file" "/etc/apt/sources.list")
           for src_f in "${check_targets[@]}"; do
             if [ -f "$src_f" ]; then
               if grep -q "aliyun.com" "$src_f"; then current_source="阿里云 (Aliyun)"; break;
               elif grep -q "tsinghua.edu.cn" "$src_f"; then current_source="清华大学 (Tsinghua)"; break;
               elif grep -q "tencent.com" "$src_f"; then current_source="腾讯云 (Tencent)"; break;
               elif grep -q "ustc.edu.cn" "$src_f"; then current_source="中科大 (USTC)"; break;
               elif grep -qE "deb.debian.org|archive.ubuntu.com" "$src_f"; then current_source="官方源 (Official)"; break;
               fi
             fi
           done
        elif [[ "$PKG_MANAGER" == "yum" || "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "dnf5" ]]; then
           repo_file=$(find /etc/yum.repos.d -maxdepth 1 \( -name "*.repo" \) 2>/dev/null | head -1)
           if [ -n "$repo_file" ] && [ -f "$repo_file" ]; then
               if grep -q "aliyun.com" /etc/yum.repos.d/*.repo 2>/dev/null; then current_source="阿里云 (Aliyun)";
               elif grep -q "tsinghua.edu.cn" /etc/yum.repos.d/*.repo 2>/dev/null; then current_source="清华大学 (Tsinghua)";
               elif grep -q "tencent.com" /etc/yum.repos.d/*.repo 2>/dev/null; then current_source="腾讯云 (Tencent)";
               elif grep -q "ustc.edu.cn" /etc/yum.repos.d/*.repo 2>/dev/null; then current_source="中科大 (USTC)";
               else current_source="官方源/默认"; fi
           fi
        fi
        echo -e "当前识别源: ${YELLOW}$current_source${RESET}"
        [ -n "$deb822_file" ] && echo -e "源格式标准: ${GREEN}DEB822 现代化格式 ($deb822_file)${RESET}"
        # ------------------

        echo -e "${CYAN}--------------------------------------------------${RESET}"
        echo -e "  ${GREEN}1${RESET}) 阿里云 (Alibaba Cloud) [推荐]"
        echo -e "  ${GREEN}2${RESET}) 清华大学 (Tsinghua University)"
        echo -e "  ${GREEN}3${RESET}) 腾讯云 (Tencent Cloud)"
        echo -e "  ${GREEN}4${RESET}) ${RED}恢复官方默认源${RESET}"
        echo -e "  ${YELLOW}0.${RESET} 返回上一级"
        echo -e "${CYAN}==================================================${RESET}"
        
        ui_menu_read mirror_provider "请选择源提供商" "[0-4]" || return 0
        
        if [ "$mirror_provider" == "0" ]; then break; fi
        
        if [[ ! "$mirror_provider" =~ ^[1-4]$ ]]; then
            echo -e "${RED}无效选项${RESET}"
            sleep 1
            continue
        fi

        # 处理 Debian/Ubuntu (APT)
        if [[ "$PKG_MANAGER" == "apt" ]]; then
            if [[ "$OS_ID" != debian && "$OS_ID" != ubuntu ]]; then
              echo -e "${RED}[×] 衍生发行版不能套用 Debian/Ubuntu 仓库，未修改源配置${RESET}"
              continue
            fi
            if [ "$IS_PVE" = true ]; then
              local pve_channel_confirm=""
              ui_confirm_explicit pve_channel_confirm "确认将 PVE 企业源切换为无订阅通道? (保留 Ceph 企业源)"
              [ "$pve_channel_confirm" = yes ] || continue
            fi
            # 精准获取 codename (避免错误 fallback 到 jammy 造成跨版本自毁)
            local codename=""
            if [ -f /etc/os-release ]; then
                codename=$(grep -oP '^VERSION_CODENAME=\K\S+' /etc/os-release 2>/dev/null || grep -oP '^UBUNTU_CODENAME=\K\S+' /etc/os-release 2>/dev/null)
            fi
            if [ -z "$codename" ] && command -v lsb_release >/dev/null 2>&1; then
                codename=$(lsb_release -cs 2>/dev/null)
            fi

            local is_deb_eol=false
            local is_ubuntu_eol=false
            local v_id="${OS_VERSION_ID%%.*}"

            # 严格根据发行版大版本精准映射与 EOL 识别
            if [ "$OS_ID" == "debian" ] || [ "$OS_ID" == "raspbian" ]; then
                case "$v_id" in
                  13) codename="trixie" ;;
                  12) codename="bookworm" ;;
                  11) codename="bullseye" ;;
                  10) codename="buster"; is_deb_eol=true ;;
                  9)  codename="stretch"; is_deb_eol=true ;;
                  8)  codename="jessie"; is_deb_eol=true ;;
                  *)  echo -e "${RED}[×] 未验证的 Debian 版本，停止换源${RESET}"; continue ;;
                esac
                if [[ "$codename" =~ ^(buster|stretch|jessie|wheezy)$ ]]; then
                  is_deb_eol=true
                fi
            elif [ "$OS_ID" == "ubuntu" ]; then
                case "$OS_VERSION_ID" in
                  "26.04") codename="resolute" ;;
                  "24.04") codename="noble" ;;
                  "22.04") codename="jammy" ;;
                  "20.04") codename="focal" ;;
                  "18.04") codename="bionic"; is_ubuntu_eol=false ;;
                  "16.04") codename="xenial"; is_ubuntu_eol=true ;;
                  "14.04") codename="trusty"; is_ubuntu_eol=true ;;
                  *) echo -e "${RED}[×] 未验证的 Ubuntu 版本，停止换源${RESET}"; continue ;;
                esac
                if [[ "$codename" =~ ^(xenial|trusty|precise)$ ]]; then
                  is_ubuntu_eol=true
                fi
            fi
            
            local url=""
            local security_url=""
            local mirror_host=""

            # 区分 Ubuntu 架构 (官方主仓库仅服务 x86/amd64，所有非 x86 架构均必须使用 ubuntu-ports)
            local ubuntu_base="ubuntu"
            if [ "$OS_ID" == "ubuntu" ]; then
                local sys_arch=""
                if command -v dpkg >/dev/null 2>&1; then
                    sys_arch=$(dpkg --print-architecture 2>/dev/null)
                fi
                [ -z "$sys_arch" ] && sys_arch=$(uname -m)
                if [[ "$sys_arch" != "amd64" && "$sys_arch" != "i386" && "$sys_arch" != "x86_64" && "$sys_arch" != "i686" ]]; then
                    ubuntu_base="ubuntu-ports"
                fi
            fi
            
            case $mirror_provider in
              1) # 阿里云
                 mirror_host="mirrors.aliyun.com"
                 if [ "$OS_ID" == "debian" ]; then
                   if [ "$is_deb_eol" = true ]; then
                     url="https://mirrors.aliyun.com/debian-archive/debian"
                     security_url="https://mirrors.aliyun.com/debian-archive/debian-security"
                   else
                     url="https://mirrors.aliyun.com/debian"
                     security_url="https://mirrors.aliyun.com/debian-security"
                   fi
                 elif [ "$OS_ID" == "raspbian" ]; then
                   url="https://mirrors.aliyun.com/raspbian/raspbian"
                   security_url="https://mirrors.aliyun.com/debian-security"
                 elif [ "$OS_ID" == "ubuntu" ]; then
                   if [ "$is_ubuntu_eol" = true ]; then
                     url="https://mirrors.aliyun.com/old-releases/ubuntu"
                     security_url="https://mirrors.aliyun.com/old-releases/ubuntu"
                   else
                     url="https://mirrors.aliyun.com/${ubuntu_base}"
                     security_url="https://mirrors.aliyun.com/${ubuntu_base}"
                   fi
                 fi
                 ;;
              2) # 清华
                 mirror_host="mirrors.tuna.tsinghua.edu.cn"
                 if [ "$OS_ID" == "debian" ]; then
                   if [ "$is_deb_eol" = true ]; then
                     url="https://mirrors.tuna.tsinghua.edu.cn/debian-archive/debian"
                     security_url="https://mirrors.tuna.tsinghua.edu.cn/debian-archive/debian-security"
                   else
                     url="https://mirrors.tuna.tsinghua.edu.cn/debian"
                     security_url="https://mirrors.tuna.tsinghua.edu.cn/debian-security"
                   fi
                 elif [ "$OS_ID" == "raspbian" ]; then
                   url="https://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian"
                   security_url="https://mirrors.tuna.tsinghua.edu.cn/debian-security"
                 elif [ "$OS_ID" == "ubuntu" ]; then
                   if [ "$is_ubuntu_eol" = true ]; then
                     url="https://mirrors.tuna.tsinghua.edu.cn/ubuntu-old-releases/ubuntu"
                     security_url="https://mirrors.tuna.tsinghua.edu.cn/ubuntu-old-releases/ubuntu"
                   else
                     url="https://mirrors.tuna.tsinghua.edu.cn/${ubuntu_base}"
                     security_url="https://mirrors.tuna.tsinghua.edu.cn/${ubuntu_base}"
                   fi
                 fi
                 ;;
              3) # 腾讯
                 mirror_host="mirrors.cloud.tencent.com"
                 if [ "$OS_ID" == "debian" ]; then
                   if [ "$is_deb_eol" = true ]; then
                     url="https://mirrors.cloud.tencent.com/debian-archive/debian"
                     security_url="https://mirrors.cloud.tencent.com/debian-archive/debian-security"
                   else
                     url="https://mirrors.cloud.tencent.com/debian"
                     security_url="https://mirrors.cloud.tencent.com/debian-security"
                   fi
                 elif [ "$OS_ID" == "raspbian" ]; then
                   url="https://mirrors.cloud.tencent.com/raspbian/raspbian"
                   security_url="https://mirrors.cloud.tencent.com/debian-security"
                 elif [ "$OS_ID" == "ubuntu" ]; then
                   url="https://mirrors.cloud.tencent.com/${ubuntu_base}"
                   security_url="https://mirrors.cloud.tencent.com/${ubuntu_base}"
                 fi
                 ;;
              4) # 官方
                 if [ "$OS_ID" == "debian" ]; then
                   if [ "$is_deb_eol" = true ]; then
                     url="http://archive.debian.org/debian"
                     security_url="http://archive.debian.org/debian-security"
                   else
                     url="http://deb.debian.org/debian"
                     security_url="http://security.debian.org/debian-security"
                   fi
                 elif [ "$OS_ID" == "raspbian" ]; then
                   url="http://raspbian.raspberrypi.org/raspbian"
                   security_url="http://security.debian.org/debian-security"
                 elif [ "$OS_ID" == "ubuntu" ]; then
                   if [ "$is_ubuntu_eol" = true ]; then
                     url="http://old-releases.ubuntu.com/ubuntu"
                     security_url="http://old-releases.ubuntu.com/ubuntu"
                   elif [ "$ubuntu_base" == "ubuntu-ports" ]; then
                     url="http://ports.ubuntu.com/ubuntu-ports"
                     security_url="http://ports.ubuntu.com/ubuntu-ports"
                   else
                     url="http://archive.ubuntu.com/ubuntu"
                     security_url="http://security.ubuntu.com/ubuntu"
                   fi
                 fi
                 ;;
            esac

            check_pm_lock || continue
            command -v python3 >/dev/null 2>&1 || { $PKG_INSTALL python3 || continue; }
            local mirror_backup
            mirror_backup=$(backup_source_transaction) || continue
            # EOL 归档源处理：由于 Debian 归档库签名早已过期，必须配置 Check-Valid-Until false
            if [ "$is_deb_eol" = true ]; then
              echo -e "${YELLOW}[!] Debian EOL 归档版本 ($codename)，仅对归档条目调整 Valid-Until，不关闭其他源的检查${RESET}"
            fi

            echo -e "${CYAN}正在配置 APT 软件源 (代号: $codename, 路径: ${ubuntu_base:-debian})...${RESET}"

            # --- 收集系统所有潜在的 APT 发行版源文件 (支持多 .sources 与多 .list 完整遍历) ---
            local apt_targets=()
            [ -f /etc/apt/sources.list ] && apt_targets+=("/etc/apt/sources.list")
            if [ -d /etc/apt/sources.list.d ]; then
              for sf in /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list; do
                [ -f "$sf" ] && apt_targets+=("$sf")
              done
            fi

            local apt_rewritten_count=0
            local cur_target
            local apt_has_error=false
            for cur_target in "${apt_targets[@]}"; do
              local apt_ret=0
              rewrite_apt_mirror_file "$cur_target" "$url" "$security_url" 2>/dev/null || apt_ret=$?
              if [ "$apt_ret" -eq 0 ]; then
                apt_rewritten_count=$((apt_rewritten_count + 1))
                echo -e "  ${GREEN}✓ 已成功更新源文件: $cur_target${RESET}"
              elif [ "$apt_ret" -eq 2 ]; then
                # 未包含官方主源 URI，安全跳过
                :
              else
                echo -e "  ${RED}✗ 更新主源配置文件失败: $cur_target (错误码 $apt_ret)${RESET}"
                apt_has_error=true
                break
              fi
            done

            if [ "$apt_has_error" = true ]; then
              rollback_source_transaction "$mirror_backup"
              echo -e "${RED}[×] 重写 APT 官方源文件失败，为防止混合损坏状态已回滚全部源配置${RESET}"
              continue
            fi

            if [ "$apt_rewritten_count" -gt 0 ]; then
                echo -e "${GREEN}[√] 共更新 $apt_rewritten_count 个 APT 软件源配置文件${RESET}"
            elif [ ! -f /etc/apt/sources.list ]; then
                if [ "$OS_ID" == "debian" ] || [ "$OS_ID" == "raspbian" ]; then
                    local components="main contrib non-free"
                    if [[ "$codename" == "bookworm" || "$codename" == "trixie" || "$codename" == "sid" ]]; then
                        components="main contrib non-free non-free-firmware"
                    fi
                    if [ "$codename" == "sid" ]; then
                      cat >/etc/apt/sources.list <<EOF
# Generated by Aurora VPS Script (Debian Sid / Unstable)
deb $url sid $components
EOF
                    elif [ "$is_deb_eol" = true ]; then
                      # 旧版 Debian (<=10) 归档安全套件路径采用 codename/updates
                      cat >/etc/apt/sources.list <<EOF
# Generated by Aurora VPS Script (Debian EOL Archive: $codename)
deb $url $codename $components
deb $security_url $codename/updates $components
EOF
                    else
                      cat >/etc/apt/sources.list <<EOF
# Generated by Aurora VPS Script
deb $url $codename $components
deb $url $codename-updates $components
deb $url $codename-backports $components
deb $security_url $codename-security $components
EOF
                    fi
                elif [ "$OS_ID" == "ubuntu" ]; then
                    local components="main restricted universe multiverse"
                    if [ "$is_ubuntu_eol" = true ]; then
                      cat >/etc/apt/sources.list <<EOF
# Generated by Aurora VPS Script (Ubuntu Old-Releases: $codename)
deb $url $codename $components
deb $security_url $codename-security $components
EOF
                    else
                      cat >/etc/apt/sources.list <<EOF
# Generated by Aurora VPS Script
deb $url $codename $components
deb $url $codename-updates $components
deb $url $codename-backports $components
deb $security_url $codename-security $components
EOF
                    fi
                fi
            else
                rollback_source_transaction "$mirror_backup"
                echo -e "${RED}[×] 未能在 APT 源文件中识别到有效的主仓库配置，已保持原配置不变${RESET}"
                continue
            fi

            # --- 树莓派专属源保护 ---
            if [ "$OS_ID" == "raspbian" ] && [ -f /etc/apt/sources.list.d/raspi.list ]; then
                echo -e "${CYAN}>>> 检测到树莓派环境，保留并维护硬件专属仓库 /etc/apt/sources.list.d/raspi.list${RESET}"
                if [ "$mirror_provider" == "1" ]; then
                    sed -i -E 's|http://archive.raspberrypi.org/debian/|https://mirrors.aliyun.com/raspberrypi/|g' /etc/apt/sources.list.d/raspi.list 2>/dev/null
                elif [ "$mirror_provider" == "2" ]; then
                    sed -i -E 's|http://archive.raspberrypi.org/debian/|https://mirrors.tuna.tsinghua.edu.cn/raspberrypi/|g' /etc/apt/sources.list.d/raspi.list 2>/dev/null
                fi
            fi

            # --- PVE (Proxmox VE) 专有源与企业源处理 ---
            if [ "$IS_PVE" = true ]; then
                echo -e "${CYAN}>>> 检测到 Proxmox VE 环境，正在维护 PVE 专属软件源...${RESET}"
                # 1. 禁用企业订阅源 (逐 Stanza 精准禁用，保留 Ceph no-subscription)
                
                # 2. 规范补充无订阅源 (pve-no-subscription)，附带 Signed-By 密钥与去重保护
                setup_pve_no_subscription_source "$codename" "$mirror_provider" || { rollback_source_transaction "$mirror_backup"; return 1; }
                disable_pve_enterprise_sources || { rollback_source_transaction "$mirror_backup"; return 1; }
            fi
            
            echo -e "${CYAN}>>> 正在更新 APT 软件源缓存...${RESET}"
            if ! apt-get update; then
              rollback_source_transaction "$mirror_backup"
              echo -e "${RED}[×] 新源缓存更新失败，未报告切换成功${RESET}"
              continue
            fi

        # 处理 CentOS/RHEL/Rocky/AlmaLinux/Fedora (YUM/DNF/DNF5)
        elif [[ "$PKG_MANAGER" == "yum" || "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "dnf5" ]]; then
            case "$OS_ID" in
              centos|rocky|rockylinux|almalinux|alma|fedora) ;;
              *) echo -e "${RED}[×] 未验证此 RHEL 衍生版的仓库布局，停止换源${RESET}"; continue ;;
            esac
            check_pm_lock || continue
            local mirror_backup
            mirror_backup=$(backup_source_transaction) || continue
            if [ "$mirror_provider" == "4" ]; then
               echo -e "${YELLOW}RHEL系官方恢复建议从备份还原 /etc/yum.repos.d/backup/${RESET}"
               if [ -d /etc/yum.repos.d/backup ]; then
                   local restore_repo restore_patterns=() restored_count=0 restore_repo_ok=true
                   case "$OS_ID" in
                     centos) restore_patterns=(/etc/yum.repos.d/backup/CentOS-*.repo /etc/yum.repos.d/backup/centos*.repo) ;;
                     rocky|rockylinux) restore_patterns=(/etc/yum.repos.d/backup/rocky*.repo) ;;
                     almalinux|alma) restore_patterns=(/etc/yum.repos.d/backup/almalinux*.repo) ;;
                     fedora) restore_patterns=(/etc/yum.repos.d/backup/fedora*.repo) ;;
                   esac
                   for restore_repo in "${restore_patterns[@]}"; do
                     [ -f "$restore_repo" ] || continue
                     cp -p "$restore_repo" /etc/yum.repos.d/ || restore_repo_ok=false
                     ((restored_count++))
                   done
                   if [ "$restore_repo_ok" = false ] || [ "$restored_count" = 0 ]; then
                     rollback_source_transaction "$mirror_backup"
                     continue
                   fi
                   echo -e "${GREEN}[√] 已从备份恢复原 Repo 文件${RESET}"
                   $PKG_UPDATE || { rollback_source_transaction "$mirror_backup"; continue; }
               else
                   echo -e "${RED}[!] 未找到备份目录 /etc/yum.repos.d/backup${RESET}"
               fi
               ui_pause "按 Enter 继续..."
               continue
            fi

            local mirror_host=""
            case $mirror_provider in
               1) mirror_host="mirrors.aliyun.com" ;;
               2) mirror_host="mirrors.tuna.tsinghua.edu.cn" ;;
               3) mirror_host="mirrors.cloud.tencent.com" ;;
            esac

            # 首份保留备份机制：若已存在备份则不自覆盖，防止二次换源冲掉官方原始源
            if [ ! -d /etc/yum.repos.d/backup ] || [ -z "$(ls -A /etc/yum.repos.d/backup/*.repo 2>/dev/null)" ]; then
                echo -e "${CYAN}>>> 正在备份官方初始 Repo 文件至 /etc/yum.repos.d/backup (首份锁定保护)...${RESET}"
                mkdir -p /etc/yum.repos.d/backup
                cp /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2>/dev/null
            else
                echo -e "${GRAY}>>> 检测到已有官方初始 Repo 备份，保留原始版本不予自覆盖${RESET}"
            fi
            
            # 检测是否为 CentOS Stream
            local is_centos_stream=false
            if grep -qi "CentOS Stream" /etc/os-release 2>/dev/null; then is_centos_stream=true; fi

            # --- 适配不同 RHEL 衍生系统 ---
            # 1. CentOS 7 / 8 (已 EOL 的 Linux 传统分支，严格区分版本归档，拒绝 8.2009 假目录)
            if [ "$OS_ID" == "centos" ] && [ "$is_centos_stream" = false ]; then
                if [[ "$OS_VERSION_ID" =~ ^7 ]]; then
                    echo -e "${YELLOW}检测到已 EOL 的 CentOS 7，正在重定向至 Vault 7.9.2009 归档源...${RESET}"
                    sed -i -e "s|^[[:space:]]*mirrorlist=|#mirrorlist=|g" \
                           -e "s|^#*[[:space:]]*baseurl=http[s]*://mirror\.centos\.org/centos/\$releasever|baseurl=https://${mirror_host}/centos-vault/7.9.2009|g" \
                           -e "s|^#*[[:space:]]*baseurl=http[s]*://mirror\.centos\.org|baseurl=https://${mirror_host}/centos-vault|g" \
                           -e "s|^#*[[:space:]]*baseurl=http[s]*://[^/]+/centos-vault|baseurl=https://${mirror_host}/centos-vault|g" \
                           /etc/yum.repos.d/CentOS-*.repo 2>/dev/null
                elif [[ "$OS_VERSION_ID" =~ ^8 ]]; then
                    echo -e "${YELLOW}检测到已 EOL 的 CentOS 8 Linux，正在重定向至 Vault 8.5.2111 归档源...${RESET}"
                    sed -i -e "s|^[[:space:]]*mirrorlist=|#mirrorlist=|g" \
                           -e "s|^#*[[:space:]]*baseurl=http[s]*://mirror\.centos\.org/centos/\$releasever|baseurl=https://${mirror_host}/centos-vault/8.5.2111|g" \
                           -e "s|^#*[[:space:]]*baseurl=http[s]*://mirror\.centos\.org|baseurl=https://${mirror_host}/centos-vault|g" \
                           -e "s|^#*[[:space:]]*baseurl=http[s]*://[^/]+/centos-vault|baseurl=https://${mirror_host}/centos-vault|g" \
                           /etc/yum.repos.d/CentOS-*.repo 2>/dev/null
                fi

            # 2. CentOS Stream (8 / 9 / 10)
            elif [ "$OS_ID" == "centos" ] && [ "$is_centos_stream" = true ]; then
                echo -e "${CYAN}正在配置 CentOS Stream $OS_VERSION_ID 国内镜像源...${RESET}"
                if [[ "$OS_VERSION_ID" =~ ^8 ]]; then
                    sed -i -E -e "s|^[[:space:]]*mirrorlist=|#mirrorlist=|g" \
                           -e "s@^#*[[:space:]]*baseurl=https?://(mirror\.centos\.org|[^/]+/centos-vault/centos)@baseurl=https://${mirror_host}/centos-vault/centos@g" \
                           /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/centos*.repo 2>/dev/null
                else
                    sed -i -E -e "s|^[[:space:]]*metalink=|#metalink=|g" \
                           -e "s@^#*[[:space:]]*baseurl=https?://(mirror\.stream\.centos\.org|[^/]+/centos-stream)@baseurl=https://${mirror_host}/centos-stream@g" \
                           /etc/yum.repos.d/centos*.repo 2>/dev/null
                fi

            # 3. Rocky Linux (8/9/10) - 单次整合正则替换，彻底消除多重替换导致的 /rocky/rocky 重叠
            elif [[ "$OS_ID" == "rocky" || "$OS_ID" == "rockylinux" ]]; then
                echo -e "${CYAN}正在配置 Rocky Linux 国内镜像源...${RESET}"
                sed -i -E -e "s|^[[:space:]]*mirrorlist=|#mirrorlist=|g" \
                          -e "s@^#*[[:space:]]*baseurl=https?://(dl[.]rockylinux[.]org/[$]contentdir(/rocky)?|download[.]rockylinux[.]org/pub/rocky|[^/]+/pub/rocky|[^/]+/rocky)@baseurl=https://${mirror_host}/rocky@g" \
                          /etc/yum.repos.d/rocky*.repo 2>/dev/null

            # 4. AlmaLinux (8/9/10) - 宽容空格匹配与单次整合正则替换
            elif [[ "$OS_ID" == "almalinux" || "$OS_ID" == "alma" ]]; then
                echo -e "${CYAN}正在配置 AlmaLinux 国内镜像源...${RESET}"
                sed -i -E -e "s|^[[:space:]]*mirrorlist=|#mirrorlist=|g" \
                          -e "s@^#*[[:space:]]*baseurl=https?://(repo\.almalinux\.org/almalinux|[^/]+/almalinux)@baseurl=https://${mirror_host}/almalinux@g" \
                          /etc/yum.repos.d/almalinux*.repo 2>/dev/null

            # 5. Fedora
            elif [ "$OS_ID" == "fedora" ]; then
                echo -e "${CYAN}正在配置 Fedora 国内镜像源...${RESET}"
                sed -i -E -e 's|^[[:space:]]*metalink=|#metalink=|g' \
                       -e "s@^#*[[:space:]]*baseurl=https?://(download\.example/pub/fedora/linux|[^/]+/fedora)@baseurl=https://${mirror_host}/fedora@g" \
                       /etc/yum.repos.d/fedora*.repo 2>/dev/null
            fi
            
            echo -e "${GREEN}[√] Repo 替换完成，正在生成元数据缓存...${RESET}"
            check_pm_lock || return 1
            if ! $PKG_UPDATE; then
              rollback_source_transaction "$mirror_backup"
              echo -e "${RED}[×] Repo 缓存更新失败，已尝试恢复本次源备份${RESET}"
              continue
            fi
        else
            echo -e "${RED}[!] 暂不支持此系统的软件源切换${RESET}"
        fi
        
        echo -e "${GREEN}[√] 切换完成${RESET}"
        ui_pause "按 Enter 继续..."
      done
      ;;
    2)
      ensure_docker || continue
      while true; do
        clear
        echo -e "${CYAN}>>> 切换 Docker 镜像源${RESET}"
        echo -e "当前 Docker 状态: ${GREEN}$(systemctl is-active docker 2>/dev/null)${RESET}"
        
        docker_source="官方源 (Official)"
        if [ -f /etc/docker/daemon.json ]; then
            if grep -q "registry-mirrors" /etc/docker/daemon.json; then
                local first_mirror=$(grep -oP 'https://[^"]+' /etc/docker/daemon.json | head -1)
                docker_source="自定义/加速源 (${first_mirror:-已配置} ...)"
            fi
        fi
        echo -e "当前检测源: ${YELLOW}$docker_source${RESET}"

        echo -e "${CYAN}--------------------------------------------------${RESET}"
        echo -e "  ${GREEN}1${RESET}) 设置/更新 最新国内镜像加速源 (增量合并配置)"
        echo -e "  ${GREEN}2${RESET}) ${RED}恢复 Docker 官方源 (清除加速配置)${RESET}"
        echo -e "  ${YELLOW}0.${RESET} 返回上一级"
        echo -e "${CYAN}==================================================${RESET}"
        
        ui_menu_read docker_choice "请输入选项" "[0-2]" || return 0
        case $docker_choice in
        1)
          mkdir -p /etc/docker
          local daemon_bak=""
          if [ -f /etc/docker/daemon.json ]; then
            daemon_bak="/etc/docker/daemon.json.bak.$(date +%Y%m%d%H%M%S)"
            cp /etc/docker/daemon.json "$daemon_bak"
          fi
          
          echo -e "${CYAN}正在安全合并加速源至 /etc/docker/daemon.json (保留用户已有其它配置)...${RESET}"
          local merge_ok=false

          if command -v python3 >/dev/null 2>&1; then
            if python3 -c "
import json, os, sys

path = '/etc/docker/daemon.json'
tmp_path = '/etc/docker/daemon.json.tmp'
data = {}
if os.path.exists(path):
    try:
        with open(path, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except Exception as e:
        sys.stderr.write('JSON 解析错误: %s\n' % e)
        sys.exit(1)

if not isinstance(data, dict):
    sys.stderr.write('daemon.json 顶层不是有效的 JSON Object\n')
    sys.exit(1)

data['registry-mirrors'] = [
    'https://docker.m.daocloud.io',
    'https://hub-mirror.c.163.com',
    'https://mirror.baidubce.com'
]

with open(tmp_path, 'w', encoding='utf-8') as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

os.replace(tmp_path, path)
"; then
              merge_ok=true
            else
              echo -e "${RED}[×] 现有 /etc/docker/daemon.json 格式损坏或无法解析！${RESET}"
              echo -e "${YELLOW}为保护现有配置不被覆盖丢失，操作已中止。请先排查修复其 JSON 语法。${RESET}"
            fi
          elif [ ! -f /etc/docker/daemon.json ]; then
            # 纯最小化系统且此前无任何配置文件，安全创建初始配置
            cat >/etc/docker/daemon.json <<EOF
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}
EOF
            merge_ok=true
          else
            echo -e "${RED}[×] 系统缺少 python3 工具，且已存在自定义 /etc/docker/daemon.json！${RESET}"
            echo -e "${YELLOW}为避免盲目覆盖清除您的自定义 Docker 配置，操作已中止。请先安装 python3 后重试。${RESET}"
          fi

          if [ "$merge_ok" = true ]; then
            echo -e "${GREEN}[√] Docker 镜像配置更新完成${RESET}"
            echo -e "${CYAN}正在重启 Docker 服务并验证运行状态...${RESET}"
            systemctl daemon-reload
            if systemctl restart docker; then
              echo -e "${GREEN}[√] Docker 已重启生效${RESET}"
            else
              echo -e "${RED}[!] Docker 重启失败！正在自动回滚原配置...${RESET}"
              if [ -n "$daemon_bak" ] && [ -f "$daemon_bak" ]; then
                cp -f "$daemon_bak" /etc/docker/daemon.json
              else
                rm -f /etc/docker/daemon.json
              fi
              systemctl daemon-reload
              systemctl restart docker 2>/dev/null
              echo -e "${YELLOW}[√] 已安全恢复修改前的状态${RESET}"
            fi
          fi
          ui_pause "按 Enter 继续..."
          ;;
        2)
          echo -e "${YELLOW}即将清除 Docker 镜像加速配置，恢复默认为官方源。${RESET}"
          ui_confirm confirm_reset "确认执行吗? [y/N] (q 取消): "
          if [[ "$confirm_reset" =~ ^[Yy]$ ]]; then
             if [ -f /etc/docker/daemon.json ]; then
                 command -v python3 >/dev/null 2>&1 || { echo -e "${RED}[×] 缺少 Python，未改动 Docker 配置${RESET}"; continue; }
                 local reset_bak="/etc/docker/daemon.json.reset-bak.$$"
                 cp -p /etc/docker/daemon.json "$reset_bak" || continue
                 if ! python3 -c "
import json, os, sys
path = '/etc/docker/daemon.json'
try:
    with open(path, 'r', encoding='utf-8') as f:
        d = json.load(f)
    if not isinstance(d, dict):
        raise ValueError('daemon.json must be an object')
    d.pop('registry-mirrors', None)
    with open(path + '.tmp', 'w', encoding='utf-8') as f:
        json.dump(d, f, indent=2, ensure_ascii=False)
    os.replace(path + '.tmp', path)
except Exception as error:
    print(error, file=sys.stderr)
    sys.exit(1)
"; then
                   echo -e "${RED}[×] Docker 配置解析/写入失败，未执行重启${RESET}"
                   continue
                 fi
                 echo -e "${GREEN}[√] 已安全移除 registry-mirrors${RESET}"
                 systemctl daemon-reload
                 if systemctl restart docker && docker info >/dev/null 2>&1; then
                   echo -e "${GREEN}[√] Docker 恢复官方源完成${RESET}"
                 else
                   cp -p "$reset_bak" /etc/docker/daemon.json
                   systemctl restart docker 2>/dev/null
                   echo -e "${RED}[×] Docker 重启失败，已尝试恢复旧配置，备份保留: $reset_bak${RESET}"
                 fi
             else
                 echo -e "${GRAY}未发现 daemon.json 配置文件，无需操作。${RESET}"
             fi
          else
             echo -e "${YELLOW}已取消${RESET}"
          fi
          ui_pause "按 Enter 继续..."
          ;;
        0) break ;;
        *) echo -e "${RED}无效选项${RESET}"; sleep 1 ;;
        esac
      done
      ;;
    0) return 0 ;; # 返回主菜单
    *) ;;
    esac
  done
}

# ---------- 常用端口与连通性检测 (场景C: 出站TCP连通性 + 本地监听与防火墙审计 + TCP连接耗时测试) ----------
manage_port_check() {
  # 辅助函数: 精准中英文字符显示宽度计算与对齐填充 (兼容东亚宽字符与ANSI颜色代码)
  pad_cell() {
    local str="$1"
    local target_w="$2"
    local align="${3:-left}"

    local clean
    clean=$(printf "%b" "$str" | sed -r "s/\x1B\[[0-9;]*[mK]//g")
    local w
    w=$(echo -n "$clean" | LC_ALL=C.UTF-8 wc -L 2>/dev/null)
    [ -z "$w" ] && w=${#clean}
    local pad_len=$((target_w - w))
    [ "$pad_len" -lt 0 ] && pad_len=0
    local spaces=""
    [ "$pad_len" -gt 0 ] && spaces=$(printf "%*s" "$pad_len" "")

    if [ "$align" = "right" ]; then
      printf "%s%b" "$spaces" "$str"
    else
      printf "%b%s" "$str" "$spaces"
    fi
  }

  # 辅助函数: 单个端口出站 TCP 连通性探测 (防整数溢出 + 参数化调用，杜绝Shell命令注入)
  probe_outbound_port() {
    local host=$1
    local port=$2
    local timeout_sec=${3:-3}

    # 1. 严格长度与格式白名单校验 (防止64位整数溢出逃逸及注入)
    if [[ ! "$port" =~ ^[0-9]{1,5}$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
      echo "1|INVALID_PORT"
      return 1
    fi
    if [[ ! "$host" =~ ^[A-Za-z0-9.:_-]+$ ]] || [ ${#host} -gt 255 ]; then
      echo "1|INVALID_HOST"
      return 1
    fi

    local start_ns end_ns diff_ms
    start_ns=$(date +%s%N 2>/dev/null)
    # 使用无注入传参，防止执行任意注入代码
    if timeout "$timeout_sec" bash -c '</dev/tcp/"$1"/"$2"' _ "$host" "$port" >/dev/null 2>&1; then
      end_ns=$(date +%s%N 2>/dev/null)
      if [[ "$start_ns" =~ ^[0-9]{10,}$ ]] && [[ "$end_ns" =~ ^[0-9]{10,}$ ]]; then
        diff_ms=$(( (end_ns - start_ns) / 1000000 ))
        [ "$diff_ms" -lt 0 ] && diff_ms=0
        echo "0|$diff_ms"
      else
        echo "0|OK"
      fi
    else
      echo "1|FAIL"
    fi
  }

  # 辅助函数: 解析单个端口或端口段是否匹配目标端口 (仅支持 TCP，严格拒绝 UDP)
  check_port_match() {
    local target_p=$1
    local spec=$2
    
    if [[ "$spec" =~ /udp$ ]]; then
      return 1
    fi
    spec="${spec%/tcp}"

    if [[ "$spec" =~ ^[0-9]{1,5}$ ]]; then
      [ "$spec" -eq "$target_p" ] && return 0
    elif [[ "$spec" =~ ^([0-9]{1,5})[:\-]([0-9]{1,5})$ ]]; then
      local p_s="${BASH_REMATCH[1]}"
      local p_e="${BASH_REMATCH[2]}"
      if [ "$target_p" -ge "$p_s" ] && [ "$target_p" -le "$p_e" ]; then
        return 0
      fi
    fi
    return 1
  }

  # 辅助函数: 解析 UFW 应用配置文件或常见应用名
  check_ufw_app_match() {
    local target_p=$1
    local app_name=$2

    case "$app_name" in
      OpenSSH|SSH|ssh) [ "$target_p" -eq 22 ] && return 0 ;;
      'Nginx HTTP'|'Apache') [ "$target_p" -eq 80 ] && return 0 ;;
      'Nginx HTTPS') [ "$target_p" -eq 443 ] && return 0 ;;
      'Nginx Full'|'Apache Full') { [ "$target_p" -eq 80 ] || [ "$target_p" -eq 443 ]; } && return 0 ;;
    esac

    if [ -d /etc/ufw/applications.d ]; then
      local app_file
      for app_file in /etc/ufw/applications.d/*; do
        [ -f "$app_file" ] || continue
        if grep -qi "\[$app_name\]" "$app_file" 2>/dev/null; then
          local ports_line
          ports_line=$(sed -n "/\[$app_name\]/,/\[/p" "$app_file" | grep -i "^ports=" | head -n 1)
          if [ -n "$ports_line" ]; then
            local p_defs="${ports_line#*=}"
            IFS=',' read -ra p_arr <<<"$p_defs"
            for p_item in "${p_arr[@]}"; do
              p_item=$(echo "$p_item" | tr -d '[:space:]')
              check_port_match "$target_p" "$p_item" && return 0
            done
          fi
        fi
      done
    fi

    return 1
  }

  # 辅助函数: UFW 规则单栈审计 (First-Match 顺序生效 + 来源限制继承)
  audit_ufw_rules() {
    local port=$1
    local is_v6=$2

    if ! command -v ufw >/dev/null 2>&1; then
      echo "INACTIVE"
      return 0
    fi

    local ufw_verbose
    if ! ufw_verbose=$(ufw status verbose 2>/dev/null); then
      echo "UNKNOWN"
      return 0
    fi

    if echo "$ufw_verbose" | grep -q "Status: inactive"; then
      echo "INACTIVE"
      return 0
    fi
    if ! echo "$ufw_verbose" | grep -q "Status: active"; then
      echo "UNKNOWN"
      return 0
    fi

    local def_incoming
    def_incoming=$(echo "$ufw_verbose" | grep -iE "^Default:.*incoming" | awk '{print tolower($2)}')

    local has_source_limit=false

    while IFS= read -r line; do
      [[ -z "$line" || "$line" =~ ^(To|--|Status:|Default:|Logging:) ]] && continue

      local is_rule_v6=false
      [[ "$line" =~ \(v6\) ]] && is_rule_v6=true
      if [ "$is_v6" = true ] && [ "$is_rule_v6" = false ]; then
        continue
      fi
      if [ "$is_v6" = false ] && [ "$is_rule_v6" = true ]; then
        continue
      fi

      if [[ "$line" =~ ^(.*)[[:space:]]+(ALLOW|LIMIT|DENY|REJECT)([[:space:]]+IN)?[[:space:]]+(.*)$ ]]; then
        local to_part="${BASH_REMATCH[1]}"
        local action="${BASH_REMATCH[2]}"
        local from_part="${BASH_REMATCH[4]}"

        to_part="${to_part% (v6)}"
        to_part=$(echo "$to_part" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
        from_part=$(echo "$from_part" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')

        if [[ "$to_part" =~ /udp$ ]]; then
          continue
        fi

        local port_matched=false
        if check_port_match "$port" "$to_part"; then
          port_matched=true
        elif check_ufw_app_match "$port" "$to_part"; then
          port_matched=true
        fi

        if [ "$port_matched" = true ]; then
          local is_any_from=false
          if [[ "$from_part" =~ ^(Anywhere|Anywhere[[:space:]]*\(v6\)|0\.0\.0\.0/0|::/0)$ ]]; then
            is_any_from=true
          fi

          if [[ "$action" =~ ^(DENY|REJECT)$ ]]; then
            if [ "$is_any_from" = true ]; then
              echo "BLOCKED"
              return 0
            else
              has_source_limit=true
            fi
          elif [[ "$action" =~ ^(ALLOW|LIMIT)$ ]]; then
            if [ "$is_any_from" = true ]; then
              if [ "$has_source_limit" = true ]; then
                echo "RESTRICTED"
              else
                echo "OPEN"
              fi
              return 0
            else
              has_source_limit=true
            fi
          fi
        fi
      fi
    done <<<"$ufw_verbose"

    if [ "$has_source_limit" = true ]; then
      echo "RESTRICTED"
      return 0
    fi

    if [[ "$def_incoming" =~ (deny|reject) ]]; then
      echo "BLOCKED"
    elif [[ "$def_incoming" =~ allow ]]; then
      echo "OPEN"
    else
      echo "UNKNOWN"
    fi
  }

  # 辅助函数: iptables 规则链逐行审计 (取反优先检测 + NEW状态限定 + 来源允许继承 + 来源端口识别)
  audit_iptables_chain() {
    local port=$1
    local cmd_name=${2:-iptables}

    if ! command -v "$cmd_name" >/dev/null 2>&1; then
      echo "INACTIVE"
      return 0
    fi

    local rules_output
    if ! rules_output=$("$cmd_name" -S INPUT 2>/dev/null); then
      echo "UNKNOWN"
      return 0
    fi
    [ -z "$rules_output" ] && { echo "INACTIVE"; return 0; }

    local def_policy
    def_policy=$(echo "$rules_output" | grep -E "^-P INPUT " | awk '{print $3}')
    [ -z "$def_policy" ] && def_policy="ACCEPT"

    local has_source_limit=false
    local has_source_allow=false

    while IFS= read -r rule; do
      [[ ! "$rule" =~ ^-A\ INPUT ]] && continue

      # 1. 优先检测是否存在否定修饰符 (!)
      if [[ "$rule" =~ [[:space:]]![[:space:]] || "$rule" =~ ^-A[[:space:]]+INPUT[[:space:]]+![[:space:]] ]]; then
        echo "UNKNOWN"
        return 0
      fi

      local tokens=($rule)
      local t_len=${#tokens[@]}
      local jump_target=""
      local source_spec=""
      local dest_spec=""
      local in_iface=""
      local proto_spec=""
      local state_spec=""
      local sport_spec=""
      local is_target_port=false
      local has_port_match=false
      local has_unknown_match=false

      for ((idx=0; idx<t_len; idx++)); do
        local tk="${tokens[idx]}"
        if [[ "$tk" = "-j" || "$tk" = "-g" || "$tk" = "--goto" ]] && [ $((idx + 1)) -lt "$t_len" ]; then
          jump_target="${tokens[$((idx + 1))]}"
        elif [ "$tk" = "-s" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          source_spec="${tokens[$((idx + 1))]}"
        elif [ "$tk" = "-d" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          dest_spec="${tokens[$((idx + 1))]}"
        elif [ "$tk" = "-i" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          in_iface="${tokens[$((idx + 1))]}"
        elif [ "$tk" = "-p" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          proto_spec="${tokens[$((idx + 1))]}"
        elif [[ "$tk" =~ ^(--state|--ctstate)$ ]] && [ $((idx + 1)) -lt "$t_len" ]; then
          state_spec="${tokens[$((idx + 1))]}"
        elif [[ "$tk" =~ ^(--sport|--sports)$ ]] && [ $((idx + 1)) -lt "$t_len" ]; then
          sport_spec="${tokens[$((idx + 1))]}"
        elif [ "$tk" = "--dport" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          has_port_match=true
          local dp="${tokens[$((idx + 1))]}"
          check_port_match "$port" "$dp" && is_target_port=true
        elif [ "$tk" = "--dports" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          has_port_match=true
          local dps="${tokens[$((idx + 1))]}"
          IFS=',' read -ra dp_list <<<"$dps"
          for single_dp in "${dp_list[@]}"; do
            check_port_match "$port" "$single_dp" && is_target_port=true
          done
        elif [ "$tk" = "-m" ] && [ $((idx + 1)) -lt "$t_len" ]; then
          local mod="${tokens[$((idx + 1))]}"
          if [[ ! "$mod" =~ ^(tcp|state|conntrack|multiport|comment)$ ]]; then
            has_unknown_match=true
          fi
        elif [[ "$tk" =~ ^(--tcp-flags|--syn|--tcp-option|--ctstatus|--ctdir|--ctorig.*|--ctrepl.*|--ctexpire|--ports)$ ]]; then
          has_unknown_match=true
        fi
      done

      # 2. 检查协议
      if [ -n "$proto_spec" ] && [ "$proto_spec" != "tcp" ]; then
        continue
      fi

      # 3. 检查连接状态: 入站新连接必须包含 NEW
      local state_allows_new=true
      if [ -n "$state_spec" ]; then
        if [[ ! "$state_spec" =~ NEW && ! "$state_spec" =~ ALL ]]; then
          state_allows_new=false
        fi
      fi

      # 不匹配 NEW 的规则对正常新连接的所有动作都不生效。
      [ "$state_allows_new" = false ] && continue

      # 4. 检查无目标端口限定的通用规则
      if [ "$has_port_match" = false ]; then
        if [ "$has_unknown_match" = true ]; then
          echo "UNKNOWN"
          return 0
        fi
        if [ "$jump_target" = "ACCEPT" ] && [ -n "$source_spec" ] && [ "$source_spec" != "0.0.0.0/0" ] && [ "$source_spec" != "::/0" ]; then
          [ "$state_allows_new" = true ] && has_source_allow=true
          continue
        fi

        if [[ "$jump_target" =~ ^(DROP|REJECT)$ ]] && { [ -n "$source_spec" ] || [ -n "$dest_spec" ] || [ -n "$sport_spec" ] || [ -n "$in_iface" ]; }; then
          has_source_limit=true
          continue
        fi

        local is_pure_general=true
        [ -n "$source_spec" ] && is_pure_general=false
        [ -n "$dest_spec" ] && is_pure_general=false
        [ -n "$in_iface" ] && is_pure_general=false
        [ -n "$sport_spec" ] && is_pure_general=false
        [ "$has_unknown_match" = true ] && is_pure_general=false

        if [ "$is_pure_general" = true ]; then
          if [[ "$jump_target" =~ ^(DROP|REJECT)$ ]]; then
            if [ "$has_source_allow" = true ]; then
              echo "RESTRICTED"
            else
              echo "BLOCKED"
            fi
            return 0
          elif [ "$jump_target" = "ACCEPT" ]; then
            if [ "$state_allows_new" = true ]; then
              if [ "$has_source_limit" = true ]; then
                echo "RESTRICTED"
              else
                echo "OPEN"
              fi
              return 0
            fi
          fi
        fi

        if [ -n "$jump_target" ] && [[ ! "$jump_target" =~ ^(ACCEPT|DROP|REJECT|LOG|RETURN)$ ]]; then
          echo "UNKNOWN"
          return 0
        fi
        continue
      fi

      # 5. 匹配到目标端口
      if [ "$is_target_port" = true ]; then
        if [ "$has_unknown_match" = true ]; then
          echo "UNKNOWN"
          return 0
        fi

        if [ -n "$in_iface" ] && [ "$in_iface" != "+" ]; then
          has_source_limit=true
          continue
        fi

        if [ -n "$dest_spec" ] && [ "$dest_spec" != "0.0.0.0/0" ] && [ "$dest_spec" != "::/0" ]; then
          has_source_limit=true
          continue
        fi

        if [ -n "$sport_spec" ]; then
          has_source_limit=true
          continue
        fi

        local is_global_src=true
        if [ -n "$source_spec" ] && [ "$source_spec" != "0.0.0.0/0" ] && [ "$source_spec" != "::/0" ]; then
          is_global_src=false
        fi

        if [[ "$jump_target" =~ ^(DROP|REJECT)$ ]]; then
          if [ "$is_global_src" = true ]; then
            if [ "$has_source_allow" = true ]; then
              echo "RESTRICTED"
            else
              echo "BLOCKED"
            fi
            return 0
          else
            has_source_limit=true
          fi
        elif [ "$jump_target" = "ACCEPT" ]; then
          if [ "$state_allows_new" = false ]; then
            continue
          fi

          if [ "$is_global_src" = true ]; then
            if [ "$has_source_limit" = true ]; then
              echo "RESTRICTED"
            else
              echo "OPEN"
            fi
            return 0
          else
            has_source_limit=true
          fi
        elif [ -n "$jump_target" ] && [ "$jump_target" != "LOG" ] && [ "$jump_target" != "RETURN" ]; then
          echo "UNKNOWN"
          return 0
        fi
      fi
    done <<<"$rules_output"

    if [ "$has_source_allow" = true ]; then
      echo "RESTRICTED"
      return 0
    fi

    if [ "$has_source_limit" = true ]; then
      echo "RESTRICTED"
      return 0
    fi

    if [ "$def_policy" = "DROP" ] || [ "$def_policy" = "REJECT" ]; then
      echo "BLOCKED"
    elif echo "$rules_output" | grep -qE -- "-j (DOCKER|KUBE|FORWARD_IN)"; then
      echo "UNKNOWN"
    else
      echo "OPEN"
    fi
  }

  # 辅助函数: Firewalld 多 Zone 独立审计 (优先级升序判定 + 来源类型全覆盖 + 无端口来源阻断识别)
  audit_firewalld_port() {
    local port=$1
    if ! command -v firewall-cmd >/dev/null 2>&1; then
      echo "INACTIVE"
      return 0
    fi
    if ! systemctl is-active firewalld >/dev/null 2>&1; then
      echo "INACTIVE"
      return 0
    fi

    local zones_raw
    if ! zones_raw=$(firewall-cmd --get-active-zones 2>/dev/null); then
      echo "UNKNOWN"
      return 0
    fi
    local zones=() line
    while IFS= read -r line; do
      [[ "$line" =~ ^[A-Za-z0-9_-]+$ ]] && zones+=("$line")
    done <<<"$zones_raw"
    if [ ${#zones[@]} -eq 0 ]; then
      local default_zone
      if ! default_zone=$(firewall-cmd --get-default-zone 2>/dev/null) || [ -z "$default_zone" ]; then
        echo "UNKNOWN"
        return 0
      fi
      zones+=("$default_zone")
    fi

    local open_zones=() blocked_zones=() z
    for z in "${zones[@]}"; do
      local target rich ports services
      if ! target=$(firewall-cmd --zone="$z" --get-target 2>/dev/null) ||
         ! rich=$(firewall-cmd --zone="$z" --list-rich-rules 2>/dev/null) ||
         ! ports=$(firewall-cmd --zone="$z" --list-ports 2>/dev/null) ||
         ! services=$(firewall-cmd --zone="$z" --list-services 2>/dev/null); then
        echo "UNKNOWN"
        return 0
      fi

      # 将普通端口/服务放行放在零优先级 rich allow 后、正优先级前。
      # 未解析的条件在其实际执行位置返回 UNKNOWN，不能覆盖更早的终结规则。
      local records="" zp svc ordinary_open=false
      for zp in $ports; do
        check_port_match "$port" "$zp" && ordinary_open=true
      done
      for svc in $services; do
        case "$svc:$port" in
          ssh:22|http:80|https:443|dns:53|mysql:3306|postgresql:5432|redis:6379)
            ordinary_open=true ;;
          *)
            # 自定义服务的端口定义可能覆盖本端口，不能当作不存在。
            records+=$'0\t3\tUNKNOWN\n' ;;
        esac
      done
      [ "$ordinary_open" = true ] && records+=$'0\t3\tOPEN\n'

      local rrule priority rank action spec
      while IFS= read -r rrule; do
        [ -z "$rrule" ] && continue
        if [[ "$rrule" =~ protocol=\"([^\"]+)\" ]] && [ "${BASH_REMATCH[1]}" != tcp ]; then
          continue
        fi
        if [[ "$rrule" =~ (^|[[:space:]])port[[:space:]]+port=\"([^\"]+)\" ]]; then
          spec="${BASH_REMATCH[2]}"
          check_port_match "$port" "$spec" || continue
        fi

        priority=0
        if [[ "$rrule" =~ priority=\"(-?[0-9]+)\" ]]; then
          priority="${BASH_REMATCH[1]}"
        fi
        action=UNKNOWN
        if [[ "$rrule" =~ (^|[[:space:]])(drop|reject)([[:space:]]|$) ]]; then
          action=BLOCKED
        elif [[ "$rrule" =~ (^|[[:space:]])accept([[:space:]]|$) ]]; then
          action=OPEN
        fi
        # 地址族、来源/目标、服务、限速及其他元素需要完整规则解释器。
        # 此处保守处理，不把带条件的规则推断成全局放行或阻断。
        if [[ "$rrule" =~ (^|[[:space:]])(source|destination|family=|service|limit|protocol[[:space:]]|source-port|forward-port|icmp|masquerade) ]]; then
          action=UNKNOWN
        fi
        rank=0
        if [ "$priority" -eq 0 ]; then
          case "$action" in BLOCKED) rank=1 ;; OPEN) rank=2 ;; *) rank=0 ;; esac
        fi
        records+="${priority}"$'\t'"${rank}"$'\t'"${action}"$'\n'
      done <<<"$rich"

      # zone target 是未命中其他规则后的最终策略。
      if [ "$target" = ACCEPT ]; then
        records+=$'32768\t0\tOPEN\n'
      elif [[ "$target" = default || "$target" = DROP || "$target" = REJECT ]]; then
        records+=$'32768\t0\tBLOCKED\n'
      else
        records+=$'32768\t0\tUNKNOWN\n'
      fi

      local ordered decision="" key previous_priority="" previous_action=""
      ordered=$(printf '%s' "$records" | sort -k1,1n -k2,2n)
      while IFS=$'\t' read -r priority rank action; do
        [ -z "$priority" ] && continue
        if [ -z "$decision" ]; then
          decision="$action"
          previous_priority="$priority"
          previous_action="$action"
          continue
        fi
        # 非零相同优先级的顺序未定义；冲突时不作确定结论。
        if [ "$previous_priority" != 0 ] && [ "$priority" = "$previous_priority" ]; then
          [ "$action" != "$previous_action" ] && decision=UNKNOWN
          continue
        fi
        break
      done <<<"$ordered"
      case "$decision" in
        OPEN) open_zones+=("$z") ;;
        BLOCKED) blocked_zones+=("$z") ;;
        *) echo "UNKNOWN"; return 0 ;;
      esac
    done

    if [ ${#open_zones[@]} -gt 0 ] && [ ${#blocked_zones[@]} -eq 0 ]; then
      echo "OPEN"
    elif [ ${#open_zones[@]} -eq 0 ] && [ ${#blocked_zones[@]} -gt 0 ]; then
      echo "BLOCKED"
    elif [ ${#open_zones[@]} -gt 0 ] && [ ${#blocked_zones[@]} -gt 0 ]; then
      echo "MULTI:${open_zones[0]}|${blocked_zones[0]}"
    else
      echo "UNKNOWN"
    fi
  }

  # 辅助函数: 综合防火墙状态审计 (杜绝查询失败掩盖 + nftables 失败保留 UNKNOWN)
  audit_firewall_status() {
    local port=$1
    local is_v4=$2
    local is_v6=$3

    # 1. 检测原生 nftables hook input (若命令存在但查询失败，或规则集含input hook，坚决返回 UNKNOWN)
    if command -v nft >/dev/null 2>&1; then
      local nft_rules
      if ! nft_rules=$(nft list ruleset 2>/dev/null); then
        echo "UNKNOWN"
        return 0
      else
        if echo "$nft_rules" | grep -qE "type filter hook input"; then
          echo "UNKNOWN"
          return 0
        fi
      fi
    fi

    # 2. 检测 UFW
    if command -v ufw >/dev/null 2>&1; then
      local ufw_v4="INACTIVE"
      local ufw_v6="INACTIVE"
      local ufw_checked=false

      if [ "$is_v4" = true ]; then
        ufw_v4=$(audit_ufw_rules "$port" false)
        [ "$ufw_v4" != "INACTIVE" ] && ufw_checked=true
      fi
      if [ "$is_v6" = true ]; then
        ufw_v6=$(audit_ufw_rules "$port" true)
        [ "$ufw_v6" != "INACTIVE" ] && ufw_checked=true
      fi

      if [ "$ufw_checked" = true ]; then
        if [ "$is_v4" = true ] && [ "$is_v6" = true ]; then
          if [ "$ufw_v4" = "$ufw_v6" ]; then
            echo "$ufw_v4"
          else
            echo "DUAL:v4_${ufw_v4}|v6_${ufw_v6}"
          fi
        elif [ "$is_v6" = true ]; then
          echo "$ufw_v6"
        else
          echo "$ufw_v4"
        fi
        return 0
      fi
    fi

    # 3. 检测 Firewalld
    if command -v firewall-cmd >/dev/null 2>&1; then
      local fwd_res
      fwd_res=$(audit_firewalld_port "$port")
      if [ "$fwd_res" != "INACTIVE" ]; then
        echo "$fwd_res"
        return 0
      fi
    fi

    # 4. 检测 iptables / ip6tables
    local ipt_v4="INACTIVE"
    local ipt_v6="INACTIVE"
    local ipt_checked=false

    if command -v iptables >/dev/null 2>&1; then
      if [ "$is_v4" = true ]; then
        ipt_v4=$(audit_iptables_chain "$port" iptables)
        [ "$ipt_v4" != "INACTIVE" ] && ipt_checked=true
      fi
    fi

    if command -v ip6tables >/dev/null 2>&1; then
      if [ "$is_v6" = true ]; then
        ipt_v6=$(audit_iptables_chain "$port" ip6tables)
        [ "$ipt_v6" != "INACTIVE" ] && ipt_checked=true
      fi
    fi

    if [ "$ipt_checked" = true ]; then
      if [ "$is_v4" = true ] && [ "$is_v6" = true ]; then
        if [ "$ipt_v4" = "$ipt_v6" ]; then
          echo "$ipt_v4"
        else
          echo "DUAL:v4_${ipt_v4}|v6_${ipt_v6}"
        fi
      elif [ "$is_v6" = true ]; then
        echo "$ipt_v6"
      else
        echo "$ipt_v4"
      fi
      return 0
    fi

    # 5. 若已安装防火墙工具但查询全部失败，返回 UNKNOWN
    if command -v ufw >/dev/null 2>&1 || command -v firewall-cmd >/dev/null 2>&1 || command -v iptables >/dev/null 2>&1; then
      if command -v ufw >/dev/null 2>&1 && ! ufw status 2>/dev/null | grep -q "Status: inactive"; then
        echo "UNKNOWN"
        return 0
      fi
      if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active firewalld >/dev/null 2>&1; then
        echo "UNKNOWN"
        return 0
      fi
    fi

    echo "INACTIVE"
  }

  # 辅助函数: 审计单个本地端口 (严格识别 127.0.0.0/8、::1、通配与非回环，区分双栈)
  audit_single_local_port() {
    local p=$1
    local desc=$2

    local listen_raw
    listen_raw=$(ss -H -tln 2>/dev/null | awk '{print $4}' | grep -E ":$p$")

    local is_wildcard=false
    local is_v4_loop=false
    local is_v6_loop=false
    local is_specific=false
    local is_v4_listen=false
    local is_v6_listen=false
    local specific_ips=()

    if [ -n "$listen_raw" ]; then
      while IFS= read -r addr_entry; do
        [ -z "$addr_entry" ] && continue
        local ip_part="${addr_entry%:$p}"
        ip_part="${ip_part#[}"
        ip_part="${ip_part%]}"

        if [ "$ip_part" = "0.0.0.0" ] || [ "$ip_part" = "*" ]; then
          is_wildcard=true
          is_v4_listen=true
        elif [ "$ip_part" = "::" ]; then
          is_wildcard=true
          is_v6_listen=true
        elif [[ "$ip_part" =~ ^127\.([0-9]{1,3}\.){2}[0-9]{1,3}$ ]]; then
          is_v4_loop=true
          is_v4_listen=true
        elif [ "$ip_part" = "::1" ]; then
          is_v6_loop=true
          is_v6_listen=true
        else
          is_specific=true
          if [[ "$ip_part" =~ : ]]; then
            is_v6_listen=true
          else
            is_v4_listen=true
          fi
          specific_ips+=("$ip_part")
        fi
      done <<<"$listen_raw"
    fi

    local listen_code="NONE"
    local listen_disp=""
    if [ "$is_wildcard" = true ]; then
      listen_code="WILDCARD"
      listen_disp="${GREEN}通配 (所有地址)${RESET}"
    elif [ "$is_specific" = true ] && { [ "$is_v4_loop" = true ] || [ "$is_v6_loop" = true ]; }; then
      listen_code="MIXED"
      listen_disp="${YELLOW}混合 (回环+定向)${RESET}"
    elif [ "$is_specific" = true ]; then
      listen_code="SPECIFIC"
      listen_disp="${CYAN}定向 (${specific_ips[0]})${RESET}"
    elif [ "$is_v4_loop" = true ] && [ "$is_v6_loop" = true ]; then
      listen_code="LOOPBACK"
      listen_disp="${YELLOW}仅回环 (v4/v6)${RESET}"
    elif [ "$is_v4_loop" = true ]; then
      listen_code="LOOPBACK"
      listen_disp="${YELLOW}仅回环 (127.0.0.1)${RESET}"
    elif [ "$is_v6_loop" = true ]; then
      listen_code="LOOPBACK"
      listen_disp="${YELLOW}仅回环 (::1)${RESET}"
    else
      listen_code="NONE"
      listen_disp="${GRAY}未检测到TCP监听${RESET}"
    fi

    local fw_status="INACTIVE"
    if [ "$listen_code" != "NONE" ]; then
      fw_status=$(audit_firewall_status "$p" "$is_v4_listen" "$is_v6_listen")
    fi

    local fw_disp=""
    case "$fw_status" in
      OPEN) fw_disp="${GREEN}规则已放行${RESET}" ;;
      RESTRICTED) fw_disp="${YELLOW}受限放行${RESET}" ;;
      BLOCKED) fw_disp="${RED}未放行/拦截${RESET}" ;;
      INACTIVE) fw_disp="${GREEN}未启用防火墙${RESET}" ;;
      MULTI:*)
        fw_disp="${YELLOW}部分Zone放行${RESET}"
        ;;
      DUAL:*)
        fw_disp="${YELLOW}双栈差异${RESET}"
        ;;
      *) fw_disp="${YELLOW}无法确定${RESET}" ;;
    esac

    local result_disp=""
    if [ "$listen_code" = "NONE" ]; then
      result_disp="${GRAY}[ 未检测到该端口监听 ]${RESET}"
    elif [ "$listen_code" = "LOOPBACK" ]; then
      result_disp="${YELLOW}[ 仅绑定本地回环 ]${RESET}"
    else
      if [ "$fw_status" = "BLOCKED" ]; then
        result_disp="${RED}[ 监听但防火墙拦截 ]${RESET}"
      elif [ "$fw_status" = "RESTRICTED" ]; then
        result_disp="${YELLOW}[ 监听但受限放行 ]${RESET}"
      elif [[ "$fw_status" =~ ^(MULTI:|DUAL:) ]]; then
        result_disp="${YELLOW}[ 不同网卡/栈差异 ]${RESET}"
      elif [ "$fw_status" = "UNKNOWN" ]; then
        result_disp="${YELLOW}[ 状态无法确定 ]${RESET}"
      else
        result_disp="${GREEN}[ 监听正常，已放行 ]${RESET}"
      fi
    fi

    local c_p c_desc c_listen c_fw c_res
    c_p=$(pad_cell "$p" 6)
    c_desc=$(pad_cell "$desc" 16)
    c_listen=$(pad_cell "$listen_disp" 18)
    c_fw=$(pad_cell "$fw_disp" 14)
    c_res=$(pad_cell "$result_disp" 19)

    echo -e "${c_p} ${c_desc} ${c_listen} ${c_fw} ${c_res}"
  }

  # 子功能 1: 出站 TCP 连通性检测 (已知公共端点，客观结论)
  sub_check_outbound() {
    clear
    echo -e "${GREEN}一、出站 TCP 连通性检测${RESET}"
    echo -e "${CYAN}=============================================================================${RESET}"
    echo -e "说明: 探测本机主动连接已知公网标准服务的 TCP 握手。连接失败代表链路受阻，原因待查。"
    echo -e "${CYAN}-----------------------------------------------------------------------------${RESET}"
    local h1 h2 h3 h4 h5
    h1=$(pad_cell "端口" 6)
    h2=$(pad_cell "常见用途" 18)
    h3=$(pad_cell "实际探测目标" 22)
    h4=$(pad_cell "连通状态" 16)
    h5=$(pad_cell "耗时(ms)" 11 "right")
    echo -e "${h1} ${h2} ${h3} ${h4} ${h5}"
    echo -e "${CYAN}-----------------------------------------------------------------------------${RESET}"

    local test_list=(
      "25|SMTP 邮件|smtp.gmail.com|smtp.qq.com"
      "465|SMTPS 邮件|smtp.gmail.com|smtp.qq.com"
      "587|SMTP 提交|smtp.gmail.com|smtp.office365.com"
      "80|HTTP 网页|www.cloudflare.com|www.qq.com"
      "443|HTTPS 安全网页|www.cloudflare.com|www.google.com"
      "53|DNS 解析 (TCP)|1.1.1.1|8.8.8.8"
      "22|SSH/Git 远程服务|github.com|gitlab.com"
      "853|DNS over TLS|1.1.1.1|8.8.8.8"
    )

    for item in "${test_list[@]}"; do
      local p name host1 host2
      IFS='|' read -r p name host1 host2 <<<"$item"

      local res code lat actual_target
      res=$(probe_outbound_port "$host1" "$p" 3)
      IFS='|' read -r code lat <<<"$res"
      actual_target="$host1"

      if [ "$code" -ne 0 ] && [ "$host2" != "-" ]; then
        local res2 code2 lat2
        res2=$(probe_outbound_port "$host2" "$p" 3)
        IFS='|' read -r code2 lat2 <<<"$res2"
        if [ "$code2" -eq 0 ]; then
          code=0
          lat="$lat2"
          actual_target="$host2 (备用)"
        else
          actual_target="$host1 / $host2"
        fi
      fi

      local col_p col_name col_target col_status col_lat
      col_p=$(pad_cell "$p" 6)
      col_name=$(pad_cell "$name" 18)
      col_target=$(pad_cell "$actual_target" 22)

      if [ "$code" -eq 0 ]; then
        local lat_str="${lat}ms"
        [ "$lat" = "OK" ] && lat_str="已联通"
        col_status=$(pad_cell "${GREEN}[ 出站畅通 ]${RESET}" 16)
        col_lat=$(pad_cell "$lat_str" 11 "right")
      else
        col_status=$(pad_cell "${RED}[ 失败/拒连 ]${RESET}" 16)
        col_lat=$(pad_cell "超时" 11 "right")
      fi

      echo -e "${col_p} ${col_name} ${col_target} ${col_status} ${col_lat}"
    done

    echo -e "${CYAN}=============================================================================${RESET}"
    echo -e "${YELLOW}检测说明与排查建议:${RESET}"
    echo -e " 1. 端口标示仅为该端口的【常见用途】，并不代表应用层实际服务协议功能正常。"
    echo -e " 2. 若 25 端口连接失败，常见原因包括主机商默认阻断出站 25 端口、对方邮件网关"
    echo -e "    信誉拒连或本端安全组限制。若需外发邮件，推荐优先使用 465 (SMTPS) 或 587 端口。"
    echo -e " 3. 数据库端口 (如 3306/5432) 因无全球公共端点，请在【自定义探测】中针对专属主机测试。"
    echo -e "${CYAN}=============================================================================${RESET}"
  }

  # 子功能 2: 本地 TCP 监听与防火墙规则审计
  sub_audit_local() {
    local should_clear="${1:-true}"
    if [ "$should_clear" = true ]; then
      clear
    fi
    echo -e "${GREEN}二、本地 TCP 监听与防火墙规则审计${RESET}"
    echo -e "${CYAN}=============================================================================${RESET}"
    echo -e "说明: 审计本地内核 TCP 监听地址范围与主机防火墙规则状态 (UFW/Firewalld/iptables)。"
    echo -e "${CYAN}-----------------------------------------------------------------------------${RESET}"
    local h1 h2 h3 h4 h5
    h1=$(pad_cell "端口" 6)
    h2=$(pad_cell "常见服务" 16)
    h3=$(pad_cell "本地监听" 18)
    h4=$(pad_cell "防火墙" 14)
    h5=$(pad_cell "单端可达性审计" 19)
    echo -e "${h1} ${h2} ${h3} ${h4} ${h5}"
    echo -e "${CYAN}-----------------------------------------------------------------------------${RESET}"

    local all_ssh_ports
    all_ssh_ports=$(get_all_ssh_ports 2>/dev/null || echo "22")
    
    local audit_ports=(
      "80|HTTP Web服务"
      "443|HTTPS Web服务"
      "8888|宝塔/1Panel面板"
      "3306|MySQL 数据库"
      "5432|PostgreSQL 库"
      "6379|Redis 内存缓存"
      "27017|MongoDB 数据库"
      "8080|Web 代理/备用"
      "3000|Node/Grafana"
      "9000|Portainer管理"
    )

    for sp in $all_ssh_ports; do
      audit_single_local_port "$sp" "SSH 远程管理"
    done

    for item in "${audit_ports[@]}"; do
      local p desc
      IFS='|' read -r p desc <<<"$item"
      [[ " $all_ssh_ports " =~ " $p " ]] && continue
      audit_single_local_port "$p" "$desc"
    done

    echo -e "${CYAN}=============================================================================${RESET}"
    echo -e "${YELLOW}审计结论说明:${RESET}"
    echo -e " - ${GREEN}[ 监听正常，已放行 ]${RESET} 代表本地向外绑定且本机防火墙放行；"
    echo -e "   公网入站可达性仍受云服务商安全组、外层路由/NAT 及运营商策略影响，单端无法完全"
    echo -e "   证明外部可达，必须通过外部客户端实测验证。"
    echo -e " - ${YELLOW}[ 仅绑定本地回环 ]${RESET} 代表服务仅监听 127.0.0.0/8 或 ::1，外部网络无法直连访问。"
    echo -e " - ${RED}[ 监听但防火墙拦截 ]${RESET} 代表本地正在监听，但被本机防火墙阻止。"
    echo -e " - ${GRAY}[ 未检测到该端口监听 ]${RESET} 仅说明未发现该端口的 TCP 监听，不代表服务完全停止。"
    echo -e "${CYAN}=============================================================================${RESET}"
  }

  # 子功能 3: 目标 TCP 连接耗时测试 (防整数溢出 + 精准均值计算)
  sub_custom_probe() {
    clear
    echo -e "${GREEN}三、目标 TCP 连接耗时测试${RESET}"
    echo -e "${CYAN}=============================================================================${RESET}"
    echo -e "说明: 针对目标 IP 或域名发起多次 TCP 握手，统计连接成功率与连接建立耗时。"
    echo -e "${CYAN}-----------------------------------------------------------------------------${RESET}"

    local target_host target_port ping_count_input
    ui_read_field target_host "目标主机/域名 (默认 1.1.1.1)" ui_probe_host "1.1.1.1" || return 0
    ui_read_field target_port "目标端口 (逗号分隔，默认 80)" ui_probe_ports "80" || return 0
    ui_read_field ping_count_input "探测次数 (1-20，默认 4)" ui_probe_count "4" || return 0
    local ping_count=$((10#$ping_count_input))

    echo -e "\n${CYAN}>>> 开始测试目标: ${WHITE}${target_host}${RESET} (每端口连接测试: ${ping_count} 次)${RESET}\n"

    IFS=',' read -ra port_arr <<<"$target_port"
    for single_p in "${port_arr[@]}"; do
      single_p=$(echo "$single_p" | tr -d '[:space:]')
      if [[ ! "$single_p" =~ ^[0-9]{1,5}$ ]] || [ "$single_p" -lt 1 ] || [ "$single_p" -gt 65535 ]; then
        echo -e "${RED}[!] 忽略非法端口号: '${single_p}' (端口有效范围必须为 1-65535 纯整数)${RESET}"
        continue
      fi

      echo -e "${YELLOW}--- 正在测试端口: ${single_p} ---${RESET}"
      local success_cnt=0
      local valid_time_cnt=0
      local min_ms=999999
      local max_ms=0
      local sum_ms=0

      for ((i = 1; i <= ping_count; i++)); do
        local res code lat
        res=$(probe_outbound_port "$target_host" "$single_p" 3)
        IFS='|' read -r code lat <<<"$res"

        if [ "$code" -eq 0 ]; then
          ((success_cnt++))
          if [[ "$lat" =~ ^[0-9]+$ ]]; then
            ((valid_time_cnt++))
            [ "$lat" -lt "$min_ms" ] && min_ms=$lat
            [ "$lat" -gt "$max_ms" ] && max_ms=$lat
            sum_ms=$((sum_ms + lat))
            echo -e "  [#$i] 连接至 ${target_host}:${single_p} -> ${GREEN}成功${RESET} (TCP 连接建立耗时: ${lat} ms)"
          else
            echo -e "  [#$i] 连接至 ${target_host}:${single_p} -> ${GREEN}成功${RESET} (耗时未知)"
          fi
        else
          echo -e "  [#$i] 连接至 ${target_host}:${single_p} -> ${RED}失败 / 超时${RESET}"
        fi
        [ "$i" -lt "$ping_count" ] && sleep 0.3
      done

      local fail_rate=$(( (ping_count - success_cnt) * 100 / ping_count ))
      echo -e "${CYAN}-----------------------------------------------------------------------------${RESET}"
      echo -e "端口 ${single_p} 统计: 发起 = $ping_count, 成功 = $success_cnt, TCP 连接失败率 = ${fail_rate}%"
      if [ "$valid_time_cnt" -gt 0 ] && [ "$min_ms" -ne 999999 ]; then
        local avg_ms=$((sum_ms / valid_time_cnt))
        echo -e "TCP 建立耗时统计: 最小 = ${min_ms} ms, 平均 = ${avg_ms} ms, 最大 = ${max_ms} ms (有效样本数: $valid_time_cnt)"
      fi
      echo -e "${CYAN}=============================================================================${RESET}\n"
    done
  }

  while true; do
    clear
    echo -e "${GREEN}七、系统管理工具菜单${RESET}"
    echo -e "${CYAN}=============================================================================${RESET}"
    echo -e "${YELLOW}(一) 常用端口与连通性检测 (场景C)${RESET}"
    echo -e "  ${YELLOW} 1.${RESET} 综合快速体检 (出站TCP连通性 + 本地监听与防火墙审计)"
    echo -e "  ${YELLOW} 2.${RESET} 出站 TCP 连通性检测 (邮件/Web/DNS/远程登录标准端点)"
    echo -e "  ${YELLOW} 3.${RESET} 本地 TCP 监听与防火墙规则审计 (22/80/443/3306/6379/8888等)"
    echo -e "  ${YELLOW} 4.${RESET} 目标 TCP 连接耗时测试 (IP/域名 + 端口探测)"
    echo -e "  ${YELLOW} 0.${RESET} 返回主菜单"
    echo -e "${CYAN}=============================================================================${RESET}"

    ui_menu_read pc_choice "请输入选项 [0-4]" "[0-4]" || return 0
    case $pc_choice in
      1)
        sub_check_outbound
        echo ""
        sub_audit_local false
        echo ""
        ui_pause "按 Enter 返回端口检测菜单..."
        ;;
      2)
        sub_check_outbound
        echo ""
        ui_pause "按 Enter 返回端口检测菜单..."
        ;;
      3)
        sub_audit_local
        echo ""
        ui_pause "按 Enter 返回端口检测菜单..."
        ;;
      4)
        sub_custom_probe
        echo ""
        ui_pause "按 Enter 返回端口检测菜单..."
        ;;
      0)
        return 0
        ;;
      *)
        echo -e "${RED}无效选项${RESET}"
        sleep 1
        ;;
    esac
  done
}

# ---------- 虚拟机磁盘空间瘦身与回收 ----------
manage_disk_shrink() {
  clear
  echo -e "${CYAN}>>> 虚拟机磁盘空间瘦身与回收${RESET}"
  local virt
  virt=$(detect_virtualization)
  echo -e "当前检测环境: ${GREEN}${virt}${RESET}"
  echo -e "${CYAN}--------------------------------------------------${RESET}"
  echo -e "  ${YELLOW}1.${RESET} VMware 专用收缩 (通过 open-vm-tools)"
  echo -e "  ${YELLOW}2.${RESET} 通用 TRIM 空间回收 (适用于 PVE/KVM/VMware/SSD, 执行 fstrim)"
  echo -e "  ${YELLOW}0.${RESET} 返回主菜单"
  echo -e "${CYAN}==================================================${RESET}"
  ui_menu_read shrink_choice "请输入选项 [0-2]" "[0-2]" || return 0
  case $shrink_choice in
    1)
      if [[ "$virt" != *"VMware"* ]]; then
        echo -e "${YELLOW}[!] 提示: 当前系统检测为 [${virt}]，可能并非运行在 VMware 平台。${RESET}"
        ui_confirm force_vmware "仍要尝试执行 VMware 收缩吗? [y/N] (q 取消): "
        if [[ ! "$force_vmware" =~ ^[Yy]$ ]]; then
          return 0
        fi
      fi
      if ! command -v vmware-toolbox-cmd >/dev/null 2>&1 && [ ! -x /usr/bin/vmware-toolbox-cmd ]; then
        echo -e "${YELLOW}[!] 未找到 vmware-toolbox-cmd 命令。${RESET}"
        ui_confirm inst_tools "是否立即自动安装 open-vm-tools? [y/N] (q 取消): "
        if [[ "$inst_tools" =~ ^[Yy]$ ]]; then
          check_pm_lock || return 1
          $PKG_INSTALL open-vm-tools
        else
          return 0
        fi
      fi
      if command -v vmware-toolbox-cmd >/dev/null 2>&1 || [ -x /usr/bin/vmware-toolbox-cmd ]; then
        echo -e "${CYAN}>>> 正在执行 VMware 磁盘收缩 (耗时较长，请耐心等待)...${RESET}"
        vmware-toolbox-cmd disk shrink /
        echo -e "${GREEN}[√] VMware 磁盘收缩完成${RESET}"
      else
        echo -e "${RED}[!] 安装后仍未检测到命令，操作终止${RESET}"
      fi
      ;;
    2)
      echo -e "${CYAN}>>> 正在执行 fstrim 释放未使用的空间...${RESET}"
      if command -v fstrim >/dev/null 2>&1; then
        fstrim -av
        echo -e "${GREEN}[√] TRIM 空间释放完成 (已通知底层存储回收空闲块)${RESET}"
      else
        echo -e "${RED}[!] 未找到 fstrim 命令${RESET}"
      fi
      ;;
    0)
      return 0
      ;;
    *)
      echo -e "${RED}无效选项${RESET}"
      sleep 1
      ;;
  esac
}

# --- 主菜单 CPU 窃取率局部动态刷新 ---
refresh_live_steal() {
  local term_lines
  term_lines=$(tput lines 2>/dev/null || echo 0)
  # 仅当终端高度足够容纳该行时才进行局部刷新，防止光标向上越界破坏窗口顶行
  if [ -n "$MENU_STEAL_OFFSET" ] && [ "$term_lines" -gt "$MENU_STEAL_OFFSET" ]; then
    if [ -f /proc/stat ]; then
      local cur_total cur_idle cur_steal
      read -r cur_total cur_idle cur_steal <<<"$(get_cpu_stat)"
      local diff_total=$((cur_total - PREV_CPU_TOTAL))
      local diff_steal=$((cur_steal - PREV_CPU_STEAL))
      local diff_idle=$((cur_idle - PREV_CPU_IDLE))

      if [ "$diff_total" -gt 0 ]; then
        local live_cpu_st
        live_cpu_st=$(awk -v s="$diff_steal" -v t="$diff_total" 'BEGIN {printf "%.1f", (s/t)*100}')
        PREV_CPU_TOTAL=$cur_total
        PREV_CPU_IDLE=$cur_idle
        PREV_CPU_STEAL=$cur_steal

        local st_int
        st_int=$(echo "$live_cpu_st" | awk -F. '{print $1}')
        local live_st_display
        if [ "$st_int" -ge 10 ]; then
          live_st_display="${RED}${live_cpu_st}% (严重抢占)${RESET}"
        elif [ "$st_int" -gt 0 ]; then
          live_st_display="${YELLOW}${live_cpu_st}% (轻微争抢)${RESET}"
        else
          live_st_display="${GREEN}${live_cpu_st}% (良好)${RESET}"
        fi

        # 保存光标 -> 向上移动到 CPU 窃取行 -> 水平移动到第 70 列 -> 刷新数值并清除行尾残留 -> 恢复光标
        printf "\033[s\0337\033[${MENU_STEAL_OFFSET}A\033[70G%b\033[K\0338\033[u" "$live_st_display"
      fi
    fi
  fi
}

read_main_menu_choice() {
  local prompt="请输入选项编号 (0/q 退出): "
  choice=""
  if [ ! -t 0 ]; then
    IFS= read -r -p "$prompt" choice || return 1
    return 0
  fi

  printf "%s" "$prompt"
  local input_buf=""
  local char=""

  while true; do
    char=""
    if read -s -r -n 1 -t 1.5 char; then
      if [ -z "$char" ] || [ "$char" = $'\n' ] || [ "$char" = $'\r' ]; then
        printf "\n"
        choice="$input_buf"
        return 0
      elif [ "$char" = $'\177' ] || [ "$char" = $'\b' ]; then
        if [ ${#input_buf} -gt 0 ]; then
          input_buf="${input_buf%?}"
          printf "\b \b"
        fi
      elif [ "$char" = $'\033' ] || [ "$char" = $'\e' ]; then
        # 吸收可能随之而来的箭头方向键转义序列
        read -s -r -n 4 -t 0.02 _junk 2>/dev/null
      elif [[ "$char" =~ [[:print:]] ]]; then
        input_buf+="$char"
        printf "%s" "$char"
      fi
    else
      local ui_read_status=$?
      [ "$ui_read_status" -le 128 ] && return 1
      # 1.5 秒超时无按键，触发局部刷新
      refresh_live_steal
    fi
  done
}

# --- [11. 核心] 主菜单逻辑 ---
while true; do
  show_menu
  SKIP_PAUSE=false

  UI_PAUSED=false
  read_main_menu_choice || exit 0
  case "$choice" in q|Q) choice=0 ;; esac
  case $choice in
  # [板块 1] 系统运维
  1) system_upgrade ;;
  2) system_cleanup ;;
  3) ui_run_menu manage_kernels ;;
  4) ui_run_menu manage_swap ;;
  5) change_hostname ;;
  6) change_timezone ;;
  7) ui_run_menu change_mirror ;;

  # [板块 2] 安全防护
  8) ui_run_menu manage_ufw ;;
  9) ui_run_menu manage_fail2ban ;;
  10) change_ssh_port ;;

  # [板块 3] 网络优化
  11) ui_run_menu configure_network_static ;;
  12) ui_run_menu manage_bbr ;;
  13) ui_run_menu manage_tcp_tuning ;;
  14) manage_ipv6 ;;
  15) modify_dns ;;

  # [板块 4] 工具应用
  16) ui_run_menu software_hub ;;
  17) ui_run_menu manage_crontab ;;
  18) ui_run_menu show_sys_monitor ;;

  # [板块 5] 性能测试
  19) stream_test ;;
  20) net_test ;;
  21) ui_run_menu manage_port_check ;;
  22) full_test ;;
  23) benchmark ;;

  # [板块 6] 系统控制
  24) manage_disk_shrink ;;
  25)
    echo -e "${YELLOW}即将重启系统...${RESET}"
    ui_confirm confirm "确认执行吗? [y/N] (q 取消): "
    [[ "$confirm" =~ ^[Yy]$ ]] && reboot
    ;;
  26)
    echo -e "${YELLOW}即将关机...${RESET}"
    ui_confirm confirm "确认执行吗? [y/N] (q 取消): "
    [[ "$confirm" =~ ^[Yy]$ ]] && poweroff
    ;;

  0)
    echo -e "${GREEN}已退出脚本，再见！${RESET}"
    exit 0
    ;;
  *)
    echo -e "${RED}[×] 无效选项，请重新输入${RESET}"
    SKIP_PAUSE=true
    sleep 1
    ;;
  esac

  if [ "$SKIP_PAUSE" = false ] && [ "$UI_PAUSED" = false ]; then
    echo -e ""
    ui_pause "按 Enter 返回主菜单..."
  fi
done
