USV mit NUT unter openSUSE Tumbleweed einrichten — inklusive SELinux
Eine USV per USB an den Rechner hängen und dafür sorgen, dass er bei Stromausfall rechtzeitig herunterfährt: klingt nach einer Viertelstunde Arbeit. Unter openSUSE Tumbleweed mit aktivem SELinux waren es bei mir eher drei Stunden, weil gewisse Details nirgends dokumentiert sind und die von OpenSUSE bereitgestellte Anleitung in die Jahre gekommen ist.
Dieses Martyrium möchte ich euch gerne ersparen. Die Nachfolgende Anleitung stelle ich euch daher zur Verfügung und hoffe, dass sie bei euch auch funktioniert.
Diese Anleitung beschreibt den kompletten Weg für den Standalone-Betrieb: Eine USV, ein Rechner, kein Netzwerkzugriff durch andere Systeme. Getestet mit NUT 2.8.5 und einer APC Back-UPS RS 900G, gilt aber unverändert für jede andere USV mit usbhid-ups.
Das Wichtigste vorweg: upsmon kennt keine Prozentschwelle
Der häufigste Irrtum beim Einstieg. upsmon reagiert ausschließlich auf zwei Flags, die die USV beziehungsweise der Treiber liefert: OB (Netzbetrieb ausgefallen) und LB (Akku fast leer). Eine Angabe wie „fahre bei 25 % herunter" gibt es in upsmon.conf nicht.
Die Schwelle wird deshalb dort konfiguriert, wo das LB-Flag entsteht — im Treiber, also in ups.conf.
Schritt 1: Installation
sudo zypper install nut
Die Konfiguration liegt auf openSUSE unter /etc/ups/, nicht unter /etc/nut/ wie bei Debian-basierten Distributionen. Fast alle Anleitungen im Netz nennen den Debian-Pfad — das ist die erste Stolperfalle.
Als Nächstes prüfen, ob die USV überhaupt erkannt wird:
lsusb
sudo nut-scanner -U
nut-scanner schlägt den passenden Treiber vor. Bei USB-USVen ist das praktisch immer usbhid-ups.
Schritt 2: /etc/ups/ups.conf
Hier wird der Treiber konfiguriert und die Abschaltschwelle festgelegt:
maxretry = 3
[usv]
driver = usbhid-ups
port = auto
desc = "APC Back-UPS RS 900G"
offdelay = 30
ondelay = 30
ignorelb
override.battery.charge.low = 25
override.battery.runtime.low = 120
Der Name in eckigen Klammern ist frei wählbar, wird aber später an mehreren Stellen wieder gebraucht — kurz und ohne Sonderzeichen halten.
Die drei entscheidenden Zeilen:
ignorelbweist den Treiber an, das Hardware-LB-Flag der USV zu ignorieren und stattdessen selbst zu entscheiden.override.battery.charge.low = 25setztLB, sobald der Ladezustand unter 25 % fällt.override.battery.runtime.low = 120setztLBzusätzlich, sobald weniger als 120 Sekunden Restlaufzeit gemeldet werden.
Beide Bedingungen sind ODER-verknüpft. Die Laufzeitschwelle ist ein wichtiges Sicherheitsnetz: Unter hoher Last können 25 % Restladung deutlich weniger Zeit bedeuten als im Leerlauf.
Ein Warnhinweis zu ignorelb: Meldet die USV weder battery.charge noch battery.runtime, wird LB nie ausgelöst und das System fährt gar nicht herunter. Nach Schritt 8 unbedingt kontrollieren, dass beide Werte vorhanden sind.
Schritt 3: /etc/ups/nut.conf
MODE=standalone
standalone bedeutet: upsd läuft, lauscht aber nur auf localhost. Genau richtig, wenn kein weiteres System auf diese USV zugreifen soll.
Schritt 4: /etc/ups/upsd.users
NUT hat eine eigene, interne Benutzerverwaltung. Diese Benutzer haben nichts mit Linux-Systembenutzern zu tun — das Passwort wird hier frei gewählt:
[upsmon]
password = EinSicheresPasswort
upsmon primary
Zwei Fallen:
- Das Trennzeichen ist ein Gleichheitszeichen, kein Doppelpunkt.
- Enthält das Passwort ein
#, schneidet der Parser ab dort alles ab. Für den ersten Durchlauf ein rein alphanumerisches Passwort verwenden.
primary ist auch im Standalone-Betrieb korrekt. Es bedeutet lediglich, dass dieses System die USV direkt am Port hängen hat.
Schritt 5: /etc/ups/upsmon.conf
MONITOR usv@localhost 1 upsmon EinSicheresPasswort primary
MINSUPPLIES 1
SHUTDOWNCMD "/usr/sbin/shutdown -h +0"
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
Die MONITOR-Zeile hat sechs Felder und die Reihenfolge ist bindend:
MONITOR <name>@<host> <powervalue> <benutzer> <passwort> primary
Fehlt ein Feld, interpretiert NUT 2.8 die Zeile als veraltetes Format und verweigert den Start mit Unable to use old-style MONITOR line without a username. Benutzername und Passwort müssen exakt dem entsprechen, was in upsd.users steht.
SHUTDOWNCMD muss in Anführungszeichen stehen, sonst zerlegt der Parser den Befehl an den Leerzeichen.
Die Standardkonfiguration verwendet WALL für Benachrichtigungen. Auf einem Desktop mit Wayland-Session erreicht das niemanden und produziert nur Fehler im Journal — deshalb oben SYSLOG+EXEC. Wer einen lokalen Mailversand eingerichtet hat, ergänzt sinnvollerweise ein NOTIFYCMD, das eine Mail verschickt.
Schritt 6: Dateirechte
Drei Dateien enthalten Zugangsdaten. Auf openSUSE ist die zuständige Gruppe upsd — nicht nut, wie in vielen Anleitungen zu lesen:
sudo chown root:upsd /etc/ups/upsd.conf /etc/ups/upsd.users /etc/ups/upsmon.conf
sudo chmod 640 /etc/ups/upsd.conf /etc/ups/upsd.users /etc/ups/upsmon.conf
Schritt 7: Die beiden openSUSE-spezifischen Stolpersteine
Hier verstecken sich die Probleme, die den größten Teil der Zeit kosten. Beide erzeugen ein Permission denied, obwohl bei oberflächlicher Prüfung alle Rechte korrekt aussehen.
7a: Gruppenmitgliedschaft für den USB-Zugriff
Das NUT-Paket liefert udev-Regeln mit, die den USB-Gerätenoten der Gruppe daemon zuweisen:
ATTR{idVendor}=="051d", ATTR{idProduct}=="0002", MODE="664", GROUP="daemon"
Der Treiber legt beim Start die Root-Rechte ab und wechselt auf den Benutzer upsd. Dessen primäre Gruppe ist aber upsd, nicht daemon. Ergebnis: Ein Prozess in Gruppe upsd steht vor einem Gerät der Gruppe daemon — kein Zugriff.
Die Fehlermeldung lautet:
libusb1: Could not open any HID devices: insufficient permissions on everything
No matching HID UPS found
Das ist irreführend, weil es nach einem fehlenden Treiber oder einer fehlenden udev-Regel klingt. Die Lösung:
sudo usermod -aG daemon upsd
id upsd
Die Ausgabe muss 453(upsd),2(daemon) enthalten.
Wichtig: Keine eigene udev-Regel schreiben. Die mitgelieferte Regel ist korrekt und deckt alle gängigen Geräte ab. Sie greift bei jedem USB-Port und nach jedem Reboot, weil sie auf Vendor- und Product-ID matcht, nicht auf einen Gerätepfad.
7b: SELinux-Kontext für das State-Verzeichnis
Nach 7a öffnet der Treiber die USV erfolgreich, scheitert aber beim nächsten Schritt:
Fatal error: unable to create listener socket
bind /var/lib/ups/usbhid-ups-usv failed: Permission denied
Die Dateirechte auf /var/lib/ups sind korrekt (upsd:daemon, 770), der Prozess läuft als Eigentümer — und trotzdem verweigert das System den Zugriff. Der Grund steht im Audit-Log:
sudo ausearch -m avc -ts recent
avc: denied { write } for comm="usbhid-ups"
scontext=system_u:system_r:nut_upsdrvctl_t:s0
tcontext=system_u:object_r:var_lib_t:s0 tclass=dir
Die SELinux-Policy kennt für NUT-Laufzeitdaten ausschließlich den Pfad /run/nut mit dem Typ nut_var_run_t. openSUSE baut NUT aber mit /var/lib/ups als State-Verzeichnis. Dieser Pfad kommt in der Policy nicht vor, trägt deshalb den generischen Typ var_lib_t — und der Schreibzugriff wird blockiert.
Die saubere Lösung ist eine Dateikontext-Regel:
sudo semanage fcontext -a -t nut_var_run_t '/var/lib/ups(/.*)?'
sudo restorecon -Rv /var/lib/ups
ls -Zd /var/lib/ups
Danach muss dort nut_var_run_t stehen. Falls semanage fehlt:
sudo zypper install policycoreutils-python-utils
Bewusst nicht empfohlen: ein Policy-Modul über audit2allow. Das würde dem Treiber Schreibzugriff auf sämtliche var_lib_t-Verzeichnisse erlauben — deutlich weitreichender als nötig, wenn das eigentliche Problem nur ein falsch etikettiertes Verzeichnis ist. Ebenso wenig ist setenforce 0 eine Lösung; als kurzer Diagnoseschritt in Ordnung, danach sofort wieder setenforce 1.
Die semanage-Regel landet in der lokalen Policy-Datenbank und übersteht Paketupdates und Reboots. Bei einer Neuinstallation muss sie erneut gesetzt werden — ein guter Kandidat für die eigene Ansible-Rolle:
- name: SELinux-Kontext für NUT-State-Verzeichnis
community.general.sefcontext:
target: '/var/lib/ups(/.*)?'
setype: nut_var_run_t
state: present
notify: restorecon ups
Schritt 8: Dienste starten
NUT 2.8 erzeugt über den nut-driver-enumerator für jede Sektion aus ups.conf eine eigene systemd-Unit. Nach Änderungen an ups.conf muss der Enumerator neu laufen, sonst existiert die Unit nicht oder ist veraltet:
sudo systemctl restart nut-driver-enumerator.service
systemctl list-units 'nut-driver@*'
Dann in dieser Reihenfolge:
sudo systemctl enable --now nut-driver@usv nut-server nut-monitor
Den Treiber nicht mit upsdrvctl starten — auf Systemen mit systemd führt das zu Konflikten mit den Unit-Instanzen. Falls doch einmal manuell nötig, upsdrvsvcctl verwenden.
Schritt 9: Kontrolle
upsc usv
Die Ausgabe sollte unter anderem enthalten:
battery.charge: 100
battery.charge.low: 25
battery.runtime: 905
battery.runtime.low: 120
driver.flag.ignorelb: enabled
ups.status: OL
Entscheidend sind driver.flag.ignorelb: enabled sowie die tatsächlich übernommenen Schwellwerte. ups.status: OL bedeutet Netzbetrieb.
Und der Monitor:
systemctl status nut-monitor
Hier darf kein ERR ACCESS-DENIED mehr auftauchen. Erscheint es doch, stimmen Benutzername oder Passwort zwischen upsd.users und der MONITOR-Zeile nicht überein. upsd liest upsd.users nur beim Start — nach einer Korrektur also nut-server neu starten, nicht nur nut-monitor.
Dass in der Prozessliste zwei upsmon-Prozesse erscheinen, ist beabsichtigt: Der root-Prozess löst später den Shutdown aus, die eigentliche Überwachung läuft unprivilegiert als upsd.
Schritt 10: Funktionstest
Den Netzstecker der USV ziehen und beobachten:
watch -n 2 'upsc usv ups.status battery.charge'
Der Status muss binnen weniger Sekunden auf OB wechseln. Ein vollständiger Test bis zur Abschaltung dauert je nach Akkukapazität allerdings lange. Wer den kompletten Ablauf inklusive Shutdown verifizieren will, setzt override.battery.charge.low vorübergehend auf 95, testet, und stellt anschließend auf den gewünschten Wert zurück.
Die richtige Schwelle wählen
25 % ist ein brauchbarer Startwert, aber keine allgemeingültige Empfehlung. Entscheidend ist, wie viel Restlaufzeit dahintersteht. Nach ein paar Tagen Betrieb lohnt sich der Blick auf battery.runtime unter realistischer Last: Bleiben bei der gewählten Ladeschwelle weniger als etwa zwei Minuten, sollte die Schwelle höher.
Auf einem Rechner mit leistungsstarker Grafikkarte kann der Unterschied zwischen Leerlauf und Volllast Faktor drei oder mehr betragen. Genau deshalb ist die zusätzliche runtime.low-Schwelle aus Schritt 2 keine Spielerei, sondern greift in genau diesem Fall.
Ein letzter Blick lohnt sich auf battery.mfr.date in der upsc-Ausgabe. Bleiakkus in USVen halten typischerweise drei bis fünf Jahre. Steht dort ein Datum jenseits dieser Spanne, ist die gemeldete Restlaufzeit mit Vorsicht zu genießen — unabhängig davon, was die Software anzeigt.
Zusammenfassung der Fallstricke
| Symptom | Ursache | Lösung |
|---|---|---|
| Konfigurationsdateien nicht auffindbar | openSUSE nutzt /etc/ups/ statt /etc/nut/ |
Pfad anpassen |
chown: ungültige Gruppe 'nut' |
Gruppe heißt auf openSUSE upsd |
chown root:upsd |
Unable to use old-style MONITOR line |
MONITOR-Zeile hat weniger als sechs Felder |
Benutzername ergänzen |
insufficient permissions on everything |
upsd ist nicht in Gruppe daemon |
usermod -aG daemon upsd |
bind ... failed: Permission denied |
SELinux-Kontext var_lib_t statt nut_var_run_t |
semanage fcontext + restorecon |
ERR ACCESS-DENIED |
Passwort stimmt nicht oder upsd nicht neu gestartet |
upsd.users prüfen, nut-server neu starten |
wall: Kommando nicht gefunden |
WALL in den NOTIFYFLAGS |
auf SYSLOG+EXEC umstellen |