14 Commits
Author SHA1 Message Date
alientim bc360a0b7f Produktiver WSGI-Server (Gunicorn) statt Flasks Entwicklungsserver
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.
2026-08-14 09:09:57 +02:00
alientim 9b3051c984 VERSION: 1.0.1
Fortlaufendes Release nach v1.0.0 -- Schema unveraendert (SCHEMA_VERSION
weiterhin 1), bereits installierte v1.0.0-Hosts aktualisieren sich beim
naechsten update.sh-Lauf per In-Place-Update (Datenbank bleibt erhalten).
2026-08-14 01:06:22 +02:00
alientim 6dd412c6e1 Import: 'database is locked' bei LDAP-/DHCP-Import behoben; Wartungs-Update mit Einzelschritten + Fehler-Log
_import_ldap()/_import_dhcp() riefen bislang set_setting() auf, das eine
EIGENE sqlite3-Verbindung oeffnet. import_apply() ruft mehrere
IMPORT_APPLIERS nacheinander auf DERSELBEN, noch offenen Verbindung auf
und committet erst am Ende -- war zu diesem Zeitpunkt (z.B. durch eine
vorher im selben Import verarbeitete Kategorie) bereits eine Schreib-
Transaktion offen, blockierte set_settings()' eigene Verbindung darauf,
bis 'sqlite3.OperationalError: database is locked' auftrat (live in
Produktion reproduziert). Beide Funktionen schreiben jetzt direkt auf
die uebergebene Verbindung. Isoliert reproduziert und verifiziert (alter
Code: Lock-Fehler: neuer Code: fehlerfrei).

Wartungs-Update fuer Linux-Clients (_apt_upgrade_command) außerdem
komplett neu aufgebaut: statt einer einzigen, mit '&&' verketteten
Befehlskette laufen jetzt einzeln benannte Schritte (apt update ->
initramfs MODULES=most dauerhaft in initramfs.conf setzen -> apt upgrade
-> apt full-upgrade -> apt clean -> apt autoremove), jeder mit eigenem
'Schritt N (...) -> erfolgreich/fehlgeschlagen (Exit-Code ...)'-Marker im
Live-Fenster (mit Zeitstempel je Zeile, wie gehabt). Bricht beim ersten
fehlgeschlagenen Schritt sofort ab. Bei einem mit Exit-Code
fehlgeschlagenen Update wird zusaetzlich eine Fehler-Logdatei unter
/var/log/tesm/clientupdate/<clientname>.error geschrieben (ueberschreibt
eine vorherige aus einem frueheren Fehlschlag desselben Clients).
2026-08-14 01:00:15 +02:00
alientim 3c3fbd8f23 update.sh: In-Place-Update statt Fresh-Install, wenn sich das DB-Schema nicht geändert hat
Neue Datei SCHEMA_VERSION (neben VERSION) markiert den DB-Schema-Stand.
update.sh laedt/entpackt das Release-Paket jetzt VOR der Bestaetigungs-
abfrage und vergleicht die SCHEMA_VERSION der bestehenden Installation
mit der des neuen Pakets:

- Stimmen beide ueberein (und existiert bereits eine sqlite.db) --
  In-Place-Update: /srv/tesm bleibt bestehen, install.sh rsynct nur den
  neuen Code darueber (sqlite.db/fernet.key/secret.key waren dort schon
  immer von rsync ausgeschlossen). Geraete/Switche/Zugangsdaten/Benutzer/
  Einstellungen bleiben vollstaendig erhalten, kein manuelles Neuanlegen
  oder Re-Import noetig.
- Unterscheiden sie sich (oder ist eine der beiden Versionen unbekannt,
  z.B. eine Installation von vor dieser Aenderung) -- weiterhin der
  bisherige sichere Weg: /srv/tesm wird beiseite verschoben (nicht
  geloescht) und komplett frisch installiert.

/srv/poe_manager (voellig andere App/Schema) wird davon unabhaengig
immer beiseite verschoben, nie in-place uebernommen.

create_db.py bekommt einen Hinweis-Kommentar, SCHEMA_VERSION bei jeder
Schema-Aenderung mit hochzuzaehlen.
2026-08-14 00:20:52 +02:00
alientim d72db26a86 Versionsnummer in der UI anzeigen (Login-Seite + Sidebar-Footer)
VERSION-Datei (neben app.py, wird von install.sh 1:1 nach /srv/tesm
mitkopiert) plus Context-Processor inject_tesm_version() -- die
zugehoerige app.py-Aenderung war bereits im vorherigen Commit
enthalten. Damit laesst sich der installierte Stand direkt in der
Oberflaeche ablesen, ohne Datei-Zeitstempel oder Commit-Hashes
vergleichen zu muessen.
2026-08-13 23:56:32 +02:00
alientim 0b9b92a228 Wartung/Update: MODULES=most beim apt upgrade + autoremove/clean danach
_apt_upgrade_command() setzt jetzt zusaetzlich MODULES=most (initramfs-
tools) fuer den Update-Aufruf -- falls dabei ein neuer Kernel installiert
und ein initramfs neu gebaut wird, landen vorsorglich ALLE verfuegbaren
Treiber darin statt nur die per Abhaengigkeitserkennung ('dep') erkannten,
damit der Client nach einem Kernel-Wechsel zuverlaessig bootet. Am Ende
zusaetzlich 'apt-get autoremove'/'apt-get clean', damit nicht mehr
benoetigte Kernel/Pakete sowie der .deb-Cache nicht auf jedem Client
unbegrenzt Platz belegen. Live gegen einen echten Ubuntu-Client
verifiziert (Exit-Code 0, komplette Kette lief durch).
2026-08-13 23:55:56 +02:00
alientim d066dbf03d SSH-Verbindungstest: 'password' vor 'keyboard-interactive' bevorzugen
Bei einigen Switches/APs (u.a. ArubaOS-Varianten), die Server-seitig
sowohl 'password' als auch 'keyboard-interactive' anbieten, fuehrte
keyboard-interactive zu einem stillen Fehlschlag ganz ohne sichtbaren
Prompt im Browser-Terminal (RFC 4256 erlaubt eine erste Runde ganz ohne
Prompts -- fuer den Nutzer unsichtbar -- gefolgt von sofortigem
Scheitern statt einer zweiten Runde mit dem eigentlichen Passwort-
Prompt). Das erklaerte exakt das gemeldete Symptom: kein Prompt, direkt
'Zugangsdaten falsch'.

'password' wird jetzt bevorzugt, wenn der Server es ueberhaupt anbietet
(Ende-zu-Ende gegen einen echten Host verifiziert: Host-Key-Bestaetigung
-> Passwort-Prompt -> erfolgreicher Login, keine Regression). Die
keyboard-interactive-Handler-Funktion wurde zusaetzlich gehaertet:
title/instructions werden jetzt mit angezeigt (manche Geraete legen den
Hinweistext dort statt in prompt_list ab) und leere prompt_text-Strings
bekommen einen Passwort-Fallback-Text, falls keyboard-interactive doch
der einzig angebotene Weg ist.
2026-08-13 22:30:41 +02:00
alientim 8fc9ce7b27 create_db.py/create_admin.py: frisches Setup auf den vollständig migrierten Schema-Stand bringen
Ein wirklich frisches Setup (create_db.py auf einer neuen Installation)
erzeugte bislang nur den ALLERERSTEN Schema-Stand aus der Projektgeschichte
-- alle seither per _ensure_schema()-Migration nachgerüsteten Spalten und
Tabellen fehlten komplett, bis diese Migration beim ersten App-Start
erfolgreich durchläuft. Live auf einem frisch installierten Produktivsystem
ist genau das nicht passiert: "sqlite3.OperationalError: no such column:
deleted_at" beim Dashboard-Aufruf, weil devices/switches/credentials/
users/groups ohne die Papierkorb-Spalte angelegt wurden. Fehlten außerdem
komplett: dhcp_subnets, dhcp_manual_reservations, service_accounts,
ldap_group_mappings (LDAP/DHCP-Funktionen wären auf einer solchen frischen
Installation gar nicht nutzbar gewesen) sowie dhcp_option_defs.is_standard.

- create_db.py jetzt deckungsgleich mit dem tatsächlichen, vollständig
  migrierten Schema (per .schema-Dump von einer produktiv laufenden Instanz
  verifiziert) -- category/deleted_at auf credentials, deleted_at auf
  switches, credential_id/ssh_port/deleted_at auf devices, deleted_at auf
  users/groups, is_standard auf dhcp_option_defs, plus die vier fehlenden
  Tabellen.
- POE_DB_PATH → TESM_DB_PATH in create_db.py UND create_admin.py (weiteres
  übersehenes Namensrelikt aus der TESM-Umbenennung), Docstring von
  create_admin.py aktualisiert.
- Live verifiziert: komplett frische DB erzeugt, App auf separatem
  Testport dagegen gestartet, alle wesentlichen Routen (Dashboard, Geräte,
  Switche, Zugangsdaten, Wartung, Papierkorb, Benutzer, Gruppen, alle
  Einstellungsseiten, Logs, Konto) liefern 200 ohne einen einzigen Fehler
  im App-Log -- eine frische Installation hängt jetzt an keiner Stelle mehr
  von der Laufzeit-Migration ab, um überhaupt benutzbar zu sein.
2026-08-13 20:53:31 +02:00
alientim 93bcf4f6b6 DHCP: Auto-Write+Neustart nach Änderungen, Lease löschen, Dedup, Autostart-Slider
- Automatisches Schreiben+Neustart: jede relevante Änderung (Reservierung,
  Option, Subnetz, globale Einstellung) ruft jetzt _dhcp_write_and_restart()
  direkt nach dem Speichern auf -- schreibt die Kea-Config neu und startet
  den Dienst neu, FALLS er gerade aktiv ist (kein ungefragtes Hochfahren
  eines bewusst gestoppten Dienstes). Ein manuelles "Datei schreiben" +
  "Neu starten" kann dadurch nicht mehr vergessen werden. Der bisherige
  "Speichern"-Button (write_dhcp_file) restartet jetzt ebenfalls
  automatisch statt nur zu schreiben.
- Leases-Übersicht dedupliziert: Keas memfile-Backend hängt bei jeder
  Verlängerung eine neue Zeile an, statt die alte zu ersetzen -- dieselbe
  Adresse stand oft dutzendfach in der CSV. _dhcp_read_leases() zeigt jetzt
  nur noch die jeweils neueste (per "expire") Zeile pro IP.
- Einzelne Leases löschen (neuer Button + Route): entfernt alle Zeilen
  einer Adresse aus der Lease-CSV und startet den Dienst neu, damit Kea sie
  beim Neuladen nicht aus dem Speicher zurückschreibt.
- Eigenständiger Autostart-Slider (wie beim Aktivieren/Deaktivieren von
  Clients) -- steuert nur systemctl enable/disable, entkoppelt vom
  aktuellen Start/Stopp-Zustand.
- KRITISCH, live gefunden und behoben: die erste Version von
  _dhcp_delete_lease() hat Kea beim Neustart zum Absturz gebracht --
  Pythons csv-Modul schreibt standardmäßig "\r\n"-Zeilenenden, Kea erwartet
  aber reines "\n" und lehnte die Datei mit "invalid column: 'pool_id^M'"
  komplett ab. Zusätzlich hätte das Neuanlegen der Datei als root (der
  App-Prozess) sie root:root statt dem Kea-Dienstnutzer gehören lassen,
  wodurch Kea keine neuen Leases mehr hätte schreiben können. Beides
  gefixt (lineterminator="\n", Eigentümer/Rechte der Originaldatei nach dem
  Schreiben wiederhergestellt) und auf POETEST bestätigt: Dienst übersteht
  den Neustart jetzt, Datei bleibt _kea:_kea, keine ^M mehr.
2026-08-13 19:11:34 +02:00
alientim 4c13127679 Wartung: Update-Ausgabe live streamen, Zeitstempel pro Zeile, Auto-Scroll
- Neue _run_ssh_command_streaming(): liest stdout/stderr zeilenweise WÄHREND
  das SSH-Update noch läuft, statt alles erst nach Abschluss auf einmal zu
  liefern. Jede Zeile bekommt sofort einen eigenen Zeitstempel und wird per
  _maintenance_job_set() live in den Job-Status geschrieben (bereits alle
  2s gepollt) -- Puffer auf die letzten 400 Zeilen begrenzt statt eines
  harten Zeichen-Limits.
- Eindeutiger Abschluss-Marker als letzte Ausgabezeile: "✔ All Updates
  Done" bei Erfolg, "✖ Update abgebrochen/fehlgeschlagen …" bei Fehler --
  vorher endete die Ausgabe einfach mitten im rohen apt-Output.
- Frontend: Ausgabefenster scrollt jetzt automatisch mit, sobald neue
  Zeilen reinkommen ("stick to bottom", solange man nicht selbst
  hochgescrollt hat) und beim ersten Öffnen direkt ans Ende.
- Live auf POETEST verifiziert: Zeilen tragen korrekte Zeitstempel, Update
  endet sichtbar mit "✔ All Updates Done".
2026-08-13 16:02:07 +02:00
alientim 51d105399b Kritischen poe.sh-Pfadbug beheben + verschachteltes <form> in Wartung fixen
- KRITISCH: usr/local/bin/custom/poe.sh wurde bei der TESM-Umbenennung
  übersehen und zeigte weiterhin komplett auf die alten Pfade
  (/srv/poe_manager/sqlite.db, /srv/poe_manager/generate_ips.py,
  /var/log/poe-manager) -- der Ping-Check/PoE-Neustart-Loop
  (tesm-check.service) lief dadurch seit dem Umzug nur noch auf Fehler
  ("No such file or directory"), das Live-Log stand seit gestern 19 Uhr
  still und automatische PoE-Neustarts bei Geräteausfall liefen die
  ganze Zeit ins Leere. Auf POETEST live behoben und verifiziert: Log
  schreibt wieder frische Einträge, Checks laufen wieder durch.
- Wartung: "Update starten" nahm live IMMER zusätzlich das erste Gerät
  mit, auch ganz ohne angehakte Checkbox. Ursache: der "Neustart"-Button
  jeder Zeile saß in einem eigenen <form>, das INNERHALB des großen
  #updateForm verschachtelt war -- ungültiges HTML, wodurch Browser das
  äußere Formular vorzeitig schließen/aufspalten. Behoben, indem jedes
  Neustart-<form> jetzt außerhalb von #updateForm steht und der Button
  es nur per form="reboot-<mac>" referenziert. Live verifiziert: ein
  gezielt einzeln ausgewähltes Gerät startet jetzt ausschließlich sich
  selbst, keine leere Auswahl mehr möglich.
- Zusätzlich defensiv: Checkboxen werden beim Laden der Wartungsseite
  jetzt explizit zurückgesetzt (manche Browser stellen den
  Checked-Zustand nach einem Redirect/Reload sonst aus dem Verlauf
  wieder her).
- Restliche "poe"-Relikte aus der TESM-Umbenennung bereinigt: Export-
  Dateiname (poe_manager_export_… → tesm_export_…), Kommentare
  (poe_web.service → tesm.service) sowie ein für den Nutzer sichtbarer
  Hinweistext in Systemeinstellungen (rpi-check.service → tesm-check.service).
2026-08-13 15:54:40 +02:00
alientim 34d9cdebd6 Netzwerkeinstellungen: Mehrere Interfaces korrekt unterstützen
- Kritischer Bug behoben: _apply_network_config() baute die netplan-
  Override-Datei bei JEDEM Speichern komplett neu auf (nur mit dem gerade
  bearbeiteten Interface) statt bestehende Einträge anderer Interfaces zu
  erhalten -- ein zweites Interface einzurichten hat dadurch die statische
  Konfiguration des ersten aus der Datei geworfen (fiel auf DHCP zurück).
  Jetzt wird die vorhandene Datei eingelesen und nur der Block des
  gespeicherten Interfaces ersetzt/ergänzt.
- Live auf POETEST reproduziert und nach dem Fix verifiziert: enp1s0 verlor
  durch den Bug seine statische 192.168.80.202/24 (fiel auf DHCP zurück),
  während enp3s0 (zweites, neu eingerichtetes Interface) 192.168.82.202/24
  behielt. Nach dem Fix enp1s0 über die echte GUI-Route wiederhergestellt
  (Werte aus dem aktiven DHCP-Lease bestätigt) -- beide Interfaces stehen
  jetzt nebeneinander in derselben netplan-Datei.
- Neuer JSON-Endpunkt /settings/network_state + JS: die Eingabemaske
  (Modus/IP/Prefix/Gateway/DNS) lädt beim Umschalten des Interface-Dropdowns
  jetzt dessen tatsächlichen Ist-Zustand nach, statt weiter die Werte des
  zuvor angezeigten Interfaces zu zeigen.
- Die Übersicht ganz oben auf der Karte zeigt jetzt JEDES aktive Interface
  gleichzeitig (eigene Zeile mit IP/Gateway/Modus/DNS) statt nur das gerade
  im Formular ausgewählte.
- net_interface-Setting wird jetzt tatsächlich persistiert (war vorbereitet,
  aber nie geschrieben), damit die Seite nach einem Reload das zuletzt
  bearbeitete statt immer nur das alphabetisch erste Interface vorbelegt.
2026-08-13 13:00:02 +02:00
alientim 3ab3407a18 Login-Seite überarbeitet, TESM-Logo als echter Hintergrund; SSH-MOTD-Banner mit Live-Daten
- 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.
2026-08-13 09:13:18 +02:00
alientim d1b10bd970 Rebrand to TESM: infra rename, LDAP/AD login, selective import/export, Papierkorb
- Vollständiges Rebranding "PoE Manager" -> TESM (TimEShepManager): neue
  Logos/Favicon (theme-aware), Sidebar/Login-Branding, Copyright-Zeile.
- Infrastruktur-Umbenennung: srv/poe_manager -> srv/tesm, alle POE_*-Env-Vars
  -> TESM_* (POE_SCRIPT bewusst unverändert, echtes PoE-Skript), systemd
  Units poe_web/rpi-check* -> tesm/tesm-check*, nginx-Site, netplan/logrotate
  Configs, Gitea-Repo alientim/Aruba-PoE-Modern -> alientim/tesm.
- LDAP/Active-Directory Same-Sign-On: Search+Bind, AD-Gruppen->App-Gruppen-
  Zuordnung (additiv), Konto-Sperren, AD-Vorab-Suche/-Anlage, eigene
  Einstellungsseite.
- Selektives Import/Export (8 Kategorien, Zwei-Schritt-Vorschau) mit eigenem
  R/E/X-Rechtemodell (X = Export, getrennt von E = Import).
- Papierkorb: Soft-Delete statt Hard-Delete für Geräte/Switche/Zugangsdaten/
  Benutzer/Gruppen (AD-Nutzer ausgenommen), eigene Seite unterhalb Wartung,
  konfigurierbare Aufbewahrungsfrist.
- rpi_ip -> ip Spalten-/Code-Umbenennung (Geräte sind längst nicht mehr auf
  Raspberry Pis beschränkt).
- README auf aktuellen Stand gebracht.
2026-08-12 22:21:52 +02:00