#!/usr/bin/env bash
# OPS Linux Doctor v7.4 launcher
# Auto UI: Chinese on UTF-8 SSH/PTS; full pinyin on traditional TTY/DCIM VNC.
# Manual override: zw (Chinese) or py (full pinyin)

_ui_lang="auto"

# Manual override. Use direct string comparison to avoid case-pattern corruption
# caused by hidden/non-ASCII dash characters after copy/upload.
_arg1="${1:-}"
if [ "$_arg1" = "py" ] || [ "$_arg1" = "-py" ]; then
    _ui_lang="pinyin"
elif [ "$_arg1" = "zw" ] || [ "$_arg1" = "-zw" ]; then
    _ui_lang="zh"
fi

if [ "$_ui_lang" = "auto" ]; then
    _tty="$(tty 2>/dev/null || true)"
    _term="${TERM:-}"
    _locale="${LC_ALL:-${LC_CTYPE:-${LANG:-}}}"
    if [[ "$_tty" =~ ^/dev/tty[0-9]+$ ]] && [ "$_term" = "linux" ]; then
        _ui_lang="pinyin"
    elif [[ "$_locale" != *UTF-8* && "$_locale" != *utf8* && "$_locale" != *utf-8* ]]; then
        _ui_lang="pinyin"
    else
        _ui_lang="zh"
    fi
fi

if [ "$_ui_lang" = "pinyin" ]; then
    _tmp_script="/tmp/ops-linux-doctor-pinyin-$$.sh"
    awk 'found { print } /^__OPS_PINYIN_PAYLOAD__$/ { found=1 }' "$0" > "$_tmp_script" || exit 1
    chmod 700 "$_tmp_script"
    bash "$_tmp_script" "$@"
    _rc=$?
    rm -f "$_tmp_script"
    exit $_rc
fi

# ==========================================================
# OPS Linux Doctor v7
# 用途：Linux 系统查询 / 问题检测 / 自动修复 / 高危扩展项
# 覆盖：CentOS 7、CentOS Stream 9、Rocky、AlmaLinux、Ubuntu 18-24、Debian 11-13
# ==========================================================

set -o pipefail

SCRIPT_NAME="ops-linux-doctor"
RUN_ID="$(date +%F_%H%M%S)"
REPORT="/dev/null"
BACKUP_DIR="/root/${SCRIPT_NAME}-backup-${RUN_ID}"
AUTO_FIX_LOW_RISK=0
CC_THRESHOLD_DEFAULT=100

RED="\033[31m"; GREEN="\033[32m"; YELLOW="\033[33m"; BLUE="\033[34m"; NC="\033[0m"
OS_ID="unknown"; OS_VERSION="unknown"; OS_FAMILY="unknown"
SSH_SERVICE="unknown"; SSH_CONFIG="/etc/ssh/sshd_config"; SSH_PORTS="22"; FIREWALL_TYPES="none"
PRIMARY_IF=""; PRIMARY_IP_CIDR=""; PRIMARY_GW=""

mkdir -p "$BACKUP_DIR" 2>/dev/null

# ------------------------- 输出函数 -------------------------
log_detail(){ [ "$REPORT" = "/dev/null" ] || echo -e "$*" >> "$REPORT"; }
run_detail(){ log_detail "[执行] $*"; "$@" >> "$REPORT" 2>&1; }
info(){ echo -e "${BLUE}[查询]${NC} $*"; }
ok(){ echo -e "${GREEN}[正常]${NC} $*"; }
warn(){ echo -e "${YELLOW}[异常]${NC} $*"; }
fixmsg(){ echo -e "${GREEN}[修复]${NC} $*"; }
err(){ echo -e "${RED}[错误]${NC} $*"; }
plan(){ echo -e "${YELLOW}[方案]${NC} $*"; }
section(){ echo ""; echo -e "${BLUE}==== $* ====${NC}"; }

need_root(){ if [ "$(id -u)" -ne 0 ]; then echo -e "${RED}请使用 root 用户执行，或 sudo bash $0${NC}"; exit 1; fi; }
confirm(){
    local msg="$1" ans
    echo -e "${YELLOW}${msg}${NC}"
    read -r -p "请输入 y 确认执行（其他输入取消）：" ans
    [ "$ans" = "y" ] || [ "$ans" = "Y" ]
}
backup_file(){ local f="$1"; [ -f "$f" ] || return 0; mkdir -p "$BACKUP_DIR"; cp -a "$f" "$BACKUP_DIR/$(echo "$f" | sed 's#/#_#g').bak" && log_detail "[备份] $f -> $BACKUP_DIR"; }
backup_dir(){ local d="$1"; [ -d "$d" ] || return 0; mkdir -p "$BACKUP_DIR"; cp -a "$d" "$BACKUP_DIR/$(basename "$d").bak" && log_detail "[备份目录] $d -> $BACKUP_DIR"; }

# ------------------------- 系统识别 -------------------------
detect_os(){
    if [ -f /etc/os-release ]; then . /etc/os-release; OS_ID="${ID:-unknown}"; OS_VERSION="${VERSION_ID:-unknown}"; fi
    case "$OS_ID" in centos|rhel|rocky|almalinux|fedora) OS_FAMILY="rhel";; ubuntu|debian) OS_FAMILY="debian";; *) OS_FAMILY="unknown";; esac
}

detect_ssh_service(){
    if systemctl list-unit-files 2>/dev/null | grep -q '^sshd.service'; then SSH_SERVICE="sshd";
    elif systemctl list-unit-files 2>/dev/null | grep -q '^ssh.service'; then SSH_SERVICE="ssh";
    elif systemctl status sshd >/dev/null 2>&1; then SSH_SERVICE="sshd";
    elif systemctl status ssh >/dev/null 2>&1; then SSH_SERVICE="ssh";
    else [ "$OS_FAMILY" = "debian" ] && SSH_SERVICE="ssh" || SSH_SERVICE="sshd"; fi
}

get_primary_network(){
    PRIMARY_GW="$(ip route show default 2>/dev/null | awk 'NR==1{print $3}')"
    PRIMARY_IF="$(ip route show default 2>/dev/null | awk 'NR==1{print $5}')"
    if [ -n "$PRIMARY_IF" ]; then PRIMARY_IP_CIDR="$(ip -o -4 addr show dev "$PRIMARY_IF" scope global 2>/dev/null | awk 'NR==1{print $4}')"; fi
    if [ -z "$PRIMARY_IF" ]; then PRIMARY_IF="$(ip -o -4 addr show scope global 2>/dev/null | awk 'NR==1{print $2}')"; fi
    if [ -n "$PRIMARY_IF" ] && [ -z "$PRIMARY_IP_CIDR" ]; then PRIMARY_IP_CIDR="$(ip -o -4 addr show dev "$PRIMARY_IF" scope global 2>/dev/null | awk 'NR==1{print $4}')"; fi
}

get_ssh_ports(){
    local ports="" cfg_ports="" listen_ports=""
    if command -v ss >/dev/null 2>&1; then
        listen_ports="$(ss -lntp 2>/dev/null | grep -E 'sshd|dropbear|:22 ' | awk '{print $4}' | sed -E 's/.*:([0-9]+)$/\1/' | grep -E '^[0-9]+$' | sort -n | uniq | xargs)"
    fi
    [ -f "$SSH_CONFIG" ] && cfg_ports="$(grep -Ei '^[[:space:]]*Port[[:space:]]+' "$SSH_CONFIG" 2>/dev/null | awk '{print $2}' | sort -n | uniq | xargs)"
    if [ -d /etc/ssh/sshd_config.d ]; then
        cfg_ports="$cfg_ports $(grep -RhiE '^[[:space:]]*Port[[:space:]]+' /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk '{print $2}' | sort -n | uniq | xargs)"
    fi
    ports="$(echo "$listen_ports $cfg_ports" | xargs -n1 2>/dev/null | grep -E '^[0-9]+$' | sort -n | uniq | xargs)"
    [ -z "$ports" ] && ports="22"
    SSH_PORTS="$ports"
}

detect_firewalls(){
    # 只识别“正在启用且可能产生拦截”的系统防火墙。
    # 说明：iptables/nftables 命令存在不代表防火墙启用；Ubuntu 上 ufw 服务/iptables-nft 存在也不代表端口需要放行。
    FIREWALL_TYPES=""

    if systemctl is-active firewalld >/dev/null 2>&1; then
        FIREWALL_TYPES="$FIREWALL_TYPES firewalld"
    fi

    if command -v ufw >/dev/null 2>&1; then
        if ufw status 2>/dev/null | grep -qi '^Status:[[:space:]]*active'; then
            FIREWALL_TYPES="$FIREWALL_TYPES ufw"
        fi
    fi

    if command -v iptables >/dev/null 2>&1 && iptables -S INPUT >/dev/null 2>&1; then
        local ipt_policy ipt_rules
        ipt_policy="$(iptables -S INPUT 2>/dev/null | awk 'NR==1{print $3}')"
        ipt_rules="$(iptables -S INPUT 2>/dev/null)"
        # 只有 INPUT 默认 DROP/REJECT，或存在明确 DROP/REJECT 规则时，才认为 iptables 是限制型防火墙。
        if [ "$ipt_policy" = "DROP" ] || [ "$ipt_policy" = "REJECT" ] || echo "$ipt_rules" | grep -qE ' -j (DROP|REJECT)( |$)'; then
            FIREWALL_TYPES="$FIREWALL_TYPES iptables"
        fi
    fi

    if command -v nft >/dev/null 2>&1 && nft list ruleset >/dev/null 2>&1; then
        local nft_rules
        nft_rules="$(nft list ruleset 2>/dev/null)"
        # 只有存在 input hook 且 policy drop，或存在明确 drop/reject 动作时，才认为 nftables 可能拦截。
        if echo "$nft_rules" | grep -qiE 'hook[[:space:]]+input' && echo "$nft_rules" | grep -qiE 'policy[[:space:]]+drop|[^a-z](drop|reject)([^a-z]|$)'; then
            FIREWALL_TYPES="$FIREWALL_TYPES nftables"
        fi
    fi

    FIREWALL_TYPES="$(echo "$FIREWALL_TYPES" | xargs)"
    [ -z "$FIREWALL_TYPES" ] && FIREWALL_TYPES="none"
}
init_detect(){ detect_os; detect_ssh_service; get_primary_network; get_ssh_ports; detect_firewalls; }

# ------------------------- 自动修复工具 -------------------------
restart_ssh_safe(){
    detect_ssh_service
    if command -v sshd >/dev/null 2>&1; then
        if ! sshd -t >> "$REPORT" 2>&1; then err "SSH 配置语法异常，拒绝重启 SSH，避免断连"; return 1; fi
    fi
    if systemctl restart "$SSH_SERVICE" >> "$REPORT" 2>&1; then fixmsg "SSH 服务已重启"; else err "SSH 服务重启失败"; return 1; fi
}

start_enable_ssh(){
    detect_ssh_service
    if ! systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then
        if systemctl start "$SSH_SERVICE" >> "$REPORT" 2>&1; then fixmsg "SSH 服务未运行，已启动 $SSH_SERVICE"; else err "SSH 服务启动失败"; return 1; fi
    fi
    if ! systemctl is-enabled "$SSH_SERVICE" >/dev/null 2>&1; then
        systemctl enable "$SSH_SERVICE" >> "$REPORT" 2>&1 && fixmsg "SSH 未开机自启，已设置 enable"
    fi
}

save_iptables(){
    if command -v service >/dev/null 2>&1 && service iptables save >> "$REPORT" 2>&1; then fixmsg "iptables 规则已保存";
    elif command -v netfilter-persistent >/dev/null 2>&1; then netfilter-persistent save >> "$REPORT" 2>&1 && fixmsg "iptables 规则已保存";
    elif [ -d /etc/sysconfig ]; then iptables-save > /etc/sysconfig/iptables 2>>"$REPORT" && fixmsg "iptables 规则已保存到 /etc/sysconfig/iptables";
    else plan "iptables 规则已临时生效，未确认持久化方式"; fi
}

allow_ssh_port_auto(){
    get_ssh_ports; detect_firewalls
    local p changed=0

    if [ "$FIREWALL_TYPES" = "none" ]; then
        ok "未检测到启用的限制型系统防火墙，SSH 端口无需放行"
        return 0
    fi

    for p in $SSH_PORTS; do
        if echo "$FIREWALL_TYPES" | grep -qw firewalld; then
            if ! firewall-cmd --query-port=${p}/tcp >/dev/null 2>&1 && ! { [ "$p" = "22" ] && firewall-cmd --query-service=ssh >/dev/null 2>&1; }; then
                firewall-cmd --permanent --add-port=${p}/tcp >> "$REPORT" 2>&1
                firewall-cmd --add-port=${p}/tcp >> "$REPORT" 2>&1
                firewall-cmd --reload >> "$REPORT" 2>&1
                fixmsg "firewalld 未放行 $p/tcp，已放行"
                changed=1
            fi
        fi

        if echo "$FIREWALL_TYPES" | grep -qw ufw; then
            if ! ufw status 2>/dev/null | grep -qE "(^|[[:space:]])${p}(/tcp)?([[:space:]]|$)"; then
                ufw allow ${p}/tcp >> "$REPORT" 2>&1 && fixmsg "ufw 未放行 $p/tcp，已放行"
                changed=1
            fi
        fi

        if echo "$FIREWALL_TYPES" | grep -qw iptables; then
            local policy
            policy="$(iptables -S INPUT 2>/dev/null | awk 'NR==1{print $3}')"
            if [ "$policy" = "DROP" ] || [ "$policy" = "REJECT" ] || iptables -S INPUT 2>/dev/null | grep -qE ' -j (DROP|REJECT)( |$)'; then
                if ! iptables -C INPUT -p tcp --dport "$p" -j ACCEPT >/dev/null 2>&1; then
                    iptables -I INPUT -p tcp --dport "$p" -j ACCEPT >> "$REPORT" 2>&1
                    fixmsg "iptables 存在限制策略且未放行 $p/tcp，已插入 ACCEPT"
                    save_iptables
                    changed=1
                fi
            fi
        fi

        if echo "$FIREWALL_TYPES" | grep -qw nftables; then
            add_nft_allow_port "$p" && changed=1 || plan "nftables 已启用但未能安全识别 input 链，请人工确认后放行 $p/tcp"
        fi
    done
    [ "$changed" = "0" ] && ok "当前 SSH 端口防火墙放行规则未发现明显异常"
}
enable_ping_temp(){
    if [ -f /proc/sys/net/ipv4/icmp_echo_ignore_all ] && [ "$(cat /proc/sys/net/ipv4/icmp_echo_ignore_all)" = "1" ]; then
        sysctl -w net.ipv4.icmp_echo_ignore_all=0 >> "$REPORT" 2>&1 && fixmsg "系统已禁 ping，已临时开启 ping"
    fi
}

selinux_temp_permissive(){
    if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce 2>/dev/null)" = "Enforcing" ]; then
        if command -v setenforce >/dev/null 2>&1; then setenforce 0 >> "$REPORT" 2>&1 && fixmsg "SELinux 为 Enforcing，已临时切换为 Permissive"; fi
    fi
}

fix_local_listen_auto(){
    # 仅注释主配置中的 ListenAddress 127.0.0.1/::1；其他复杂 ListenAddress 不自动改
    [ -f "$SSH_CONFIG" ] || return 0
    if grep -qE '^[[:space:]]*ListenAddress[[:space:]]+(127\.0\.0\.1|::1)' "$SSH_CONFIG"; then
        backup_file "$SSH_CONFIG"
        sed -i -E 's/^[[:space:]]*ListenAddress[[:space:]]+(127\.0\.0\.1|::1)/#ListenAddress \1/g' "$SSH_CONFIG"
        fixmsg "SSH 只监听本地地址，已注释本地 ListenAddress 限制"
        restart_ssh_safe
    fi
}

find_gateway_from_configs(){
    local gw=""
    gw="$(grep -RhsE '^(GATEWAY=|gateway[[:space:]]+|via:[[:space:]]*)' /etc/sysconfig/network-scripts/ifcfg-* /etc/network/interfaces /etc/netplan/*.yaml 2>/dev/null | head -1 | sed -E 's/.*(GATEWAY=|gateway[[:space:]]+|via:[[:space:]]*)([0-9.]+).*/\2/')"
    [ -n "$gw" ] && echo "$gw"
}

fix_default_route_auto(){
    get_primary_network
    if ip route show default 2>/dev/null | grep -q '^default'; then return 0; fi
    local gw dev
    gw="$(find_gateway_from_configs)"
    dev="$PRIMARY_IF"
    [ -z "$dev" ] && dev="$(ip -o -4 addr show scope global 2>/dev/null | awk 'NR==1{print $2}')"
    if [ -n "$gw" ] && [ -n "$dev" ]; then
        if ip route replace default via "$gw" dev "$dev" >> "$REPORT" 2>&1; then fixmsg "默认路由缺失，已根据配置添加 default via $gw dev $dev"; else err "默认路由自动添加失败"; fi
    else
        warn "默认路由缺失，且无法从配置中识别网关"
        plan "进入【默认路由修复】菜单，手动输入网关后添加"
    fi
}

openssl_sshd_issue_detected(){
    detect_ssh_service
    local tmp="/tmp/${SCRIPT_NAME}-sshlib-${RUN_ID}.txt"
    : > "$tmp"
    systemctl status "$SSH_SERVICE" --no-pager >> "$tmp" 2>&1
    journalctl -u "$SSH_SERVICE" --no-pager -n 200 >> "$tmp" 2>&1
    [ -f /var/log/secure ] && tail -n 300 /var/log/secure >> "$tmp"
    [ -f /var/log/auth.log ] && tail -n 300 /var/log/auth.log >> "$tmp"
    cat "$tmp" >> "$REPORT"
    grep -qiE 'OpenSSL|libcrypto|libssl|version mismatch|symbol lookup error|undefined symbol|library not found|error while loading shared libraries' "$tmp"
}

fix_openssl_sshd_auto(){
    if systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then return 0; fi
    if ! openssl_sshd_issue_detected; then return 0; fi
    warn "SSH 服务启动失败，检测到 OpenSSL/libcrypto/libssl 依赖库异常特征"
    if [ "$AUTO_FIX_LOW_RISK" = "1" ]; then
        # 包级修复风险较高，自动模式下只在明确命中时执行；仍输出说明
        fixmsg "明确命中 SSH 依赖库异常，开始执行系统对应修复"
        detect_os
        if [ "$OS_FAMILY" = "rhel" ]; then
            if command -v dnf >/dev/null 2>&1; then dnf distro-sync -y >> "$REPORT" 2>&1; else yum update -y openssh-server openssl openssl-libs >> "$REPORT" 2>&1; fi
            ldconfig >> "$REPORT" 2>&1
            systemctl restart "$SSH_SERVICE" >> "$REPORT" 2>&1 && fixmsg "OpenSSL/sshd 依赖库修复后已重启 SSH"
        elif [ "$OS_FAMILY" = "debian" ]; then
            apt update >> "$REPORT" 2>&1
            apt install --reinstall -y openssh-server openssl >> "$REPORT" 2>&1
            ldconfig >> "$REPORT" 2>&1
            systemctl restart "$SSH_SERVICE" >> "$REPORT" 2>&1 && fixmsg "OpenSSL/sshd 依赖库修复后已重启 SSH"
        fi
    else
        plan "进入【OpenSSL/sshd 依赖库异常修复】菜单执行修复"
    fi
}

# ------------------------- 检查模块：终端只出结果，详细写日志 -------------------------
check_system_network(){
    section "1. 系统与网络基础检查"
    detect_os; get_primary_network
    info "系统：$OS_ID $OS_VERSION；内核：$(uname -r)；主机名：$(hostname)"
    info "主网卡：${PRIMARY_IF:-未识别}；主IP：${PRIMARY_IP_CIDR:-未识别}；默认网关：${PRIMARY_GW:-未识别}"
    log_detail "---- ip -br addr ----"; ip -br addr >> "$REPORT" 2>&1
    log_detail "---- ip route ----"; ip route >> "$REPORT" 2>&1
    log_detail "---- resolv.conf ----"; cat /etc/resolv.conf >> "$REPORT" 2>&1
    log_detail "---- ip -br link ----"; ip -br link >> "$REPORT" 2>&1

    if [ -z "$PRIMARY_IP_CIDR" ]; then warn "IP 地址：未识别到主 IP"; plan "检查网卡配置或云平台网卡绑定"; else ok "IP 地址：已识别主 IP $PRIMARY_IP_CIDR"; fi
    if [ -z "$PRIMARY_GW" ]; then warn "默认路由：缺失"; plan "可自动从配置文件识别网关后添加；无法识别时需手动输入网关"; [ "$AUTO_FIX_LOW_RISK" = "1" ] && fix_default_route_auto; else ok "默认路由：via $PRIMARY_GW dev $PRIMARY_IF"; fi
    if [ -n "$PRIMARY_GW" ] && ! ping -c 1 -W 2 "$PRIMARY_GW" >/dev/null 2>&1; then warn "网关连通性：$PRIMARY_GW 不可达"; plan "检查 IP/掩码/网关、VLAN、上联交换机或安全组"; else [ -n "$PRIMARY_GW" ] && ok "网关连通性：正常"; fi
    if grep -q '^nameserver' /etc/resolv.conf 2>/dev/null; then ok "DNS：已配置 $(awk '/^nameserver/{print $2}' /etc/resolv.conf | xargs)"; else warn "DNS：未配置 nameserver"; plan "写入客户指定 DNS，或临时配置 1.1.1.1 / 8.8.8.8"; fi
}

check_ssh_service(){
    section "2. SSH 服务与监听检查 / 自动处理"
    detect_ssh_service; get_ssh_ports
    info "SSH 服务名：$SSH_SERVICE；识别端口：$SSH_PORTS"
    if ! systemctl list-unit-files 2>/dev/null | grep -qE "^${SSH_SERVICE}\.service" && ! systemctl status "$SSH_SERVICE" >/dev/null 2>&1; then
        warn "SSH 服务：未检测到 $SSH_SERVICE 服务"
        plan "可进入【SSH 服务扩展项】安装 openssh-server"
        return
    fi
    if systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then ok "SSH 服务：运行中"; else warn "SSH 服务：未运行或启动失败，开始自动处理"; start_enable_ssh || true; systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1 || fix_openssl_sshd_auto || true; fi
    if systemctl is-enabled "$SSH_SERVICE" >/dev/null 2>&1; then ok "SSH 自启：已启用"; else warn "SSH 自启：未启用，开始自动设置"; systemctl enable "$SSH_SERVICE" >/dev/null 2>&1 && fixmsg "已设置 SSH 开机自启" || err "设置 SSH 开机自启失败"; fi
    local missing=0 global=0 localonly=0 p
    get_ssh_ports
    for p in $SSH_PORTS; do
        if ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${p}$"; then
            ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(^0\.0\.0\.0:|^\[::\]:|^\*:|^:::).*${p}$" && global=1
            ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(^127\.0\.0\.1:|^\[::1\]:|^::1:).*${p}$" && localonly=1
        else missing=1; fi
    done
    if [ "$missing" = "1" ]; then warn "SSH 监听：配置端口未全部监听，尝试安全重启 SSH"; restart_ssh_safe || true; else ok "SSH 监听：端口已监听"; fi
    if [ "$global" = "0" ] && [ "$localonly" = "1" ]; then warn "SSH 监听地址：可能只监听本地地址，开始自动修复"; fix_local_listen_auto || true; else ok "SSH 监听地址：未发现仅本地监听问题"; fi
}

check_ssh_config(){
    section "3. SSH 配置与登录策略检查"
    [ -f "$SSH_CONFIG" ] || { warn "SSH 配置：$SSH_CONFIG 不存在"; plan "安装 openssh-server"; return; }
    log_detail "---- ssh config grep ----"; grep -RniE '^[#[:space:]]*(Port|ListenAddress|PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|UsePAM|KbdInteractiveAuthentication|ChallengeResponseAuthentication|AllowUsers|DenyUsers|AllowGroups|DenyGroups)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf >> "$REPORT" 2>&1 || true
    if command -v sshd >/dev/null 2>&1 && sshd -t >> "$REPORT" 2>&1; then ok "sshd_config 语法：正常"; else warn "sshd_config 语法：异常"; plan "根据日志错误行修复；语法异常时脚本不会重启 SSH"; fi
    local pass root pam
    pass="$(sshd -T 2>/dev/null | awk '/^passwordauthentication /{print $2}')"
    root="$(sshd -T 2>/dev/null | awk '/^permitrootlogin /{print $2}')"
    pam="$(sshd -T 2>/dev/null | awk '/^usepam /{print $2}')"
    [ "$pass" = "no" ] && { warn "密码登录：已禁用"; plan "如需密码登录，进入【SSH 登录策略检查与开关项】开启 PasswordAuthentication"; } || ok "密码登录：${pass:-未知}"
    case "$root" in no|prohibit-password|forced-commands-only) warn "root 登录：$root，可能被限制"; plan "如需 root 登录，进入【SSH 登录策略检查与开关项】允许 root 登录";; *) ok "root 登录：${root:-未知}";; esac
    [ "$pam" = "no" ] && { warn "UsePAM：no，可能影响密码认证"; plan "进入【SSH 登录策略检查与开关项】启用 UsePAM"; } || ok "UsePAM：${pam:-未知}"
    if [ -f /etc/ssh/sshd_config.d/50-cloud-init.conf ] && grep -qiE '^[[:space:]]*PasswordAuthentication[[:space:]]+no' /etc/ssh/sshd_config.d/50-cloud-init.conf; then warn "cloud-init：50-cloud-init.conf 禁用了密码登录"; plan "进入【SSH 登录策略检查与开关项】修复 cloud-init 覆盖"; else ok "cloud-init：未发现禁用密码登录覆盖"; fi
    if grep -RqiE '^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null; then warn "用户访问限制：存在 Allow/Deny 用户或组策略"; plan "确认策略后再通过菜单注释限制项"; else ok "用户访问限制：未发现 Allow/Deny 限制"; fi
}

check_firewall(){
    section "4. 防火墙与端口放行检查 / 自动处理"
    get_ssh_ports; detect_firewalls
    info "检测到启用的限制型防火墙：$FIREWALL_TYPES；SSH 端口：$SSH_PORTS"
    if [ "$FIREWALL_TYPES" = "none" ]; then ok "系统防火墙：未检测到启用的限制型防火墙，SSH 端口无需放行"; return 0; fi
    local issue=0 p policy
    if echo "$FIREWALL_TYPES" | grep -qw firewalld; then for p in $SSH_PORTS; do firewall-cmd --query-port=${p}/tcp >/dev/null 2>&1 || { [ "$p" = "22" ] && firewall-cmd --query-service=ssh >/dev/null 2>&1; } || { warn "firewalld：未放行 $p/tcp"; issue=1; }; done; fi
    if echo "$FIREWALL_TYPES" | grep -qw ufw; then for p in $SSH_PORTS; do ufw status 2>/dev/null | grep -qE "(^|[[:space:]])${p}(/tcp)?([[:space:]]|$)" || { warn "ufw：未放行 $p/tcp"; issue=1; }; done; fi
    if echo "$FIREWALL_TYPES" | grep -qw iptables; then policy="$(iptables -S INPUT 2>/dev/null | awk 'NR==1{print $3}')"; if [ "$policy" = "DROP" ] || [ "$policy" = "REJECT" ] || iptables -S INPUT 2>/dev/null | grep -qE ' -j (DROP|REJECT)( |$)'; then for p in $SSH_PORTS; do iptables -C INPUT -p tcp --dport "$p" -j ACCEPT >/dev/null 2>&1 || { warn "iptables：存在限制策略，未显式放行 $p/tcp"; issue=1; }; done; fi; fi
    if echo "$FIREWALL_TYPES" | grep -qw nftables; then for p in $SSH_PORTS; do nft list ruleset 2>/dev/null | grep -qE "dport[[:space:]]+$p[[:space:]]+accept" || { warn "nftables：存在限制型规则，未确认 $p/tcp 已放行"; issue=1; }; done; fi
    if [ "$issue" = "1" ]; then fixmsg "检测到 SSH 端口防火墙放行异常，开始自动放行当前 SSH 端口"; allow_ssh_port_auto || true; else ok "防火墙：未发现 SSH 端口放行异常"; fi
}

check_security(){
    section "5. SELinux 与 ping 检查 / 自动处理"
    if command -v getenforce >/dev/null 2>&1; then local se; se="$(getenforce)"; if [ "$se" = "Enforcing" ]; then warn "SELinux：Enforcing，开始临时切换为 Permissive"; selinux_temp_permissive || true; else ok "SELinux：$se"; fi; else ok "SELinux：未检测到 SELinux 工具或不适用"; fi
    if [ -f /proc/sys/net/ipv4/icmp_echo_ignore_all ]; then local v; v="$(cat /proc/sys/net/ipv4/icmp_echo_ignore_all)"; if [ "$v" = "1" ]; then warn "Ping：系统已禁 ping，开始临时开启"; enable_ping_temp || true; else ok "Ping：系统允许 ping"; fi; fi
}

check_openssl_sshd_internal(){
    section "6. OpenSSL/sshd 依赖库异常判断"
    detect_ssh_service
    if systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then ok "SSH 依赖库：SSH 服务正常，未触发依赖库修复判断"; return; fi
    if openssl_sshd_issue_detected; then warn "SSH 依赖库：检测到 OpenSSL/libcrypto/libssl 异常特征"; [ "$AUTO_FIX_LOW_RISK" = "1" ] && { fixmsg "一键检测已触发 OpenSSL/sshd 自动修复"; fix_openssl_sshd_auto; } || plan "进入【OpenSSL/sshd 依赖库异常修复】执行系统包修复"; else ok "SSH 依赖库：未匹配到 OpenSSL/libcrypto/libssl 异常特征"; fi
}

check_netplan(){
    section "7. netplan 配置检查"
    if [ ! -d /etc/netplan ]; then ok "netplan：当前系统未使用 /etc/netplan"; return; fi
    log_detail "---- netplan files ----"; ls -l /etc/netplan >> "$REPORT" 2>&1; sed -n '1,200p' /etc/netplan/*.yaml >> "$REPORT" 2>&1 || true
    if command -v netplan >/dev/null 2>&1; then
        if netplan generate >> "$REPORT" 2>&1; then ok "netplan：语法检查通过"; else warn "netplan：generate 失败，配置可能异常"; plan "进入【netplan 网络配置修复】菜单，先备份后使用 netplan try/apply"; fi
    else ok "netplan：目录存在但 netplan 命令不存在"
    fi
}

check_cc_attack(){
    section "10. CC/高连接 IP 检查"
    local threshold="${1:-$CC_THRESHOLD_DEFAULT}" ports="80|443"
    if ! command -v ss >/dev/null 2>&1; then warn "CC 检查：ss 命令不存在"; return; fi
    local top high
    top="$(ss -Hant state established 2>/dev/null | awk -v ports="$ports" '$4 ~ ":("ports")$" {peer=$5; gsub(/^\[/,"",peer); gsub(/\]$/,"",peer); sub(/:[0-9]+$/,"",peer); if(peer!="" && peer!="127.0.0.1" && peer!="::1") c[peer]++} END{for(ip in c) print c[ip], ip}' | sort -nr | head -10)"
    high="$(echo "$top" | awk -v th="$threshold" '$1>=th{print}')"
    log_detail "---- high connection top ----"; echo "$top" >> "$REPORT"
    if [ -n "$high" ]; then warn "高连接 IP：发现超过阈值 $threshold 的来源"; echo "$high" | while read -r cnt ip; do [ -n "$ip" ] && warn "来源 $ip 连接数 $cnt"; done; plan "默认不封禁，确认不是 CDN/代理/监控/真实业务后进入【CC连接限制】封禁"; else ok "高连接 IP：未发现超过阈值 $threshold 的来源"; fi
}

check_open_ports(){
    section "11. 对外开放端口检查"
    if ! command -v ss >/dev/null 2>&1; then warn "开放端口：ss 命令不存在"; return; fi
    log_detail "---- TCP listen ----"; ss -lntp >> "$REPORT" 2>&1
    log_detail "---- UDP listen ----"; ss -lnup >> "$REPORT" 2>&1
    local ports
    ports="$(ss -lntp 2>/dev/null | awk 'NR>1 && $4 !~ /^127\.0\.0\.1:/ && $4 !~ /^\[::1\]:/ && $4 !~ /^::1:/ {print $4}' | sed -E 's/.*:([0-9]+)$/\1/' | grep -E '^[0-9]+$' | sort -n | uniq | xargs)"
    info "对外监听 TCP 端口：${ports:-未发现}"
    plan "如发现不应开放的敏感端口，先确认进程用途，再关闭服务或限制来源 IP"
}

check_all(){
    AUTO_FIX_LOW_RISK=0
    init_detect
    section "完整检查：只检测不修改系统"
    check_system_network
    check_ssh_service
    check_ssh_config
    check_firewall
    check_security
    check_openssl_sshd_internal
    check_netplan
    check_cc_attack "$CC_THRESHOLD_DEFAULT"
    check_open_ports
    section "检查完成"
}

auto_check_and_fix(){
    AUTO_FIX_LOW_RISK=1
    init_detect
    section "一键检测并自动处理常见问题（包含 SSH OpenSSL/依赖异常）"
    check_system_network
    check_ssh_service
    check_ssh_config
    check_firewall
    check_security
    check_openssl_sshd_internal
    check_netplan
    check_cc_attack "$CC_THRESHOLD_DEFAULT"
    check_open_ports
    AUTO_FIX_LOW_RISK=0
    section "自动检测修复完成"
}

# ------------------------- 菜单修复 -------------------------
set_sshd_kv(){ local key="$1" val="$2"; if grep -qE "^[#[:space:]]*${key}[[:space:]]+" "$SSH_CONFIG"; then sed -i "s/^[#[:space:]]*${key}[[:space:]].*/${key} ${val}/g" "$SSH_CONFIG"; else echo "${key} ${val}" >> "$SSH_CONFIG"; fi; }

fix_ssh_service_menu(){
    section "SSH 服务扩展项"
    detect_ssh_service; detect_os; get_ssh_ports
    echo "当前服务：$SSH_SERVICE；当前识别端口：$SSH_PORTS"
    echo "1) 重新执行 SSH 服务检查并自动处理"
    echo "2) 启动 SSH 服务并设置开机自启"
    echo "3) 安全重启 SSH（先 sshd -t）"
    echo "4) 安装/重装 openssh-server"
    echo "5) 修复 SSH 只监听 127.0.0.1/::1"
    echo "6) 查看 SSH 实际监听端口"
    echo "7) 修改 SSH 端口（会备份配置并安全重启）"
    echo "8) 重新生成 SSH HostKey（谨慎，客户端会提示主机指纹变化）"
    echo "0) 返回"
    read -r -p "选择：" c
    case "$c" in
        1) check_ssh_service;;
        2) start_enable_ssh;;
        3) restart_ssh_safe;;
        4) confirm "将安装/重装 openssh-server，确认？" || return; if command -v apt >/dev/null 2>&1; then apt update >/dev/null 2>&1; apt install --reinstall -y openssh-server >/dev/null 2>&1; elif command -v dnf >/dev/null 2>&1; then dnf reinstall -y openssh-server >/dev/null 2>&1 || dnf install -y openssh-server >/dev/null 2>&1; else yum reinstall -y openssh-server >/dev/null 2>&1 || yum install -y openssh-server >/dev/null 2>&1; fi; fixmsg "openssh-server 安装/重装命令已执行"; restart_ssh_safe;;
        5) fix_local_listen_auto;;
        6) ss -lntp 2>/dev/null | grep -E 'sshd|dropbear|:22 ' || true;;
        7) read -r -p "请输入新的 SSH 端口：" newp; echo "$newp" | grep -Eq '^[0-9]+$' || { warn "端口无效"; return; }; backup_file "$SSH_CONFIG"; if grep -qE '^[#[:space:]]*Port[[:space:]]+' "$SSH_CONFIG"; then sed -i -E "0,/^[#[:space:]]*Port[[:space:]]+[0-9]+/s//Port $newp/" "$SSH_CONFIG"; else echo "Port $newp" >> "$SSH_CONFIG"; fi; SSH_PORTS="$newp"; allow_ssh_port_auto; restart_ssh_safe;;
        8) confirm "重新生成 SSH HostKey 会导致客户端主机指纹变化，确认？" || return; mkdir -p /etc/ssh/old-hostkeys-${RUN_ID}; mv /etc/ssh/ssh_host_* /etc/ssh/old-hostkeys-${RUN_ID}/ 2>/dev/null || true; ssh-keygen -A >/dev/null 2>&1; fixmsg "SSH HostKey 已重新生成，旧文件保存在 /etc/ssh/old-hostkeys-${RUN_ID}"; restart_ssh_safe;;
        0) return;;
        *) warn "无效选项";;
    esac
}

fix_ssh_policy_menu(){
    section "SSH 登录策略检查与开关项"
    detect_ssh_service
    check_ssh_config
    echo "1) 开启密码登录"
    echo "2) 关闭密码登录"
    echo "3) 允许 root 登录"
    echo "4) 禁止 root 登录"
    echo "5) 启用 UsePAM"
    echo "6) 关闭 UsePAM"
    echo "7) 修复 cloud-init 禁用密码登录"
    echo "8) 注释 AllowUsers/DenyUsers/AllowGroups/DenyGroups 限制"
    echo "9) 校验并重启 SSH"
    echo "0) 返回"
    read -r -p "选择：" c
    [ "$c" = "0" ] && return
    confirm "该操作会修改 SSH 登录策略并备份配置，确认？" || return
    backup_file "$SSH_CONFIG"; backup_dir /etc/ssh/sshd_config.d
    case "$c" in
        1) set_sshd_kv PasswordAuthentication yes; set_sshd_kv KbdInteractiveAuthentication yes; set_sshd_kv ChallengeResponseAuthentication yes; set_sshd_kv UsePAM yes;;
        2) set_sshd_kv PasswordAuthentication no;;
        3) set_sshd_kv PermitRootLogin yes;;
        4) set_sshd_kv PermitRootLogin no;;
        5) set_sshd_kv UsePAM yes;;
        6) set_sshd_kv UsePAM no;;
        7) mkdir -p /etc/ssh/sshd_config.d; if [ -f /etc/ssh/sshd_config.d/50-cloud-init.conf ]; then sed -i 's/^[#[:space:]]*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config.d/50-cloud-init.conf; else echo 'PasswordAuthentication yes' > /etc/ssh/sshd_config.d/50-cloud-init.conf; fi;;
        8) sed -i -E 's/^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)/#&/g' "$SSH_CONFIG"; grep -RliE '^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)' /etc/ssh/sshd_config.d/*.conf 2>/dev/null | while read -r f; do sed -i -E 's/^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)/#&/g' "$f"; done;;
        9) :;;
        *) warn "无效选项"; return;;
    esac
    restart_ssh_safe
}

fix_firewall_port_menu(){
    section "防火墙端口扩展项"
    init_detect
    echo "当前 SSH 端口：$SSH_PORTS"
    echo "1) 自动放行当前 SSH 端口"
    echo "2) 手动输入端口放行"
    echo "3) nftables 尝试放行"
    echo "0) 返回"
    read -r -p "选择：" c
    case "$c" in
        1) allow_ssh_port_auto;;
        2) read -r -p "请输入端口：" ports; confirm "将放行端口：$ports/tcp，确认？" || return; SSH_PORTS="$ports"; allow_ssh_port_auto;;
        3) read -r -p "请输入端口，默认当前 SSH 端口：" ports_in; fix_nftables_port "${ports_in:-$SSH_PORTS}";;
        0) return;;
        *) warn "无效选项";;
    esac
}

fix_nftables_port(){
    local ports="$1" table chain
    command -v nft >/dev/null 2>&1 || { warn "nft 不存在"; return; }
    table="$(nft list ruleset 2>/dev/null | awk '/table inet/{print $3; exit}')"
    chain="$(nft list ruleset 2>/dev/null | awk '/chain /{c=$2} /hook input/{print c; exit}')"
    if [ -z "$table" ] || [ -z "$chain" ]; then warn "无法明确识别 nftables inet input 链，拒绝自动写入"; plan "示例：nft add rule inet filter input tcp dport <端口> accept"; return; fi
    confirm "将执行 nft add rule inet $table $chain tcp dport { $ports } accept，确认？" || return
    local p; for p in $ports; do nft add rule inet "$table" "$chain" tcp dport "$p" accept >> "$REPORT" 2>&1 && fixmsg "nftables 已放行 $p/tcp"; done
    nft list ruleset > /etc/nftables.conf 2>>"$REPORT" && fixmsg "已尝试保存到 /etc/nftables.conf"
}

set_sysctl_conf(){ local k="$1" v="$2"; grep -q "^${k}" /etc/sysctl.conf 2>/dev/null && sed -i "s/^${k}.*/${k} = ${v}/" /etc/sysctl.conf || echo "${k} = ${v}" >> /etc/sysctl.conf; }

fix_security_menu(){
    section "SELinux / Ping 扩展项"
    echo "1) 临时关闭 SELinux（切换为 Permissive）"
    echo "2) 永久关闭 SELinux"
    echo "3) 将 SELinux 设置为 Permissive"
    echo "4) 为当前 SSH 端口添加 ssh_port_t"
    echo "5) 临时开启 ping"
    echo "6) 永久开启 ping"
    echo "7) 临时关闭 ping"
    echo "8) 永久关闭 ping"
    echo "0) 返回"
    read -r -p "选择：" c
    case "$c" in
        1) selinux_temp_permissive;;
        2) confirm "永久关闭 SELinux 需要重启后完全生效，确认？" || return; backup_file /etc/selinux/config; sed -i 's/^SELINUX=.*/SELINUX=disabled/g' /etc/selinux/config && fixmsg "SELinux 永久配置已改为 disabled";;
        3) backup_file /etc/selinux/config; sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config; command -v setenforce >/dev/null 2>&1 && setenforce 0; fixmsg "SELinux 已设置为 permissive";;
        4) get_ssh_ports; local p; for p in $SSH_PORTS; do if command -v semanage >/dev/null 2>&1; then semanage port -l | grep -q "ssh_port_t.*\b$p\b" || semanage port -a -t ssh_port_t -p tcp "$p" >> "$REPORT" 2>&1 || semanage port -m -t ssh_port_t -p tcp "$p" >> "$REPORT" 2>&1; fixmsg "已检查/添加 ssh_port_t $p/tcp"; else warn "semanage 不存在，请安装 policycoreutils-python-utils/policycoreutils-python"; fi; done;;
        5) sysctl -w net.ipv4.icmp_echo_ignore_all=0 >> "$REPORT" 2>&1 && fixmsg "已临时开启 ping";;
        6) backup_file /etc/sysctl.conf; set_sysctl_conf net.ipv4.icmp_echo_ignore_all 0; sysctl -p >> "$REPORT" 2>&1; fixmsg "已永久开启 ping";;
        7) sysctl -w net.ipv4.icmp_echo_ignore_all=1 >> "$REPORT" 2>&1 && fixmsg "已临时关闭 ping";;
        8) backup_file /etc/sysctl.conf; set_sysctl_conf net.ipv4.icmp_echo_ignore_all 1; sysctl -p >> "$REPORT" 2>&1; fixmsg "已永久关闭 ping";;
        0) return;;
        *) warn "无效选项";;
    esac
}

fix_netplan_menu(){
    section "高危修复：netplan 网络配置"
    [ -d /etc/netplan ] || { warn "/etc/netplan 不存在"; return; }
    echo "1) 检查 netplan 语法（generate）"
    echo "2) 执行 netplan try"
    echo "3) 执行 netplan apply"
    echo "4) 备份 netplan 配置"
    echo "5) 根据当前 IP/GW/DNS 生成 netplan 草案"
    echo "0) 返回"
    read -r -p "选择：" c
    case "$c" in
        1) netplan generate >> "$REPORT" 2>&1 && ok "netplan generate 通过" || warn "netplan generate 失败";;
        2) confirm "netplan try 可能影响网络，确认？" || return; netplan try;;
        3) confirm "netplan apply 可能导致断网，确认？" || return; backup_dir /etc/netplan; netplan apply >> "$REPORT" 2>&1 && fixmsg "netplan apply 已执行";;
        4) backup_dir /etc/netplan; fixmsg "netplan 已备份";;
        5) get_primary_network; if [ -z "$PRIMARY_IF" ] || [ -z "$PRIMARY_IP_CIDR" ] || [ -z "$PRIMARY_GW" ]; then warn "缺少主网卡/IP/网关，无法生成"; return; fi; backup_dir /etc/netplan; cat > "$BACKUP_DIR/01-${PRIMARY_IF}-generated.yaml" <<EON
network:
  version: 2
  renderer: networkd
  ethernets:
    ${PRIMARY_IF}:
      addresses:
        - ${PRIMARY_IP_CIDR}
      routes:
        - to: default
          via: ${PRIMARY_GW}
      nameservers:
        addresses: [1.1.1.1, 8.8.8.8]
EON
        fixmsg "已生成 netplan 草案：$BACKUP_DIR/01-${PRIMARY_IF}-generated.yaml";;
        0) return;;
        *) warn "无效选项";;
    esac
}

detect_network_config_method(){
    # 先识别系统，再结合实际启用的网络管理器与配置文件判断。
    # 避免 Ubuntu/Debian 因残留 ifcfg 文件而被误判为 CentOS 配置方式。
    NET_MGR="unknown"; NET_CFG=""; NET_CON=""
    detect_os
    get_primary_network

    case "$OS_ID" in
        ubuntu)
            if ls /etc/netplan/*.yaml >/dev/null 2>&1; then
                NET_MGR="netplan"
                NET_CFG="$(grep -Rsl "$PRIMARY_IF" /etc/netplan/*.yaml 2>/dev/null | head -1)"
                [ -z "$NET_CFG" ] && NET_CFG="$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)"
                return
            fi
            if command -v nmcli >/dev/null 2>&1 && systemctl is-active NetworkManager >/dev/null 2>&1; then
                NET_CON="$(nmcli -t -f NAME,DEVICE con show --active 2>/dev/null | awk -F: -v dev="$PRIMARY_IF" '$2==dev{print $1; exit}')"
                [ -n "$NET_CON" ] && { NET_MGR="NetworkManager"; NET_CFG="/etc/NetworkManager/system-connections/*.nmconnection"; return; }
            fi
            ;;
        debian)
            if ls /etc/netplan/*.yaml >/dev/null 2>&1; then
                NET_MGR="netplan"
                NET_CFG="$(grep -Rsl "$PRIMARY_IF" /etc/netplan/*.yaml 2>/dev/null | head -1)"
                [ -z "$NET_CFG" ] && NET_CFG="$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)"
                return
            fi
            if grep -RqsE "^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+$PRIMARY_IF([[:space:]]|$)" /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null; then
                NET_MGR="interfaces"
                NET_CFG="$(grep -RslE "^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+$PRIMARY_IF([[:space:]]|$)" /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null | head -1)"
                [ -z "$NET_CFG" ] && NET_CFG="/etc/network/interfaces"
                return
            fi
            ;;
        centos|rhel)
            if [[ "$OS_VERSION" == 7* ]] && [ -f "/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}" ]; then
                NET_MGR="ifcfg"; NET_CFG="/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}"; return
            fi
            ;;
    esac

    # Rocky / AlmaLinux / CentOS Stream 8/9 及其他实际使用 NetworkManager 的系统。
    if command -v nmcli >/dev/null 2>&1 && systemctl is-active NetworkManager >/dev/null 2>&1; then
        NET_CON="$(nmcli -t -f NAME,DEVICE con show --active 2>/dev/null | awk -F: -v dev="$PRIMARY_IF" '$2==dev{print $1; exit}')"
        if [ -n "$NET_CON" ]; then
            NET_MGR="NetworkManager"
            NET_CFG="/etc/NetworkManager/system-connections/*.nmconnection"
            return
        fi
    fi

    # RHEL 系兼容/遗留 ifcfg。
    if [ -n "$PRIMARY_IF" ] && [ -f "/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}" ]; then
        NET_MGR="ifcfg"
        NET_CFG="/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}"
        return
    fi

    # systemd-networkd 兜底。
    if systemctl is-active systemd-networkd >/dev/null 2>&1 && ls /etc/systemd/network/*.network >/dev/null 2>&1; then
        NET_MGR="systemd-networkd"
        NET_CFG="$(grep -Rsl "$PRIMARY_IF" /etc/systemd/network/*.network 2>/dev/null | head -1)"
        [ -z "$NET_CFG" ] && NET_CFG="$(ls /etc/systemd/network/*.network 2>/dev/null | head -1)"
        return
    fi

    # Debian interfaces 最后兜底。
    if [ -f /etc/network/interfaces ]; then
        NET_MGR="interfaces"
        NET_CFG="/etc/network/interfaces"
    fi
}

show_route_persistence_notes(){
    section "默认路由永久配置说明"
    cat <<'EON'
CentOS 7：
  网络管理方式：network-scripts / ifcfg
  配置文件：/etc/sysconfig/network-scripts/ifcfg-网卡名
  永久生效配置：GATEWAY=网关IP、DEFROUTE=yes
  常用生效方式：systemctl restart network

Rocky / AlmaLinux / CentOS Stream 8/9：
  网络管理方式：NetworkManager
  配置位置：/etc/NetworkManager/system-connections/*.nmconnection
  推荐命令：nmcli connection modify 连接名 ipv4.gateway 网关IP ipv4.never-default no
  常用生效方式：nmcli connection up 连接名

Ubuntu 18/20/22/24、部分 Debian：
  网络管理方式：netplan
  配置文件：/etc/netplan/*.yaml
  永久生效配置：gateway4: 网关IP 或 routes: [{to: default, via: 网关IP}]
  常用生效方式：netplan generate && netplan try，确认正常后 apply

Debian 11/12/13 ifupdown：
  网络管理方式：interfaces
  配置文件：/etc/network/interfaces 或 /etc/network/interfaces.d/*
  永久生效配置：gateway 网关IP
  常用生效方式：systemctl restart networking

systemd-networkd：
  网络管理方式：systemd-networkd
  配置文件：/etc/systemd/network/*.network
  永久生效配置：[Route] Gateway=网关IP
  常用生效方式：systemctl restart systemd-networkd
EON
}

list_default_routes(){
    ip route show default 2>/dev/null | nl -w1 -s') '
}

get_default_route_by_number(){
    local n="$1"
    ip route show default 2>/dev/null | sed -n "${n}p"
}

delete_default_route_by_number(){
    section "删除已识别的默认路由"
    local count sel route via dev metric cmd
    count="$(ip route show default 2>/dev/null | wc -l | awk '{print $1}')"
    [ "$count" = "0" ] && { ok "当前没有默认路由可删除"; return; }

    info "检测到默认路由："
    list_default_routes
    read -r -p "请选择要删除的默认路由编号：" sel
    route="$(get_default_route_by_number "$sel")"
    [ -z "$route" ] && { warn "选择无效"; return; }

    via="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}')"
    dev="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}')"
    metric="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="metric") print $(i+1)}')"

    cmd="ip route del default"
    [ -n "$via" ] && cmd="$cmd via $via"
    [ -n "$dev" ] && cmd="$cmd dev $dev"
    [ -n "$metric" ] && cmd="$cmd metric $metric"

    echo "[命令] $cmd"
    if [ "$count" = "1" ]; then
        confirm "当前只有 1 条默认路由，删除后可能导致服务器无法访问外网，确认删除？" || return
    else
        confirm "确认删除选中的默认路由？" || return
    fi
    eval "$cmd" >> "$REPORT" 2>&1 && fixmsg "已删除默认路由：$route" || err "默认路由删除失败"
}

write_default_route_permanent(){
    local gw="$1" mgr="$NET_MGR" cfg="$NET_CFG" con="$NET_CON" cmd
    [ -z "$gw" ] && { warn "网关为空，无法写入"; return 1; }
    [ -z "$PRIMARY_IF" ] && { warn "未识别主网卡，无法写入"; return 1; }

    case "$mgr" in
        NetworkManager)
            [ -z "$con" ] && { warn "未识别 NetworkManager 连接名"; return 1; }
            info "系统网络配置方式：NetworkManager；适用：Rocky/AlmaLinux/CentOS Stream 8/9，也可能适用于部分 Ubuntu/Debian"
            info "配置位置：/etc/NetworkManager/system-connections/*.nmconnection"
            echo "[命令] nmcli connection modify \"$con\" ipv4.gateway \"$gw\" ipv4.never-default no"
            echo "[命令] nmcli connection up \"$con\""
            confirm "确认将默认路由永久写入 NetworkManager？" || return
            nmcli connection modify "$con" ipv4.gateway "$gw" ipv4.never-default no >> "$REPORT" 2>&1 && \
            nmcli connection up "$con" >> "$REPORT" 2>&1 && fixmsg "默认路由已永久写入 NetworkManager：$gw" || err "NetworkManager 写入失败"
            ;;
        ifcfg)
            [ -f "$cfg" ] || { warn "ifcfg 配置文件不存在：$cfg"; return 1; }
            info "系统网络配置方式：ifcfg/network-scripts；适用：CentOS 7"
            info "配置文件：$cfg"
            echo "[命令] cp -a $cfg $BACKUP_DIR/"
            echo "[命令] 写入 GATEWAY=$gw 和 DEFROUTE=yes"
            echo "[命令] systemctl restart network"
            confirm "确认将默认路由永久写入 ifcfg？" || return
            backup_file "$cfg"
            grep -q '^GATEWAY=' "$cfg" && sed -i "s/^GATEWAY=.*/GATEWAY=$gw/" "$cfg" || echo "GATEWAY=$gw" >> "$cfg"
            grep -q '^DEFROUTE=' "$cfg" && sed -i 's/^DEFROUTE=.*/DEFROUTE=yes/' "$cfg" || echo 'DEFROUTE=yes' >> "$cfg"
            fixmsg "已写入 $cfg。请根据业务窗口执行 systemctl restart network"
            ;;
        netplan)
            [ -f "$cfg" ] || { warn "netplan 配置文件不存在：$cfg"; return 1; }
            info "系统网络配置方式：netplan；适用：Ubuntu 18/20/22/24、部分 Debian"
            info "配置文件：$cfg"
            echo "[命令] cp -a $cfg $BACKUP_DIR/"
            if grep -qE '^[[:space:]]*gateway4:' "$cfg"; then
                echo "[命令] sed -i 's/^\\([[:space:]]*gateway4:[[:space:]]*\\).*/\\1$gw/' $cfg"
                echo "[命令] netplan generate && netplan try"
                confirm "确认替换 netplan 中的 gateway4？" || return
                backup_file "$cfg"
                sed -i -E "s/^([[:space:]]*gateway4:[[:space:]]*).*/\\1$gw/" "$cfg"
                netplan generate >> "$REPORT" 2>&1 && fixmsg "已写入 netplan gateway4=$gw；建议执行 netplan try 验证" || err "netplan generate 失败，请检查 $cfg"
            else
                local draft="$BACKUP_DIR/netplan-${PRIMARY_IF}-default-route-draft.yaml"
                cat > "$draft" <<EON
# netplan 默认路由草案，适用 Ubuntu/Debian netplan。
# 请人工合并到 $cfg 的 $PRIMARY_IF 网卡段落中，然后执行：netplan generate && netplan try
routes:
  - to: default
    via: $gw
EON
                plan "当前 netplan 未发现 gateway4，已生成 routes 草案：$draft"
                plan "为避免误改 YAML 层级，脚本未直接覆盖原 netplan；请人工合并后执行 netplan try"
            fi
            ;;
        interfaces)
            [ -f "$cfg" ] || cfg="/etc/network/interfaces"
            [ -f "$cfg" ] || { warn "interfaces 配置文件不存在"; return 1; }
            info "系统网络配置方式：interfaces/ifupdown；适用：Debian 11/12/13 使用 /etc/network/interfaces 的场景"
            info "配置文件：$cfg"
            echo "[命令] cp -a $cfg $BACKUP_DIR/"
            echo "[命令] 修改主网卡 iface 段落中的 gateway $gw"
            echo "[命令] systemctl restart networking"
            confirm "确认将默认路由永久写入 interfaces？" || return
            backup_file "$cfg"
            awk -v iface="$PRIMARY_IF" -v gw="$gw" '
                BEGIN{inif=0; wrote=0}
                $0 ~ "^[[:space:]]*iface[[:space:]]+" iface "[[:space:]]+inet" {inif=1; wrote=0; print; next}
                inif && $0 ~ "^[[:space:]]*iface[[:space:]]+" {if(!wrote){print "    gateway " gw}; inif=0}
                inif && $1=="gateway" {if(!wrote){print "    gateway " gw; wrote=1}; next}
                {print}
                END{if(inif && !wrote) print "    gateway " gw}
            ' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg"
            fixmsg "已写入 $cfg。请根据业务窗口执行 systemctl restart networking"
            ;;
        systemd-networkd)
            [ -f "$cfg" ] || { warn "systemd-networkd 配置文件不存在"; return 1; }
            info "系统网络配置方式：systemd-networkd"
            info "配置文件：$cfg"
            echo "[命令] cp -a $cfg $BACKUP_DIR/"
            echo "[命令] 写入 [Route] Gateway=$gw"
            echo "[命令] systemctl restart systemd-networkd"
            confirm "确认将默认路由永久写入 systemd-networkd？" || return
            backup_file "$cfg"
            if grep -q '^Gateway=' "$cfg"; then sed -i "s/^Gateway=.*/Gateway=$gw/" "$cfg"; else printf '\n[Route]\nGateway=%s\n' "$gw" >> "$cfg"; fi
            fixmsg "已写入 $cfg。请根据业务窗口执行 systemctl restart systemd-networkd"
            ;;
        *)
            warn "未能识别网络配置方式，无法安全写入永久配置"
            plan "可先临时执行：ip route replace default via $gw dev $PRIMARY_IF"
            ;;
    esac
}

fix_default_route_menu(){
    section "高危修复：默认路由"
    detect_os; get_primary_network; detect_network_config_method
    local defaults gw sel route dev metric
    defaults="$(ip route show default 2>/dev/null)"

    if [ -n "$defaults" ]; then
        info "当前默认路由："
        list_default_routes
    else
        warn "当前默认路由：缺失"
    fi
    info "主网卡：${PRIMARY_IF:-未识别}；主IP：${PRIMARY_IP_CIDR:-未识别}；当前网关：${PRIMARY_GW:-未识别}"
    info "网络配置方式：$NET_MGR；配置位置：${NET_CFG:-未识别}"

    case "$NET_MGR" in
        NetworkManager) plan "永久生效将使用 nmcli 写入 NetworkManager 连接配置。适用 Rocky/AlmaLinux/CentOS Stream 8/9，也可能适用于部分 Ubuntu/Debian。";;
        ifcfg) plan "永久生效将写入 ifcfg 文件。适用 CentOS 7 network-scripts。";;
        netplan) plan "永久生效将写入/生成 netplan 配置。适用 Ubuntu 18/20/22/24、部分 Debian。";;
        interfaces) plan "永久生效将写入 /etc/network/interfaces。适用 Debian ifupdown。";;
        systemd-networkd) plan "永久生效将写入 /etc/systemd/network/*.network。适用 systemd-networkd。";;
        *) plan "未识别永久配置方式，仅建议临时修改默认路由。";;
    esac

    echo "1) 临时修改默认路由"
    echo "2) 永久修改默认路由"
    echo "3) 删除已识别的默认路由"
    echo "4) 修改默认路由 metric"
    echo "5) 查看不同系统默认路由写入说明"
    echo "0) 返回"
    read -r -p "选择：" c

    case "$c" in
        1)
            [ -z "$PRIMARY_IF" ] && read -r -p "请输入网卡名：" PRIMARY_IF
            read -r -p "请输入新的默认网关 IP：" gw
            [ -z "$gw" ] && { warn "网关不能为空"; return; }
            echo "[命令] ip route replace default via $gw dev $PRIMARY_IF"
            confirm "该命令立即生效但重启后可能失效，确认执行？" || return
            ip route replace default via "$gw" dev "$PRIMARY_IF" >> "$REPORT" 2>&1 && fixmsg "默认路由已临时修改为：default via $gw dev $PRIMARY_IF" || err "临时修改默认路由失败"
            ;;
        2)
            read -r -p "请输入新的默认网关 IP：" gw
            [ -z "$gw" ] && { warn "网关不能为空"; return; }
            write_default_route_permanent "$gw"
            ;;
        3)
            delete_default_route_by_number
            ;;
        4)
            if [ -z "$defaults" ]; then warn "当前没有默认路由可修改 metric"; return; fi
            info "检测到默认路由："; list_default_routes
            read -r -p "请选择要修改 metric 的默认路由编号：" sel
            route="$(get_default_route_by_number "$sel")"
            [ -z "$route" ] && { warn "选择无效"; return; }
            dev="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}')"
            gw="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}')"
            read -r -p "请输入新的 metric：" metric
            [ -z "$gw" ] || [ -z "$dev" ] || [ -z "$metric" ] && { warn "缺少网关/网卡/metric，无法执行"; return; }
            echo "[命令] ip route replace default via $gw dev $dev metric $metric"
            confirm "确认修改默认路由 metric？" || return
            ip route replace default via "$gw" dev "$dev" metric "$metric" >> "$REPORT" 2>&1 && fixmsg "默认路由 metric 已修改" || err "metric 修改失败"
            ;;
        5)
            show_route_persistence_notes
            ;;
        0) return;;
        *) warn "无效选项";;
    esac
}

fix_openssl_sshd_menu(){
    section "OpenSSL / sshd 依赖库异常修复"
    detect_os; detect_ssh_service
    log_detail "---- ldd /usr/sbin/sshd ----"; ldd /usr/sbin/sshd >> "$REPORT" 2>&1 || true
    if command -v rpm >/dev/null 2>&1; then rpm -q openssh-server openssl openssl-libs >> "$REPORT" 2>&1 || true; rpm -V openssh-server openssl-libs >> "$REPORT" 2>&1 || true; fi
    if command -v dpkg >/dev/null 2>&1; then dpkg -l openssh-server openssl 'libssl*' >> "$REPORT" 2>&1 || true; fi
    echo "1) 按当前系统自动修复依赖库"
    echo "2) 仅执行 ldconfig 并安全重启 SSH"
    echo "0) 返回"
    read -r -p "选择：" c
    case "$c" in
        1)
            confirm "该操作可能调整系统包并重启 SSH，建议在 VNC/控制台执行。确认？" || return
            if [ "$OS_FAMILY" = "rhel" ]; then if command -v dnf >/dev/null 2>&1; then dnf distro-sync -y >> "$REPORT" 2>&1; else yum update -y openssh-server openssl openssl-libs >> "$REPORT" 2>&1; fi
            elif [ "$OS_FAMILY" = "debian" ]; then apt update >> "$REPORT" 2>&1; apt install --reinstall -y openssh-server openssl >> "$REPORT" 2>&1; fi
            ldconfig >> "$REPORT" 2>&1; restart_ssh_safe;;
        2) ldconfig >> "$REPORT" 2>&1; restart_ssh_safe;;
        0) return;;
        *) warn "无效选项";;
    esac
}

fix_cc_block_menu(){
    section "CC连接限制 / 封禁高连接 IP"
    echo "输入 0 返回，或直接回车使用默认值。"
    read -r -p "请输入连接数阈值，默认100：" th
    [ "$th" = "0" ] && return
    th="${th:-100}"
    read -r -p "请输入统计端口，默认 80|443：" ports; ports="${ports:-80|443}"
    local ips ip
    ips="$(ss -Hant state established 2>/dev/null | awk -v ports="$ports" -v th="$th" '$4 ~ ":("ports")$" {peer=$5; gsub(/^\[/,"",peer); gsub(/\]$/,"",peer); sub(/:[0-9]+$/,"",peer); c[peer]++} END{for(ip in c) if(c[ip]>=th) print ip}' | grep -Ev '^(127\.0\.0\.1|::1)$')"
    [ -z "$ips" ] && { ok "未发现超过阈值的 IP"; return; }
    echo "将封禁以下 IP："; echo "$ips"
    confirm "确认封禁这些 IP？建议先确认是否为 CDN/代理/真实业务来源。" || return
    for ip in $ips; do
        if systemctl is-active firewalld >/dev/null 2>&1; then firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='${ip}' drop" >> "$REPORT" 2>&1; firewall-cmd --add-rich-rule="rule family='ipv4' source address='${ip}' drop" >> "$REPORT" 2>&1; fixmsg "firewalld 已封禁 $ip"; fi
        if command -v iptables >/dev/null 2>&1; then iptables -C INPUT -s "$ip" -j DROP >/dev/null 2>&1 || iptables -I INPUT -s "$ip" -j DROP >> "$REPORT" 2>&1; fixmsg "iptables 已封禁 $ip"; fi
        if systemctl is-active ufw >/dev/null 2>&1; then ufw deny from "$ip" >> "$REPORT" 2>&1; fixmsg "ufw 已封禁 $ip"; fi
        if command -v nft >/dev/null 2>&1; then plan "nftables 封禁未自动写入，请按规则集人工确认。IP=$ip"; fi
    done
    save_iptables
    systemctl is-active firewalld >/dev/null 2>&1 && firewall-cmd --reload >> "$REPORT" 2>&1
}


apply_network_config_after_replace(){
    local mgr="$1" cfg="$2"
    case "$mgr" in
        netplan)
            command -v netplan >/dev/null 2>&1 || { warn "netplan 命令不存在，配置已替换但未应用"; return 1; }
            netplan generate >/dev/null 2>&1 || { err "netplan generate 失败，未应用网络；原配置备份位于 $BACKUP_DIR"; return 1; }
            netplan apply >/dev/null 2>&1 && fixmsg "netplan 配置已应用" || err "netplan apply 失败"
            ;;
        NetworkManager)
            nmcli connection reload >/dev/null 2>&1
            if [ -n "$NET_CON" ] && nmcli connection up "$NET_CON" >/dev/null 2>&1; then fixmsg "NetworkManager 连接已重新加载：$NET_CON"; else err "NetworkManager 连接应用失败，请使用控制台检查"; fi
            ;;
        ifcfg)
            systemctl restart network >/dev/null 2>&1 && fixmsg "network 服务已重启，新配置已应用" || err "network 服务重启失败"
            ;;
        interfaces)
            systemctl restart networking >/dev/null 2>&1 && fixmsg "networking 服务已重启，新配置已应用" || err "networking 服务重启失败"
            ;;
        systemd-networkd)
            systemctl restart systemd-networkd >/dev/null 2>&1 && fixmsg "systemd-networkd 已重启，新配置已应用" || err "systemd-networkd 重启失败"
            ;;
        *)
            warn "未识别网络管理方式，配置已替换但无法自动应用"
            ;;
    esac
}

backup_clean_primary_config(){
    section "备份主网卡配置，并在当前配置路径生成只保留主 IP 的配置文件"
    detect_network_config_method
    [ -z "$PRIMARY_IF" ] && { warn "未识别主网卡"; return; }
    [ -z "$PRIMARY_IP_CIDR" ] && { warn "未识别主 IP"; return; }
    info "主网卡：$PRIMARY_IF；主IP：$PRIMARY_IP_CIDR；网关：${PRIMARY_GW:-未识别}"
    info "网络配置方式：$NET_MGR；配置位置：${NET_CFG:-未识别}"
    local files=""
    case "$NET_MGR" in
        ifcfg) [ -f "$NET_CFG" ] && files="$NET_CFG";;
        NetworkManager) if [ -n "$NET_CON" ]; then files="$(find /etc/NetworkManager/system-connections -maxdepth 1 -type f 2>/dev/null | while read -r f; do grep -q "interface-name=$PRIMARY_IF\|id=$NET_CON" "$f" 2>/dev/null && echo "$f"; done | head -1)"; fi; [ -z "$files" ] && files="$(find /etc/NetworkManager/system-connections -maxdepth 1 -type f -name '*.nmconnection' 2>/dev/null | head -1)";;
        netplan|interfaces|systemd-networkd) [ -f "$NET_CFG" ] && files="$NET_CFG";;
        *) files="$(grep -Rsl "$PRIMARY_IF" /etc/netplan/*.yaml /etc/sysconfig/network-scripts/ifcfg-* /etc/network/interfaces /etc/network/interfaces.d/* /etc/systemd/network/*.network 2>/dev/null | head -1)";;
    esac
    [ -z "$files" ] && { warn "未找到主网卡配置文件；可能由云平台或动态配置管理"; return; }
    local f clean
    for f in $files; do
        backup_file "$f"
        clean="$(create_clean_config_copy "$f")"
        info "已在当前配置路径生成：$clean"
        echo "[说明] 原配置已备份到：$BACKUP_DIR"
        confirm "是否用该文件替换原配置并立即重启/应用网络？该操作会移除附加 IP，可能影响业务。确认？" || continue
        cp -a "$clean" "$f" && fixmsg "已替换原配置：$f"
        apply_network_config_after_replace "$NET_MGR" "$f"
    done
}

create_clean_config_copy(){
    local f="$1" dir base clean
    dir="$(dirname "$f")"; base="$(basename "$f")"; clean="${dir}/${base}.only-primary-${RUN_ID}"
    cp -a "$f" "$clean"
    case "$f" in
        *ifcfg-*) sed -i -E '/^(IPADDR[0-9]+|PREFIX[0-9]+|NETMASK[0-9]+|GATEWAY[0-9]+|IPADDR_SECONDARIES|IPV6ADDR_SECONDARIES)=/d' "$clean";;
        *.nmconnection) sed -i -E '/^address[2-9][0-9]*=/d' "$clean"; awk 'BEGIN{FS=OFS="="} /^address1=/{split($2,a,";"); $2=a[1]} {print}' "$clean" > "$clean.tmp" && mv "$clean.tmp" "$clean"; chmod 600 "$clean" 2>/dev/null || true;;
        *.yaml) awk -v iface="$PRIMARY_IF" -v primary="$PRIMARY_IP_CIDR" 'function indent_len(s){match(s,/[^ ]/);return RSTART-1} BEGIN{in_iface=0;iface_indent=-1;in_addr_block=0;addr_indent=-1} {line=$0; if(line ~ "^[[:space:]]*" iface ":[[:space:]]*$"){in_iface=1;iface_indent=indent_len(line);print line;next} if(in_iface && indent_len(line)<=iface_indent && line !~ "^[[:space:]]*$"){in_iface=0;in_addr_block=0} if(in_iface && indent_len(line)==iface_indent+2 && line ~ /^[[:space:]]*addresses:[[:space:]]*\[/){sub(/addresses:[[:space:]]*\[[^]]*\]/,"addresses: [" primary "]",line);print line;next} if(in_iface && indent_len(line)==iface_indent+2 && line ~ /^[[:space:]]*addresses:[[:space:]]*$/){print line;addr_indent=indent_len(line);print substr(line,1,addr_indent) "  - " primary;in_addr_block=1;next} if(in_addr_block){if(line ~ /^[[:space:]]*-[[:space:]]*[0-9a-fA-F:.]+\//)next; if(indent_len(line)<=addr_indent && line !~ /^[[:space:]]*$/)in_addr_block=0; else if(line ~ /^[[:space:]]*-/)next} print line}' "$clean" > "$clean.tmp" && mv "$clean.tmp" "$clean";;
        *interfaces*) sed -i -E "/ip addr add .* dev ${PRIMARY_IF}/d" "$clean";;
        *.network) awk 'BEGIN{seen=0} /^Address=/{seen++; if(seen>1) next} {print}' "$clean" > "$clean.tmp" && mv "$clean.tmp" "$clean";;
    esac
    echo "$clean"
}

# ------------------------- 菜单 -------------------------
menu(){
    while true; do
        echo ""
        echo "=================================================="
        echo " OPS Linux 系统诊断与修复工具 v7.4"
        echo " 配置变更备份目录：$BACKUP_DIR"
        echo "=================================================="
        echo "1) 一键检测并自动处理常见问题"
        echo "2) 查询系统与网络基础信息"
        echo "3) SSH 服务检查与自动处理"
        echo "4) SSH 登录策略检查与开关项"
        echo "5) 防火墙端口检查与自动处理"
        echo "6) SELinux / Ping 检查与自动处理"
        echo "7) OpenSSL/sshd 依赖库检查与修复"
        echo "8) 检查 netplan 配置"
        echo "9) 检查 CC/高连接 IP"
        echo "10) 检查机器对外开放端口"
        echo "11) SSH 服务扩展项"
        echo "12) 防火墙端口扩展项"
        echo "13) SELinux / Ping 扩展项"
        echo "14) 高危修复：netplan 网络配置"
        echo "15) 高危修复：默认路由"
        echo "16) 高危操作：CC连接限制 / 封禁高连接 IP"
        echo "17) 备份主网卡配置并生成仅保留主IP配置"
        echo "0) 退出"
        echo "=================================================="
        read -r -p "请选择：" choice
        init_detect
        case "$choice" in
            1) auto_check_and_fix;;
            2) check_system_network;;
            3) check_ssh_service;;
            4) fix_ssh_policy_menu;;
            5) check_firewall;;
            6) check_security;;
            7) check_openssl_sshd_internal; fix_openssl_sshd_menu;;
            8) check_netplan;;
            9) read -r -p "连接数阈值，默认100：" t; check_cc_attack "${t:-100}";;
            10) check_open_ports;;
            11) fix_ssh_service_menu;;
            12) fix_firewall_port_menu;;
            13) fix_security_menu;;
            14) fix_netplan_menu;;
            15) fix_default_route_menu;;
            16) fix_cc_block_menu;;
            17) backup_clean_primary_config;;
            0) exit 0;;
            *) warn "无效选项";;
        esac
    done
}

need_root
init_detect
menu

exit 0
__OPS_PINYIN_PAYLOAD__
#!/usr/bin/env bash
# ==========================================================
# OPS Linux Doctor v7
# yongtu:Linux xitong cha xun / wen ti jian ce / zi dong xiu fu / gao wei kuozhan xiang
# fu gai:CentOS 7,CentOS Stream 9,Rocky,AlmaLinux,Ubuntu 18-24,Debian 11-13
# ==========================================================

set -o pipefail

SCRIPT_NAME="ops-linux-doctor"
RUN_ID="$(date +%F_%H%M%S)"
REPORT="/dev/null"
BACKUP_DIR="/root/${SCRIPT_NAME}-backup-${RUN_ID}"
AUTO_FIX_LOW_RISK=0
CC_THRESHOLD_DEFAULT=100

RED="\033[31m"; GREEN="\033[32m"; YELLOW="\033[33m"; BLUE="\033[34m"; NC="\033[0m"
OS_ID="unknown"; OS_VERSION="unknown"; OS_FAMILY="unknown"
SSH_SERVICE="unknown"; SSH_CONFIG="/etc/ssh/sshd_config"; SSH_PORTS="22"; FIREWALL_TYPES="none"
PRIMARY_IF=""; PRIMARY_IP_CIDR=""; PRIMARY_GW=""

mkdir -p "$BACKUP_DIR" 2>/dev/null

# ------------------------- shu chu han shu -------------------------
log_detail(){ [ "$REPORT" = "/dev/null" ] || echo -e "$*" >> "$REPORT"; }
run_detail(){ log_detail "[zhi xing] $*"; "$@" >> "$REPORT" 2>&1; }
info(){ echo -e "${BLUE}[cha xun]${NC} $*"; }
ok(){ echo -e "${GREEN}[zheng chang]${NC} $*"; }
warn(){ echo -e "${YELLOW}[yi chang]${NC} $*"; }
fixmsg(){ echo -e "${GREEN}[xiu fu]${NC} $*"; }
err(){ echo -e "${RED}[cuo wu]${NC} $*"; }
plan(){ echo -e "${YELLOW}[fangan]${NC} $*"; }
section(){ echo ""; echo -e "${BLUE}==== $* ====${NC}"; }

need_root(){ if [ "$(id -u)" -ne 0 ]; then echo -e "${RED} qing shi yong root yonghu zhi xing, huo sudo bash $0${NC}"; exit 1; fi; }
confirm(){
    local msg="$1" ans
    echo -e "${YELLOW}${msg}${NC}"
    read -r -p "qing shu ru y queren zhi xing (qi ta shu ru qu xiao):" ans
    [ "$ans" = "y" ] || [ "$ans" = "Y" ]
}
backup_file(){ local f="$1"; [ -f "$f" ] || return 0; mkdir -p "$BACKUP_DIR"; cp -a "$f" "$BACKUP_DIR/$(echo "$f" | sed 's#/#_#g').bak" && log_detail "[bei fen] $f -> $BACKUP_DIR"; }
backup_dir(){ local d="$1"; [ -d "$d" ] || return 0; mkdir -p "$BACKUP_DIR"; cp -a "$d" "$BACKUP_DIR/$(basename "$d").bak" && log_detail "[bei fen mulu] $d -> $BACKUP_DIR"; }

# ------------------------- xitong shi bie -------------------------
detect_os(){
    if [ -f /etc/os-release ]; then . /etc/os-release; OS_ID="${ID:-unknown}"; OS_VERSION="${VERSION_ID:-unknown}"; fi
    case "$OS_ID" in centos|rhel|rocky|almalinux|fedora) OS_FAMILY="rhel";; ubuntu|debian) OS_FAMILY="debian";; *) OS_FAMILY="unknown";; esac
}

detect_ssh_service(){
    if systemctl list-unit-files 2>/dev/null | grep -q '^sshd.service'; then SSH_SERVICE="sshd";
    elif systemctl list-unit-files 2>/dev/null | grep -q '^ssh.service'; then SSH_SERVICE="ssh";
    elif systemctl status sshd >/dev/null 2>&1; then SSH_SERVICE="sshd";
    elif systemctl status ssh >/dev/null 2>&1; then SSH_SERVICE="ssh";
    else [ "$OS_FAMILY" = "debian" ] && SSH_SERVICE="ssh" || SSH_SERVICE="sshd"; fi
}

get_primary_network(){
    PRIMARY_GW="$(ip route show default 2>/dev/null | awk 'NR==1{print $3}')"
    PRIMARY_IF="$(ip route show default 2>/dev/null | awk 'NR==1{print $5}')"
    if [ -n "$PRIMARY_IF" ]; then PRIMARY_IP_CIDR="$(ip -o -4 addr show dev "$PRIMARY_IF" scope global 2>/dev/null | awk 'NR==1{print $4}')"; fi
    if [ -z "$PRIMARY_IF" ]; then PRIMARY_IF="$(ip -o -4 addr show scope global 2>/dev/null | awk 'NR==1{print $2}')"; fi
    if [ -n "$PRIMARY_IF" ] && [ -z "$PRIMARY_IP_CIDR" ]; then PRIMARY_IP_CIDR="$(ip -o -4 addr show dev "$PRIMARY_IF" scope global 2>/dev/null | awk 'NR==1{print $4}')"; fi
}

get_ssh_ports(){
    local ports="" cfg_ports="" listen_ports=""
    if command -v ss >/dev/null 2>&1; then
        listen_ports="$(ss -lntp 2>/dev/null | grep -E 'sshd|dropbear|:22 ' | awk '{print $4}' | sed -E 's/.*:([0-9]+)$/\1/' | grep -E '^[0-9]+$' | sort -n | uniq | xargs)"
    fi
    [ -f "$SSH_CONFIG" ] && cfg_ports="$(grep -Ei '^[[:space:]]*Port[[:space:]]+' "$SSH_CONFIG" 2>/dev/null | awk '{print $2}' | sort -n | uniq | xargs)"
    if [ -d /etc/ssh/sshd_config.d ]; then
        cfg_ports="$cfg_ports $(grep -RhiE '^[[:space:]]*Port[[:space:]]+' /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk '{print $2}' | sort -n | uniq | xargs)"
    fi
    ports="$(echo "$listen_ports $cfg_ports" | xargs -n1 2>/dev/null | grep -E '^[0-9]+$' | sort -n | uniq | xargs)"
    [ -z "$ports" ] && ports="22"
    SSH_PORTS="$ports"
}

detect_firewalls(){
    # zhi shi bie "zheng zai qi yongqie ke neng chan sheng lan jie" de xitong fang huoqiang.
    # shuo ming:iptables/nftables ming ling cun zai bu dai biao fang huoqiang qi yong;Ubuntu shang ufw fu wu/iptables-nft cun zai ye bu dai biao duankou xu yao fangxing.
    FIREWALL_TYPES=""

    if systemctl is-active firewalld >/dev/null 2>&1; then
        FIREWALL_TYPES="$FIREWALL_TYPES firewalld"
    fi

    if command -v ufw >/dev/null 2>&1; then
        if ufw status 2>/dev/null | grep -qi '^Status:[[:space:]]*active'; then
            FIREWALL_TYPES="$FIREWALL_TYPES ufw"
        fi
    fi

    if command -v iptables >/dev/null 2>&1 && iptables -S INPUT >/dev/null 2>&1; then
        local ipt_policy ipt_rules
        ipt_policy="$(iptables -S INPUT 2>/dev/null | awk 'NR==1{print $3}')"
        ipt_rules="$(iptables -S INPUT 2>/dev/null)"
        # zhi you INPUT mo ren DROP/REJECT, huo cun zai ming que DROP/REJECT gui ze shi, cai ren wei iptables shixian zhi xing fang huoqiang.
        if [ "$ipt_policy" = "DROP" ] || [ "$ipt_policy" = "REJECT" ] || echo "$ipt_rules" | grep -qE ' -j (DROP|REJECT)( |$)'; then
            FIREWALL_TYPES="$FIREWALL_TYPES iptables"
        fi
    fi

    if command -v nft >/dev/null 2>&1 && nft list ruleset >/dev/null 2>&1; then
        local nft_rules
        nft_rules="$(nft list ruleset 2>/dev/null)"
        # zhi you cun zai input hook qie policy drop, huo cun zai ming que drop/reject dong zuo shi, cai ren wei nftables ke neng lan jie.
        if echo "$nft_rules" | grep -qiE 'hook[[:space:]]+input' && echo "$nft_rules" | grep -qiE 'policy[[:space:]]+drop|[^a-z](drop|reject)([^a-z]|$)'; then
            FIREWALL_TYPES="$FIREWALL_TYPES nftables"
        fi
    fi

    FIREWALL_TYPES="$(echo "$FIREWALL_TYPES" | xargs)"
    [ -z "$FIREWALL_TYPES" ] && FIREWALL_TYPES="none"
}
init_detect(){ detect_os; detect_ssh_service; get_primary_network; get_ssh_ports; detect_firewalls; }

# ------------------------- zi dong xiu fu gong ju -------------------------
restart_ssh_safe(){
    detect_ssh_service
    if command -v sshd >/dev/null 2>&1; then
        if ! sshd -t >> "$REPORT" 2>&1; then err "SSH pei zhiyu fa yi chang, ju jue zhong qi SSH, bi mian duanlian"; return 1; fi
    fi
    if systemctl restart "$SSH_SERVICE" >> "$REPORT" 2>&1; then fixmsg "SSH fu wu yi zhong qi"; else err "SSH fu wu zhong qi shi bai"; return 1; fi
}

start_enable_ssh(){
    detect_ssh_service
    if ! systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then
        if systemctl start "$SSH_SERVICE" >> "$REPORT" 2>&1; then fixmsg "SSH fu wu weiyun xing, yi qi dong $SSH_SERVICE"; else err "SSH fu wu qi dong shi bai"; return 1; fi
    fi
    if ! systemctl is-enabled "$SSH_SERVICE" >/dev/null 2>&1; then
        systemctl enable "$SSH_SERVICE" >> "$REPORT" 2>&1 && fixmsg "SSH weikai jizi qi, yi she zhi enable"
    fi
}

save_iptables(){
    if command -v service >/dev/null 2>&1 && service iptables save >> "$REPORT" 2>&1; then fixmsg "iptables gui ze yi bao cun";
    elif command -v netfilter-persistent >/dev/null 2>&1; then netfilter-persistent save >> "$REPORT" 2>&1 && fixmsg "iptables gui ze yi bao cun";
    elif [ -d /etc/sysconfig ]; then iptables-save > /etc/sysconfig/iptables 2>>"$REPORT" && fixmsg "iptables gui ze yi bao cun dao /etc/sysconfig/iptables";
    else plan "iptables gui ze yi lin shi sheng xiao, weiqueren chi jiu hua fangshi"; fi
}

allow_ssh_port_auto(){
    get_ssh_ports; detect_firewalls
    local p changed=0

    if [ "$FIREWALL_TYPES" = "none" ]; then
        ok "weijian ce dao qi yongde xian zhi xing xitong fang huoqiang,SSH duankou wu xu fangxing"
        return 0
    fi

    for p in $SSH_PORTS; do
        if echo "$FIREWALL_TYPES" | grep -qw firewalld; then
            if ! firewall-cmd --query-port=${p}/tcp >/dev/null 2>&1 && ! { [ "$p" = "22" ] && firewall-cmd --query-service=ssh >/dev/null 2>&1; }; then
                firewall-cmd --permanent --add-port=${p}/tcp >> "$REPORT" 2>&1
                firewall-cmd --add-port=${p}/tcp >> "$REPORT" 2>&1
                firewall-cmd --reload >> "$REPORT" 2>&1
                fixmsg "firewalld weifangxing $p/tcp, yi fangxing"
                changed=1
            fi
        fi

        if echo "$FIREWALL_TYPES" | grep -qw ufw; then
            if ! ufw status 2>/dev/null | grep -qE "(^|[[:space:]])${p}(/tcp)?([[:space:]]|$)"; then
                ufw allow ${p}/tcp >> "$REPORT" 2>&1 && fixmsg "ufw weifangxing $p/tcp, yi fangxing"
                changed=1
            fi
        fi

        if echo "$FIREWALL_TYPES" | grep -qw iptables; then
            local policy
            policy="$(iptables -S INPUT 2>/dev/null | awk 'NR==1{print $3}')"
            if [ "$policy" = "DROP" ] || [ "$policy" = "REJECT" ] || iptables -S INPUT 2>/dev/null | grep -qE ' -j (DROP|REJECT)( |$)'; then
                if ! iptables -C INPUT -p tcp --dport "$p" -j ACCEPT >/dev/null 2>&1; then
                    iptables -I INPUT -p tcp --dport "$p" -j ACCEPT >> "$REPORT" 2>&1
                    fixmsg "iptables cun zai xian zhi ce lue qie weifangxing $p/tcp, yi cha ru ACCEPT"
                    save_iptables
                    changed=1
                fi
            fi
        fi

        if echo "$FIREWALL_TYPES" | grep -qw nftables; then
            add_nft_allow_port "$p" && changed=1 || plan "nftables yi qi yongdan weineng an quan shi bie input lian, qing ren gong queren hou fangxing $p/tcp"
        fi
    done
    [ "$changed" = "0" ] && ok "dang qian SSH duankou fang huoqiang fangxing gui ze weifa xian ming xianyi chang"
}
enable_ping_temp(){
    if [ -f /proc/sys/net/ipv4/icmp_echo_ignore_all ] && [ "$(cat /proc/sys/net/ipv4/icmp_echo_ignore_all)" = "1" ]; then
        sysctl -w net.ipv4.icmp_echo_ignore_all=0 >> "$REPORT" 2>&1 && fixmsg "xitong yi jin ping, yi lin shi kai qi ping"
    fi
}

selinux_temp_permissive(){
    if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce 2>/dev/null)" = "Enforcing" ]; then
        if command -v setenforce >/dev/null 2>&1; then setenforce 0 >> "$REPORT" 2>&1 && fixmsg "SELinux wei Enforcing, yi lin shi qie huan wei Permissive"; fi
    fi
}

fix_local_listen_auto(){
    # jin zhushi zhu pei zhizhong de ListenAddress 127.0.0.1/::1; qi ta fu za ListenAddress bu zi dong gai
    [ -f "$SSH_CONFIG" ] || return 0
    if grep -qE '^[[:space:]]*ListenAddress[[:space:]]+(127\.0\.0\.1|::1)' "$SSH_CONFIG"; then
        backup_file "$SSH_CONFIG"
        sed -i -E 's/^[[:space:]]*ListenAddress[[:space:]]+(127\.0\.0\.1|::1)/#ListenAddress \1/g' "$SSH_CONFIG"
        fixmsg "SSH zhi jian ting bende de zhi, yi zhushi bende ListenAddress xian zhi"
        restart_ssh_safe
    fi
}

find_gateway_from_configs(){
    local gw=""
    gw="$(grep -RhsE '^(GATEWAY=|gateway[[:space:]]+|via:[[:space:]]*)' /etc/sysconfig/network-scripts/ifcfg-* /etc/network/interfaces /etc/netplan/*.yaml 2>/dev/null | head -1 | sed -E 's/.*(GATEWAY=|gateway[[:space:]]+|via:[[:space:]]*)([0-9.]+).*/\2/')"
    [ -n "$gw" ] && echo "$gw"
}

fix_default_route_auto(){
    get_primary_network
    if ip route show default 2>/dev/null | grep -q '^default'; then return 0; fi
    local gw dev
    gw="$(find_gateway_from_configs)"
    dev="$PRIMARY_IF"
    [ -z "$dev" ] && dev="$(ip -o -4 addr show scope global 2>/dev/null | awk 'NR==1{print $2}')"
    if [ -n "$gw" ] && [ -n "$dev" ]; then
        if ip route replace default via "$gw" dev "$dev" >> "$REPORT" 2>&1; then fixmsg "mo ren lu you queshi, yi genjupei zhitianjia default via $gw dev $dev"; else err "mo ren lu you zi dong tianjia shi bai"; fi
    else
        warn "mo ren lu you queshi, qie wu fa cong pei zhizhong shi bie wang guan"
        plan "jin ru [mo ren lu you xiu fu] cai dan, shou dong shu ru wang guan hou tianjia"
    fi
}

openssl_sshd_issue_detected(){
    detect_ssh_service
    local tmp="/tmp/${SCRIPT_NAME}-sshlib-${RUN_ID}.txt"
    : > "$tmp"
    systemctl status "$SSH_SERVICE" --no-pager >> "$tmp" 2>&1
    journalctl -u "$SSH_SERVICE" --no-pager -n 200 >> "$tmp" 2>&1
    [ -f /var/log/secure ] && tail -n 300 /var/log/secure >> "$tmp"
    [ -f /var/log/auth.log ] && tail -n 300 /var/log/auth.log >> "$tmp"
    cat "$tmp" >> "$REPORT"
    grep -qiE 'OpenSSL|libcrypto|libssl|version mismatch|symbol lookup error|undefined symbol|library not found|error while loading shared libraries' "$tmp"
}

fix_openssl_sshd_auto(){
    if systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then return 0; fi
    if ! openssl_sshd_issue_detected; then return 0; fi
    warn "SSH fu wu qi dong shi bai, jian ce dao OpenSSL/libcrypto/libssl yi lai ku yi chang tezheng"
    if [ "$AUTO_FIX_LOW_RISK" = "1" ]; then
        # bao ji xiu fu feng xian jiao gao, zi dong mo shi xia zhi zai ming queming zhong shi zhi xing; reng shu chu shuo ming
        fixmsg "ming queming zhong SSH yi lai ku yi chang, kai shi zhi xing xitong dui ying xiu fu"
        detect_os
        if [ "$OS_FAMILY" = "rhel" ]; then
            if command -v dnf >/dev/null 2>&1; then dnf distro-sync -y >> "$REPORT" 2>&1; else yum update -y openssh-server openssl openssl-libs >> "$REPORT" 2>&1; fi
            ldconfig >> "$REPORT" 2>&1
            systemctl restart "$SSH_SERVICE" >> "$REPORT" 2>&1 && fixmsg "OpenSSL/sshd yi lai ku xiu fu hou yi zhong qi SSH"
        elif [ "$OS_FAMILY" = "debian" ]; then
            apt update >> "$REPORT" 2>&1
            apt install --reinstall -y openssh-server openssl >> "$REPORT" 2>&1
            ldconfig >> "$REPORT" 2>&1
            systemctl restart "$SSH_SERVICE" >> "$REPORT" 2>&1 && fixmsg "OpenSSL/sshd yi lai ku xiu fu hou yi zhong qi SSH"
        fi
    else
        plan "jin ru [OpenSSL/sshd yi lai ku yi chang xiu fu] cai dan zhi xing xiu fu"
    fi
}

# ------------------------- jian cha mo kuai: zhong duanzhi chu jie guo, xiang xi xie ri zhi -------------------------
check_system_network(){
    section "1. xitong yu wang luo ji chu jian cha"
    detect_os; get_primary_network
    info "xitong:$OS_ID $OS_VERSION; nei he:$(uname -r); zhu jiming:$(hostname)"
    info "zhu wang ka:${PRIMARY_IF:-weishi bie}; zhuIP:${PRIMARY_IP_CIDR:-weishi bie}; mo ren wang guan:${PRIMARY_GW:-weishi bie}"
    log_detail "---- ip -br addr ----"; ip -br addr >> "$REPORT" 2>&1
    log_detail "---- ip route ----"; ip route >> "$REPORT" 2>&1
    log_detail "---- resolv.conf ----"; cat /etc/resolv.conf >> "$REPORT" 2>&1
    log_detail "---- ip -br link ----"; ip -br link >> "$REPORT" 2>&1

    if [ -z "$PRIMARY_IP_CIDR" ]; then warn "IP de zhi: weishi bie dao zhu IP"; plan "jian cha wang ka pei zhihuo yun ping tai wang ka bang ding"; else ok "IP de zhi: yi shi bie zhu IP $PRIMARY_IP_CIDR"; fi
    if [ -z "$PRIMARY_GW" ]; then warn "mo ren lu you: queshi"; plan "ke zi dong cong pei zhiwen jian shi bie wang guan hou tianjia; wu fa shi bie shi xu shou dong shu ru wang guan"; [ "$AUTO_FIX_LOW_RISK" = "1" ] && fix_default_route_auto; else ok "mo ren lu you:via $PRIMARY_GW dev $PRIMARY_IF"; fi
    if [ -n "$PRIMARY_GW" ] && ! ping -c 1 -W 2 "$PRIMARY_GW" >/dev/null 2>&1; then warn "wang guan lian tong xing:$PRIMARY_GW bu ke da"; plan "jian cha IP/yanma/wang guan,VLAN, shang lian jiao huan jihuo an quan zu"; else [ -n "$PRIMARY_GW" ] && ok "wang guan lian tong xing: zheng chang"; fi
    if grep -q '^nameserver' /etc/resolv.conf 2>/dev/null; then ok "DNS: yi pei zhi $(awk '/^nameserver/{print $2}' /etc/resolv.conf | xargs)"; else warn "DNS: weipei zhi nameserver"; plan "xie ru ke hu zhi ding DNS, huo lin shi pei zhi 1.1.1.1 / 8.8.8.8"; fi
}

check_ssh_service(){
    section "2. SSH fu wu yu jian ting jian cha / zi dong chu li"
    detect_ssh_service; get_ssh_ports
    info "SSH fu wu ming:$SSH_SERVICE; shi bie duankou:$SSH_PORTS"
    if ! systemctl list-unit-files 2>/dev/null | grep -qE "^${SSH_SERVICE}\.service" && ! systemctl status "$SSH_SERVICE" >/dev/null 2>&1; then
        warn "SSH fu wu: weijian ce dao $SSH_SERVICE fu wu"
        plan "ke jin ru [SSH fu wu kuozhan xiang] an zhuang openssh-server"
        return
    fi
    if systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then ok "SSH fu wu: yun xing zhong"; else warn "SSH fu wu: weiyun xing huo qi dong shi bai, kai shi zi dong chu li"; start_enable_ssh || true; systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1 || fix_openssl_sshd_auto || true; fi
    if systemctl is-enabled "$SSH_SERVICE" >/dev/null 2>&1; then ok "SSH zi qi: yi qi yong"; else warn "SSH zi qi: weiqi yong, kai shi zi dong she zhi"; systemctl enable "$SSH_SERVICE" >/dev/null 2>&1 && fixmsg "yi she zhi SSH kai jizi qi" || err "she zhi SSH kai jizi qi shi bai"; fi
    local missing=0 global=0 localonly=0 p
    get_ssh_ports
    for p in $SSH_PORTS; do
        if ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${p}$"; then
            ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(^0\.0\.0\.0:|^\[::\]:|^\*:|^:::).*${p}$" && global=1
            ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(^127\.0\.0\.1:|^\[::1\]:|^::1:).*${p}$" && localonly=1
        else missing=1; fi
    done
    if [ "$missing" = "1" ]; then warn "SSH jian ting: pei zhiduankou weiquan bu jian ting, chang shi an quan zhong qi SSH"; restart_ssh_safe || true; else ok "SSH jian ting: duankou yi jian ting"; fi
    if [ "$global" = "0" ] && [ "$localonly" = "1" ]; then warn "SSH jian ting de zhi: ke neng zhi jian ting bende de zhi, kai shi zi dong xiu fu"; fix_local_listen_auto || true; else ok "SSH jian ting de zhi: weifa xian jin bende jian ting wen ti"; fi
}

check_ssh_config(){
    section "3. SSH pei zhiyu denglu ce lue jian cha"
    [ -f "$SSH_CONFIG" ] || { warn "SSH pei zhi:$SSH_CONFIG bu cun zai"; plan "an zhuang openssh-server"; return; }
    log_detail "---- ssh config grep ----"; grep -RniE '^[#[:space:]]*(Port|ListenAddress|PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|UsePAM|KbdInteractiveAuthentication|ChallengeResponseAuthentication|AllowUsers|DenyUsers|AllowGroups|DenyGroups)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf >> "$REPORT" 2>&1 || true
    if command -v sshd >/dev/null 2>&1 && sshd -t >> "$REPORT" 2>&1; then ok "sshd_config yu fa: zheng chang"; else warn "sshd_config yu fa: yi chang"; plan "genjuri zhi cuo wu xing xiu fu; yu fa yi chang shi jiao benbu hui zhong qi SSH"; fi
    local pass root pam
    pass="$(sshd -T 2>/dev/null | awk '/^passwordauthentication /{print $2}')"
    root="$(sshd -T 2>/dev/null | awk '/^permitrootlogin /{print $2}')"
    pam="$(sshd -T 2>/dev/null | awk '/^usepam /{print $2}')"
    [ "$pass" = "no" ] && { warn "mi ma denglu: yi jin yong"; plan "ru xu mi ma denglu, jin ru [SSH denglu ce lue jian cha yu kai guan xiang] kai qi PasswordAuthentication"; } || ok "mi ma denglu:${pass:-weizhi}"
    case "$root" in no|prohibit-password|forced-commands-only) warn "root denglu:$root, ke neng bei xian zhi"; plan "ru xu root denglu, jin ru [SSH denglu ce lue jian cha yu kai guan xiang] yun xu root denglu";; *) ok "root denglu:${root:-weizhi}";; esac
    [ "$pam" = "no" ] && { warn "UsePAM:no, ke neng ying xiang mi ma ren zheng"; plan "jin ru [SSH denglu ce lue jian cha yu kai guan xiang] qi yong UsePAM"; } || ok "UsePAM:${pam:-weizhi}"
    if [ -f /etc/ssh/sshd_config.d/50-cloud-init.conf ] && grep -qiE '^[[:space:]]*PasswordAuthentication[[:space:]]+no' /etc/ssh/sshd_config.d/50-cloud-init.conf; then warn "cloud-init:50-cloud-init.conf jin yongle mi ma denglu"; plan "jin ru [SSH denglu ce lue jian cha yu kai guan xiang] xiu fu cloud-init fu gai"; else ok "cloud-init: weifa xian jin yongmi ma denglu fu gai"; fi
    if grep -RqiE '^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null; then warn "yonghu fang wen xian zhi: cun zai Allow/Deny yonghu huo zu ce lue"; plan "queren ce lue hou zai tong guo cai dan zhushi xian zhi xiang"; else ok "yonghu fang wen xian zhi: weifa xian Allow/Deny xian zhi"; fi
}

check_firewall(){
    section "4. fang huoqiang yu duankou fangxing jian cha / zi dong chu li"
    get_ssh_ports; detect_firewalls
    info "jian ce dao qi yongde xian zhi xing fang huoqiang:$FIREWALL_TYPES;SSH duankou:$SSH_PORTS"
    if [ "$FIREWALL_TYPES" = "none" ]; then ok "xitong fang huoqiang: weijian ce dao qi yongde xian zhi xing fang huoqiang,SSH duankou wu xu fangxing"; return 0; fi
    local issue=0 p policy
    if echo "$FIREWALL_TYPES" | grep -qw firewalld; then for p in $SSH_PORTS; do firewall-cmd --query-port=${p}/tcp >/dev/null 2>&1 || { [ "$p" = "22" ] && firewall-cmd --query-service=ssh >/dev/null 2>&1; } || { warn "firewalld: weifangxing $p/tcp"; issue=1; }; done; fi
    if echo "$FIREWALL_TYPES" | grep -qw ufw; then for p in $SSH_PORTS; do ufw status 2>/dev/null | grep -qE "(^|[[:space:]])${p}(/tcp)?([[:space:]]|$)" || { warn "ufw: weifangxing $p/tcp"; issue=1; }; done; fi
    if echo "$FIREWALL_TYPES" | grep -qw iptables; then policy="$(iptables -S INPUT 2>/dev/null | awk 'NR==1{print $3}')"; if [ "$policy" = "DROP" ] || [ "$policy" = "REJECT" ] || iptables -S INPUT 2>/dev/null | grep -qE ' -j (DROP|REJECT)( |$)'; then for p in $SSH_PORTS; do iptables -C INPUT -p tcp --dport "$p" -j ACCEPT >/dev/null 2>&1 || { warn "iptables: cun zai xian zhi ce lue, weixianshi fangxing $p/tcp"; issue=1; }; done; fi; fi
    if echo "$FIREWALL_TYPES" | grep -qw nftables; then for p in $SSH_PORTS; do nft list ruleset 2>/dev/null | grep -qE "dport[[:space:]]+$p[[:space:]]+accept" || { warn "nftables: cun zai xian zhi xing gui ze, weiqueren $p/tcp yi fangxing"; issue=1; }; done; fi
    if [ "$issue" = "1" ]; then fixmsg "jian ce dao SSH duankou fang huoqiang fangxing yi chang, kai shi zi dong fangxing dang qian SSH duankou"; allow_ssh_port_auto || true; else ok "fang huoqiang: weifa xian SSH duankou fangxing yi chang"; fi
}

check_security(){
    section "5. SELinux yu ping jian cha / zi dong chu li"
    if command -v getenforce >/dev/null 2>&1; then local se; se="$(getenforce)"; if [ "$se" = "Enforcing" ]; then warn "SELinux:Enforcing, kai shi lin shi qie huan wei Permissive"; selinux_temp_permissive || true; else ok "SELinux:$se"; fi; else ok "SELinux: weijian ce dao SELinux gong ju huo bu shi yong"; fi
    if [ -f /proc/sys/net/ipv4/icmp_echo_ignore_all ]; then local v; v="$(cat /proc/sys/net/ipv4/icmp_echo_ignore_all)"; if [ "$v" = "1" ]; then warn "Ping: xitong yi jin ping, kai shi lin shi kai qi"; enable_ping_temp || true; else ok "Ping: xitong yun xu ping"; fi; fi
}

check_openssl_sshd_internal(){
    section "6. OpenSSL/sshd yi lai ku yi chang pan duan"
    detect_ssh_service
    if systemctl is-active "$SSH_SERVICE" >/dev/null 2>&1; then ok "SSH yi lai ku:SSH fu wu zheng chang, weichu fa yi lai ku xiu fu pan duan"; return; fi
    if openssl_sshd_issue_detected; then warn "SSH yi lai ku: jian ce dao OpenSSL/libcrypto/libssl yi chang tezheng"; [ "$AUTO_FIX_LOW_RISK" = "1" ] && { fixmsg "yi jian jian ce yi chu fa OpenSSL/sshd zi dong xiu fu"; fix_openssl_sshd_auto; } || plan "jin ru [OpenSSL/sshd yi lai ku yi chang xiu fu] zhi xing xitong bao xiu fu"; else ok "SSH yi lai ku: weipi pei dao OpenSSL/libcrypto/libssl yi chang tezheng"; fi
}

check_netplan(){
    section "7. netplan pei zhijian cha"
    if [ ! -d /etc/netplan ]; then ok "netplan: dang qian xitong weishi yong /etc/netplan"; return; fi
    log_detail "---- netplan files ----"; ls -l /etc/netplan >> "$REPORT" 2>&1; sed -n '1,200p' /etc/netplan/*.yaml >> "$REPORT" 2>&1 || true
    if command -v netplan >/dev/null 2>&1; then
        if netplan generate >> "$REPORT" 2>&1; then ok "netplan: yu fa jian cha tong guo"; else warn "netplan:generate shi bai, pei zhike neng yi chang"; plan "jin ru [netplan wang luo pei zhixiu fu] cai dan, xian bei fen hou shi yong netplan try/apply"; fi
    else ok "netplan: mulu cun zai dan netplan ming ling bu cun zai"
    fi
}

check_cc_attack(){
    section "10. CC/gao lian jie IP jian cha"
    local threshold="${1:-$CC_THRESHOLD_DEFAULT}" ports="80|443"
    if ! command -v ss >/dev/null 2>&1; then warn "CC jian cha:ss ming ling bu cun zai"; return; fi
    local top high
    top="$(ss -Hant state established 2>/dev/null | awk -v ports="$ports" '$4 ~ ":("ports")$" {peer=$5; gsub(/^\[/,"",peer); gsub(/\]$/,"",peer); sub(/:[0-9]+$/,"",peer); if(peer!="" && peer!="127.0.0.1" && peer!="::1") c[peer]++} END{for(ip in c) print c[ip], ip}' | sort -nr | head -10)"
    high="$(echo "$top" | awk -v th="$threshold" '$1>=th{print}')"
    log_detail "---- high connection top ----"; echo "$top" >> "$REPORT"
    if [ -n "$high" ]; then warn "gao lian jie IP: fa xian chao guo yu zhi $threshold de lai yuan"; echo "$high" | while read -r cnt ip; do [ -n "$ip" ] && warn "lai yuan $ip lian jie shu $cnt"; done; plan "mo ren bu feng jin, queren bu shi CDN/dai li/jian kong/zhen shi ye wu hou jin ru [CC lian jie xian zhi] feng jin"; else ok "gao lian jie IP: weifa xian chao guo yu zhi $threshold de lai yuan"; fi
}

check_open_ports(){
    section "11. dui wai kai fangduankou jian cha"
    if ! command -v ss >/dev/null 2>&1; then warn "kai fangduankou:ss ming ling bu cun zai"; return; fi
    log_detail "---- TCP listen ----"; ss -lntp >> "$REPORT" 2>&1
    log_detail "---- UDP listen ----"; ss -lnup >> "$REPORT" 2>&1
    local ports
    ports="$(ss -lntp 2>/dev/null | awk 'NR>1 && $4 !~ /^127\.0\.0\.1:/ && $4 !~ /^\[::1\]:/ && $4 !~ /^::1:/ {print $4}' | sed -E 's/.*:([0-9]+)$/\1/' | grep -E '^[0-9]+$' | sort -n | uniq | xargs)"
    info "dui wai jian ting TCP duankou:${ports:-weifa xian}"
    plan "ru fa xian bu ying kai fangde min gan duankou, xian queren jin cheng yongtu, zai guan bi fu wu huo xian zhi lai yuan IP"
}

check_all(){
    AUTO_FIX_LOW_RISK=0
    init_detect
    section "wan zheng jian cha: zhi jian ce bu xiu gaixitong"
    check_system_network
    check_ssh_service
    check_ssh_config
    check_firewall
    check_security
    check_openssl_sshd_internal
    check_netplan
    check_cc_attack "$CC_THRESHOLD_DEFAULT"
    check_open_ports
    section "jian cha wan cheng"
}

auto_check_and_fix(){
    AUTO_FIX_LOW_RISK=1
    init_detect
    section "yi jian jian ce bing zi dong chu li chang jian wen ti (bao han SSH OpenSSL/yi lai yi chang)"
    check_system_network
    check_ssh_service
    check_ssh_config
    check_firewall
    check_security
    check_openssl_sshd_internal
    check_netplan
    check_cc_attack "$CC_THRESHOLD_DEFAULT"
    check_open_ports
    AUTO_FIX_LOW_RISK=0
    section "zi dong jian ce xiu fu wan cheng"
}

# ------------------------- cai dan xiu fu -------------------------
set_sshd_kv(){ local key="$1" val="$2"; if grep -qE "^[#[:space:]]*${key}[[:space:]]+" "$SSH_CONFIG"; then sed -i "s/^[#[:space:]]*${key}[[:space:]].*/${key} ${val}/g" "$SSH_CONFIG"; else echo "${key} ${val}" >> "$SSH_CONFIG"; fi; }

fix_ssh_service_menu(){
    section "SSH fu wu kuozhan xiang"
    detect_ssh_service; detect_os; get_ssh_ports
    echo "dang qian fu wu:$SSH_SERVICE; dang qian shi bie duankou:$SSH_PORTS"
    echo "1) zhong xin zhi xing SSH fu wu jian cha bing zi dong chu li"
    echo "2) qi dong SSH fu wu bing she zhikai jizi qi"
    echo "3) an quan zhong qi SSH (xian sshd -t)"
    echo "4) an zhuang/zhong zhuang openssh-server"
    echo "5) xiu fu SSH zhi jian ting 127.0.0.1/::1"
    echo "6) cha kan SSH shi ji jian ting duankou"
    echo "7) xiu gai SSH duankou (hui bei fen pei zhibing an quan zhong qi)"
    echo "8) zhong xin sheng cheng SSH HostKey (jin shen, ke hu duanhui ti shizhu jizhi wenbian hua)"
    echo "0) fan hui"
    read -r -p "xuan ze:" c
    case "$c" in
        1) check_ssh_service;;
        2) start_enable_ssh;;
        3) restart_ssh_safe;;
        4) confirm "jiang an zhuang/zhong zhuang openssh-server, queren?" || return; if command -v apt >/dev/null 2>&1; then apt update >/dev/null 2>&1; apt install --reinstall -y openssh-server >/dev/null 2>&1; elif command -v dnf >/dev/null 2>&1; then dnf reinstall -y openssh-server >/dev/null 2>&1 || dnf install -y openssh-server >/dev/null 2>&1; else yum reinstall -y openssh-server >/dev/null 2>&1 || yum install -y openssh-server >/dev/null 2>&1; fi; fixmsg "openssh-server an zhuang/zhong zhuang ming ling yi zhi xing"; restart_ssh_safe;;
        5) fix_local_listen_auto;;
        6) ss -lntp 2>/dev/null | grep -E 'sshd|dropbear|:22 ' || true;;
        7) read -r -p "qing shu ru xin de SSH duankou:" newp; echo "$newp" | grep -Eq '^[0-9]+$' || { warn "duankou wu xiao"; return; }; backup_file "$SSH_CONFIG"; if grep -qE '^[#[:space:]]*Port[[:space:]]+' "$SSH_CONFIG"; then sed -i -E "0,/^[#[:space:]]*Port[[:space:]]+[0-9]+/s//Port $newp/" "$SSH_CONFIG"; else echo "Port $newp" >> "$SSH_CONFIG"; fi; SSH_PORTS="$newp"; allow_ssh_port_auto; restart_ssh_safe;;
        8) confirm "zhong xin sheng cheng SSH HostKey hui dao zhi ke hu duanzhu jizhi wenbian hua, queren?" || return; mkdir -p /etc/ssh/old-hostkeys-${RUN_ID}; mv /etc/ssh/ssh_host_* /etc/ssh/old-hostkeys-${RUN_ID}/ 2>/dev/null || true; ssh-keygen -A >/dev/null 2>&1; fixmsg "SSH HostKey yi zhong xin sheng cheng, jiu wen jian bao cun zai /etc/ssh/old-hostkeys-${RUN_ID}"; restart_ssh_safe;;
        0) return;;
        *) warn "wu xiao xuan xiang";;
    esac
}

fix_ssh_policy_menu(){
    section "SSH denglu ce lue jian cha yu kai guan xiang"
    detect_ssh_service
    check_ssh_config
    echo "1) kai qi mi ma denglu"
    echo "2) guan bi mi ma denglu"
    echo "3) yun xu root denglu"
    echo "4) jin zhi root denglu"
    echo "5) qi yong UsePAM"
    echo "6) guan bi UsePAM"
    echo "7) xiu fu cloud-init jin yongmi ma denglu"
    echo "8) zhushi AllowUsers/DenyUsers/AllowGroups/DenyGroups xian zhi"
    echo "9) xiao yan bing zhong qi SSH"
    echo "0) fan hui"
    read -r -p "xuan ze:" c
    [ "$c" = "0" ] && return
    confirm "gai cao zuo hui xiu gai SSH denglu ce lue bing bei fen pei zhi, queren?" || return
    backup_file "$SSH_CONFIG"; backup_dir /etc/ssh/sshd_config.d
    case "$c" in
        1) set_sshd_kv PasswordAuthentication yes; set_sshd_kv KbdInteractiveAuthentication yes; set_sshd_kv ChallengeResponseAuthentication yes; set_sshd_kv UsePAM yes;;
        2) set_sshd_kv PasswordAuthentication no;;
        3) set_sshd_kv PermitRootLogin yes;;
        4) set_sshd_kv PermitRootLogin no;;
        5) set_sshd_kv UsePAM yes;;
        6) set_sshd_kv UsePAM no;;
        7) mkdir -p /etc/ssh/sshd_config.d; if [ -f /etc/ssh/sshd_config.d/50-cloud-init.conf ]; then sed -i 's/^[#[:space:]]*PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config.d/50-cloud-init.conf; else echo 'PasswordAuthentication yes' > /etc/ssh/sshd_config.d/50-cloud-init.conf; fi;;
        8) sed -i -E 's/^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)/#&/g' "$SSH_CONFIG"; grep -RliE '^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)' /etc/ssh/sshd_config.d/*.conf 2>/dev/null | while read -r f; do sed -i -E 's/^[[:space:]]*(AllowUsers|DenyUsers|AllowGroups|DenyGroups)/#&/g' "$f"; done;;
        9) :;;
        *) warn "wu xiao xuan xiang"; return;;
    esac
    restart_ssh_safe
}

fix_firewall_port_menu(){
    section "fang huoqiang duankou kuozhan xiang"
    init_detect
    echo "dang qian SSH duankou:$SSH_PORTS"
    echo "1) zi dong fangxing dang qian SSH duankou"
    echo "2) shou dong shu ru duankou fangxing"
    echo "3) nftables chang shi fangxing"
    echo "0) fan hui"
    read -r -p "xuan ze:" c
    case "$c" in
        1) allow_ssh_port_auto;;
        2) read -r -p "qing shu ru duankou:" ports; confirm "jiang fangxing duankou:$ports/tcp, queren?" || return; SSH_PORTS="$ports"; allow_ssh_port_auto;;
        3) read -r -p "qing shu ru duankou, mo ren dang qian SSH duankou:" ports_in; fix_nftables_port "${ports_in:-$SSH_PORTS}";;
        0) return;;
        *) warn "wu xiao xuan xiang";;
    esac
}

fix_nftables_port(){
    local ports="$1" table chain
    command -v nft >/dev/null 2>&1 || { warn "nft bu cun zai"; return; }
    table="$(nft list ruleset 2>/dev/null | awk '/table inet/{print $3; exit}')"
    chain="$(nft list ruleset 2>/dev/null | awk '/chain /{c=$2} /hook input/{print c; exit}')"
    if [ -z "$table" ] || [ -z "$chain" ]; then warn "wu fa ming queshi bie nftables inet input lian, ju jue zi dong xie ru"; plan "shili:nft add rule inet filter input tcp dport <duankou> accept"; return; fi
    confirm "jiang zhi xing nft add rule inet $table $chain tcp dport { $ports } accept, queren?" || return
    local p; for p in $ports; do nft add rule inet "$table" "$chain" tcp dport "$p" accept >> "$REPORT" 2>&1 && fixmsg "nftables yi fangxing $p/tcp"; done
    nft list ruleset > /etc/nftables.conf 2>>"$REPORT" && fixmsg "yi chang shi bao cun dao /etc/nftables.conf"
}

set_sysctl_conf(){ local k="$1" v="$2"; grep -q "^${k}" /etc/sysctl.conf 2>/dev/null && sed -i "s/^${k}.*/${k} = ${v}/" /etc/sysctl.conf || echo "${k} = ${v}" >> /etc/sysctl.conf; }

fix_security_menu(){
    section "SELinux / Ping kuozhan xiang"
    echo "1) lin shi guan bi SELinux (qie huan wei Permissive)"
    echo "2) yongjiu guan bi SELinux"
    echo "3) jiang SELinux she zhiwei Permissive"
    echo "4) wei dang qian SSH duankou tianjia ssh_port_t"
    echo "5) lin shi kai qi ping"
    echo "6) yongjiu kai qi ping"
    echo "7) lin shi guan bi ping"
    echo "8) yongjiu guan bi ping"
    echo "0) fan hui"
    read -r -p "xuan ze:" c
    case "$c" in
        1) selinux_temp_permissive;;
        2) confirm "yongjiu guan bi SELinux xu yao zhong qi hou wan quan sheng xiao, queren?" || return; backup_file /etc/selinux/config; sed -i 's/^SELINUX=.*/SELINUX=disabled/g' /etc/selinux/config && fixmsg "SELinux yongjiu pei zhiyi gaiwei disabled";;
        3) backup_file /etc/selinux/config; sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config; command -v setenforce >/dev/null 2>&1 && setenforce 0; fixmsg "SELinux yi she zhiwei permissive";;
        4) get_ssh_ports; local p; for p in $SSH_PORTS; do if command -v semanage >/dev/null 2>&1; then semanage port -l | grep -q "ssh_port_t.*\b$p\b" || semanage port -a -t ssh_port_t -p tcp "$p" >> "$REPORT" 2>&1 || semanage port -m -t ssh_port_t -p tcp "$p" >> "$REPORT" 2>&1; fixmsg "yi jian cha/tianjia ssh_port_t $p/tcp"; else warn "semanage bu cun zai, qing an zhuang policycoreutils-python-utils/policycoreutils-python"; fi; done;;
        5) sysctl -w net.ipv4.icmp_echo_ignore_all=0 >> "$REPORT" 2>&1 && fixmsg "yi lin shi kai qi ping";;
        6) backup_file /etc/sysctl.conf; set_sysctl_conf net.ipv4.icmp_echo_ignore_all 0; sysctl -p >> "$REPORT" 2>&1; fixmsg "yi yongjiu kai qi ping";;
        7) sysctl -w net.ipv4.icmp_echo_ignore_all=1 >> "$REPORT" 2>&1 && fixmsg "yi lin shi guan bi ping";;
        8) backup_file /etc/sysctl.conf; set_sysctl_conf net.ipv4.icmp_echo_ignore_all 1; sysctl -p >> "$REPORT" 2>&1; fixmsg "yi yongjiu guan bi ping";;
        0) return;;
        *) warn "wu xiao xuan xiang";;
    esac
}

fix_netplan_menu(){
    section "gao wei xiu fu:netplan wang luo pei zhi"
    [ -d /etc/netplan ] || { warn "/etc/netplan bu cun zai"; return; }
    echo "1) jian cha netplan yu fa (generate)"
    echo "2) zhi xing netplan try"
    echo "3) zhi xing netplan apply"
    echo "4) bei fen netplan pei zhi"
    echo "5) genjudang qian IP/GW/DNS sheng cheng netplan cao an"
    echo "0) fan hui"
    read -r -p "xuan ze:" c
    case "$c" in
        1) netplan generate >> "$REPORT" 2>&1 && ok "netplan generate tong guo" || warn "netplan generate shi bai";;
        2) confirm "netplan try ke neng ying xiang wang luo, queren?" || return; netplan try;;
        3) confirm "netplan apply ke neng dao zhi duanwang, queren?" || return; backup_dir /etc/netplan; netplan apply >> "$REPORT" 2>&1 && fixmsg "netplan apply yi zhi xing";;
        4) backup_dir /etc/netplan; fixmsg "netplan yi bei fen";;
        5) get_primary_network; if [ -z "$PRIMARY_IF" ] || [ -z "$PRIMARY_IP_CIDR" ] || [ -z "$PRIMARY_GW" ]; then warn "queshao zhu wang ka/IP/wang guan, wu fa sheng cheng"; return; fi; backup_dir /etc/netplan; cat > "$BACKUP_DIR/01-${PRIMARY_IF}-generated.yaml" <<EON
network:
  version: 2
  renderer: networkd
  ethernets:
    ${PRIMARY_IF}:
      addresses:
        - ${PRIMARY_IP_CIDR}
      routes:
        - to: default
          via: ${PRIMARY_GW}
      nameservers:
        addresses: [1.1.1.1, 8.8.8.8]
EON
        fixmsg "yi sheng cheng netplan cao an:$BACKUP_DIR/01-${PRIMARY_IF}-generated.yaml";;
        0) return;;
        *) warn "wu xiao xuan xiang";;
    esac
}

detect_network_config_method(){
    # xian shi bie xitong, zai jie he shi ji qi yongde wang luo guan li qi yu pei zhiwen jian pan duan.
    # bi mian Ubuntu/Debian yin can liu ifcfg wen jian er bei wu pan wei CentOS pei zhifangshi.
    NET_MGR="unknown"; NET_CFG=""; NET_CON=""
    detect_os
    get_primary_network

    case "$OS_ID" in
        ubuntu)
            if ls /etc/netplan/*.yaml >/dev/null 2>&1; then
                NET_MGR="netplan"
                NET_CFG="$(grep -Rsl "$PRIMARY_IF" /etc/netplan/*.yaml 2>/dev/null | head -1)"
                [ -z "$NET_CFG" ] && NET_CFG="$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)"
                return
            fi
            if command -v nmcli >/dev/null 2>&1 && systemctl is-active NetworkManager >/dev/null 2>&1; then
                NET_CON="$(nmcli -t -f NAME,DEVICE con show --active 2>/dev/null | awk -F: -v dev="$PRIMARY_IF" '$2==dev{print $1; exit}')"
                [ -n "$NET_CON" ] && { NET_MGR="NetworkManager"; NET_CFG="/etc/NetworkManager/system-connections/*.nmconnection"; return; }
            fi
            ;;
        debian)
            if ls /etc/netplan/*.yaml >/dev/null 2>&1; then
                NET_MGR="netplan"
                NET_CFG="$(grep -Rsl "$PRIMARY_IF" /etc/netplan/*.yaml 2>/dev/null | head -1)"
                [ -z "$NET_CFG" ] && NET_CFG="$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)"
                return
            fi
            if grep -RqsE "^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+$PRIMARY_IF([[:space:]]|$)" /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null; then
                NET_MGR="interfaces"
                NET_CFG="$(grep -RslE "^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+$PRIMARY_IF([[:space:]]|$)" /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null | head -1)"
                [ -z "$NET_CFG" ] && NET_CFG="/etc/network/interfaces"
                return
            fi
            ;;
        centos|rhel)
            if [[ "$OS_VERSION" == 7* ]] && [ -f "/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}" ]; then
                NET_MGR="ifcfg"; NET_CFG="/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}"; return
            fi
            ;;
    esac

    # Rocky / AlmaLinux / CentOS Stream 8/9 ji qi ta shi ji shi yong NetworkManager de xitong.
    if command -v nmcli >/dev/null 2>&1 && systemctl is-active NetworkManager >/dev/null 2>&1; then
        NET_CON="$(nmcli -t -f NAME,DEVICE con show --active 2>/dev/null | awk -F: -v dev="$PRIMARY_IF" '$2==dev{print $1; exit}')"
        if [ -n "$NET_CON" ]; then
            NET_MGR="NetworkManager"
            NET_CFG="/etc/NetworkManager/system-connections/*.nmconnection"
            return
        fi
    fi

    # RHEL xijian rong/yi liu ifcfg.
    if [ -n "$PRIMARY_IF" ] && [ -f "/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}" ]; then
        NET_MGR="ifcfg"
        NET_CFG="/etc/sysconfig/network-scripts/ifcfg-${PRIMARY_IF}"
        return
    fi

    # systemd-networkd dou di.
    if systemctl is-active systemd-networkd >/dev/null 2>&1 && ls /etc/systemd/network/*.network >/dev/null 2>&1; then
        NET_MGR="systemd-networkd"
        NET_CFG="$(grep -Rsl "$PRIMARY_IF" /etc/systemd/network/*.network 2>/dev/null | head -1)"
        [ -z "$NET_CFG" ] && NET_CFG="$(ls /etc/systemd/network/*.network 2>/dev/null | head -1)"
        return
    fi

    # Debian interfaces zui hou dou di.
    if [ -f /etc/network/interfaces ]; then
        NET_MGR="interfaces"
        NET_CFG="/etc/network/interfaces"
    fi
}

show_route_persistence_notes(){
    section "mo ren lu you yongjiu pei zhishuo ming"
    cat <<'EON'
CentOS 7:
  wang luo guan li fangshi:network-scripts / ifcfg
  pei zhiwen jian:/etc/sysconfig/network-scripts/ifcfg-wang ka ming
  yongjiu sheng xiao pei zhi:GATEWAY=wang guanIP,DEFROUTE=yes
  chang yongsheng xiao fangshi:systemctl restart network

Rocky / AlmaLinux / CentOS Stream 8/9:
  wang luo guan li fangshi:NetworkManager
  pei zhiwei zhi:/etc/NetworkManager/system-connections/*.nmconnection
  tuijian ming ling:nmcli connection modify lian jie ming ipv4.gateway wang guanIP ipv4.never-default no
  chang yongsheng xiao fangshi:nmcli connection up lian jie ming

Ubuntu 18/20/22/24, bu fen Debian:
  wang luo guan li fangshi:netplan
  pei zhiwen jian:/etc/netplan/*.yaml
  yongjiu sheng xiao pei zhi:gateway4: wang guanIP huo routes: [{to: default, via: wang guanIP}]
  chang yongsheng xiao fangshi:netplan generate && netplan try, queren zheng chang hou apply

Debian 11/12/13 ifupdown:
  wang luo guan li fangshi:interfaces
  pei zhiwen jian:/etc/network/interfaces huo /etc/network/interfaces.d/*
  yongjiu sheng xiao pei zhi:gateway wang guanIP
  chang yongsheng xiao fangshi:systemctl restart networking

systemd-networkd:
  wang luo guan li fangshi:systemd-networkd
  pei zhiwen jian:/etc/systemd/network/*.network
  yongjiu sheng xiao pei zhi:[Route] Gateway=wang guanIP
  chang yongsheng xiao fangshi:systemctl restart systemd-networkd
EON
}

list_default_routes(){
    ip route show default 2>/dev/null | nl -w1 -s') '
}

get_default_route_by_number(){
    local n="$1"
    ip route show default 2>/dev/null | sed -n "${n}p"
}

delete_default_route_by_number(){
    section "shan chu yi shi bie de mo ren lu you"
    local count sel route via dev metric cmd
    count="$(ip route show default 2>/dev/null | wc -l | awk '{print $1}')"
    [ "$count" = "0" ] && { ok "dang qian mei you mo ren lu you ke shan chu"; return; }

    info "jian ce dao mo ren lu you:"
    list_default_routes
    read -r -p "qing xuan zeyao shan chu de mo ren lu you bian hao:" sel
    route="$(get_default_route_by_number "$sel")"
    [ -z "$route" ] && { warn "xuan zewu xiao"; return; }

    via="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}')"
    dev="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}')"
    metric="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="metric") print $(i+1)}')"

    cmd="ip route del default"
    [ -n "$via" ] && cmd="$cmd via $via"
    [ -n "$dev" ] && cmd="$cmd dev $dev"
    [ -n "$metric" ] && cmd="$cmd metric $metric"

    echo "[ming ling] $cmd"
    if [ "$count" = "1" ]; then
        confirm "dang qian zhi you 1 tiao mo ren lu you, shan chu hou ke neng dao zhi fu wu qi wu fa fang wen wai wang, queren shan chu?" || return
    else
        confirm "queren shan chu xuan zhong de mo ren lu you?" || return
    fi
    eval "$cmd" >> "$REPORT" 2>&1 && fixmsg "yi shan chu mo ren lu you:$route" || err "mo ren lu you shan chu shi bai"
}

write_default_route_permanent(){
    local gw="$1" mgr="$NET_MGR" cfg="$NET_CFG" con="$NET_CON" cmd
    [ -z "$gw" ] && { warn "wang guan wei kong, wu fa xie ru"; return 1; }
    [ -z "$PRIMARY_IF" ] && { warn "weishi bie zhu wang ka, wu fa xie ru"; return 1; }

    case "$mgr" in
        NetworkManager)
            [ -z "$con" ] && { warn "weishi bie NetworkManager lian jie ming"; return 1; }
            info "xitong wang luo pei zhifangshi:NetworkManager; shi yong:Rocky/AlmaLinux/CentOS Stream 8/9, ye ke neng shi yongyu bu fen Ubuntu/Debian"
            info "pei zhiwei zhi:/etc/NetworkManager/system-connections/*.nmconnection"
            echo "[ming ling] nmcli connection modify \"$con\" ipv4.gateway \"$gw\" ipv4.never-default no"
            echo "[ming ling] nmcli connection up \"$con\""
            confirm "queren jiang mo ren lu you yongjiu xie ru NetworkManager?" || return
            nmcli connection modify "$con" ipv4.gateway "$gw" ipv4.never-default no >> "$REPORT" 2>&1 && \
            nmcli connection up "$con" >> "$REPORT" 2>&1 && fixmsg "mo ren lu you yi yongjiu xie ru NetworkManager:$gw" || err "NetworkManager xie ru shi bai"
            ;;
        ifcfg)
            [ -f "$cfg" ] || { warn "ifcfg pei zhiwen jian bu cun zai:$cfg"; return 1; }
            info "xitong wang luo pei zhifangshi:ifcfg/network-scripts; shi yong:CentOS 7"
            info "pei zhiwen jian:$cfg"
            echo "[ming ling] cp -a $cfg $BACKUP_DIR/"
            echo "[ming ling] xie ru GATEWAY=$gw he DEFROUTE=yes"
            echo "[ming ling] systemctl restart network"
            confirm "queren jiang mo ren lu you yongjiu xie ru ifcfg?" || return
            backup_file "$cfg"
            grep -q '^GATEWAY=' "$cfg" && sed -i "s/^GATEWAY=.*/GATEWAY=$gw/" "$cfg" || echo "GATEWAY=$gw" >> "$cfg"
            grep -q '^DEFROUTE=' "$cfg" && sed -i 's/^DEFROUTE=.*/DEFROUTE=yes/' "$cfg" || echo 'DEFROUTE=yes' >> "$cfg"
            fixmsg "yi xie ru $cfg. qing genjuye wu chuang kou zhi xing systemctl restart network"
            ;;
        netplan)
            [ -f "$cfg" ] || { warn "netplan pei zhiwen jian bu cun zai:$cfg"; return 1; }
            info "xitong wang luo pei zhifangshi:netplan; shi yong:Ubuntu 18/20/22/24, bu fen Debian"
            info "pei zhiwen jian:$cfg"
            echo "[ming ling] cp -a $cfg $BACKUP_DIR/"
            if grep -qE '^[[:space:]]*gateway4:' "$cfg"; then
                echo "[ming ling] sed -i 's/^\\([[:space:]]*gateway4:[[:space:]]*\\).*/\\1$gw/' $cfg"
                echo "[ming ling] netplan generate && netplan try"
                confirm "queren tihuan netplan zhong de gateway4?" || return
                backup_file "$cfg"
                sed -i -E "s/^([[:space:]]*gateway4:[[:space:]]*).*/\\1$gw/" "$cfg"
                netplan generate >> "$REPORT" 2>&1 && fixmsg "yi xie ru netplan gateway4=$gw; jian yi zhi xing netplan try yan zheng" || err "netplan generate shi bai, qing jian cha $cfg"
            else
                local draft="$BACKUP_DIR/netplan-${PRIMARY_IF}-default-route-draft.yaml"
                cat > "$draft" <<EON
# netplan mo ren lu you cao an, shi yong Ubuntu/Debian netplan.
# qing ren gong he bing dao $cfg de $PRIMARY_IF wang ka duan luo zhong, ran hou zhi xing:netplan generate && netplan try
routes:
  - to: default
    via: $gw
EON
                plan "dang qian netplan weifa xian gateway4, yi sheng cheng routes cao an:$draft"
                plan "wei bi mian wu gai YAML ceng ji, jiao benweizhi jie fu gai yuan netplan; qing ren gong he bing hou zhi xing netplan try"
            fi
            ;;
        interfaces)
            [ -f "$cfg" ] || cfg="/etc/network/interfaces"
            [ -f "$cfg" ] || { warn "interfaces pei zhiwen jian bu cun zai"; return 1; }
            info "xitong wang luo pei zhifangshi:interfaces/ifupdown; shi yong:Debian 11/12/13 shi yong /etc/network/interfaces de chang jing"
            info "pei zhiwen jian:$cfg"
            echo "[ming ling] cp -a $cfg $BACKUP_DIR/"
            echo "[ming ling] xiu gaizhu wang ka iface duan luo zhong de gateway $gw"
            echo "[ming ling] systemctl restart networking"
            confirm "queren jiang mo ren lu you yongjiu xie ru interfaces?" || return
            backup_file "$cfg"
            awk -v iface="$PRIMARY_IF" -v gw="$gw" '
                BEGIN{inif=0; wrote=0}
                $0 ~ "^[[:space:]]*iface[[:space:]]+" iface "[[:space:]]+inet" {inif=1; wrote=0; print; next}
                inif && $0 ~ "^[[:space:]]*iface[[:space:]]+" {if(!wrote){print "    gateway " gw}; inif=0}
                inif && $1=="gateway" {if(!wrote){print "    gateway " gw; wrote=1}; next}
                {print}
                END{if(inif && !wrote) print "    gateway " gw}
            ' "$cfg" > "$cfg.tmp" && mv "$cfg.tmp" "$cfg"
            fixmsg "yi xie ru $cfg. qing genjuye wu chuang kou zhi xing systemctl restart networking"
            ;;
        systemd-networkd)
            [ -f "$cfg" ] || { warn "systemd-networkd pei zhiwen jian bu cun zai"; return 1; }
            info "xitong wang luo pei zhifangshi:systemd-networkd"
            info "pei zhiwen jian:$cfg"
            echo "[ming ling] cp -a $cfg $BACKUP_DIR/"
            echo "[ming ling] xie ru [Route] Gateway=$gw"
            echo "[ming ling] systemctl restart systemd-networkd"
            confirm "queren jiang mo ren lu you yongjiu xie ru systemd-networkd?" || return
            backup_file "$cfg"
            if grep -q '^Gateway=' "$cfg"; then sed -i "s/^Gateway=.*/Gateway=$gw/" "$cfg"; else printf '\n[Route]\nGateway=%s\n' "$gw" >> "$cfg"; fi
            fixmsg "yi xie ru $cfg. qing genjuye wu chuang kou zhi xing systemctl restart systemd-networkd"
            ;;
        *)
            warn "weineng shi bie wang luo pei zhifangshi, wu fa an quan xie ru yongjiu pei zhi"
            plan "ke xian lin shi zhi xing:ip route replace default via $gw dev $PRIMARY_IF"
            ;;
    esac
}

fix_default_route_menu(){
    section "gao wei xiu fu: mo ren lu you"
    detect_os; get_primary_network; detect_network_config_method
    local defaults gw sel route dev metric
    defaults="$(ip route show default 2>/dev/null)"

    if [ -n "$defaults" ]; then
        info "dang qian mo ren lu you:"
        list_default_routes
    else
        warn "dang qian mo ren lu you: queshi"
    fi
    info "zhu wang ka:${PRIMARY_IF:-weishi bie}; zhuIP:${PRIMARY_IP_CIDR:-weishi bie}; dang qian wang guan:${PRIMARY_GW:-weishi bie}"
    info "wang luo pei zhifangshi:$NET_MGR; pei zhiwei zhi:${NET_CFG:-weishi bie}"

    case "$NET_MGR" in
        NetworkManager) plan "yongjiu sheng xiao jiang shi yong nmcli xie ru NetworkManager lian jie pei zhi. shi yong Rocky/AlmaLinux/CentOS Stream 8/9, ye ke neng shi yongyu bu fen Ubuntu/Debian.";;
        ifcfg) plan "yongjiu sheng xiao jiang xie ru ifcfg wen jian. shi yong CentOS 7 network-scripts.";;
        netplan) plan "yongjiu sheng xiao jiang xie ru/sheng cheng netplan pei zhi. shi yong Ubuntu 18/20/22/24, bu fen Debian.";;
        interfaces) plan "yongjiu sheng xiao jiang xie ru /etc/network/interfaces. shi yong Debian ifupdown.";;
        systemd-networkd) plan "yongjiu sheng xiao jiang xie ru /etc/systemd/network/*.network. shi yong systemd-networkd.";;
        *) plan "weishi bie yongjiu pei zhifangshi, jin jian yi lin shi xiu gaimo ren lu you.";;
    esac

    echo "1) lin shi xiu gaimo ren lu you"
    echo "2) yongjiu xiu gaimo ren lu you"
    echo "3) shan chu yi shi bie de mo ren lu you"
    echo "4) xiu gaimo ren lu you metric"
    echo "5) cha kan bu tong xitong mo ren lu you xie ru shuo ming"
    echo "0) fan hui"
    read -r -p "xuan ze:" c

    case "$c" in
        1)
            [ -z "$PRIMARY_IF" ] && read -r -p "qing shu ru wang ka ming:" PRIMARY_IF
            read -r -p "qing shu ru xin de mo ren wang guan IP:" gw
            [ -z "$gw" ] && { warn "wang guan bu neng wei kong"; return; }
            echo "[ming ling] ip route replace default via $gw dev $PRIMARY_IF"
            confirm "gai ming ling li ji sheng xiao dan zhong qi hou ke neng shi xiao, queren zhi xing?" || return
            ip route replace default via "$gw" dev "$PRIMARY_IF" >> "$REPORT" 2>&1 && fixmsg "mo ren lu you yi lin shi xiu gaiwei:default via $gw dev $PRIMARY_IF" || err "lin shi xiu gaimo ren lu you shi bai"
            ;;
        2)
            read -r -p "qing shu ru xin de mo ren wang guan IP:" gw
            [ -z "$gw" ] && { warn "wang guan bu neng wei kong"; return; }
            write_default_route_permanent "$gw"
            ;;
        3)
            delete_default_route_by_number
            ;;
        4)
            if [ -z "$defaults" ]; then warn "dang qian mei you mo ren lu you ke xiu gai metric"; return; fi
            info "jian ce dao mo ren lu you:"; list_default_routes
            read -r -p "qing xuan zeyao xiu gai metric de mo ren lu you bian hao:" sel
            route="$(get_default_route_by_number "$sel")"
            [ -z "$route" ] && { warn "xuan zewu xiao"; return; }
            dev="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}')"
            gw="$(echo "$route" | awk '{for(i=1;i<=NF;i++) if($i=="via") print $(i+1)}')"
            read -r -p "qing shu ru xin de metric:" metric
            [ -z "$gw" ] || [ -z "$dev" ] || [ -z "$metric" ] && { warn "queshao wang guan/wang ka/metric, wu fa zhi xing"; return; }
            echo "[ming ling] ip route replace default via $gw dev $dev metric $metric"
            confirm "queren xiu gaimo ren lu you metric?" || return
            ip route replace default via "$gw" dev "$dev" metric "$metric" >> "$REPORT" 2>&1 && fixmsg "mo ren lu you metric yi xiu gai" || err "metric xiu gaishi bai"
            ;;
        5)
            show_route_persistence_notes
            ;;
        0) return;;
        *) warn "wu xiao xuan xiang";;
    esac
}

fix_openssl_sshd_menu(){
    section "OpenSSL / sshd yi lai ku yi chang xiu fu"
    detect_os; detect_ssh_service
    log_detail "---- ldd /usr/sbin/sshd ----"; ldd /usr/sbin/sshd >> "$REPORT" 2>&1 || true
    if command -v rpm >/dev/null 2>&1; then rpm -q openssh-server openssl openssl-libs >> "$REPORT" 2>&1 || true; rpm -V openssh-server openssl-libs >> "$REPORT" 2>&1 || true; fi
    if command -v dpkg >/dev/null 2>&1; then dpkg -l openssh-server openssl 'libssl*' >> "$REPORT" 2>&1 || true; fi
    echo "1) an dang qian xitong zi dong xiu fu yi lai ku"
    echo "2) jin zhi xing ldconfig bing an quan zhong qi SSH"
    echo "0) fan hui"
    read -r -p "xuan ze:" c
    case "$c" in
        1)
            confirm "gai cao zuo ke neng diao zheng xitong bao bing zhong qi SSH, jian yi zai VNC/kong zhi tai zhi xing. queren?" || return
            if [ "$OS_FAMILY" = "rhel" ]; then if command -v dnf >/dev/null 2>&1; then dnf distro-sync -y >> "$REPORT" 2>&1; else yum update -y openssh-server openssl openssl-libs >> "$REPORT" 2>&1; fi
            elif [ "$OS_FAMILY" = "debian" ]; then apt update >> "$REPORT" 2>&1; apt install --reinstall -y openssh-server openssl >> "$REPORT" 2>&1; fi
            ldconfig >> "$REPORT" 2>&1; restart_ssh_safe;;
        2) ldconfig >> "$REPORT" 2>&1; restart_ssh_safe;;
        0) return;;
        *) warn "wu xiao xuan xiang";;
    esac
}

fix_cc_block_menu(){
    section "CC lian jie xian zhi / feng jin gao lian jie IP"
    echo "shu ru 0 fan hui, huo zhi jie hui che shi yongmo ren zhi."
    read -r -p "qing shu ru lian jie shu yu zhi, mo ren100:" th
    [ "$th" = "0" ] && return
    th="${th:-100}"
    read -r -p "qing shu ru tong ji duankou, mo ren 80|443:" ports; ports="${ports:-80|443}"
    local ips ip
    ips="$(ss -Hant state established 2>/dev/null | awk -v ports="$ports" -v th="$th" '$4 ~ ":("ports")$" {peer=$5; gsub(/^\[/,"",peer); gsub(/\]$/,"",peer); sub(/:[0-9]+$/,"",peer); c[peer]++} END{for(ip in c) if(c[ip]>=th) print ip}' | grep -Ev '^(127\.0\.0\.1|::1)$')"
    [ -z "$ips" ] && { ok "weifa xian chao guo yu zhi de IP"; return; }
    echo "jiang feng jin yi xia IP:"; echo "$ips"
    confirm "queren feng jin zhe xie IP? jian yi xian queren shifou wei CDN/dai li/zhen shi ye wu lai yuan." || return
    for ip in $ips; do
        if systemctl is-active firewalld >/dev/null 2>&1; then firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='${ip}' drop" >> "$REPORT" 2>&1; firewall-cmd --add-rich-rule="rule family='ipv4' source address='${ip}' drop" >> "$REPORT" 2>&1; fixmsg "firewalld yi feng jin $ip"; fi
        if command -v iptables >/dev/null 2>&1; then iptables -C INPUT -s "$ip" -j DROP >/dev/null 2>&1 || iptables -I INPUT -s "$ip" -j DROP >> "$REPORT" 2>&1; fixmsg "iptables yi feng jin $ip"; fi
        if systemctl is-active ufw >/dev/null 2>&1; then ufw deny from "$ip" >> "$REPORT" 2>&1; fixmsg "ufw yi feng jin $ip"; fi
        if command -v nft >/dev/null 2>&1; then plan "nftables feng jin weizi dong xie ru, qing an gui ze ji ren gong queren.IP=$ip"; fi
    done
    save_iptables
    systemctl is-active firewalld >/dev/null 2>&1 && firewall-cmd --reload >> "$REPORT" 2>&1
}


apply_network_config_after_replace(){
    local mgr="$1" cfg="$2"
    case "$mgr" in
        netplan)
            command -v netplan >/dev/null 2>&1 || { warn "netplan ming ling bu cun zai, pei zhiyi tihuan dan weiying yong"; return 1; }
            netplan generate >/dev/null 2>&1 || { err "netplan generate shi bai, weiying yongwang luo; yuan pei zhibei fen wei yu $BACKUP_DIR"; return 1; }
            netplan apply >/dev/null 2>&1 && fixmsg "netplan pei zhiyi ying yong" || err "netplan apply shi bai"
            ;;
        NetworkManager)
            nmcli connection reload >/dev/null 2>&1
            if [ -n "$NET_CON" ] && nmcli connection up "$NET_CON" >/dev/null 2>&1; then fixmsg "NetworkManager lian jie yi zhong xin jia zai:$NET_CON"; else err "NetworkManager lian jie ying yongshi bai, qing shi yongkong zhi tai jian cha"; fi
            ;;
        ifcfg)
            systemctl restart network >/dev/null 2>&1 && fixmsg "network fu wu yi zhong qi, xin pei zhiyi ying yong" || err "network fu wu zhong qi shi bai"
            ;;
        interfaces)
            systemctl restart networking >/dev/null 2>&1 && fixmsg "networking fu wu yi zhong qi, xin pei zhiyi ying yong" || err "networking fu wu zhong qi shi bai"
            ;;
        systemd-networkd)
            systemctl restart systemd-networkd >/dev/null 2>&1 && fixmsg "systemd-networkd yi zhong qi, xin pei zhiyi ying yong" || err "systemd-networkd zhong qi shi bai"
            ;;
        *)
            warn "weishi bie wang luo guan li fangshi, pei zhiyi tihuan dan wu fa zi dong ying yong"
            ;;
    esac
}

backup_clean_primary_config(){
    section "bei fen zhu wang ka pei zhi, bing zai dang qian pei zhilu jing sheng cheng zhi bao liu zhu IP de pei zhiwen jian"
    detect_network_config_method
    [ -z "$PRIMARY_IF" ] && { warn "weishi bie zhu wang ka"; return; }
    [ -z "$PRIMARY_IP_CIDR" ] && { warn "weishi bie zhu IP"; return; }
    info "zhu wang ka:$PRIMARY_IF; zhuIP:$PRIMARY_IP_CIDR; wang guan:${PRIMARY_GW:-weishi bie}"
    info "wang luo pei zhifangshi:$NET_MGR; pei zhiwei zhi:${NET_CFG:-weishi bie}"
    local files=""
    case "$NET_MGR" in
        ifcfg) [ -f "$NET_CFG" ] && files="$NET_CFG";;
        NetworkManager) if [ -n "$NET_CON" ]; then files="$(find /etc/NetworkManager/system-connections -maxdepth 1 -type f 2>/dev/null | while read -r f; do grep -q "interface-name=$PRIMARY_IF\|id=$NET_CON" "$f" 2>/dev/null && echo "$f"; done | head -1)"; fi; [ -z "$files" ] && files="$(find /etc/NetworkManager/system-connections -maxdepth 1 -type f -name '*.nmconnection' 2>/dev/null | head -1)";;
        netplan|interfaces|systemd-networkd) [ -f "$NET_CFG" ] && files="$NET_CFG";;
        *) files="$(grep -Rsl "$PRIMARY_IF" /etc/netplan/*.yaml /etc/sysconfig/network-scripts/ifcfg-* /etc/network/interfaces /etc/network/interfaces.d/* /etc/systemd/network/*.network 2>/dev/null | head -1)";;
    esac
    [ -z "$files" ] && { warn "weizhaodao zhu wang ka pei zhiwen jian; ke neng you yun ping tai huo dong tai pei zhiguan li"; return; }
    local f clean
    for f in $files; do
        backup_file "$f"
        clean="$(create_clean_config_copy "$f")"
        info "yi zai dang qian pei zhilu jing sheng cheng:$clean"
        echo "[shuo ming] yuan pei zhiyi bei fen dao:$BACKUP_DIR"
        confirm "shifou yonggai wen jian tihuan yuan pei zhibing li ji zhong qi/ying yongwang luo? gai cao zuo hui yichu fu jia IP, ke neng ying xiang ye wu. queren?" || continue
        cp -a "$clean" "$f" && fixmsg "yi tihuan yuan pei zhi:$f"
        apply_network_config_after_replace "$NET_MGR" "$f"
    done
}

create_clean_config_copy(){
    local f="$1" dir base clean
    dir="$(dirname "$f")"; base="$(basename "$f")"; clean="${dir}/${base}.only-primary-${RUN_ID}"
    cp -a "$f" "$clean"
    case "$f" in
        *ifcfg-*) sed -i -E '/^(IPADDR[0-9]+|PREFIX[0-9]+|NETMASK[0-9]+|GATEWAY[0-9]+|IPADDR_SECONDARIES|IPV6ADDR_SECONDARIES)=/d' "$clean";;
        *.nmconnection) sed -i -E '/^address[2-9][0-9]*=/d' "$clean"; awk 'BEGIN{FS=OFS="="} /^address1=/{split($2,a,";"); $2=a[1]} {print}' "$clean" > "$clean.tmp" && mv "$clean.tmp" "$clean"; chmod 600 "$clean" 2>/dev/null || true;;
        *.yaml) awk -v iface="$PRIMARY_IF" -v primary="$PRIMARY_IP_CIDR" 'function indent_len(s){match(s,/[^ ]/);return RSTART-1} BEGIN{in_iface=0;iface_indent=-1;in_addr_block=0;addr_indent=-1} {line=$0; if(line ~ "^[[:space:]]*" iface ":[[:space:]]*$"){in_iface=1;iface_indent=indent_len(line);print line;next} if(in_iface && indent_len(line)<=iface_indent && line !~ "^[[:space:]]*$"){in_iface=0;in_addr_block=0} if(in_iface && indent_len(line)==iface_indent+2 && line ~ /^[[:space:]]*addresses:[[:space:]]*\[/){sub(/addresses:[[:space:]]*\[[^]]*\]/,"addresses: [" primary "]",line);print line;next} if(in_iface && indent_len(line)==iface_indent+2 && line ~ /^[[:space:]]*addresses:[[:space:]]*$/){print line;addr_indent=indent_len(line);print substr(line,1,addr_indent) "  - " primary;in_addr_block=1;next} if(in_addr_block){if(line ~ /^[[:space:]]*-[[:space:]]*[0-9a-fA-F:.]+\//)next; if(indent_len(line)<=addr_indent && line !~ /^[[:space:]]*$/)in_addr_block=0; else if(line ~ /^[[:space:]]*-/)next} print line}' "$clean" > "$clean.tmp" && mv "$clean.tmp" "$clean";;
        *interfaces*) sed -i -E "/ip addr add .* dev ${PRIMARY_IF}/d" "$clean";;
        *.network) awk 'BEGIN{seen=0} /^Address=/{seen++; if(seen>1) next} {print}' "$clean" > "$clean.tmp" && mv "$clean.tmp" "$clean";;
    esac
    echo "$clean"
}

# ------------------------- cai dan -------------------------
menu(){
    while true; do
        echo ""
        echo "=================================================="
        echo " OPS Linux xitong zhen duanyu xiu fu gong ju v7.4"
        echo " pei zhibian geng bei fen mulu:$BACKUP_DIR"
        echo "=================================================="
        echo "1) yi jian jian ce bing zi dong chu li chang jian wen ti"
        echo "2) cha xun xitong yu wang luo ji chu xin xi"
        echo "3) SSH fu wu jian cha yu zi dong chu li"
        echo "4) SSH denglu ce lue jian cha yu kai guan xiang"
        echo "5) fang huoqiang duankou jian cha yu zi dong chu li"
        echo "6) SELinux / Ping jian cha yu zi dong chu li"
        echo "7) OpenSSL/sshd yi lai ku jian cha yu xiu fu"
        echo "8) jian cha netplan pei zhi"
        echo "9) jian cha CC/gao lian jie IP"
        echo "10) jian cha jiqi dui wai kai fangduankou"
        echo "11) SSH fu wu kuozhan xiang"
        echo "12) fang huoqiang duankou kuozhan xiang"
        echo "13) SELinux / Ping kuozhan xiang"
        echo "14) gao wei xiu fu:netplan wang luo pei zhi"
        echo "15) gao wei xiu fu: mo ren lu you"
        echo "16) gao wei cao zuo:CC lian jie xian zhi / feng jin gao lian jie IP"
        echo "17) bei fen zhu wang ka pei zhibing sheng cheng jin bao liu zhuIP pei zhi"
        echo "0) tui chu"
        echo "=================================================="
        read -r -p "qing xuan ze:" choice
        init_detect
        case "$choice" in
            1) auto_check_and_fix;;
            2) check_system_network;;
            3) check_ssh_service;;
            4) fix_ssh_policy_menu;;
            5) check_firewall;;
            6) check_security;;
            7) check_openssl_sshd_internal; fix_openssl_sshd_menu;;
            8) check_netplan;;
            9) read -r -p "lian jie shu yu zhi, mo ren100:" t; check_cc_attack "${t:-100}";;
            10) check_open_ports;;
            11) fix_ssh_service_menu;;
            12) fix_firewall_port_menu;;
            13) fix_security_menu;;
            14) fix_netplan_menu;;
            15) fix_default_route_menu;;
            16) fix_cc_block_menu;;
            17) backup_clean_primary_config;;
            0) exit 0;;
            *) warn "wu xiao xuan xiang";;
        esac
    done
}

need_root
init_detect
menu
