Ein Sub-Lizenzserver (role='license_server') meldet jetzt seinen kompletten eigenen Kunden-/Ticket-/Lizenzstand an den Master, der ihn als Ueberblick auf einer neuen Kunden-Detailseite anzeigt: unter dem Kunden, der die Lizenzserver-Lizenz haelt, erscheinen dessen eigene Kunden mit ihren Tickets/Lizenzen -- genau wie gewuenscht. Transport: huckepack auf dem bereits bestehenden 6-Stunden-Heartbeat (licensing.py: neues optionales 'report'-Feld in build_client_request(), signiert wie jedes andere Feld -- ein Angreifer ohne den privaten Lizenzschluessel kann keinen gefaelschten Bericht unterschieben). Zusaetzlich sofortiger Best-Effort-Report per Hintergrund-Thread nach jeder lokalen Ticket-/Kunden-/Lizenz-Aenderung (_report_to_master_async()), damit der Master nicht bis zu 6h auf Aktualitaet warten muss. Funktioniert automatisch auch ueber den Offline-Code-Weg (/licenses/manual-code), da beide Transportwege denselben _process_heartbeat()-Code nutzen. - Drei neue, rein informative Tabellen (reported_sub_customers/ _tickets/_licenses), verankert an source_license_id (die Lizenz, die den Sub-Lizenzserver identifiziert). Ein Bericht ist immer ein VOLLER Schnappschuss -- bei jedem Empfang werden alle bisherigen Zeilen dieser source_license_id ersetzt (kein Diffing noetig, ein beim Sub-Lizenzserver geloeschtes Ticket verschwindet dadurch automatisch korrekt auch hier). - _build_own_issuance_report(): baut den Schnappschuss auf der Sub-Lizenzserver-Seite. - _ingest_sub_report(): uebernimmt ihn auf der Master-Seite, NUR wenn row['role']=='license_server' fuer die absendende Lizenz (Verteid- igung in der Tiefe -- row['role'] steht in der Master-DB fest, nicht im Client-Request, ein gewoehnlicher Kunde kann sich also nicht als Lizenzserver ausgeben). - Neue Kunden-Detailseite (/customers/<id>, customer_detail.html): Stammdaten, eigene Tickets bei uns, und -- falls dieser Kunde eine aktive role='license_server'-Lizenz haelt -- die gemeldeten eigenen Kunden mit Tickets/Lizenzen (Ampelfarbe/Resttage ueber dieselbe _license_dict_for_display()-Funktion wie ueberall sonst). Kunden- liste verlinkt jetzt auf die Detailseite, zeigt 'Lizenzserver'-Badge. Getestet: licensing.py isoliert (report signiert+verifiziert, Manipulation bricht die Signatur, unveraendertes Verhalten ohne report-Argument), _ingest_sub_report() isoliert (Erstbericht korrekt uebernommen, Zweitbericht ersetzt korrekt inkl. verschwindender Tickets, Isolation zwischen mehreren source_license_id sauber). ZUSAETZLICH ein vollstaendiger Zwei-Prozess-Integrationstest ueber echtes HTTP (zwei unabhaengige, native laufende Instanzen: 'Master' + 'Sub-Lizenzserver' auf unterschiedlichen Ports): Ticket mit role=license_server angelegt, Lizenz ausgestellt, auf der zweiten Instanz hochgeladen und ONLINE aktiviert, dort ein eigener Endkunde samt Ticket/Lizenz angelegt, automatischer Report ausgeloest -- Master-Kundendetailseite zeigt den gemeldeten Endkunden mit korrektem Ticket-Typ/Laufzeit/Status und aktuellem Heartbeat-Zeitstempel. 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.