Root Cause (live auf POETEST reproduziert): /var/log/tesm war 0o777
(noch aus einer Zeit vor dem Umstieg auf root-laufende systemd-Units).
logrotate verweigert seit den aktuellen Haertungen jede Rotation in
einem group-/world-writable Verzeichnis ("insecure permissions") komplett
-- live.log wuchs auf POETEST seit der letzten Neuinstallation (9 Tage)
unbegrenzt, /var/lib/logrotate/status hatte fuer tesm ueberhaupt keinen
Eintrag. Direkte Folge: die neue Verlauf-Seite blieb leer, weil nie
live.log.1/.2/... entstanden.
Fix:
- os.chmod(TESM_LOG_DIR) und os.chmod(TESM_CLIENT_UPDATE_LOG_DIR) in
app.py: 0o777 -> 0o755 (tesm.service/tesm-check.service laufen beide
als root, kein anderer Dienstbenutzer schreibt hierher -- Keas eigenes
Log liegt separat unter /var/log/kea/). Laeuft bei jedem App-Start,
heilt bestehende Installationen also automatisch beim naechsten
Neustart/Update.
- install.sh: chmod 777 -> chmod 755 fuer /var/log/tesm bei einer
Frischinstallation, veraltete/falsche Kommentar-Begruendung (angeblich
auch fuer Kea noetig) korrigiert.
- _write_logrotate_config(): zusaetzlich 'su root root' in der
generierten Konfiguration als Absicherung, falls das Verzeichnis aus
irgendeinem Grund wieder zu weit geoeffnet wird.
- VERSION 1.0.5 -> 1.0.6.
Live verifiziert auf Testsystem-Update-Restart: logrotate -d zeigt nach
dem Fix keine 'insecure permissions'-Fehler mehr, ein erzwungener Lauf
(logrotate -f) erzeugt sauber live.log.1/live.log.2.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live gefunden: der 'Schema unveraendert -> kein Backup'-Kurzschluss aus
v1.0.3 liess einen rein CODE-seitigen Fehler im neuen Paket (z.B. ein
kaputtes app.py -- mit dem DB-Schema hat das nichts zu tun) komplett
ungeschuetzt durch. Backup + Health-Check + ggf. Rollback laufen jetzt
bei JEDEM Update einer bestehenden Installation, unabhaengig von
SCHEMA_VERSION -- die bleibt nur noch fuer den Wortlaut der Log-Meldung
relevant (In-Place-Update vs. Migration).
Zusaetzlich beim Rollback selbst: 'mv' durch 'cp -a' beim Zurueckspielen
ersetzt, plus die fehlgeschlagene Installation vorher separat beiseite
gelegt (/srv/tesm-failed-update-<timestamp>) statt geloescht -- vorher
wurde das Backup beim Zurueckspielen selbst zum neuen /srv/tesm und
verschwand dadurch als eigenstaendige Sicherung (widersprach der
eigenen Zusage 'Backup bleibt erhalten').
Live auf der Testbox in allen drei Pfaden verifiziert:
- Erfolgreiches Update: Backup erstellt, Health-Check bestanden,
Backup geloescht, Daten erhalten.
- Migration (kuenstlich abweichende SCHEMA_VERSION, echte Struktur
unveraendert): Backup erstellt, Health-Check bestanden, Backup
geloescht.
- Fehlgeschlagenes Update (absichtlich kaputte app.py): Backup
erstellt, Health-Check schlaegt korrekt fehl, automatischer Rollback
stellt den letzten funktionierenden Zustand wieder her (Dienst aktiv,
Health-Check 200, Datenbank-Canary erhalten), Backup UND
fehlgeschlagene Installation bleiben als zwei getrennte Verzeichnisse
zur Fehlersuche erhalten.
VERSION: 1.0.4 (v1.0.3 hatte die oben beschriebene Luecke und wurde
bereits ausgerollt, daher hier ein neues Release statt eines erneuten
Tags auf v1.0.3).
install.sh erkennt jetzt selbststaendig eine bestehende Installation
(an sqlite.db) und verhaelt sich entsprechend:
- Keine vorhandene Installation -> normale Frischinstallation (wie
bisher).
- Vorhanden, SCHEMA_VERSION unveraendert -> einfaches In-Place-Update,
kein Backup noetig (rsync schliesst sqlite.db ohnehin aus).
- Vorhanden, SCHEMA_VERSION unbekannt/abweichend -> Update MIT
Sicherheitsnetz: die KOMPLETTE bisherige Installation (Code, venv,
Datenbank, Schluessel) wird vorher 1:1 nach
/srv/tesm-backup-pre-migration-<timestamp> kopiert (bewusst nicht nur
die Datenbank -- ein reines DB-Rollback wuerde beim naechsten Start
mit dem NEUEN Code sofort denselben Migrationsfehler wiederholen).
Die eigentliche Schema-Migration uebernimmt danach die App selbst
beim Start (_ensure_schema() in app.py). Kommt der Dienst
anschliessend per HTTP-Check auf /login nachweislich NICHT gesund
hoch, wird automatisch die komplette Sicherung zurueckgespielt und
bleibt zur manuellen Pruefung erhalten. Kommt er gesund hoch, gilt
die Migration als erfolgreich und das Backup wird geloescht.
update.sh delegiert diese gesamte /srv/tesm-spezifische Logik jetzt an
install.sh und ist selbst nur noch fuer das Ablösen AELTERER, TESM
vorausgehender Installationsstufen zustaendig (PoE Manager unter
/srv/poe_manager, das urspruengliche reine Bash-Setup) -- bleibt aber
als bequemer Ein-Skript-Bootstrapper (Download + Entpacken + Aufruf von
install.sh) bestehen, wie gewuenscht. install.sh selbst ist dadurch
jetzt auch OHNE update.sh direkt aus einem entpackten Release-Paket
lauffaehig und erkennt ein Update trotzdem korrekt.
VERSION: 1.0.3 (SCHEMA_VERSION unveraendert, 1 -- keine echte Schema-
Aenderung in diesem Release, In-Place-Update-Pfad bleibt der Normalfall).
tesm.service startete die App bisher direkt per 'python3 app.py', was
Flasks eingebauten Werkzeug-Entwicklungsserver nutzt -- der meldet bei
jedem Start 'WARNING: This is a development server. Do not use it in a
production deployment.' und ist bewusst nicht für Dauerbetrieb gebaut.
Umgestellt auf Gunicorn, mit zwei bewusst gesetzten Einschraenkungen:
- '--workers 1': die App haelt laufende Wartungs-Jobs/Import-Vorschauen
nur im Arbeitsspeicher DES EINEN Prozesses (_maintenance_jobs,
_pending_imports, ...) -- mehrere Worker-Prozesse wuerden diesen
Zustand auf mehrere, sich nicht kennende Prozesse verteilen.
- '--worker-class gthread --threads 16' statt eines async-Workers: das
Browser-SSH-Terminal (flask-sock) belegt pro offener WebSocket-
Verbindung einen blockierenden Thread.
- '--timeout 0': deaktiviert Gunicorns eigenen 30s-Worker-Timeout, der
sonst auch eine offene, aber gerade inaktive SSH-Terminal-Verbindung
killen wuerde.
Live auf POETEST verifiziert: keine WARNING-Zeile mehr, genau 1 Master-
+ 1 Worker-Prozess, alle Kernseiten weiterhin 200, und als kritischer
Regressionstest das WebSocket-SSH-Terminal Ende-zu-Ende gegen einen
echten Host erfolgreich getestet (Host-Key/Passwort-Prompt/Login/Shell).
Dabei einen unabhaengigen Altlast-Fehler in POETESTs venv gefunden und
allgemein in install.sh gefixt: './venv/bin/pip' hat eine zur
Venv-Erstellung fest einprogrammierte Shebang-Zeile -- nach der
historischen Umbenennung 'poe_manager' -> 'tesm' zeigte sie auf einen
nicht mehr existierenden Pfad und schlug mit 'cannot execute: required
file not found' fehl. 'python3 -m pip' umgeht das, da nur der (korrekt
referenzierte) venv-Python-Interpreter selbst gebraucht wird -- betrifft
sonst auch jedes kuenftige In-Place-Update auf einem aehnlich
gewachsenen Host.
"clear" schlägt ohne TERM/Pseudo-Terminal mit Exit-Code 1 fehl ("TERM
environment variable not set") -- z.B. genau dann, wenn install.sh (wie von
update.sh vorgesehen) per SSH exec_command ohne pty aufgerufen wird. Mit
"set -e" hat das die GESAMTE Installation sofort an Zeile 2 abgebrochen,
noch bevor auch nur ein apt-Paket installiert oder eine einzige Zeile ins
Install-Log geschrieben wurde -- kein Fehlertext, kein Hinweis, einfach
Stille. Live reproduziert: update.sh lud das Release-Paket korrekt herunter
und entpackte es, install.sh selbst tat aber schlicht nichts.
Behoben: "clear" ist jetzt bewusst nie fatal (2>/dev/null || true) -- rein
kosmetisch, hat keinen Einfluss auf die eigentliche Installation.
- Login-Seite: WiS-/TESM-Logo und "Melde dich an, um fortzufahren" aus der
Box entfernt; die TESM-Wortmarke mit Funktionszeile ist jetzt der
tatsächliche Seitenhintergrund (absolut positioniert, hinter allem), die
Box liegt zentriert davor. Nebenbei zwei echte Bugs gefunden und behoben:
fehlendes Cache-Busting auf login.html (asset_url() wie in base.html,
sonst hält nginx' 7-Tage-Cache eine alte style.css/app.js fest) und ein
durch den fehlenden align-items-Wert auf die volle Höhe gestrecktes
Login-Formular.
- Neuer SSH-Login-Banner (dynamisches MOTD) mit Live-Flottenstatus statt der
Standard-Ubuntu-Willkommensmeldung: srv/tesm/motd_banner.py liest
Online/Offline- und DHCP-Status aus denselben Quellen wie das
Web-Dashboard (get_device_status()/_dhcp_read_leases()), eingebunden über
etc/update-motd.d/50-tesm. install.sh deaktiviert dabei die Standard-
Ubuntu-MOTD-Skripte (chmod -x, reversibel) und installiert den neuen Hook.
Logging-Infrastruktur (Logs können nach einem Import mit vielen Geräten
schnell vollaufen):
- Vier eigene, rotierte Log-Dateien statt allem in /var/log vermischt:
Live (poe.sh, jetzt eine einzige durchgehend fortgeschriebene Datei
statt vorher ein neues rpi-<timestamp>.log pro Dienst-Neustart),
Änderungen (Spiegel der audit_log-Tabelle), App (Flask-Fehler/
Warnungen, vorher nur im journalctl-Output), Kea-DHCP (eigene
Logging-Konfiguration in der generierten kea-dhcp4.conf)
- Kea-DHCP-Log bewusst NICHT im gemeinsamen Verzeichnis: Keas
AppArmor-Profil erlaubt Schreibzugriff exklusiv auf
/var/log/kea/kea-dhcp4.log (kein Verzeichnis-Wildcard) -- live als
"DENIED ... mknod ... .lock" reproduziert; die App nutzt Keas eigenen,
bereits erlaubten Pfad statt das AppArmor-Profil eines Systemdienstes
aufzuweichen. logrotate deckt trotzdem beide Verzeichnisse mit einer
Regel ab
- Neue Log-Seite "Kea-DHCP" (Logs-Bereich, eigenes Recht logs_kea.view)
- Rotation (Intervall täglich/wöchentlich/monatlich, Aufbewahrung als
Anzahl Rotationen) unter Systemeinstellungen → Logs einstellbar,
generiert /etc/logrotate.d/poe-manager; copytruncate, da keiner der
drei schreibenden Prozesse ein Reopen-Signal implementiert
- Einmalige Migration räumt alte, verwaiste rpi-*.log-Dateien auf
Zeitzone (Systemeinstellungen, mit Hostname in einer Kachel "Host"):
setzt die System-Zeitzone per timedatectl, damit Logs/Änderungsverlauf
die tatsächliche lokale Zeit statt UTC zeigen. time.tzset() direkt nach
dem Setzen lässt die bereits laufende poe_web.service-Instanz die neue
Zeitzone sofort übernehmen, ohne Neustart.
Systemeinstellungen neu sortiert: Host + Netzwerkeinstellungen (beide
Host-Konfiguration) nebeneinander, dann Logs, dann Prüfintervall.
Netzwerkeinstellungen: DNS-Anzeige zeigte bisher den lokalen
systemd-resolved-Stub (127.0.0.53) aus /etc/resolv.conf statt der
echten Server -- jetzt über "resolvectl dns <interface>", inkl. Fix
für eine falsch geparste IPv6-Fortsetzungszeile ohne Label, und
gefiltert auf IPv4 (diese Seite verwaltet nur IPv4-Konfiguration, per
IPv6-Router-Advertisement gelernte Server sind hier irreführend).
Dashboard: die vier Stat-Kacheln (Online/Offline/Deaktiviert/Gesamt)
sind jetzt klickbare Filter (blenden alle anderen Abschnitte aus, Klick
auf die aktive Kachel oder "Gesamt" hebt den Filter auf); jeder
Abschnitt lässt sich unabhängig davon per Klick auf seinen Titel ein-/
ausklappen. Beide Zustände bleiben über die automatischen Live-Updates
hinweg erhalten.
Design-Politur:
- Hauptinhaltsbereich nutzt jetzt die volle verfügbare Breite (kein
Bootstrap-typisches festes max-width mehr) -- Modals bleiben bewusst
bei ihrer kompakten, festen Breite
Akzentfarbe exakt auf das Orange aus dem WiS-Logo abgestimmt
(#ff7100, per Pixel-Sampling ermittelt) statt einer nur ähnlichen,
frei gewählten Nuance
- Abmelden führt zurück auf das (auch ohne Login einsehbare) Dashboard
statt auf die Login-Seite
Live gegen die echte Test-VM verifiziert: alle vier Logs werden
geschrieben, logrotate-Konfiguration korrekt generiert, ein
Zeitzonenwechsel (UTC → Europe/Berlin) wirkte sofort, DNS-Anzeige zeigt
jetzt die echten IPv4-Server (1.1.1.1, 8.8.8.8) statt Stub/IPv6,
Dashboard-Filter/-Collapse und Logout-Redirect per Playwright bestätigt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Auf der Test-VM war 'ping' nicht vorhanden (Minimal-Ubuntu-Image liefert
es nicht immer vor) -- fuer die Erreichbarkeitspruefung der Geraete
(Pruefintervall / rpi-check-Dienst) zwingend noetig. Live auf dem
Testsystem nachinstalliert und verifiziert.
Live auf frischem Testsystem (Ubuntu 24.04, 192.168.80.114) verifiziert:
Skript brach beim Kopieren nach /srv/poe_manager mit 'rsync: command
not found' ab, weil rsync nirgends in der apt-get-install-Zeile stand.
- Neues, eigenständiges Frontend (Sidebar, zentriertes Logo in der Topbar,
Dark/Light-Theme, Karten-Dashboard, Toasts/Modals statt Bootstrap)
- Oeffentliches Kurz-Dashboard ohne Login (Status-Uebersicht)
- Browser-SSH-Terminal (paramiko, plattformunabhaengig) zum Testen von
Switch-Zugangsdaten inkl. interaktiver Host-Key-Bestaetigung
- Granulares Rechtesystem mit Gruppen (Devices/Switches-Berechtigungen)
- Aufgeraeumtes Backend mit konfigurierbaren Pfaden, auto-generierten
Secrets statt hart codierter Werte im Original
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>