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

    graph TD
  UPS["UPS (USB)"] --> nut
  nut["LXC nut<br>192.168.1.3"]
  pihole["VM pihole<br>192.168.1.2"]
  router["VM router<br>192.168.1.1"]
  wlc["VM wlc"]
  hass["VM hass"]

  subgraph proxmox_host [proxmox 192.168.1.254]
    nut
    pihole
    router
    hass
    wlc
  end
  

PROXMOX: Instalo el contenedor LXC

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

Configura desde el administrador de Proxmox que el dispositivo USB se le asigne al nuevo LXC nut. Para comprobar que lo has hecho bien, mira la configuración dentro de proxmox del LXC en /etc/pve/lxc/123.conf, deberías tener algo como lo siguiente (comprueba con lsusb dónde tienes a la UPS)

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

Parametrizo el LXC nut:

Arranco el nuevo contenedor y entro en su shell como root.

apt update && apt full-upgrade -y

Dentro del nuevo LXC instalonut:

Averiguo dónde me pasa proxmox el dispositivo usb. En mi caso lo tendré disponible en /dev/bus/usb/001/003*.

root@nut:~# lsusb
:
Bus 001 Device 003: ID 0001:0000 Fry's Electronics MEC0003
:

Instalo nut

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

Fichero /etc/nut/nut.conf:

MODE=standalone

Fichero /etc/nut/ups.conf

[ups]
  driver = blazer_usb
  port = /dev/bus/usb/001/003
  desc = "Fry's Electronics MEC0003"

Fichero /etc/nut/upsd.conf

LISTEN 127.0.0.1 3493
LISTEN 192.168.1.3 3493

Fichero /etc/nut/upsd.users

[homeassistant]
  password = upspass
  upsmon master
  instcmds = all

Fichero /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 contenedor LXC:

En contenedores no privilegiados (como es el caso), tanto el driver de NUT (blazer_usb) como el daemon no puede acceder al dispositivo USB y el directorio /run por las restricciones de seguridad que impone este tipo de contenedor. Por lo tanto haremos un truco.

Primero manualmente ejecuta:

mkdir -p /run/nut
chown nut:nut /run/nut
chown nut:nut /dev/bus/usb/001/003

Es necesario poner los permisos de nuevo en cada arranque, para conseguirlo creo un script: /etc/rc.local

#!/bin/sh
# Fichero /etc/rc.local que llamaré desde systemd
mkdir -p /run/nut
chown nut:nut /run/nut
chown nut:nut /dev/bus/usb/001/003
exit 0

Y un servicio 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

Integración en Home Assistant (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 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_*

Creo el script de apagado:

A continuación tienes la chicha, 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.

Fichero /usr/local/sbin/nut-shutdown-master.sh:



Referencias