L’onduleur est un dispositif UPS, UPS signifie Uninterruptible Power Supply (en français : Alimentation Sans Interruption, ou ASI). Ainsi on parle aussi bien d’onduleur que d’UPS.

Avant d’acheter un onduleur il est important de vérifier que cet onduleur est connectable (par exemple en USB) aux serveurs que vous voulez arrêter proprement. Ainsi dans le cas de l’onduleur UPS ERA PLUS STRIP 800, il y a une sortie usb type B. Via un câble USB type A vers type B nous pouvons relié le serveur proxmox à cet onduleur.

2 appareils à arrêter proprement :

  • serveur opnsense
  • serveur proxmox

Installation du logiciel NUT

Nous allons utiliser le logiciel NUT.
J’ai une contrainte logiciel : mon onduleur a une connexion Serial to USB. Or sur opnsense l’administration du port serie nécessite une internvention complexe (c’est pas géré par défaut).
Dans l’idéal il faudrait :
Opnsense primary device : celui qui sera connecté directement à l’onduleur, et qui va monitorer l’onduleur. Et très important également, celui qui s’arrêtera en dernier.
Proxmox secondary device : s’arrête avant le primary device car plus sensible + dépend du primary device pour monitorer l’UPS.

Mais en pratique je vais inverser cela, car nut est beaucoup plus facile à administrer sur debian étant donné la contrainte que j’ai avec mon onduleur Serial to USB.
Donc mon choix final :

  • proxmox en primary : serveur nut
  • opnsense en secondary : client nut

Installation de NUT server sur le minipc proxmox et configuration de nut

root@pve:~# nut-scanner -U
Cannot load SNMP library (libnetsnmp.so.40) : file not found. SNMP search disabled.
Cannot load XML library (libneon.so.27) : file not found. XML search disabled.
Cannot load IPMI library (libfreeipmi.so.17) : file not found. IPMI search disabled.
Scanning USB bus.
[nutdev1]
        driver = "nutdrv_qx"
        port = "auto"
        vendorid = "0665"
        productid = "5161"
        product = "USB to Serial"
        vendor = "INNO TECH"
        bus = "001"
        device = "006"
        busport = "002"
        ###NOTMATCHED-YET###bcdDevice = "0002"

L’absence des librairies SNMP, XML et IPMI ne sont pas gênantes dans le cas d’une connexion USB entre le serveur et l’onduleur, on peut les ignorer.

L’onduleur est bien reconnu puisque le driver est déterminé : driver = « nutdrv_qx », et également tous les autres paramètres nécessaire à la conf on pu être lus.

D’après la hardware compatibility list c’est le driver blazer_usb qui est le mieux adapté à cet onduleur.

Dans /etc/nut/ups.conf nous devons mettre les dispositifs UPS à surveiller, dans mon cas un seul onduleur :

[nutdev1]
        driver = "blazer_usb"
        port = "auto"
        vendorid = "0665"
        productid = "5161"
        product = "USB to Serial"
        vendor = "INNO TECH"
        bus = "001"
        device = "006"
        busport = "002"

vim /etc/nut/nut.conf

MODE=standalone

standalone: This mode address a local only configuration, with 1 UPS protecting the local system. This implies to start the 3 NUT layers (driver, upsd and upsmon) and the matching configuration files. This mode can also address UPS redundancy.

Lorsque l’on redémarrer le service nut-server on voit qu’il ne peut pas se connecter à l’appareil UPS

root@pve:~# systemctl status nut-server
● nut-server.service - Network UPS Tools - power devices information server
     Loaded: loaded (/usr/lib/systemd/system/nut-server.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-04-11 09:31:59 CEST; 3s ago
 Invocation: 005d7fc2869a4f58901428d42f3d8cb6
    Process: 722128 ExecStartPre=/usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/nut-common-tmpfiles.conf (code=exited, status=0/SUCCESS)
   Main PID: 722130 (upsd)
      Tasks: 1 (limit: 74144)
     Memory: 520K (peak: 2M)
        CPU: 14ms
     CGroup: /system.slice/nut-server.service
             └─722130 /lib/nut/upsd -F

Apr 11 09:31:59 pve upsd[722130]: Can't connect to UPS [nutdev1] (blazer_usb-nutdev1): No such file or directory
Apr 11 09:31:59 pve upsd[722130]: Found 1 UPS defined in ups.conf
Apr 11 09:31:59 pve nut-server[722130]: Running as foreground process, not saving a PID file
Apr 11 09:31:59 pve nut-server[722130]: upsnotify: notify about state 2 with libsystemd: was requested, but not running as a service unit now, will >
Apr 11 09:31:59 pve nut-server[722130]: upsnotify: failed to notify about state 2: no notification tech defined, will not spam more about it
Apr 11 09:31:59 pve nut-server[722130]: upsnotify: logged the systemd watchdog situation once, will not spam more about it
Apr 11 09:31:59 pve upsd[722130]: Running as foreground process, not saving a PID file
Apr 11 09:31:59 pve upsd[722130]: upsnotify: notify about state 2 with libsystemd: was requested, but not running as a service unit now, will not sp>
Apr 11 09:31:59 pve upsd[722130]: upsnotify: failed to notify about state 2: no notification tech defined, will not spam more about it
Apr 11 09:31:59 pve upsd[722130]: upsnotify: logged the systemd watchdog situation once, will not spam more about it

D’après la documentation suivante l’erreur est causée car le périphérique n’appartient pas au groupe nut :

root@pve:~# ls -l /dev/bus/usb/001/006
crw-rw-r-- 1 root root 189, 5 Apr 11 09:22 /dev/bus/usb/001/006

On change le groupe :

root@pve:~# chgrp nut /dev/bus/usb/001/006
root@pve:~# ls -l /dev/bus/usb/001/006
crw-rw-r-- 1 root nut 189, 5 Apr 11 09:22 /dev/bus/usb/001/006
root@pve:~# systemctl restart nut-server
root@pve:~# systemctl status nut-server

001 est le numéro de bus
006
est le numéro de périphérique

On peut tester que la connexion à l’appareil UPS se fait correctement :

root@pve:~# upsc nutdev1
Init SSL without certificate database
battery.charge: 100
battery.voltage: 13.90
battery.voltage.high: 13.00
battery.voltage.low: 10.40
battery.voltage.nominal: 12.0
...
ups.status: OL
...

nutdev1 est le nom de l’onduleur que nous avons mentionné dans ups.conf
Si l’on débranche l’onduleur du secteur, ups.status change immédiatement, et battery.charge va progressivement baisser.

Autoboot du service au redémarrage linux :

systemctl enable nut-server
systemctl enable nut-monitor

Le système « primary » est le système qui est connecté directement à l’onduleur.

Le ou les systèmes « secondary » sont les systèmes qui doivent s’arrêter avant le système primary puisqu’ils dependent du serveur nut (primary).

Les systèmes secondaires n’interagissent pas directement avec l’UPS (l’onduleur) mais ils interrogent le process upsd présent sur le système primaire qui lui interroge directement l’onduleur via la connexion USB.

vim /etc/upsd.users

[admin]
        password = mypassword
        actions = SET
        instcmds = ALL

[proxmoxuser]
        password = mypassproxmoxuser
        upsmon primary
[opnsenseuser]
        password = mypassopnsenseuser
        upsmon secondary

2 types d’utilisateurs :

  • utilisateurs destinés à l’administration manuelle de l’UPS, par exemple ici admin

actions = SET : Permet de modifier des paramètres de l’onduleur (ex: sensibilité, délais)
instcmds = ALL : Permet d’exécuter des commandes instantanées (ex: shutdown.return, load.off)
Cas d’usage : pour configurer ou administrer l’onduleur depuis la ligne de commande

  • utilisateurs de monitoring (= pour upsmon), ici proxmoxuser et opnsenseuser

Surveillance et gestion des arrêts : utilisé par le démon upsmon pour recevoir des alertes et déclencher des arrêts propres en cas de coupure.
Ne peut pas modifier les paramètres de l’onduleur (sauf si explicitement autorisé).

vim upsd.conf

LISTEN localhost 3493
LISTEN 127.0.0.1 3493
LISTEN 10.0.10.101 3493

10.0.10.101 est l’ip de proxmox lui même, c’est afin qu’il autorise les requêtes à destination de 10.0.10.101:3493 donc les requêtes de clients NUT qui essaierai de l’atteindre puisqu’il est serveur NUT.

Ensuite :

systemctl restart nut-monitor
systemctl restart nut-server

vim /etc/upsmon.conf

MONITOR nutdev1@localhost 1 proxmoxuser mypassproxmoxuser primary
SHUTDOWNCMD "/sbin/shutdown -h +0"  # Commande d'arrêt de Proxmox
POWERDOWNFLAG /etc/killpower  # POWERDOWNFLAG - Flag file for forcing UPS shutdown on the primary system

FINALDELAY 5 – last sleep interval before shutting down the system => On a primary, upsmon will wait this long after sending the NOTIFY_SHUTDOWN before executing your SHUTDOWNCMD => proxmox n’attendra pas les clients pour s’arrêter, il envoie l’information aux clients NUT puis 5 secondes après que ces derniers ont acquiescé il s’arrêtera.

Notifications d’évènements sur votre UPS

Pour être notifiés des évènements de l’UPS, rajoutez les lignes suivantes dans /etc/upsmon.conf du serveur nut :

NOTIFYCMD /usr/bin/notifyme_ups_event.sh

NOTIFYFLAG ONLINE     SYSLOG+WALL+EXEC
NOTIFYFLAG ONBATT     SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT    SYSLOG+WALL+EXEC
NOTIFYFLAG FSD        SYSLOG+WALL+EXEC
NOTIFYFLAG COMMOK     SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD    SYSLOG+WALL+EXEC
NOTIFYFLAG SHUTDOWN   SYSLOG+WALL+EXEC
NOTIFYFLAG REPLBATT   SYSLOG+WALL+EXEC
NOTIFYFLAG NOCOMM     SYSLOG+WALL+EXEC
NOTIFYFLAG NOPARENT   SYSLOG+WALL+EXEC

EXEC => exécute NOTIFYCMD

vim /usr/bin/notifyme_ups_event.sh

#!/bin/bash
#
# This script send an email to an existing smtp server

# Read of parameter
HOST=yoursmtp_server
MAIL_FROM=no-reply@codetodevops.com
MAIL_TO=nfourniol@novrh.com;nfourniol@gmail.com
SUBJECT="Notification UPS"
CONTENT=$NOTIFYTYPE

CLEAN_MAIL_TO=$(echo $MAIL_TO | sed "s/'//g")       # Remove simple quote present in MAIL_TO
ESCAPED_CONTENT=$(echo "$CONTENT" | sed 's/"/\"/g') # Escape double quotes
IFS=';'
for email in $CLEAN_MAIL_TO
do
  curl smtp://${HOST} --mail-from "$MAIL_FROM" --mail-rcpt "$email" -T <(echo -e "From: ${MAIL_FROM}\nTo: ${email}\nContent-type: text/plain;charset=utf-8\nSubject: ${SUBJECT}\n\n${ESCAPED_CONTENT}")
done
IFS=$' \t\n'

« The environment string NOTIFYTYPE will contain the type string of whatever caused this event to happen. »

chmod 770 /usr/bin/notifyme_ups_event.sh && chown nut: /usr/bin/notifyme_ups_event.sh

Installation de NUT client sur opnsense

Installation et configuration de NUT client sur opnsense

Dans le menu Services d’opnsense apparaît un nouveau menu « NUT » s’il n’apparaît pas rafraîchir la page (Ctrl+F5).

Le service Mode est netclient car nous sommes dans le cas d’un client NUT.

Il y a actuellement un bug dans l’interface graphique du plugin NUT on devrait pouvoir indiquer l’ip du serveur NUT et le port d’écoute du serveur NUT.
Donc en attendant que cela soit corrigé, vim /etc/local/etc/nut/upsmon.conf

# Please don't modify this file as your changes might be overwritten with
# the next update.
#
MONITOR nutdev1 1 opnsenseuser mypassopnsenseuser master
SHUTDOWNCMD "/usr/local/etc/rc.halt"
POWERDOWNFLAG /etc/killpower

Ensuite redémarrez nut client via l’interface graphique du plugin nut (en haut à droite) :

Quand on clique sur apply ça écrase les fichiers modifiés dans /usr/local/etc/nut/ ==> donc il est préférable de sauvegarder les fichiers modifiés :

cp upsmon.conf upsmon.conf.backup

On vérifie que opnsense peut demander les informations ups au serveur nut présents sur proxmox 10.0.10.101 :

root@OPNsense:/usr/local/etc/nut # upsc nutdev1@10.0.10.101
battery.charge: 100
battery.voltage: 13.90
...

Quelques tests

  • on peut débrancher la batterie et vérifier :
    • qu’on reçoit une notification d’évènement UPS
    • que la commande upsc nutdev1 affiche un status différent de 0L
  • on peut tester l’arrêt des serveur : upsmon -c fsd
    • un évènement logiciel demandant l’arrêt est envoyé à tous les process upsmon (donc primary et secondary)
    • ça n’affecte pas l’onduleur, ça va simplement simuler l’évènement arrêt des serveurs pour tous les process upsmon qui écoutent, et donc on peut vérifier que le primary et les secondary s’arrêtent
  • tester la connexion du client nut au serveur nut :
root@OPNsense:/usr/local/etc/nut # upscmd -l nutdev1@10.0.10.101
Instant commands supported on UPS [nutdev1]:

beeper.toggle - Toggle the UPS beeper
driver.killpower - Tell the driver daemon to initiate UPS shutdown; should be unlocked with driver.flag.allow_killpower option or variable setting
driver.reload - Reload running driver configuration from the file system (only works for changes in some options)
driver.reload-or-error - Reload running driver configuration from the file system (only works for changes in some options); return an error if something changed and could not be applied live (so the caller can restart it with new options)
driver.reload-or-exit - Reload running driver configuration from the file system (only works for changes in some options); exit the running driver if something changed and could not be applied live (so service management framework can restart it with new options)
load.off - Turn off the load immediately
load.on - Turn on the load immediately
shutdown.return - Turn off the load and return when power is back
shutdown.stayoff - Turn off the load and remain off
shutdown.stop - Stop a shutdown in progress
test.battery.start - Start a battery test
test.battery.start.deep - Start a deep battery test
test.battery.start.quick - Start a quick battery test
test.battery.stop - Stop the battery test

on voit là la liste des commandes que supportent cet ups. On peut essayer ceci :


root@OPNsense:/usr/local/etc/nut # upscmd -u mypassopnsenseuser nutdev1@10.0.10.101 beeper.togglebeeper.toggle
Unexpected response from upsd: ERR ACCESS-DENIED

Cependant l’erreur que l’on rencontre est due au fait que l’utilisateur mypassopnsenseuser n’a pas ls droits d’exécutions de cette commande on pourrait faire un test et autoriser cette commande beeper.toggle dans upsd.users du serveur nut (proxmox) :

[opnsenseuser]
        password = mypassopnsenseuser
        upsmon secondary
        instcmds = beeper.toggle

Et ainsi on aurait alors :

root@OPNsense:/usr/local/etc/nut # upscmd -u mypassopnsenseuser nutdev1@10.0.10.101 beeper.togglebeeper.toggle
OK