Network UPS Tools monitoring for the CyberPower
CST135UC2 connected over USB (usbhid-ups driver, standalone mode). On
power loss it pushes ntfy alerts; when the battery runs low it shuts the server
down cleanly and tells the UPS to cut its outlets so everything restarts when
wall power returns.
- Install NUT (the root
setup.sh --profile serverinstalls it viapackages.json, or):sudo apt install nut
- Configure:
cd linux-server/ups cp .env.example .env # set UPSMON_PASSWORD (openssl rand -hex 16) + the ntfy URL/topic
- Deploy configs to
/etc/nutand enable the services:bash setup.sh --dry-run # works before .env exists sudo bash setup.sh - Verify:
upsc cyberpower ups.status # expect: OL (on line power) upsc cyberpower # full variable dump — charge, runtime, load bash ../../verify.sh --platform server journalctl -u nut-monitor -f
- Test a notification (safe — no shutdown involved):
sudo -u nut NOTIFYTYPE=ONBATT /etc/nut/ups-notify.sh "test event" - In BIOS, set Restore on AC Power Loss → Power On so the machine boots when the UPS re-energizes its outlets after an outage.
- Wall power drops → upsmon logs
ONBATTand pushes an urgent ntfy alert. - Power returns before the battery runs low →
ONLINEalert, nothing else. - Battery hits the low threshold → upsmon runs
SHUTDOWNCMD(shutdown -h +0), sets/etc/killpower, and the Debian shutdown hook tells the UPS to power off its outlets after the OS halts. - Wall power returns → the UPS re-energizes, and with the BIOS set to power-on the server boots unattended.
- Low-battery threshold: CyberPower firmware fires LOWBATT late (~10%
charge). For more headroom, uncomment in
ups.confand re-runsetup.sh:override.battery.charge.low = 25 # shut down at 25% charge override.battery.runtime.low = 300 # or at 5 minutes runtime leftsetup.shrestartsnut-driver@cyberpowerwhenups.confchanges, so the new threshold is active immediately without a reboot. - Poll/timing knobs (
POLLFREQ,HOSTSYNC,DEADTIME,FINALDELAY) are at conventional values inupsmon.conf.template. - Brief blips send an ONBATT + ONLINE alert pair. If that gets noisy,
upsschedcan debounce (only alert after N seconds on battery) — not wired up; layer it in later if needed. - Communication loss alerts fire on the
COMMBADtransition and recovery onCOMMOK; recurringNOCOMMwarnings stay in the system journal instead of pushing every five minutes.
PeaNUT serves a web dashboard with
charge/load/runtime graphs at https://peanut.<tailnet>.ts.net/ (Tailscale
sidecar per ../HTTPS.md, host-networked variant like glances —
PeaNUT itself must stay on the host network to reach the loopback-only upsd)
and feeds the homepage ups card (type: peanut widget via localhost).
Auth is disabled (read-only stats on a trusted network, same posture as
glances). Its runtime settings dir (peanut-config/) is gitignored.
cd linux-server/ups
# set TS_AUTHKEY in .env (same OAuth client secret as the other sidecars)
docker compose up -d| Repo file | Deployed to | Purpose |
|---|---|---|
nut.conf |
/etc/nut/nut.conf |
MODE=standalone (turns NUT on) |
ups.conf |
/etc/nut/ups.conf |
usbhid-ups driver for the CyberPower |
upsd.conf |
/etc/nut/upsd.conf |
upsd listens on loopback only |
upsd.users.template |
/etc/nut/upsd.users |
upsmon user (password from .env) |
upsmon.conf.template |
/etc/nut/upsmon.conf |
shutdown + notification policy |
ups-notify.sh |
/etc/nut/ups-notify.sh |
NOTIFYCMD → ntfy |
(rendered from .env) |
/etc/nut/ups-notify.env |
ntfy settings for the hook |
Everything in /etc/nut is root:nut 640 (the notify script 750); secrets
live only in the gitignored .env and the rendered /etc/nut files.