UPS/NUT(Network UPS Tool)

NUT

NUT (Network UPS Tool)，集成了一系列方便操作、通知的 UPS 工具。

Nut 主要包含三个核心服务:

nut-driver: 这个服务负责通过特定放驱动来与 UPS 进行通信

nut-server: 该服务利用 nut-dirver 沟通 UPS, 并将 UPS 状态通过网络服务发布

nut-monitor (nut-client): 该服务连接 nut-server, 根据 UPS 状态做出特定响应

注意: nut-client 实际上是一个 systemd 软连接文件，本质上还是 nut-monitor.

Copy
┌─────────────┐ ┌────────────┐
┌──── │ nut-monitor │ ───────────────► │ nut-server │
│ └─────────────┘ └────────────┘
│
│ │
│ │
▼ ▼
┌─────────────┐ ┌────────────┐
│ upssched │ │ nut-driver │
└─────────────┘ └────────────┘

│ │
│ │
│ │
▼ ▼
┌────────────────┐ ┌─────────────┐
│ user scripts │ │ UPS │
└────────────────┘ └─────────────┘

安装 NUT

pve 控制台直接安装

Copy
apt install -y nut

连接 UPS，通过命令：

Copy
# nut-scanner -U
SNMP library not found. SNMP search disabled.
Neon library not found. XML search disabled.
IPMI library not found. IPMI search disabled.
Scanning USB bus.
[nutdev1]
driver = "blazer_usb"
port = "auto"
vendorid = "0665"
productid = "5161"
product = "USB to Serial"
vendor = "INNO TECH"
bus = "001"

or

Copy
/bin/upsc nutdev1@localhost
Init SSL without certificate database
battery.charge: 100
battery.voltage: 13.50
battery.voltage.high: 13.00
battery.voltage.low: 10.40
battery.voltage.nominal: 12.0
...

查询 UPS 的状态。

NUT Server 配置

在 Nut 安装完成后会自动在 /etc/nut 目录生成配置文件，接下来的配置工作主要是调整该目录下的各种配置文件.

nut.conf

该配置主要定义 nut 的运行模式，只有一个配置字段 MODE=xxxx, 该配置可选值及含义如下:

none: Nut 未配置或使用外部系统启动 Nut 服务，可以理解为 "啥也不干".

standalone: 独立模式，一般在只有一个 UPS 且只负责本地系统 (不提供网络服务) 的情况下使用.

netserver: 跟独立模式类似，会启动 driver、upsd 和 upsmon 服务，不同之处是可以提供网络服务，其他机器上的 nut-monitor 可以通过网络来连接 Nut Server.

netclient: 仅客户端模式，只启动 nut-monitor, 用于连接远程的 Nut 服务.

这里我使用的 SANTAK TG-BOX 850 通过 USB 连接我的 pve，pve 安装了 NUT 工具，所以 pve 当作 server，其他的查询 server 的状态就行。

Copy
# cat /etc/nut/nut.conf
MODE=netserver

ups.conf

该配置用于定义 nut-driver 如何连接到物理 UPS, 该配置文件的饿配置格式如下:

Copy
# 需要注意的是, 如果想要群晖或者 QNAP 能通过网络连接 UPS,
# 那么 UPS 的名称是有特殊要求的(群晖必须起名叫 ups, QNAP 没有测试过可能叫 qnapups);
[ups]
driver = usbhid-ups
port = auto
desc = "TG-BOX 850"

upsd.conf

该配置文件用于控制 nut-server 的网络服务，例如监听端口、最大连接数、证书配置等；由于我是在内网使用，所以只需要配置网络监听，其他参数保持默认即可:

Copy
LISTEN 0.0.0.0 3493

upsd.users

upsd.users 配置文件用于定义通过网络连接到 nut-server 的用户名和密码，该配置样例如下:

Copy
[monuser]
password = secret
upsmon master

关于 群晖 和 QNAP 用户：如果期望这两个 NAS 可以直接连接到 Nut Server, 可以确认的是群晖需要保证存在用户名为 monuser 密码为 secret 的用户；QNAP 我没有验证过，网上查询到的结果是需要保证存在用户名为 admin 密码为 123456 的用户.

NUT 监控配置

相较于基本配置来说，Nut 核心处理在于如何做好监控配置；Nut 对于监控策略支持大致有两种:

1、直接由upsmon.conf配置，任何事件直接由用户指定的脚本负责处理，没有定时器等高级特性

2、在 upsmon.conf中配置执行脚本为upssched, 任何事件先由upssched处理，借助于 upssched 可以实现一些高级功能，比如选择性触发关机等

upsmon.conf

该配置主要用于配置 nut-monitor 如何监控 UPS, 同时定义 UPS 出现哪些事件要进行怎样的处理动作，下面详细解释一下核心配置.

MONITOR 指令

MONITOR 指令用于定义要监控 UPS 的连接地址， 其格式如下:

Copy
MONITOR <system> <powervalue> <username> <password> ("master"|"slave")

<system>: nut-server 链接地址，格式为 "UPS 名称" + "@" + "nut-server 地址", 例如myups@192.168.1.2

<powervalue>: UPS 数量，大多数情况你只有一个 UPS 电源，所以写 1 就行

<username>/<password>: 在upsd.users中定义的用户名和密码

`master/slave:master表示该系统将最后关闭，让从属系统先关闭；slave表示该系统立即关闭

以下为我的配置样例:

Copy
MONITOR ups@localhost 1 monuser secret master

SHUTDOWNCMD

SHUTDOWNCMD 指令用于定义在 UPS 电量不足或者需要主动关机时的关机命令，建议填写完整路径.

Copy
SHUTDOWNCMD "/usr/sbin/poweroff"

NOTIFYCMD

NOTIFYCMD 指令是一个非常重要的指令，该指令用于配置在发生特定事件 (如市电中断、UPS 处于低电量等) 时， nut-monitor 所执行的命令. 简单的说就是 NOTIFYCMD 定义了具体执行命令，可以直接在此处配置一个自己编写的脚本，当 UPS 有事件发生时此脚本都会被调用.

NOTIFYCMD 指令大致有两种配置方式，一种是配置成自己的脚本，脚本需要有可执行权限，脚本内可以通过 NOTIFY 环境变量获取事件类型，然后自己进行处理. 这种方式有点 "简单粗暴" 的意思，可定制化程度完全依赖于你的脚本怎么写。具体可以使用哪些变量请自行测试，因为版本不同可能环境变量名称也不同.

还有一种方式是将 NOTIFYCMD 配置为执行内置的 upssched 命令；upssched 是 Nut 提供的一个带有特定策略的调度程序；简而言之就是基于常用的功能进行了抽象，upssched 有自己单独配置，可以实现 "如果市电在 180s 内恢复则不进行关机" 的这种高级调度策略.

这里我选择使用第二种方式，

Copy
NOTIFYCMD /usr/sbin/upssched

NOTIFYFLAG

NOTIFYFLAG 同样是关键配置，需要与 NOTIFYCMD 配合使用；NOTIFYFLAG 指令负责指定一系列的 UPS 事件应该触发何种操作，该指令格式如下:

Copy
NOTIFYFLAG <notify type> <flag>[+<flag>][+<flag>] ...

其中 表示事件类型，可选类型如下:

ONLINE: UPS 在线，即市电恢复时会触发

ONBATT: UPS 使用电池供电，即市电中断时会触发

LOWBATT: UPS 低电量时会触发

FSD: UPS 正在被关闭 (Forced Shutdown)

COMMOK: 与 nut-server 成功建立连接时触发

COMMBAD: 与 nut-server 建立连接失败 (连接丢失) 时触发

SHUTDOWN: UPS 发出关机指令触发

REPLBATT: UPS 需要更换电池时触发

NOCOMM: 无法与 UPS 建立连接 (UPS 未就绪) 时触发

对于 flag 标志通常有四种，多种组合时用加号 (+) 连接:

SYSLOG: 只打印 syslog

WALL: 在终端上弹出消息 (/bin/wall)

EXEC: 调用 NOTIFYCMD指定的命令，并传递相关事件

IGNORE: 啥也不干，忽略该事件

如果 NOTIFYCMD 使用了自定义脚本，则此处请根据实际需要来配置需要脚本处理的事件；如果 NOTIFYCMD 配置为使用 upssched, 可以将所有事件配置为 EXEC, 然后具体过滤在 upssched 处理.

例如如果只想让 NOTIFYCMD 配置的脚本只处理市电中断和恢复事件，同时打印 syslog, 可以这样配置:

Copy
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC

由于我 NOTIFYCMD 配置的是 upssched, 所以我这里将所有事件全部传递给 upssched:

Copy
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG FSD SYSLOG+EXEC
NOTIFYFLAG COMMOK SYSLOG+EXEC
NOTIFYFLAG COMMBAD SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC
NOTIFYFLAG REPLBATT SYSLOG+EXEC
NOTIFYFLAG NOPARENT SYSLOG+EXEC

其他配置

除了以上的核心配置，其他配置如果没特殊情况一般保持默认即可；具体的配置可以参考官方文档: UPSMON.CONF(5).

upssched.conf

使用该配置的前提是 upsmon.conf 配置中的 NOTIFYCMD 指向了 upssched, 并且 NOTIFYFLAG 为相关事件配置了 EXEC; 该配置主要的作用是使用一些 upssched 内置的高级语法来控制特定事件的处理方式. upssched.conf 配置主要包含两部分：头部的选项配置和尾部的规则配置.

4.2.1、选项配置

头部选项配置只包含三个配置:

CMDSCRIPT: 该配置应该位于首行 (推荐这样，实际上是 AT 指令之前就行), 该指令用于定义事件的处理脚本；脚本一般由用户自行编写，upssched会根据规则将指定参数传递到此脚本并执行.

PIPEFN: 用于进程间通信的管道文件，需要位于 AT 指令之前

LOCKFN: 互斥锁文件，用于防止 upsmon 同时调度多个文件，需要位于 AT 指令之前

关于 CMDSCRIPT 配置的脚本，下面是一个样例:

Copy
#!/usr/bin/env bash

set -ex

exec >> /var/log/upssched-cmd.log 2>&1

# 主要负责关闭系统的函数
function xpoweroff(){
logger -t upssched-cmd '准备关闭 XPEnology...'
logger -t upssched-cmd '准备关闭 TProxy Gateway...'
logger -t upssched-cmd '所有必要系统已成功关闭...'
}

# 判断 upssched 触发的事件
case $1 in
onbattwarn)
logger -t upssched-cmd 'UPS 已经切换到电池供电, 准备安全关闭系统...'
xpoweroff
;;
ups-back-on-line)
logger -t upssched-cmd '市电已恢复...'
;;
lowbatt)
logger -t upssched-cmd 'UPS 电量不足, 立即关闭系统...'
xpoweroff
;;
*)
logger -t upssched-cmd "Unrecognized command: $1"
;;
esac

有一点需要注意，CMDSCRIPT 配置的脚本默认是以 nut 用户运行的，所以要处理好 nut 用户权限问题.

对于 PIPEFN 和 LOCKFN 官方推荐单独创建叫 upssched 目录，然后文件放在这个目录里；但是有些系统例如 Ubuntu Server 22.04, /run/nut/ 是 tmpfs, 重启会丢失目录导致出现权限问题；所以推荐直接不创建独立目录直接配置:

Copy
CMDSCRIPT /opt/scripts/upssched-cmd.sh
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock

4.2.2、规则配置

使用 upssched 的好处就是内置了一个规则引擎，我们可以通过一些简单的语法来配置复杂规则；upssched 的规则语法如下:

Copy
AT notifytype upsname command

规则以 AT 开头，notifytype 用于指定需要关注的事件类型；upsname 指定 UPS 名称，只有一个的情况下或者不想区分时可以无脑写 *; command 部分用于指定需要执行的动作，command 大致有三种类型:

START-TIMER: 启动一个定时器

CANCEL-TIMER: 取消一个定时器

EXECUTE: 立即执行

这部分看起来复杂实际很简单，例如以下规则实现了: 当市电断开 (UPS 使用电池供电时) 启动一个名字叫 onbattwarn 的定时器，这个定时器在 180s 后会执行 CMDSCRIPT 定义的脚本并将 onbattwarn 作为第一个参数传递给脚本；同时如果市电在计时器的 180s 之内恢复 (临时闪断), 则取消执行脚本.

Copy
AT ONBATT * START-TIMER onbattwarn 180
AT ONLINE * CANCEL-TIMER onbattwarn

当然也有些事件是需要立即执行的，例如: 市电中断立即发送通知

Copy
# 立即执行 `CMDSCRIPT` 指定的脚本, 并将 `onbattnoti` 作为参数传递给脚本
AT ONBATT * EXECUTE onbattnoti

NUT Server 配置文件

完整配置为：

nut.conf

Copy
MODE=netserver

ups.conf

Copy
# 名称是为了一开始兼容群晖, 所以就叫 UPS
[ups]
driver = "usbhid-ups"
port = "auto"

upsd.conf

Copy
LISTEN 0.0.0.0 3493

upsd.users

Copy
[monuser]
password = secret
upsmon master
[qnapups]
password = 123456
upsmon master

upsmon.conf

Copy
MONITOR ups@localhost 1 monuser secret master
MINSUPPLIES 1
SHUTDOWNCMD "/usr/sbin/poweroff"
NOTIFYCMD /usr/sbin/upssched
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 30
DEADTIME 15
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG FSD SYSLOG+EXEC
NOTIFYFLAG COMMOK SYSLOG+EXEC
NOTIFYFLAG COMMBAD SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC
NOTIFYFLAG REPLBATT SYSLOG+EXEC
NOTIFYFLAG NOPARENT SYSLOG+EXEC
RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5

upssched.conf

Copy
CMDSCRIPT /opt/scripts/upssched-cmd.sh
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock
AT ONBATT * START-TIMER onbattwarn 180
AT ONLINE * CANCEL-TIMER onbattwarn
AT ONLINE * EXECUTE ups-back-on-line
AT LOWBATT * EXECUTE lowbatt

upssched-cmd.sh

Copy
#!/usr/bin/env bash

set -ex

exec >> /var/log/upssched-cmd.log 2>&1

echo "执行 ID: $(id)"

SSH_CMD='ssh -o StrictHostKeyChecking=no'

case $1 in
onbattwarn)
logger -t upssched-cmd 'UPS 已经切换到电池供电, 准备安全关闭系统...'
;;
ups-back-on-line)
logger -t upssched-cmd '市电已恢复...'
;;
lowbatt)
logger -t upssched-cmd 'UPS 电量不足, 立即关闭系统...'
;;
*)
logger -t upssched-cmd "Unrecognized command: $1"
;;
esac

重启 NUT Server

Copy
systemctl restart nut-server.service
systemctl restart nut-driver.service
systemctl restart nut-monitor.service

NUT Client 配置

配置 nut 客户端

编辑 /etc/nut/nut.conf 文件，将 MODE 设置为 netclient 模式

Copy
MODE=netclient

配置 upsmon

编辑 /etc/nut/upsmon.conf 文件，添加以下配置项：

Copy
MONITOR ups的名称@ups的ip地址 1 用户名 密码 slave
MINSUPPLIES 1
SHUTDOWNCMD "/usr/sbin/poweroff"
NOTIFYCMD /usr/sbin/upssched
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 30
DEADTIME 15
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG FSD SYSLOG+EXEC
NOTIFYFLAG COMMOK SYSLOG+EXEC
NOTIFYFLAG COMMBAD SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC
NOTIFYFLAG REPLBATT SYSLOG+EXEC
NOTIFYFLAG NOPARENT SYSLOG+EXEC
RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5
RUN_AS_USER root

这里注意 RUN_AS_USER，在 upsmon.conf 文件中，通常会定义 upsmon 运行的用户。在某些系统中，upsmon 默认以普通用户运行，这可能导致无法执行关机命令，所以需要 root。

配置 upssched

wall 命令在系统中用于向所有已登录用户广播消息。upsmon 默认配置会使用 wall 命令来发送通知消息给所有用户。

这里需要安装 wall 工具，Gentoo 安装过程为：

Copy
echo "sys-apps/util-linux tty-helpers" >> /etc/portage/package.use
sudo emerge -v sys-apps/util-linux
# 验证是否安装
which wall

upssched 工具允许你在特定条件下执行脚本。编辑 /etc/nut/upssched.conf 文件，并添加以下内容：

Copy
CMDSCRIPT /etc/nut/upssched-cmd.sh
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock
AT ONBATT * START-TIMER onbattwarn 600
AT ONLINE * CANCEL-TIMER onbattwarn
AT ONLINE * EXECUTE ups-back-on-line
AT LOWBATT * EXECUTE lowbatt

这将会在 ups 供电后 600 秒后发送 onbattwarn 事件到脚本，然后在脚本定义执行的命令。

/etc/nut/upssched-cmd.sh 内容为

Copy
#!/usr/bin/env bash

set -ex

exec >> /var/log/upssched-cmd.log 2>&1

echo "执行 ID: $(id)"

function xpoweroff(){
/sbin/shutdown -h +0 "UPS has been on battery for 1 minute, shutting down."
}

case $1 in
onbattwarn)
logger -t upssched-cmd 'UPS 已经切换到电池供电, 准备安全关闭系统...'
xpoweroff
;;
ups-back-on-line)
logger -t upssched-cmd '市电已恢复...'
;;
lowbatt)
logger -t upssched-cmd 'UPS 电量不足, 立即关闭系统...'
xpoweroff
;;
*)
logger -t upssched-cmd "Unrecognized command: $1"
;;
esac

赋予可执行权限

Copy
sudo chmod +x /etc/nut/upssched-cmd.sh

重启 NUT Client

Copy
systemctl enable nut-monitor.service
systemctl restart nut-monitor.service
systemctl status nut-monitor.service

验证配置

你可以通过以下命令测试 UPS 状态：

Copy
upsc <ups_name>@IP

参考

影音环境折腾 篇八：PVE 做 UPS Server 串联 QNAP 和 unraid

PVE UPS NUT 配置 自动关机

UPS Nut 配置教程