群晖NAS非官方UPS接入:基于NUT实现断电保护与状态监控
1. 项目概述:从一次断电事故说起
去年夏天,我经历了一次刻骨铭心的教训。一场突如其来的雷暴导致小区短暂停电,虽然只有不到一分钟,但我的群晖NAS(网络附加存储)却因此“罢工”,一块硬盘出现了坏道,导致一个存放家庭照片的存储卷降级。数据虽然没丢,但那种心惊肉跳的感觉,让我下定决心要彻底解决NAS的供电保护问题。于是,一台不间断电源(UPS)被提上了日程。
市面上支持群晖的UPS不少,但要么价格昂贵,要么功能单一。我手头恰好有一台老旧的APC BK650M2,这是一款非常经典的入门级UPS,性价比极高,但它与群晖的“官方”兼容性仅限于通过USB连接实现最基本的断电关机功能。这意味着,我无法在群晖的DSM管理界面里看到UPS的实时负载、电池电量、预计运行时间等关键信息,更别提进行精细化的电源管理策略配置了。这显然不符合我这种喜欢“掌控一切”的极客风格。
正是在这种背景下,我开始了对“群晖UPS NUT接口开发”的探索。NUT,全称Network UPS Tools,是一个开源、跨平台的UPS监控与管理套件。它的强大之处在于,通过一个统一的守护进程(upsd)和驱动层,可以支持市面上成百上千种不同品牌、型号的UPS,并将状态信息通过标准的网络协议(如upsc命令)暴露出来。我的目标很明确:让这台“非官方”支持的APC UPS,通过NUT在群晖上“重生”,实现媲美甚至超越官方兼容UPS的监控与管理能力。这不仅是为了解决我自己的问题,更是为所有拥有“非标”UPS、又希望深度集成到群晖生态中的用户,探索一条可行的技术路径。
2. 核心思路与技术选型解析
要让NUT在群晖上跑起来,首先得理解群晖DSM系统的本质。DSM是基于Linux的定制化操作系统,但其内核和用户空间经过了深度裁剪和封装,普通用户通过图形界面(GUI)能接触到的系统层面非常有限。直接使用apt-get或yum安装软件包是行不通的。因此,我们的核心思路是:在DSM的系统框架内,以“非侵入”或“最小侵入”的方式,部署并运行完整的NUT套件。
这里有几个关键的技术选型考量:
2.1 部署方式:套件 vs. Docker vs. 命令行
套件(.spk)安装:这是最“原生”的群晖方式,通过群晖的套件中心安装。但为NUT开发一个正式的.spk套件,需要向群晖提交申请、进行签名和上架,流程复杂,且对于个人开发者而言几乎不可能。此外,不同型号的群晖(ARM、x86)需要分别编译,维护成本极高。因此,这条路首先被排除。
Docker容器部署:这是目前社区最流行、最灵活的方案。我们可以在Docker Hub上找到现成的NUT镜像(如
nut-server),通过群晖的Docker套件一键部署。它的优势是隔离性好,不污染宿主机环境,升级和迁移都非常方便。但缺点同样明显:UPS的USB设备需要映射到容器内,这涉及到设备权限和持久化挂载的问题。更关键的是,让DSM系统(宿主)感知到Docker容器内NUT服务发出的关机信号,需要额外的脚本和配置,增加了复杂度。命令行直接安装:直接在DSM的Linux环境下,通过包管理器的替代方案(如
ipkg/opkg,或手动编译)安装NUT。这种方式最“底层”,能获得最好的性能和最直接的控制。DSM虽然移除了apt,但其底层仍是Linux,存在/lib、/usr等目录结构。通过社区维护的entware或synogear这类第三方软件仓库,可以安装预编译的NUT包。这是我最终选择的方案。原因在于:- 直接性:NUT服务以原生进程运行在DSM上,与系统集成度最高。
- 控制力:可以直接修改配置文件,调试方便。
- 可靠性:不依赖额外的容器引擎,服务生命周期更可控。
2.2 NUT工作模式选择
NUT支持多种工作模式,对于单NAS场景,最常用的是独立模式(standalone)和主从模式(master/slave)。
- 独立模式:一台机器既是UPS的监控服务器(运行
upsd),也是需要被保护的客户端。配置简单,适合单一设备。 - 主从模式:一台机器作为主服务器(master)连接并监控UPS,其他机器(slave)通过网络从主服务器获取状态并执行相应命令。适合保护多台服务器。
对于绝大多数家庭用户,一台群晖NAS连接一台UPS,独立模式完全够用,配置也最简单。因此,我们的探索将围绕独立模式展开。
2.3 与DSM的集成策略
这是整个项目的灵魂。仅仅运行NUT服务还不够,必须让DSM系统知道:“哦,现在有UPS在保护我,并且我应该在电池电量低时安全关机。”
- 状态信息获取:我们需要通过NUT的工具(如
upsc)定期获取UPS状态(电量、负载、状态等)。 - 触发DSM关机流程:当NUT监测到电池电量低于阈值或市电中断时间过长时,需要触发DSM的标准关机流程,而不是简单地执行
poweroff命令。DSM有自己的关机脚本和清理流程。 - 状态可视化(可选但重要):理想情况下,我们希望在DSM的控制面板中看到UPS信息。这通常需要修改Web界面,难度极大。一个折中的、更可行的方案是:通过计划任务或脚本,将UPS状态信息写入一个文件,然后通过DSM的Web Station搭建一个简单的状态监控页面,或者集成到Home Assistant等智能家居平台中。
基于以上分析,我的技术路线确定为:在DSM上通过第三方包管理器安装NUT,配置为独立模式,并编写自定义脚本桥接NUT事件与DSM系统关机命令。
3. 实战部署:在DSM上安装与配置NUT
注意:以下操作涉及SSH登录和命令行修改,请确保你熟悉基本Linux操作,并在操作前对重要数据进行备份。不同DSM版本和群晖机型可能存在差异,请灵活调整。
3.1 环境准备:开启SSH与安装Entware
- 开启SSH:进入DSM控制面板 -> 终端机和SNMP -> 勾选“启动SSH功能”,端口默认22。
- 安装Entware:Entware是一个为嵌入式设备(如路由器、NAS)优化的软件仓库。我们使用社区提供的脚本来安装。
- 通过SSH客户端(如PuTTY、Termius)以
root用户登录你的群晖NAS。密码与你登录DSM Web界面时的管理员密码相同。 - 执行以下命令一键安装Entware:
/bin/sh -c "$(curl -kfsSL https://raw.githubusercontent.com/Entware/Entware/ng/master/generic.sh)" - 安装完成后,更新软件包列表:
opkg update。 - 将Entware的路径添加到系统环境变量,方便后续使用。编辑
/root/.profile文件(如果不存在就创建):echo 'export PATH=/opt/bin:/opt/sbin:$PATH' >> /root/.profile source /root/.profile
- 通过SSH客户端(如PuTTY、Termius)以
3.2 安装NUT软件包
通过Entware安装NUT及其USB驱动支持:
opkg install nut nut-common nut-usb安装完成后,主要的可执行文件和配置文件会位于/opt/sbin和/opt/etc/nut目录下。
3.3 连接并识别UPS
将你的UPS通过USB线连接到群晖NAS的USB端口。然后使用lsusb命令查看是否识别到设备:
lsusb你应该能看到类似Bus 001 Device 003: ID 051d:0002 American Power Conversion UPS的输出。记下ID(本例为051d:0002),这有助于后续确认驱动。
3.4 配置NUT(独立模式)
NUT的配置文件主要位于/opt/etc/nut/目录。我们需要修改三个核心文件。
配置驱动 (
nut.conf):此文件定义NUT的运行模式。vi /opt/etc/nut/nut.conf将其内容修改为:
MODE=standalone这表示我们运行在独立模式。
配置UPS驱动 (
ups.conf):此文件定义如何与你的UPS硬件通信。vi /opt/etc/nut/ups.conf添加你的UPS配置。以我的APC BK650M2为例,使用
usbhid-ups驱动:[apc-ups] driver = usbhid-ups port = auto desc = "APC Back-UPS 650M2"[apc-ups]:给你的UPS实例起个名字,后续会用到。driver:驱动名。APC的USB UPS大多使用usbhid-ups。其他品牌如CyberPower可能用blazer_usb,需要查阅NUT的硬件兼容列表。port:设为auto让NUT自动发现USB设备。desc:描述信息,可选。
配置守护进程 (
upsd.conf):此文件定义upsd服务监听的网络设置。vi /opt/etc/nut/upsd.conf对于独立模式,我们只需要监听本地即可:
LISTEN 127.0.0.1 34933493是NUT的默认端口。
配置用户与权限 (
upsd.users):定义可以访问upsd服务的用户。vi /opt/etc/nut/upsd.users添加一个用于本地监控和关机的用户:
[monuser] password = your_secure_password_here upsmon master[monuser]:用户名。password:设置一个强密码。upsmon master:表示此用户具有upsmon进程所需的“master”权限,可以执行关机等命令。
配置监控客户端 (
upsmon.conf):upsmon是监控UPS状态并在必要时采取行动的客户端程序。vi /opt/etc/nut/upsmon.conf添加监控配置:
MONITOR apc-ups@localhost 1 monuser your_secure_password_here master NOTIFYCMD /opt/etc/nut/notifycmd.sh NOTIFYFLAG ONLINE SYSLOG+WALL NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC NOTIFYFLAG FSD SYSLOG+WALL+EXEC NOTIFYFLAG COMMOK SYSLOG+WALL NOTIFYFLAG COMMBAD SYSLOG+WALL+EXEC SHUTDOWNCMD "/opt/sbin/upsmon-shutdown" POWERDOWNFLAG /opt/etc/nut/upsmon.powerdownflagMONITOR:定义监控哪个UPS(apc-ups@localhost),1表示一个电源,后面是用户名密码和master角色。NOTIFYCMD:指定一个自定义通知脚本的路径,我们稍后创建。NOTIFYFLAG:定义各种事件(如在线、转电池、低电量、强制关机FSD)触发时的行为(写系统日志SYSLOG、发消息给所有用户WALL、执行命令EXEC)。SHUTDOWNCMD:这是最关键的一行!它定义了当需要关机时执行的命令。我们不能直接用/sbin/poweroff,必须调用DSM的关机流程。这里指向一个我们即将创建的自定义脚本。POWERDOWNFLAG:指定一个标志文件路径,用于防止重复关机。
3.5 创建关键桥接脚本
创建DSM安全关机脚本 (
/opt/sbin/upsmon-shutdown):vi /opt/sbin/upsmon-shutdown输入以下内容:
#!/bin/sh # 记录关机日志 logger -t upsmon "UPS requested DSM shutdown via upsmon-shutdown script." # 执行DSM的正常关机命令 /usr/syno/sbin/synopoweroff然后赋予执行权限:
chmod +x /opt/sbin/upsmon-shutdown/usr/syno/sbin/synopoweroff是群晖DSM系统提供的标准关机命令,它会执行完整的服务停止、缓存回写等流程,比直接poweroff安全得多。创建事件通知脚本 (
/opt/etc/nut/notifycmd.sh):这个脚本用于在发生特定事件时执行自定义操作,比如发送邮件通知。vi /opt/etc/nut/notifycmd.sh输入一个基础模板:
#!/bin/sh # 参数说明: $1 = 事件类型, $2 = UPS名称 logger -t upsmon-notify "UPS Event: $1 for UPS $2" # 你可以在这里添加发送邮件、调用Webhook等逻辑 # 例如,使用curl调用SendGrid API发邮件,或者调用钉钉/企业微信机器人赋予执行权限:
chmod +x /opt/etc/nut/notifycmd.sh
3.6 启动NUT服务并设置开机自启
首次启动与测试:
- 启动UPS驱动:
/opt/sbin/upsdrvctl start - 启动
upsd守护进程:/opt/sbin/upsd - 启动
upsmon监控客户端:/opt/sbin/upsmon可以使用ps aux | grep ups查看进程是否运行。
- 启动UPS驱动:
测试查询UPS状态:
/opt/bin/upsc apc-ups@localhost如果配置正确,你会看到一长串UPS的实时数据,包括:
battery.charge: 100 battery.runtime: 1800 input.voltage: 220.0 output.voltage: 220.0 ups.status: OLOL表示在线(On Line),OB表示使用电池(On Battery)。模拟断电测试(非常重要!):在确保所有重要数据已保存的前提下,可以安全地拔掉UPS的市电插头。观察系统日志:
tail -f /var/log/messages你应该会看到
upsmon检测到ONBATT事件,并开始记录电池剩余时间和电量。重新插上市电,观察是否恢复OL状态。暂时不要测试低电量关机,等一切稳定后再进行。配置开机自启: 为了让NUT在NAS重启后自动运行,我们需要创建自启动脚本。在DSM中,可以通过创建自定义
rc脚本实现。vi /usr/local/etc/rc.d/S99nut.sh输入以下内容:
#!/bin/sh case "$1" in start) echo "Starting NUT services..." /opt/sbin/upsdrvctl start sleep 2 /opt/sbin/upsd sleep 2 /opt/sbin/upsmon ;; stop) echo "Stopping NUT services..." /opt/sbin/upsmon -c stop sleep 2 /opt/sbin/upsd -c stop sleep 2 /opt/sbin/upsdrvctl stop ;; *) echo "Usage: $0 {start|stop}" exit 1 ;; esac exit 0赋予执行权限:
chmod +x /usr/local/etc/rc.d/S99nut.sh现在,你可以用
/usr/local/etc/rc.d/S99nut.sh start和stop来管理服务,并且下次NAS重启时,NUT会自动启动。
4. 高级集成与状态可视化方案
基础功能实现后,我们可以追求更优雅的集成。毕竟,每次SSH敲命令查状态太不“群晖”了。
4.1 创建Web状态监控页面
我们可以利用群晖自带的Web Station套件,创建一个简单的PHP页面来实时显示UPS状态。
- 确保Web Station和PHP已安装:在套件中心安装“Web Station”并启用PHP支持(如PHP 7.4)。
- 在Web目录创建页面:例如,在
/var/services/web/ups/目录下创建index.php。<?php header('Content-Type: text/plain; charset=utf-8'); // 执行upsc命令获取状态 $ups_name = 'apc-ups@localhost'; $command = "/opt/bin/upsc $ups_name 2>&1"; $output = shell_exec($command); if ($output === null) { echo "无法获取UPS状态。请检查NUT服务是否运行。\n"; exit; } echo "=== UPS 实时状态 ===\n"; echo "更新时间: " . date('Y-m-d H:i:s') . "\n\n"; echo $output; // 可以解析$output,提取关键信息(如battery.charge, ups.status)用更美观的HTML展示 ?> - 通过浏览器访问:打开
http://你的NAS地址/ups/,就能看到一个纯文本的状态页。你可以进一步用HTML/CSS美化它,做成一个仪表盘。
4.2 集成到Home Assistant
如果你使用Home Assistant进行智能家居管理,将UPS状态接入其中能实现更强大的自动化(比如电量低于20%时向手机发送紧急通知,并自动暂停所有下载任务)。
- 在Home Assistant的
configuration.yaml中添加NUT集成:nut: - name: my_nas_ups host: 你的群晖NAS的IP地址 port: 3493 username: monuser password: your_secure_password_here resources: - ups.status - battery.charge - battery.runtime - input.voltage - output.voltage - 重启Home Assistant后,你就能在实体列表中看到
sensor.my_nas_ups_status等传感器,并可以用于创建自动化。
4.3 自定义DSM任务计划进行状态日志与告警
利用DSM控制面板中的“任务计划”,我们可以定期检查UPS状态并执行告警。
- 创建一个“用户定义的脚本”计划任务,比如每5分钟运行一次。
- 脚本内容示例:
这个脚本会在市电断开(状态为OB)或电量过低时,调用一个PHP脚本发送邮件。你需要事先配置好PHP的邮件发送功能(如使用SMTP)。#!/bin/bash STATUS=$(/opt/bin/upsc apc-ups@localhost ups.status 2>/dev/null) CHARGE=$(/opt/bin/upsc apc-ups@localhost battery.charge 2>/dev/null) RUNTIME=$(/opt/bin/upsc apc-ups@localhost battery.runtime 2>/dev/null) if [ "$STATUS" = "OB" ]; then # 正在使用电池! echo "警告:NAS正在使用UPS电池供电!当前电量 ${CHARGE}%,预计运行时间 ${RUNTIME}秒。" | \ /usr/bin/php /volume1/homes/admin/send_mail.php "UPS电池供电告警" fi if [ -n "$CHARGE" ] && [ "$CHARGE" -lt 30 ]; then # 电池电量低于30% echo "紧急:UPS电池电量低至 ${CHARGE}%!请立即处理。" | \ /usr/bin/php /volume1/homes/admin/send_mail.php "UPS电量低紧急告警" fi
5. 避坑指南与疑难问题排查
在实际操作中,我踩过不少坑。这里把常见问题和解决方案记录下来,希望能帮你节省时间。
5.1 驱动识别失败,upsc命令无输出或报错
- 现象:执行
upsc apc-ups@localhost后长时间无响应,或提示Error: Driver not connected。 - 排查:
- 检查USB连接与权限:运行
lsusb确认UPS已被系统识别。然后检查/dev下是否有相关的USB设备节点(如/dev/usb/hiddev0)。确保nut用户(或root)有访问权限。有时需要修改/opt/etc/nut/ups.conf中的port为具体设备路径,如port = /dev/usb/hiddev0。 - 检查驱动是否匹配:访问NUT官网的硬件兼容列表,确认你的UPS型号对应的正确驱动名。APC的很多型号都用
usbhid-ups,但有些老型号可能需要apcsmart(串口)或apcupsd-ups(通过apcupsd守护进程桥接)。 - 查看驱动日志:启动驱动时加上调试参数:
/opt/sbin/upsdrvctl -D start,观察终端输出,看是否有权限错误或通信失败信息。 - 尝试其他驱动:对于某些国产或较新的UPS,
blazer_usb驱动可能兼容性更好。可以在ups.conf中将driver改为blazer_usb试试。
- 检查USB连接与权限:运行
5.2upsmon无法启动,提示“Invalid password”或“Access denied”
- 现象:启动
upsmon时失败,系统日志/var/log/messages中有认证错误。 - 排查:
- 核对用户名密码:确保
upsd.users文件中定义的用户名、密码与upsmon.conf中的MONITOR行完全一致,包括大小写。 - 检查文件权限:确保
/opt/etc/nut/目录下的配置文件(特别是upsd.users)权限正确,upsd进程能够读取。通常nut用户需要读权限。可以运行chown -R nut:nut /opt/etc/nut/和chmod 640 /opt/etc/nut/*.conf试试。 - 重启
upsd:修改upsd.users后,需要重启upsd服务(/opt/sbin/upsd -c stop然后/opt/sbin/upsd)才能使新的用户配置生效。
- 核对用户名密码:确保
5.3 断电测试时,NAS没有正常关机
- 现象:拔掉市电后,UPS电池耗尽,NAS直接硬关机。
- 排查:
- 检查
upsmon进程:确保upsmon正在运行(ps aux | grep upsmon)。 - 检查事件触发:查看系统日志
tail -f /var/log/messages,拔电后是否出现了ONBATT和LOWBATT事件,最终是否有FSD(Forced Shutdown)事件。如果没有FSD,可能是upsmon.conf中的NOTIFYFLAG配置或电池阈值设置有问题。 - 检查
SHUTDOWNCMD脚本:这是最关键的一步。手动执行你的/opt/sbin/upsmon-shutdown脚本,看是否能正常触发DSM关机。确保脚本有执行权限(chmod +x),并且其中的/usr/syno/sbin/synopoweroff路径在你的DSM上确实存在。 - 检查
upsmon的权限:upsmon需要以root权限运行才能执行关机命令。确保你的启动脚本(如S99nut.sh)是以root身份执行的。
- 检查
5.4 群晖系统更新后NUT服务失效
- 现象:DSM系统升级后,NUT服务无法启动,或USB设备无法识别。
- 解决方案:
- Entware环境恢复:DSM大版本更新可能会重置系统环境。SSH登录后,重新执行
source /root/.profile加载Entware路径,或重新运行Entware安装脚本的“修复”部分。 - 驱动重新编译(罕见):极少数情况下,内核更新可能导致预编译的驱动模块不兼容。此时需要尝试通过Entware重新安装NUT(
opkg upgrade nut nut-usb),或者寻找针对新内核的社区编译版本。 - 自启动脚本检查:检查
/usr/local/etc/rc.d/S99nut.sh是否还在,权限是否正常。DSM更新有时会清理/usr/local/etc/rc.d/目录。
- Entware环境恢复:DSM大版本更新可能会重置系统环境。SSH登录后,重新执行
5.5 如何安全地移除或卸载NUT
如果你后续想改用官方支持的UPS或Docker方案,需要干净地移除当前安装。
- 停止服务:
/usr/local/etc/rc.d/S99nut.sh stop - 删除自启动脚本:
rm /usr/local/etc/rc.d/S99nut.sh - 通过Entware卸载:
opkg remove nut nut-common nut-usb - (可选)清理配置目录:
rm -rf /opt/etc/nut/
整个探索过程下来,最大的体会是“自由”与“责任”并存。通过NUT,我们打破了厂商锁定的壁垒,让老设备焕发新生,获得了极其丰富的监控和控制能力。但这也意味着所有的稳定性、安全性都需要自己来保障。从驱动调试、脚本编写到最后的断电模拟测试,每一个环节都需要耐心和严谨。当最终在自制的小网页上看到UPS的实时参数,或者在Home Assistant里收到低电量推送时,那种成就感远非插上即用的官方方案所能比拟。这不仅仅是一个UPS的接入,更是一次对自家数据中心基础设施的深度理解和掌控。对于爱折腾的NAS玩家来说,这个过程本身,就是最大的乐趣所在。