Testanleitung: eigenes X-Display je Projekt fuer Parallelbetrieb
CI / Build & Tests (Linux) (push) Canceled after 0s
CI / Plattformneutralität & Hygiene (push) Canceled after 0s

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>
This commit is contained in:
Richard
2026-08-27 20:49:28 +02:00
co-authored by Claude Opus 5
parent 62570cfdd8
commit 9b2294cb4c
33 changed files with 979 additions and 244 deletions
+80 -5
View File
@@ -190,6 +190,26 @@ namespace PolyTrader.App.Avalonia
ConfigureSecretProtection(logger);
// Selbstheilende Migration (B-12): ein noch im Klartext abgelegter Lizenzschluessel
// wird beim ersten Start mit Master-Key verschluesselt nachgezogen. server_settings.xml
// traegt neben ihm auch den Watchdog-Token - die Konvention "Secrets nur verschluesselt
// at-rest" soll dort genauso gelten wie in der Datenbank.
if (SecretProtection.IsConfigured
&& !string.IsNullOrWhiteSpace(serverSettings.LicenseKey)
&& !SecretProtection.IsEncrypted(serverSettings.LicenseKey))
{
try
{
serverSettings.LicenseKey = SecretProtection.Protect(serverSettings.LicenseKey);
serverSettings.Save(ServerSettingsPath);
logger.Info("🔐 Lizenzschlüssel lag im Klartext vor und wurde verschlüsselt nachgezogen.");
}
catch (Exception ex)
{
logger.Warning($"⚠️ Lizenzschlüssel konnte nicht verschlüsselt nachgezogen werden: {ex.Message}");
}
}
// Anzeige-Zeitzone einmalig setzen (Logs, Auswertungen). Ein unbekannter Wert ist nicht
// fatal - es wird auf die Systemzeitzone ausgewichen und gewarnt.
AppTimeZone.Configure(serverSettings.ApplicationTimeZoneId, m => logger.Warning(m));
@@ -373,6 +393,51 @@ namespace PolyTrader.App.Avalonia
Console.WriteLine($"[FEHLER] Einstellungs-Editor: {ex.GetType().Name}: {ex.Message}");
}
// Der Editor waehlt den Feldtyp nach dem Eigenschaftstyp. Fehlt ein Typ in dieser
// Zuordnung, faellt er still auf "nur Anzeige" zurueck - die Einstellung ist dann in
// der Oberflaeche nicht mehr aenderbar, ohne dass irgendwo ein Fehler auftaucht.
// Genau so waren saemtliche Dezimal-Einstellungen (Positionsgroessen, Preisgrenzen,
// Kill-Switch-Schwellen) unbemerkt zu reinen Anzeigen geworden. Deshalb hier gegen die
// Modelle mit den meisten Zahlenfeldern pruefen.
foreach (object model in new object[]
{
new PolyTraderSharp.Models.CopyTradingAccountSettings(),
new PolyTrader.Modules.ResolutionFarming.Models.RfSettings(),
new ServerSettings()
})
{
try
{
// Listen (z.B. "Deaktivierte Module") stehen bewusst nur zur Anzeige - die
// pflegt der Modul-Tab des Dashboards. Geprueft werden Einzelwerte.
var notEditable = ViewModels.SettingsModelBuilder.Build(model)
.SelectMany(sec => sec.Fields)
.Where(f => f.IsEditable
&& f is ViewModels.ReadOnlySettingsField
&& !typeof(System.Collections.IEnumerable).IsAssignableFrom(f.ValueType))
.Select(f => f.Label)
.ToList();
if (notEditable.Count > 0)
{
failures++;
Console.WriteLine($"[FEHLER] Einstellungs-Editor ({model.GetType().Name}): " +
$"{notEditable.Count} beschreibbare Felder werden nur angezeigt: " +
string.Join(", ", notEditable));
}
else
{
Console.WriteLine($"[OK] Einstellungs-Editor ({model.GetType().Name}): " +
"alle beschreibbaren Felder sind auch bearbeitbar");
}
}
catch (Exception ex)
{
failures++;
Console.WriteLine($"[FEHLER] Einstellungs-Editor ({model.GetType().Name}): {ex.GetType().Name}: {ex.Message}");
}
}
// Farbschema: jeder Token muss in BEIDEN Varianten aufloesen. Ein Tippfehler im
// Schluessel faellt sonst nicht auf - ThemeManager.Brush liefert dann still Grau.
string[] themeKeys =
@@ -427,25 +492,35 @@ namespace PolyTrader.App.Avalonia
/// einer erzeugt – D-5/D-10) und aktiviert die at-rest-Verschlüsselung. Muss VOR jeder
/// Credential-Entschlüsselung laufen.
/// </summary>
private static void ConfigureSecretProtection(TerminalLogger logger)
private static void ConfigureSecretProtection(TerminalLogger logger) =>
ConfigureSecretProtection(msg => logger.Warning(msg), msg => logger.Info(msg));
/// <summary>
/// Wie oben, nur ohne <see cref="TerminalLogger"/>: die Lizenz-CLI läuft VOR dem
/// Host-Aufbau und hat noch keinen. Bis zum 26.08.2026 fehlte dieser Aufruf dort ganz –
/// <c>--license-set-key</c> legte den Schlüssel deshalb im Klartext ab, obwohl der Code
/// <see cref="SecretProtection.Protect"/> aufrief: ohne Master-Key reicht der Aufruf den
/// Klartext unverändert durch (Befund B-12 des Linux-Sichttests).
/// </summary>
internal static void ConfigureSecretProtection(Action<string> warn, Action<string>? info = null)
{
string keyFile = Path.Combine(AppContext.BaseDirectory, "master.key");
string? masterKey = MasterKeyResolver.Resolve(
keyFile,
() => Environment.GetEnvironmentVariable("POLYTRADER_MASTER_KEY"),
onGenerated: _ => logger.Warning(
onGenerated: _ => warn(
"🔐 Kein Master-Key gefunden – ein neuer wurde erzeugt und in \"master.key\" " +
"gespeichert (App-Ordner, gitignored). WICHTIG: Diese Datei SOFORT separat sichern " +
"(Passwort-Manager/Offline-Backup) – ihr Verlust macht jedes ab jetzt verschlüsselte " +
"Secret unwiederbringlich unlesbar."),
onWarning: msg => logger.Warning($"⚠️ {msg}"));
onWarning: msg => warn($"⚠️ {msg}"));
SecretProtection.Configure(masterKey);
if (SecretProtection.IsConfigured)
logger.Info("🔐 Secret-Verschlüsselung aktiv – Account-Credentials werden at-rest verschlüsselt (AES-256-GCM).");
info?.Invoke("🔐 Secret-Verschlüsselung aktiv – Account-Credentials werden at-rest verschlüsselt (AES-256-GCM).");
else
logger.Warning("⚠️ SICHERHEIT: Kein POLYTRADER_MASTER_KEY gesetzt – Account-Credentials liegen UNVERSCHLÜSSELT in der DB.");
warn("⚠️ SICHERHEIT: Kein POLYTRADER_MASTER_KEY gesetzt – Account-Credentials liegen UNVERSCHLÜSSELT in der DB.");
}
}
}