Logo ups y nut

Tener un sistema de respaldo eléctrico (UPS) es fundamental en servidores domésticos que alojan múltiples servicios. Pero no basta con que la UPS suministre energía unos minutos: lo crítico es que, en caso de corte prolongado, todo el sistema se apague de forma controlada y ordenada. En este artículo documento cómo he desplegado una solución basada en NUT sobre Proxmox para conseguirlo.

En este ejemplo verás que mi servidor NUC está conectado por cable USB a la UPS. Parte de los servicios a apagar son críticos (uno es una VM con el router linux a internet, el otro es el que da servicios DNS/DHCP). Lo que hago es crear un contenedor (LXC) especial dedicado a NUT (Network UPS Tools), al que le paso la USB y se va a encargar de todo, incluso de apagar a su propio Host proxmox…

Introducción

Creo y configuro el nuevo LXC llamado nut con el fin de:

  • Monitorizar el estado de una UPS conectada por USB.
  • Exponer el estado a Home Assistant (hass), para “monitorizar” a la UPS.
  • Ejecutar un apagado ordenado de todo lo que corre en el NUC (proxmox) ante fallo eléctrico prolongado.

Ejemplo de topología
Ejemplo de topología.

Servidor Proxmox

Lo primero que hago es crear un contenedor LXC para nut. Lo puedes hacer desde el UI, yo me bajé el template de debian 12. Si prefieres desde línea de comando:

pct create 123 local:vztmpl/debian-12-standard_12.0-1_amd64.tar.zst \
  -hostname nut \
  -memory 512 \
  -net0 name=eth0,bridge=vmbr0,ip=192.168.1.3/24,gw=192.168.1.1 \
  -rootfs local-lvm:8 \
  -features nesting=1 \
  -unprivileged 1
pct start 123
pct exec 123 -- bash

Averiguamos dónde está la UPS.

root@proxmox:~# lsusb
:
Bus 001 Device 002: ID 051d:0002 Fry's Electronics MEC0003
------------------     ---------
      |                    |
      |                    +---> Lo usaremos en /etc/udev/rules.d/50-ups.rules
      |
      +------------------------> Lo usamos en /etc/pve/lxc/123.conf
:

/etc/pve/lxc/123.conf

Ese fichero es el del contendor LXC nuc (ojo que en tu caso puedes haber asignado otro número). En él tenemos que añadir lo siguiente al final, asignándole el mismo bus/device Id’s (2 veces) bajo /dev

lxc.cgroup2.devices.allow: c 189:* rwm
lxc.mount.entry: /dev/bus/usb/001/002 dev/bus/usb/001/002 none bind,optional,create=file

/etc/udev/rules.d/50-ups.rules

Creamos este fichero para cambiar los permisos del USB a 0666.

El contenedor nut no es privilegiado, por lo que su usuario nut no puede escribir en el puerto USB, así que el truco es darle permisos de escritura desde el host. Se hadce hace en el host (proxmox), no en el contenedor, porque el contenedor está cogiendo prestado el hardware del host. Fíjate en el device ID del comando lsusb anterior: ID 051d:0002.

SUBSYSTEM=="usb", ATTR{idVendor}=="051d", ATTR{idProduct}=="0002", MODE="0666"

Recargo las reglas udev en Proxmox para que se apliquen de inmediato

udevadm control --reload-rules && udevadm trigger

Contenedor LXC nut

Arranco el nuevo contenedor, hago login como root y lo primero es actualizarlo.

apt update && apt full-upgrade -y

Instalo nut

apt update && apt full-upgrade -y
apt install nut nut-client -y

Modifico /etc/nut/nut.conf:

MODE=standalone

Modifico /etc/nut/ups.conf

[ups]
  driver = blazer_usb
  port = auto
  desc = "Fry's Electronics MEC0003"

Modifico /etc/nut/upsd.conf

LISTEN 127.0.0.1 3493
LISTEN 192.168.1.3 3493

Modifico /etc/nut/upsd.users

[homeassistant]
  password = upspass
  upsmon master
  instcmds = all

Modifico /etc/nut/upsmon.conf

MONITOR ups@localhost 1 homeassistant upspass master
MINSUPPLIES 1
SHUTDOWNCMD "/usr/local/sbin/nut-shutdown-master.sh"
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /etc/killpower
RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5

Permisos en nut

Como decia, en contenedores no privilegiados (como es el caso) y ejecutándose como usuario nut, tanto el driver de NUT (blazer_usb) como el daemon no puede acceder al dispositivo USB (/dev/bus/usb/...) ni al directorio /run. Para resolverlo modifqué a 0666 el device (en el host) y voy a crear un script para resolver lo de /run.

Creo un script, por ejemplo: nano /etc/rc.local

#!/bin/sh
# Fichero /etc/rc.local que llamaré desde systemd
mkdir -p /run/nut
chown nut:nut /run/nut
# Esto lo usaba antiguamente pero dejé de hacerlo porque el puerto puede cambiar
# así que lo resolví en proxomo con udev rules
#chown nut:nut /dev/bus/usb/001/002
exit 0

Creo un servicio personalizado en systemd para que lo llame al arrancar: /etc/systemd/system/rc-local.service

# Mini servicio que ejecuta /etc/rc.local durante el arranque
[Unit]
Description=Ejecutar /etc/rc.local
ConditionPathExists=/etc/rc.local
After=network.target

[Service]
Type=forking
ExecStart=/etc/rc.local
TimeoutSec=0
RemainAfterExit=yes
GuessMainPID=no

[Install]
WantedBy=multi-user.target

Programo que arranque siempre

systemctl daemon-reload
systemctl enable rc-local.service

Arranque y prueba de servicios

systemctl daemon-reload
systemctl enable nut-server
systemctl enable nut-monitor
systemctl start nut-server
systemctl start nut-monitor

Verifica que todo funciona bien

systemctl status nut-server
systemctl status nut-monitor
upsc ups@localhost

Script de apagado en nut

Este es el script que va a ejecutar nut cuando detecta un fallo prolongado en la alimentación:

  • Apagado remoto de pihole y router por SSH.
  • Apagado de VMs hass y wlc desde Proxmox (al que le da las órdenes por SSH).
  • Apagado del host Proxmox.
  • Apagado del contenedor nut (poweroff).

Fíjate que nut es el que manda, usa IPs para evitar dependencia de DNS y este script se dispara automáticamente ante fallo crítico de alimentación.

Ver script /usr/local/sbin/nut-shutdown-master.sh


Integración Home Assistant

Nos conectamos con nuestra página de administración webui de HASS

  • Settings > Devices & Services > “Network UPS Tool (NUT)”.
    • Configuro nombre, direccion IP del LXC nut (192.168.1.3), usuario homeassistant y contraseña upspass

Configuración de NUT en Devices & Services en HASS
Configuración de NUT en Devices & Services en HASS.

Configuración SSH:

Para que nut pueda acceder sin contraseña a los equipos: proxmox, router, pihole, necesito dar de alta las claves SSH correctamente, tienen que confiar en “nut” para dejarle que ejecute comandos a nivel de root (para poder hacer el apagado ordenado).

En nut:

ssh-keygen -t ed25519 -f /root/.ssh/nut_shutdown_key

Luego añadir el contenido de la clave pública (nut_shutdown_key.pub) a los siguientes ficheros:

  • router (192.168.1.1) -> /root/.ssh/authorized_keys
  • pihole (192.168.1.2) -> /root/.ssh/authorized_keys
  • proxmox (192.168.1.254) -> /etc/pve/priv/authorized_keys

Verificar que los tres tienen bien configurado /etc/ssh/sshd_config para que acepte autenticación por clave pública. Ejemplo (muy permisivo): permite acceso root directo, solo recomendable en entornos de confianza. De hecho se podría mejorar creando usuarios y usando sudo, ten en cuenta que esto es solo un ejemplo.

# Config para sshd que acepta ssh vía root.
# primero intenta clave public/private y si no va pide contraseña.
Port 22
PubkeyAuthentication yes
PasswordAuthentication yes
PermitRootLogin yes
UsePAM no
AllowAgentForwarding yes
AllowTcpForwarding yes
GatewayPorts yes
X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes
AddressFamily inet
PrintMotd no
PrintLastLog no
Subsystem sftp /usr/lib64/misc/sftp-server
AcceptEnv LANG LC_*

Referencias