Damit mehrere Agenten gleichzeitig Oberflaechen testen koennen, bekommt jedes Projekt eine feste Displaynummer (Vorgabe Richard): :99 PolyTrader, :98 Predictalytics, :97 ClawdDotNet, :96 IBKRTrader, :95 und absteigend fuer kuenftige Projekte. Zwei Prozesse auf demselben Display sehen die Fenster des jeweils anderen - Klicks landen im falschen Programm, Aufnahmen zeigen fremde Anwendungen. Beim Durcharbeiten fielen zwei weitere Kollisionspunkte auf, die zum Parallelbetrieb dazugehoeren und mit aufgenommen wurden: - Screenshot- und Logdateien in /tmp brauchen ein Projektpraefix. /tmp/v_1.png ueberschreiben sich parallel laufende Projekte gegenseitig. - "xvfb-run -a" darf NICHT mehr verwendet werden: Das -a sucht sich selbst ein freies Display und beginnt dabei bei :99 - ausgerechnet PolyTraders Nummer. Stattdessen "xvfb-run -n <nummer>". Die Warnung ist nicht theoretisch: Auf der VM lag ein verwaister X98-Socket, entstanden durch genau so einen "xvfb-run -a"-Aufruf beim smoke-ui-Test, als :99 belegt war. Er blockierte Predictalytics' reservierte Nummer, ohne dass ein Prozess lief. Entfernt, und die Anleitung erklaert jetzt, wie man verwaiste Sockets erkennt (ls /tmp/.X11-unix/ gegen pgrep -a Xvfb lesen) und aufraeumt. Weiter ergaenzt: - pkill nie ohne Displaynummer - "pkill Xvfb" erschlaegt die Sitzungen aller anderen Projekte. - DISPLAY in jedem Skript setzen, sonst landet man auf :0, dem echten Desktop. - Die Skriptvorlage hat jetzt einen Kopf mit PROJEKT/DISP/FENSTER; das ist alles, was sich je Projekt unterscheidet. Praktisch gegengetestet. - Aufraeumabschnitt und Belegungsliste um Displays und Verzeichnisse erweitert. Nebenbei aufgefallen: Das PolyTrader-Publish lag unter ~/pt-test statt ~/polytrader-test und verletzte damit die eigene Verzeichniskonvention. Umbenannt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
193 lines
8.9 KiB
C#
193 lines
8.9 KiB
C#
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.IO;
|
||
using System.Xml.Serialization;
|
||
|
||
namespace PolyTraderSharp.Models
|
||
{
|
||
/// <summary>Farbschema der Oberfläche.</summary>
|
||
public enum AppThemeVariant
|
||
{
|
||
/// <summary>Folgt der Einstellung des Betriebssystems.</summary>
|
||
System,
|
||
Hell,
|
||
Dunkel
|
||
}
|
||
|
||
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("Anzeige")]
|
||
[DisplayName("Farbschema")]
|
||
[Description("Hell, Dunkel oder der Einstellung des Betriebssystems folgen. " +
|
||
"Lässt sich jederzeit über den Umschalter in der Fensterleiste wechseln; " +
|
||
"der zuletzt gewählte Stand wird hier gespeichert.")]
|
||
public AppThemeVariant Theme { get; set; } = AppThemeVariant.System;
|
||
|
||
[Category("Anzeige")]
|
||
[DisplayName("Zeitzone")]
|
||
[Description("Zeitzone für Anzeige und Logdatei-Tagesgrenzen, IANA-Schreibweise (z.B. \"Europe/Berlin\", " +
|
||
"\"America/New_York\"). Leer = Zeitzone des Systems. Wird üblicherweise EINMALIG bei der " +
|
||
"Installation gesetzt: eine spätere Änderung verschiebt Tagesgrenzen in Logs und Auswertungen. " +
|
||
"Greift erst nach einem Neustart.")]
|
||
public string ApplicationTimeZoneId { get; set; } = PolyTraderSharp.Services.AppTimeZone.DefaultId;
|
||
|
||
[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("Pfad zum Mullvad-Kommandozeilenwerkzeug. Der Standard richtet sich nach dem " +
|
||
"Betriebssystem: unter Linux /usr/bin/mullvad, unter Windows die mullvad.exe im " +
|
||
"Installationsverzeichnis.")]
|
||
public string MullvadCliPath { get; set; } = DefaultMullvadCliPath;
|
||
|
||
/// <summary>
|
||
/// Standardpfad des Mullvad-CLI je Plattform. Bis zum 26.08.2026 stand hier fest der
|
||
/// Windows-Pfad - auf dem Linux-Zielserver war der Standardwert damit sinnlos und die
|
||
/// VPN-Rotation scheiterte mit einer Meldung, der man die Ursache nicht ansieht
|
||
/// (Befund B-5 des Linux-Sichttests).
|
||
/// </summary>
|
||
public static string DefaultMullvadCliPath =>
|
||
System.OperatingSystem.IsWindows()
|
||
? @"C:\Program Files\Mullvad VPN\resources\mullvad.exe"
|
||
: "/usr/bin/mullvad";
|
||
|
||
|
||
[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 Watchdog " +
|
||
"des Deploymentcenters. 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("Deploymentcenter-URL")]
|
||
[Description("Basis-URL des Deploymentcenters, z.B. https://dc.mhdf.de (ersetzt den früheren " +
|
||
"eigenständigen Watchdog-Server). 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);
|
||
}
|
||
}
|
||
}
|