Files
PolyTraderSharp/docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md
RichardandClaude Opus 5 e14dd25979
CI / Build & Tests (Linux) (push) Waiting to run
CI / Plattformneutralität & Hygiene (push) Waiting to run
Pruefplan fuer den ersten Linux-Lauf (A4a)
docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md: Schritt-fuer-Schritt-Protokoll zum
Abarbeiten auf einer Test-VM, jeder Schritt mit erwartetem Ergebnis und der
Angabe, was zurueckzumelden ist.

Zwei Fallen vorab, die beim Vorbereiten aufgefallen sind:

1. Der Linux-Publish enthaelt appsettings.Local.json mit den echten
   MySQL-Zugangsdaten. Ungeprueft auf die VM kopiert, laeuft der Test gegen die
   PRODUKTIVdatenbank - und der MarketSyncService schreibt dort als
   Hintergrunddienst hinein. Der Plan legt deshalb zuerst eine eigene
   Testdatenbank samt eigenem Benutzer an; das Umstellen der Verbindung ist
   ausdruecklich als wichtigster Schritt markiert.
2. Es gibt keine automatischen Migrationen beim Start (nachgeprueft: kein
   Migrate()/EnsureCreated() im Code). Das Schema muss vorher stehen - fuenf
   DbContexts, jeder mit eigenen Migrationen, ueber die Umgebungsvariable
   POLYTRADER_MYSQL.

Inhaltliche Schwerpunkte des Plans:
- Die Hypothese, dass --headless OHNE GUI-Bibliotheken auskommt, weil
  BuildAvaloniaApp() dort nie aufgerufen wird und Skia damit nie geladen wird.
  Der Plan prueft das bewusst zuerst ohne die Pakete - das Ergebnis entscheidet,
  was spaeter ins Server-Grundimage muss.
- Die WorkingDirectory-Falle aus D-11: server_settings.xml relativ zum
  Arbeitsverzeichnis, master.key relativ zur Programmdatei. Bei Abweichung legt
  ServerSettings.Load() kommentarlos eine neue Datei an. Wird per find ueber
  frisch geaenderte Dateien nachgewiesen.
- SIGTERM-Shutdown gegen TimeoutStopSec=45 gemessen, Restart=on-failure
  per pkill -9 gegengetestet.
- T2 (TerminalLogger stempelt DateTime.Now statt AppTimeZone) wird auf einer
  UTC-VM erstmals nachweisbar statt nur behauptet.
- Logrotate mit copytruncate, weil die App die Logdatei offen haelt.
- Master-Key als Umgebungsvariable im Dienst: erster echter Lauf von
  FilePermissions unter Linux-Rechten.

Am Ende eine Liste der zehn zurueckzumeldenden Ausgaben und die Befehle zum
Aufraeumen der VM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 22:33:00 +02:00

14 KiB

Prüfplan: Linux-Betrieb (Roadmap A4a)

Erstellt: 23.08.2026 · Für den ersten Lauf auf einer Test-VM. Roadmap-Bezug: A4a

Warum das wichtig ist: PolyTrader ist noch nie auf Linux gelaufen. Verifiziert ist bisher nur, dass sich die Anwendung für linux-x64 übersetzen und publishen lässt — das ist etwas völlig anderes. Dieser Durchgang klärt, ob sie dort auch startet, läuft und sich sauber beenden lässt.

So arbeitest du damit: Schritte der Reihe nach abarbeiten. Bei jedem Schritt steht, was erwartet wird und was du mir melden sollst. Wenn etwas abweicht: abbrechen, Ausgabe schicken — nicht selbst reparieren, sonst wissen wir am Ende nicht, was der Ausgangszustand war.


⚠️ Vorab: zwei Dinge, die schiefgehen können

1. Nicht gegen die Produktivdatenbank testen

Der Publish enthält appsettings.Local.json mit deinen echten MySQL-Zugangsdaten. Startest du die Anwendung so auf der VM, verbindet sie sich mit deiner produktiven Datenbank — und der MarketSyncService läuft als Hintergrunddienst und schreibt dort hinein. Zwei Instanzen auf derselben Datenbank sind unsauber.

Deshalb: eigene Testdatenbank (Schritt 1.3). Kein Weltuntergang, wenn es passiert — es gibt keine automatischen Migrationen beim Start, und ohne Lizenz laufen die Trading-Module gar nicht. Aber sauber ist anders.

2. Zugangsdaten gehen über die Leitung

appsettings.Local.json ist eine Secret-Datei. Beim Kopieren auf die VM landet sie dort im Klartext. Für eine Test-VM in deinem Netz vertretbar — aber setz dort die Testdatenbank-Daten ein, nicht die produktiven (siehe 1.3), dann ist das Risiko gegenstandslos.


Teil 1 — Vorbereitung auf dem Windows-Rechner

1.1 Publish erstellen

dotnet publish src/PolyTrader.App.Avalonia/PolyTrader.App.Avalonia.csproj -c Release -r linux-x64 --self-contained false -o ./publish-linux

Erwartet: 0 Fehler, ~41 MB, darin PolyTrader.App.Avalonia (ohne Endung), libSkiaSharp.so, libHarfBuzzSharp.so, appsettings.json, setup.json.

--self-contained false ist Absicht: Das ist der Weg, den auch der Deploymentcenter-Packager geht. Wir testen die echte Auslieferungsform, nicht eine bequemere. Dafür braucht die VM die .NET-10-Runtime (Schritt 2.2).

1.2 Testdatenbank anlegen

Auf deinem MySQL-Server:

CREATE DATABASE polytrader_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Und einen eigenen Benutzer dafür — nicht den produktiven wiederverwenden:

CREATE USER 'polytrader_test'@'%' IDENTIFIED BY '<eigenes-passwort>';
GRANT ALL PRIVILEGES ON polytrader_test.* TO 'polytrader_test'@'%';
FLUSH PRIVILEGES;

1.3 Schema in die Testdatenbank migrieren

Es gibt keine automatischen Migrationen beim Start — das Schema muss vorher stehen. Die Design-Time-Factories lesen die Verbindung aus POLYTRADER_MYSQL. Fünf DbContexts, jeder mit eigenen Migrationen:

export POLYTRADER_MYSQL="Server=<db-host>;Port=3306;Database=polytrader_test;User ID=polytrader_test;Password=<passwort>;"
dotnet ef database update --project src/PolyTrader.Core
dotnet ef database update --project src/PolyTrader.Modules.CopyTrading
dotnet ef database update --project src/PolyTrader.Modules.ResolutionFarming
dotnet ef database update --project src/PolyTrader.Modules.Supervisor
dotnet ef database update --project src/PolyTrader.Modules.Accounting

Falls dotnet ef fehlt: dotnet tool install --global dotnet-ef

Erwartet: Fünfmal „Done." Melde mir: ob alle fünf durchlaufen — und bei Fehlern die vollständige Meldung. Prüfen mit:

USE polytrader_test; SHOW TABLES;

Erwartet: Tabellen mit den Präfixen core_, ct_, rf_, sup_, acc_.

1.4 Zugangsdaten für die VM eintragen

In ./publish-linux/appsettings.Local.json die Verbindung auf die Testdatenbank ändern:

{
  "Database": {
    "MySqlConnectionString": "Server=<db-host>;Port=3306;Database=polytrader_test;User ID=polytrader_test;Password=<passwort>;"
  }
}

⚠️ Dieser Schritt ist der wichtigste des ganzen Prüfplans. Wird er vergessen, läuft der Test gegen die Produktivdatenbank.

1.5 systemd-Unit dazulegen und übertragen

Die Unit liegt im Repo, nicht im Publish — sie muss mit:

cp deploy/polytrader.service ./publish-linux/
scp -r ./publish-linux <user>@<vm-host>:/tmp/polytrader

Teil 2 — VM vorbereiten

2.1 Bestandsaufnahme

cat /etc/os-release; uname -m; timedatectl | head -3; free -m | head -2

Melde mir die vollständige Ausgabe. Danach richten sich die Paketnamen in 2.2/2.3, und die Zeitzonen-Angabe brauche ich für Schritt 6.

2.2 .NET-10-Runtime installieren

Die Anwendung ist framework-abhängig gebaut, sie braucht die ASP.NET-Core-Runtime nicht, wohl aber die .NET-Runtime 10.

Debian/Ubuntu:

sudo apt-get update && sudo apt-get install -y dotnet-runtime-10.0

Falls das Paket in den Distributionsquellen fehlt, über Microsofts Quelle:

curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 --runtime dotnet --install-dir /usr/share/dotnet
sudo ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet

RHEL/Fedora: sudo dnf install -y dotnet-runtime-10.0

Prüfen:

dotnet --list-runtimes

Erwartet: eine Zeile Microsoft.NETCore.App 10.0.x. Melde mir die Ausgabe.

2.3 Native Abhängigkeiten

libSkiaSharp.so und libHarfBuzzSharp.so liegen zwar bei, brauchen aber Systembibliotheken.

Hypothese, die dieser Test klärt: Im --headless-Betrieb wird Avalonia gar nicht hochgefahren (BuildAvaloniaApp() wird nicht aufgerufen), also sollte Skia nie geladen werden und keine GUI-Bibliothek nötig sein. Das ist die Erwartung — bestätigt ist sie nicht. Deshalb: 2.3 zunächst überspringen und direkt Schritt 3 versuchen. Nur wenn dort ein DllNotFoundException oder Unable to load shared library auftaucht, hierher zurückkommen:

sudo apt-get install -y libfontconfig1 libice6 libsm6 libx11-6

Melde mir: ob Schritt 3 ohne diese Pakete lief. Das ist ein echtes Ergebnis — es entscheidet, was später ins Server-Grundimage muss.


Teil 3 — Erststart von Hand

Bewusst noch ohne systemd: erst wissen, ob das Programm überhaupt läuft.

cd /tmp/polytrader
chmod +x PolyTrader.App.Avalonia
./PolyTrader.App.Avalonia --headless

Erwartet: Startmeldungen; darin sinngemäß Kopfloser Betrieb: keine Oberfläche, Trading-Dienste laufen. Beenden mit Strg+C. Dazu eine Warnung über die fehlende Lizenz und den eingeschränkten Modus — das ist richtig so: Ohne Deploymentcenter-Lizenz startet die Core-Shell ohne Trading-Module, und genau das wollen wir hier.

Ein paar Minuten laufen lassen, dann Strg+C.

Melde mir:

  1. Die vollständige Ausgabe vom Start bis nach Strg+C.
  2. Ob der Prozess auf Strg+C sauber endet oder hängt.
  3. Ob eine Logs/-Datei entstanden ist: ls -la Logs/

Wenn es hier abbricht — Ausgabe schicken, nicht weitermachen. Typische Kandidaten: fehlende Runtime (2.2), fehlende native Bibliothek (2.3), Datenbank nicht erreichbar.

3.1 Gegenprobe: Verhalten ohne Datenbank

Kurz die Verbindung unbrauchbar machen und erneut starten:

cp appsettings.Local.json appsettings.Local.json.bak
sed -i 's/Server=[^;]*/Server=192.0.2.1/' appsettings.Local.json
./PolyTrader.App.Avalonia --headless

Erwartet: eine verständliche Fehlermeldung, kein Stacktrace-Absturz, kein endloser Neustart-Sturm. 192.0.2.1 ist eine Adresse, die per Definition nirgends antwortet — es gibt also einen Verbindungs-Timeout.

Danach zurücksetzen:

mv appsettings.Local.json.bak appsettings.Local.json

Melde mir: wie sich die Anwendung verhält und wie lange es bis zur Meldung dauert.


Teil 4 — Als systemd-Dienst

4.1 Einrichten

sudo useradd --system --home-dir /opt/polytrader --shell /usr/sbin/nologin polytrader
sudo mkdir -p /opt/polytrader
sudo cp -r /tmp/polytrader/* /opt/polytrader/
sudo chown -R polytrader:polytrader /opt/polytrader
sudo chmod +x /opt/polytrader/PolyTrader.App.Avalonia
sudo cp /tmp/polytrader/polytrader.service /etc/systemd/system/polytrader.service

Die Unit ist unverändert übernehmbar: Sie erwartet genau /opt/polytrader als WorkingDirectory und das Konto polytrader — beides oben so angelegt. Dann:

sudo systemctl daemon-reload
sudo systemctl enable --now polytrader
sleep 10
sudo systemctl status polytrader --no-pager

Erwartet: active (running).

Melde mir: die Statusausgabe und

sudo journalctl -u polytrader -n 60 --no-pager

4.2 Die WorkingDirectory-Falle prüfen (D-11)

Der interessanteste Einzelpunkt dieses Prüfplans. server_settings.xml wird relativ zum Arbeitsverzeichnis geladen, master.key relativ zur Programmdatei. Stimmen die nicht überein, legt ServerSettings.Load() kommentarlos eine neue Einstellungsdatei an — der Dienst läuft dann mit Standardwerten, ohne dass irgendwo eine Warnung erscheint.

ls -la /opt/polytrader/server_settings.xml
sudo find / -name "server_settings.xml" -newermt "-10 minutes" 2>/dev/null

Erwartet: die Datei liegt in /opt/polytrader/ und nirgends sonst. Taucht sie in einem anderen Verzeichnis auf (etwa / oder im Home des Dienstkontos), hat die Falle zugeschnappt.

Melde mir beide Ausgaben.


Teil 5 — Beenden und Neustart

5.1 Geordnetes Beenden

time sudo systemctl stop polytrader
sudo journalctl -u polytrader -n 30 --no-pager

Erwartet: Beenden deutlich unter 45 Sekunden (TimeoutStopSec=45); im Journal Meldungen über das geordnete Herunterfahren, kein SIGKILL/Killed. Der Shutdown-Pfad ist wichtig: Im Echtbetrieb hängen daran offene Positionen und der DB-Flush.

Melde mir: die Dauer aus time und ob SIGKILL im Journal auftaucht.

5.2 Neustart nach Absturz

sudo systemctl start polytrader
sleep 5
sudo pkill -9 -f PolyTrader.App.Avalonia
sleep 20
sudo systemctl status polytrader --no-pager | head -5

Erwartet: Restart=on-failure greift, der Dienst läuft nach ~10 s wieder (RestartSec=10).

Melde mir: ob er von selbst zurückkommt.


Teil 6 — Zeitzonen-Befund (T2)

Hier wird ein bisher nur theoretischer Befund erstmals nachweisbar: Der TerminalLogger stempelt mit DateTime.Now statt der konfigurierten AppTimeZone.

timedatectl | grep "Time zone"
grep -i timezone /opt/polytrader/server_settings.xml
ls -la /opt/polytrader/Logs/
date

Zu klären: Steht die VM auf UTC und ApplicationTimeZoneId auf Europe/Berlin, dann müsste die Logdatei nach UTC-Datum benannt sein, während die Zeitstempel in der Anzeige Berliner Zeit zeigen. Zwischen 00:00 und 02:00 Berliner Zeit fallen sie auf verschiedene Tage.

Melde mir alle vier Ausgaben plus die ersten Zeilen der aktuellen Logdatei:

head -5 /opt/polytrader/Logs/$(date -u +%Y-%m-%d).jsonl

Falls die VM auf Europe/Berlin steht, ist der Effekt nicht sichtbar — dann bitte sudo timedatectl set-timezone UTC, Dienst neu starten und erneut prüfen. Das ist der realistische Fall: Server laufen üblicherweise auf UTC.


Teil 7 — Logrotate

sudo tee /etc/logrotate.d/polytrader > /dev/null <<'EOF'
/opt/polytrader/Logs/*.jsonl {
    daily
    rotate 14
    compress
    missingok
    notifempty
    copytruncate
    su polytrader polytrader
}
EOF
sudo logrotate -d /etc/logrotate.d/polytrader

-d ist ein Trockenlauf, er ändert nichts.

copytruncate ist bewusst gewählt: Die Anwendung hält die Logdatei offen und bekommt ein Umbenennen nicht mit — sie würde sonst in die rotierte Datei weiterschreiben.

Melde mir: die Ausgabe des Trockenlaufs.


Teil 8 — Master-Key unter Linux-Rechten

Bisher nur unter Windows erprobt.

sudo systemctl stop polytrader
sudo mkdir -p /etc/systemd/system/polytrader.service.d
sudo tee /etc/systemd/system/polytrader.service.d/masterkey.conf > /dev/null <<'EOF'
[Service]
Environment="POLYTRADER_MASTER_KEY=nur-fuer-den-test-bitte-spaeter-ersetzen"
EOF
sudo systemctl daemon-reload
sudo systemctl start polytrader
sleep 10
sudo journalctl -u polytrader -n 40 --no-pager | grep -iE "master|key|verschl|encrypt|permission|denied"

Erwartet: keine Fehler zu Dateirechten. FilePermissions setzt unter Linux restriktive Rechte (0600) — das ist der Pfad, der hier erstmals wirklich läuft.

Melde mir: die gefilterte Ausgabe. Der Testschlüssel wird später ersetzt (Roadmap A5).


Teil 9 — Was ich am Ende brauche

Sammle bitte:

  1. cat /etc/os-release und dotnet --list-runtimes (Schritt 2.1/2.2)
  2. Ob Schritt 3 ohne die GUI-Pakete aus 2.3 lief
  3. Die vollständige Startausgabe aus Schritt 3
  4. Verhalten ohne Datenbank (3.1)
  5. systemctl status + Journal (4.1)
  6. Ergebnis der WorkingDirectory-Prüfung (4.2)
  7. Stopp-Dauer und ob SIGKILL auftrat (5.1), Neustart-Verhalten (5.2)
  8. Die vier Zeitzonen-Ausgaben (Teil 6)
  9. Logrotate-Trockenlauf (Teil 7)
  10. Master-Key-Ausgabe (Teil 8)

Daraus werte ich aus, korrigiere die polytrader.service und die Doku, und hake A4a in der Roadmap ab — oder trage die gefundenen Fehler als neue Punkte ein.

Aufräumen (die VM wieder freigeben):

sudo systemctl disable --now polytrader
sudo rm -f /etc/systemd/system/polytrader.service /etc/logrotate.d/polytrader
sudo rm -rf /etc/systemd/system/polytrader.service.d
sudo systemctl daemon-reload
sudo rm -rf /opt/polytrader /tmp/polytrader
sudo userdel polytrader

Und die Testdatenbank, falls nicht mehr gebraucht: DROP DATABASE polytrader_test;