nut: Configure upsmon

`upsmon` is the component of NUT that tracks the status of UPSs and
reacts to their changing by sending notifications and/or shutting down
the system.  It is a networked application that can run on any system;
it can run on a different system than `upsd`, and indeed can run on
multiple systems simultaneously.

Each system that runs `upsmon` will need a username and password for
each UPS it will monitor.  Using the CUE [function pattern][0], I've
made it pretty simple to declare the necessary values under
`nut.monitor`.

[0]: https://cuetorials.com/patterns/functions/
master
Dustin 2024-01-19 08:52:14 -06:00
parent 227ce8cfcf
commit fb74f0e81c
7 changed files with 120 additions and 0 deletions

View File

@ -26,8 +26,25 @@ package schema
port: int
}
#NutMonitorSystem: {
system: string
powervalue: int
username: string
password: string
role: "primary" | "secondary"
}
#NutMonitor: {
monitor: [...#NutMonitorSystem]
minsupplies: int | *1
shutdowncmd?: string
notifycmd?: string
}
#Nut: {
listen: #Listen
ups: [string]: #Ups
users: [string]: #User
monitor?: #NutMonitor
}

View File

@ -51,3 +51,26 @@ templates: containerudev.templates + [
}
},
]
monitor: templates: [
{
template: "nut/upsmon.conf"
dest: "/etc/ups/upsmon.conf"
owner: "root"
group: "nut"
mode: "u=rw,g=r,o="
hooks: {
changed: [{run: "systemctl try-reload-or-restart nut-server"}]
}
},
{
template: "nut/nut-monitor.container"
dest: "/etc/containers/systemd/nut-monitor.container"
hooks: {
changed: [
{run: "systemctl daemon-reload", immediate: true},
{run: "systemctl restart nut-monitor"},
]
}
},
]

21
env/prod/nut.cue vendored
View File

@ -2,6 +2,8 @@ package prod
import "du5t1n.me/cfg/app/nut/schema"
ups_server: "nut.pyrocufflink.blue"
nut: schema.#Nut & {
listen: {
address: "::"
@ -56,6 +58,25 @@ nut: schema.#Nut & {
}
}
#nut_monitor: {
#server: string | *ups_server
#username: string
#role: "primary" | *"secondary"
monitor: [
for k, v in nut.ups {
{
system: k + "@" + #server
powervalue: 1
username: #username
password: nut.users[#username].password
role: #role
}
},
]
minsupplies: 1
}
let _nut = nut
collectd: nut: schema.#CollectdNut & {

View File

@ -6,4 +6,10 @@ import (
nut: prod.nut
nut: monitor: prod.#nut_monitor & {
#server: "localhost"
#username: "upsmon"
#role: "primary"
}
collectd: prod.collectd

View File

@ -10,5 +10,6 @@ import (
render: list.Concat([
collectd.templates,
nut.templates,
nut.monitor.templates,
nut.collectd.templates,
])

View File

@ -0,0 +1,24 @@
# vim: set ft=systemd :
[Unit]
Description=Network UPS Tools - power device monitor and shutdown controller
Wants=network-online.target
After=network-online.target
After=nut-server.service
[Container]
Image=git.pyrocufflink.net/containerimages/nut:latest
Pull=newer
Exec=upsmon -F
User=nut
RunInit=true
Mount=type=tmpfs,dest=/run/nut,chown=true
Volume=/etc/ups:/etc/ups:ro
ReadOnly=true
VolatileTmp=true
Network=host
[Service]
ExecReload=podman exec systemd-%N upsmon -c reload
[Install]
WantedBy=multi-user.target

28
templates/nut/upsmon.conf Normal file
View File

@ -0,0 +1,28 @@
{% for monitor in nut.monitor.monitor -%}
MONITOR {{ monitor.system }} {{ monitor.powervalue }} {{ monitor.username }} {{ monitor.password | decrypt }} {{ monitor.role }}
{% endfor %}
MINSUPPLIES {{ nut.monitor.minsupplies }}
{% if nut.monitor.shutdowncmd is defined -%}
SHUTDOWNCMD "{{ nut.monitor.shutdowncmd }}"
{% endif -%}
{% if nut.monitor.notifycmd is defined -%}
NOTIFYCMD {{ nut.monitor.notifycmd }}
{% endif %}
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /run/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 NOCOMM SYSLOG+EXEC
NOTIFYFLAG NOPARENT SYSLOG+EXEC
RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5