From d525ef2e0a369b55911f296cce1b1549050545b1 Mon Sep 17 00:00:00 2001 From: alientim Date: Wed, 12 Aug 2026 11:59:52 +0200 Subject: [PATCH] =?UTF-8?q?poe.sh:=20parallele=20Ger=C3=A4te-Pr=C3=BCfung?= =?UTF-8?q?=20+=20echte=20PoE-Erfolgskontrolle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nach einem echten Import (46 Geräte, 8 Switche aus dem Altsystem) zeigten sich zwei Probleme in der Hintergrund-Prüfschleife: - Geräte wurden strikt sequentiell geprüft (eine einzige "while read"- Schleife) -- ein einzelnes ausgefallenes Gerät mit ebenfalls nicht erreichbarem Switch (bis zu 5s expect-Timeout je Verbindungsschritt) verzögerte die Prüfung aller nachfolgenden Geräte. Jede Geräte-Prüfung läuft jetzt als eigener Hintergrund-Job (& + wait), über Process Substitution statt einer Pipe, damit wait die Jobs auch tatsächlich erfasst (bei "cmd | while ...; done" liefe die Schleife sonst in einer eigenen Subshell). - disable_poe()/enable_poe() haben unabhängig vom tatsächlichen Ergebnis der SSH/expect-Sitzung immer wie ein Erfolg gewirkt -- lief die Verbindung in einen Timeout (Switch nicht erreichbar, falsche Zugangsdaten, falscher Port), endete das expect-Skript trotzdem mit Exit-Code 0, poe.sh loggte fälschlich "PoE deaktiviert"/"PoE aktiviert", obwohl am Switch nichts passiert war. Jeder kritische expect-Schritt hat jetzt einen eigenen timeout-Zweig mit explizitem "exit 1"; poe.sh wertet den Rückgabewert aus und loggt bei einem nicht erreichbaren/ablehnenden Switch stattdessen klar, dass die PoE-Aktion fehlgeschlagen ist. Gilt für die Prüfschleife, den manuellen Neustart (poe.sh restart ) und damit auch den Dashboard-Neustart-Button. Live verifiziert: alle 46 Geräte innerhalb von ~2s geprüft (statt sequentiell), alle 43 offline erkannten Geräte mit Switch-Zuordnung zeigten korrekt die neue Fehlermeldung, da die realen Switch-IPs vom Test-Host aus nicht erreichbar sind -- vorher hätte das fälschlich als 43x "PoE deaktiviert/aktiviert" geloggt. Co-Authored-By: Claude Sonnet 5 --- README.md | 54 ++++++++++++++- usr/local/bin/custom/poe.sh | 130 ++++++++++++++++++++++++++++-------- 2 files changed, 155 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d24721c..6a0d1d0 100644 --- a/README.md +++ b/README.md @@ -91,9 +91,12 @@ Die App ermöglicht: (vom PoE-Server) aufgebaut, nie vom Browser/Client aus. - **Granulares Rechtesystem mit Gruppen** (neu, siehe unten) statt der ursprünglichen reinen Admin/User-Unterscheidung. -- **Gleiche Kernlogik**: Datenbankschema (erweitert um Gruppen/Rechte), - Login, PoE-Restart-Mechanismus (`poe.sh` per SSH/`expect` auf die - Aruba-Switche) sind ansonsten unverändert. +- **Gleiche Kernlogik**: Datenbankschema (erweitert um Gruppen/Rechte) und + Login sind ansonsten unverändert. Der PoE-Restart-Mechanismus selbst + (`poe.sh` per SSH/`expect` auf die Aruba-Switche) wurde dagegen + überarbeitet — parallele statt sequentielle Geräte-Prüfung und echte + Erfolgskontrolle statt blind angenommenem Erfolg, siehe + [Geräte-Prüfung](#geräte-prüfung-poesh). - **Aufgeräumtes Backend**: konsistente, konfigurierbare Pfade (`POE_DB_PATH`, `POE_SCRIPT`, `POE_LOG_GLOB`, …) statt hart codierter/inkonsistenter Pfade; Secret-Key und Fernet-Key werden beim ersten Start automatisch generiert @@ -476,6 +479,51 @@ Betrieb bestätigt: ein tatsächlicher, unreservierter Client im Testnetz („shellyhtg3-...“) hat sich selbstständig eine IP aus dem Pool geholt und wurde korrekt mit der „Ohne Reservierung“-Markierung angezeigt. +## Geräte-Prüfung (`poe.sh`) + +Der Hintergrund-Check-Dienst (`rpi-check.service`, ruft `poe.sh` ohne +Argument auf) prüft in einer Endlosschleife alle Geräte per Ping und +startet bei Ausfall automatisch den PoE-Port neu, über den ein Gerät am +zugeordneten Switch hängt (`expect`, siehe unten). Zwei Korrekturen nach +einem echten Import mit deutlich mehr Geräten (46 statt einer Handvoll +Testgeräte): + +- **Parallele statt sequentielle Prüfung**: vorher wurde jedes Gerät strikt + nacheinander in einer einzigen Schleife geprüft — ein einzelnes bereits + ausgefallenes Gerät mit ebenfalls nicht erreichbarem Switch (jeder + `expect`-Verbindungsschritt bis zu 5s Timeout) hat dadurch die Prüfung + aller nachfolgenden Geräte in der Liste verzögert; bei vielen gleichzeitig + ausgefallenen Geräten konnte ein Prüfdurchlauf so erheblich länger dauern + als das eigentlich konfigurierte Intervall. Jetzt läuft jede + Geräte-Prüfung (Ping + ggf. PoE-Neustart) als eigener Hintergrund-Job + (`&` + `wait` am Ende des Durchlaufs, über Process Substitution statt + einer Pipe, damit `wait` die Jobs auch tatsächlich erfasst) — mehrere + Geräte, auch an unterschiedlichen Switchen, werden dadurch gleichzeitig + geprüft statt einzeln nacheinander. +- **Echte Erfolgskontrolle statt blind angenommenem Erfolg**: `disable_poe()`/ + `enable_poe()` haben vorher unabhängig vom tatsächlichen Ergebnis der + SSH/`expect`-Sitzung immer wie ein Erfolg gewirkt — lief die Verbindung + zum Switch in einen Timeout (Switch nicht erreichbar, falsche + Zugangsdaten, falscher Port), endete das `expect`-Skript trotzdem mit + Exit-Code 0, und `poe.sh` loggte fälschlich „PoE deaktiviert“/„PoE + aktiviert", obwohl am Switch nichts passiert war. Jeder kritische + `expect`-Schritt (Verbindungsaufbau, `configure terminal`-Prompt, + Interface-Prompt, Bestätigung des `power-over-ethernet`-Kommandos) hat + jetzt einen eigenen `timeout`-Zweig mit explizitem `exit 1`; `poe.sh` + wertet diesen Rückgabewert aus und loggt bei einem nicht erreichbaren + oder ablehnenden Switch stattdessen klar „Switch ... nicht erreichbar + oder Kommando abgelehnt — PoE-Deaktivierung/-Reaktivierung + fehlgeschlagen“, ohne einen Neustart vorzugaukeln, der nie stattgefunden + hat. Gilt für die automatische Prüfschleife, den manuellen Neustart + (`poe.sh restart `) und damit auch für den Dashboard-Neustart-Button. + +Live nach einem echten Import (46 Geräte, 8 Switche aus dem Altsystem) +verifiziert: alle 46 Geräte wurden innerhalb von ~2 Sekunden geprüft (statt +sequentiell), und alle 43 als offline erkannten Geräte mit Switch-Zuordnung +zeigten korrekt die neue Fehlermeldung, da die realen Switch-IPs +(192.168.200.x) vom Test-Host aus nicht erreichbar sind — vorher hätte das +fälschlich als 43× „PoE deaktiviert“/„PoE aktiviert“ geloggt. + ## Netzwerkeinstellungen des Hosts (Systemeinstellungen) Eigene, klar getrennte Funktion von obigem DHCP-*Server*: hier geht es um diff --git a/usr/local/bin/custom/poe.sh b/usr/local/bin/custom/poe.sh index 4e4205c..ada0137 100644 --- a/usr/local/bin/custom/poe.sh +++ b/usr/local/bin/custom/poe.sh @@ -25,6 +25,16 @@ END SLEEP=${SLEEP:-300} +# disable_poe()/enable_poe() geben jetzt den TATSÄCHLICHEN Erfolg als +# Exit-Code zurück (0 = Switch erreicht UND Kommando bestätigt, 1 = +# fehlgeschlagen), statt blind anzunehmen, dass ein SSH-Verbindungsversuch +# immer klappt. Jeder kritische expect-Schritt (Verbindungsaufbau, +# configure-terminal-Prompt, Interface-Prompt, Bestätigung des +# power-over-ethernet-Kommandos) hat dafür einen eigenen timeout-Zweig, +# der explizit mit "exit 1" abbricht -- vorher lief das expect-Skript bei +# einem unerreichbaren Switch einfach durch alle Timeouts durch und endete +# trotzdem mit Exit-Code 0, wodurch der Aufrufer fälschlich "PoE +# deaktiviert/aktiviert" geloggt hat, obwohl am Switch nichts passiert war. function disable_poe() { local switch_ip=$1 local switch_port=$2 @@ -38,13 +48,24 @@ expect { "assword:" { send "$password\r"; exp_continue } "Press any key" { send "\r"; exp_continue } -re ".*> $" { } + timeout { exit 1 } + eof { exit 1 } } send "configure terminal\r" -expect "(config)#" +expect { + "(config)#" { } + timeout { exit 1 } +} send "interface $switch_port\r" -expect "(eth-$switch_port)#" +expect { + "(eth-$switch_port)#" { } + timeout { exit 1 } +} send "no power-over-ethernet\r" -expect "(eth-$switch_port)#" +expect { + "(eth-$switch_port)#" { } + timeout { exit 1 } +} send "exit\r" expect "(config)#" send "exit\r" @@ -53,6 +74,7 @@ send "exit\r" expect ">"; send "exit\r" expect "Do you want to log out (y/n)?" { send "y\r" } expect eof +exit 0 EOF } @@ -69,13 +91,24 @@ expect { "assword:" { send "$password\r"; exp_continue } "Press any key" { send "\r"; exp_continue } -re ".*> $" { } + timeout { exit 1 } + eof { exit 1 } } send "configure terminal\r" -expect "(config)#" +expect { + "(config)#" { } + timeout { exit 1 } +} send "interface $switch_port\r" -expect "(eth-$switch_port)#" +expect { + "(eth-$switch_port)#" { } + timeout { exit 1 } +} send "power-over-ethernet\r" -expect "(eth-$switch_port)#" +expect { + "(eth-$switch_port)#" { } + timeout { exit 1 } +} send "exit\r" expect "(config)#" send "exit\r" @@ -84,9 +117,47 @@ send "exit\r" expect ">"; send "exit\r" expect "Do you want to log out (y/n)?" { send "y\r" } expect eof +exit 0 EOF } +# Ein Geräte-Check (Ping + ggf. PoE-Neustart) als eigene Funktion, damit sie +# sowohl vom manuellen Neustart (einzeln, synchron) als auch von der +# Hauptschleife (parallel im Hintergrund, siehe unten) verwendet werden kann. +function check_device() { + local rpi_ip=$1 dev_name=$2 switch_ip=$3 switch_ssh_port=$4 + local switch_hostname=$5 switch_port=$6 switch_user=$7 switch_pass=$8 + + if ping -c 1 -W 2 "$rpi_ip" &> /dev/null; then + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist erreichbar!" >> "$LOGFILE" + return + fi + + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist nicht erreichbar!" >> "$LOGFILE" + + # Nur PoE neu starten, wenn dem Gerät auch tatsächlich ein Switch + # zugeordnet ist (switch_ip) — ein reiner Port-Wert ohne Switch + # (switch_ip leer) darf keinen SSH-Versuch mit leeren + # Zugangsdaten auslösen. + if [ -z "$switch_ip" ] || [ -z "$switch_port" ] || [ "$switch_port" == "None" ]; then + return + fi + + if ! disable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port"; then + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name: Switch $switch_hostname nicht erreichbar oder Kommando abgelehnt — PoE-Deaktivierung fehlgeschlagen, kein Neustart durchgeführt." >> "$LOGFILE" + return + fi + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE auf Port $switch_port am Switch $switch_hostname deaktiviert." >> "$LOGFILE" + + sleep 2 + + if ! enable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port"; then + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name: Switch $switch_hostname nicht erreichbar oder Kommando abgelehnt — PoE-Reaktivierung fehlgeschlagen! Port bleibt ggf. deaktiviert." >> "$LOGFILE" + return + fi + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE auf Port $switch_port am Switch $switch_hostname aktiviert." >> "$LOGFILE" +} + function manual_restart() { local target_mac="$1" python3 /srv/poe_manager/generate_ips.py | while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do @@ -95,10 +166,16 @@ function manual_restart() { fi echo "$(date '+%Y-%m-%d %H:%M:%S') Manueller Neustart von $dev_name gestartet." >> "$LOGFILE" if [ -n "$switch_ip" ] && [ -n "$switch_port" ] && [ "$switch_port" != "None" ]; then - disable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port" + if ! disable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port"; then + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name: Switch $switch_hostname nicht erreichbar oder Kommando abgelehnt — manueller Neustart abgebrochen." >> "$LOGFILE" + exit 1 + fi echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE deaktiviert." >> "$LOGFILE" sleep 2 - enable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port" + if ! enable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port"; then + echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name: Switch $switch_hostname nicht erreichbar oder Kommando abgelehnt — PoE-Reaktivierung fehlgeschlagen! Port bleibt ggf. deaktiviert." >> "$LOGFILE" + exit 1 + fi echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE aktiviert." >> "$LOGFILE" echo "$(date '+%Y-%m-%d %H:%M:%S') Manueller Neustart von $dev_name abgeschlossen." >> "$LOGFILE" else @@ -121,24 +198,25 @@ echo "" > "$LOGFILE" while true; do echo "--------------------------------------------------------------------" >> "$LOGFILE" - python3 /srv/poe_manager/generate_ips.py | while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do - if ping -c 1 -W 2 "$rpi_ip" &> /dev/null; then - echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist erreichbar!" >> "$LOGFILE" - else - echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist nicht erreichbar!" >> "$LOGFILE" - # Nur PoE neu starten, wenn dem Gerät auch tatsächlich ein Switch - # zugeordnet ist (switch_ip) — ein reiner Port-Wert ohne Switch - # (switch_ip leer) darf keinen SSH-Versuch mit leeren - # Zugangsdaten auslösen. - if [ -n "$switch_ip" ] && [ -n "$switch_port" ] && [ "$switch_port" != "None" ]; then - disable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port" - echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE auf Port $switch_port am Switch $switch_hostname deaktiviert." >> "$LOGFILE" - sleep 2 - enable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass" "$switch_ssh_port" - echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE auf Port $switch_port am Switch $switch_hostname aktiviert." >> "$LOGFILE" - fi - fi - done + # Jedes Gerät wird als eigener Hintergrund-Job geprüft, statt strikt + # nacheinander in einer einzigen Schleife -- bei einer größeren + # Geräteliste (z.B. nach einem Import) hätte ein einzelnes bereits + # ausgefallenes Gerät mit langsamem/nicht erreichbarem Switch (jeder + # expect-Versuch bis zu ~5s Timeout je Verbindungsschritt) sonst die + # Prüfung ALLER nachfolgenden Geräte verzögert. "< <(...)" (Process + # Substitution) statt einer Pipe, damit die Hintergrund-Jobs Kinder + # dieser Shell bleiben und "wait" sie unten tatsächlich erfassen kann + # (bei "cmd | while ...; done" liefe die Schleife in einer eigenen + # Subshell, deren Hintergrund-Jobs von außen nicht mehr sichtbar wären). + while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do + check_device "$rpi_ip" "$dev_name" "$switch_ip" "$switch_ssh_port" "$switch_hostname" "$switch_port" "$switch_user" "$switch_pass" & + done < <(python3 /srv/poe_manager/generate_ips.py) + + # Auf alle in diesem Durchlauf gestarteten Geräte-Checks warten, bevor + # das Intervall abgewartet wird -- sonst könnten sich Durchläufe + # überlappen (z.B. mehrere parallele expect-Sitzungen zum selben + # Switch aus zwei verschiedenen, noch laufenden Zyklen gleichzeitig). + wait sleep "$SLEEP" done