Arbeitsstand gesichert: Deploymentcenter-Integration (D-0 bis D-5) und Einfenster-Shell
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>
This commit is contained in:
@@ -7,6 +7,15 @@
|
||||
> **Lizenzverwaltung, Watchdog und Error-Reporting** grundlegend. Alles, was in diesem Dokument zu
|
||||
> LicenseLabrador oder zum Watchdog-Heartbeat steht, ist damit Übergangszustand — dort nichts mehr
|
||||
> ausbauen.
|
||||
>
|
||||
> **P3c hat seit dem 14.08.2026 einen eigenen Plan:**
|
||||
> **[UMSETZUNGSPLAN-Deploymentcenter-Integration.md](./umsetzungsplaene/UMSETZUNGSPLAN-Deploymentcenter-Integration.md)**
|
||||
> — er ist ab jetzt maßgeblich und korrigiert zwei Aussagen dieses Dokuments:
|
||||
> §5.2 unterschätzt die Watchdog-Umstellung („nur Header und URL"; tatsächlich kommen `version`,
|
||||
> `os`, `checks`, `metrics` und `status: "stopped"` dazu), und §5.2 wie Risiko **R6** stützen die
|
||||
> headless-Lizenzprüfung auf `ILicensePrompt` aus dem SDK — diese Schnittstelle wird dort jedoch
|
||||
> **nirgends benutzt** und trägt nichts. Der Umfang liegt bei **4–7 PT** statt 2–4 PT, weil
|
||||
> Error-Reporting, Auslieferung und Erstinstallation hier noch nicht enthalten waren.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,816 @@
|
||||
# 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
|
||||
|
||||
Entscheidung Richard (14.08.2026): kein Parallelbetrieb. Läuft **nach** D-1 und D-2, damit zu
|
||||
keinem Zeitpunkt beides fehlt.
|
||||
|
||||
- `LicenseLabrador.Client` aus `PolyTrader.App.csproj` und `lib/nuget` entfernen, Quellen-Mapping
|
||||
in `nuget.config` von `LicenseLabrador.*` auf `Deploymentcenter.*` umstellen.
|
||||
- `Licensing/LicenseGate.cs`, `Ui/LicenseDialog.cs` (+ `.Designer.cs`) löschen — ersetzt durch die
|
||||
Avalonia-Fassung aus D-2.
|
||||
- Alte `Watchdog*`-Felder aus `ServerSettings` und 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: `ILicensePrompt` trägt die headless-Story nicht).
|
||||
- Serverseitig: `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:** 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).
|
||||
Reference in New Issue
Block a user