Der Status des Projekts stand verstreut in elf Umsetzungsplaenen, drei Konzepten, der Linux-Analyse und dem Projektstand - teils widersprechend, teils wochenlang veraltet. Ab jetzt gibt es genau eine Statusquelle. docs/ROADMAP.md (neu): - Alle Vorhaben in vier Stufen A bis D, plus technische Schuld und Verlauf. Die Stufen sind eine Reihenfolge, keine Termine: jede schafft die Voraussetzung fuer die naechste. - Statuszeichen: erledigt / offen / blockiert (mit Ursache) / bewusst zurueckgestellt / Idee, nicht beschlossen. Damit ist das, was wir NICHT bauen wollen, sichtbar vorgehalten statt unauffindbar in einem Plan zu schlummern. - Inhaltlich getragen, nicht nur verlinkt: je Vorhaben Ziel, Phasen, Akzeptanzkriterien, offene Entscheidungen und Leitplanken aus den Quelldokumenten. - Sichtbar gemacht, was vorher zwischen den Dokumenten verborgen lag: CopyTrading Phase 1 ist der Engpass der gesamten Roadmap (MarketMaking und BundleArbitrage haben harte Voraussetzungen darauf), und die Sniper-Metriken aus Phase 3.2 sind ein Spezialfall des StrategieDrift-Fingerprints - zusammen bauen statt doppelt. Archiv (docs/archiv/): - 15 Dokumente verschoben (11 Umsetzungsplaene, 3 Konzepte, ANALYSE-Linux-Portierung). Sie bleiben die Bauanleitungen mit Code-Bezuegen, Risikotabellen und Begruendungen - eingefroren ist nur ihr Status. - archiv/README.md ordnet jedes Dokument seinem Roadmap-Punkt zu. Verweise nachgezogen - der eigentliche Aufwand: - 25 Markdown-Links repariert. 15 davon verschiebungsbedingt (eine Ebene tiefer), der Rest war schon vorher falsch: die Ideensammlung verlinkte Quellcode relativ zum Repo-Wurzelverzeichnis statt zu docs/. - 12 Dateien ausserhalb von docs/ verwiesen in Kommentaren auf die Plaene (csproj, props, setup.json, sechs Quelldateien) - alle auf archiv/ umgebogen. - Verweise auf Dateien, die der Fruehjahrsputz geloescht hat (Ui/, Program.cs, WindowMenuBar), zu Klartext entschaerft statt tote Links zu lassen. - Gegenprobe: 85 Links geprueft, 0 kaputt. Build gruen, 476 Tests gruen. PROJEKTSTAND.md entdoppelt: Abschnitt "Offen" verweist jetzt auf die Roadmap. Arbeitsteilung ist damit klar - Projektstand sagt was IST, Roadmap was KOMMT. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
828 lines
56 KiB
Markdown
828 lines
56 KiB
Markdown
# Umsetzungsplan: Deploymentcenter in PolyTrader einbinden (P3c)
|
||
|
||
> Stand: 14.08.2026 · Grundlage: `J:\Softwareprojekte\Deploymentcenter\docs` (Fassung 2.4) und die
|
||
> SDK-Quellen unter `client-dotnet\Deploymentcenter.Client`.
|
||
> Löst ab: [UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration.md](./UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration.md)
|
||
> (Watchdog + LicenseLabrador als Einzeldienste).
|
||
> Ersetzt in `ANALYSE-Linux-Portierung.md` den 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:
|
||
|
||
1. **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.)
|
||
2. **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:
|
||
|
||
1. **Kein Dokument erwähnt diese Kopplung.** `bugtracker.md §3` führt `environment` als reine
|
||
Metadatum-Angabe für `/report` (Bugtracker), nicht als Zugriffs-Gate. Für `/api/errors/v1/report`
|
||
steht es nirgends. Andere Endpunkte (`watchdog:ping`, `watchdog:read/evaluate`,
|
||
`license:deactivate`) übergeben `$environment` gar nicht erst an `requireScope` — die Kopplung
|
||
gilt also inkonsistent nur für diesen einen Endpunkt.
|
||
2. **Die Fehlermeldung nennt die falsche Ursache.** `401` mit `"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
|
||
|
||
1. **Das SDK kommt als lokales NuGet-Paket** in `lib/nuget`, wie schon `LicenseLabrador.Client` —
|
||
keine Projektreferenz ins Nachbar-Repo. Begründung unverändert: PolyTrader muss ohne das
|
||
Schwester-Repo bauen (Build-Server, Zielland-System).
|
||
2. **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.
|
||
3. **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.
|
||
4. **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.
|
||
5. **Revidiert bei der Umsetzung (14.08.2026):** Ursprünglich geplant war, dass der gesamte
|
||
Deploymentcenter-Block nach `appsettings.json` zieht. Für Watchdog-Felder (`WatchdogUrl`,
|
||
`WatchdogToken`, `WatchdogSource`, `WatchdogInstance`, `WatchdogIntervalSeconds`) blieb es bei
|
||
`server_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 in
|
||
`appsettings.json`/`DeploymentcenterOptions`; Secrets darin (Token, LicenseKey für D-2) in der
|
||
gitignorierten `appsettings.Local.json`, zusätzlich über `SecretProtection` verschlüsselbar wo
|
||
die bestehende Ablage (`ServerSettings.LicenseKey`) das schon leistet.
|
||
6. **Ausgeliefert wird ausschließlich `PolyTrader.App.Avalonia`**, für `win-x64` und `linux-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.cs` und `Ui/LicenseDialog.cs` im
|
||
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.
|
||
7. **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**.
|
||
8. **Ein Anwendungstoken je Installation**, per `source: "provision"` beim Setup gezogen und auf
|
||
`watchdog:ping` + `bugtracker:report` beschränkt. Ein Master-Token wird **nie** ausgeliefert.
|
||
9. **Produkt-Slug ist `polytrader`** (Entscheidung Richard, 14.08.2026). Das alte `pt` aus dem
|
||
LicenseLabrador-Admin entfällt mit diesem Umzug ersatzlos.
|
||
10. **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](../../../src/PolyTrader.Core/Services/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/ping` und `…/api/watchdog/v1/event`, Header `Authorization: Bearer`
|
||
statt `X-Watchdog-Key`. `TryBuildEndpoint` nimmt jetzt einen relativen Pfad statt eines
|
||
Aktionsnamens (`PingPath`/`EventPath`-Konstanten).
|
||
- Payload ergänzt: `version` (aus `PolyTrader.Core.BuildInfo.Version`, nicht mehr aus der
|
||
Assembly-Version – stimmt jetzt mit `Directory.Build.props` überein), `os`
|
||
(`RuntimeInformation.OSDescription`). Das bisherige `metadata`-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. In `Program.cs` (Avalonia) verdrahtet: `vpn`-Prüfung gegen
|
||
`MullvadVpnService.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 `StopAsync` jetzt **zwei** Dinge (vorher nur das Event): einen
|
||
Heartbeat mit `status:"stopped"` (das ist, was der Evaluator tatsächlich auswertet) **und**
|
||
weiterhin das `stopped_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](../../../src/PolyTrader.App.Avalonia/Licensing/LicenseGate.cs),
|
||
[Licensing/LicenseCli.cs](../../../src/PolyTrader.App.Avalonia/Licensing/LicenseCli.cs) und den neuen
|
||
Lizenz-Schaltflächen in [SettingsWindow.axaml(.cs)](../../../src/PolyTrader.App.Avalonia/Views/SettingsWindow.axaml).
|
||
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 in `server_settings.xml`) plus ein neuer `btnCheckLicense` = „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
|
||
mitgelieferte `ConsoleLicensePrompt` (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 vor `StartWithClassicDesktopLifetime` unnö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.CheckUsable`
|
||
validiert den in `server_settings.xml` gespeicherten Schlüssel synchron (wie einst die
|
||
WinForms-Fassung) und entscheidet, welche Module überhaupt registriert werden – **nicht** über
|
||
`EnsureLicensedAsync`/`ILicensePrompt` des 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).
|
||
|
||
- `DeploymentcenterOptions` wird jetzt **vor** dem Host-Aufbau eigenständig aus
|
||
`appsettings.json`/`appsettings.Local.json` geladen (`Program.LoadDeploymentcenterOptions`), weil
|
||
die Modulliste schon vor dem eigentlichen `IHost` feststehen muss. Dieselbe Instanz wird
|
||
anschließend auch reguär in die DI registriert.
|
||
- **`--smoke-ui` bewusst 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-ui` konstruiert 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-deactivate` nach der
|
||
Konvention aus LICENSE_INTEGRATION_GUIDE §4. `--license-deactivate` verlangt `DC_SHARED_KEY` aus
|
||
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.Tests` referenziert `PolyTrader.App.Avalonia` nicht, 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](../../../src/PolyTrader.Core/Services/DeploymentcenterErrorReporter.cs)
|
||
und [SecretRedactor.cs](../../../src/PolyTrader.Core/Security/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 `.Error` hö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 neue `SanitizeStackTrace` (≤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-`/report` dedupliziert `/api/errors/v1/report` bereits serverseitig über einen
|
||
inhaltsbasierten Gruppenschlüssel (bestätigt im Live-Test unten, `occurrence_count` stieg ohne
|
||
jedes Zutun). Ein zusätzlicher `client_ref` hätte hier keinen Zweck gehabt.
|
||
- Registrierung als `BackgroundService` wie `WatchdogHeartbeatService` (rein ereignisgesteuert,
|
||
`ExecuteAsync` wartet nur auf Abbruch) – konsistent mit dem etablierten Muster, obwohl kein
|
||
eigener Takt gebraucht wird.
|
||
- Konfiguration über `DeploymentcenterOptions.Token`/`.ErrorReportingEnabled` (aus
|
||
`appsettings.Local.json`) – anders als beim Watchdog gibt es hierfür keine bestehende
|
||
UI-Heimat in `ServerSettings`, 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. `internal` gemachter 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.props` mit **einer** `<Version>` für alle Projekte (D-8).
|
||
- `Deploymentcenter.BuildInfo.targets` nach `build/` übernehmen und einbinden; `BuildInfo.Version`
|
||
ersetzt das heutige `Assembly.GetName().Version` im Heartbeat (D-7).
|
||
- SDK paketieren: `dotnet pack Deploymentcenter.Client -c Release -o lib/nuget`; `nuget.config` um
|
||
das Quellen-Mapping `Deploymentcenter.*` → `local` ergänzen; `LicenseLabrador.Client` samt
|
||
`.nupkg` entfernen.
|
||
- Neuer Konfigurationsabschnitt `Deploymentcenter` in `appsettings.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/ping` und `…/api/watchdog/v1/event`, Header
|
||
`Authorization: Bearer`. TLS-Zwang und Loopback-Ausnahme bleiben.
|
||
- Payload ergänzen: `version` (aus `BuildInfo`), `os`, `group`.
|
||
- `checks` fü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). Ein `ok`-Heartbeat mit fehlschlagender Prüfung wird serverseitig auf `warning` gestuft.
|
||
- `metrics` schlank 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 bestehenden
|
||
`stopped_graceful`-Event) — das ist der vom Evaluator ausgewertete Weg.
|
||
- Konfiguration aus `DeploymentcenterOptions`; die alten `Watchdog*`-Felder aus `ServerSettings`
|
||
entfernen (mit einmaliger Übernahme beim Start, damit niemand neu tippt).
|
||
- `WatchdogHeartbeatTests` nachziehen.
|
||
|
||
**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
|
||
|
||
- `LicenseGate` auf `Deploymentcenter.Client.LicenseClient` umbauen:
|
||
`ValidateAsync(slug, key, baseUrl, appVersion)`, Ergebnis `LicenseValidationResult`.
|
||
`LicenseClient.DefaultAppVersion = BuildInfo.Version` einmal 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.cs` und
|
||
`Ui/LicenseDialog.cs` werden mit D-6 gelöscht, nicht portiert.
|
||
- CLI-Schalter `--license-status`, `--license-set-key`, `--license-deactivate` nach 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
|
||
|
||
- `DeploymentcenterErrorReporter` in `PolyTrader.Core`: `POST /api/errors/v1/report`, Scope
|
||
`bugtracker: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_ref` je 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)](../../../src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml):
|
||
fire-and-forget, nur aktiv bei `Deploymentcenter:UpdateCheckEnabled` (Vorgabe `false`), nutzt
|
||
`ReleaseCredentials.FromLicenseKey(...)` mit dem entschlüsselten `ServerSettings.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.Unauthorized` wird 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 den `excludePatterns`/`preservePatterns`
|
||
aus 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-Zugangsdaten
|
||
+ `updateservice: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ür `polytrader` aktuell
|
||
**kein** Release auf dem Server liegt (die `2.0.1`-Zeile in `sql/schema.sql` ist 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/pubkey` liefert einen
|
||
RSA-Key (Fingerprint `ed99a38d…`) — die zuvor offene Checkliste-Voraussetzung war in Wahrheit
|
||
schon erfüllt.
|
||
- `dotnet publish -c Release --self-contained true` für `win-x64` (124 MB) und `linux-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 — die `CopyToOutputDirectory`-Regel in der `.csproj`, die genau für diesen
|
||
Fall in D-6 der Befunde und im eigenen `packager.config.example.json`-Kommentar dokumentiert ist.
|
||
Vor jedem weiteren Schritt von Hand entfernt, dann zusätzlich mit einem eigenen Grep nach
|
||
gefüllten `Password=`-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 aus `Deploymentcenter/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 Verzeichnisse `clawddotnet/` und `predictalytics/`
|
||
echter Nachbarprojekte. `ftpRemoteBaseDir: "/releases"` entsprechend gesetzt.
|
||
- Ein `updateservice:publish`-Token separat provisioniert (`environment: "all"`, wie schon bei D-3
|
||
gelernt) — nicht das Dev-Token aus `appsettings.Local.json` wiederverwendet, das hat nur
|
||
`watchdog:ping`/`bugtracker:report`.
|
||
- **Beide Releases veröffentlicht** über `pack-and-deploy`: `polytrader 0.1.0 (prod, win-x64)` und
|
||
`polytrader 0.1.0 (prod, linux-x64)`. Versionsgegenprobe lief automatisch durch (Assembly-Version
|
||
stimmte mit `--version` überein). `appsettings.json` korrekt als „GESCHÜTZT" (preservePatterns)
|
||
markiert, `*.pdb` je Projekt korrekt ausgeschlossen.
|
||
|
||
**Live-Abnahme (20.08.2026):**
|
||
|
||
- [x] `GET /api/updateservice/v1/check?product=polytrader&version=0.0.0&channel=prod&platform=win-x64`
|
||
**und** `...&platform=linux-x64` → beide `update_available: true, latest_version: "0.1.0"`.
|
||
- [x] Beide Antworten enthalten ein gefülltes `manifest_signature`-Feld — signiert.
|
||
- [x] Zugangsschutz: **nicht sofort aktiv** — `curl -I .../package.tar.gz` lieferte direkt nach dem
|
||
Publish noch `200` statt `401`. Deckt sich mit der Dokumentation: für ein *neues* Produkt
|
||
entsteht `/releases/polytrader/` erst mit dem ersten Upload, der Schutz erst mit dem
|
||
nächsten `cli/tick.php`-Durchlauf. Per Hintergrund-Poll auf `401` gewartet statt einfach
|
||
anzunehmen, dass er greift — nach rund zwei Minuten aktiv geworden.
|
||
- [x] **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`; `.htpasswd` direkt abgerufen → `403`.
|
||
|
||
Reihenfolge ist hier bindend — siehe D-5.
|
||
|
||
**Ursprüngliche Planung (jetzt umgesetzt, s.o.):**
|
||
|
||
1. `packager.config.json` mit strikten `excludePatterns` (`appsettings.Local.json`, `master.key`,
|
||
`openrouter.key`, `server_settings.xml`, `*.db`, `Logs/**`, `*.pdb`) und `preservePatterns`
|
||
(`appsettings.json`).
|
||
2. Sauberes Publish-Verzeichnis je Plattform (`win-x64`, `linux-x64`), **nicht** das Debug-Output.
|
||
3. 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).
|
||
4. **Erst danach** das erste Release veröffentlichen.
|
||
5. 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:
|
||
|
||
```bash
|
||
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](../../../src/PolyTrader.App.Avalonia/setup.json) im App-Projekt (per `CopyToOutputDirectory`
|
||
im Publish-Paket) mit zwei Zielen: `appsettings.json` (Deploymentcenter-Block: `BaseUrl` via
|
||
`detect:baseurl`, `ProjectSlug` via `detect:project`, `Channel`, `Token` via `provision` mit den
|
||
Scopes `watchdog:ping`+`bugtracker:report`) und `appsettings.Local.json` (`Database:MySqlConnectionString`
|
||
als `secret`).
|
||
**Korrektur gegenüber der ursprünglichen Planung:** kein `Watchdog:Source`-Feld — die
|
||
Watchdog-Konfiguration liegt seit D-1 bewusst in `server_settings.xml` (maskierte UI,
|
||
Live-Reload), und `setup.json` kann nur `json`/`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). Im `setup.json`-Kommentar dokumentiert, damit das
|
||
nicht als vergessenes Feld missverstanden wird.
|
||
- **Master-Key-Erzeugung (D-10)** in [MasterKeyResolver.cs](../../../src/PolyTrader.Core/Security/MasterKeyResolver.cs)
|
||
(neu, testbar in `PolyTrader.Core` statt direkt in `Program.cs` verdrahtet) + gemeinsamer
|
||
[FilePermissions.cs](../../../src/PolyTrader.Core/Security/FilePermissions.cs)-Helfer (ersetzt die
|
||
bisher in `SettingsWindow.axaml.cs` duplizierte `RestrictToOwner`-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 ohne `master.key` →
|
||
`--smoke-ui`-Lauf erzeugt die Datei mit der erwarteten Log-Meldung, `SecretProtection` aktiviert
|
||
sich sofort danach in derselben Sitzung.
|
||
- [polytrader.service](../../../deploy/polytrader.service) (systemd-Unit-Vorlage) löst **D-11**: setzt
|
||
`WorkingDirectory` ausdrücklich auf das Installationsverzeichnis, mit Begründung im Kommentar
|
||
(sonst laden `server_settings.xml`, relativ zum Arbeitsverzeichnis, und `master.key`, relativ zu
|
||
`AppContext.BaseDirectory`, aus unterschiedlichen Orten). `SIGTERM` mit 45 s Frist für den
|
||
geordneten Shutdown-Pfad statt `SIGKILL`.
|
||
|
||
**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 install` auf 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):
|
||
`ConfigureSecretProtection` legt, wenn weder `POLYTRADER_MASTER_KEY` noch `master.key` vorliegen,
|
||
einen kryptografisch zufälligen Schlüssel an und schreibt ihn mit `File.SetUnixFileMode(…,
|
||
UserRead | UserWrite)` — sonst entsteht er unter Linux mit `644`, 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.key` gehört in `excludePatterns`** (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 ✅ Code-seitig erledigt (22.08.2026), Serverabschaltung offen
|
||
|
||
Entscheidung Richard (14.08.2026): kein Parallelbetrieb. Läuft **nach** D-1 und D-2, damit zu
|
||
keinem Zeitpunkt beides fehlt. Umgesetzt im Frühjahrsputz vom 22.08.2026, gemeinsam mit dem
|
||
WinForms-Ausbau (P11/L5) — beide Schnitte betrafen dieselben Dateien.
|
||
|
||
- [x] `LicenseLabrador.Client` aus `lib/nuget` entfernt; `PolyTrader.App.csproj` existiert nicht
|
||
mehr. Quellen-Mapping in `NuGet.Config` trägt nur noch `Deploymentcenter.*`, `lib/nuget/README.md`
|
||
beschreibt jetzt das Deploymentcenter-SDK.
|
||
- [x] `Licensing/LicenseGate.cs` und `Ui/LicenseDialog.cs` gelöscht — ersetzt durch die
|
||
Avalonia-Fassung aus D-2.
|
||
- [x] Beide Altpläne als **abgelöst** gekennzeichnet;
|
||
[ANALYSE-Linux-Portierung.md](../ANALYSE-Linux-Portierung.md) auf Revision 6 gezogen (P11/L5
|
||
erledigt), Leitfaden-Abschnitte B und C aufgelöst.
|
||
- [x] **Abweichung vom Plan, bewusst:** Die `Watchdog*`-Felder in `ServerSettings` bleiben stehen.
|
||
Die Planzeile „alte Watchdog-Felder entfernen" stammt aus der Zeit vor D-1 — D-1 hat die Felder
|
||
jedoch **umgewidmet** (sie zeigen jetzt auf die Deploymentcenter-API) statt sie zu ersetzen. Sie
|
||
werden von `WatchdogHeartbeatService`, `DeploymentcenterOptions`, dem Einstellungsfenster und den
|
||
Tests aktiv benutzt; ein Entfernen wäre ein Rückschritt gewesen.
|
||
- [ ] **Serverseitig offen:** `watchdog.mhdf.de` und `license.mhdf.de` abschalten, 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-Kriterium erfüllt:** Die Solution baut ohne LicenseLabrador-Paket (0 Fehler, 476 Tests
|
||
grün), und `grep -ri licenselabrador` findet im Code nur noch historische Erwähnungen in
|
||
Kommentaren und Plandokumenten.
|
||
|
||
**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)
|
||
|
||
- [x] Projekt `polytrader` im Deploymentcenter angelegt (Slug festgelegt, id 2, aus dem Seed-Bestand)
|
||
- [x] `cli/tick.php` läuft als Cron — `/api/health` meldet `checks.evaluator.ok: true` (14.08.2026 geprüft)
|
||
- [x] Master-Token vorhanden; Dev-Sub-Token für `polytrader` provisioniert (Scopes `watchdog:ping`,
|
||
`bugtracker:report`) — für den Produktivbetrieb ein eigenes Anwendungstoken je Installation
|
||
nachziehen, siehe Leitentscheidung 8
|
||
- [ ] Release-Signierschlüssel hinterlegt (`/api/updateservice/v1/pubkey` antwortet) — vor D-4
|
||
- [x] `max_activations` für `polytrader` großzügig gesetzt (Richard, vor 15.08.2026: Dev-Lizenz mit
|
||
9999 Aktivierungen) — `default_cache_ttl_hours` ungeprü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:
|
||
|
||
1. 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.
|
||
2. Der Zugangsschutz (§5A des UpdateService-Guides) verändert `.htaccess`/`.htpasswd` in
|
||
`/releases/` — einem Verzeichnisbaum, den auch andere Produkte nutzen.
|
||
3. 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).
|