poe.sh: parallele Geräte-Prüfung + echte PoE-Erfolgskontrolle

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 <mac>) 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 11:59:52 +02:00
co-authored by Claude Sonnet 5
parent 3b2a5fc76c
commit d525ef2e0a
2 changed files with 155 additions and 29 deletions
+104 -26
View File
@@ -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