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>
192 lines
8.5 KiB
C#
192 lines
8.5 KiB
C#
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.IO;
|
||
using System.Xml.Serialization;
|
||
|
||
namespace PolyTraderSharp.Models
|
||
{
|
||
public class ServerSettings
|
||
{
|
||
[Category("Module")]
|
||
[DisplayName("Deaktivierte Module")]
|
||
[Description("Namen der Module (z.B. \"CopyTrading\"), die beim App-Start NICHT geladen werden. " +
|
||
"Leer = alle Module aktiv. Änderungen greifen nach dem nächsten Neustart. " +
|
||
"Wird i.d.R. über das Dashboard gepflegt.")]
|
||
[XmlArray("DisabledModules")]
|
||
[XmlArrayItem("Module")]
|
||
public List<string> DisabledModules { get; set; } = new();
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Threema Enabled")]
|
||
[Description("Enable or disable Threema notifications.")]
|
||
public bool ThreemaEnabled { get; set; } = true;
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Gateway ID")]
|
||
[Description("The Threema Gateway ID (e.g. *3MAGW01).")]
|
||
public string ThreemaGatewayId { get; set; } = "*3MAGW01";
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Gateway Secret")]
|
||
[Description("The secret for the Threema Gateway integration.")]
|
||
public string ThreemaSecret { get; set; } = "";
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Private Key")]
|
||
[Description("The Private Key (hex) for End-to-End encryption.")]
|
||
public string ThreemaPrivateKey { get; set; } = "";
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Group ID")]
|
||
[Description("The Threema Group ID to send messages to.")]
|
||
public string ThreemaGroupId { get; set; } = "";
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Webhook Port")]
|
||
[Description("The local port to listen on for incoming Threema messages (e.g. 8080).")]
|
||
public int ThreemaWebhookPort { get; set; } = 8080;
|
||
|
||
[Category("Threema Notifications")]
|
||
[DisplayName("Report Interval (Hours)")]
|
||
[Description("Interval for the automatic summary report.")]
|
||
public int ThreemaReportIntervalHours { get; set; } = 6;
|
||
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("VPN Enabled")]
|
||
[Description("Enable or disable automatic VPN rotation.")]
|
||
public bool VpnEnabled { get; set; } = false;
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("Mullvad Account")]
|
||
[Description("Account ID for Mullvad VPN. In server_settings.xml (gitignored) hinterlegen – NICHT im Code committen.")]
|
||
public string MullvadAccount { get; set; } = ""; // F3: Secret nicht mehr im Quellcode
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("VPN Location")]
|
||
[Description("Target VPN location (e.g. cz).")]
|
||
public string VpnLocation { get; set; } = "cz";
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("Mullvad CLI Path")]
|
||
[Description("Path to mullvad.exe")]
|
||
public string MullvadCliPath { get; set; } = @"C:\Program Files\Mullvad VPN\resources\mullvad.exe";
|
||
|
||
|
||
[Category("Blockchain Listener")]
|
||
[DisplayName("Enable Blockchain Listener")]
|
||
[Description("If true, connects to Alchemy WSS for faster on-chain signal detection.")]
|
||
public bool EnableBlockchainListener { get; set; } = true;
|
||
|
||
[Category("Blockchain Listener")]
|
||
[DisplayName("Polygon RPC URL")]
|
||
[Description("RPC-/WSS-URL inkl. Alchemy-API-Key. In server_settings.xml (gitignored) hinterlegen – der Key gehört NICHT in den Quellcode/Git. Format: wss://polygon-mainnet.g.alchemy.com/v2/<KEY>")]
|
||
public string PolygonRpcUrl { get; set; } = ""; // F3: Alchemy-Key nicht mehr im Quellcode
|
||
|
||
[Category("Polymarket WebSockets")]
|
||
[DisplayName("Use Polymarket WebSockets")]
|
||
[Description("If true, connects to Polymarket WSS for live market prices and user events.")]
|
||
public bool UsePolymarketWebsockets { get; set; } = false;
|
||
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Watchdog Enabled")]
|
||
[Description("Wenn true, sendet PolyTrader im festen Takt einen Heartbeat an den externen " +
|
||
"Watchdog-Server. Bleiben die Heartbeats aus, alarmiert der Watchdog (Dead-Man's-Switch). " +
|
||
"Ein nicht erreichbarer Watchdog beeinträchtigt PolyTrader NIE.")]
|
||
public bool WatchdogEnabled { get; set; } = false;
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Watchdog URL")]
|
||
[Description("Basis-URL des Watchdog-Servers, z.B. https://watchdog.example.de. " +
|
||
"HTTPS ist Pflicht – http:// wird nur für localhost akzeptiert, damit der Agent-Token " +
|
||
"nicht im Klartext über das Netz geht.")]
|
||
public string WatchdogUrl { get; set; } = "";
|
||
|
||
/// <summary>
|
||
/// Agent-Token für den Header <c>X-Watchdog-Key</c>. Bewusst NICHT im PropertyGrid sichtbar:
|
||
/// Setzen erfolgt über den Button „Watchdog-Token setzen …" mit maskierter Eingabe, damit das
|
||
/// Secret nicht offen im Fenster steht und bei gesetztem Master-Key verschlüsselt
|
||
/// (<c>enc:v1:…</c>) in der gitignorierten server_settings.xml landet.
|
||
/// </summary>
|
||
[Browsable(false)]
|
||
public string WatchdogToken { get; set; } = "";
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Agent-Token")]
|
||
[Description("Status des Agent-Tokens. Setzen/Ändern über den Button „Watchdog-Token setzen …" +
|
||
"\" in der Werkzeugleiste (maskierte Eingabe).")]
|
||
[ReadOnly(true)]
|
||
[XmlIgnore]
|
||
public string WatchdogTokenStatus =>
|
||
string.IsNullOrWhiteSpace(WatchdogToken)
|
||
? "nicht gesetzt"
|
||
: (WatchdogToken.StartsWith("enc:v1:", System.StringComparison.Ordinal)
|
||
? "gesetzt (verschlüsselt)"
|
||
: "gesetzt (Klartext)");
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Source")]
|
||
[Description("Dienst-Kennung im Watchdog (muss dem dort angelegten Monitor entsprechen), z.B. \"polytrader\".")]
|
||
public string WatchdogSource { get; set; } = "polytrader";
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Instance")]
|
||
[Description("Instanz-Kennung, falls mehrere PolyTrader-Instanzen laufen, z.B. \"prod-1\".")]
|
||
public string WatchdogInstance { get; set; } = "prod-1";
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Heartbeat-Intervall (Sekunden)")]
|
||
[Description("Sende-Takt des Heartbeats. Der Watchdog leitet daraus seine Staleness-Frist ab " +
|
||
"(Intervall × Faktor + Grace). Minimum 10 s, Empfehlung 60 s.")]
|
||
public int WatchdogIntervalSeconds { get; set; } = 60;
|
||
|
||
|
||
/// <summary>
|
||
/// Lizenzschlüssel (LicenseLabrador). Wie der Watchdog-Token bewusst NICHT im PropertyGrid
|
||
/// sichtbar; gepflegt über den Button „Lizenzschlüssel setzen …" bzw. den Startdialog. Bei
|
||
/// gesetztem Master-Key verschlüsselt (<c>enc:v1:…</c>) in der gitignorierten server_settings.xml.
|
||
/// </summary>
|
||
[Browsable(false)]
|
||
public string LicenseKey { get; set; } = "";
|
||
|
||
[Category("Lizenz")]
|
||
[DisplayName("Lizenzschlüssel")]
|
||
[Description("Status des Lizenzschlüssels. Setzen/Ändern über den Button „Lizenzschlüssel setzen …\" " +
|
||
"in der Werkzeugleiste bzw. beim Programmstart, falls keine gültige Lizenz vorliegt.")]
|
||
[ReadOnly(true)]
|
||
[XmlIgnore]
|
||
public string LicenseKeyStatus =>
|
||
string.IsNullOrWhiteSpace(LicenseKey)
|
||
? "nicht gesetzt"
|
||
: (LicenseKey.StartsWith("enc:v1:", System.StringComparison.Ordinal)
|
||
? "gesetzt (verschlüsselt)"
|
||
: "gesetzt (Klartext)");
|
||
|
||
|
||
public static ServerSettings Load(string path)
|
||
{
|
||
if (!File.Exists(path))
|
||
return new ServerSettings();
|
||
|
||
try
|
||
{
|
||
var serializer = new XmlSerializer(typeof(ServerSettings));
|
||
using var fs = new FileStream(path, FileMode.Open);
|
||
return (ServerSettings?)serializer.Deserialize(fs) ?? new ServerSettings();
|
||
}
|
||
catch
|
||
{
|
||
return new ServerSettings();
|
||
}
|
||
}
|
||
|
||
public void Save(string path)
|
||
{
|
||
var serializer = new XmlSerializer(typeof(ServerSettings));
|
||
using var fs = new FileStream(path, FileMode.Create);
|
||
serializer.Serialize(fs, this);
|
||
}
|
||
}
|
||
}
|