← 返回首页

Debian12装KVM+Proxmox:零断网桥接清单 🚀

远程给 Debian 12 装 KVM + Proxmox VE,最怕的就是切桥接那一刻把自己关在门外。

其实可以做到零断网 🚀 —— 腾讯云实测:br0 切换前后内网 IP、出口公网 IP 全部不变,本机每 15 秒探活全程零中断。

关键不在于"桥接本身",而在于顺序:先把看门狗武装好,再去动网络。

本文把全过程整理成可直接复制粘贴的命令,共 6 个阶段 👇

配图

💡 三条铁律(先记住这三条)

  • 先武装,再动网 —— 任何网络变更前,看门狗必须已经在跑。顺序反了就没有退路。
  • 只用 ifreload -a —— ifdown/ifup 和 systemctl restart networking 必然断网,禁用。
  • 高危动作 detached 跑 —— setsid nohup ... </dev/null &,SSH 断了脚本照样跑完并自己回滚。

🎯 为什么不会断网

  • 云主机:公网 IP 是 NAT 映射到内网 IP,DHCP 按 MAC 分配。br0 沿用原网卡 MAC,就拿到同一个内网 IP,NAT 映射不变。
  • 物理机:公网 IP 是静态直配,跟 MAC 无关。br0 写成 inet static 原样搬过去即可。
真正会让你失联的另有其人 👇
  1. cloud-init —— /etc/network/interfaces.d/50-cloud-init 文件头自己写着"重启后不保留",它才是"重启就失联"的真凶。
  2. 主机名指向回环 127.0.1.1 —— PVE 硬性要求解析到非回环 IP,不修会导致集群服务异常。
  3. Proxmox 官方源在国内基本不可用 —— 实测 935 B/s,装 1.5GB 要 20 天。必须换清华 TUNA 镜像(1.2 MB/s,快 1300 倍)。

阶段 0 · 侦察(只读,不改任何东西)

功能:摸清家底 —— 网卡名、CIDR、网关、MAC、有没有 /dev/kvm,并判定 IP 是静态还是 DHCP。
这个判定是整个方案的分水岭,它决定后面桥接怎么写、回滚怎么走。

BASH
mkdir -p /root/kvm-bridge && cd /root/kvm-bridge

echo "===== 1. 网卡与地址(记下来:网卡名 / CIDR / 网关)====="
ip -br addr
ip route

echo "===== 2. 物理网卡 MAC(后面必须沿用)====="
for d in /sys/class/net/*; do
  d=${d##*/}
  [ "$(cat /sys/class/net/$d/type 2>/dev/null)" = 1 ] && echo "  $d  $(cat /sys/class/net/$d/address)"
done

echo "===== 3. 网络配置来源(判定 static / dhcp 的关键)====="
cat /etc/network/interfaces
echo "--- interfaces.d ---"
ls -l /etc/network/interfaces.d/ 2>/dev/null && grep -r . /etc/network/interfaces.d/ 2>/dev/null
echo "--- iface 写法 ---"
grep -rE 'iface[[:space:]]+[a-z0-9]+[[:space:]]+inet[[:space:]]+(dhcp|static)' \
     /etc/network/interfaces /etc/network/interfaces.d/ 2>/dev/null || echo "(无)"

echo "===== 4. 虚拟化支持(有无 /dev/kvm 决定能否跑 VM)====="
echo -n "  vmx 标志数: "; grep -cw vmx /proc/cpuinfo
ls -l /dev/kvm 2>/dev/null || echo "  无 /dev/kvm —— PVE 能装能管,但跑不了硬件加速 VM"

echo "===== 5. 主机名解析(PVE 要求非回环)====="
hostname
echo -n "  hostname -i => "; hostname --ip-address 2>/dev/null
cat /etc/hosts

echo "===== 6. 网络工具与磁盘 ====="
command -v ifreload || echo "  无 ifreload(装 PVE 后才有,属 ifupdown2)"
df -h / | tail -1
free -m | head -2

echo "===== 7. 兜底通道自检 ====="
echo "  云主机:确认控制台 VNC 可用"
echo "  物理机:确认 IPMI/iDRAC/iLO 能登录(ipmitool chassis status 或直接开管理页)"

通过标准:能明确回答三件事 —— ① 网卡名 ② 完整 CIDR + 网关 ③ 是 static 还是 dhcp。

阶段 1 · 落盘看门狗 + 备份加固(不动网络)

功能:先把"安全网"织好。本阶段完全不碰网络配置,只做三件事 —— 全量备份、切断干扰源(cloud-init / hosts 回环)、部署并武装看门狗。

1.1 写入看门狗(整段粘贴,一次成型)

功能:落盘 netguard.sh + systemd 服务。它是"死手开关"—— 每 15 秒多目标投票探测连通性,
连续不通超过窗口期(开机 120s / 平时 300s)就自动三级回滚。

  • 为什么必须是 systemd 常驻:用 nohup 挂在 SSH 里,SSH 一断它跟着死,这是最经典的翻车点。
  • 为什么多目标投票:只 ping 一个 IP 会误判,误判就会把好配置回滚掉。
BASH
cat > /root/kvm-bridge/netguard.sh <<'SCRIPT_EOF'
#!/bin/bash
# =============================================================================
# netguard — 网络变更看门狗 / dead-man switch        【云主机 / 物理机 通用】
# run/arm/disarm/status/check/rollback
# 回滚策略由 /etc/netguard/config 里的 IP_MODE 决定:
#   static(物理机公网 IP 直配):① 静态写回 → ② DHCP 兜底 → ③ 保留现场不重启
#   dhcp  (云主机 NAT 场景):    ① 裸 DHCP   → ② 重写配置   → ③ 静态写回
# 【为什么静态场景不能先试 DHCP】机房通常不提供 DHCP,dhclient 空跑 30s 失败,
#   而被 flush 掉的原地址不会回来 —— 把"抖动"升级成"确定断网"。
# =============================================================================
set -u

CONF=/etc/netguard/config
STATE_DIR=/var/lib/netguard
LOG=/var/log/netguard.log
ARMED=$STATE_DIR/armed
FAILSINCE=$STATE_DIR/fail_since
ESCALATE=$STATE_DIR/ESCALATE

mkdir -p "$STATE_DIR"
touch "$LOG" 2>/dev/null || true

log() { echo "$(date '+%F %T') [$1] $2" >> "$LOG"; echo "[$1] $2" >&2; }

[ -f "$CONF" ] || { echo "missing $CONF" >&2; exit 1; }
. "$CONF"
: "${IP_MODE:=dhcp}"
: "${TARGETS:=icmp:114.114.114.114 icmp:223.5.5.5 icmp:119.29.29.29}"
: "${MIN_OK:=2}"
: "${FAIL_WINDOW:=300}"
: "${FAIL_WINDOW_BOOT:=120}"
: "${CHECK_INTERVAL:=15}"
: "${BACKUP_DIR:=}"
: "${ORIG_IP:=}"; : "${ORIG_CIDR:=}"; : "${ORIG_GW:=}"; : "${ORIG_DNS:=}"

detect_phys() {
    local want="" dev m
    [ -f "$STATE_DIR/phys_mac" ] && want=$(cat "$STATE_DIR/phys_mac")
    if [ -n "$want" ]; then
        for dev in /sys/class/net/*; do
            dev=${dev##*/}
            case "$dev" in lo|br*|vmbr*|tap*|veth*|fw*|bond*) continue ;; esac
            m=$(cat "/sys/class/net/$dev/address" 2>/dev/null)
            [ "$m" = "$want" ] && { echo "$dev"; return; }
        done
    fi
    for dev in /sys/class/net/*; do
        dev=${dev##*/}
        case "$dev" in lo|br*|vmbr*|tap*|veth*|fw*|bond*) continue ;; esac
        [ "$(cat /sys/class/net/$dev/type 2>/dev/null)" = 1 ] && { echo "$dev"; return; }
    done
    echo eth0
}

net_ok() {
    local ok=0 t
    for t in $TARGETS; do
        case "$t" in
            icmp:*) ping -c1 -W2 "${t#icmp:}" >/dev/null 2>&1 && ok=$((ok+1)) ;;
            http:*) curl -s -m 4 -o /dev/null "${t#http:}" && ok=$((ok+1)) ;;
            dns:*)  getent hosts "${t#dns:}" >/dev/null 2>&1 && ok=$((ok+1)) ;;
        esac
    done
    [ "$ok" -ge "$MIN_OK" ]
}

snapshot() {
    { echo "=== $(date '+%F %T') phase=$1"
      ip -o addr; ip -o link show | awk '{print $2,$17,$(NF-1)}'; ip route
      echo "--- resolve"; cat /etc/resolv.conf
    } >> "$STATE_DIR/forensics.log" 2>&1
}

tear_bridge() {
    if ip link show br0 >/dev/null 2>&1; then
        ip addr flush dev br0 2>/dev/null
        ip link set br0 down 2>/dev/null
        ip link del br0 2>/dev/null
        log WARN "已拆除 br0"
    fi
    rm -f /etc/network/interfaces.d/netguard-br0 /etc/network/interfaces.d/50-cloud-init 2>/dev/null
}

restore_conf_files() {
    if [ -n "$BACKUP_DIR" ] && [ -f "$BACKUP_DIR/network/interfaces" ]; then
        cp -a "$BACKUP_DIR/network/interfaces" /etc/network/interfaces 2>/dev/null \
            && log INFO "已从备份恢复 /etc/network/interfaces"
    fi
}

restore_static() {
    local dev; dev=$(detect_phys)
    if [ -z "$ORIG_CIDR" ] || [ -z "$ORIG_GW" ]; then
        log WARN "缺少 ORIG_CIDR/ORIG_GW,无法静态回写"
        return 1
    fi
    log WARN "静态回写:拆除 br0,把 $ORIG_CIDR gw $ORIG_GW 写回物理网卡 $dev"
    snapshot STATIC-before
    tear_bridge
    restore_conf_files
    ip link set "$dev" up
    ip addr flush dev "$dev" 2>/dev/null
    ip addr add "$ORIG_CIDR" dev "$dev"
    ip route replace default via "$ORIG_GW" dev "$dev"
    printf 'nameserver %s\n' ${ORIG_DNS:-223.5.5.5} > /etc/resolv.conf
    sleep 5
    snapshot STATIC-after
    net_ok
}

restore_dhcp_raw() {
    local dev; dev=$(detect_phys)
    log WARN "裸 DHCP:拆除 br0,物理网卡 $dev 直接 dhclient"
    snapshot DHCPRAW-before
    tear_bridge
    pkill -f 'dhclient' 2>/dev/null
    sleep 2
    ip link set "$dev" up
    ip addr flush dev "$dev" 2>/dev/null
    timeout 30 dhclient -4 -pf "/run/dhclient.$dev.pid" -lf "/var/lib/dhcp/dhclient.$dev.leases" "$dev" >>"$LOG" 2>&1
    [ -n "$ORIG_GW" ] && ip route replace default via "$ORIG_GW" dev "$dev" 2>/dev/null
    printf 'nameserver %s\n' ${ORIG_DNS:-223.5.5.5} > /etc/resolv.conf
    sleep 5
    snapshot DHCPRAW-after
    net_ok
}

restore_dhcp_conf() {
    local dev; dev=$(detect_phys)
    log WARN "重写 interfaces 为极简 DHCP($dev),重启 networking"
    cp -a /etc/network/interfaces "/etc/network/interfaces.netguard-l2" 2>/dev/null
    cat > /etc/network/interfaces <<EOF
# netguard fallback $(date '+%F %T')
auto lo
iface lo inet loopback

auto $dev
iface $dev inet dhcp
EOF
    mkdir -p /etc/cloud/cloud.cfg.d
    printf 'network: {config: disabled}\n' > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
    pkill -f 'dhclient' 2>/dev/null; sleep 2
    if command -v ifreload >/dev/null 2>&1; then
        timeout 60 ifreload -a >>"$LOG" 2>&1 || true
    else
        timeout 60 /etc/init.d/networking restart >>"$LOG" 2>&1 || true
    fi
    sleep 8
    snapshot DHCPCONF-after
    net_ok
}

do_rollback() {
    local why="$1"
    log ERROR "判定断网($why),进入回滚编排(IP_MODE=$IP_MODE)"

    if [ "$IP_MODE" = "static" ]; then
        if restore_static; then
            log INFO "静态回写成功,网络恢复"; touch "$STATE_DIR/RECOVERED"; return 0
        fi
        log WARN "静态回写未恢复,尝试 DHCP 兜底"
        if restore_dhcp_raw; then
            log INFO "DHCP 兜底成功"; touch "$STATE_DIR/RECOVERED"; return 0
        fi
    else
        if restore_dhcp_raw; then
            log INFO "L1(DHCP) 回滚成功,网络恢复"; touch "$STATE_DIR/RECOVERED"; return 0
        fi
        log WARN "L1 未恢复,进入 L2"
        if restore_dhcp_conf; then
            log INFO "L2 回滚成功"; touch "$STATE_DIR/RECOVERED"; return 0
        fi
        log WARN "L2 未恢复,进入 L3 静态写回"
        if restore_static; then
            log INFO "L3 回滚成功"; touch "$STATE_DIR/RECOVERED"; return 0
        fi
    fi

    log ERROR "回滚全部失败,保留现场,【不重启】。请用 IPMI/iDRAC 带外管理、云控制台 VNC、或机房人工 介入。"
    { echo "ESCALATE $(date '+%F %T')  IP_MODE=$IP_MODE"
      ip -o addr; ip route; ip -o link; cat /etc/network/interfaces
    } > "$ESCALATE"
    return 1
}

main_loop() {
    local window="$FAIL_WINDOW"
    if [ -f /etc/netguard/AUTOARM ] && [ ! -f "$ARMED" ]; then
        cmd_arm >/dev/null 2>&1
        log INFO "检测到 /etc/netguard/AUTOARM,开机自动武装"
    fi
    local up_s; up_s=$(cut -d. -f1 /proc/uptime)
    if [ "$up_s" -lt 600 ]; then
        window="$FAIL_WINDOW_BOOT"
        log INFO "开机自检模式:窗口缩短为 ${window}s,先宽限 45s 等网络就绪"
        sleep 45
    fi
    log INFO "netguard 启动,IP_MODE=$IP_MODE,检查间隔 ${CHECK_INTERVAL}s,判死窗口 ${window}s(uptime ${up_s}s)"

    while true; do
        if [ ! -f "$ARMED" ]; then
            sleep 30
            window="$FAIL_WINDOW"
            continue
        fi
        if [ -f "$STATE_DIR/HANDLED" ]; then
            sleep 30
            continue
        fi
        if net_ok; then
            [ -f "$FAILSINCE" ] && log INFO "连通性已恢复,计时清零"
            rm -f "$FAILSINCE"
        else
            if [ ! -f "$FAILSINCE" ]; then
                date +%s > "$FAILSINCE"
                log WARN "探测失败,开始计时"
            else
                local since now el
                since=$(cat "$FAILSINCE"); now=$(date +%s); el=$((now - since))
                log WARN "仍不通,已持续 ${el}s / ${window}s"
                if [ "$el" -ge "$window" ]; then
                    if do_rollback "持续 ${el}s 不通"; then
                        log INFO "回滚完成,保持常驻继续观察"
                    fi
                    touch "$STATE_DIR/HANDLED"
                fi
            fi
        fi
        sleep "$CHECK_INTERVAL"
    done
}

cmd_arm() {
    rm -f "$FAILSINCE" "$STATE_DIR/HANDLED" "$ESCALATE" "$STATE_DIR/RECOVERED"
    date +%s > "$ARMED"
    log INFO "已武装(armed)"
}
cmd_disarm() { rm -f "$ARMED"; log INFO "已解除武装(disarm),日志保留于 $LOG"; }

cmd_status() {
    echo "armed      : $([ -f "$ARMED" ] && echo YES || echo no)"
    echo "handled    : $([ -f "$STATE_DIR/HANDLED" ] && echo YES || echo no)"
    echo "recovered  : $([ -f "$STATE_DIR/RECOVERED" ] && echo YES || echo no)"
    echo "escalate   : $([ -f "$ESCALATE" ] && echo YES || echo no)"
    echo "fail_since : $(cat "$FAILSINCE" 2>/dev/null || echo -)"
    echo "ip_mode    : $IP_MODE"
    echo "orig       : ${ORIG_CIDR:-?} gw ${ORIG_GW:-?}"
    echo "phys       : $(detect_phys) mac=[$(cat "$STATE_DIR/phys_mac" 2>/dev/null)]"
    echo "net_ok     : $(net_ok && echo YES || echo NO)"
}

case "${1:-status}" in
    run)    main_loop ;;
    arm)    cmd_arm ;;
    disarm) cmd_disarm ;;
    status) cmd_status ;;
    check)  net_ok && echo OK || echo FAIL ;;
    rollback) do_rollback "manual" ;;
    *) echo "usage: $0 {run|arm|disarm|status|check|rollback}" ;;
esac
SCRIPT_EOF

cat > /root/kvm-bridge/netguard.service <<'SCRIPT_EOF'
[Unit]
Description=netguard network dead-man switch
After=network.target
Wants=network.target

[Service]
Type=simple
ExecStart=/usr/local/sbin/netguard.sh run
Restart=always
RestartSec=5
Nice=-10
KillMode=process
TimeoutStopSec=10

[Install]
WantedBy=multi-user.target
SCRIPT_EOF

chmod +x /root/kvm-bridge/netguard.sh
bash -n /root/kvm-bridge/netguard.sh && echo "netguard.sh 语法 OK"
wc -l /root/kvm-bridge/netguard.sh

1.2 写入阶段 1 脚本

功能:生成备份加固脚本。四件事 —— ① 全量备份 /etc/network、/etc/hosts、grub 等;
② 自动判定 static / dhcp(四证据:interfaces 写法、dhclient 租约、cloud-init 配置、networkd 配置);
③ 禁掉 cloud-init 管网络 + 修 hosts 回环;④ 写入 /etc/netguard/config 并武装看门狗。

BASH
cat > /root/kvm-bridge/01-backup-harden.sh <<'SCRIPT_EOF'
#!/bin/bash
# 阶段 1:备份 + 加固 + 部署看门狗  【完全不修改任何网络配置】
# 自动判定 IP_MODE(static/dhcp)→ 决定后续桥接配置与回滚方向
# 可用 IP_MODE=static 环境变量强制覆盖自动判定
set -u

TS=$(date +%Y%m%d-%H%M%S)
NG=/root/netguard
BK=$NG/backup-$TS

PHYS=$(ip -o -4 route show default | awk '{print $5}' | head -1)
if [ -z "$PHYS" ]; then
    PHYS=$(ip -o -4 addr show | awk '$2!="lo"{print $2; exit}')
fi
[ -n "$PHYS" ] || { echo "!! 无法识别物理网卡,中止"; exit 1; }

PMAC=$(cat "/sys/class/net/$PHYS/address")
OIP=$(ip -o -4 addr show dev "$PHYS" | awk '{print $4}' | head -1)
[ -n "$OIP" ] || { echo "!! $PHYS 上没有 IPv4 地址,中止(先确认网卡选对了)"; exit 1; }
OGW=$(ip -o -4 route show default | awk '{print $3}' | head -1)
ODNS=$(awk '/^nameserver/{printf "%s ",$2}' /etc/resolv.conf)
HOST=$(hostname)

echo "### 探测: phys=$PHYS mac=$PMAC ip=$OIP gw=$OGW dns=[$ODNS] host=$HOST"

# ---------- 判定 IP 来源:static 还是 dhcp(分水岭)----------
detect_ip_mode() {
    local iponly forced
    iponly=${OIP%%/*}
    forced="${IP_MODE:-}"
    if [ -n "$forced" ]; then echo "$forced"; return; fi

    if grep -rqsE "^[[:space:]]*iface[[:space:]]+${PHYS}[[:space:]]+inet[[:space:]]+dhcp" \
         /etc/network/interfaces /etc/network/interfaces.d/ 2>/dev/null; then
        echo dhcp; return
    fi
    for lf in /var/lib/dhcp/dhclient*.leases /var/lib/dhcpcd/*.lease; do
        [ -f "$lf" ] || continue
        if grep -qs "$iponly" "$lf"; then echo dhcp; return; fi
    done
    for cf in /etc/network/interfaces.d/50-cloud-init /etc/network/interfaces.d/90-cloud-init; do
        [ -f "$cf" ] || continue
        if grep -qs dhcp "$cf"; then echo dhcp; return; fi
    done
    if grep -rqs 'DHCP=yes\|DHCP=ipv4\|method=auto' /etc/systemd/network/ 2>/dev/null; then
        echo dhcp; return
    fi
    echo static
}

IP_MODE_DETECTED=$(detect_ip_mode)
echo "### IP 来源判定: IP_MODE=$IP_MODE_DETECTED"
case "$IP_MODE_DETECTED" in
    static) echo "    → 静态地址直配:回滚策略 = 静态写回(拆桥 + 原 IP 写回物理网卡)" ;;
    dhcp)   echo "    → DHCP 动态地址:回滚策略 = 裸 DHCP 优先(按 MAC 拿回同一地址)" ;;
esac

# ---------- 探测目标自适应 ----------
TARGETS_VAL="icmp:114.114.114.114 icmp:223.5.5.5 icmp:119.29.29.29"
if curl -s -m 4 -o /dev/null http://169.254.0.23/latest/meta-data/instance-id 2>/dev/null; then
    TARGETS_VAL="$TARGETS_VAL http:http://169.254.0.23/latest/meta-data/instance-id"
    echo "### 云厂商 metadata(169.254.0.23) 可达 → 纳入探测目标"
else
    echo "### 无云厂商 metadata → 探测目标仅用公网 DNS(物理机场景正常)"
fi
echo "### TARGETS = $TARGETS_VAL"

# ---------- 全量备份(逐项判存在)----------
mkdir -p "$BK" "$NG"
bk() { if [ -e "$1" ]; then cp -a "$1" "$BK/$2" 2>/dev/null && echo "  [备份] $1"; else echo "  [跳过] $1(不存在)"; fi; }
echo "### 全量备份 -> $BK"
bk /etc/network            network
bk /etc/hosts              hosts
bk /etc/resolv.conf        resolv.conf
bk /etc/cloud              cloud
bk /etc/default/grub       grub
bk /etc/fstab              fstab
bk /etc/systemd/network    systemd-network
bk /etc/netplan            netplan
bk /etc/NetworkManager     NetworkManager
bk /etc/apt/sources.list   sources.list
bk /etc/apt/sources.list.d sources.list.d

{
  echo "=== snapshot $TS"
  echo "phys=$PHYS mac=$PMAC ip=$OIP gw=$OGW dns=$ODNS host=$HOST ip_mode=$IP_MODE_DETECTED"
  echo "--- ip addr";   ip -o addr
  echo "--- ip link";   ip -o link show
  echo "--- ip route";  ip route
  echo "--- cmdline";   cat /proc/cmdline
  echo "--- lsblk";     lsblk
  echo "--- os";        head -3 /etc/os-release; uname -r
  echo "--- pkgs";      dpkg -l
  echo "--- crontab";   crontab -l 2>/dev/null
  echo "--- cloud.cfg.d"; ls -l /etc/cloud/cloud.cfg.d/ 2>/dev/null
  echo "--- 网卡命名策略"; ls -l /etc/systemd/network/*.link 2>/dev/null
  echo "--- 是否 ifupdown2"; command -v ifreload || echo "(无 ifreload)"
} > "$BK/SNAPSHOT.txt" 2>&1

echo "$BK"   > "$NG/BACKUP_PATH"
echo "$PMAC" > "$NG/ORIG_MAC"
echo "$OIP"  > "$NG/ORIG_CIDR"
echo "$OGW"  > "$NG/ORIG_GW"
echo "$ODNS" > "$NG/ORIG_DNS"
echo "$PHYS" > "$NG/ORIG_DEV"

# ---------- 加固 A:禁止 cloud-init 覆盖网络 ----------
if [ -d /etc/cloud ]; then
    mkdir -p /etc/cloud/cloud.cfg.d
    if [ ! -f /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg ]; then
        printf 'network: {config: disabled}\n' > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
        echo "### 已写入 99-disable-network-config.cfg"
    else
        echo "### 99-disable-network-config.cfg 已存在"
    fi
    cat /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
else
    echo "### 无 /etc/cloud(物理机常见),无需处理 cloud-init"
fi

# ---------- 加固 B:主机名解析到非回环 IP ----------
if ! grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[[:space:]]+$HOST" /etc/hosts; then
    cp -a /etc/hosts /etc/hosts.netguard-orig
    {
      echo "127.0.0.1       localhost"
      printf '%s\t%s.local %s\n' "${OIP%%/*}" "$HOST" "$HOST"
      echo "::1             ip6-localhost ip6-loopback"
      echo "fe00::0         ip6-localnet"
      echo "ff00::0         ip6-mcastprefix"
      echo "ff02::1         ip6-allnodes"
      echo "ff02::2         ip6-allrouters"
      echo "ff02::3         ip6-allhosts"
    } > /etc/hosts
    echo "### 已重写 /etc/hosts"
else
    echo "### /etc/hosts 已含非回环解析,跳过"
fi
grep -E "$HOST|localhost" /etc/hosts
echo "--- hostname --ip-address => $(hostname --ip-address 2>/dev/null)"

# ---------- 部署看门狗 ----------
install -m 0755 /root/kvm-bridge/netguard.sh      /usr/local/sbin/netguard.sh
install -m 0644 /root/kvm-bridge/netguard.service /etc/systemd/system/netguard.service
mkdir -p /etc/netguard
cat > /etc/netguard/config <<EOF
# netguard config ($TS)   IP_MODE=$IP_MODE_DETECTED
BACKUP_DIR="$BK"
ORIG_DEV="$PHYS"
ORIG_IP="${OIP%%/*}"
ORIG_CIDR="$OIP"
ORIG_GW="$OGW"
ORIG_DNS="$ODNS"
# static = 物理机公网 IP 直配 → 回滚走"静态写回"
# dhcp   = 云主机 NAT 场景   → 回滚走"裸 DHCP 优先"
IP_MODE="$IP_MODE_DETECTED"
TARGETS="$TARGETS_VAL"
MIN_OK=2
FAIL_WINDOW=300
FAIL_WINDOW_BOOT=120
CHECK_INTERVAL=15
EOF
cat /etc/netguard/config

mkdir -p /var/lib/netguard
echo "$PMAC" > /var/lib/netguard/phys_mac

systemctl daemon-reload
systemctl enable netguard.service
systemctl restart netguard.service
sleep 3

# ---------- 切换能力预检 ----------
echo
echo "### ==== 桥接能力预检 ===="
if command -v ifreload >/dev/null 2>&1; then
    echo "ifreload    : 可用(ifupdown2 已装)→ 阶段4 可零中断增量切换 OK"
else
    echo "ifreload    : 【不可用】当前是 ifupdown 而非 ifupdown2"
    echo "              物理机场景请【先跑阶段 2+3 装 PVE】(会自动换成 ifupdown2),"
    echo "              否则阶段 4 的 ifreload -a 会直接失败。"
fi
if [ "$IP_MODE_DETECTED" = "static" ]; then
    echo "MAC 提示    : 物理机常见坑 —— 部分机房要求报备 MAC / 交换机端口安全。"
    echo "              桥接沿用原 MAC(本方案已强制 hwaddress)通常无碍,"
    echo "              但若机房做过 IP-MAC 绑定,动手前先问运维确认。"
fi
if systemctl is-active --quiet systemd-networkd 2>/dev/null; then
    echo "警告        : systemd-networkd 正在运行,它可能接管网卡、覆盖 ifupdown 配置。"
fi
echo "备份目录    : $BK"

echo
echo "### ==== 汇总 ===="
systemctl is-active netguard.service && echo "netguard : active"
/usr/local/sbin/netguard.sh check
/usr/local/sbin/netguard.sh arm
/usr/local/sbin/netguard.sh status
echo "### 阶段1 完成,网络未做任何改动"
SCRIPT_EOF

chmod +x /root/kvm-bridge/01-backup-harden.sh
bash -n /root/kvm-bridge/01-backup-harden.sh && echo "01 语法 OK"

1.3 执行阶段 1

功能:跑起来。执行完必须逐条核对通过标准,不符合就停下来查。

BASH
bash /root/kvm-bridge/01-backup-harden.sh

通过标准(三条全中才继续):

阶段 2 · 接入 PVE 源(自动测速选镜像)

功能:只写源 + 预演,不装任何包。脚本会依次给清华、中科大、腾讯、阿里云、官方源各下 5MB 样本测速,
自动选最快的那个写入 /etc/apt/sources.list.d/pve-install-repo.list,然后跑一遍 apt 预演,让你先看清楚会动哪些包。

注意:Range 请求成功返回的是 206 不是 200,脚本两个都认 —— 只认 200 会永远选不出镜像。
BASH
cat > /root/kvm-bridge/02-pve-repo.sh <<'SCRIPT_EOF'
#!/bin/bash
# 阶段 2:接入 Proxmox 仓库 —— 镜像源自动测速选优 + apt 预演
set -u
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_SUSPEND=1

CODE=bookworm
SRC=/etc/apt/sources.list.d/pve-install-repo.list

echo "### 1) 回看备份"
BK=$(cat /root/netguard/BACKUP_PATH 2>/dev/null || echo "")
[ -n "$BK" ] && { ls -l "$BK"; echo "--- 原始 50-cloud-init:"; cat "$BK/network/interfaces.d/50-cloud-init" 2>/dev/null; }

echo
echo "### 2) 测速选源(各下载 5MB 样本)"
MIRRORS="
https://mirrors.tuna.tsinghua.edu.cn/proxmox/debian/pve
https://mirrors.ustc.edu.cn/proxmox/debian/pve
https://mirrors.cloud.tencent.com/proxmox/debian/pve
https://mirrors.aliyun.com/proxmox/debian/pve
http://download.proxmox.com/debian/pve
"
BEST=""; BEST_SPD=0
for m in $MIRRORS; do
    r=$(curl -s --max-time 15 -o /dev/null \
        -w '%{http_code} %{speed_download}' \
        -r 0-5242880 "$m/dists/$CODE/pve-no-subscription/binary-amd64/Packages" 2>/dev/null)
    code=$(echo "$r" | awk '{print $1}')
    spd=$(echo "$r" | awk '{print int($2)}')
    printf '  %-70s code=%s speed=%s B/s\n' "$m" "$code" "${spd:-0}"
    # Range 请求成功返回 206 而非 200,两个都算可用
    case "$code" in
        200|206) if [ "${spd:-0}" -gt "$BEST_SPD" ]; then BEST="$m"; BEST_SPD="$spd"; fi ;;
    esac
done
if [ -z "$BEST" ]; then echo "!! 所有镜像源均不可用,请检查网络"; exit 1; fi
echo "  => 选用: $BEST  ($BEST_SPD B/s)"

echo
echo "### 3) 写入 PVE 源 + 官方签名密钥"
echo "deb [arch=amd64] $BEST $CODE pve-no-subscription" > "$SRC"
cat "$SRC"
wget -q https://enterprise.proxmox.com/debian/proxmox-release-$CODE.gpg \
     -O /etc/apt/trusted.gpg.d/proxmox-release-$CODE.gpg
echo "--- 密钥 sha512(期望值见下方)"
sha512sum /etc/apt/trusted.gpg.d/proxmox-release-$CODE.gpg
echo "  期望: 7da6fe34168adc6e479327ba517796d4702fa2f8b4f0a9833f5ea6e6b48f6507a6da403a274fe201595edc86a84463d50383d07f64bdde2e3658108db7d6dc87"

echo
echo "### 4) postfix 非交互预置"
printf 'postfix postfix/main_mailer_type select Local only\n' | debconf-set-selections
printf 'postfix postfix/mailname string %s\n' "$(hostname)" | debconf-set-selections

echo
echo "### 5) apt update"
apt-get update 2>&1 | tail -8

echo
echo "### 6) 候选版本"
apt-cache policy proxmox-ve ifupdown2 proxmox-default-kernel | sed -n '1,40p'

echo
echo "### 7) 预演:会【移除】哪些包(重点看 ifupdown / ntp)"
apt-get -s install -y proxmox-ve postfix open-iscsi chrony 2>&1 | sed -n '/REMOVED/,/^$/p'
echo "### 8) 预演:新装/升级包数量"
apt-get -s install -y proxmox-ve postfix open-iscsi chrony 2>&1 | grep -cE '^Inst' || true
echo "### 9) 预演:ifupdown 相关动作(会 ifupdown -> ifupdown2,属高风险步)"
apt-get -s install -y proxmox-ve postfix open-iscsi chrony 2>&1 | grep -E 'ifupdown' || echo "(无 ifupdown 动作)"
SCRIPT_EOF

chmod +x /root/kvm-bridge/02-pve-repo.sh
bash -n /root/kvm-bridge/02-pve-repo.sh && echo "02 语法 OK"
BASH
bash /root/kvm-bridge/02-pve-repo.sh

通过标准:=> 选用: 后面是 mirrors.tuna.tsinghua.edu.cn(或其他 >500 kB/s 的源);预演里能看到 Remv ifupdown。

阶段 3 · 安装 KVM + Proxmox VE(后台跑,不怕断线)

功能:真正开装。用 setsid nohup 脱离 SSH 会话,即使终端断了安装照样跑完。
约 563 个包,其中 ifupdown → ifupdown2、ntp → chrony(这是唯一会动到网络工具链的一步)。

本阶段网络零改动,SSH 不应断。

BASH
cat > /root/kvm-bridge/03-install-pve.sh <<'SCRIPT_EOF'
#!/bin/bash
# 阶段 3:安装 KVM + PVE(detached,脱离 SSH 会话)
set -u
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_SUSPEND=1

echo "### 当前 PVE 源"; cat /etc/apt/sources.list.d/pve-install-repo.list
echo "### 镜像源可达性"; apt-cache policy proxmox-ve | head -4

cat > /root/netguard/pve-install.sh <<'EOS'
#!/bin/bash
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_SUSPEND=1
echo "INSTALL_START $(date '+%F %T')"
apt-get -y \
  -o Dpkg::Options::=--force-confdef \
  -o Dpkg::Options::=--force-confold \
  -o Acquire::Retries=3 \
  install proxmox-ve postfix open-iscsi chrony
echo "APT_EXIT=$?"
echo "INSTALL_DONE $(date '+%F %T')"
EOS
chmod +x /root/netguard/pve-install.sh

rm -f /var/log/pve-install.log
# setsid + nohup + </dev/null:彻底脱离 SSH 会话,会话断开也不影响安装
setsid nohup /root/netguard/pve-install.sh > /var/log/pve-install.log 2>&1 < /dev/null &
sleep 25
echo "### 后台安装已启动,前 25 秒输出:"
tail -n 25 /var/log/pve-install.log
echo "### 进程:"
pgrep -af 'apt-get|dpkg' | head -5
SCRIPT_EOF

chmod +x /root/kvm-bridge/03-install-pve.sh
bash -n /root/kvm-bridge/03-install-pve.sh && echo "03 语法 OK"
BASH
bash /root/kvm-bridge/03-install-pve.sh

# 轮询进度(可反复执行,看到 INSTALL_DONE 就停)
for i in $(seq 1 60); do
  grep -q INSTALL_DONE /var/log/pve-install.log && break
  echo "[$(date '+%T')] 已下载: $(du -sh /var/cache/apt/archives 2>/dev/null | cut -f1)"
  sleep 20
done
grep -E 'INSTALL_START|INSTALL_DONE|APT_EXIT' /var/log/pve-install.log
grep -nE '^E:|dpkg: error|Errors were encountered' /var/log/pve-install.log | tail -10

通过标准:APT_EXIT=0,且 dpkg: error 一条都没有。此阶段网络零改动,SSH 不应断。

阶段 4 · 切换 br0 桥接(唯一高危步骤)

功能:全场唯一会改网络的一步,也是唯一可能断网的一步。脚本做的事 👇

  • 自动探测物理网卡(多网卡时优先跟随默认路由所在的那张,避免挑错)
  • 按 IP_MODE 生成 inet static(含 CIDR→点分掩码转换)或 inet dhcp
  • 强制 hwaddress ether 沿用原 MAC —— 这是云机不断网的核心
  • 新 interfaces 故意不 source interfaces.d/* —— 防止 cloud-init 复活
  • ifreload -a 增量应用,然后自检 75 秒,失败立即触发回滚(不等 5 分钟窗口)
BASH
cat > /root/kvm-bridge/04-switch-bridge.sh <<'SCRIPT_EOF'
#!/bin/bash
# 阶段 4:切换到 br0 桥接 【高危 · 目标是零断网】
# 按 /etc/netguard/config 的 IP_MODE 自适应生成 static 或 dhcp 配置
set -u
LOG=/root/netguard/switch.log
mkdir -p /root/netguard
exec >> "$LOG" 2>&1
TS=$(date '+%F %T')
echo
echo "================ SWITCH START $TS ================"

NG=/usr/local/sbin/netguard.sh

# 读阶段1 记录的场景参数
if [ -f /etc/netguard/config ]; then
    . /etc/netguard/config
fi
IP_MODE="${IP_MODE:-}"
ORIG_CIDR="${ORIG_CIDR:-}"
ORIG_GW="${ORIG_GW:-}"
ORIG_DNS="${ORIG_DNS:-}"
echo "preflight: IP_MODE=${IP_MODE:-<未设>} ORIG_CIDR=${ORIG_CIDR:-<未设>} ORIG_GW=${ORIG_GW:-<未设>}"

if ! command -v ifreload >/dev/null 2>&1; then
    echo "!! 没有 ifreload(需要 ifupdown2)—— 无法做零中断增量切换,中止。"
    echo "!! 请先跑阶段 2+3 安装 Proxmox VE(会自动把 ifupdown 换成 ifupdown2)。"
    exit 1
fi

# 自动探测物理网卡
DEV=""
for d in /sys/class/net/*; do
    d=${d##*/}
    case "$d" in lo|br*|vmbr*|tap*|veth*|fw*|bond*) continue ;; esac
    [ "$(cat "/sys/class/net/$d/type" 2>/dev/null)" = 1 ] && { DEV=$d; break; }
done
[ -n "$DEV" ] || { echo "!! 未找到物理网卡,中止"; exit 1; }

# 多网卡时以"承载默认路由的那张"为准,避免挑错
DEV_R=$(ip -o -4 route show default | awk '{print $5}' | head -1)
if [ -n "$DEV_R" ] && [ -e "/sys/class/net/$DEV_R" ]; then
    case "$DEV_R" in lo|br*|vmbr*|tap*|veth*|fw*|bond*) ;; *) DEV="$DEV_R" ;; esac
fi

MAC=$(cat "/sys/class/net/$DEV/address")
CUR=$(ip -o -4 addr show dev "$DEV" | awk '{print $4}' | head -1)
CURGW=$(ip -o -4 route show default | awk '{print $3}' | head -1)
echo "preflight: dev=$DEV mac=$MAC addr=$CUR gw=$CURGW"
echo "preflight: route => $(ip route | tr '\n' '|')"
echo "preflight: netguard => $($NG status | tr '\n' ' ')"

[ -n "$CUR" ] && ORIG_CIDR="$CUR"
[ -n "$CURGW" ] && ORIG_GW="$CURGW"

cidr2mask() {
    local p=$1 o i m=""
    for i in 0 1 2 3; do
        if   [ "$p" -ge 8 ]; then o=255; p=$((p-8))
        elif [ "$p" -le 0 ]; then o=0
        else o=$((256 - (1 << (8-p)) )); p=0
        fi
        m="$m.$o"
    done
    echo "${m#.}"
}

$NG arm

# 停掉会和我们抢网卡的 DHCP 客户端
pkill -f dhclient 2>/dev/null
sleep 2
echo "dhclient after kill: $(pgrep -c -f dhclient 2>/dev/null || echo 0)"
if systemctl is-active --quiet systemd-networkd 2>/dev/null; then
    echo "!! 注意:systemd-networkd 仍在运行,可能覆盖配置。若切换失败优先怀疑它。"
fi

# 写桥接配置(故意不 source interfaces.d/*)
cp -a /etc/network/interfaces /root/netguard/interfaces.before-bridge
mv -f /etc/network/interfaces.d/50-cloud-init /root/netguard/50-cloud-init.disabled 2>/dev/null

DNS_LIST="${ORIG_DNS:-119.29.29.29 223.5.5.5}"

{
  echo "# Managed by netguard bridge switch $TS   IP_MODE=${IP_MODE:-auto}"
  echo "# 故意不 source /etc/network/interfaces.d/* —— 防止 cloud-init 重新生成的配置被读取"
  echo "auto lo"
  echo "iface lo inet loopback"
  echo
  echo "auto $DEV"
  echo "iface $DEV inet manual"
  echo
  echo "auto br0"
  if [ "$IP_MODE" = "static" ]; then
      ADDR_ONLY="${ORIG_CIDR%%/*}"
      PREFIX="${ORIG_CIDR##*/}"
      if [ -z "$ORIG_CIDR" ] || [ "$PREFIX" = "$ORIG_CIDR" ]; then
          echo "!! 无法解析静态地址(ORIG_CIDR=$ORIG_CIDR),中止"
          exit 1
      fi
      echo "iface br0 inet static"
      echo "    address $ADDR_ONLY"
      echo "    netmask $(cidr2mask "$PREFIX")"
      [ -n "$ORIG_GW" ] && echo "    gateway $ORIG_GW"
  else
      echo "iface br0 inet dhcp"
  fi
  echo "    bridge-ports $DEV"
  echo "    bridge-stp off"
  echo "    bridge-fd 0"
  echo "    hwaddress ether $MAC"
  echo "    dns-nameservers $DNS_LIST"
} > /etc/network/interfaces

echo "--- 新配置 ---"
cat /etc/network/interfaces

echo "--- ifreload -a @ $(date '+%T')"
ifreload -a
RC=$?
echo "--- ifreload rc=$RC @ $(date '+%T')"

# 自检:最多 75 秒
OK=no
for i in $(seq 1 15); do
    sleep 5
    if [ "$($NG check 2>/dev/null)" = "OK" ]; then OK=yes; break; fi
done
echo "--- 自检结果: $OK @ $(date '+%T')"

{
  echo "--- ip -br addr";  ip -br addr
  echo "--- ip route";     ip route
  echo "--- bridge link";  bridge link 2>/dev/null || ip -o link show master br0
  echo "--- resolv.conf";  cat /etc/resolv.conf
  echo "--- 出口 IP"
  curl -s -m 8 https://ifconfig.me 2>/dev/null; echo
  curl -s -m 5 http://169.254.0.23/latest/meta-data/instance-id 2>/dev/null; echo
}

if [ "$OK" = "yes" ]; then
    IP=$(ip -o -4 addr show dev br0 | awk '{print $4}')
    echo "SUCCESS br0=$IP"
    HOSTN=$(hostname)
    sed -i -E "s|^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+([[:space:]]+.*${HOSTN}.*)\$|${IP%%/*}\1|" /etc/hosts 2>/dev/null
    grep -E "$HOSTN" /etc/hosts || true
else
    echo "FAILED —— 立即触发 netguard 回滚(不等 5 分钟窗口)"
    $NG rollback
    sleep 5
    echo "回滚后: $($NG check)"
fi
echo "================ SWITCH END $(date '+%F %T') ================"
SCRIPT_EOF

chmod +x /root/kvm-bridge/04-switch-bridge.sh
bash -n /root/kvm-bridge/04-switch-bridge.sh && echo "04 语法 OK"
BASH
/usr/local/sbin/netguard.sh status

# detached 执行:即使 SSH 断了,脚本也会跑完并自己回滚
setsid nohup bash /root/kvm-bridge/04-switch-bridge.sh >/dev/null 2>&1 </dev/null &
sleep 20
tail -40 /root/netguard/switch.log

通过标准:日志里 SUCCESS br0=<你的原 IP>,且 ifreload rc=0。

阶段 5 · 验证

功能:端到端复核六个区块 —— 网络、外部连通性、Proxmox、配置卫生、看门狗、磁盘。
重点核对 br0 的 MAC 是否等于物理网卡原 MAC、出口公网 IP 是否没变。

BASH
echo "=================== 1. 网络 ==================="
ip -br addr
echo "--- 路由 ---"; ip route
echo "--- 桥接关系 ---"; bridge link 2>/dev/null; ip -o link show master br0 2>/dev/null
echo "--- MAC 核对(br0 应等于物理网卡原 MAC)---"
PHYS=$(ip -o -4 route show default | awk '{print $5}' | head -1)
for d in $PHYS br0; do
  [ -e "/sys/class/net/$d" ] && echo "  $d mac=$(cat /sys/class/net/$d/address)"
done

echo
echo "=================== 2. 外部连通性 ==================="
echo -n "出口公网 IP : "; curl -s -m 8 https://ifconfig.me; echo
echo -n "DNS 解析    : "; getent hosts mirrors.tuna.tsinghua.edu.cn >/dev/null 2>&1 && echo OK || echo FAIL
echo -n "看门狗单测  : "; /usr/local/sbin/netguard.sh check

echo
echo "=================== 3. Proxmox ==================="
pveversion 2>&1 | head -12
echo "--- 8006 监听 ---"; ss -tlnp 2>/dev/null | grep -E '8006|3128' || echo "(未监听)"
echo "--- 服务状态 ---"
for s in pveproxy pvedaemon pvestatd pve-cluster pve-firewall; do
  printf '  %-14s %s\n' "$s" "$(systemctl is-active $s 2>&1)"
done
echo "--- 主机名解析(PVE 要求非回环)---"
echo "  hostname -f => $(hostname -f)"
echo "  hostname -i => $(hostname -i)"

echo
echo "=================== 4. 网络配置卫生 ==================="
echo "--- interfaces.d 内容(应为空/无 cloud-init)---"
ls -A /etc/network/interfaces.d/ 2>/dev/null || echo "(空)"
echo "--- 网络管理工具 ---"
dpkg -l 2>/dev/null | grep -E 'ifupdown|bridge-utils' | awk '{print "  "$2, $3}'

echo
echo "=================== 5. 看门狗 ==================="
/usr/local/sbin/netguard.sh status
echo "--- 最近日志(应无 WARN/ERROR)---"
tail -n 15 /var/log/netguard.log 2>/dev/null

echo
echo "=================== 6. 磁盘 ==================="
df -h / /boot 2>/dev/null

通过标准:br0 与物理网卡 MAC 一致、出口 IP 不变、pveversion 有输出、8006 在监听、看门狗日志无 WARN/ERROR。

稳定观察后解除武装

功能:确认长期稳定后关掉自动回滚。想保留"重启自动重新武装"的保护,只做 disarm、别删 /etc/netguard/AUTOARM。

BASH
# 建议至少观察 15 分钟,确认无异常再执行
/usr/local/sbin/netguard.sh disarm
rm -f /etc/netguard/AUTOARM
/usr/local/sbin/netguard.sh status

附 · 出事救援

功能:真出事时的处置命令。优先级:看状态日志 → 手动立即回滚 → 带外(IPMI/VNC)手工恢复。

实时看门狗状态与日志

BASH
/usr/local/sbin/netguard.sh status
tail -50 /var/log/netguard.log
tail -50 /root/netguard/switch.log
cat /var/lib/netguard/forensics.log 2>/dev/null | tail -60

手动立即回滚(不等 5 分钟窗口)

BASH
/usr/local/sbin/netguard.sh rollback

从 IPMI / 控制台手工恢复(自动救援全失败时)

BASH
# 1) 恢复网络配置为原始状态
cp -a /root/netguard/backup-*/network/interfaces /etc/network/interfaces

# 2) 拆掉桥,让物理网卡自己起来
ip addr flush dev br0 2>/dev/null
ip link set br0 down 2>/dev/null
ip link del br0 2>/dev/null

# 3) 重启网络
systemctl restart networking

# 4) 验证
ip -br addr; ip route; ping -c2 114.114.114.114

静态机器临时手工写回(不进 ifupdown,最快恢复连通)

BASH
DEV=$(ip -o -4 route show default | awk '{print $5}' | head -1)
ip link set $DEV up
ip addr flush dev $DEV
ip addr add <CIDR> dev $DEV          # 例如 203.0.113.5/28
ip route replace default via <网关> dev $DEV
echo "nameserver 223.5.5.5" > /etc/resolv.conf
ip -br addr; ping -c2 114.114.114.114

关掉看门狗 / 还原

BASH
# 关保护
/usr/local/sbin/netguard.sh disarm
rm -f /etc/netguard/AUTOARM
systemctl disable --now netguard.service

# 还原到未装 PVE 前(谨慎,确认后再做)
# cp -a /root/netguard/backup-*/network/interfaces /etc/network/interfaces
# systemctl restart networking

# 卸载 PVE(激进操作)
# apt-get -y purge proxmox-ve postfix open-iscsi chrony
# apt-get -y install ifupdown && apt-get -y purge ifupdown2
# apt-get -y autoremove

📌 一页速记

功能:下次直接照这段跑,严格按序别跳步。

BASH
# 阶段顺序(严格按序,别跳)
bash /root/kvm-bridge/01-backup-harden.sh   # 备份+加固+武装看门狗(不动网络)
bash /root/kvm-bridge/02-pve-repo.sh        # 测速选源+预演
bash /root/kvm-bridge/03-install-pve.sh     # 后台装 PVE
bash /root/kvm-bridge/04-switch-bridge.sh   # 切桥接(detached 启动)
# 阶段5 直接粘贴上面的验证命令

# 看门狗
/usr/local/sbin/netguard.sh status|check|arm|disarm|rollback

# 唯一安全的改网命令
ifreload -a

# 高危动作前的底线
# 1) netguard status 显示 armed: YES
# 2) 带外通道(VNC / IPMI)已实测可用
# 3) 已抄下原 CIDR / 网关 / DNS

⚠️ 最后三句提醒

  1. 动手前先确认带外通道能用 —— 云主机用控制台 VNC,物理机用 IPMI/iDRAC/iLO。这是所有兜底的底牌。
  2. 不要照搬任何 IP / MAC / 网关 —— 换机器前先跑阶段 0 重新核对。
  3. 物理机的 static 分支未经真机实测 —— 第一次上物理机,挑一台有 IPMI 的先验证。
祝顺利,别断网 😀

💬 评论 (0)

今日还可评论 3 条

还没有评论,来抢沙发吧。