_process_activate() hielt beim Ersetzen einer bereits aktiven Lizenz fuer denselben Fingerprint (z.B. Trial durch Enterprise, oder schlicht eine Re-Aktivierung nach einem Systemwechsel) eine offene, noch nicht committete Schreibtransaktion auf 'conn', waehrend es innerhalb der Supersede-Schleife log_action_system() aufrief -- diese Funktion oeffnet INTERN eine EIGENE, zweite Verbindung zur selben Datenbank und versucht sofort zu committen. SQLite erlaubt aber nur einen Schreiber gleichzeitig (kein WAL-Modus): die zweite Verbindung wartet auf die erste, die wiederum auf die zweite wartet, bis das Busy-Timeout (Python-Default 5s) mit 'sqlite3.OperationalError: database is locked' aufgibt. Live reproduziert: sowohl /api/activate (Online) als auch /licenses/manual-code (Offline-Code) schlugen dadurch fehl, sobald ein Kunde eine Lizenz aktivierte, die eine fuer denselben Fingerprint bereits aktive ersetzt -- de facto war jede Aktivierung auf einem bereits bekannten System blockiert. Fix: die betroffenen log_action_system()-Aufrufe in der Supersede- Schleife werden nur noch vorgemerkt (reine Lesezugriffe auf die offene Verbindung sind unproblematisch) und erst NACH conn.commit()/ conn.close() nachgeholt. Zusaetzlich get_db_connection() auf timeout=15 (statt Python-Default 5s) angehoben als generelle Verteidigungslinie gegen kurze, legitime Ueberschneidungen -- ersetzt nicht die eigentliche Regel (niemals eine zweite Verbindung waehrend einer noch offenen Schreibtransaktion committen), ist aber zusaetzliche Sicherheit. Alle anderen log_action()/log_action_system()-Aufrufstellen in dieser Session (customers(), ticket_new/-edit/-delete, license_delete, customer_portal, _issue_license_from_ticket, _revoke_license_row, _process_deactivate, _process_heartbeat) wurden systematisch geprueft -- committen bereits korrekt VOR dem Logging, betroffen war ausschliesslich diese eine Stelle. Verifiziert: direkter Reproduktionstest gegen eine echte Kopie der Live-Datenbank (POETESTs echte aktive Lizenz wird durch eine zweite ersetzt, exakt das gemeldete Szenario) -- vorher deadlock/Timeout, nachher 0.04s, korrekt superseded + Hostname + Audit-Log-Eintraege. Als Notfall-Hotfix bereits direkt auf dem Live-Master eingespielt (Dienst neu gestartet, seither keine weiteren 'database is locked'- Fehler); dieser Commit bringt Repo/Release auf denselben Stand, damit 'latest' nicht wieder auf die kaputte v1.2.0 zurueckfaellt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TESM-Lizenzserver
Master-Lizenzserver für TESM (TimEShepManager) — verwaltet Kunden und deren Lizenzen, statt Netzwerkgeräte. Ursprünglich als Teil des TESM-Hauptrepos gebaut (gleiche Codebasis: Login/Benutzer/Gruppen, LDAP/AD, NGINX- und Systemeinstellungen, Live-Log/Verlauf/Auditlog, Im-/Export-Grundgerüst, das komplette Rechtesystem) und in dieses eigene Repo ausgelagert.
Das Projekt ist ausschließlich für Linux ausgelegt (Zielsystem: eine Linux-VM, identisches Deployment-Muster wie TESM selbst).
Kernfunktionen
- Kunden-/Lizenzübersicht als Dashboard (Typ, Module, Ablaufdatum, Aktivierungsstatus, letzter Heartbeat)
- Lizenz-Ausstellung: Trial/Standard/Custom/Enterprise, frei wählbare Gültigkeitsdauer, bei Custom einzeln wählbare Module
- Ed25519-signierte Lizenzdateien (siehe
srv/tesm-license/licensing.py) — dieselbe Datei existiert byte-identisch im TESM-Repo, da Client (TESM) und Master (dieses Repo) exakt dasselbe Signier-/ Verifikationsprotokoll sprechen müssen - Online-Aktivierung/-Deaktivierung/-Heartbeat über eine schlanke
JSON-API (
/api/activate,/api/deactivate,/api/heartbeat, unauthentifiziert per Design — die Signatur der Anfrage selbst ist der Berechtigungsnachweis) - Offline-Fallback: dieselbe Aktivierungs-/Deaktivierungs-/Heartbeat- Logik auch als manuell kopierbarer Code für Kunden ohne Netzwerkzugriff auf den Lizenzserver
- E-Mail-Versand ausgestellter Lizenzen per Microsoft Graph (Client-Credentials-Flow, keine zusätzliche Abhängigkeit) inkl. Einrichtungsanleitung und Verbindungstest in der GUI
- Eigene Bootstrap-Lizenz des Masters selbst (
create_master_license.py, rein lokal, kein externer Super-Master nötig)
Installation
sudo ./install.sh
Richtet System-Pakete, die Flask-App (systemd: tesm-license.service)
sowie nginx als Reverse-Proxy unter /srv/tesm-license ein. Erkennt
selbstständig, ob dort bereits eine Installation existiert, und
aktualisiert sie entsprechend (In-Place bei unverändertem
Datenbank-Schema, sonst mit automatischem Backup + Health-Check +
Rückroll bei Fehlschlag — siehe Kommentarkopf in install.sh).
Nach der Erstinstallation:
sudo /srv/tesm-license/venv/bin/python3 /srv/tesm-license/create_admin.py
sudo /srv/tesm-license/venv/bin/python3 /srv/tesm-license/create_master_license.py
Update (von einem bereits installierten System aus)
sudo ./update.sh
# oder für eine bestimmte Version statt "latest":
TESM_RELEASE_TAG=v1.0.0 sudo -E ./update.sh
Lädt das aktuelle Gitea-Release herunter und übergibt an install.sh.
Zusammenspiel mit TESM
Eine TESM-Instanz aktiviert sich gegen genau einen Lizenzserver
(master_endpoint, in der Lizenzdatei hinterlegt). Ein Protokoll, zwei
Transportwege: online automatisch per HTTPS/JSON, offline als manuell
auszutauschender Code — beide Seiten nutzen dieselben Funktionen aus
licensing.py. Details zum kryptografischen Format und Ablauf siehe die
Docstrings in srv/tesm-license/licensing.py bzw. dem identischen Modul
im TESM-Repo.