UPS Nut 配置教程

一、Nut 安装

这里我采用的是 Ubuntu Server 22.04 系统, 安装直接一条 apt 命令即可:

1
apt install -y nut

二、Nut 服务组成

在配置 Nut 之前需要先了解下一 Nut 的各个组件及其作用, Nut 主要包含三个核心服务:

  • nut-driver: 这个服务负责通过特定放驱动来与 UPS 进行通信
  • nut-server: 该服务利用 nut-dirver 沟通 UPS, 并将 UPS 状态通过网络服务发布
  • nut-monitor(nut-client): 该服务连接 nut-server, 根据 UPS 状态做出特定响应

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

为了更容易理解这里画了一个简单的图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
             ┌─────────────┐                  ┌────────────┐
┌──── │ nut-monitor │ ───────────────► │ nut-server │
│ └─────────────┘ └────────────┘

│ │
│ │
▼ ▼
┌─────────────┐ ┌────────────┐
│ upssched │ │ nut-driver │
└─────────────┘ └────────────┘

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

三、Nut 基本配置

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

3.1、nut.conf

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

  • none: Nut 未配置或使用外部系统启动 Nut 服务, 可以理解为 “啥也不干”.
  • standalone: 独立模式, 一般在只有一个 UPS 且只负责本地系统(不提供网络服务)的情况下使用.
  • netserver: 跟独立模式类似, 会启动 driver、upsd 和 upsmon 服务, 不同之处是可以提供网络服务, 其他机器上的 nut-monitor 可以通过网络来连接 Nut Server.
  • netclient: 仅客户端模式, 只启动 nut-monitor, 用于连接远程的 Nut 服务.

我这是为了方便后续扩展, 所以使用了最全的 netserver 模式:

1
MODE=netserver

3.2、ups.conf

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

1
2
3
[nutdev1]
driver = "usbhid-ups"
port = "auto"

其中 nutdev1 表示该 UPS 名称, 可以随意定义; driver 用于定义连接到该 UPS 需要使用的驱动, 一般情况下如果使用 USB 连接像我这样写都是可以识别到的. 如果同样都是用 USB 连接但识别不到, 可以使用 nut-scanner 命令进行扫描, 扫描成功后会在控制台打印出 UPS 相关配置样例.

需要注意的是, 如果想要群晖或者 QNAP 能通过网络连接 UPS, 那么 UPS 的名称是有特殊要求的(群晖必须起名叫 ups, QNAP 没有测试过可能叫 qnapups); 因为这两个系统都写死了, 包括后面的用户名和密码也是, 具体在下文会有说明.

除了 USB 连接 UPS 之外 Nut 还支持多种驱动连接 UPS, 例如 APC 专用的驱动程序, 有关于 Nut 具体连接方式请查看官方文档:

3.3、upsd.conf

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

1
LISTEN 0.0.0.0 3493

如果想要完整查看支持哪些配置, 请参考官方文档: UPSD.CONF(5)

3.4、upsd.users

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

1
2
3
4
5
6
[monuser]
password = secret
upsmon = master
[admin]
password = 123456
upsmon = master

上面的 [xxxx] 代表用户名, 密码由 password 字段指定; 除此之外还有一些特殊参数:

  • actions: 指定该用户具有哪些操作权限, 可选值 SET(更改 UPS 变量)、FSD(设置 UPS 强制关机标志); 如果需要两个都指定, 则需要写两次 actions.
  • instcmds: 让用户启动的即时命令, 值 ALL 代表所有, 其他的可通过 upscmd -l 查看; 同样如果要指定多个需要写多次 instcmds.
  • upsmon: 为 upsmon 进程添加必要的操作, 可选值 primarysecondary(一般用不到)

注意: Ubuntu Server 22.04 上的版本可能没有那么新, upsmon 实际上支持的是 masterslave 两个参数(怀疑与某场运动有关).


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

四、Nut 监控配置

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

  • 1、直接由 upsmon.conf 配置, 任何事件直接由用户指定的脚本负责处理, 没有定时器等高级特性
  • 2、在 upsmon.conf 中配置执行脚本为 upssched, 任何事件先由 upssched 处理, 借助于 upssched 可以实现一些高级功能, 比如选择性触发关机等

4.1、upsmon.conf

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

4.1.1、MONITOR 指令

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

1
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 表示该系统立即关闭

以下为我的配置样例:

1
MONITOR ups@localhost 1 monuser secret master

4.1.2、SHUTDOWNCMD

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

1
SHUTDOWNCMD "/usr/sbin/poweroff"

4.1.3、NOTIFYCMD

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

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

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

这里我选择使用第二种方式, 因为自己搓脚本能力实在不怎么样:

1
NOTIFYCMD /usr/sbin/upssched

4.1.4、NOTIFYFLAG

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

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

其中 <notify type> 表示事件类型, 可选类型如下:

  • 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, 可以这样配置:

1
2
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC

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

1
2
3
4
5
6
7
8
9
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

4.1.5、其他配置

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

4.2、upssched.conf

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

4.2.1、选项配置

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

  • CMDSCRIPT: 该配置应该位于首行(推荐这样, 实际上是 AT 指令之前就行), 该指令用于定义事件的处理脚本; 脚本一般由用户自行编写, upssched 会根据规则将指定参数传递到此脚本并执行.
  • PIPEFN: 用于进程间通信的管道文件, 需要位于 AT 指令之前
  • LOCKFN: 互斥锁文件, 用于防止 upsmon 同时调度多个文件, 需要位于 AT 指令之前

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/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 用户权限问题.

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

1
2
3
CMDSCRIPT /opt/scripts/upssched-cmd.sh
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock

4.2.2、规则配置

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

1
AT notifytype upsname command 

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

  • START-TIMER: 启动一个定时器
  • CANCEL-TIMER: 取消一个定时器
  • EXECUTE: 立即执行

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

1
2
AT ONBATT * START-TIMER onbattwarn 180
AT ONLINE * CANCEL-TIMER onbattwarn

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

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

五、配置参考

上面说了那么多, 下面给一个完整的我个人的配置参考:

nut.conf

1
MODE=netserver

ups.conf

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

upsd.conf

1
LISTEN 0.0.0.0 3493

upsd.users

1
2
3
4
5
6
[monuser]
password = secret
upsmon = master
[qnapups]
password = 123456
upsmon = primary

upsmon.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env bash

set -ex

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

echo "执行 ID: $(id)"

SSH_CMD='ssh -o StrictHostKeyChecking=no'

function xpoweroff(){
logger -t upssched-cmd '准备关闭 XPEnology...'
${SSH_CMD} root@192.168.1.2 poweroff || true
logger -t upssched-cmd '准备关闭 TProxy Gateway...'
${SSH_CMD} root@192.168.1.3 poweroff || true
logger -t upssched-cmd '所有必要系统已成功关闭...'
}

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

UPS Nut 配置教程
https://mritd.com/2023/03/18/ups-nut-configuration-tutorial/
作者
Kovacs
发布于
2023年3月18日
许可协议