#!/usr/bin/env python3 """Erzeugt/erneuert die Bootstrap-Lizenz DIESES Lizenzservers selbst. Läuft rein lokal (kein Netzwerk, kein externer Super-Master) -- der Master prüft seine eigene Lizenz über denselben license_active()-Mechanismus wie Kundeninstanzen (siehe app.py), nur ohne Heartbeat-Ziel: dieses Skript setzt den Aktivierungsstatus direkt in der Datenbank, da es keine höhere Instanz gibt, die eine Aktivierung bestätigen könnte. Nutzt denselben Master-Signaturschlüssel, den der Server auch zum Ausstellen von Kundenlizenzen verwendet (wird beim ersten Aufruf automatisch erzeugt, falls noch nicht vorhanden) -- WICHTIG: dieser Schlüssel wird beim erneuten Aufruf (Verlängerung) wiederverwendet, nie neu erzeugt, siehe _load_or_create_master_signing_key() weiter unten und die identische Funktion in app.py: ein Schlüsselwechsel würde die Offline-Verifikation künftiger Aktivierungs-/Heartbeat-Antworten für ALLE bereits an Kunden ausgegebenen Lizenzen brechen. Aufruf: python3 create_master_license.py [--days 730] [--name "Firma XY"] Erneuerung (z.B. kurz vor Ablauf): einfach erneut aufrufen -- überschreibt die bestehende Bootstrap-Lizenz, der Signaturschlüssel bleibt unverändert. """ import argparse import json import os import sqlite3 import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import licensing BASE_DIR = os.path.dirname(os.path.abspath(__file__)) MASTER_SIGNING_KEY_PATH = os.environ.get("TESM_LICENSE_MASTER_KEY", os.path.join(BASE_DIR, "master_signing_key.json")) LICENSE_PATH = os.environ.get("TESM_LICENSE_PATH", os.path.join(BASE_DIR, "license.json")) DB_PATH = os.environ.get("TESM_LICENSE_DB_PATH", os.path.join(BASE_DIR, "sqlite.db")) def _load_or_create_master_signing_key(): try: with open(MASTER_SIGNING_KEY_PATH, "r", encoding="utf-8") as f: data = json.load(f) return data["private"], data["public"], False except (OSError, ValueError, KeyError): priv, pub = licensing.generate_keypair() with open(MASTER_SIGNING_KEY_PATH, "w", encoding="utf-8") as f: json.dump({"private": priv, "public": pub}, f) try: os.chmod(MASTER_SIGNING_KEY_PATH, 0o600) except OSError: pass return priv, pub, True def main(): parser = argparse.ArgumentParser(description="Erzeugt/erneuert die Bootstrap-Lizenz dieses Lizenzservers.") parser.add_argument("--days", type=int, default=730, help="Gültigkeitsdauer in Tagen (Standard: 730 = 2 Jahre)") parser.add_argument("--name", default="Lizenzserver (Eigenbetrieb)", help="Anzeigename in der Lizenzdatei") args = parser.parse_args() master_priv, master_pub, created = _load_or_create_master_signing_key() if created: print(f"Neuer Master-Signaturschlüssel erzeugt: {MASTER_SIGNING_KEY_PATH}") else: print(f"Bestehenden Master-Signaturschlüssel wiederverwendet: {MASTER_SIGNING_KEY_PATH}") license_file, _pub = licensing.issue_license( customer={"name": args.name, "contact_email": ""}, license_type="enterprise", modules=[], valid_days=args.days, master_private_key_b64=master_priv, master_public_key_b64=master_pub, master_endpoint="", vendor={"name": "", "phone": "", "email": "", "address": "", "logo_base64": ""}, ) with open(LICENSE_PATH, "w", encoding="utf-8") as f: json.dump(license_file, f, indent=2) try: os.chmod(LICENSE_PATH, 0o600) except OSError: pass print(f"Bootstrap-Lizenz geschrieben: {LICENSE_PATH} (gültig {args.days} Tage, bis {license_file['expires_at']})") if os.path.exists(DB_PATH): conn = sqlite3.connect(DB_PATH) conn.execute( "INSERT INTO settings (key, value) VALUES ('license_activation_status', 'active') " "ON CONFLICT(key) DO UPDATE SET value=excluded.value" ) conn.commit() conn.close() print("Aktivierungsstatus in der Datenbank auf 'active' gesetzt.") else: print( f"WARNUNG: Datenbank {DB_PATH} nicht gefunden -- bitte zuerst create_db.py ausführen, " "dann dieses Skript erneut aufrufen, damit der Aktivierungsstatus gesetzt werden kann." ) print("Fertig -- Dienst neu starten (oder warten, bis der Prozess die Lizenz beim nächsten Zugriff neu lädt).") if __name__ == "__main__": main()