UPS management with NUT
August 19, 2024
Intro
This article has been in the works for a while. I’ve tried to get NUT configured multiple times but was never able to get it quite like I wanted. I believe I finally have everything configured well enough that I’m confident it will power down my systems correctly if the power goes out and I’m not home to handle things manually. Let’s walk through how I have everything configured.
Overall plan and initial install
My initial thought was to physically connect the UPS to pfSense and run the NUT server from there. I ran in to a couple issues with that and ultimately settled on using my Raspberry Pi as the NUT server. I’ve already got a Raspberry Pi powered via POE and running a backup DNS server so this was a perfect fit for the NUT server as well.
I currently have 3 main physical systems in my homelab (not counting the Pi). These are pfSense, Synology, and XCP-NG. My plan is to shutdown XCP-NG once the power has been off for 2 minutes. Synology and pfSense will then continue to operate until the UPS reaches 50% capacity. At that point, the NUT server on the Pi will command everything to shutdown. When power is restored, the UPS boots back up. The Pi automacially powers back up (POE), and the pfSense machine is set to always power up on AC restore. Synology can be powered back on via WOL, and my XCP-NG box has IPMI which can be used to start it up. By shutting down the UPS at 50% initially, it should be able to handle a couple of short term power restores if required.
The initial install on the Pi is as simple as running sudo apt install nut
. There are a few other things that I did during initial install that will be further explained later. For now, I added my user to the nut group with sudo usermod -a -G nut john
. I also created a set of SSH keys for the Pi with ssh-keygen
.
Configuration
When I first plugged my UPS in to my Pi, it would constanlty connect/disconnect. You could see this by running lsusb
and watch the device count increasing. After some Googling, this seemed to be a somewhat common problem. I found a couple of people suggesting that editting of udev rules was required to get this to stop. I found one thread that pointed to not adding the vendor and product IDs in the ups.conf file. This worked for me and was much cleaner than editing udev rules.
I’m using a script that I found online to cleanly shutdown my XCP-NG server. That script can be found here. If this link becomes unavailable in the future, just let me know, and I can provide my copy. I commented out several lines in the script as I only have one host. I then copied this file to XCP-NG and made it executable. On the Pi, I used ssh-copy-id [email protected]
to copy my SSH key from the Pi to XCP-NG. This will allow my to run a custom SSH command from the Pi to launch the shutdown script on XCP-NG. You can see this in the /bin/upssched-cmd
file below. Also, since these keys are tied to my user (john), I need to run the upsmon.conf
as the user john. This is possible becuase I added my user to the nut group earlier.
Here is what my various configuration files and scripts look like. These files are all very well documented on the NUT website.
ups.conf
# /etc/nut/ups.conf
pollinterval = 1
maxretry = 3
#
# do not include vendor and item IDs
# shut systems down at 50% battery level
[ups]
driver = usbhid-ups
desc = "SM1500RMXL2UTAA"
port = auto
ignorelb
override.battery.charge.warning = 60
override.battery.charge.low = 50
upsd.conf
# /etc/nut/upsd.conf
# listen on all interfaces
LISTEN 0.0.0.0 3493
nut.conf
# /etc/nut/nut.conf
MODE=netserver
upsd.users
# /etc/nut/upsd.users
#
# user for monitoring on Pi
[upsmon]
password = supersecret
upsmon primary
#
# use for pfSense
[upsremote]
password = verysecret
upsmon secondary
#
# user for Synology
[monuser]
password = secret
upsmon secondary
upsmon.conf
# /etc/nut/upsmon.conf
# some of these settings may already be in the default config file
RUN_AS_USER john
MONITOR ups@localhost 1 upsmon supersecret primary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h +0"
NOTIFYCMD /sbin/upssched
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 120
DEADTIME 15
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 90
upssched.conf
# /etc/nut/upssched.conf
CMDSCRIPT /bin/upssched-cmd
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock
AT ONBATT * START-TIMER shutdown_xcpng 120
AT ONLINE * CANCEL-TIMER shutdown_xcpng
upssched-cmd
# /bin/upssched-cmd
case $1 in
shutdown_xcpng)
# use ssh to execut xcp-ng shutdown script
/bin/ssh [email protected] './xcp-ng_shutdown.sh'
logger -t upssched-cmd "executed shutdown_xcpng"
;;
*)
logger -t upssched-cmd "Unrecognized command: $1"
;;
esac
Client configurations
For pfSense, simply install the NUT package. Then go to services -> UPS to configure everything. The UPS type will be “remote NUT server”. Enter your UPS name as well as the IP address of the server and user login information that was configured in the upsd.users file. If needed, the configuration files can be found on pfSense at /usr/local/etc/nut
.
Synology is very easy. Go to control panel -> hardware and power -> UPS. Check the box to enable UPS support then select the type as Synology UPS server. I’m using the option “same as server” because I want the Synology to run until the UPS reaches 50% power. Add the IP address of the NUT server. You do not have to enter the user and password here to log in to the NUT server, because Synology hard codes these values. This is why I have a specific Synology user in the upsd.users file.
Conclusion
It feels good to finally have this completed. I now feel better about leaving my lab running when I’m gone for an extended period of time. In a perfect world, everything will shutdown as planned and boot back up when power returns automatically. This should allow me to recover from a power failure even while traveling. Once the power returns and my firewall automaticall boots back up, I can use my VPN to power on the Synology and XCP-NG systems remotely. In theory at least. Thanks for reading.