很久之前买了一个山特850的UPS,一直接在威联通的NAS上

自己也就仅限于控制自己的NAS断电自关机了,虽说IP里面写着通知PVE母鸡关机,但其实完全没在PVE上配置
晚上想到自己过段时间要去外地,心血来潮一次在pve里配置好了NUT client
话不多说,立马开始,先在PVE里安装一下nut-client
安装
apt update apt install nut-client
配置NUT客户端连接到威联通NAS
先编辑/etc/nut/nut.conf
设置以下字段
MODE=netclient
继续编辑/etc/nut/upsmon.conf,为了方便维护,我把所有相关的脚本全部放到了/upssh文件夹下
# 监控威联通的UPS MONITOR qnapups@<威联通IP> 1 upsmon <密码> slave # 设置关机命令 SHUTDOWNCMD "/upssh/pve-shutdown.sh" # 断电后等待时间(秒) FINALDELAY 120 # 电池低电量时的延迟(秒) HOSTSYNC 15 # 设置通知命令 NOTIFYCMD /upssh/upssched
检查NUT连接
root@pve:~# upsc qnapups@192.168.1.5 #配置的UPS服务器地址 Init SSL without certificate database battery.charge: 100 battery.charge.low: 20 battery.runtime: 1296 battery.type: PbAc device.mfr: EATON device.model: SANTAK TG-BOX 850 device.serial: Blank device.type: ups driver.name: usbhid-ups driver.parameter.pollfreq: 30 driver.parameter.pollinterval: 2 driver.parameter.port: /dev/ttyS1 driver.parameter.synchronous: no driver.version: 2.7.4 driver.version.data: MGE HID 1.39 driver.version.internal: 0.41 input.transfer.high: 264 input.transfer.low: 184 outlet.1.desc: PowerShare Outlet 1 outlet.1.id: 1 outlet.1.status: on outlet.1.switchable: no outlet.desc: Main Outlet outlet.id: 0 outlet.switchable: yes output.frequency.nominal: 50 output.voltage: 230.0 output.voltage.nominal: 220 ups.beeper.status: enabled ups.delay.shutdown: 20 ups.delay.start: 30 ups.firmware: 02.08.0010 ups.load: 23 ups.mfr: EATON ups.model: SANTAK TG-BOX 850 ups.power.nominal: 850 ups.productid: ffff ups.serial: Blank ups.status: OL ups.timer.shutdown: 0 ups.timer.start: 0 ups.type: offline / line interactive ups.vendorid: 0463
配置自定义的关机脚本
PVE作为虚拟化平台,我们自然不能让他一下子就关机,而不考虑虚拟机的问题。所以我们的构想是,关机脚本需要先把所有虚拟机休眠(挂起到硬盘,方便恢复),等待全部休眠后再关闭PVE,这样会减少数据丢失的可能
在查过PVE的官方文档后,发现将虚拟机休眠到硬盘的指令是
qm suspend <vmid> --todisk
先创建一个/upssh/pve-shutdown.sh,内容如下(想要完美脚本的直接跳到最后一步)
#!/bin/bash
# 记录日志
logger "UPS: 开始休眠所有虚拟机到磁盘..."
# 获取所有运行中的VM
RUNNING_VMS=$(qm list | grep running | awk '{print $1}')
RUNNING_CTS=$(pct list | grep running | awk '{print $1}')
# 休眠所有运行中的VM到磁盘
for vmid in $RUNNING_VMS; do
echo "正在休眠VM $vmid 到磁盘..."
logger "UPS: 正在休眠VM $vmid 到磁盘"
qm suspend $vmid --todisk
done
# 停止所有运行中的LXC容器
for ctid in $RUNNING_CTS; do
echo "正在停止容器 $ctid..."
logger "UPS: 正在停止容器 $ctid"
pct stop $ctid
done
# 等待所有VM休眠完成
echo "等待虚拟机休眠到磁盘..."
while [ $(qm list | grep running | wc -l) -gt 0 ]; do
sleep 5
echo "仍有虚拟机在运行,继续等待..."
done
logger "UPS: 所有虚拟机已休眠到磁盘"
echo "测试完成:所有虚拟机已休眠到磁盘,状态已保存"
# 测试阶段先注释掉
# /sbin/shutdown -h now然后给这个脚本相应的权限
chmod +x /upssh/pve-shutdown.sh
配置延迟关闭时间
打开/etc/nut/upssched.conf
修改中间的CMDSCRIPT为
CMDSCRIPT /upssh/upssched-cmd
在最后添加
PIPEFN /run/nut/upssched/upssched.pipe LOCKFN /run/nut/upssched/upssched.lock AT ONBATT * START-TIMER shutdown 300 AT ONLINE * CANCEL-TIMER shutdown
接下来创建/upssh/upssched-cmd,并写入
#!/bin/bash case $1 in shutdown) logger "UPS: 电池供电已5分钟,开始关机流程..." /upssh/pve-shutdown.sh ;; *) logger "UPS: 未知命令 $1" ;; esac
同样设置权限
chmod +x /upssh/upssched-cmd
重启服务
systemctl restart nut-client systemctl enable nut-client systemctl restart nut-monitor systemctl enable nut-monitor
第一次执行出错
当我满怀信心的开始测试SH脚本能不能正常使用的时候,BUG出现了
root@pve:~# sh /upssh/pve-shutdown.sh
/upssh/pve-shutdown.sh: line 4: $'\r': command not found
/upssh/pve-shutdown.sh: line 6: syntax error near unexpected token $'do\r''
'upssh/pve-shutdown.sh: line 6: for vmid in $(qm list | grep running | awk '{print $1}'); do居然出现了Windows格式的换行符,这时候就需要使用dos2unix了
# 安装dos2unix apt install dos2unix # 转换文件格式 dos2unix /upssh/pve-shutdown.sh dos2unix /upssh/upssched-cmd # 再次运行测试 sh /upssh/pve-shutdown.sh
第二次执行出错
我寻思现在应该没啥问题了,结果继续出问题了
我有一个虚拟机使用了硬盘直通,直接无法休眠了
root@pve:~# sh /upssh/pve-shutdown.sh 正在休眠VM 100 到磁盘... cannot suspend VM to disk due to passed-through PCI device(s), which lack the possibility to save/restore their internal state 正在休眠VM 101 到磁盘... Formatting '/mnt/ssd-1024g/images/101/vm-101-state-suspend-2025-08-18.raw', fmt=raw size=9114222592 preallocation=off State saved, quitting 正在休眠VM 102 到磁盘... Formatting '/mnt/ssd-1024g/images/102/vm-102-state-suspend-2025-08-18.raw', fmt=raw size=13409189888 preallocation=off State saved, quitting 正在休眠VM 103 到磁盘... Formatting '/mnt/ssd-1024g/images/103/vm-103-state-suspend-2025-08-18.raw', fmt=raw size=4819255296 preallocation=off State saved, quitting 等待虚拟机休眠到磁盘... 仍有虚拟机在运行,继续等待...
该死的PVE居然不支持让直通虚拟机休眠
于是乎改变策略,让有PCI直通的虚拟机直接执行关机指令,懒得改脚本了,直接丢给Claude重新写一个
#!/bin/bash
# 记录日志
logger "UPS: 开始处理所有虚拟机..."
# 获取所有运行中的VM和容器
RUNNING_VMS=$(qm list | grep running | awk '{print $1}')
RUNNING_CTS=$(pct list | grep running | awk '{print $1}')
# 处理每个运行中的VM
for vmid in $RUNNING_VMS; do
echo "正在检查VM $vmid..."
# 检查是否有PCI直通设备
if qm config $vmid | grep -q "^hostpci"; then
echo "VM $vmid 有PCI直通设备,执行关机..."
logger "UPS: VM $vmid 有PCI直通,执行关机"
qm shutdown $vmid --timeout 60 &
else
echo "VM $vmid 正在休眠到磁盘..."
logger "UPS: VM $vmid 休眠到磁盘"
qm suspend $vmid --todisk &
fi
done
# 等待处理完成
echo "等待所有操作完成..."
MAX_WAIT=180
WAITED=0
while [ $(qm list | grep running | wc -l) -gt 0 ] && [ $WAITED -lt $MAX_WAIT ]; do
sleep 10
WAITED=$((WAITED + 10))
RUNNING=$(qm list | grep running | awk '{print $1}' | tr '\n' ' ')
if [ -n "$RUNNING" ]; then
echo "仍在运行: $RUNNING (已等待 ${WAITED}秒)"
fi
done
# 强制停止超时的VM
if [ $(qm list | grep running | wc -l) -gt 0 ]; then
echo "超时!强制停止剩余的虚拟机..."
for vmid in $(qm list | grep running | awk '{print $1}'); do
echo "强制停止VM $vmid"
logger "UPS: 强制停止VM $vmid"
qm stop $vmid
done
fi
# 停止所有容器
for ctid in $RUNNING_CTS; do
echo "正在停止容器 $ctid..."
logger "UPS: 正在停止容器 $ctid"
pct stop $ctid
done
logger "UPS: 所有虚拟机已处理完成"
echo "完成:所有虚拟机已处理"
# 生产环境取消注释
# /sbin/shutdown -h nowroot@pve:~# sh /upssh/pve-shutdown.sh 正在检查VM 100... VM 100 有PCI直通设备,执行关机... 正在检查VM 101... VM 101 正在休眠到磁盘... 正在检查VM 102... Formatting '/mnt/ssd-1024g/images/101/vm-101-state-suspend-2025-08-18.raw', fmt=raw size=9114222592 preallocation=off VM 102 正在休眠到磁盘... 正在检查VM 103... VM 103 正在休眠到磁盘... 等待所有操作完成... Formatting '/mnt/ssd-1024g/images/102/vm-102-state-suspend-2025-08-18.raw', fmt=raw size=13409189888 preallocation=off Formatting '/mnt/ssd-1024g/images/103/vm-103-state-suspend-2025-08-18.raw', fmt=raw size=4819255296 preallocation=off State saved, quitting State saved, quitting State saved, quitting 仍在运行: 100 (已等待 10秒) 完成:所有虚拟机已处理
先在已经完全没问题了,就可以把脚本最后一行的关机指令取消注释了,十分完美
评论