Baut auf licensing.py (Phase 0) auf und schaltet TESM erstmals auf ein lizenziertes Modulmodell um. Standard-Funktionen (Dashboard, Clients, Switche, Zugangsdaten, Benutzer, Gruppen, Auditlog, Live-Log, Verlauf, Import) bleiben immer frei nutzbar. - license_active()/module_licensed()-Gate-Funktionen + In-Memory- Lizenzcache (_load_license, _write_license_file, _delete_license_file), Jinja-Globals fuer Templates. - Vollstaendige Module (DHCP inkl. Kea-Log, Fileshare, Wartung) werden ohne passendes Modul in der Lizenz komplett aus der Navigation ausgeblendet (_nav_key_visible) UND serverseitig auf Routenebene abgewiesen (_require_module) -- ein direkter URL-Aufruf hilft nicht. - 'Sichtbar, aber ohne Lizenz inaktiv': LDAP- und NGINX-Einstellungen, Papierkorb-Bearbeiten, Export sowie das Pruefintervall-Feld (aus dem gemeinsamen Systemeinstellungen-Gate herausgeloest, Hostname/Zeitzone/ Netzwerk/Logs bleiben frei) zeigen ohne Lizenz einen Hinweis statt der Aktion. - Export bleibt als bewusste Ausnahme auch im lizenzlosen Zustand nutzbar, wenn dieser durch eine selbst ausgeloeste Deaktivierung (Systemwechsel) entstanden ist (license_export_grace). - Automatischer PoE-Neustart bei Ausfall wird in poe.sh (nicht in Python) gegatet -- einmal pro Schleifendurchlauf gelesen, an check_device() durchgereicht; der manuelle Neustart-Button (devices.restart) bleibt komplett unangetastet. - Topbar-Lizenzanzeige (Prioritaet: fehlt > abgelaufen > laeuft in <=30 Tagen ab > Heartbeat seit >=14 Tagen ausgeblieben (rein informativ) > alles ok), eigene CSS-Blink-Varianten fuer Rot/Orange (die vorhandene pulse-dot-Animation war fest auf Gruen verdrahtet). - Neue Settings-Unterseite 'Lizenz': Status, Lizenzdatei-Upload, Online- Aktivierung/-Deaktivierung mit automatischem Fallback auf einen manuell auszutauschenden Code (ein Protokoll, zwei Transportwege, siehe licensing.py), Anbieter-Kontaktkarte. - 6-stuendiger Heartbeat-Hintergrundthread (_license_heartbeat_loop, exakt nach dem Vorbild von _audit_archive_loop). Isoliert auf dem Testsystem (192.168.82.51) verifiziert: alle fuenf Topbar-/Sichtbarkeitszustaende (keine/gueltige/bald ablaufende/abgelaufene/ heartbeat-stale Lizenz) per Playwright durchgespielt, kompletter Online- Fallback-auf-Offline-Code-Austausch fuer Aktivierung UND Deaktivierung gegen einen simulierten Master erfolgreich durchlaufen (inkl. Export- Grace-Ausnahme nach Deaktivierung). Bewusst NICHT nach main gemergt/getaggt/ausgerollt: ohne den in Phase 2 noch zu bauenden Master-Lizenzserver kann POETEST noch keine echte Lizenz beziehen -- ein Rollout jetzt wuerde dort DHCP/Fileshare/Wartung/LDAP/ NGINX/Papierkorb/Export/automatischen Neustart sofort abschalten. Bleibt auf dev, bis Phase 2 (Master) + Phase 3 (POETEST-Enterprise-Lizenz) so weit sind, dass beides zusammen ausgerollt werden kann. Teil des Lizenz-/Modulsystems, siehe Plan toasty-twirling-hickey.md (Phase 1 von 3). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
177 lines
6.2 KiB
Bash
177 lines
6.2 KiB
Bash
#!/bin/bash
|
|
LOG_DIR="/var/log/tesm"
|
|
LOGFILE="$LOG_DIR/live.log"
|
|
mkdir -p "$LOG_DIR"
|
|
touch "$LOGFILE"
|
|
|
|
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}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
local license_active=$9
|
|
|
|
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"
|
|
|
|
if [ -z "$switch_ip" ] || [ -z "$switch_port" ] || [ "$switch_port" == "None" ]; then
|
|
return
|
|
fi
|
|
|
|
# Automatischer PoE-Neustart bei Ausfall ist ein lizenzpflichtiges
|
|
# Feature (siehe Lizenzsystem) -- der manuelle Neustart-Button im
|
|
# Dashboard (manual_restart() weiter unten) ruft disable_poe/enable_poe
|
|
# dagegen IMMER direkt auf und bleibt davon bewusst unberührt.
|
|
if [ "$license_active" != "1" ]; then
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name: automatischer PoE-Neustart übersprungen -- keine gültige Lizenz." >> "$LOGFILE"
|
|
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"
|
|
|
|
# Einmal pro Schleifendurchlauf gelesen (wie SLEEP oben beim Skriptstart),
|
|
# nicht pro Gerät -- reicht als Shell-Variable an check_device() durch.
|
|
LICENSE_ACTIVE=$(python3 - <<'END'
|
|
import sys
|
|
sys.path.insert(0, "/srv/tesm")
|
|
from app import license_active
|
|
print("1" if license_active() else "0")
|
|
END
|
|
)
|
|
LICENSE_ACTIVE=${LICENSE_ACTIVE:-0}
|
|
|
|
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" "$LICENSE_ACTIVE" &
|
|
done < <(python3 /srv/tesm/generate_ips.py)
|
|
|
|
wait
|
|
sleep "$SLEEP"
|
|
done
|