Watchdog-Heartbeat + LicenseLabrador-Lizenzpruefung eingebunden
Slices W-A/W-B/W-C und L-A aus UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration. Watchdog (Dead-Man's-Switch, externer Server): - WatchdogHeartbeatService als BackgroundService + DI-Singleton; jeder Sendeversuch gekapselt, ein Ausfall des Watchdogs beeintraechtigt PolyTrader nie. - Eigene Implementierung statt Test-Client des Fremdprojekts: TLS-Pruefung bleibt aktiv, http:// nur fuer localhost (Agent-Token nicht im Klartext ins Netz). - Status aus dem App-Log abgeleitet (Error mit 5-Minuten-Sticky-Fenster, entprellt), Lifecycle-Events started/stopping. - Konfiguration in ServerSettings; Agent-Token [Browsable(false)] mit maskierter Eingabe + Statusanzeige, bei gesetztem Master-Key verschluesselt (enc:v1:). Lizenz (LicenseLabrador, Ed25519): - LicenseGate.RunStartupGate prueft beim Start; bei ungueltiger Lizenz wird die Modulliste leer gebaut, sodass nur die Core-Shell (Terminal/Einstellungen) startet. Bewusst kein Environment.Exit - ein Trading-Bot darf nicht mitten im Lauf hart sterben. TamperSuspected gilt als nicht nutzbar. - LicenseDialog (partial + .Designer.cs) fuer Start- und Verwalten-Modus, mit Hardware-ID zum Kopieren; Smoke-UI konstruiert beide Modi headless. - Master-Key wird jetzt VOR dem Host-Build geladen, da auch der Lizenzschluessel entschluesselt werden muss; derselbe TerminalLogger wird als Singleton weitergereicht, damit die Startmeldungen im Terminal-Fenster erscheinen. Der Lizenz-SDK-Client wird per Cross-Repo-Projektreferenz auf ..\..\LicenseLabrador eingebunden, damit SDK-Fixes ohne Kopie einfliessen. 484 Zeilen Tests fuer den Heartbeat; Suite gruen (438 Tests). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+41
-6
@@ -69,6 +69,14 @@ internal static class Program
|
||||
// erst geladen werden. Dieselbe Instanz wird als Singleton weitergereicht (eine Quelle).
|
||||
var serverSettings = PolyTraderSharp.Models.ServerSettings.Load(ServerSettingsPath);
|
||||
|
||||
// Früher Logger: wird unten als DI-Singleton weitergereicht, damit die Startmeldungen
|
||||
// (Master-Key, Lizenz) später im Terminal-Fenster erscheinen (History-Replay).
|
||||
var bootLog = new TerminalLogger();
|
||||
|
||||
// F1 (Sicherheit): Master-Key VOR jeder Entschlüsselung laden – wird sowohl für die
|
||||
// Account-Credentials als auch für den gespeicherten Lizenzschlüssel gebraucht.
|
||||
ConfigureSecretProtection(bootLog);
|
||||
|
||||
// Alle bekannten Module; „modules" enthält nur die aktiven (nicht in DisabledModules).
|
||||
var allModules = new System.Collections.Generic.List<IPolyTraderModule>
|
||||
{
|
||||
@@ -79,7 +87,15 @@ internal static class Program
|
||||
};
|
||||
var disabledModules = new System.Collections.Generic.HashSet<string>(
|
||||
serverSettings.DisabledModules, StringComparer.OrdinalIgnoreCase);
|
||||
var modules = allModules.Where(m => !disabledModules.Contains(m.Name)).ToList();
|
||||
|
||||
// B.4: Lizenzprüfung beim Start. Bei ungültiger Lizenz zeigt der Gate einen Dialog und
|
||||
// gibt false zurück → es werden KEINE Module registriert (nur die Core-Shell startet,
|
||||
// damit Terminal und Einstellungen zum Eintragen einer Lizenz erreichbar bleiben).
|
||||
// Bewusst KEIN Environment.Exit – ein Trading-Bot darf nicht mitten im Lauf hart sterben.
|
||||
bool licensed = PolyTraderSharp.Licensing.LicenseGate.RunStartupGate(serverSettings, bootLog, ServerSettingsPath);
|
||||
var modules = licensed
|
||||
? allModules.Where(m => !disabledModules.Contains(m.Name)).ToList()
|
||||
: new System.Collections.Generic.List<IPolyTraderModule>();
|
||||
|
||||
AppHost = Host.CreateDefaultBuilder()
|
||||
// Config immer neben der EXE suchen (nicht im Arbeitsverzeichnis), damit die App
|
||||
@@ -126,11 +142,17 @@ internal static class Program
|
||||
}
|
||||
});
|
||||
});
|
||||
services.AddSingleton<TerminalLogger>();
|
||||
// Denselben früh erzeugten Logger als Singleton weiterreichen, damit die
|
||||
// Master-Key-/Lizenz-Meldungen von oben im Terminal-Fenster auftauchen.
|
||||
services.AddSingleton(bootLog);
|
||||
services.AddSingleton<MullvadVpnService>();
|
||||
services.AddSingleton<ThreemaService>();
|
||||
services.AddSingleton<JobManager>();
|
||||
|
||||
// Watchdog-Heartbeat (Dead-Man's-Switch). Als Singleton registriert, damit das
|
||||
// Settings-Fenster denselben Dienst für ReloadSettings/Test-Heartbeat nutzt.
|
||||
services.AddSingleton(sp => new WatchdogHeartbeatService(sp.GetRequiredService<TerminalLogger>()));
|
||||
|
||||
// MUSS als erster HostedService laufen: hydriert den State, bevor die
|
||||
// Trading-Services gegen einen leeren State anlaufen.
|
||||
services.AddHostedService<StartupHydrationService>();
|
||||
@@ -145,13 +167,12 @@ internal static class Program
|
||||
services.AddHostedService<MarketSyncService>();
|
||||
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<MullvadVpnService>());
|
||||
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<ThreemaService>());
|
||||
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<WatchdogHeartbeatService>());
|
||||
services.AddSingleton<PolyTraderSharp.Ui.ShellUiHost>();
|
||||
services.AddTransient<PolyTraderSharp.Ui.LauncherForm>();
|
||||
}).Build();
|
||||
|
||||
// F1 (Sicherheit): Master-Key VOR jeder Credential-Entschlüsselung (Hydration) laden.
|
||||
// Quelle: Umgebungsvariable POLYTRADER_MASTER_KEY, sonst gitignorierte master.key im App-Ordner.
|
||||
ConfigureSecretProtection(AppHost.Services.GetRequiredService<TerminalLogger>());
|
||||
// (Master-Key wurde bereits VOR dem Host-Build geladen – siehe ConfigureSecretProtection(bootLog) oben.)
|
||||
|
||||
// F5 (Sicherheit): warnen, wenn die (remote) DB-Verbindung keine TLS-Erzwingung hat.
|
||||
WarnIfDbTlsNotEnforced(AppHost.Services);
|
||||
@@ -212,7 +233,8 @@ internal static class Program
|
||||
viewServices.GetRequiredService<MullvadVpnService>(),
|
||||
viewServices.GetRequiredService<TerminalLogger>(),
|
||||
viewServices.GetRequiredService<PolyTrader.Core.Persistence.IAccountRepository>(),
|
||||
viewServices.GetRequiredService<TradingState>());
|
||||
viewServices.GetRequiredService<TradingState>(),
|
||||
viewServices.GetRequiredService<WatchdogHeartbeatService>());
|
||||
return view;
|
||||
}
|
||||
});
|
||||
@@ -533,6 +555,19 @@ internal static class Program
|
||||
catch (Exception ex) { failures++; Console.WriteLine($"[FEHLER] {id}: {ex.GetType().Name}: {ex.Message}"); }
|
||||
}
|
||||
|
||||
// B.4: Lizenzdialog headless konstruieren (kein Netz, keine Message-Loop) – fängt
|
||||
// Designer-/Konstruktionsregressionen ab.
|
||||
try
|
||||
{
|
||||
var licClient = new LicenseLabrador.Client.LicenseClient(PolyTraderSharp.Licensing.LicenseGate.BuildConfig());
|
||||
var dummy = new LicenseLabrador.Client.LicenseResult(
|
||||
LicenseLabrador.Client.LicenseState.NoLicense, null, null, "Smoke-Test", string.Empty);
|
||||
using (var licDlg = new PolyTraderSharp.Ui.LicenseDialog(licClient, string.Empty, dummy, startupContext: true))
|
||||
using (var licDlgManage = new PolyTraderSharp.Ui.LicenseDialog(licClient, string.Empty, null, startupContext: false))
|
||||
Console.WriteLine("[OK] license.dialog konstruiert (Start- und Verwalten-Modus)");
|
||||
}
|
||||
catch (Exception ex) { failures++; Console.WriteLine($"[FEHLER] license.dialog: {ex.GetType().Name}: {ex.Message}"); }
|
||||
|
||||
Console.WriteLine(failures == 0 ? "=== Smoke-UI OK ===" : $"=== Smoke-UI: {failures} Fehler ===");
|
||||
return failures;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user