Files
PolyTraderSharp/src/PolyTrader.Core/Models/ServerSettings.cs
T
RichardandClaude Opus 5 9b2294cb4c
CI / Build & Tests (Linux) (push) Canceled after 0s
CI / Plattformneutralität & Hygiene (push) Canceled after 0s
Testanleitung: eigenes X-Display je Projekt fuer Parallelbetrieb
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>
2026-08-27 20:49:28 +02:00

193 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}