Instanzmanager: Lizenz und Sub-Token wandern in den Manager (#528, E-M3)
Neues Projekt ClawdDotNet.Licensing. LicenseGate, LicenseWatch, LicenseInfo und ILicensePrompt sind aus ClawdDotNet.App dorthin gezogen, weil zwei Prozesse dieselbe Lizenzpruefung brauchen und keiner von beiden den anderen kennen darf. In Core konnte es nicht landen - dann zoege jedes der sechzehn Werkzeugprojekte das Deploymentcenter-SDK mit. Dabei ist die Kopplung an SettingsManager verschwunden. LicenseGate kennt jetzt nur ILicenseStore: Serveradresse lesen, Schluessel lesen und schreiben, speichern. Die Anwendung fuehrt ihn in Settings.json, der Manager in manager.json. Der Torwaechter prueft Schluessel; Dateien verwaltet er nicht. Auch die Anwendungsversion kommt als Parameter statt aus einem globalen Zugriff - diese Schicht weiss nicht, welche Anwendung sie benutzt. ManagerCore ist die Klammer: eigene Einstellungen, Lizenz, Register, Meldeweg. Drei Regeln: - Ohne gueltige Lizenz startet er keine Instanz. Laufende werden trotzdem in die Liste genommen - man soll sie sehen und anhalten koennen. - Ein Widerruf haelt alle an. Der einzige Fall, in dem der Manager das von sich aus tut. Ein Serverausfall gehoert ausdruecklich nicht dazu; das SDK trennt Urteil und gescheiterten Versuch, und nur das Urteil kommt hier an. Sonst naehme ein Netzproblem saemtliche Installationen mit. - Ohne hinterlegte Lizenzpruefung sperrt er nichts, sonst gaebe es eine Henne-Ei-Sperre, bevor jemand einen Schluessel eintragen kann. Sub-Token je Instanz: Der Manager haelt das Master-Token und tauscht es beim ANLEGEN einer Instanz gegen ein eigenes (watchdog:ping, bugtracker:report, einzeln widerrufbar). Beim Anlegen und nicht beim ersten Start, weil die Instanzdatei dann zweifelsfrei niemandem gehoert - sie spaeter zu schreiben, waehrend der Instanzprozess laeuft, hiesse zwei Schreiber auf einer Datei. Fuer Instanzen, die es vor dem Manager schon gab, wird es vor dem Start nachgeholt. Danach fragt er ueber whoami nach, ob das Token an ein Projekt gebunden ist (SDK 2.8.1). Ein fehlgeschlagener Tokenbezug haelt die Instanz nicht auf. NOCH NICHT VOLLSTAENDIG: Die Instanzen pruefen die Lizenz weiterhin selbst. Das laesst sich erst abschalten, wenn eine Instanz feststellen kann, ob ein Manager da ist - vorher entstuende eine Fassung ganz ohne Durchsetzung. Und ein Testfehler, der zweimal derselbe war: Die Socket-Antwort war hinterlegt, BEVOR der Manager hochfuhr. Damit sagt man "diese Instanz laeuft bereits", der Manager uebernimmt sie, der Starter wird nie angefasst - und der Test prueft etwas anderes, als er behauptet. Es gibt jetzt eine Hilfsmethode, die das ausschliesst. Build ohne Warnungen, 774 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
namespace ClawdDotNet.Licensing;
|
||||
|
||||
/// <summary>
|
||||
/// Wie der <see cref="LicenseGate"/> mit dem Benutzer spricht.
|
||||
///
|
||||
/// Vorher rief er unmittelbar <c>MessageBox.Show</c> und einen WinForms-Dialog.
|
||||
/// Das band die Lizenzprüfung an WinForms — und wäre im kopflosen Betrieb fatal
|
||||
/// gewesen: Ein Dienst, der beim Start ein Fenster öffnet und auf eine Eingabe wartet,
|
||||
/// hängt für immer, ohne dass jemand die Meldung je zu sehen bekäme.
|
||||
///
|
||||
/// Es gibt drei Umsetzungen:
|
||||
/// <list type="bullet">
|
||||
/// <item><c>AvaloniaLicensePrompt</c> — Dialoge im Fenster (Desktop).</item>
|
||||
/// <item><see cref="ConsoleLicensePrompt"/> — Eingabe über die Konsole (Kommandozeile).</item>
|
||||
/// <item><see cref="NonInteractiveLicensePrompt"/> — antwortet nie (systemd-Dienst).</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public interface ILicensePrompt
|
||||
{
|
||||
/// <summary>
|
||||
/// Fragt einen Lizenzschlüssel ab. <c>null</c> heißt Abbruch — der Aufrufer beendet
|
||||
/// die Anwendung.
|
||||
/// </summary>
|
||||
/// <param name="hardwareId">Wird angezeigt, damit der Nutzer ihn an den Support geben kann.</param>
|
||||
/// <param name="problem">Warum der bisherige Schlüssel nicht taugt; <c>null</c> beim ersten Fragen.</param>
|
||||
/// <param name="currentKey">Vorbelegung des Eingabefelds.</param>
|
||||
Task<string?> RequestKeyAsync(string hardwareId, string? problem, string? currentKey);
|
||||
|
||||
/// <summary>Eine Meldung, die keine Antwort braucht (etwa „offline gültig bis …").</summary>
|
||||
Task ShowInfoAsync(string title, string message);
|
||||
|
||||
/// <summary>Ein Fehler, nach dem die Anwendung nicht weiterläuft.</summary>
|
||||
Task ShowErrorAsync(string title, string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Für den kopflosen Betrieb: fragt nicht, sondern lehnt ab.
|
||||
///
|
||||
/// Ein Dienst ohne Sitzung kann keinen Schlüssel entgegennehmen. Statt zu blockieren
|
||||
/// meldet er, was zu tun ist — der Schlüssel wird vorab über die Kommandozeile
|
||||
/// hinterlegt (<c>--license-set-key</c>).
|
||||
/// </summary>
|
||||
public sealed class NonInteractiveLicensePrompt(Action<string> log) : ILicensePrompt
|
||||
{
|
||||
public Task<string?> RequestKeyAsync(string hardwareId, string? problem, string? currentKey)
|
||||
{
|
||||
log($"Lizenz erforderlich, aber kein Eingabeweg vorhanden. Hardware-ID: {hardwareId}. "
|
||||
+ (problem is null ? "" : $"Grund: {problem}. ")
|
||||
+ "Schlüssel mit '--license-set-key <SCHLÜSSEL>' hinterlegen.");
|
||||
|
||||
return Task.FromResult<string?>(null);
|
||||
}
|
||||
|
||||
public Task ShowInfoAsync(string title, string message)
|
||||
{
|
||||
log($"{title}: {message}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ShowErrorAsync(string title, string message)
|
||||
{
|
||||
log($"{title}: {message}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Eingabe über die Konsole — für Kommandozeilenaufrufe.</summary>
|
||||
public sealed class ConsoleLicensePrompt : ILicensePrompt
|
||||
{
|
||||
public Task<string?> RequestKeyAsync(string hardwareId, string? problem, string? currentKey)
|
||||
{
|
||||
if (problem is not null)
|
||||
Console.Error.WriteLine($"Lizenz: {problem}");
|
||||
|
||||
Console.WriteLine($"Hardware-ID: {hardwareId}");
|
||||
Console.Write("Lizenzschlüssel: ");
|
||||
|
||||
var input = Console.ReadLine()?.Trim();
|
||||
return Task.FromResult(string.IsNullOrWhiteSpace(input) ? null : input);
|
||||
}
|
||||
|
||||
public Task ShowInfoAsync(string title, string message)
|
||||
{
|
||||
Console.WriteLine($"{title}: {message}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ShowErrorAsync(string title, string message)
|
||||
{
|
||||
Console.Error.WriteLine($"{title}: {message}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user