L2: Kopfloser Linux-Dienst - der Trading-Kern laeuft ohne Oberflaeche
Meilenstein: Trading, Worker, Accounting, Supervisor, REST und MCP laufen
als systemd-Dienst auf Linux. Nur noch IBKRTrader.App ist Windows-gebunden.
Neu IBKRTrader.Hosting: die komplette Host-Zusammenstellung, aus der
WinForms-Program.cs herausgezogen. Bewusst ein eigenes Projekt - laege sie
weiter in der Shell, muesste sie fuer den Daemon dupliziert werden, und
beide wuerden auseinanderlaufen. Ein Modul, das nur in einer Variante
registriert ist, faellt sonst erst im Betrieb auf. RunStartupChecks buendelt
die Startschritte in fester Reihenfolge (Zeitzone vor dem ersten Logeintrag,
Master-Key vor jeder Entschluesselung).
Neu IBKRTrader.Daemon: kopfloser Einstiegspunkt mit SIGTERM-Behandlung,
--check (Trockenlauf: Host bauen, Startpruefungen, keine Boersenverbindung -
fuer Deployment und CI) und --db-version.
AppPaths: Logs, Backups, settings.json und master.key lagen neben der
Binaerdatei. Unter /opt hat der Dienstbenutzer dort keinen Schreibzugriff -
der Dienst waere beim ersten Logeintrag gescheitert. Aufloesung jetzt:
Umgebungsvariable, sonst Binaerverzeichnis WENN beschreibbar (haelt Windows
und die Entwicklung unveraendert), sonst FHS. Der Schreibtest ist ein echter
Schreibversuch - Attributpruefung traegt nicht, weil unter Linux
Besitzer/Gruppe/Modus und unter Windows die ACL entscheiden.
Rechtepruefung fuer master.key/openrouter.key auf Unix: Windows-ACLs
uebertragen sich beim Kopieren auf einen Linux-Host nicht, eine key-Datei mit
644 waere dort fuer jeden lokalen Nutzer lesbar. Nur eine Warnung.
InvariantGlobalization ausdruecklich auf false. Der naheliegende Griff zum
schlanken Image waere hier ein Eigentor gewesen und haette genau die zwei
Punkte aus L1b lautlos rueckgaengig gemacht: ohne ICU faellt die Aufloesung
von Windows-Zeitzonen-IDs aus (Rueckfall auf UTC im Container) und
CultureInfo.GetCultureInfo("de-DE") liefert die invariante Kultur, womit die
PDF-Betraege wieder das Format wechseln.
systemd: Unit mit Type=notify plus UseSystemd() im Host - ohne sd_notify
haette systemctl start bis zum Timeout gehangen. Unit mit Absicherung
(ProtectSystem=strict, ReadWritePaths), deploy/README.md mit Voraussetzungen,
Verzeichnissen, Zeitzonen-Festlegung und dem, was der Daemon NICHT loest
(IB Gateway headless bleibt eine eigene Baustelle).
Verifiziert: 193 Tests gruen (+5), Build 0 Fehler/0 Warnungen, --smoke-ui
konstruiert alle 7 Fenster, --check laeuft durch, und publish -r linux-x64
liefert 11 MB mit ELF-Launcher, ohne eine einzige Windows-Abhaengigkeit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Kopfloser Betrieb auf Linux
|
||||
|
||||
Der Dienst `IBKRTrader.Daemon` fährt Trading-Kern, Worker, Accounting, Supervisor, REST-API und
|
||||
MCP-Light **ohne Oberfläche**. Er nutzt denselben Host-Aufbau wie die Desktop-Shell
|
||||
(`IBKRTrader.Hosting`), damit beide Varianten nicht auseinanderlaufen.
|
||||
|
||||
## Veröffentlichen
|
||||
|
||||
```bash
|
||||
dotnet publish src/IBKRTrader.Daemon -c Release -r linux-x64 --self-contained false -o out/
|
||||
```
|
||||
|
||||
Ergebnis: ~11 MB, ein ELF-Launcher `IBKRTrader.Daemon`, **keine Windows-Abhängigkeiten**.
|
||||
Mit `--self-contained true` entfällt die Runtime-Installation auf dem Zielhost (dann ~80 MB).
|
||||
|
||||
## Voraussetzungen auf dem Host
|
||||
|
||||
| Paket | Wofür | Pflicht? |
|
||||
|---|---|---|
|
||||
| `dotnet-runtime-10.0` | Laufzeit | ja (außer bei `--self-contained`) |
|
||||
| `libicu` | Kulturen und Zeitzonen-ID-Umrechnung | **ja – siehe Warnung unten** |
|
||||
| `fonts-dejavu-core` | PDF-Export des Accounting-Moduls | nur für den PDF-Export |
|
||||
| `mariadb-client` | `mariadb-dump` für den BackupWorker | nur fürs DB-Backup |
|
||||
|
||||
> **ICU ist nicht optional.** Ohne ICU (bzw. mit `InvariantGlobalization=true`) passieren zwei
|
||||
> Dinge – beide **lautlos**, ohne Fehlermeldung:
|
||||
> 1. `AppTimeZone` kann Windows-Zeitzonen-IDs nicht mehr auflösen und fällt auf die
|
||||
> Systemzeitzone zurück, im Container also meist UTC. Genau die Verschiebung von
|
||||
> Buchungszeitstempeln, die wir beseitigt haben.
|
||||
> 2. Der PDF-Export formatiert Beträge fest gegen `de-DE`. Ohne ICU liefert
|
||||
> `CultureInfo.GetCultureInfo("de-DE")` die invariante Kultur – aus `1.234,56` wird
|
||||
> `1,234.56`, in einem Dokument, das als prüfbare Aufstellung gilt.
|
||||
>
|
||||
> Der Daemon setzt deshalb ausdrücklich `InvariantGlobalization=false`.
|
||||
|
||||
## Verzeichnisse
|
||||
|
||||
Die Anwendung schreibt in drei Verzeichnisse. Aufgelöst wird in dieser Reihenfolge:
|
||||
|
||||
1. Umgebungsvariable — `IBKRTRADER_CONFIG_DIR`, `IBKRTRADER_DATA_DIR`, `IBKRTRADER_LOG_DIR`
|
||||
2. Neben der Binärdatei, **wenn dort geschrieben werden darf** (Entwicklung, portable Installation)
|
||||
3. Sonst FHS — `/etc/ibkrtrader`, `/var/lib/ibkrtrader`, `/var/log/ibkrtrader`
|
||||
|
||||
| Verzeichnis | Inhalt |
|
||||
|---|---|
|
||||
| Config | `settings.json`, `master.key`, `openrouter.key` |
|
||||
| Data | `Backups/` |
|
||||
| Logs | Textlog je Modul, JSONL je Tag |
|
||||
|
||||
Beim Start steht die tatsächliche Ablage im Log (`Ablage: config=…, data=…, logs=…`).
|
||||
|
||||
## Einrichtung
|
||||
|
||||
```bash
|
||||
sudo useradd --system --no-create-home --shell /usr/sbin/nologin ibkrtrader
|
||||
sudo mkdir -p /opt/ibkrtrader /etc/ibkrtrader /var/lib/ibkrtrader /var/log/ibkrtrader
|
||||
sudo chown -R ibkrtrader:ibkrtrader /var/lib/ibkrtrader /var/log/ibkrtrader /etc/ibkrtrader
|
||||
sudo chmod 750 /etc/ibkrtrader
|
||||
```
|
||||
|
||||
Master-Key ablegen (**nicht** in die Unit-Datei – die ist für alle lesbar):
|
||||
|
||||
```bash
|
||||
sudo install -o ibkrtrader -g ibkrtrader -m 600 /dev/null /etc/ibkrtrader/master.key
|
||||
```
|
||||
|
||||
Der Dienst warnt beim Start, wenn `master.key` oder `openrouter.key` für Gruppe oder andere
|
||||
zugänglich sind — Windows-ACLs übertragen sich beim Kopieren auf einen Linux-Host nicht.
|
||||
|
||||
## Dienst einrichten
|
||||
|
||||
```bash
|
||||
sudo cp deploy/ibkrtrader.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now ibkrtrader
|
||||
journalctl -u ibkrtrader -f
|
||||
```
|
||||
|
||||
## Vor dem ersten Start prüfen
|
||||
|
||||
```bash
|
||||
/opt/ibkrtrader/IBKRTrader.Daemon --check
|
||||
```
|
||||
|
||||
Baut den Host, fährt alle Startprüfungen (Zeitzone, Ablageorte, Master-Key, DB-TLS) und beendet
|
||||
sich — **ohne** Worker zu starten oder eine Verbindung zur Börse aufzubauen. Geeignet für
|
||||
Deployment-Skripte und CI.
|
||||
|
||||
```bash
|
||||
/opt/ibkrtrader/IBKRTrader.Daemon --db-version
|
||||
```
|
||||
|
||||
Gibt die MariaDB-Serverversion aus (für das EF-`ServerVersion`-Pinning).
|
||||
|
||||
## Betriebszeitzone
|
||||
|
||||
`Trading.ApplicationTimeZoneId` in `settings.json`, IANA-Schreibweise:
|
||||
|
||||
| Instanz | Wert |
|
||||
|---|---|
|
||||
| EU | `Europe/Berlin` |
|
||||
| US | `America/New_York` |
|
||||
|
||||
**Vor den ersten Trades festlegen und danach nicht mehr ändern.** Ein Wechsel verschiebt
|
||||
rückwirkend die Tagesgrenzen von Logs, Berichten und Buchungsperioden. Gespeichert wird immer
|
||||
UTC — nur so bleiben die Daten beider Instanzen vergleichbar.
|
||||
|
||||
## Was der Daemon *nicht* löst
|
||||
|
||||
Das **IB Gateway** ist eine Java-Anwendung mit Oberfläche. Für den Dauerbetrieb ohne Bildschirm
|
||||
braucht es IBC plus Xvfb, dazu die Behandlung des täglichen Neustarts und des 2FA-Handlings —
|
||||
eine eigene Baustelle, unabhängig von diesem Dienst. Alternative: das Gateway bleibt auf dem
|
||||
Windows-Rechner, der Linux-Dienst verbindet sich über Port 4002. Dann muss in der
|
||||
TWS-Konfiguration die erlaubte Client-IP eingetragen sein; der TWS-API-Verkehr ist
|
||||
**unverschlüsselt** und gehört nicht über ein unvertrautes Netz.
|
||||
Reference in New Issue
Block a user