Files
IBKRTrader/docs/archiv/KONZEPT-Deploymentcenter-Integration.md
RichardandClaude Opus 5 9f66183f1c
Build & Test / build (ubuntu-latest) (push) Waiting to run
Build & Test / build (windows-latest) (push) Waiting to run
Eine Roadmap statt sieben Konzepte; Quelldokumente ins Archiv
Der offene Stand lag ueber sieben Konzepte, zwei Referenzdokumente und die
Phasen-Checkliste der Architektur verteilt. Dieselbe Aufgabe stand teils
doppelt unter zwei Namen - die asynchrone Fill-Verfolgung etwa als "bekannte
Grenze" in IBKR-Integration.md und zugleich als W-2 im OptionsWheel-Konzept.
Wer wissen wollte, was als Naechstes ansteht, musste alles neun lesen.

docs/ROADMAP.md fuehrt das zusammen:
  - Fuenf Stufen in Abhaengigkeitsreihenfolge, von "Fundament schliessen" bis
    zum OptionsWheel, dazu vier laufende Bahnen (Auslieferung, Accounting,
    Supervisor, technische Schulden).
  - Jede Zeile traegt ihre Herkunft (W-2, P5, Kapitalmodell 5, ...), damit die
    Herleitung im Archiv auffindbar bleibt.
  - Eigene Abschnitte fuer Zurueckgestelltes und Verworfenes. Zurueckgestellte
    Ideen nennen ausdruecklich, WAS sie wieder aktuell macht; verworfene nennen
    den Grund, damit sie nicht in sechs Monaten erneut vorgeschlagen werden.
  - Erledigtes bleibt als Zeile mit Datum stehen statt zu verschwinden.

Archiv (git erkennt alle sieben als Umbenennung, Historie bleibt):
  docs/konzepte/*         -> docs/archiv/
  docs/Kapital-und-Buchmodell.md -> docs/archiv/
Jedes archivierte Dokument bekommt oben einen Vermerk, warum es erhalten bleibt
und wo der lebende Stand steht. Inhaltlich ist keines veraendert.

Weiter gepflegt werden ARCHITECTURE.md (Aufbau + Phasen-Historie),
IBKR-Integration.md (Adapter-Design und Grenzen) und die TWS-Setup-Checkliste -
das sind Referenzen, keine Planung. Ihre eigenen Offen-Listen verweisen jetzt
mit Roadmap-Kennung dorthin, statt einen zweiten Stand zu fuehren.

Alle 63 relativen Markdown-Links geprueft, keiner tot. Fuenf Pfadverweise in
Code, csproj und systemd-Unit mitgezogen; die Unit zeigte auf die
Portierungsanalyse, die ausdruecklich den Stand VOR dem Umbau beschreibt - jetzt
auf ARCHITECTURE.md. Build 0 Warnungen, 198/198 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:14:51 +02:00

36 KiB
Raw Permalink Blame History

📩 Archiviert am 2026-08-23

Dieses Dokument wird nicht mehr gepflegt. Was davon noch offen ist, steht in der Roadmap (Bahn „Auslieferung / Deploymentcenter") – dort und nur dort wird der Stand nachgefĂŒhrt.

Es bleibt erhalten, weil es die bewussten Abweichungen vom DC-Leitfaden begruendet – Sperrbetrieb statt Prozessende, exitCurrentApp: false – und die Befunde P1–P6 und D1–D7 nachweist. Zum Nachschlagen also weiterhin richtig, als Aufgabenliste nicht mehr.


KONZEPT: Deploymentcenter-Integration

Stand: 2026-08-14 · Deploymentcenter-Version 2.5.1 · Quelle: J:\Softwareprojekte\Deploymentcenter\docs, GET /api/updateservice/v1/changelog?since=2.4 Zielprojekt: IBKRTrader (App + Daemon), Projekt-Slug ibkrtrader SDK-Zielversion: Deploymentcenter.Client 2.5.1 (DeploymentcenterSdk.Version mitfĂŒhren, damit kĂŒnftige Anbindungen ĂŒber ?since=2.5.1 direkt sehen, was sich seither geĂ€ndert hat)

Nachtrag 2026-08-14: Die ursprĂŒngliche Fassung dieses Dokuments (Stand DC 2.4) enthielt sieben Befunde/Bedenken in §8, die dem Deploymentcenter-Entwickler mitgeteilt wurden. Alle sieben sind mit 2.5.0/2.5.1 behoben — Einzelheiten weiterhin in §8, jetzt mit Fix-Vermerk. Zwei Neuerungen aus derselben Runde gehen ĂŒber die ursprĂŒnglichen Befunde hinaus und sind in §5.1, §7.2 und §2.2 eingearbeitet: exitCurrentApp ist jetzt als harter Kill dokumentiert, und BuildInfo.targets liegt im NuGet-Paket selbst.

Das Deploymentcenter (https://dc.mhdf.de) bĂŒndelt fĂŒnf Dienste, die IBKRTrader bisher alle nicht hat: Lizenz, Watchdog, Error-Reporting, Bugtracker und UpdateService inkl. Erstinstallation. Dieses Dokument beschreibt, in welcher Reihenfolge sie angebunden werden, was dabei am Projekt vorher aufgerĂ€umt werden muss und wo die vom Deploymentcenter vorgeschlagenen Muster fĂŒr ein handelndes System nicht ohne Weiteres passen.


0. Ausgangslage

Zustand heute
DC-Anbindung keine — kein Treffer fĂŒr Deploymentcenter, dc.mhdf.de oder DC_TOKEN im Quelltext
Version Zeichenkette "1.0.0" in App/Program.cs:34 und Daemon/Program.cs:43, keine <Version>, keine Directory.Build.props
Globale Fehlerbehandlung keine — weder AppDomain.UnhandledException noch TaskScheduler.UnobservedTaskException
Auslieferung dotnet publish von Hand, deploy/README.md, systemd-Unit
CI Gitea Actions, Matrix ubuntu-latest / windows-latest, Checkout nur von IBKRTrader

Referenzintegration ist Predictalytics (src/Predictalytics.Hosting/Dc*.cs, docs/BETRIEB-Deploymentcenter.md). Sie liegt auf SDK-Stand 2.2 und ist damit unvollstĂ€ndig — siehe §8, Punkt D2. Übernommen wird das Muster, nicht der Code eins zu eins.

Der Einbauort ist IBKRTrader.Hosting: den Host teilen sich Shell und Daemon (AppHostBuilder), damit gilt jede Anbindung automatisch fĂŒr beide Betriebsformen. IBKRTrader.Core bleibt frei davon — der Core soll keine NetzabhĂ€ngigkeit zu einem Verwaltungsserver bekommen.


1. Voraussetzungen im Deploymentcenter (vor jedem Code)

Ohne diese Schritte lÀuft nichts von dem, was danach kommt:

  1. Projekt ibkrtrader anlegen (WebUI → Projekte). Slug wird von Lizenz, UpdateService, Fehler-Stream und Bugtracker gemeinsam benutzt.
  2. Offline-Gnadenfrist festlegen (default_cache_ttl_hours). Vorgabe 168 h. FĂŒr einen Handelsdienst, der auch bei gestörter Internetverbindung weiterlaufen soll, ist das die entscheidende Stellschraube — siehe §5.
  3. Lizenz fĂŒr ibkrtrader ausstellen. Ohne sie kommt die Anwendung nach dem ersten Release nicht mehr an ihre Updates (Zugangsschutz seit 2.4).
  4. Master-Token mit watchdog:ping, bugtracker:report — fĂŒr die Anwendung. Zweites Token mit updateservice:publish — fĂŒr die Release-Pipeline, gehört nicht in die Anwendung.
  5. Installationskonto (Rolle installer) fĂŒr die Erstinstallation.
  6. Evaluator-Cron prĂŒfen. * * * * * php cli/tick.php --quiet. Ohne ihn bleibt ein abgestĂŒrzter Trading-Daemon im Dashboard dauerhaft grĂŒn — der Watchdog wĂ€re dekorativ. Derselbe Job erzeugt auch den Release-Zugangsschutz.

2. Build-Grundlage

2.1 Directory.Build.props mit <Version>

<Project>
  <PropertyGroup>
    <Version>1.0.0</Version>
  </PropertyGroup>
</Project>

pack-and-deploy liest die Version aus der Hauptassembly und bricht bei einer Abweichung zu --version ab. Der Grund ist unangenehm genug dafĂŒr: Wird 1.0.1 als 1.0.2 veröffentlicht, aktualisiert die gesamte Installationsbasis, meldet danach weiterhin 1.0.1, hĂ€lt das Release erneut fĂŒr neu — und aktualisiert bei jedem Start wieder.

Gleichzeitig entfallen die beiden hartkodierten "1.0.0" in den Program.cs; sie werden durch BuildInfo.Version ersetzt.

2.2 SDK-Anbindung — entschieden, in zwei Stufen

Deploymentcenter.Client (netstandard2.0;net8.0) wird von net10.0 problemlos konsumiert. Seit SDK 2.5.0 liegt BuildInfo.targets im Paket selbst (build/-Ordner, von NuGet automatisch importiert) — der frĂŒhere <Import> per Cross-Repo-Pfad ist damit nicht mehr nötig. Das entscheidet die Frage grundsĂ€tzlich zugunsten eines echten NuGet-Bezugs. GeprĂŒft (2026-08-14): Weder https://dc.mhdf.de/nuget/ noch die Gitea-Package-Registry unter http://192.168.178.10:8418/api/packages/Richard/nuget/ fĂŒhren aktuell ein Paket Deploymentcenter.Client — es gibt also noch keine Quelle, gegen die eine PackageReference auflösen könnte.

Deshalb zweistufig:

Stufe Weg Zustand
Jetzt (umgesetzt) Cross-Repo-ProjectReference (Muster Predictalytics), Deploymentcenter.BuildInfo.targets weiterhin per <Import> eingebunden — der pfadbasierte Import funktioniert laut Leitfaden unverĂ€ndert weiter, auch wenn er nicht mehr nötig wĂ€re funktioniert sofort, keine neue Infrastruktur, keine Push-Entscheidung nötig
Folgeschritt (offen) dotnet pack im Deploymentcenter-Repo, Push nach .../api/packages/Richard/nuget/, NuGet.config um Quelle gitea mit packageSourceMapping auf Deploymentcenter.* ergĂ€nzen, <Import> durch <PackageReference Include="Deploymentcenter.Client" Version="2.5.1" /> ersetzen braucht einen Push in ein fremdes Repo (Deploymentcenter) und Package-Registry-Zugangsdaten — bewusst nicht Teil dieses Durchlaufs

Der Cross-Repo-Bezug bricht die Gitea-CI von IBKRTrader (der Runner checkt nur dieses eine Repo aus) — siehe P5. Das ist mit der Wahl von Stufe 1 bewusst in Kauf genommen, weil eine funktionierende lokale Integration wichtiger ist als eine grĂŒne CI, die eine noch nicht existierende Paketquelle voraussetzt.

2.3 BuildInfo

Deploymentcenter.BuildInfo.targets in die csproj von IBKRTrader.Hosting importieren. Die Klasse entsteht in $(RootNamespace), also IBKRTrader.Hosting.BuildInfo — erreichbar ohne using. Liefert Version, GitCommit, GitCommitShort, BuildDateUtc, Channel, Summary.

Erledigt mit 2.5.1 (vormals §8 D4): BuildDateUtc trug die volle Uhrzeit, WriteOnlyWhenDifferent griff dadurch nie, und jedes importierende Projekt wurde bei jedem Build neu ĂŒbersetzt. Seit 2.5.1 ist das Feld tagesgenau — höchstens ein Neubau pro Tag. Vorbedingung: die Targets-Datei im Schwester-Repo muss auf 2.5.1 oder neuer stehen (git -C ../Deploymentcenter log -1 --oneline -- client-dotnet/Deploymentcenter.Client/Deploymentcenter.BuildInfo.targets).


3. Konfiguration und gemeinsame Bausteine

3.1 DcConfig (kompiliert, nicht einstellbar)

namespace IBKRTrader.Hosting.Deploymentcenter;

public static class DcConfig
{
    public const string BaseUrl     = "https://dc.mhdf.de";   // bewusst KEINE Einstellung
    public const string ProductSlug = "ibkrtrader";
    public const string MonitorGroup = "Trading";
    public static string AppVersion     => BuildInfo.Version;
    public static string GitCommitShort => BuildInfo.GitCommitShort;
}

Die Basis-URL entscheidet, wohin die LizenzprĂŒfung geht und woher ausfĂŒhrbarer Code kommt. Einstellbar wĂ€re sie eine Einladung, die Anwendung auf einen gefĂ€lschten Update-Server zu zeigen.

3.2 Neuer Abschnitt in AppSettings

ErgÀnzt src/IBKRTrader.Core/Settings/AppSettings.cs um DeploymentcenterSettings (gleiches Muster wie LoggingSettings, mit Category/DisplayName/Description, damit es in der Settings-Ansicht auftaucht):

Feld Vorgabe Bedeutung
ApiToken (leer) Authorization: Bearer. Leer = kein Heartbeat, keine Fehlermeldung. [PasswordPropertyText]
HeartbeatEnabled true Dead-Man's-Switch
MonitorSource (Hostname) Monitorname im Dashboard
MonitorInstance default mehrere Instanzen (EU/US) desselben Dienstes
HeartbeatIntervalSeconds 60 warning nach 2×, down nach 4×
ReportErrors true Error/Fatal an den Fehler-Stream
CheckUpdatesOnStart true prĂŒft nur, installiert nie von selbst
UpdateChannel prod prod, beta, dev
LicenseKey (leer) nur RĂŒckfall; Regelfall ist der verschlĂŒsselte Cache

ApiToken und LicenseKey sind schutzbedĂŒrftig und laufen ĂŒber SecretProtection (AES-256-GCM at rest), wie die ĂŒbrigen Geheimnisse des Projekts.

3.3 DcApiClient

Schlanker JSON-Client fĂŒr Heartbeat und Fehler-Stream (Lizenz und Update bringen ihren eigenen mit). Übernimmt von Predictalytics:

  • DcApiException mit stabilem error.code aus dem Umschlag {"status":"error","error":{"code":"
"}} — reagiert wird auf den Code, nie auf den Text.
  • IsPermanent fĂŒr 401/403/unauthorized: danach wird nicht weiter versucht.
  • Zeitgrenze 10 s. Ein nicht erreichbares Deploymentcenter darf den Handel niemals beeinflussen — jeder Aufruf ist best effort.

4. Watchdog

Neuer Worker DcHeartbeatWorker in IBKRTrader.Hosting, registriert wie die ĂŒbrigen (IWorker + AddHostedService), damit er in der Worker-Ansicht auftaucht und manuell auslösbar ist.

POST /api/watchdog/v1/ping alle n Sekunden mit source, instance, interval, os, version = BuildInfo.Version, group = "Trading".

4.1 checks — was IBKRTrader wirklich ĂŒber sich weiß

Ein Heartbeat beweist nur, dass ein Timer lÀuft. Der klassische Fehlerfall dieses Projekts ist ein anderer: der Prozess lebt, aber die TWS-Verbindung ist seit einer Stunde tot und es kommen keine Kurse mehr.

Check Quelle ok wenn
db EF/MySqlConnector, SELECT 1, gedeckelt auf 5 s Verbindung steht
ibkr_gateway IBKRGatewayService verbunden oder UseTwsApi = false (NullBroker ist ein gewollter Zustand, kein Fehler)
market_data IBKRMarketDataRepository letzter Tick jĂŒnger als x min — nur wenn TWS aktiv
worker_engine WorkerEngine kein Worker im Fehlerzustand

Bewusst kein Check auf „Trading aktiv": TradingEnabled = false ist ein gewollter Betriebszustand und wĂŒrde den Monitor sonst dauerhaft auf warning halten. Er wandert als Metrik mit.

4.2 metrics

uptime_sec, trading_enabled (0/1), open_positions, orders_today, account_value, workers_running, memory_mb, decision_journal_lag_sec.

Serverseitig 14 Tage Verlauf mit Abweichung zum 7-Tage-Mittel desselben Monitors — damit lĂ€sst sich „heute plötzlich 40 Orders statt 3" erkennen, ohne im Deploymentcenter Schwellwerte fĂŒr jedes Projekt zu pflegen.

4.3 Sauberes Beenden

Beim geordneten Herunterfahren (SIGTERM/systemd, Fenster schließen) geht ein Ping mit status: "stopped" raus. Ohne ihn folgt wenige Minuten nach jedem planmĂ€ĂŸigen Neustart ein Fehlalarm. Der Aufruf gehört in StopAsync vor das Beenden der ĂŒbrigen Dienste und braucht eine eigene, kurze Zeitgrenze (3 s), damit ein hĂ€ngender Monitoring-Server das Herunterfahren nicht blockiert.

4.4 Hierarchie

LĂ€uft der Daemon auf einem eigenen Host, gehört im WebUI unter WatchDog → System-Hierarchie die Übergeordnete EntitĂ€t auf diesen Host gesetzt. Sonst erzeugt ein Maschinenausfall zwei Meldungen fĂŒr ein Problem.


5. Lizenz

LicenseGuard in IBKRTrader.Hosting, aufgerufen aus RunStartupChecks — nach der Zeitzone (Logausgaben) und vor dem Start der Worker.

  • Produkt-Slug ibkrtrader, Hardware-ID v2 (2:win:
 / 2:lin:
).
  • LicenseClient.DefaultAppVersion = BuildInfo.Version einmalig beim Start, sonst trĂ€gt jede Installation in der Aktivierungsliste dieselbe 1.0.0.
  • Revalidierung zur Laufzeit alle 12 h; Warnung, wenn die Gnadenfrist in unter 48 h ablĂ€uft.
  • LicenseConfig.SetStorageDirectory(AppPaths.ConfigFile("license")) — ausdrĂŒcklich setzen. Die Vorgabe des SDK löst unter Linux ĂŒber $XDG_CONFIG_HOME bzw. $HOME auf; ein systemd-Dienst mit User=ibkrtrader und ohne Heimatverzeichnis landet im RĂŒckfall auf AppContext.BaseDirectory/license, also unter /opt — dort ist nichts schreibbar, und es gĂ€be keinen Offline-Cache. Mit AppPaths fĂŒgt sich die Ablage in die bereits vorhandene Verzeichnisauflösung des Projekts ein. Erledigt/entschĂ€rft mit 2.5.1 (vormals §8 D4): der RĂŒckfall ohne $HOME zeigt jetzt serverseitig auf /var/lib/<slug> statt ins Installationsverzeichnis — unser eigenes explizites Setzen bleibt trotzdem die robustere Wahl und ist unabhĂ€ngig vom SDK-RĂŒckfall. ⚠ Breaking Change 2.5.1: SetStorageDirectory hĂ€ngt jetzt selbst <slug>/license an (vorher wurde der Pfad roh benutzt). Der Aufruf wird zu LicenseConfig.SetStorageDirectory(AppPaths.Config) — ohne das eigene "license"-Suffix, sonst entstĂŒnde 
/license/ibkrtrader/license.

5.1 Kopflose Schalter

Konvention aus dem Lizenz-Leitfaden, im Daemon umzusetzen (das SDK bringt keine CLI mit):

IBKRTrader.Daemon --license-status
IBKRTrader.Daemon --license-set-key XXXXX-XXXXX-XXXXX-XXXXX-XXXXX

--license-deactivate nicht verdrahten: es verlangt den shared_key des Servers, der gehört nicht in eine ausgelieferte Anwendung. Freigeben lĂ€uft ĂŒber das WebUI (Hardware-Liste → „Freigeben").

GeĂ€ndert mit 2.5.0: der update-agent nimmt den LizenzschlĂŒssel nicht mehr als --license-key-Argument entgegen — ps zeigt Prozessargumente jedem Nutzer der Maschine, dieselbe BegrĂŒndung wie beim Crontab-Verzicht in UPGRADE §5. Der Weg ist jetzt die Umgebungsvariable DC_LICENSE_KEY (LaunchUpdateAgent setzt sie selbst, wenn licenseKey ĂŒbergeben wird — kein eigener Code nötig). Unsere --license-set-key-Implementierung ruft also LicenseClient.ValidateAsync direkt auf und schreibt nicht --license-key in einen eigenen Aufruf des Agenten.

Ebenfalls neu: EnsureLicensedAsync() auf LicenseClient — nimmt den zwischengespeicherten SchlĂŒssel, fragt bei Bedarf ĂŒber ILicensePrompt nach, prĂŒft, fragt bei Ablehnung erneut. allowPrompt:false lehnt ohne Cache ab, statt im Daemon auf eine Eingabe zu warten, die nie kommt — das ist fĂŒr den Daemon die richtige Einstiegsmethode, nicht das manuelle Zusammensetzen aus TryGetCachedKey + ValidateAsync.

5.2 Reaktion — hier weicht IBKRTrader bewusst vom Leitfaden ab

Die DC-Doku empfiehlt bei einem endgĂŒltigen Urteil Environment.Exit(1). FĂŒr einen Dienst mit offenen Positionen ist das die falsche Reaktion. Ein abgelaufener Vertrag ist kein Grund, ein Portfolio unbeaufsichtigt zu lassen — Stop-Loss- und Exit-Logik mĂŒssen weiterlaufen, sonst richtet die Lizenzdurchsetzung mehr Schaden an als der unlizenzierte Betrieb.

Vorschlag — drei Stufen statt zwei:

Ergebnis ZustÀnde Reaktion
IsValid valid, valid_offline Normalbetrieb
IsTransient server_unavailable, cache_expired kein Urteil, nur keine Auskunft. Warnen, weiterlaufen, beim nÀchsten Durchlauf erneut fragen
Urteil revoked, expired, not_found, activation_limit, suspended, clock_rollback Sperrbetrieb: TradingEnabled hart auf false, keine neuen Einstiege, Risiko-/Exit-Worker und Buchhaltung laufen weiter, Meldung an Watchdog (status: "warning") und Fehler-Stream. Beendet wird erst, wenn keine Position mehr offen ist — oder auf ausdrĂŒckliche Anweisung

Bei der Desktop-Shell darf es beim Start weiterhin ein Lizenzfenster geben; der Daemon darf niemals auf eine Eingabe warten.


6. Error-Reporting

Zwei Wege in POST /api/errors/v1/report, beide in IBKRTrader.Hosting:

  1. Senke am LoggingService. LoggingService.EntryWritten existiert bereits und ist genau dafĂŒr gedacht (die OberflĂ€che hĂ€ngt schon dran). Ein DcErrorSink filtert auf AppLogLevel.Error und meldet mit level: "error", build: BuildInfo.Version, file/line aus dem Stacktrace, context mit GitCommitShort und CorrelationId/SignalId, wenn vorhanden. Die SignalId mitzuschicken ist der eigentliche Gewinn: eine Fehlermeldung lĂ€sst sich damit im Deploymentcenter direkt gegen das Entscheidungsjournal halten.
  2. Globale Handler — AppDomain.UnhandledException (level: "fatal", blockierend gesendet, der Prozess ist gleich weg) und TaskScheduler.UnobservedTaskException. Beide fehlen im Projekt heute vollstĂ€ndig; sie mĂŒssen sowieso her.

6.1 Bremse

Verpflichtend, sonst verbrennt ein Fehler in einer Handelsschleife das serverseitige Rate-Limit binnen Sekunden:

  • höchstens 20 Meldungen pro Minute,
  • derselbe Fehler höchstens alle 5 Minuten,
  • bei 401/403 dauerhaft abschalten,
  • bei 429 Intervall verdoppeln.

Betriebsbedingtes Rauschen gehört nicht in diese Bremse, sondern in die Ignore-Regeln des WebUI: dort wird weitergezĂ€hlt, aber nicht gemeldet — und eine Alarmschwelle schlĂ€gt an, wenn ein bekannter Fehler plötzlich hundertmal so oft auftritt.

Nicht melden: alles, was Kontodaten, Positionen oder SchlĂŒsselmaterial im Text fĂŒhren könnte. Der Fehler-Stream ist eine externe Ablage. Vor dem Senden wird der Text durch dieselbe Maskierung geschickt, die schon fĂŒr die Logs gilt.


7. UpdateService, Erstinstallation und Release-Pipeline

7.1 PrĂŒfen, nicht installieren

DcUpdateService.CheckAsync beim Start (GET /api/updateservice/v1/check). Mit Zugangsdaten, sonst gibt es seit 2.4 nur noch 401:

var key = settings.Deploymentcenter.LicenseKey is { Length: > 0 } k
        ? k
        : LicenseClient.TryGetCachedKey(DcConfig.ProductSlug);

var check = await new UpdateClient().CheckForUpdateAsync(
    baseUrl:        DcConfig.BaseUrl,
    projectId:      DcConfig.ProductSlug,
    currentVersion: BuildInfo.Version,
    channel:        settings.Deploymentcenter.UpdateChannel,
    credentials:    ReleaseCredentials.FromLicenseKey(key),
    cancellationToken: ct);

if (check.Unauthorized) { /* Lizenzproblem, KEIN Netzwerkfehler — eigene Meldung */ }

Gefunden wird nur, installiert wird nichts von selbst. Ein Trading-Dienst, der sich unangekĂŒndigt neu startet, ist keine akzeptable Vorgabe.

  • Kein Update → Info ins Log.
  • Update verfĂŒgbar → Warnung + Hinweis in der Shell.
  • Kritisch → zusĂ€tzlich Dialog (Shell) bzw. Watchdog-Event (Daemon).

7.2 Anwenden

Über UpdateClient.LaunchUpdateAgent(..., licenseKey: key); der Agent bekommt --restart, --wait-for-pid, --platform von selbst, den LizenzschlĂŒssel seit 2.5.0 nicht mehr als --license-key-Argument, sondern ĂŒber die Umgebungsvariable DC_LICENSE_KEY (siehe §5.1). Vorher geht ein status: "stopped" an den Watchdog.

Vorbedingung, die die Anwendung selbst prĂŒfen muss: kein offener Auftrag, keine laufende Order. Ein Update, das den Prozess zwischen Order-Absendung und StatusrĂŒckmeldung abrĂ€umt, hinterlĂ€sst eine Order bei IBKR ohne GegenstĂŒck im Journal. Das Update wird also erst nach dem Handelsschluss bzw. bei leerem Auftragsbuch angeboten.

VerschĂ€rft mit 2.5.0: exitCurrentApp: true ist jetzt ausdrĂŒcklich als harter Kill dokumentiert — laufende finally-Blöcke, IHostApplicationLifetime und Destruktoren kommen nicht mehr zum Zug. FĂŒr IBKRTrader heißt das: Solange die Vorbedingung oben nicht sicher geprĂŒft ist (offenes Auftragsbuch, laufende Buchungstransaktion), muss exitCurrentApp: false gesetzt werden. Der Agent wartet ĂŒber --wait-for-pid ohnehin auf das Prozessende; das eigene Herunterfahren lĂ€uft dann ĂŒber host.StopAsync(...) wie in App/Program.cs bereits vorhanden — nicht ĂŒber LaunchUpdateAgent selbst. ZusĂ€tzlich waitTimeoutSeconds heraufsetzen, falls das geordnete Beenden (EF-Flush, Kestrel, Log-Flush, DB-Backup-Worker) lĂ€nger als die Vorgabe von 60 s dauern kann — lĂ€uft die Zeit ab, bricht der Agent ohne Änderung ab.

FĂŒr den Linux-Dienst siehe §8 D3 — dafĂŒr gibt es im Deploymentcenter derzeit keinen fertigen Weg.

7.3 Was ausgeliefert wird — Ausschließen vs. SchĂŒtzen

Das ist die Stelle, an der Updates in anderen Projekten schon Konfigurationen zerstört haben. FĂŒr IBKRTrader gilt:

Datei Liste BegrĂŒndung
settings.example.json (im Paket) Vorlage mit Platzhaltern — Erstinstallation braucht sie
settings.json preservePatterns enthĂ€lt DB-Zugang, DC-Token, Flex-Token. Muss ein Update ĂŒberleben
appsettings.Local.json preservePatterns Verbindungszeichenfolge des Zielsystems
master.key, openrouter.key excludePatterns darf nie ins Paket
Logs/**, Backups/** excludePatterns Laufzeitdaten
*.pdb, *.xml excludePatterns Build-Artefakte
update-agent* bewusst entscheiden mitliefern = aktualisiert sich selbst mit

pack-and-deploy warnt von sich aus bei Dateien, die nach Zugangsdaten aussehen. Die Warnung ist ernst zu nehmen: Release-Pakete liegen zwar hinter einem Zugangsschutz, aber jeder lizenzierte Kunde kann sie auspacken.

7.4 setup.json

Beschreibt der Erstinstallation, was abzufragen ist; wird ins Paket kopiert. Entwurf fĂŒr IBKRTrader:

{
  "schema": 1,
  "displayName": "IBKRTrader",
  "targets": [
    { "id": "app", "file": "settings.json", "location": "install", "format": "json" }
  ],
  "fields": [
    { "key": "Database:Host",     "label": "MySQL-Host",    "type": "string" },
    { "key": "Database:Database", "label": "Datenbank",     "type": "string" },
    { "key": "Database:User",     "label": "Benutzer",      "type": "string" },
    { "key": "Database:Password", "label": "Passwort",      "type": "secret" },
    { "key": "IBKR:Host",         "label": "TWS/Gateway",   "default": "127.0.0.1" },
    { "key": "IBKR:Port",         "label": "Port (Paper 4002 / Live 4001)", "type": "int", "default": "4002" },
    { "key": "Trading:ApplicationTimeZoneId", "label": "Betriebszeitzone", "default": "Europe/Berlin" },
    { "key": "Deploymentcenter:BaseUrl",  "source": "detect:baseurl", "type": "url" },
    { "key": "Deploymentcenter:ApiToken", "label": "Token dieser Installation",
      "source": "provision", "scopes": ["watchdog:ping", "bugtracker:report"] },
    { "key": "Deploymentcenter:MonitorSource", "source": "detect:hostname" }
  ]
}

Zwei Punkte dazu:

  • location: "install" ist nur unter Windows richtig — und auch dort nur, solange nicht nach C:\Program Files installiert wird. Siehe §8 P2; solange AppPaths nicht korrigiert ist, passen setup.json und die tatsĂ€chliche Leseposition der Anwendung unter Linux nicht zusammen. Dieser Punkt blockiert die Erstinstallation und gehört vor §7.4.
  • Der Installer schreibt Klartext. SecretProtection muss also Klartext unverĂ€ndert durchreichen und beim ersten Speichern verschlĂŒsseln — die Weiche ist zu prĂŒfen und im Test ausdrĂŒcklich als aktiv genutzt zu beschreiben, damit sie nicht eines Tages als tot entfernt wird.

7.5 Release-Pipeline

scripts/release.ps1 und scripts/release.sh sind per curl von /docs/release-template/ geholt (wie release.md empfiehlt, statt aus dem lokalen Schwester-Repo kopiert — damit ist sichergestellt, dass die kanonische Fassung im Projekt liegt) und unverĂ€ndert gelassen. Nur scripts/release.config.json ist ausgefĂŒllt:

{
  "project": "ibkrtrader",
  "csproj": "src/IBKRTrader.Daemon/IBKRTrader.Daemon.csproj",
  "runtimes": ["win-x64", "linux-x64"],
  "selfContained": true,
  "setupJson": "setup.json"
}

Entschieden: nur der Daemon, keine Zwei-Produkte-Aufteilung. IBKRTrader hat zwei Einstiegspunkte (App = Avalonia-Shell, Daemon = kopflos). Die Vorlage nimmt aber genau eine csproj fĂŒr alle Runtimes in der Liste — sie ruft dotnet publish <csproj> -r <rid> in einer Schleife auf, es gibt keine Möglichkeit, App fĂŒr win-x64 und Daemon fĂŒr linux-x64 in demselben Lauf zu veröffentlichen, ohne das (laut Anleitung unverĂ€nderte) Skript anzufassen. UrsprĂŒnglich in diesem Dokument vorgeschlagen war genau das ("Windows = App, Linux = Daemon") — das ist mit der Vorlage so nicht umsetzbar und wurde beim Umsetzen korrigiert.

IBKRTrader.Daemon ist der bewusste Kandidat: laut README „plattformneutral" (net10.0 ohne Plattformbindung), lĂ€uft kopflos auf beiden Zielsystemen und ist ohnehin die fĂŒr den Serverbetrieb vorgesehene Form (deploy/README.md). Eine automatisierte Auslieferung der Avalonia-Shell (App) als Windows-Desktop-Distribution ist damit nicht Teil dieses Durchlaufs — dafĂŒr brĂ€uchte es entweder einen zweiten Produkt-Slug (ibkrtrader-app, doppelte Lizenz-/Update-Pflege) oder ein angepasstes Release-Skript. ZurĂŒckgestellt, bis tatsĂ€chlich Bedarf an einer automatisierten Desktop-Auslieferung besteht — bis dahin bleibt dotnet run --project src/IBKRTrader.App bzw. eine manuelle dotnet publish der ĂŒbliche Weg fĂŒr die Shell.

Zugangsdaten kommen aus DC_FTP_HOST / DC_FTP_USER / DC_FTP_PASS / DC_TOKEN, .dc-tools/ und artifacts/ sind in der .gitignore.

packager.config.json (excludePatterns/preservePatterns) ist nicht Teil dieses Durchlaufs. Sie liegt "neben dem Werkzeug", also unter .dc-tools/ — das entsteht erst, wenn pack-and-deploy beim ersten echten Lauf von release.ps1/release.sh selbst heruntergeladen wird (PrĂŒfsummen-geprĂŒft). Ein erster Release-Lauf braucht DC_FTP_*/DC_TOKEN und ist damit eine credentialierte, vom Betreiber auszulösende Aktion. Sicherheitsnetz bis dahin: Der Packager bricht seit 2.5.0 ohnehin hart ab, wenn er im Publish-Verzeichnis nach Zugangsdaten aussehende Dateien findet (Name und Inhalt) — die in §7.3 beschriebenen Dateien (settings.json, appsettings.Local.json) sind ohnehin gitignored und landen nur dann im Publish-Output, wenn sie lokal im Projektverzeichnis liegen (<None Update Condition="Exists(...)"> in den .csproj-Dateien kopiert sie dann mit). Bricht release.ps1 mit einem Hinweis auf eine vermutete Zugangsdatei ab, ist das der Packager, der genau das abfĂ€ngt — kein Fehlschlag, sondern die Absicherung, die 2.5.0 dafĂŒr eingefĂŒhrt hat.


8. Befunde und offene Punkte

Status 2026-08-23: P1–P4 sind umgesetzt (Einzelheiten in §9). P5 ist eingetreten, P6 entscheidet sich erst beim ersten Release. Der ursprĂŒngliche Vermerk von 2026-08-14:

Status 2026-08-14: Alle sieben ursprĂŒnglich an den Deploymentcenter-Entwickler gemeldeten Befunde (D1–D7) sind mit den Versionen 2.5.0/2.5.1 behoben — bestĂ€tigt ĂŒber GET /api/updateservice/v1/changelog?since=2.4. Der Abschnitt bleibt als Nachweis stehen, jeder Punkt trĂ€gt jetzt einen Fix-Vermerk. Die Projekt-Befunde (P1–P6) sind davon unabhĂ€ngig und weiterhin offen — sie werden in diesem Durchlauf abgearbeitet.

Am Projekt (P) — muss vor der Integration bereinigt werden

P1 · Echte Zugangsdaten als Vorgabewerte im Quelltext. src/IBKRTrader.Core/Settings/AppSettings.cs:14-35 trĂ€gt Host, Datenbankname, Benutzer und Passwort einer produktiven MariaDB als Vorgabe. Die Datei ist versioniert, die Werte stehen damit auch in der Git-Historie — settings.json im .gitignore hilft dagegen nicht. Das Passwort ist als kompromittiert zu behandeln und zu wechseln; die Vorgaben gehören auf Platzhalter. ZusĂ€tzlich brisant, weil die Vorlage kĂŒnftig im Release-Paket ausgeliefert wird.

VerschĂ€rft mit 2.5.0: der Packager bricht jetzt hart ab, wenn er Zugangsdaten im Publish-Verzeichnis erkennt (Dateiname und Inhalt, u. a. gefĂŒlltes Password=) — vorher nur eine Warnung, die auf der Preserve-Liste sogar unterdrĂŒckt wurde. Damit ist P1 nicht mehr „sollte bereinigt werden", sondern release-blockierend: Ohne Fix lĂ€sst sich settings.example.json gar nicht mehr veröffentlichen.

P2 · AppPaths fĂ€llt unter Windows auf einen Linux-Pfad zurĂŒck. Configuration/AppPaths.cs:57-71: ist das BinĂ€rverzeichnis nicht beschreibbar, gilt der FHS-RĂŒckfall /etc/ibkrtrader. Unter Windows löst .NET das zu C:\etc\ibkrtrader auf. Genau dieser Fall tritt ein, sobald der DC-Installer seinen Windows-Vorschlag Programme\<slug> benutzt — dann liegen settings.json, master.key und Logs/ in C:\etc\. Nötig: plattformabhĂ€ngiger RĂŒckfall (%ProgramData%\IBKRTrader bzw. %APPDATA%).

P3 · Keine globalen Ausnahmebehandler. Ohne AppDomain.UnhandledException und TaskScheduler.UnobservedTaskException gibt es keine fatal-Meldungen — und heute auch keinen Logeintrag, wenn der Prozess wegbricht. UnabhĂ€ngig vom Deploymentcenter ĂŒberfĂ€llig.

P4 · Version an zwei Stellen hartkodiert. App/Program.cs:34 und Daemon/Program.cs:43. Ohne Directory.Build.props bricht pack-and-deploy beim ersten Release ab — zu Recht.

P5 · CI kennt das Schwester-Repo nicht. .gitea/workflows/build.yml checkt nur IBKRTrader aus. Eine ProjectReference nach ..\..\..\..\Deploymentcenter\
 macht den Build auf beiden Matrix-Zielen rot. Siehe die Entscheidung in §2.2.

Seit 2026-08-23 eingetreten, nicht mehr nur vorhergesagt. Die ProjectReference ist mit der Integration auf main gelandet, die CI ist damit rot. Das war die bewusst in Kauf genommene Folge der Interimslösung aus §2.2 — lokal baut die Projektmappe, sobald das Schwester-Repo daneben liegt. Auflösung ist Schritt 10: SDK als NuGet-Paket in die Gitea-Registry, dann PackageReference statt Cross-Repo-Pfad.

P6 · Konfiguration liegt im Installationsverzeichnis. Unter Windows schreibt AppPaths neben die BinĂ€rdatei. Das ist mit preservePatterns beherrschbar (§7.3), muss aber beim ersten Release stimmen — ein Update, das settings.json ĂŒberschreibt, nimmt einer laufenden Installation Datenbank, Token und Flex-Zugang gleichzeitig.

Am Deploymentcenter (D) — gemeldet, Stand vor 2.5.0

D1 · Der LizenzschlĂŒssel als Update-Zugang war nirgends vollstĂ€ndig erklĂ€rt. ✅ Behoben (2.5.1, Doku). Laut Changelog explizit als „LĂŒckenschluss" genannt: „Woher die Anwendung den LizenzschlĂŒssel fĂŒr den Update-Zugang nimmt (TryGetCachedKey)". FĂŒr uns bleibt zusĂ€tzlich EnsureLicensedAsync() (neu in 2.5.0) relevant — siehe §5.1.

D2 · Predictalytics wurde vom Zugangsschutz ausgesperrt. ✅ BestĂ€tigt und behoben (2.5.1). Der Changelog bestĂ€tigt den Verdacht wörtlich: „bei Predictalytics ist genau das passiert". Fix: Zugangsschutz ist jetzt je Produkt abschaltbar (WebUI → UpdateService → Zugangsschutz) — fĂŒr Installationsbasen ohne SDK 2.4 lĂ€sst sich das Produkt ausnehmen, mit SchlĂŒssel neu ausliefern, danach wieder einschalten. FĂŒr IBKRTrader heißt das: genau diese Falle von Anfang an vermeiden, indem der erste ausgelieferte Build bereits licenseKey mitgibt (§7.5, §9 Reihenfolge).

D3 · FĂŒr einen systemd-Dienst gab es keinen dokumentierten Update-Weg. ✅ Behoben (2.5.1, Doku). Laut Changelog ergĂ€nzt: „wie ein laufender systemd-Dienst aktualisiert wird". Bleibt in §7.2/§8-P laufend zu beobachten, ob die neue Beschreibung mit unserem tatsĂ€chlichen Vorgehen (--target-dir, Dienstbenutzer-Rechte) ĂŒbereinstimmt — bei der Umsetzung von Aufgabe „Update-PrĂŒfung mit ReleaseCredentials" gegenprĂŒfen.

D4 · LicenseConfig.GetStorageDirectory/SetStorageDirectory war fĂŒr Dienste unglĂŒcklich. ✅ Behoben, mit Breaking Change (2.5.1). Zwei Korrekturen: (a) RĂŒckfall ohne $HOME zeigt jetzt auf /var/lib/<slug> statt ins Installationsverzeichnis. (b) SetStorageDirectory hĂ€ngt jetzt selbst <slug>/license an, statt den Pfad roh zu verwenden — das Ă€ndert unseren Aufruf, siehe §5 (AppPaths.Config statt AppPaths.ConfigFile("license")).

D5 · BuildInfo.targets verhinderte inkrementelle Builds. ✅ Behoben (2.5.1). BuildDateUtc ist jetzt tagesgenau statt mit voller Uhrzeit, WriteOnlyWhenDifferent greift wieder. Siehe §2.3.

D6 · Die Empfehlung „bei einem Urteil beenden" passte fĂŒr handelnde Systeme nicht. ✅ Behoben (2.5.1, Doku) — als LĂŒckenschluss genannt: „was statt Environment.Exit(1) zu tun ist, wenn Beenden die gefĂ€hrlichere Option wĂ€re". Unser Sperrbetrieb-Ansatz (§5.2) bleibt die konkrete Umsetzung fĂŒr IBKRTrader. ZusĂ€tzlich, ĂŒber D6 hinausgehend: 2.5.0 dokumentiert exitCurrentApp: true jetzt als harten Kill ohne finally/Lifecycle-Hooks — praktisch relevanter fĂŒr uns als die Lizenz-Frage allein, siehe §7.2.

D7 · Kleinigkeiten (README-Verweis auf AGENT_RELEASE_GUIDE.md, Rate-Limits nicht durchgĂ€ngig beziffert). ✅ Behoben (2.5.1, Doku) — Changelog nennt „die Drosselungsgrenzen aller Endpunkte" als ergĂ€nzt; docs/README.md fĂŒhrt jetzt /docs/release.md und den Changelog-Endpunkt im Index.


9. Reihenfolge

Schritt Inhalt AbhÀngig von Stand
0 P1 Zugangsdaten wechseln, Vorgaben auf Platzhalter — ✅ Vorgaben sind Platzhalter. Die Rotation des geleakten Passworts steht weiterhin aus (Nutzer-Aktion)
1 P2 AppPaths-RĂŒckfall plattformabhĂ€ngig, P4 Directory.Build.props — ✅ 2026-08-23
2 SDK-Bezug Stufe 1: Cross-Repo-ProjectReference (§2.2) — ✅ 2026-08-23, inkl. PrĂŒf-Target mit lesbarer Fehlermeldung
3 DcConfig, DeploymentcenterSettings, DcApiClient, BuildInfo 1, 2 ✅ 2026-08-23
4 P3 globale Handler + Fehler-Stream 3 ✅ 2026-08-23 (DcCrashHandlers, DcErrorSink, DcErrorReporter)
5 Watchdog-Heartbeat inkl. stopped 3 ✅ 2026-08-23 (DcHeartbeatWorker)
6 Lizenz mit Sperrbetrieb statt Abbruch (§5.2), EnsureLicensedAsync 3 ✅ 2026-08-23 (LicenseGuard)
7 Update-PrĂŒfung mit ReleaseCredentials, exitCurrentApp:false bei offenem Zustand 6 (braucht den SchlĂŒssel) đŸ”¶ Die PrĂŒfung lĂ€uft beim Start. Das Anwenden ist es nicht: LaunchAgent ist fertig, aber kein Aufrufer fĂ€hrt danach geordnet herunter
8 setup.json, preserve/exclude, Release-Pipeline (Vorlage per curl von /docs/release-template/) 1–7 ✅ 2026-08-23 (setup.json, scripts/release.*). Noch nie ausgefĂŒhrt – das erste Release steht aus
9 Bugtracker-Baustein nach AGENTS.md / .agents/rules Projekt-Slug angelegt ⬜ offen — Projekt ibkrtrader muss im WebUI erst angelegt werden
10 Folgeschritt, nicht Teil dieses Durchlaufs: Deploymentcenter.Client als NuGet-Paket in die Gitea-Registry pushen, <Import> durch PackageReference ersetzen, Gitea-CI (P5) auf den Paketbezug umstellen 2 ⬜ offen — siehe §2.2

Schritt 7 und 8 hĂ€ngen zusammen: der erste ausgelieferte Build muss die Zugangsdaten schon mitbringen. FĂŒr ein Produkt, das noch nie veröffentlicht hat, entsteht /releases/ibkrtrader/ mit dem ersten Upload und wird vom nĂ€chsten tick.php-Lauf geschĂŒtzt — es gibt kein Zeitfenster, das SDK danach nachzurĂŒsten. Wird Schritt 7 ĂŒbersprungen, fĂ€llt die TĂŒr hinter dem ersten Release zu (genau der Fall, der laut Changelog bei Predictalytics bereits eingetreten ist, siehe D2).

Schritt 9 (Bugtracker) setzt voraus, dass das Projekt ibkrtrader im Deploymentcenter-WebUI angelegt ist und ein Token mit bugtracker:report vorliegt — beides serverseitige Handarbeit, die dieser Durchlauf nicht auslösen kann. Wird zurĂŒckgestellt.