Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt, welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen. Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json ueberschneiden und darum gemeinsam abgelegt werden: Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig): - Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert - DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli - WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt (version, os, checks, metrics, status stopped) - Security: MasterKeyResolver, SecretRedactor, FilePermissions - Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin) - deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben ueber .gitignore aussen vor - setup.json fuer die Erstinstallation - UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md verweist auf den neuen Plan Einfenster-Shell (UI-Redesign): - ShellWindow + ShellNavModel als Seitenleisten-Shell - WindowMenuBar und WindowMenuModel entfallen - Modul- und Kernfenster auf die Shell-Einbettung angepasst Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 KiB
Umsetzungsplan: Deploymentcenter in PolyTrader einbinden (P3c)
Stand: 14.08.2026 · Grundlage:
J:\Softwareprojekte\Deploymentcenter\docs(Fassung 2.4) und die SDK-Quellen unterclient-dotnet\Deploymentcenter.Client. Löst ab: UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration.md (Watchdog + LicenseLabrador als Einzeldienste). Ersetzt inANALYSE-Linux-Portierung.mdden Punkt P3c und präzisiert dort §5.2.
0. Was sich gegenüber der bisherigen Planung geändert hat
Das Deploymentcenter ist nicht mehr „Lizenz + Watchdog an einem Ort", sondern deckt inzwischen vier Bereiche ab. Zwei davon standen in unserer Planung bisher gar nicht:
| Bereich | Zustand bei uns |
|---|---|
| Lizenz | vorhanden, aber gegen LicenseLabrador gebaut (LicenseGate, Ui/LicenseDialog) — und in der Avalonia-App gar nicht verdrahtet, dort läuft derzeit alles ungeprüft |
| Watchdog | vorhanden (WatchdogHeartbeatService), aber gegen die alte Watchdog-API gebaut — URL, Header und Payload passen nicht |
| Error-Reporting / Bugtracker | existiert bei uns nicht. Es gibt im gesamten Repo weder AppDomain.UnhandledException noch TaskScheduler.UnobservedTaskException |
| UpdateService + Erstinstallation | existiert bei uns nicht. Kein Paket, kein Release-Weg, keine Versionsdisziplin (<Version> steht in keiner einzigen .csproj) |
Die Schätzung aus der Analyse (2–4 PT) bezog sich auf die ersten beiden Punkte. Mit Error-Reporting, Auslieferung und Erstinstallation liegt der Umfang bei 4–7 PT.
1. Befunde — Probleme, Bugs und Konzeptfehler
Sortiert nach Wirkung auf uns. Spalte „Wo" trennt, was wir lösen und was im Deploymentcenter gelöst werden müsste.
D-1 · Der Update-Weg beendet die Anwendung hart — für einen Trading-Bot unzulässig · hoch · Wo: wir + DC
UpdateClient.LaunchUpdateAgent ruft Environment.Exit(0)
(client-dotnet/Deploymentcenter.Client/UpdateClient.cs:401), und exitCurrentApp: true ist die
Vorgabe. Genau das haben wir bei der Lizenz schon einmal ausgeschlossen: Eine Anwendung mit
offenen Positionen, laufenden CLOB-Aufrufen und einem 10-Sekunden-Bestätigungsdialog darf nicht
mitten im Lauf abgeschossen werden.
Lösbar auf unserer Seite (exitCurrentApp: false und danach der bestehende geordnete
Shutdown-Pfad). Nicht lösbar auf unserer Seite: Der Agent wartet über --wait-for-pid auf das
Ende der Anwendung, aber nur --wait-timeout Sekunden — Vorgabe 60
(Deploymentcenter.UpdateAgent/Program.cs:70). LaunchUpdateAgent hat für diesen Wert
keinen Parameter (UpdateClient.cs:330-342). Unser Herunterfahren nimmt sich allein für
host.StopAsync bis zu 30 s, dazu Avalonia-Teardown und die Modul-Stopps. Reißt die Frist, tut der
Agent nichts — immerhin sicher, aber ohne Rückmeldung und ohne Stellschraube.
Wunsch an DC: waitTimeoutSeconds in LaunchUpdateAgent durchreichen.
Bis dahin bei uns: den Agenten selbst starten (eigener ProcessStartInfo) statt über
LaunchUpdateAgent — dann setzen wir --wait-timeout selbst.
D-2 · Der Lizenzschlüssel wandert in die Kommandozeile · hoch · Wo: DC
Der Agent nimmt die Zugangsdaten der Release-Ablage ausschließlich als Argument entgegen:
--license-key bzw. --download-user/--download-password
(Deploymentcenter.UpdateAgent/Program.cs:70-79), und UpdateClient.LaunchUpdateAgent hängt sie
ebenso an (UpdateClient.cs:366-369). Umgebungsvariablen liest der Agent an keiner Stelle.
Auf einem Mehrbenutzersystem zeigt ps (bzw. der Task-Manager) die Argumente jedes laufenden
Prozesses. Das ist wörtlich dieselbe Begründung, mit der UPGRADE.md §5 den curl-Aufruf im
Crontab verwirft — an dieser Stelle wird sie nicht angewandt. Für uns kommt hinzu: „keine Secrets
in argv" ist bei PolyTrader ein bereits umgesetzter Sicherheitsbefund; diese Integration würde
ihn zurücknehmen.
Wunsch an DC: Zugangsdaten zusätzlich über DC_LICENSE_KEY / DC_DOWNLOAD_PASSWORD oder über
stdin annehmen. Sauberer Workaround auf unserer Seite gibt es nicht.
D-3 · Das Lizenzurteil ist nicht signiert — Rückschritt gegenüber LicenseLabrador · mittel–hoch · Wo: DC
LicenseClient liest den Status aus reinem JSON (LicenseClient.cs:191-237). Der mitgeschickte
nonce (:113) wird in der Antwort nie geprüft, eine Signatur gibt es nicht. LicenseLabrador
verifizierte die Serverantwort per Ed25519 gegen einen einkompilierten Public Key und reflektierte
die Nonce — deshalb konnte niemand ohne den privaten Schlüssel ein „valid" erfinden.
Jetzt trägt allein TLS. Wer DNS oder die hosts-Datei umbiegt und ein eigenes Zertifikat in den
Trust Store legt — also der Besitzer der Maschine — kann sich ein {"status":"valid"} selbst
ausstellen. Für unser erklärtes Bedrohungsmodell („Schutz gegen einfaches Kopieren auf ein fremdes
System") reicht das; sobald Dritte die Software bekommen, ist es die schwächste Stelle der Kette.
Bemerkenswert: Für Release-Pakete führt UPDATESERVICE_INTEGRATION_GUIDE §6 eine ausführliche
und richtige Begründung, warum dort asymmetrisch signiert werden muss („ein Update wird auf dem
Zielsystem geprüft") — beim Lizenzurteil gilt dasselbe Argument und fehlt die Umsetzung.
Zusatz: Der Client erzwingt kein https:// (serverBaseUrl wird ungeprüft übernommen,
LicenseClient.cs:118). Das prüfen wir wie beim Heartbeat selbst.
D-4 · ILicensePrompt ist eine tote Naht · mittel · Wo: DC (Doku) + unsere Analyse
LicenseClient nimmt ein ILicensePrompt entgegen und legt es in _prompt ab
(LicenseClient.cs:62,69-72) — benutzt wird es nirgends, im ganzen SDK gibt es keine weitere
Fundstelle. Unsere ANALYSE-Linux-Portierung.md (§5.2 und Risiko R6) baut die headless-Story
darauf auf, dass diese Abstraktion den Start auf einem Server ohne Display trägt. Sie tut nichts.
Kein Beinbruch — die Ablauflogik gehört ohnehin zu uns (LicenseGate hat sie bereits) —, aber die
Analyse muss korrigiert werden, und die Abstraktion bauen wir selbst.
D-5 · Reihenfolgefalle beim Zugangsschutz trifft uns in der scharfen Variante · mittel · Wo: wir
UPGRADE.md §16.1 beschreibt für bestehende Produkte „erst ausliefern, dann scharfschalten". Für
neue Produkte gilt das ausdrücklich nicht: /releases/polytrader/ entsteht erst mit dem ersten
Upload und wird beim nächsten cli/tick.php-Lauf geschützt. Es gibt also kein Zeitfenster, in dem
sich ein noch ungeschütztes Paket ziehen ließe.
Der allererste ausgelieferte Build muss die Zugangsdaten bereits mitbringen. Veröffentlichen wir
zuerst ein Release, dessen Anwendung credentials noch nicht übergibt, fällt die Tür hinter ihm zu
und jede Erstinstallation bekommt 401.
Zweitens: Der Schutz beruht auf .htaccess und trägt nur unter Apache mit AllowOverride. Die
Anzeige „GESCHÜTZT" beweist nichts — nur der Selbsttest tut es. Gehört in unsere Abnahme, nicht
in die Zuversicht.
D-6 · Der Packager würde unsere Secrets mitveröffentlichen · hoch · Wo: wir
pack-and-deploy verpackt das Publish-Verzeichnis. Bei uns liegt dort:
| Datei | Inhalt | Wie sie dorthin kommt |
|---|---|---|
appsettings.Local.json |
MySQL-Verbindung mit Passwort | CopyToOutputDirectory in PolyTrader.App.Avalonia.csproj |
master.key |
AES-Master-Key aller at-rest-Secrets | wird neben der Anwendung erwartet (Program.cs:309) |
openrouter.key, server_settings.xml, data.db, Logs/ |
API-Schlüssel, Watchdog-Token, Lizenzschlüssel, Betriebsdaten | Betrieb |
Ohne strikte excludePatterns landet all das im Paket und damit auf dem Server. Der Zugangsschutz
macht es nicht harmlos: UPDATESERVICE §5A/Grenzen sagt es selbst — dann ist es eben statt
öffentlich „für jeden Lizenznehmer sichtbar". Der Packager warnt inzwischen bei
zugangsdatenverdächtigen Dateien, aber die Warnung ist keine Sperre.
Gegenmaßnahme: eigenes Publish-Profil in ein sauberes Verzeichnis, explizite excludePatterns,
und als Abnahmekriterium ein Blick in das fertige Archiv.
D-7 · BuildInfo.targets kommt bei unserer Auslieferungsart nicht mit · mittel · Wo: DC + wir
Deploymentcenter.Client.csproj setzt weder PackageId noch Version und packt die
Deploymentcenter.BuildInfo.targets nicht ins NuGet-Paket (kein
<None Pack="true" PackagePath="build/">). Der Guide empfiehlt stattdessen einen relativen
<Import> ins Nachbar-Repo — genau die Kopplung, die wir bei LicenseLabrador bewusst aufgegeben
haben (Begründung in lib/nuget/README.md: PolyTrader muss ohne das Schwester-Repo bauen).
Wunsch an DC: PackageId/Version setzen und die Targets als build/Deploymentcenter.Client.targets
mitpacken — dann greift sie beim Paketverweis von selbst.
Bis dahin bei uns: die Datei einmalig nach build/ kopieren, mit Herkunfts- und
Aktualisierungsvermerk.
D-8 · Wir haben keine Versionsdisziplin · mittel · Wo: wir
In keiner .csproj steht ein <Version>, eine Directory.Build.props gibt es nicht. Damit trägt
jede Assembly 1.0.0.0.
Der Packager prüft --version gegen die Hauptassembly und bricht bei Abweichung ab — das ist gut
so, denn der Fehler dahinter ist übel: Wird 1.0.1 als 1.0.2 veröffentlicht, aktualisieren alle
Installationen, melden weiterhin 1.0.1, halten das Release erneut für neu und aktualisieren bei
jedem Start wieder. Ohne zentrale Version ist ein Release entweder unmöglich oder gefährlich.
Muss vor dem ersten Paket stehen.
D-9 · setup.json kann unsere Konfiguration nicht schreiben · mittel · Wo: wir
Der Installer beherrscht die Formate json und env (SETUP_INTEGRATION_GUIDE §3/§4). Unsere
Betriebseinstellungen liegen in server_settings.xml — eigenes XML-Format, das er nicht kennt.
Konsequenz: Entweder verzichtet die Erstinstallation auf jede Vorbelegung, oder der
Deploymentcenter-Block (BaseUrl, Token, Kanal, Source, Lizenzschlüssel) zieht nach
appsettings.json. Zweiteres ist ohnehin sauberer — die Anwendung liest bereits
appsettings.Local.json über die IConfiguration, und nur so kann die Erstinstallation das
Anwendungstoken per source: "provision" selbst ziehen, ohne dass jemand ein Token abtippt.
D-10 · Die Erstinstallation kann den Master-Key nicht lösen · mittel · Wo: wir
Der Installer schreibt ausdrücklich Klartext und verschlüsselt nichts
(SETUP_INTEGRATION_GUIDE §3/Dateirechte). Einen POLYTRADER_MASTER_KEY kann er nicht erzeugen.
Eine frisch installierte Instanz hätte also alle Secrets unverschlüsselt liegen, bis jemand von
Hand nachhilft — auf einem Zielsystem im Ausland der wahrscheinlichste Ort für „später".
Optionen: (a) Master-Key als type: "secret"-Feld abfragen, (b) PolyTrader erzeugt beim ersten
Start selbst einen und legt ihn mit 0600 ab, (c) über die systemd-Unit als Umgebungsvariable.
Empfehlung: (b) + (c), (a) wäre eine weitere Stelle, an der jemand ein Geheimnis abtippt.
Positiv: Die im Guide geforderte Weiche „Entschlüsselung reicht Klartext unverändert durch" haben
wir bereits (SecretProtection.Unprotect). Der Guide warnt zu Recht, sie nicht eines Tages als
tote Altlast zu entfernen — sie gehört als aktiv genutzt dokumentiert.
D-11 · Pfad-Inkonsistenz, die erst beim Installieren auffällt · klein · Wo: wir
ServerSettings.Load("server_settings.xml") ist relativ zum Arbeitsverzeichnis, master.key
wird dagegen aus AppContext.BaseDirectory gelesen (Program.cs:309). Solange man aus dem
Projektordner startet, fällt das nicht auf. Nach einer Installation nach /opt/polytrader mit
einer systemd-Unit, deren WorkingDirectory woanders zeigt, liest die Anwendung eine andere Datei,
als der Installer geschrieben hat. Vor D-6/L2 klären.
D-12 · Der bestehende Heartbeat passt nicht — mehr als „Header und URL" · klein · Wo: wir
WatchdogHeartbeatService sendet an {base}/api/heartbeat mit X-Watchdog-Key
(WatchdogHeartbeatService.cs:280,428). Das Deploymentcenter erwartet
{base}/api/watchdog/v1/ping mit Authorization: Bearer (alternativ X-Agent-Token), Events
gehen nach /api/watchdog/v1/event.
ANALYSE-Linux-Portierung.md §5.2 nennt das „nur Header und Ziel-URL". Tatsächlich kommen dazu:
version (sonst lässt sich ein Ausfall keinem Rollout zuordnen), os, group, das ganze
checks-Konzept, metrics — und status: "stopped", das die Fehlalarm-Vermeidung beim geordneten
Beenden übernimmt. Unser metadata-Feld liest der Server dagegen nicht; es entfällt.
D-13 · Error-Reporting braucht einen Handler, den es bei uns nicht gibt · mittel · Wo: wir
Weder AppDomain.CurrentDomain.UnhandledException noch TaskScheduler.UnobservedTaskException
kommen im Repo vor. Beim Bau lauern zwei Fallen:
- Rückkopplung. Der Reporter meldet einen Fehlschlag über
TerminalLogger.Error— was den nächsten Report auslöst. Der Reporter darf seine eigenen Fehler niemals über den eskalierenden Pfad melden. (DC drosselt bei 60 Reports/Minute/IP; darauf verlassen wir uns nicht.) - Datenabfluss. Unsere Fehlermeldungen enthalten Wallet-Adressen, Marktkennungen und Beträge,
der DC-Server steht beim Hoster. Das vorhandene
Sanitize()des Heartbeats muss auch auf Meldung und Stacktrace des Error-Reports laufen.
D-14 · Kein Update ohne gültige Lizenz — auch nicht das reparierende · klein, bewusst · Wo: Entscheidung
Der Zugangsschutz koppelt Updates an eine gültige Lizenz. Eine Installation mit abgelaufener Lizenz kann sich also nicht mehr selbst reparieren. Für eigene Systeme belanglos; sobald Kunden im Spiel sind, ist es eine Entscheidung, keine Nebenwirkung.
D-16 · environment im Fehler-Report ist heimlich ein Auth-Gate · klein–mittel, überraschend · Wo: DC (Doku + Fehlermeldung)
Beim Live-Test von D-3 lieferte /api/errors/v1/report mit einem funktionierenden,
bugtracker:report-berechtigten Token beharrlich 401 unauthorized: "Erwartet wird ein Token mit dem Recht \"bugtracker:report\"" — obwohl der Token genau dieses Recht trug (per
/api/tokens/v1/provision selbst bestätigt).
Ursache: report.php liest environment aus dem Request-Body und reicht es an
ApiAuth::requireScope($db, 'bugtracker:report', $environment, false) durch;
TokenManager::validateToken lehnt danach ab, wenn token['environment'] !== 'all' && token['environment'] !== $environment (TokenManager.php:317). Unser Token war bei der
Provisionierung auf environment: "development" festgelegt, der Report schickte
"environment": "production" — Mismatch, Ablehnung.
Zwei Dinge daran sind problematisch:
- Kein Dokument erwähnt diese Kopplung.
bugtracker.md §3führtenvironmentals reine Metadatum-Angabe für/report(Bugtracker), nicht als Zugriffs-Gate. Für/api/errors/v1/reportsteht es nirgends. Andere Endpunkte (watchdog:ping,watchdog:read/evaluate,license:deactivate) übergeben$environmentgar nicht erst anrequireScope— die Kopplung gilt also inkonsistent nur für diesen einen Endpunkt. - Die Fehlermeldung nennt die falsche Ursache.
401mit"Token mit dem Recht bugtracker:report"legt einen fehlenden Scope nahe. Der tatsächliche Grund (Environment- Mismatch) taucht in der Antwort nirgends auf — von der Client-Seite aus ist das ohne Blick in den Serverquelltext nicht diagnostizierbar.
Für uns gelöst: Token mit environment: "all" neu provisioniert (dc_sub_109c1a03…), gilt für
jeden gemeldeten Umgebungswert. Wunsch an DC: entweder den wahren Grund in der Fehlerantwort
nennen (z.B. eigener Code environment_mismatch), oder die Kopplung ganz aus /errors/v1/report
entfernen und environment wie bei den anderen Endpunkten rein deskriptiv behandeln.
D-15 · Unauthorized wird nur im statischen Zweig erkannt · klein · Wo: DC
CheckForUpdateAsync setzt result.Unauthorized nur, wenn die statische latest.json mit 401
antwortet (UpdateClient.cs:105-111). Fehlt die latest.json, fällt die Prüfung auf die
offene API zurück — eine tote Lizenz sieht dann aus wie „kein Update vorhanden". Für die
Fehlersuche im Feld relevant.
2. Leitentscheidungen
- Das SDK kommt als lokales NuGet-Paket in
lib/nuget, wie schonLicenseLabrador.Client— keine Projektreferenz ins Nachbar-Repo. Begründung unverändert: PolyTrader muss ohne das Schwester-Repo bauen (Build-Server, Zielland-System). - Der Heartbeat bleibt unsere eigene Implementierung. Der Service ist erprobt (Live-Reload, Entprellung, TLS-Zwang, No-Throw-Vertrag, 21 Tests); umgestellt werden Endpunkt, Auth und Payload — nicht die Mechanik.
- Kein
Environment.Exit, nirgends. Weder bei ungültiger Lizenz (Entscheidung vom 31.07.2026) noch beim Update (D-1). Ein Update läuft über denselben geordneten Weg wie das Beenden über die Menüleiste, inklusive Bestätigungsdialog. - Updates werden nie automatisch installiert. Die Anwendung prüft beim Start, meldet im
Launcher und installiert erst auf ausdrückliche Bestätigung. Auch ein
is_critical-Release erzwingt nichts — offene Positionen schlagen jedes Sicherheitsargument. - Revidiert bei der Umsetzung (14.08.2026): Ursprünglich geplant war, dass der gesamte
Deploymentcenter-Block nach
appsettings.jsonzieht. Für Watchdog-Felder (WatchdogUrl,WatchdogToken,WatchdogSource,WatchdogInstance,WatchdogIntervalSeconds) blieb es beiserver_settings.xml/ServerSettings— die bestehende UI (maskierte Eingabe, Live-Reload) deckt den Bedarf bereits ab, ein zweites Konfigurationssystem dafür wäre unnötig gewesen. Nur Werte ohne bestehende UI-Heimat (ProjectSlug, Channel, Feature-Schalter für D-3/D-4) liegen inappsettings.json/DeploymentcenterOptions; Secrets darin (Token, LicenseKey für D-2) in der gitignoriertenappsettings.Local.json, zusätzlich überSecretProtectionverschlüsselbar wo die bestehende Ablage (ServerSettings.LicenseKey) das schon leistet. - Ausgeliefert wird ausschließlich
PolyTrader.App.Avalonia, fürwin-x64undlinux-x64(Entscheidung Richard, 14.08.2026: WinForms verschwindet demnächst vollständig, L5). Folge für diesen Plan: Lizenzdialog und Startgate werden nur noch in Avalonia gebaut, nicht in beiden Anwendungen.Licensing/LicenseGate.csundUi/LicenseDialog.csim WinForms-Projekt werden nicht portiert, sondern ersetzt und gelöscht. Das spart eine komplette zweite Verdrahtung — und die WinForms-Fassung wäre ohnehin die einzige mit Lizenzprüfung gewesen, während die tatsächlich benutzte Anwendung ungeprüft läuft. - Watchdog und LicenseLabrador werden vollständig ausgebaut (Entscheidung Richard, 14.08.2026). Kein Parallelbetrieb, keine Rückfallebene: Nach D-1/D-2 gibt es genau einen Gegenüber, das Deploymentcenter. Siehe Schnitt D-6.
- Ein Anwendungstoken je Installation, per
source: "provision"beim Setup gezogen und aufwatchdog:ping+bugtracker:reportbeschränkt. Ein Master-Token wird nie ausgeliefert. - Produkt-Slug ist
polytrader(Entscheidung Richard, 14.08.2026). Das alteptaus dem LicenseLabrador-Admin entfällt mit diesem Umzug ersatzlos. - Der Master-Key wird bei der Erstinstallation erzeugt (Entscheidung Richard, 14.08.2026), nicht abgefragt — Einzelheiten in D-5.
3. Umsetzung in Schnitten
D-0 · Fundament (ohne Serverkontakt) ✅ ERLEDIGT (14.08.2026)
Umgesetzt wie geplant: Directory.Build.props mit <Version>0.1.0</Version> für alle Projekte,
Deploymentcenter.Client 2.5.0 als lokales NuGet-Paket in lib/nuget (PackageReference in
PolyTrader.Core.csproj, BuildInfo.targets kommt jetzt automatisch über build/ aus dem Paket –
D-7 war zu diesem Zeitpunkt bereits vom Deploymentcenter-Team behoben, keine manuelle Übernahme
nötig), nuget.config-Mapping um Deploymentcenter.* ergänzt, DeploymentcenterOptions +
Deploymentcenter-Sektion in appsettings.json (Secrets in appsettings.Local.json, gitignored).
PolyTrader.Core.BuildInfo.Version erzeugt sich korrekt bei jedem Build. LicenseLabrador-Paket
bewusst noch nicht entfernt (das ist D-6, erst nach Live-Abnahme).
D-0-Rest · Zwischenverifikation der Befunde (14.08.2026)
Vor dem Start Richards Hinweis geprüft, dass die Befunde behoben sein sollten: 5 von 6 bestätigt
behoben (Commit 1967b49, Deploymentcenter-Team) – D-1 (Exit/--wait-timeout), D-2 (argv-Key,
jetzt DC_LICENSE_KEY-Umgebungsvariable), D-4 (ILicensePrompt jetzt über EnsureLicensedAsync()
genutzt), D-6 (Packager bricht bei Secrets ab statt nur zu warnen), D-7 (Paket jetzt IsPackable,
Version 2.5.0). Als Bonus zusätzlich behoben: D-15 (Unauthorized jetzt auch im API-Rückfallzweig
erkannt). D-3 (unsigniertes Lizenzurteil) bleibt bestätigt offen – vom Deploymentcenter-Team
selbst so benannt („bewusst nicht Teil dieses Commits"), deckt sich mit unserer Einordnung in §6.
D-1 · Watchdog auf die Deploymentcenter-API umstellen ✅ ERLEDIGT (14.08.2026)
Umgesetzt in WatchdogHeartbeatService.cs.
Abweichung von der ursprünglichen Planung: kein neues Konfigurationsschema. ServerSettings
(XML, Live-Reload, maskierte Token-Eingabe – bereits erprobte Mechanik) bleibt die Quelle für
WatchdogUrl/WatchdogToken/WatchdogSource/WatchdogInstance/WatchdogIntervalSeconds; nur
ihre Bedeutung ändert sich (zeigen jetzt auf das Deploymentcenter statt auf watchdog.mhdf.de).
DeploymentcenterOptions/appsettings.json wäre hier ein zweites, unnötiges Konfigurationssystem
gewesen – die bestehende UI/Masking/Live-Reload-Maschinerie deckt genau denselben Bedarf ab.
DeploymentcenterOptions bleibt für D-2/D-3/D-4 reserviert (Werte ohne bestehende UI-Heimat:
ProjectSlug, Channel, Feature-Schalter).
- Endpunkte
…/api/watchdog/v1/pingund…/api/watchdog/v1/event, HeaderAuthorization: BearerstattX-Watchdog-Key.TryBuildEndpointnimmt jetzt einen relativen Pfad statt eines Aktionsnamens (PingPath/EventPath-Konstanten). - Payload ergänzt:
version(ausPolyTrader.Core.BuildInfo.Version, nicht mehr aus der Assembly-Version – stimmt jetzt mitDirectory.Build.propsüberein),os(RuntimeInformation.OSDescription). Das bisherigemetadata-Feld entfällt (der Server liest es nicht, siehe Befund D-12). checks-Mechanik neu:RegisterCheck(name, Func<(bool ok, string? message)>)/UnregisterCheck(name)– Composition Root registriert die konkreten Prüfungen, der Dienst selbst kennt ihre Bedeutung nicht. Eine werfende Prüfung meldet sich selbst als fehlgeschlagen statt den Heartbeat zu verhindern. InProgram.cs(Avalonia) verdrahtet:vpn-Prüfung gegenMullvadVpnService.IsConnected.db/market_feed-Prüfungen sind bewusst noch nicht verdrahtet – die jeweiligen Dienste geben aktuell kein Gesundheitssignal nach außen; das ist ein Fast-Follow, kein offener Blocker.- Beim Herunterfahren sendet
StopAsyncjetzt zwei Dinge (vorher nur das Event): einen Heartbeat mitstatus:"stopped"(das ist, was der Evaluator tatsächlich auswertet) und weiterhin dasstopped_graceful-Event ins Ereignisprotokoll. - Tests: 8 neue/geänderte Fälle (Endpunkte, Bearer-Header,
checks-Payload inkl. Fehlerfall), gesamt 454 Tests grün.
Live-Abnahme (14.08.2026, gegen den echten Server, Dev-Sub-Token):
POST /api/watchdog/v1/ping mit dem neuen Payload → state: "up", app_version korrekt übernommen.
POST /api/watchdog/v1/event (stopped_graceful) → angenommen. Erneuter Ping mit
status:"stopped" → Monitor wechselt zu state: "stopped", state_changed: true. Test-Monitor
polytrader-dev-probe bleibt im Deploymentcenter stehen (Zustand stopped, alarmiert nicht).
D-2 · Lizenz auf das Deploymentcenter umstellen ✅ ERLEDIGT (15.08.2026)
Umgesetzt in Licensing/LicenseGate.cs, Licensing/LicenseCli.cs und den neuen Lizenz-Schaltflächen in SettingsWindow.axaml(.cs). Zwei Abweichungen von der ursprünglichen Planung, beide vereinfachend:
-
Kein eigenes
ILicensePrompt, kein eigener modaler Lizenzdialog. Für die GUI wird die bereits vorhandene, erprobte Settings-Oberfläche wiederverwendet — dieselbe maskierte Prompt/Save-Mechanik wie beim Watchdog-Token (btnLicenseKey= „Lizenzschlüssel setzen …", speichert verschlüsselbar inserver_settings.xml) plus ein neuerbtnCheckLicense= „Lizenz jetzt prüfen", der den gespeicherten Schlüssel gegen den Server validiert und Status + Hardware-ID in einem Info-Dialog anzeigt. Ein zusätzliches, eigenes Dialogfenster nur für die Lizenz wäre eine zweite, redundante Eingabe-Oberfläche gewesen. Für--headless/CLI genügt die vom SDK mitgelieferteConsoleLicensePrompt(wird hier gar nicht gebraucht, siehe nächster Punkt) — eine eigene Fassung hätte keinen Mehrwert geboten. Grund für den Verzicht auf einen echten Avalonia-Dialog vor dem Hauptfenster: Avalonias Startreihenfolge (AppBuilder.Setup()darf pro Prozess nur einmal laufen) macht einen zweiten, vorgeschalteten Dialog vorStartWithClassicDesktopLifetimeunnötig fragil; ein im laufenden Fenster erreichbarer Button ist die robustere Lösung und deckt denselben Bedarf. -
Modulentscheidung läuft still, ohne Dialog, vor dem Host-Aufbau.
LicenseGate.CheckUsablevalidiert den inserver_settings.xmlgespeicherten Schlüssel synchron (wie einst die WinForms-Fassung) und entscheidet, welche Module überhaupt registriert werden – nicht überEnsureLicensedAsync/ILicensePromptdes SDK (die interaktive Korrektur läuft über die Settings-UI, s.o., nicht über eine erneute Abfrage an dieser Stelle). Eine per Dialog neu eingetragene Lizenz wirkt für Module deshalb erst beim nächsten Start – bewusst, konsistent mit der Entscheidung vom 31.07.2026 (keine Laufzeit-Revalidierung). -
DeploymentcenterOptionswird jetzt vor dem Host-Aufbau eigenständig ausappsettings.json/appsettings.Local.jsongeladen (Program.LoadDeploymentcenterOptions), weil die Modulliste schon vor dem eigentlichenIHostfeststehen muss. Dieselbe Instanz wird anschließend auch reguär in die DI registriert. -
--smoke-uibewusst ausgenommen:enforceLicense: !smoke— die Konstruktionsprüfung soll auf einem (nie lizenzierten) Build-Server weiterhin ALLE Modul-Fenster testen, nicht nur die Core-Shell. Verifiziert:--smoke-uikonstruiert weiterhin alle 4 Modul-Fenster plus das Einstellungsfenster mit den neuen Lizenz-Feldern (7 statt 6 Settings-Abschnitte). -
TLS wird — anders als im SDK (Befund D-3) — im Gate selbst erzwungen, exakt wie beim Watchdog.
-
CLI-Schalter
--license-status,--license-set-key <schlüssel>,--license-deactivatenach der Konvention aus LICENSE_INTEGRATION_GUIDE §4.--license-deactivateverlangtDC_SHARED_KEYaus der Umgebung (Administrationsrechner) und verweigert sich mit klarer Meldung ohne ihn — der Shared Key gehört nicht in die ausgelieferte Anwendung. -
Keine eigenen Unit-Tests angelegt (wie schon bei der WinForms-Fassung dokumentiert begründet):
PolyTrader.TestsreferenziertPolyTrader.App.Avalonianicht, der Gate ist dünner Leim über dem SDK. Abdeckung über--smoke-ui(Konstruktion) und die Live-Verifikation unten.
Live-Verifikation (15.08.2026, echter Server, Dev-Lizenz mit 9999 Aktivierungen von Richard):
> PolyTrader.App.Avalonia.exe --license-set-key LLAB2-12345-67890-ABCDE-FGHIJ
valid: License is valid
Lizenz gültig und in server_settings.xml gespeichert (Klartext).
> PolyTrader.App.Avalonia.exe --license-status
HWID v2: 2:win:a765bd47c2458fd518a7a1ad8a9d1d4b654c91085dca4667f33e7cad4427bf93 (machine-guid)
Produkt: polytrader
Server: https://dc.mhdf.de
Cache: valid
Offline noch bis: 2026-08-18 07:56:54Z
Gespeicherter Schlüssel (server_settings.xml): gesetzt (Klartext)
Damit ist derselbe Codepfad geprüft, den LicenseGate.CheckUsable beim Start benutzt
(LicenseClient.ValidateAsync) – nicht nur ein isolierter Testaufruf. Die volle GUI/--headless
wurde bewusst nicht gestartet, um keine echten Handelsverbindungen (Polymarket-WSS,
Alchemy-Listener) aus dieser Sitzung heraus zu öffnen; das bleibt Richards manuelle Abnahme.
max_activations war bereits großzügig gesetzt (Richards Hinweis), keine Admin-Änderung nötig.
D-3 · Fehler-Reporting ✅ ERLEDIGT (17.08.2026)
Umgesetzt in DeploymentcenterErrorReporter.cs
und SecretRedactor.cs (die
Secret-Muster-Prüfung aus WatchdogHeartbeatService.Sanitize wurde dorthin ausgelagert, damit
Heartbeat-Meldung — kurz, einzeilig — und Stacktrace — lang, mehrzeilig — dieselbe Musterprüfung,
aber unterschiedliche Kürzungsregeln bekommen, ohne Code zu verdoppeln).
- Drei Quellen wie geplant:
AppDomain.UnhandledException(level: "fatal", synchron mit 3-s-Frist gesendet, da der Prozess unmittelbar danach stirbt),TaskScheduler.UnobservedTaskException("warning",SetObserved()verhindert einen Prozessabbruch durch die schon behandelte Ausnahme),TerminalLogger-Fehler ("error", Fire-and-forget wie beim Heartbeat). - No-Throw + keine Rückkopplung wie gefordert: eigene Sendefehler laufen ausschließlich über
TerminalLogger.Warning(niemals.Error– sonst würde der Reporter sich selbst erneut auslösen, da er genau auf.Errorhört). - Eigene Ratenbegrenzung: 20 Meldungen/Minute (gleitendes Fenster, kein Hintergrund-Timer), deutlich unter dem Server-Limit von 60/Minute/IP – Absicht ist, lokal zu drosseln, bevor eine Fehlerschleife den Server oder andere Dienste auf derselben IP überhaupt erreicht.
Sanitize(Nachricht, ≤200 Zeichen, einzeilig) und die neueSanitizeStackTrace(≤4000 Zeichen, mehrzeilig bleibt erhalten – ein einzeiliger Stacktrace wäre zum Debuggen wertlos) laufen beide vor jedem Versand.- Korrektur gegenüber der Planung: kein
client_ref/Idempotenz-Schlüssel implementiert – anders als beim Bugtracker-/reportdedupliziert/api/errors/v1/reportbereits serverseitig über einen inhaltsbasierten Gruppenschlüssel (bestätigt im Live-Test unten,occurrence_countstieg ohne jedes Zutun). Ein zusätzlicherclient_refhätte hier keinen Zweck gehabt. - Registrierung als
BackgroundServicewieWatchdogHeartbeatService(rein ereignisgesteuert,ExecuteAsyncwartet nur auf Abbruch) – konsistent mit dem etablierten Muster, obwohl kein eigener Takt gebraucht wird. - Konfiguration über
DeploymentcenterOptions.Token/.ErrorReportingEnabled(ausappsettings.Local.json) – anders als beim Watchdog gibt es hierfür keine bestehende UI-Heimat inServerSettings, passt also zur in D-1 begründeten Aufteilung. - 14 neue Tests (Payload, Auth-Header, Secrets aus Nachricht UND Stacktrace, Ratenbegrenzung,
alle drei Quellen inkl.
internalgemachter Handler-Methoden für direkte Testbarkeit ohne echte Prozessabstürze zu provozieren). Gesamt 468 Tests grün.
Live-Verifikation (17.08.2026, echter Server): Zwei identische Meldungen über
/api/errors/v1/report mit dem exakten Payload-Format des neuen Codes → erste Meldung
is_new: true, occurrence_count: 1, zweite is_new: false, occurrence_count: 2 – serverseitige
Deduplizierung bestätigt.
Nebenbefund beim Live-Test: D-16 (neu, siehe §1) – das environment-Feld im Report-Body ist
ein undokumentiertes Auth-Gate gegen die Provisionierung des Tokens; die Fehlermeldung bei einem
Mismatch nennt fälschlich einen fehlenden Scope. Für uns gelöst durch Neuprovisionierung mit
environment: "all" (Token in appsettings.Local.json aktualisiert, betrifft auch den
Watchdog-Token-Kommentar dort).
D-0 · Fundament (ohne Serverkontakt) — ursprüngliche Planung
Directory.Build.propsmit einer<Version>für alle Projekte (D-8).Deploymentcenter.BuildInfo.targetsnachbuild/übernehmen und einbinden;BuildInfo.Versionersetzt das heutigeAssembly.GetName().Versionim Heartbeat (D-7).- SDK paketieren:
dotnet pack Deploymentcenter.Client -c Release -o lib/nuget;nuget.configum das Quellen-MappingDeploymentcenter.*→localergänzen;LicenseLabrador.Clientsamt.nupkgentfernen. - Neuer Konfigurationsabschnitt
Deploymentcenterinappsettings.json+DeploymentcenterOptions(BaseUrl, ProjectSlug, Channel, Token, LicenseKey, Watchdog-Source/-Instance/-Intervall, Schalter für Heartbeat / Fehlermeldungen / Update-Prüfung).
Abnahme: Solution baut, Tests grün, BuildInfo.Version stimmt mit <Version> überein,
lib/nuget enthält nur noch das DC-Paket.
D-1 · Watchdog auf die Deploymentcenter-API umstellen — ursprüngliche Planung
- Endpunkte
…/api/watchdog/v1/pingund…/api/watchdog/v1/event, HeaderAuthorization: Bearer. TLS-Zwang und Loopback-Ausnahme bleiben. - Payload ergänzen:
version(ausBuildInfo),os,group. checksfüllen — das ist der eigentliche Gewinn gegenüber heute:db(MySQL erreichbar),market_feed(Alter des letzten Ticks),vpn(Mullvad-Zustand),modules(laufen die aktiven Module). Einok-Heartbeat mit fehlschlagender Prüfung wird serverseitig aufwarninggestuft.metricsschlank halten (Jobs in der Warteschlange, offene Orders als Anzahl). Weiterhin keine Kontostände, Wallet-Adressen oder Trader-Namen.- Beim Herunterfahren
status: "stopped"senden (zusätzlich zum bestehendenstopped_graceful-Event) — das ist der vom Evaluator ausgewertete Weg. - Konfiguration aus
DeploymentcenterOptions; die altenWatchdog*-Felder ausServerSettingsentfernen (mit einmaliger Übernahme beim Start, damit niemand neu tippt). WatchdogHeartbeatTestsnachziehen.
Abnahme: Monitor erscheint auf dc.mhdf.de mit korrekter version; Beenden über die
Menüleiste → Monitor stopped, kein Alarm; Prozess abschießen → nach 4× Intervall genau ein
Down-Alarm. Voraussetzung, die vorher zu prüfen ist: cli/tick.php läuft dort tatsächlich als
Cron — ohne ihn sind alle Monitorzustände wertlos (/api/health → checks.evaluator.ok).
D-2 · Lizenz auf das Deploymentcenter umstellen — ursprüngliche Planung
LicenseGateaufDeploymentcenter.Client.LicenseClientumbauen:ValidateAsync(slug, key, baseUrl, appVersion), ErgebnisLicenseValidationResult.LicenseClient.DefaultAppVersion = BuildInfo.Versioneinmal beim Start.- Statusabbildung:
IsValid→ voller Start;IsTransient→ weiterlaufen und nur warnen (Netzstörung ist kein Urteil); alles andere → eingeschränkter Modus ohne Module.https://selbst erzwingen (D-3). - Eigene Prompt-Abstraktion bauen (D-4): Avalonia-Dialog für den Desktop, Konsolenvariante für
--headless. Der Dialog zeigt Hardware-ID (kopierbar), Status und Offline-Restlaufzeit. - Gate und Dialog entstehen im Avalonia-Projekt — dort läuft heute gar keine Prüfung, und die
WinForms-Fassung wird nicht mitgezogen (Leitentscheidung 6).
Licensing/LicenseGate.csundUi/LicenseDialog.cswerden mit D-6 gelöscht, nicht portiert. - CLI-Schalter
--license-status,--license-set-key,--license-deactivatenach der Konvention des Guides.
Abnahme: gültige Lizenz → alle Module; Netz getrennt → valid_offline mit sichtbarer
Restlaufzeit; Schlüssel entfernt → nur Core-Shell, kein Absturz; --headless ohne Display
blockiert nicht.
Vor dem ersten echten Start im DC einstellen: Produkt polytrader mit großzügigem
max_activations (ein Windows-Neuinstall erzeugt eine neue MachineGuid = neuer Platz) und
default_cache_ttl_hours als Offline-Kulanz für eigene Maschinen.
Ein Aktivierungsplatz wird beim ersten echten Start verbraucht. Die Prüfung deshalb nicht „mal eben" von einer Entwicklermaschine aus ausprobieren, sondern dort, wo die Installation hingehört — so wie beim letzten Mal gehandhabt.
D-3 · Fehler-Reporting — ursprüngliche Planung
DeploymentcenterErrorReporterinPolyTrader.Core:POST /api/errors/v1/report, Scopebugtracker:report.- Quellen:
AppDomain.UnhandledException(level: "fatal"),TaskScheduler.UnobservedTaskException(warning),TerminalLogger-Fehler (error). - Pflichtmerkmale: No-Throw, keine Rückkopplung (D-13), eigene Ratenbegrenzung,
Sanitize()auf Meldung und Stacktrace, abschaltbar über die Konfiguration. client_refje Prozessstart+Fehlergruppe setzen, damit ein Neuversuch kein Duplikat anlegt.- Serverseitig Ignore-Regeln für bekanntes Rauschen anlegen (Polymarket-429, VPN-Reconnect) — jeweils mit Alarmschwelle, denn der Zähler ist der eigentliche Zweck.
Abnahme: erzwungener Testfehler erscheint als Gruppe; zweites Auftreten erhöht
occurrence_count, statt ein zweites Item anzulegen; bei nicht erreichbarem DC läuft PolyTrader
unverändert weiter und erzeugt keine Fehlerschleife.
D-4 · Auslieferung (UpdateService) ✅ VOLLSTÄNDIG ERLEDIGT (20.08.2026)
Umgesetzt (Schritte 1 und 3 der ursprünglichen Planung):
- Update-Prüfung beim Start in LauncherWindow.axaml(.cs):
fire-and-forget, nur aktiv bei
Deploymentcenter:UpdateCheckEnabled(Vorgabefalse), nutztReleaseCredentials.FromLicenseKey(...)mit dem entschlüsseltenServerSettings.LicenseKey. Ein gefundenes Update erscheint als Banner (Version, Changelog,is_critical-Hinweis) mit „Jetzt installieren …" und „Später". Installiert nie automatisch — deckt sich mit Leitentscheidung 4.result.Unauthorizedwird erkannt und bewusst still behandelt (die Lizenzprüfung aus D-2 hat eine tote Lizenz bereits gemeldet, ein zweiter Hinweis wäre Rauschen). - Installation nach Bestätigungsdialog über
UpdateClient.LaunchUpdateAgent(..., exitCurrentApp: false, waitForCurrentProcess: true, waitTimeoutSeconds: 40, licenseKey: ...), danach_uiHost.RequestShutdown()— derselbe geordnete Weg wie „Beenden" über die Menüleiste, inklusive der 10-Sekunden-Sicherheitsabfrage. Kein Sonderpfad, der die Bestätigung umgeht. deploy/packager.config.example.json(Schritt 1) mit denexcludePatterns/preservePatternsaus der Planung, plus Nachtrag*.pfx/*.key/*.pem/*.p12(der Packager selbst prüft seit Version 2.5.0 zusätzlich Dateiname UND Inhalt, siehe D-6 der Befunde — diese Liste ist die erste, nicht die einzige Verteidigungslinie).deploy/packager.config.json(echte FTP-Zugangsdatenupdateservice:publish-Token) neu in.gitignore.
- Live geprüft (HTTP-Vertrag, per curl):
GET /api/updateservice/v1/check?product=polytrader&version=0.1.0&channel=prod→update_available: false, latest_version: "0.1.0"— bestätigt, dass fürpolytraderaktuell kein Release auf dem Server liegt (die2.0.1-Zeile insql/schema.sqlist Beispiel-Seed-Daten aus dem Deploymentcenter-Repo, nicht live). Der Code-Pfad selbst nutzt dieselbe SDK-Methode, die bereits in D-1/D-2/D-3 erprobt ist.
Nachgezogen (20.08.2026, auf Richards ausdrücklichen Wunsch — „lass uns einen Win und Linux Release erstellen und hochladen"):
- Signierschlüssel serverseitig bestätigt:
GET /api/updateservice/v1/pubkeyliefert einen RSA-Key (Fingerprinted99a38d…) — die zuvor offene Checkliste-Voraussetzung war in Wahrheit schon erfüllt. dotnet publish -c Release --self-contained truefürwin-x64(124 MB) undlinux-x64(120 MB) in saubere/tmp-Verzeichnisse, nicht das Debug-Output. Self-contained bewusst gewählt, weil der Zweck genau der Erstinstallations-Test auf einem frischen System ist, das nicht zwingend eine .NET-Laufzeit mitbringt.- Fund dabei:
appsettings.Local.json(mein echtes lokales MySQL-Passwort + Dev-Token) landete im Publish-Output — dieCopyToOutputDirectory-Regel in der.csproj, die genau für diesen Fall in D-6 der Befunde und im eigenenpackager.config.example.json-Kommentar dokumentiert ist. Vor jedem weiteren Schritt von Hand entfernt, dann zusätzlich mit einem eigenen Grep nach gefülltenPassword=-Feldern,dc_master_…/dc_sub_…-Tokens und privaten Schlüsseln über beide Verzeichnisse geprüft (leer). Der Packager selbst hätte es beim--allow-secrets-losen Lauf ohnehin blockiert — das war die zweite, nicht die einzige Verteidigungslinie. deploy/packager.config.json(gitignored) angelegt. FTP-Zugangsdaten kamen auf Richards Anweisung ausDeploymentcenter/scripts/deploy_config.json(dieselbe Hosting-Zugangsdaten, mit denen auch das Deploymentcenter selbst deployt wird). Remote-Pfad per read-only FTPS-Listing verifiziert statt geraten:/releases/liegt als eigenständiges Verzeichnis auf FTP-Root-Ebene (nicht unter/public_html/releases/, wie das Beispiel im UPDATESERVICE-Guide nahelegt) — bestätigt durch die dort bereits vorhandenen Verzeichnisseclawddotnet/undpredictalytics/echter Nachbarprojekte.ftpRemoteBaseDir: "/releases"entsprechend gesetzt.- Ein
updateservice:publish-Token separat provisioniert (environment: "all", wie schon bei D-3 gelernt) — nicht das Dev-Token ausappsettings.Local.jsonwiederverwendet, das hat nurwatchdog:ping/bugtracker:report. - Beide Releases veröffentlicht über
pack-and-deploy:polytrader 0.1.0 (prod, win-x64)undpolytrader 0.1.0 (prod, linux-x64). Versionsgegenprobe lief automatisch durch (Assembly-Version stimmte mit--versionüberein).appsettings.jsonkorrekt als „GESCHÜTZT" (preservePatterns) markiert,*.pdbje Projekt korrekt ausgeschlossen.
Live-Abnahme (20.08.2026):
GET /api/updateservice/v1/check?product=polytrader&version=0.0.0&channel=prod&platform=win-x64und...&platform=linux-x64→ beideupdate_available: true, latest_version: "0.1.0".- Beide Antworten enthalten ein gefülltes
manifest_signature-Feld — signiert. - Zugangsschutz: nicht sofort aktiv —
curl -I .../package.tar.gzlieferte direkt nach dem Publish noch200statt401. Deckt sich mit der Dokumentation: für ein neues Produkt entsteht/releases/polytrader/erst mit dem ersten Upload, der Schutz erst mit dem nächstencli/tick.php-Durchlauf. Per Hintergrund-Poll auf401gewartet statt einfach anzunehmen, dass er greift — nach rund zwei Minuten aktiv geworden. - Vollständige Selbsttest-Sequenz danach grün: ohne Zugangsdaten →
401(beide Plattformen); mit dem Dev-Lizenzschlüssel als Basic-Auth (lic_<sha256-präfix>:<schlüssel>) →200;.htpasswddirekt abgerufen →403.
Reihenfolge ist hier bindend — siehe D-5.
Ursprüngliche Planung (jetzt umgesetzt, s.o.):
packager.config.jsonmit striktenexcludePatterns(appsettings.Local.json,master.key,openrouter.key,server_settings.xml,*.db,Logs/**,*.pdb) undpreservePatterns(appsettings.json).- Sauberes Publish-Verzeichnis je Plattform (
win-x64,linux-x64), nicht das Debug-Output. - In der Anwendung: Update-Prüfung beim Start mit
credentials: ReleaseCredentials.FromLicenseKey(...), Anzeige im Launcher, Installation nur auf Bestätigung und über den geordneten Shutdown-Pfad (D-1). - Erst danach das erste Release veröffentlichen.
- Zugangsschutz erzeugen und Selbsttest laufen lassen; zusätzlich von Hand:
curl -I …/releases/polytrader/prod/win-x64/<version>/package.tar.gz→ 401.
Abnahme: Update auf einem Testsystem behält appsettings.Local.json, server_settings.xml,
master.key und data.db; eine im neuen Release entfallene DLL ist verschwunden; ein Abbruch
mitten im Anwenden hinterlässt eine lauffähige Installation (Rollback); und der Blick ins Archiv:
tar -tzf package.tar.gz | grep -Ei 'local|master\.key|openrouter|settings\.xml|\.db$'
Diese Ausgabe muss leer sein.
D-5 · Erstinstallation (setup.json) ✅ Code-seitig erledigt (17.08.2026), Live-Abnahme offen
Umgesetzt:
- setup.json im App-Projekt (per
CopyToOutputDirectoryim Publish-Paket) mit zwei Zielen:appsettings.json(Deploymentcenter-Block:BaseUrlviadetect:baseurl,ProjectSlugviadetect:project,Channel,Tokenviaprovisionmit den Scopeswatchdog:ping+bugtracker:report) undappsettings.Local.json(Database:MySqlConnectionStringalssecret). Korrektur gegenüber der ursprünglichen Planung: keinWatchdog:Source-Feld — die Watchdog-Konfiguration liegt seit D-1 bewusst inserver_settings.xml(maskierte UI, Live-Reload), undsetup.jsonkann nurjson/env-Ziele schreiben, kein XML. Aktivierung von Watchdog und Eintragen des Lizenzschlüssels bleiben deshalb bewusste Post-Install-Schritte über das Einstellungsfenster — beide sind seit D-1/D-2 genau dafür gebaut (maskierte Eingabe-Buttons, sofort wirksam ohne Neustart). Imsetup.json-Kommentar dokumentiert, damit das nicht als vergessenes Feld missverstanden wird. - Master-Key-Erzeugung (D-10) in MasterKeyResolver.cs
(neu, testbar in
PolyTrader.Corestatt direkt inProgram.csverdrahtet) + gemeinsamer FilePermissions.cs-Helfer (ersetzt die bisher inSettingsWindow.axaml.csduplizierteRestrictToOwner-Logik). Reihenfolge: Umgebungsvariable → vorhandene Datei → nur wenn beides fehlt neu erzeugen. 8 Tests, u.a. explizit „ein zweiter/dritter Aufruf erzeugt niemals neu" und „ein fehlgeschlagener Schreibversuch wirft nie, sondern warnt". Live geprüft: frisches Verzeichnis ohnemaster.key→--smoke-ui-Lauf erzeugt die Datei mit der erwarteten Log-Meldung,SecretProtectionaktiviert sich sofort danach in derselben Sitzung. - polytrader.service (systemd-Unit-Vorlage) löst D-11: setzt
WorkingDirectoryausdrücklich auf das Installationsverzeichnis, mit Begründung im Kommentar (sonst ladenserver_settings.xml, relativ zum Arbeitsverzeichnis, undmaster.key, relativ zuAppContext.BaseDirectory, aus unterschiedlichen Orten).SIGTERMmit 45 s Frist für den geordneten Shutdown-Pfad stattSIGKILL.
Bewusst nicht gemacht:
- Installationskonto im DC anlegen — das Anlegen eines Kontos mit Zugangsdaten ist eine
Handlung, die grundsätzlich beim Menschen bleibt, nicht beim Agenten. Richard legt es im WebUI
unter System → 👤 Benutzer an (Rolle
installer, siehe SETUP_INTEGRATION_GUIDE §2). - Volle Live-Abnahme (
update-agent --action installauf einem echten System) — braucht ein veröffentlichtes Release (D-4, noch offen, siehe dort) und das Installationskonto von oben. Ohne beides lässt sich nur die App-seitige Hälfte prüfen, was hiermit geschehen ist.
Ursprünglich geplant, jetzt überholt:
- Master-Key beim ersten Start selbst erzeugen (Entscheidung Richard, 14.08.2026):
ConfigureSecretProtectionlegt, wenn wederPOLYTRADER_MASTER_KEYnochmaster.keyvorliegen, einen kryptografisch zufälligen Schlüssel an und schreibt ihn mitFile.SetUnixFileMode(…, UserRead | UserWrite)— sonst entsteht er unter Linux mit644, also world-readable. Drei Punkte, die dabei nicht untergehen dürfen:- Nur erzeugen, wenn wirklich keiner da ist. Ein versehentliches Überschreiben macht jedes bereits verschlüsselte Secret in der Datenbank unlesbar — das ist der einzige wirklich unumkehrbare Fehler in diesem ganzen Plan. Erzeugung deshalb ausschließlich beim Nichtvorhandensein der Datei, mit deutlichem Log-Eintrag und einem Hinweis, den Schlüssel zu sichern.
- Ort ist
AppContext.BaseDirectory— dieselbe Stelle, an der er heute gelesen wird. Zusammen mit D-11 klären, dass die Anwendung nach der Installation dieselbe Datei findet. master.keygehört inexcludePatterns(D-6) und darf nie in ein Paket geraten.
- Installationskonto im DC anlegen (Rolle
installer, kein Admin). - systemd-Unit schreiben — der Installer legt bewusst keinen Dienst an. Dabei D-11 auflösen
(
WorkingDirectory= Installationsverzeichnis oder absolute Pfade).
Abnahme: update-agent --action install auf einem frischen Linux-System führt zu einer
lauffähigen Installation, ohne dass eine Datei von Hand bearbeitet wird; master.key entsteht mit
600; ein zweiter Start erzeugt keinen neuen Schlüssel.
D-6 · Watchdog und LicenseLabrador ausbauen
Entscheidung Richard (14.08.2026): kein Parallelbetrieb. Läuft nach D-1 und D-2, damit zu keinem Zeitpunkt beides fehlt.
LicenseLabrador.ClientausPolyTrader.App.csprojundlib/nugetentfernen, Quellen-Mapping innuget.configvonLicenseLabrador.*aufDeploymentcenter.*umstellen.Licensing/LicenseGate.cs,Ui/LicenseDialog.cs(+.Designer.cs) löschen — ersetzt durch die Avalonia-Fassung aus D-2.- Alte
Watchdog*-Felder ausServerSettingsund der Settings-Oberfläche entfernen (der Token-Button und die Statusanzeige wandern zum Deploymentcenter-Block). - Beide Umsetzungspläne als abgelöst kennzeichnen,
ANALYSE-Linux-Portierung.md§5.2 und die Risikozeile R6 korrigieren (D-4 der Befunde:ILicensePromptträgt die headless-Story nicht). - Serverseitig:
watchdog.mhdf.deundlicense.mhdf.deabschalten, sobald die Live-Abnahme steht. Damit erledigen sich die beiden offenen Auflagen aus dem Altplan (Secrets rotieren, UTC/NOW()) von selbst — nicht vorher abschalten, sonst fehlt die Rückfallebene während der Abnahme.
Abnahme: Die Solution baut ohne LicenseLabrador-Paket, grep -ri licenselabrador findet im
Code nur noch historische Erwähnungen in Plandokumenten, und server_settings.xml enthält keine
Watchdog-Felder mehr.
Reihenfolge: Die alten Dienste erst abschalten, wenn D-1 und D-2 live abgenommen sind — nicht, wenn sie gebaut sind.
4. Reihenfolge, Abhängigkeiten, Aufwand
| Schritt | Inhalt | hängt an | Aufwand | Stand |
|---|---|---|---|---|
| D-0 | Fundament: Version, SDK-Paket, Optionen | — | 0,5 PT | ✅ 14.08.2026 |
| D-1 | Watchdog umstellen | D-0 | 0,5–1 PT | ✅ 14.08.2026, live verifiziert |
| D-2 | Lizenz umstellen + Prompt-Naht (nur Avalonia) | D-0 | 1 PT | ✅ 15.08.2026, live verifiziert |
| D-3 | Fehler-Reporting | D-0 | 0,5–1 PT | ✅ 17.08.2026, live verifiziert |
| D-4 | Auslieferung | D-0, D-2 (Schlüssel für den Paketabruf) | 1–2 PT | ✅ 20.08.2026, polytrader 0.1.0 live für win-x64+linux-x64, signiert, live geprüft |
| D-5 | Erstinstallation + Master-Key-Erzeugung | D-4 | 0,5–1 PT | Code ✅ 17.08.2026; Release liegt jetzt vor — Live-Install fehlt nur noch das Installationskonto (Richard) |
| D-6 | Watchdog/LicenseLabrador ausbauen | Live-Abnahme von D-1 und D-2 | 0,5 PT | offen — beide Vorbedingungen live ✅, wartet auf Richards manuelle GUI-Abnahme |
Summe 4–7 PT. D-1, D-2 und D-3 sind untereinander unabhängig und können in beliebiger Reihenfolge laufen. Zwei Reihenfolgen sind dagegen bindend:
- D-2 vor D-4 — ohne Lizenzschlüssel im Update-Weg sperrt uns der Zugangsschutz mit dem ersten Release aus (Befund D-5).
- D-6 nach der Live-Abnahme, nicht nach dem Bau.
Serverseitige Voraussetzungen (vor D-1)
- Projekt
polytraderim Deploymentcenter angelegt (Slug festgelegt, id 2, aus dem Seed-Bestand) cli/tick.phpläuft als Cron —/api/healthmeldetchecks.evaluator.ok: true(14.08.2026 geprüft)- Master-Token vorhanden; Dev-Sub-Token für
polytraderprovisioniert (Scopeswatchdog:ping,bugtracker:report) — für den Produktivbetrieb ein eigenes Anwendungstoken je Installation nachziehen, siehe Leitentscheidung 8 - Release-Signierschlüssel hinterlegt (
/api/updateservice/v1/pubkeyantwortet) — vor D-4 max_activationsfürpolytradergroßzügig gesetzt (Richard, vor 15.08.2026: Dev-Lizenz mit 9999 Aktivierungen) —default_cache_ttl_hoursungeprüft übernommen (Live-Test zeigte plausible ~72h Offline-Kulanz), bei Bedarf im Admin nachjustieren- Installationskonto (Rolle
installer) angelegt — vor D-5
5. Entscheidungen (Richard, 14.08.2026)
| Frage | Entscheidung | Wirkt auf |
|---|---|---|
| Produkt-Slug | polytrader, pt entfällt |
D-2, D-4, Serverseite |
| Zielarchitektur | Alles auf das Deploymentcenter. Watchdog und LicenseLabrador verschwinden, kein Parallelbetrieb | D-6 |
| Oberfläche | Nur noch Avalonia, WinForms wird demnächst vollständig ausgebaut | Leitentscheidung 6, D-2, D-4 |
| Master-Key | Bei der Erstinstallation erzeugen | D-5, D-10 |
6. Weiterhin offen
Stand 17.08.2026: Von den ursprünglich zwei DC-seitigen Wünschen ist einer erledigt
(Zugangsdaten aus argv → DC_LICENSE_KEY-Umgebungsvariable, waitTimeoutSeconds einstellbar,
BuildInfo.targets jetzt im Paket — alles Commit 1967b49, siehe §0). Offen bleibt nur D-3 der
Befunde (unsigniertes Lizenzurteil) — vom Deploymentcenter-Team selbst bestätigt und bewusst
zurückgestellt. Trägt für den Eigenbetrieb, wird relevant, sobald Dritte die Software bekommen.
Kein Blocker für diesen Plan.
Dazu neu D-16 (§1): das environment-Feld in /api/errors/v1/report ist ein undokumentiertes
Auth-Gate mit irreführender Fehlermeldung — für uns durch Neuprovisionierung gelöst (Token mit
environment: "all"), als Wunsch an DC offen (entweder die echte Ursache in der Fehlerantwort
nennen, oder die Kopplung entfernen).
Entscheidung nötig: erstes Release veröffentlichen (D-4)
D-4 ist bis auf die eigentliche Auslieferung fertig — Update-Prüfung, Installations-Bestätigung und der geordnete Neustart-Weg sind gebaut und laufen über denselben Pfad wie „Beenden". Nicht durchgeführt: ein Paket schnüren und veröffentlichen. Drei Dinge sprechen dafür, das nicht nebenbei zu tun, sondern mit Richard abzustimmen:
- Ein veröffentlichtes Release ist auf einem von mehreren Projekten geteilten Server persistent, nicht so folgenlos rückgängig zu machen wie ein lokaler Build.
- Der Zugangsschutz (§5A des UpdateService-Guides) verändert
.htaccess/.htpasswdin/releases/— einem Verzeichnisbaum, den auch andere Produkte nutzen. - Der Release-Signierschlüssel ist serverseitig noch nicht bestätigt (offener Punkt in der Checkliste §4). Ein erstes, unsigniertes Release wäre vermeidbar.
Sobald Richard grünes Licht gibt: deploy/packager.config.json aus der Vorlage anlegen (echtes
updateservice:publish-Token, FTP-Zugangsdaten), dotnet publish für win-x64 und linux-x64 in
saubere Verzeichnisse (nicht das Debug-Output), pack-and-deploy je Plattform aufrufen, danach
Zugangsschutz + Selbsttest prüfen (§4B der Planung).