南风大叔

PVE + ImmortalWrt 专用出口网关:PVE 一键脚本版

PVE + ImmortalWrt 专用出口网关:PVE 一键脚本版

本文定位: 在已经理解并完成《Part 1|手动教程》的前提下, 将所有“可以在 PVE 层完成的操作”自动化,并由 PVE 统一触发 VM 内的出口切换。

明确边界

  • ✅ 自动化:PVE 网络、VM 硬件、网卡、VM 内路由开关
  • ❌ 不自动化:ImmortalWrt 内的 Passwall 节点 / 订阅 / 规则

自动化目标与设计思路

目标

设计思路

  1. PVE 只做基础设施:网桥、VM 硬件、虚拟网卡。
  2. Guest OS 的事情仍在 Guest OS 做:路由切换不硬塞进宿主机。
  3. “像在 PVE 执行”即可:通过 SSH 远程下发脚本实现统一入口。
  4. 幂等优先:脚本可重复执行,不破坏现有环境。

脚本一览

脚本名 作用 执行位置
pve-egress-setup.sh 基础设施搭建 PVE 宿主机
pve-egress-ctl.sh VM 出口控制 PVE 宿主机
egress-toggle 实际切路由 VM 内(由 PVE 下发)

脚本一:pve-egress-setup.sh

用途: 一次性准备 PVE 侧基础设施。

功能

使用方式

# 示例:为 VM 120 / 121 添加专用出口网卡
TARGET_VMIDS="120 121" IWT_VMID=101 ./pve-egress-setup.sh

脚本正文

#!/usr/bin/env bash
set -euo pipefail

# ===== 基本参数 =====
VMBR_ACCEL="vmbr1"
VMBR_LAN="vmbr0"

# ImmortalWrt VM
IWT_VMID="${IWT_VMID:-101}"
IWT_NAME="immortalwrt-egress"
IWT_CORES=1
IWT_MEM=512
IWT_DISK_GB=1
IWT_STORAGE="local-lvm"

# 目标 VM(需要加 net1 的 VMID)
TARGET_VMIDS=(${TARGET_VMIDS:-""})

need_root() { [[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }; }

ensure_vmbr1() {
  ip link show "$VMBR_ACCEL" >/dev/null 2>&1 && return
  cat >> /etc/network/interfaces <<EOF

auto $VMBR_ACCEL
iface $VMBR_ACCEL inet manual
    bridge-ports none
    bridge-stp off
    bridge-fd 0
EOF
  ifreload -a
}

ensure_iwt_vm() {
  qm status "$IWT_VMID" >/dev/null 2>&1 && return
  qm create "$IWT_VMID" \
    --name "$IWT_NAME" \
    --cores "$IWT_CORES" \
    --memory "$IWT_MEM" \
    --ostype l26 \
    --scsihw virtio-scsi-pci \
    --net0 virtio,bridge=$VMBR_ACCEL \
    --net1 virtio,bridge=$VMBR_LAN \
    --agent 1
  qm set "$IWT_VMID" --scsi0 "$IWT_STORAGE:$IWT_DISK_GB" --boot order=scsi0
}

add_net1() {
  local vmid="$1"
  qm config "$vmid" | grep -q '^net1:' && return
  qm set "$vmid" --net1 virtio,bridge=$VMBR_ACCEL
}

need_root
ensure_vmbr1
ensure_iwt_vm

for v in "${TARGET_VMIDS[@]}"; do
  add_net1 "$v"
done

echo "[OK] PVE egress infrastructure ready"

脚本二:pve-egress-ctl.sh

用途: 从 PVE 宿主机统一控制 VM 是否启用“专用出口”。

支持动作

使用方式

# 安装切换脚本
HOSTS="192.168.31.6" ./pve-egress-ctl.sh install

# 启用专用出口
HOSTS="192.168.31.6" ./pve-egress-ctl.sh on

# 关闭专用出口
HOSTS="192.168.31.6" ./pve-egress-ctl.sh off

脚本正文

#!/usr/bin/env bash
set -euo pipefail

HOSTS=(${HOSTS:-""})
SSH_USER=root
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"

LAN_IF=ens18
ACC_IF=ens19
LAN_GW=192.168.31.1
ACC_GW=10.10.10.1
LAN_NET=192.168.31.0/24

ACTION=${1:-status}

remote_install() {
ssh $SSH_OPTS $SSH_USER@$1 <<EOF
cat >/usr/local/bin/egress-toggle <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail

LAN_IF=$LAN_IF
ACC_IF=$ACC_IF
LAN_GW=$LAN_GW
ACC_GW=$ACC_GW
LAN_NET=$LAN_NET

case "\${1:-status}" in
  on)
    ip route del default via $LAN_GW dev $LAN_IF 2>/dev/null || true
    ip route replace default via $ACC_GW dev $ACC_IF metric 100
    ip route replace $LAN_NET dev $LAN_IF metric 50
    ;;
  off)
    ip route del default via $ACC_GW dev $ACC_IF 2>/dev/null || true
    ip route replace default via $LAN_GW dev $LAN_IF metric 100
    ip route replace $LAN_NET dev $LAN_IF metric 50
    ;;
  status)
    ip route
    ;;
esac
SCRIPT
chmod +x /usr/local/bin/egress-toggle
EOF
}

remote_exec() {
  ssh $SSH_OPTS $SSH_USER@$1 "egress-toggle $ACTION"
}

for h in "${HOSTS[@]}"; do
  if [[ "$ACTION" == "install" ]]; then
    remote_install "$h"
  else
    remote_exec "$h"
  fi
done

使用建议


为什么不自动化 Passwall?

本方案刻意把“网络基础设施”和“策略内容”解耦。


总结

这套自动化方案的本质是:

把“出口选择”当成一项可编排的虚拟基础设施能力。

(完)

#PVE #ImmortalWrt #Homelab #Automation #Shell