Deploymentcenter-Integration (Schritte 0-8 des Konzepts)
Bindet Lizenz, Watchdog, Fehler-Stream, UpdateService und Erstinstallation an
das Deploymentcenter 2.5.1 an. Einbauort ist IBKRTrader.Hosting - den Host
teilen sich Shell und Daemon.
Projekt-Befunde aus dem Konzept vorab bereinigt:
P1 Echte DB-Zugangsdaten als Vorgabewerte in AppSettings -> Platzhalter.
Das alte Passwort steht weiterhin in der Git-Historie und ist als
kompromittiert zu behandeln (Rotation ist Nutzer-Aktion).
P2 AppPaths fiel unter Windows auf /etc/ibkrtrader zurueck, was .NET zu
C:\etc\ibkrtrader aufloest. Jetzt %ProgramData%\IBKRTrader.
P3 Globale Ausnahmebehandler (AppDomain / TaskScheduler) - vorher gab es
keinen Logeintrag, wenn der Prozess unbehandelt wegbrach.
P4 Version einmal zentral in Directory.Build.props statt zweimal hartkodiert.
Bewusste Abweichungen vom DC-Leitfaden, beide fuer ein handelndes System:
- Lizenz-Urteil fuehrt zum Sperrbetrieb (TradingEnabled=false) statt zu
Environment.Exit(1). Keine neuen Einstiege, aber Risiko-, Exit- und
Buchhaltungslogik laufen weiter.
- exitCurrentApp bleibt immer false; der Aufrufer beendet geordnet.
Das SDK haengt als Cross-Repo-ProjectReference am Schwester-Repo
Deploymentcenter (Interim, siehe Konzept 2.2). Damit ist P5 offen: die
Gitea-CI checkt das Schwester-Repo nicht aus und wird rot, bis der Bezug auf
ein NuGet-Paket umgestellt ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
using Deploymentcenter.Client;
|
||||
using IBKRTrader.Core.Configuration;
|
||||
using IBKRTrader.Core.Logging;
|
||||
using IBKRTrader.Core.Modularity;
|
||||
using IBKRTrader.Hosting;
|
||||
using IBKRTrader.Hosting.Deploymentcenter;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
@@ -21,6 +24,16 @@ internal static class Program
|
||||
if (HasFlag(args, "--db-version"))
|
||||
return DbVersion.Print();
|
||||
|
||||
// Lizenz-Konvention aus dem Deploymentcenter-Leitfaden: das SDK bringt keine eigene CLI mit,
|
||||
// diese beiden Schalter sind hier die vom Leitfaden empfohlene Umsetzung. Kein Host nötig -
|
||||
// beide arbeiten nur mit dem lokalen Lizenz-Cache bzw. einer einzelnen Validierung.
|
||||
if (HasFlag(args, "--license-status"))
|
||||
return PrintLicenseStatus();
|
||||
|
||||
var setKeyIndex = Array.IndexOf(args, "--license-set-key");
|
||||
if (setKeyIndex >= 0 && setKeyIndex + 1 < args.Length)
|
||||
return await SetLicenseKeyAsync(args[setKeyIndex + 1]).ConfigureAwait(false);
|
||||
|
||||
// Trockenlauf: Host bauen, Startprüfungen fahren, Dienste NICHT starten. Für Deployment
|
||||
// und CI – prüft Konfiguration, DB-Verbindungszeichenfolge, Master-Key und Ablageorte,
|
||||
// ohne eine einzige Verbindung zur Börse aufzubauen.
|
||||
@@ -40,7 +53,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
logger.Info("Core", "=== IBKRTrader (kopflos) startet ===");
|
||||
logger.Info("Core", $"Version: 1.0.0 | .NET {Environment.Version} | {RuntimeDescription()}");
|
||||
logger.Info("Core", $"Version: {IBKRTrader.Hosting.BuildInfo.Summary} | .NET {Environment.Version} | {RuntimeDescription()}");
|
||||
|
||||
await host.StartAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -73,6 +86,41 @@ internal static class Program
|
||||
private static bool HasFlag(string[] args, string flag) =>
|
||||
args.Any(a => string.Equals(a, flag, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// ─── Lizenz-CLI (kein Host nötig) ──────────────────────────────────────────
|
||||
|
||||
private static int PrintLicenseStatus()
|
||||
{
|
||||
LicenseConfig.SetStorageDirectory(AppPaths.Config);
|
||||
|
||||
var hw = HardwareId.GetHardwareId(DcConfig.ProductSlug);
|
||||
var state = LicenseClient.TryGetCachedState(DcConfig.ProductSlug);
|
||||
|
||||
Console.WriteLine($"HWID v2: {hw.HardwareId} ({hw.HwidSource})");
|
||||
Console.WriteLine($"Schlüssel: {LicenseClient.TryGetCachedKey(DcConfig.ProductSlug) ?? "keiner"}");
|
||||
Console.WriteLine($"Cache: {(state == null ? "leer" : state.Status)}");
|
||||
|
||||
if (state is { CacheExpiresAt: > 0 })
|
||||
{
|
||||
var until = DateTimeOffset.FromUnixTimeSeconds(state.CacheExpiresAt);
|
||||
Console.WriteLine($"Offline noch bis: {until:u}");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static async Task<int> SetLicenseKeyAsync(string key)
|
||||
{
|
||||
LicenseConfig.SetStorageDirectory(AppPaths.Config);
|
||||
|
||||
// ValidateAsync legt den Schluessel bei Erfolg selbst im verschluesselten Cache ab - eine
|
||||
// eigene Ablage braucht es dafuer nicht.
|
||||
var client = new LicenseClient();
|
||||
var result = await client.ValidateAsync(DcConfig.ProductSlug, key, DcConfig.BaseUrl, DcConfig.AppVersion)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine(result.Message);
|
||||
return result.IsValid ? 0 : 1;
|
||||
}
|
||||
|
||||
private static string RuntimeDescription() =>
|
||||
$"{System.Runtime.InteropServices.RuntimeInformation.OSDescription.Trim()} " +
|
||||
$"({System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture})";
|
||||
|
||||
Reference in New Issue
Block a user