Lizenzsystem auf Deploymentcenter umgestellt
Predictalytics hing noch am abgeloesten LicenseLabrador (license.mhdf.de), waehrend die Lizenzen inzwischen im Deploymentcenter (dc.mhdf.de) gepflegt werden. Vor dem Umbau mit einem Wegwerf-Probelauf gegen den echten Client abgesichert: Slug predictalytics, HWID v2 aus machine-guid, Server meldet valid. Hosting/LicenseGuard.cs auf Deploymentcenter.Client umgeschrieben. Die Aufteilung bleibt: plattformneutrale Pruefung im Hosting, interaktive Aktivierung in den Hosts. Gewinn fuer die Linux-Portierung: - Hardware-ID v2 mit Quellen-Kaskade /etc/machine-id, dbus, DMI-UUID, MAC, Schluesseldatei; Container ueber DEPLOYMENTCENTER_HWID - Der Rechnername steckt nicht mehr im Hash, Umbenennen verbraucht keinen Aktivierungsplatz - Cache mit AES-256-GCM statt DPAPI, ohne stillen Klartext-Rueckfall unter Linux. Damit erledigt sich Punkt 3.2 Nr. 2 der Portierungsanalyse. - XDG_CONFIG_HOME wird beruecksichtigt - Keine Basic-Auth-Zugangsdaten mehr im Quelltext Neue CLI-Schalter aus dem Integrationsleitfaden, ausgewertet vor jeder Avalonia-Initialisierung: --license-status, --license-set-key, --license-deactivate. Schluessel werden in der Ausgabe maskiert. Die Lizenzfenster beider Hosts zeigen jetzt die Hardware-ID an, an die der Server die Aktivierung bindet. Build: 0 Fehler. Tests: 100 bestanden. Verifiziert: --license-status meldet gueltig; die GUI startet vollstaendig durch bis MainWindowViewModel.Start(), belegt durch die Logdateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,17 +29,16 @@ internal static class HeadlessRunner
|
||||
|
||||
try
|
||||
{
|
||||
var client = await EnsureLicensedAsync();
|
||||
if (client == null) return ExitNoLicense;
|
||||
if (!await EnsureLicensedAsync()) return ExitNoLicense;
|
||||
|
||||
var host = new PredictalyticsHost(options);
|
||||
using var cts = new CancellationTokenSource();
|
||||
using var shutdownSignals = RegisterShutdown(cts);
|
||||
|
||||
using var licenseWatch = LicenseGuard.StartPeriodicRevalidation(client, result =>
|
||||
using var licenseWatch = LicenseGuard.StartPeriodicRevalidation(result =>
|
||||
{
|
||||
Log.Fatal("Lizenz nicht mehr gültig ({State}): {Message} — Dienst wird beendet.",
|
||||
result.State, result.Message);
|
||||
Log.Fatal("Lizenz nicht mehr gültig ({Status}): {Message} — Dienst wird beendet.",
|
||||
result.Status, result.Message);
|
||||
cts.Cancel();
|
||||
});
|
||||
|
||||
@@ -76,33 +75,36 @@ internal static class HeadlessRunner
|
||||
/// Lizenzpruefung ohne Dialog: zuerst der zwischengespeicherte Schluessel, sonst
|
||||
/// einmalige Aktivierung mit dem Schluessel aus der Umgebungsvariable.
|
||||
/// </summary>
|
||||
private static async Task<LicenseLabrador.Client.LicenseClient?> EnsureLicensedAsync()
|
||||
private static async Task<bool> EnsureLicensedAsync()
|
||||
{
|
||||
var client = LicenseGuard.CreateClient();
|
||||
|
||||
var result = await LicenseGuard.RevalidateAsync(client);
|
||||
if (LicenseGuard.IsUsable(client, result)) return client;
|
||||
var result = await LicenseGuard.ValidateCachedAsync();
|
||||
if (result?.IsValid == true)
|
||||
{
|
||||
Log.Information("Lizenz gültig ({Status}{Cached}).",
|
||||
result.Status, result.IsCached ? ", aus lokalem Cache" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = Environment.GetEnvironmentVariable(LicenseKeyVariable);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
Log.Fatal("Keine nutzbare Lizenz ({State}: {Message}) und {Variable} ist nicht gesetzt. " +
|
||||
Log.Fatal("Keine nutzbare Lizenz ({Status}) und {Variable} ist nicht gesetzt. " +
|
||||
"Im Headless-Betrieb gibt es keinen Aktivierungsdialog — bitte den Lizenzschlüssel " +
|
||||
"über die Umgebungsvariable bereitstellen.",
|
||||
result.State, result.Message, LicenseKeyVariable);
|
||||
return null;
|
||||
"über die Umgebungsvariable bereitstellen oder einmalig mit --license-set-key aktivieren.",
|
||||
result?.Status ?? "kein Schlüssel hinterlegt", LicenseKeyVariable);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Information("Aktiviere Lizenz mit dem Schlüssel aus {Variable}…", LicenseKeyVariable);
|
||||
result = await client.ValidateAsync(key.Trim().ToUpperInvariant());
|
||||
if (LicenseGuard.IsUsable(client, result))
|
||||
result = await LicenseGuard.ValidateAsync(key);
|
||||
if (result.IsValid)
|
||||
{
|
||||
Log.Information("Lizenz aktiviert ({State}).", result.State);
|
||||
return client;
|
||||
Log.Information("Lizenz aktiviert ({Status}).", result.Status);
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Fatal("Aktivierung fehlgeschlagen ({State}): {Message}", result.State, result.Message);
|
||||
return null;
|
||||
Log.Fatal("Aktivierung fehlgeschlagen ({Status}): {Message}", result.Status, result.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static WatchdogHeartbeatService? StartWatchdog(PredictalyticsOptions options, PredictalyticsHost host)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
using Predictalytics.Hosting;
|
||||
|
||||
namespace Predictalytics.Shell.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Lizenzverwaltung von der Kommandozeile — die im Deploymentcenter-Integrations-
|
||||
/// leitfaden vorgesehenen Schalter. Gedacht fuer Server ohne Oberflaeche, wo es
|
||||
/// keinen Aktivierungsdialog geben kann.
|
||||
/// </summary>
|
||||
internal static class LicenseCli
|
||||
{
|
||||
public const string StatusSwitch = "--license-status";
|
||||
public const string SetKeySwitch = "--license-set-key";
|
||||
public const string DeactivateSwitch = "--license-deactivate";
|
||||
|
||||
public static bool IsLicenseCommand(string[] args) =>
|
||||
args.Length > 0 && args[0] is StatusSwitch or SetKeySwitch or DeactivateSwitch;
|
||||
|
||||
public static async Task<int> RunAsync(string[] args)
|
||||
{
|
||||
switch (args[0])
|
||||
{
|
||||
case StatusSwitch: return await ShowStatusAsync();
|
||||
case SetKeySwitch: return await SetKeyAsync(args.Length > 1 ? args[1] : null);
|
||||
case DeactivateSwitch: return await DeactivateAsync();
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<int> ShowStatusAsync()
|
||||
{
|
||||
var hw = LicenseGuard.GetHardwareInfo();
|
||||
Console.WriteLine("Predictalytics — Lizenzstatus");
|
||||
Console.WriteLine($" Produkt : {LicenseGuard.ProductSlug}");
|
||||
Console.WriteLine($" Hardware-ID : {hw.HardwareId}");
|
||||
Console.WriteLine($" HWID-Quelle : {hw.HwidSource} (v{hw.HwidVersion}, {hw.Platform})");
|
||||
Console.WriteLine($" Cache-Ablage : {LicenseGuard.StorageDirectory}");
|
||||
|
||||
var cachedKey = LicenseGuard.GetCachedLicenseKey();
|
||||
if (cachedKey == null)
|
||||
{
|
||||
Console.WriteLine(" Schlüssel : keiner hinterlegt");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Aktivierung: {SetKeySwitch} <SCHLÜSSEL>");
|
||||
return HeadlessRunner.ExitNoLicense;
|
||||
}
|
||||
|
||||
Console.WriteLine($" Schlüssel : {Mask(cachedKey)}");
|
||||
Console.WriteLine();
|
||||
Console.Write("Prüfe am Server… ");
|
||||
|
||||
var result = await LicenseGuard.ValidateCachedAsync();
|
||||
Console.WriteLine(result is { IsValid: true }
|
||||
? $"gültig ({result.Status}{(result.IsCached ? ", aus lokalem Cache" : "")})"
|
||||
: $"NICHT gültig ({result?.Status}): {result?.Message}");
|
||||
|
||||
return result is { IsValid: true } ? HeadlessRunner.ExitOk : HeadlessRunner.ExitNoLicense;
|
||||
}
|
||||
|
||||
private static async Task<int> SetKeyAsync(string? key)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
Console.Error.WriteLine($"Aufruf: {SetKeySwitch} <SCHLÜSSEL>");
|
||||
return HeadlessRunner.ExitFailed;
|
||||
}
|
||||
|
||||
Console.Write($"Aktiviere {Mask(LicenseGuard.Normalize(key))} … ");
|
||||
var result = await LicenseGuard.ValidateAsync(key);
|
||||
|
||||
if (result.IsValid)
|
||||
{
|
||||
Console.WriteLine($"erfolgreich ({result.Status})");
|
||||
Console.WriteLine($"Hardware-ID: {result.HardwareId}");
|
||||
return HeadlessRunner.ExitOk;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"fehlgeschlagen ({result.Status}): {result.Message}");
|
||||
return HeadlessRunner.ExitNoLicense;
|
||||
}
|
||||
|
||||
private static async Task<int> DeactivateAsync()
|
||||
{
|
||||
if (LicenseGuard.GetCachedLicenseKey() == null)
|
||||
{
|
||||
Console.Error.WriteLine("Kein hinterlegter Schlüssel — nichts zu deaktivieren.");
|
||||
return HeadlessRunner.ExitFailed;
|
||||
}
|
||||
|
||||
Console.Write("Gebe Aktivierungsplatz am Server frei… ");
|
||||
var ok = await LicenseGuard.DeactivateAsync();
|
||||
Console.WriteLine(ok ? "erfolgreich" : "fehlgeschlagen");
|
||||
return ok ? HeadlessRunner.ExitOk : HeadlessRunner.ExitFailed;
|
||||
}
|
||||
|
||||
/// <summary>Nur die Segmentstruktur zeigen, nicht den vollstaendigen Schluessel.</summary>
|
||||
private static string Mask(string key)
|
||||
{
|
||||
var parts = key.Split('-');
|
||||
if (parts.Length < 2) return "*****";
|
||||
for (var i = 1; i < parts.Length - 1; i++) parts[i] = new string('*', parts[i].Length);
|
||||
return string.Join('-', parts);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user