From e14dd25979e41fa4279815eda90a3fc3530c4ff7 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 23 Aug 2026 22:33:00 +0200 Subject: [PATCH] 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 --- docs/README.md | 3 +- docs/ROADMAP.md | 3 + docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md | 411 ++++++++++++++++++++ 3 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md diff --git a/docs/README.md b/docs/README.md index cfebdf1..6526c10 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,8 @@ Kurzformel: **PROJEKTSTAND = was ist. ROADMAP = was kommt.** - **[`steuer/`](./steuer/)** — Steuer-/Buchhaltungs-Fachdokumente und Vorlagen, auch zum Weitergeben an Berater. - `Accounting-US-Tax-Questionnaire.md` — Fragebogen (EN) für die US-Steuerberaterin (Florida LLC). -- **[`pruefplaene/`](./pruefplaene/)** — Prüf-/Validierungspläne. +- **[`pruefplaene/`](./pruefplaene/)** — Prüf-/Validierungspläne zum Abarbeiten. + - `PRUEFPLAN-Linux-Betrieb.md` — erster Lauf auf einer Linux-VM (Roadmap A4a). - `PREDICTALYTICS-PRUEFPLAN-Master-Auswahl.md` — Master-Trader-Auswahl (separates Predictalytics-Projekt). - **`ideen/`** — frühe Ideen, bevor sie zu einem Konzept reifen. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3393840..2ac609d 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -151,6 +151,9 @@ Zu prüfen: **Ergebnis:** Danach ist belegt, dass die Software auf Linux läuft — nicht nur, dass sie sich übersetzen lässt. +→ **Durchführung: [pruefplaene/PRUEFPLAN-Linux-Betrieb.md](./pruefplaene/PRUEFPLAN-Linux-Betrieb.md)** +— Schritt-für-Schritt-Protokoll mit erwarteten Ergebnissen, zum Abarbeiten auf der Test-VM. + ### A4b 🔒 Feldtest im Zielland *Blockiert durch A3 und A4a.* diff --git a/docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md b/docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md new file mode 100644 index 0000000..db61da2 --- /dev/null +++ b/docs/pruefplaene/PRUEFPLAN-Linux-Betrieb.md @@ -0,0 +1,411 @@ +# 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;`