# Prüfplan: Linux-Betrieb (Roadmap A4a) **Erstellt: 23.08.2026** · Für den ersten Lauf auf einer Test-VM. Roadmap-Bezug: [A4a](../ROADMAP.md) > **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 ```bash 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: ```sql CREATE DATABASE polytrader_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` Und einen eigenen Benutzer dafür — nicht den produktiven wiederverwenden: ```sql CREATE USER 'polytrader_test'@'%' IDENTIFIED BY ''; 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: ```bash export POLYTRADER_MYSQL="Server=;Port=3306;Database=polytrader_test;User ID=polytrader_test;Password=;" 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: ```sql 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: ```json { "Database": { "MySqlConnectionString": "Server=;Port=3306;Database=polytrader_test;User ID=polytrader_test;Password=;" } } ``` ⚠️ **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: ```bash cp deploy/polytrader.service ./publish-linux/ scp -r ./publish-linux @:/tmp/polytrader ``` --- ## Teil 2 — VM vorbereiten ### 2.1 Bestandsaufnahme ```bash 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:** ```bash sudo apt-get update && sudo apt-get install -y dotnet-runtime-10.0 ``` Falls das Paket in den Distributionsquellen fehlt, über Microsofts Quelle: ```bash 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: ```bash 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: ```bash 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. ```bash 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: ```bash 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: ```bash 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 ```bash 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: ```bash 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 ```bash 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. ```bash 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 ```bash 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 ```bash 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`. ```bash 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: ```bash 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 ```bash 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. ```bash 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): ```bash 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;`