PVE + ImmortalWrt 专用出口网关:PVE 一键脚本版
PVE + ImmortalWrt 专用出口网关:PVE 一键脚本版
本文定位: 在已经理解并完成《Part 1|手动教程》的前提下, 将所有“可以在 PVE 层完成的操作”自动化,并由 PVE 统一触发 VM 内的出口切换。
明确边界:
- ✅ 自动化:PVE 网络、VM 硬件、网卡、VM 内路由开关
- ❌ 不自动化:ImmortalWrt 内的 Passwall 节点 / 订阅 / 规则
自动化目标与设计思路
目标
-
一条命令完成:
- 创建
vmbr1 - 创建 / 准备 ImmortalWrt 网关 VM(双网口)
- 给指定 VM 添加第二张网卡(接入
vmbr1)
- 创建
-
从 PVE 宿主机 统一控制:
- VM 是否启用“专用出口”
on / off / status
设计思路
- PVE 只做基础设施:网桥、VM 硬件、虚拟网卡。
- Guest OS 的事情仍在 Guest OS 做:路由切换不硬塞进宿主机。
- “像在 PVE 执行”即可:通过 SSH 远程下发脚本实现统一入口。
- 幂等优先:脚本可重复执行,不破坏现有环境。
脚本一览
| 脚本名 | 作用 | 执行位置 |
|---|---|---|
pve-egress-setup.sh |
基础设施搭建 | PVE 宿主机 |
pve-egress-ctl.sh |
VM 出口控制 | PVE 宿主机 |
egress-toggle |
实际切路由 | VM 内(由 PVE 下发) |
脚本一:pve-egress-setup.sh
用途: 一次性准备 PVE 侧基础设施。
功能
- 检查并创建
vmbr1 - 创建 ImmortalWrt VM(仅硬件层面)
- 给指定 VM 添加
net1 → vmbr1
使用方式
# 示例:为 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 是否启用“专用出口”。
支持动作
install:向 VM 下发egress-toggleon:启用专用出口off:恢复普通出口status:查看路由状态
使用方式
# 安装切换脚本
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
使用建议
- 首次部署:先跑 Part 1 手动教程,确认链路正确
- 长期使用:日常只用
pve-egress-ctl.sh on/off - 容器较多时:建议把“需要专用出口”的服务拆到独立 VM
为什么不自动化 Passwall?
- 节点/订阅高频变动
- 规则因人而异
- 自动化反而增加不可控风险
本方案刻意把“网络基础设施”和“策略内容”解耦。
总结
这套自动化方案的本质是:
把“出口选择”当成一项可编排的虚拟基础设施能力。
- 对 PVE:清晰、干净、无黑魔法
- 对 VM:可开可关、可回退
- 对未来:可扩展到更多出口 / 更多 VM
(完)
#PVE #ImmortalWrt #Homelab #Automation #Shell