disable_poe()/enable_poe() waren waehrend dieser Sitzung um explizite
'timeout { exit 1 }'/'eof { exit 1 }'-Abbrueche 'gehaertet' worden, um
echte SSH-/Kommando-Fehlschlaege vom Aufrufer unterscheidbar zu machen.
Live an Switch 'ST-2000-Sued' (Aruba JL322A 2930M) reproduziert: das
dafuer verwendete Login-Prompt-Pattern -re ".*> $" matcht nur einen
Operator-Prompt ("switchname>"). Landet der SSH-Account (hier: admin)
nach dem Login mit Manager-Rechten direkt im privilegierten Modus
("switchname#"), matcht das Pattern nie -> 5s-Timeout -> exit 1,
obwohl Passwort und Verbindung tatsaechlich in Ordnung waren. Jeder
manuelle wie automatische PoE-Neustart ueber einen Manager-Account
schlug dadurch fehl.
disable_poe()/enable_poe() sind jetzt wieder exakt auf den Stand der
urspruenglichen, in Produktion ueber Jahre bewaehrten Fassung
zurueckgesetzt (keine expliziten timeout/eof-Abbrueche, kein
abschliessendes exit 0) -- nur der Kommentar wurde aktualisiert, um
diesen Vorfall zu dokumentieren.
206 lines
8.5 KiB
Bash
206 lines
8.5 KiB
Bash
#!/bin/bash
|
|
# ============================================================================
|
|
# PoE Device Check Script
|
|
# - prüft Erreichbarkeit von Geräten
|
|
# - startet PoE-Port bei Ausfall neu
|
|
# - loggt Ereignisse
|
|
# ============================================================================
|
|
|
|
# Eine einzige, durchgehend fortgeschriebene Datei statt vorher bei jedem
|
|
# (auch täglich per tesm-check-restart.timer automatisch ausgelösten)
|
|
# Dienst-Neustart ein neues rpi-<timestamp>.log -- Rotation/Aufbewahrung
|
|
# übernimmt jetzt zentral logrotate (siehe app.py: _write_logrotate_config,
|
|
# einstellbar unter Systemeinstellungen → Logs), nicht mehr dieses Skript
|
|
# selbst. Pfad muss mit TESM_LOG_DIR/live.log in app.py übereinstimmen.
|
|
LOG_DIR="/var/log/tesm"
|
|
LOGFILE="$LOG_DIR/live.log"
|
|
mkdir -p "$LOG_DIR"
|
|
touch "$LOGFILE"
|
|
|
|
# Intervall aus DB abrufen
|
|
SLEEP=$(python3 - <<'END'
|
|
import sqlite3
|
|
conn = sqlite3.connect("/srv/tesm/sqlite.db")
|
|
row = conn.execute("SELECT value FROM settings WHERE key='check_interval'").fetchone()
|
|
conn.close()
|
|
print(row[0] if row else 300)
|
|
END
|
|
)
|
|
|
|
SLEEP=${SLEEP:-300}
|
|
|
|
# disable_poe()/enable_poe(): bewusst OHNE expliziten "timeout { exit 1 }"/
|
|
# "eof { exit 1 }"-Abbruch in den einzelnen expect-Schritten -- ein nicht
|
|
# matchender Schritt laesst expect nach Ablauf von "set timeout 5" einfach
|
|
# zur naechsten Zeile weiterlaufen, statt abzubrechen. Das ist absichtlich
|
|
# so belassen: ein Versuch, hier "echte" Fehlererkennung nachzuruesten
|
|
# (Exit-Code 0 nur bei bestaetigtem Erfolg je Schritt), hat sich live als
|
|
# Regression herausgestellt. Grund: das Login-Prompt-Pattern
|
|
# "-re ".*> $"" unten matcht nur ein Operator-Prompt ("switchname>"). Auf
|
|
# ArubaOS-Switches, bei denen der SSH-Account (z.B. "admin") mit
|
|
# Manager-Rechten direkt im privilegierten Modus landet ("switchname#"
|
|
# statt "switchname>", bestaetigt an einem 2930M/"ST-2000-Sued"), matcht
|
|
# das Pattern nie -- mit einem harten Timeout-Abbruch waere das faelschlich
|
|
# als Fehlschlag gewertet worden, obwohl Passwort und Verbindung in
|
|
# Ordnung waren. Diese Funktionen hier sind daher wieder exakt auf den
|
|
# Stand der urspruenglichen, in Produktion bewaehrten Fassung zurueckgesetzt.
|
|
function disable_poe() {
|
|
local switch_ip=$1
|
|
local switch_port=$2
|
|
local username=$3
|
|
local password=$4
|
|
local ssh_port=${5:-22}
|
|
expect <<EOF
|
|
set timeout 5
|
|
spawn ssh -p $ssh_port $username@$switch_ip
|
|
expect {
|
|
"assword:" { send "$password\r"; exp_continue }
|
|
"Press any key" { send "\r"; exp_continue }
|
|
-re ".*> $" { }
|
|
}
|
|
send "configure terminal\r"
|
|
expect "(config)#"
|
|
send "interface $switch_port\r"
|
|
expect "(eth-$switch_port)#"
|
|
send "no power-over-ethernet\r"
|
|
expect "(eth-$switch_port)#"
|
|
send "exit\r"
|
|
expect "(config)#"
|
|
send "exit\r"
|
|
expect "#"
|
|
send "exit\r"
|
|
expect ">"; send "exit\r"
|
|
expect "Do you want to log out (y/n)?" { send "y\r" }
|
|
expect eof
|
|
EOF
|
|
}
|
|
|
|
function enable_poe() {
|
|
local switch_ip=$1
|
|
local switch_port=$2
|
|
local username=$3
|
|
local password=$4
|
|
local ssh_port=${5:-22}
|
|
expect <<EOF
|
|
set timeout 5
|
|
spawn ssh -p $ssh_port $username@$switch_ip
|
|
expect {
|
|
"assword:" { send "$password\r"; exp_continue }
|
|
"Press any key" { send "\r"; exp_continue }
|
|
-re ".*> $" { }
|
|
}
|
|
send "configure terminal\r"
|
|
expect "(config)#"
|
|
send "interface $switch_port\r"
|
|
expect "(eth-$switch_port)#"
|
|
send "power-over-ethernet\r"
|
|
expect "(eth-$switch_port)#"
|
|
send "exit\r"
|
|
expect "(config)#"
|
|
send "exit\r"
|
|
expect "#"
|
|
send "exit\r"
|
|
expect ">"; send "exit\r"
|
|
expect "Do you want to log out (y/n)?" { send "y\r" }
|
|
expect eof
|
|
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/tesm/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 [[ "$mac" != "$target_mac" ]]; then
|
|
continue
|
|
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
|
|
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
|
|
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
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name besitzt keinen PoE-Port." >> "$LOGFILE"
|
|
fi
|
|
exit 0
|
|
done
|
|
}
|
|
|
|
if [[ "$1" == "restart" ]]; then
|
|
if [[ -z "$2" ]]; then
|
|
echo "MAC-Adresse fehlt."
|
|
exit 1
|
|
fi
|
|
manual_restart "$2"
|
|
exit 0
|
|
fi
|
|
|
|
while true; do
|
|
echo "--------------------------------------------------------------------" >> "$LOGFILE"
|
|
|
|
# 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/tesm/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
|