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>
This commit is contained in:
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user