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>
412 lines
14 KiB
Markdown
412 lines
14 KiB
Markdown
# 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 '<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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```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=<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:
|
|
|
|
```bash
|
|
cp deploy/polytrader.service ./publish-linux/
|
|
scp -r ./publish-linux <user>@<vm-host>:/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;`
|