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>
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 falseist 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 einDllNotFoundExceptionoderUnable to load shared libraryauftaucht, 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:
- Die vollständige Ausgabe vom Start bis nach
Strg+C. - Ob der Prozess auf
Strg+Csauber endet oder hängt. - 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.
copytruncateist 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:
cat /etc/os-releaseunddotnet --list-runtimes(Schritt 2.1/2.2)- Ob Schritt 3 ohne die GUI-Pakete aus 2.3 lief
- Die vollständige Startausgabe aus Schritt 3
- Verhalten ohne Datenbank (3.1)
systemctl status+ Journal (4.1)- Ergebnis der
WorkingDirectory-Prüfung (4.2) - Stopp-Dauer und ob
SIGKILLauftrat (5.1), Neustart-Verhalten (5.2) - Die vier Zeitzonen-Ausgaben (Teil 6)
- Logrotate-Trockenlauf (Teil 7)
- 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;