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>
36 KiB
đŠ 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.4Zielprojekt: IBKRTrader (App + Daemon), Projekt-SlugibkrtraderSDK-Zielversion:Deploymentcenter.Client2.5.1 (DeploymentcenterSdk.VersionmitfĂŒhren, damit kĂŒnftige Anbindungen ĂŒber?since=2.5.1direkt 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:
exitCurrentAppist jetzt als harter Kill dokumentiert, undBuildInfo.targetsliegt 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:
- Projekt
ibkrtraderanlegen (WebUI â Projekte). Slug wird von Lizenz, UpdateService, Fehler-Stream und Bugtracker gemeinsam benutzt. - 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. - Lizenz fĂŒr
ibkrtraderausstellen. Ohne sie kommt die Anwendung nach dem ersten Release nicht mehr an ihre Updates (Zugangsschutz seit 2.4). - Master-Token mit
watchdog:ping,bugtracker:reportâ fĂŒr die Anwendung. Zweites Token mitupdateservice:publishâ fĂŒr die Release-Pipeline, gehört nicht in die Anwendung. - Installationskonto (Rolle
installer) fĂŒr die Erstinstallation. - 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):
BuildDateUtctrug die volle Uhrzeit,WriteOnlyWhenDifferentgriff 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:
DcApiExceptionmit stabilemerror.codeaus dem Umschlag{"status":"error","error":{"code":"âŠ"}}â reagiert wird auf den Code, nie auf den Text.IsPermanentfĂŒr401/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.Versioneinmalig beim Start, sonst trĂ€gt jede Installation in der Aktivierungsliste dieselbe1.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_HOMEbzw.$HOMEauf; ein systemd-Dienst mitUser=ibkrtraderund ohne Heimatverzeichnis landet im RĂŒckfall aufAppContext.BaseDirectory/license, also unter/optâ dort ist nichts schreibbar, und es gĂ€be keinen Offline-Cache. MitAppPathsfĂŒ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$HOMEzeigt 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:SetStorageDirectoryhĂ€ngt jetzt selbst<slug>/licensean (vorher wurde der Pfad roh benutzt). Der Aufruf wird zuLicenseConfig.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-agentnimmt den LizenzschlĂŒssel nicht mehr als--license-key-Argument entgegen âpszeigt Prozessargumente jedem Nutzer der Maschine, dieselbe BegrĂŒndung wie beim Crontab-Verzicht in UPGRADE §5. Der Weg ist jetzt die UmgebungsvariableDC_LICENSE_KEY(LaunchUpdateAgentsetzt sie selbst, wennlicenseKeyĂŒbergeben wird â kein eigener Code nötig). Unsere--license-set-key-Implementierung ruft alsoLicenseClient.ValidateAsyncdirekt auf und schreibt nicht--license-keyin einen eigenen Aufruf des Agenten.Ebenfalls neu:
EnsureLicensedAsync()aufLicenseClientâ nimmt den zwischengespeicherten SchlĂŒssel, fragt bei Bedarf ĂŒberILicensePromptnach, prĂŒft, fragt bei Ablehnung erneut.allowPrompt:falselehnt 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 ausTryGetCachedKey+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:
- Senke am
LoggingService.LoggingService.EntryWrittenexistiert bereits und ist genau dafĂŒr gedacht (die OberflĂ€che hĂ€ngt schon dran). EinDcErrorSinkfiltert aufAppLogLevel.Errorund meldet mitlevel: "error",build: BuildInfo.Version,file/lineaus dem Stacktrace,contextmitGitCommitShortundCorrelationId/SignalId, wenn vorhanden. DieSignalIdmitzuschicken ist der eigentliche Gewinn: eine Fehlermeldung lĂ€sst sich damit im Deploymentcenter direkt gegen das Entscheidungsjournal halten. - Globale Handler â
AppDomain.UnhandledException(level: "fatal", blockierend gesendet, der Prozess ist gleich weg) undTaskScheduler.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/403dauerhaft abschalten, - bei
429Intervall 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: trueist jetzt ausdrĂŒcklich als harter Kill dokumentiert â laufendefinally-Blöcke,IHostApplicationLifetimeund 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), mussexitCurrentApp: falsegesetzt werden. Der Agent wartet ĂŒber--wait-for-pidohnehin auf das Prozessende; das eigene Herunterfahren lĂ€uft dann ĂŒberhost.StopAsync(...)wie inApp/Program.csbereits vorhanden â nicht ĂŒberLaunchUpdateAgentselbst. ZusĂ€tzlichwaitTimeoutSecondsheraufsetzen, 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 nachC:\Program Filesinstalliert wird. Siehe §8 P2; solangeAppPathsnicht korrigiert ist, passensetup.jsonund 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.
SecretProtectionmuss 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 sichsettings.example.jsongar 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
ProjectReferenceist mit der Integration aufmaingelandet, 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, dannPackageReferencestatt 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.