Set up nut-upsd and peanut in your homelab
I run my Synology server and network switches off a UPS. I decided I wanted a dashboard for the UPS, here’s how I did set up Network UPS Tools, aka NUT, and the PeaNUT dashboard.
Table of Contents
Prerequisites
- A UPS compatible with Network UPS Tools, aka NUT.
- A linux box with a free UPS port. I’m using my Orange Pi 5, but this would work on a Raspberry Pi or any other linux server.
- A USB-A to USB-B cable to connect the UPS to the linux box
Setup
Connect the UPS to your server
Connect the UPS to your linux server. Check which USB device it’s showing up as by running lsusb
.
On my machine, it appears on bus 2. Note how it shows up on the bus, we’ll need it to determine which nut driver to use.
$ lsusb
Bus 006 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 008 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 007 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 002: ID 0764:0501 Cyber Power System, Inc. CP1500 AVR UPS
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
$
Configure NUT
I looked up the CP1500 on the NUTS Hardware Compatibility List, and that says to use the usbhid-ups
driver.
Confirm the driver you need is present
docker run --rm --entrypoint /bin/ls instantlinux/nut-upsd /usr/lib/nut | grep YOURDRIVER
Create a docker-compose.yaml for nut-upsd
Now we can create a docker-compose.yaml
file for nut.
version: '3.9'
services:
nut-upsd:
image: instantlinux/nut-upsd
container_name: nut
environment:
- TZ=${TZ:-America/Denver}
- API_PASSWORD=${API_PASSWORD:-'YourPasswordForAPIaccess'}
# Look up your UPS here https://networkupstools.org/stable-hcl.html to
# determine which driver to use.
- DRIVER=usbhid-ups
# If you want the ups to be named something other than 'ups', set the NAME env variable
# -NAME=cp800avr
devices:
# Device numbers are subject to change, so map in the whole bus. NUT
# will automatically find the UPS for you.
- /dev/bus/usb:/dev/bus/usb
ports:
- "3493:3493"
restart: unless-stopped
Start the container with docker-compose up -d
Confirm UPS was detected by NUT
Confirm that nut
detected your UPS. telnet YOURHOST 3493
, then enter LIST UPS
It should look similar to
$ telnet localhost 3493
Trying ::1...
Connected to localhost.
Escape character is '^]'.
list ups
BEGIN LIST UPS
UPS ups "UPS"
END LIST UPS
Now confirm that it is reporting reasonable looking metrics by entering list var ups
.
Here’s what NUT reported about my UPS:
list var ups
BEGIN LIST VAR ups
VAR ups battery.charge "100"
VAR ups battery.charge.low "10"
VAR ups battery.charge.warning "20"
VAR ups battery.mfr.date "CPS"
VAR ups battery.runtime "1226"
VAR ups battery.runtime.low "300"
VAR ups battery.type "PbAcid"
VAR ups battery.voltage "13.3"
VAR ups battery.voltage.nominal "12"
VAR ups device.mfr "CPS"
VAR ups device.model "CP800AVRa"
VAR ups device.serial "CXPHX2000916"
VAR ups device.type "ups"
VAR ups driver.debug "0"
VAR ups driver.flag.allow_killpower "0"
VAR ups driver.name "usbhid-ups"
VAR ups driver.parameter.pollfreq "30"
VAR ups driver.parameter.pollinterval "2"
VAR ups driver.parameter.port "auto"
VAR ups driver.parameter.synchronous "auto"
VAR ups driver.state "quiet"
VAR ups driver.version "2.8.1"
VAR ups driver.version.data "CyberPower HID 0.8"
VAR ups driver.version.internal "0.52"
VAR ups driver.version.usb "libusb-1.0.26 (API: 0x1000109)"
VAR ups input.voltage "117.0"
VAR ups input.voltage.nominal "120"
VAR ups output.voltage "117.0"
VAR ups ups.beeper.status "enabled"
VAR ups ups.delay.shutdown "20"
VAR ups ups.delay.start "30"
VAR ups ups.load "38"
VAR ups ups.mfr "CPS"
VAR ups ups.model "CP800AVRa"
VAR ups ups.productid "0501"
VAR ups ups.realpower.nominal "450"
VAR ups ups.serial "CXPHX2000916"
VAR ups ups.status "OL"
VAR ups ups.test.result "No test initiated"
VAR ups ups.timer.shutdown "-60"
VAR ups ups.timer.start "-60"
VAR ups ups.vendorid "0764"
END LIST VAR ups
Set up Peanut webui
Edit: I now scrape the NUT data into Prometheus and view it with a Grafana dashboard. Details are here.
It’d be nice to have a pretty web dashboard for the UPS. Enter peanut.
Now to add the peanut service to our docker-compose.yaml
. Because it’s in the same docker-compose.yaml
file (and therefore by default, the same docker network), we can use the nut
container name as the NUT_HOST
. We’re also going to use depends_on
to make it start after the nut
container is up and running.
For simplicity, I’m not including how to make PeaNUT accessible via https. If you do want to secure PeaNUT with SSL, I’ve documented how to use nginx-proxy-manager as an SSL reverse proxy here.
Add a PeaNUT stanza to docker-compose.yaml
.
# Monitor our UPS
version: '3.9'
services:
nut-upsd:
image: instantlinux/nut-upsd
container_name: nut
environment:
- TZ=${TZ:-America/Denver}
- API_PASSWORD=${API_PASSWORD:-'aPasswordForAPIaccess'}
# Driver found with this tool https://networkupstools.org/stable-hcl.html
- DRIVER=usbhid-ups
# If you want the ups to be named something other than 'ups', set the NAME env variable
# -NAME=cp800avr
devices:
# Device numbers are subject to change, so map in the bus
- /dev/bus/usb:/dev/bus/usb
ports:
- "3493:3493"
restart: unless-stopped
peanut:
image: brandawg93/peanut:latest
container_name: peanut
restart: unless-stopped
# Don't start peanut until after nut-upsd is running
depends_on:
- nut-upsd
ports:
- 8080:8080
environment:
- NUT_HOST=nut
- NUT_PORT=3493
- WEB_PORT=8080
Do a docker-compose down && docker-compose up -d
to force docker-compose
to recognize the new service, and now you can open https://yourserver:8080 and see a nice dashboard.
Here’s the dashboard for the UPS that drives my network stack & Synology:
Looks like it’s time to get a second UPS and split the load - I’d like to have at least 45 minutes of network & Home Asssitant run time if the power goes out.
Edit: Add link to nginx-proxy-manager post documenting how to put http services like PeaNUT behind an SSL reverse proxy.