alientimandClaude Sonnet 5 c2989a6e2f KRITISCH: 'database is locked' beim Aktivieren blockierte Online- UND Offline-Aktivierung (v1.2.1)
_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>
2026-08-23 16:33:16 +02:00

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.

S
Description
TESM-Lizenzserver -- Master-Lizenzserver fuer TESM (Kunden-/Lizenzverwaltung, Aktivierungs-/Heartbeat-API)
Readme
3.7 MiB
2026-09-03 16:18:55 +02:00
Languages
Python 62.4%
HTML 21.3%
Shell 7.7%
CSS 5.5%
JavaScript 3.1%