Files
Deploymentcenter/docs/UPGRADE.md
T
Deploymentcenter BotandClaude Opus 5 2388b5abe1 feat(updateservice): Plattform-Dimension, signierte Releases, Update mit Rollback
Behebt eine Reihe zusammenhaengender Fehler im Update-Weg, die zusammen
verhindert haben, fuer mehr als eine Plattform auszuliefern - und die im
Fehlerfall halb aktualisierte Installationen hinterliessen.

Server
- Migration 009: Spalte platform samt neuem Unique-Key. Zuvor verdraengte das
  zuletzt veroeffentlichte Paket alle anderen Plattformen derselben Version,
  weil ON DUPLICATE KEY auf (slug, version, channel) griff. Ein Linux-System
  zog sich damit das Windows-Paket.
- Aufloesungsregel: je Version das plattformgenaue Paket, sonst das
  plattformunabhaengige. Ein Client ohne Plattformangabe sieht ausschliesslich
  'any' - lieber kein Update als das falsche.
- manifest_json wird endlich befuellt; die Spalte blieb bisher immer leer,
  wodurch die API nie der Rueckfall sein konnte, als der sie gedacht war.
- Releases werden serverseitig mit RSA-SHA256 signiert, neuer Endpunkt
  /api/updateservice/v1/pubkey. Bewusst kein HMAC: der Pruefende laeuft auf
  fremden Systemen und darf den Signierschluessel nicht besitzen.

Packager
- Bricht ab, statt die Versionshistorie zu verlieren. Schlug das Lesen der
  bestehenden latest.json fehl, ersetzte ein leeres catch die komplette
  Historie durch einen einzigen Eintrag - ohne jede Meldung.
- Echte Glob-Muster. Zuvor trafen "logs/**" und "scratch/**" aus der
  mitgelieferten Beispielkonfiguration nie zu.
- preservePatterns: Konfigurationsvorlagen werden ausgeliefert, ersetzen am
  Ziel aber keine vorhandene Datei. Eine settings.json mit Zugangsdaten
  ueberschrieb bisher beim Update die Konfiguration jedes Zielsystems.
- Warnt vor Dateien, die nach Zugangsdaten aussehen und auf keiner Liste stehen.
- Prueft --version gegen die Hauptassembly. Eine Abweichung fuehrte zu einer
  Endlosschleife: Clients aktualisieren, melden weiter die alte Version,
  halten das Release erneut fuer neu.
- --platform mit Ableitung aus dem Publish-Pfad.

Agent
- Anwenden mit Plan, Backup und vollstaendigem Rollback. Die Stelle war als
  "Atomic Replace with Backup" kommentiert und war eine Kopierschleife.
- Verwaiste Dateien werden entfernt, aber nur solche aus dem Manifest der
  Vorversion. Was nicht aus einem Release stammt, bleibt liegen.
- Das laufende Agent-Binary wird zur Seite gelegt statt ueberschrieben.
- API-Rueckfall in FetchManifestAsync; bisher nur im SDK vorhanden, weshalb
  die Anwendung "Update verfuegbar" und der Agent "kein Release" sagen konnte.
- Installierte Version aus --current-version oder manifest.json statt des
  Textes "Unbekannt", der als 0 gelesen wurde und jede Version neuer erscheinen
  liess. Reparatur funktioniert damit auch ohne manifest.json.
- Setzt das Ausfuehrungsbit fuer Linux-Pakete, die unter Windows gebaut wurden.

SDK
- ResolveAgentPath() liefert den plattformrichtigen Namen; ein fest verdrahtetes
  "update-agent.exe" wird unter Linux nie gefunden.
- LaunchUpdateAgent uebergibt jetzt --restart (wurde nie uebergeben, die
  Anwendung blieb nach dem Update zu), --wait-for-pid (kein Wettlauf mehr mit
  dem Herunterfahren) und --platform.

Enthaelt ausserdem die bislang nicht committete Arbeit an Watchdog, Lizenz-
Client und cli/tick.php samt Migration 008; die betroffenen Dateien liessen
sich nicht getrennt stagen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 19:56:35 +02:00

499 lines
20 KiB
Markdown

# Umstellung auf Version 2.0 — Ablaufplan
Diese Fassung enthält Sicherheitskorrekturen, die das Verhalten der
Schnittstellen ändern. Bitte in dieser Reihenfolge vorgehen.
> **Version 2.1** ergänzt Korrekturen im .NET-SDK und ein neues Feld am
> Heartbeat. Sie erfordert keine Konfigurationsänderung, aber eine
> Datenbankmigration und ein Nachziehen der Clients:
> **[§14 Umstellung auf 2.1](#14-umstellung-auf-21)**.
>
> **Version 2.2** überarbeitet den UpdateService grundlegend: Plattform-Dimension,
> signierte Releases, Staging-Swap mit Rollback und geschützte Konfigurationsdateien.
> **Vor dem nächsten Release einzuspielen** —
> **[§15 Umstellung auf 2.2](#15-umstellung-auf-22)**.
---
## 1. Vor dem Deployment: Zugangsdaten wechseln
`Serverdaten.txt`, `config/config.php`, `config/.htpasswd` und
`scripts/deploy_config.json` lagen im Git-Repository. Sie sind jetzt per
`.gitignore` ausgeschlossen und aus dem Index entfernt — **die Git-Historie
enthält sie aber weiterhin**. Alle betroffenen Zugangsdaten sind daher als
kompromittiert zu behandeln:
- [ ] MySQL-Passwort ändern, danach in `config/config.php` eintragen
- [ ] FTP-Passwort ändern, danach in `scripts/deploy_config.json` eintragen
- [ ] `.htpasswd`-Passwort für `deploy` neu setzen
- [ ] Git-Token `eb429575…` widerrufen und neu ausstellen
- [ ] Admin-Passwort im WebUI ändern (die alte Fassung setzte es bei jedem
Aufruf von `install_db.php` auf `Admin1337!` zurück — jeder im Internet
konnte das auslösen)
Wenn die Historie bereinigt werden soll, geht das mit
[`git filter-repo`](https://github.com/newren/git-filter-repo). Das schreibt
alle Commit-Hashes um; bei einem Repository mit mehreren Nutzern vorher abstimmen.
---
## 2. Konfiguration ergänzen
`config/config.php` braucht drei neue Schlüssel unter `security`. Die
mitgelieferte Datei enthält bereits erzeugte Werte; für eine neue Installation:
```bash
cp config/config.example.php config/config.php
openssl rand -hex 32 # je einmal für shared_key, webhook_key, license_key
```
| Schlüssel | Zweck |
|---|---|
| `security.shared_key` | Server-zu-Server-Aufrufe: Evaluator-Cron, Migration, Deaktivierung |
| `security.webhook_key` | HMAC-Signatur ausgehender Webhooks |
| `security.license_key` | Signatur der Offline-Lizenzdateien (`.lic`) |
| `app.debug` | Auf Produktivsystemen `false` — steuert, ob Exception-Texte ausgeliefert werden |
> Der bisherige `shared_key` (`DC_MASTER_SECURE_TOKEN_2026_x98f`) stand im
> Repository und wurde ersetzt. Wer ihn irgendwo eingetragen hat — etwa für
> `/api/license/v1/deactivate` — muss den neuen Wert nachziehen.
---
## 3. Deployment
```bash
python scripts/deploy.py
```
Das Skript überträgt unter anderem die neuen Verzeichnisse `var/` (Logs) und
die zusätzlichen `.htaccess`-Dateien in `config/`, `src/` und `sql/`.
---
## 4. Migration ausführen
Im WebUI anmelden, dann **System → DB-Migration → Migration jetzt ausführen**.
Alternativ über die Kommandozeile:
```bash
php public/install_db.php
```
Oder mit dem Shared Key:
```bash
curl -H "Authorization: Bearer <SHARED_KEY>" https://dc.mhdf.de/install_db.php
```
Die Migration ist additiv und legt an bzw. korrigiert:
- `dc_migrations` — vermerkt angewendete Versionen, damit nichts doppelt läuft
- `dc_login_attempts` — Drosselung fehlgeschlagener Anmeldungen
- `dc_webhooks` — ausgehende Benachrichtigungen
- Bugtracker: Claim/Lease, `client_ref`, `dedup_key`, Code-Kontextfelder, Indizes
- Watchdog: `last_state_change_utc`, `down_since_utc`, Zustand `unknown`
- **Reparatur von Migration 005** — deren Spalten (`push_id`, `target_agent`,
`tags`) fehlten bisher auf Datenbanken, die aus Migration 004 stammen. Die
alte Fassung nutzte dynamisches SQL, dessen Semikolons in String-Literalen
vom damaligen Installer als Statement-Ende gelesen wurden; die Fehler wurden
stillschweigend verschluckt.
- **Reparatur der Token-Hashes** — die Validierung vergleicht jetzt nur noch
den SHA-256-Hash. Die geseedeten Beispiel-Tokens trugen Hashes, die nicht zu
ihrem Klartext passten; sie werden korrigiert, damit bestehende Tokens
weiterhin funktionieren.
---
## 5. Cron für den Watchdog-Evaluator einrichten
**Ohne diesen Schritt sind die Monitor-Zustände wertlos.** Der Evaluator fehlte
bisher vollständig — der Zustand änderte sich nur beim Eintreffen eines
Heartbeats, ein ausgefallener Server blieb dauerhaft grün.
### Empfohlen: interner Aufruf (seit 2.1)
```bash
* * * * * /usr/bin/php /pfad/zum/deploymentcenter/cli/tick.php --quiet
```
Das entspricht dem früheren `watchdog/cli/tick.php`. Gegenüber dem HTTP-Weg:
- **Kein Schlüssel im Crontab.** `ps` zeigt die Argumente laufender Prozesse
jedem Nutzer der Maschine — bei einem `curl -H "Authorization: Bearer …"`
also auch den Shared Key.
- **Keine Abhängigkeit von Webserver, TLS oder DNS.** Der Evaluator läuft auch
dann, wenn der Webserver klemmt — also gerade dann, wenn das Erkennen von
Ausfällen wichtig ist.
- **Keine überlappenden Läufe.** Das Skript sperrt sich per `flock` selbst; ein
langsamer Vorlauf führt nicht zu doppelten Alarmen.
`--quiet` gibt nur bei Zustandswechseln und Fehlern etwas aus — sonst erzeugt
ein minütlicher Job 1440 Cron-Mails am Tag. Rückgabewerte: `0` erfolgreich,
`1` Fehler, `2` übersprungen (es lief bereits ein Tick).
### Alternativ: über die Schnittstelle
Sinnvoll, wenn der Cron auf einer anderen Maschine läuft als die Anwendung:
```bash
* * * * * curl -fsS -H "Authorization: Bearer <SHARED_KEY>" https://dc.mhdf.de/api/watchdog/v1/evaluate > /dev/null
```
Beide Wege rufen denselben Code auf und schreiben denselben Lauf-Vermerk in
`watchdog_cron_jobs` — nur einen von beiden einrichten.
Solange der Job fehlt, zeigt das WebUI oben einen Warnhinweis mit einer
Schaltfläche für einen einmaligen Lauf.
---
## 6. Agenten-Tokens ausstellen
Die Ingest-Endpunkte verlangen jetzt zwingend ein Token.
1. WebUI → **Token-Verwaltung → Master-Token erstellen**
2. Scopes wählen (für einen Coding-Agenten: `bugtracker:report`,
`bugtracker:read`, `bugtracker:manage`)
3. Master-Token einmalig kopieren und auf dem Agenten-Rechner als `DC_TOKEN`
hinterlegen — oder den Agenten per `/api/tokens/v1/provision` ein eigenes
Sub-Token ziehen lassen
---
## 7. Bestehende Integrationen anpassen
| Betroffen | Was zu tun ist |
|---|---|
| Aufrufe von `/api/bugtracker/v1/report` ohne Token | Token-Header ergänzen |
| Aufrufe von `/api/bugtracker/v1/projects` ohne Token | Token-Header ergänzen |
| Skripte, die Releases veröffentlichen | Token mit `updateservice:publish` ergänzen |
| Auswertung der Antworten | Neues Format: `{"status":"success",…}` bzw. `{"status":"error","error":{"code":…}}` |
| Watchdog-Agenten mit `wd_live_…`-Token | Laufen unverändert weiter |
| Clients, die `/api/updateservice/v1/check` aufrufen | Unverändert, weiterhin ohne Token |
| Clients, die `/api/license/v1/validate` aufrufen | Unverändert, weiterhin ohne Token |
---
## 8. Zeitzonen
Datenbankzeitstempel liegen jetzt durchgängig in **UTC**; das WebUI rechnet für
die Anzeige in `app.timezone` (Europe/Berlin) um. Vorhandene Datensätze wurden
in Serverzeit geschrieben und erscheinen daher einmalig um den Zeitzonenversatz
verschoben. Für Monitoring-Daten ist das ohne Bedeutung, für den Audit-Log
gegebenenfalls beachten.
---
## 9. Prüfen, ob alles läuft
```bash
curl https://dc.mhdf.de/api/health -H "Authorization: Bearer <SHARED_KEY>"
```
Erwartet wird `"healthy": true`, eine leere `schema.pending`-Liste und ein
`checks.evaluator.ok` von `true`.
Zusätzlich stichprobenartig im WebUI prüfen:
- [ ] Anmeldung funktioniert
- [ ] Projekt anlegen und wieder löschen
- [ ] Monitor bearbeiten — das Feld **Betriebssystem** bleibt nach dem
Speichern erhalten (wurde zuvor bei jedem Speichern geleert)
- [ ] Bugtracker: „🔍 Details" öffnet den Dialog, „✔" öffnet den
Lösen-Dialog (dessen HTML fehlte bisher komplett)
- [ ] Token widerrufen und löschen (warf zuvor `HY093`)
- [ ] Lizenz-Aktivierung freigeben (warf zuvor `Class "Audit" not found`)
- [ ] Nach dem Speichern F5 drücken — es entsteht kein zweiter Eintrag mehr
---
## 10. Fehler-Stream (Migration 007)
Neu ist eine eigene Schnittstelle für Laufzeitfehler, gedacht für den globalen
Exception-Handler einer Anwendung:
```
POST /api/errors/v1/report
```
Gespeichert wird in derselben Tabelle wie der Bugtracker — ein zweiter Speicher
wäre nur ein zweiter Ort, an dem man suchen müsste. Die Trennung von Rauschen
und Signal leisten stattdessen **Ignore-Regeln**.
### Bekannte, harmlose Fehler stummschalten
Im WebUI unter **Bugtracker → 🔇 Ignore-Regeln**. Ein Treffer bedeutet nicht
„wegwerfen": Der Fehler wird weiterhin erfasst und hochgezählt, bleibt aber aus
der Übersicht heraus und löst keine Benachrichtigung aus.
Der Zähler ist dabei der eigentliche Zweck. Zu jeder Regel lässt sich eine
Alarmschwelle hinterlegen — dass ein bekannter Fehler auftritt, ist normal;
dass er plötzlich hundertmal so oft auftritt, bedeutet, dass sich etwas
geändert hat.
Eine deaktivierte Vorlage für den Duplicate-Entry-Fall liegt bereits vor; sie
lässt sich im WebUI anpassen und einschalten.
### Gruppierung
Veränderliche Anteile werden beim Zusammenfassen ausgeblendet: Werte in
Anführungszeichen, die Ziffern enthalten, Speicheradressen, GUIDs, Zeitstempel
und Zeilennummern. `Duplicate entry 'MKT-88213'` und `Duplicate entry 'AA-1'`
landen damit in einer Gruppe — ein anderer Unique-Key dagegen nicht.
> **Hinweis:** Migration 007 ändert die Berechnung des Gruppenschlüssels.
> Bereits erfasste Einträge behalten ihren alten Schlüssel; ein erneut
> auftretender Fehler legt daher einmalig eine neue Gruppe an. Danach ist der
> Zustand wieder konsistent.
## 11. Metrik-Verlauf und Health-Checks
Numerische Werte aus dem Heartbeat-Feld `metrics` werden jetzt mit Zeitstempel
abgelegt (Aufbewahrung 14 Tage) und lassen sich über
`GET /api/watchdog/v1/metrics` abfragen. Zuvor wurde `metrics_json` bei jedem
Heartbeat überschrieben — es gab immer nur den letzten Moment.
Zusätzlich kann eine Anwendung ihren Gesundheitszustand selbst mitschicken:
```json
{ "source": "polytrader-worker", "status": "ok", "interval": 60,
"checks": {
"db": { "ok": true },
"market_feed": { "ok": false, "message": "Letzter Tick vor 14 min" }
} }
```
Das Deploymentcenter interpretiert die Namen nicht — es liest nur `ok` und
`message`. Schlägt eine Prüfung fehl, wird ein als `ok` gemeldeter Heartbeat auf
`warning` herabgestuft. **Es müssen keine Ports geöffnet werden**, der Weg ist
ausgehend.
Neu sind außerdem die Statuswerte `stopped` und `maintenance`. Ohne sie erzeugte
jedes geplante Herunterfahren wenige Minuten später einen Fehlalarm — es gab
schlicht keinen Weg, ein beabsichtigtes Ende mitzuteilen:
```json
{ "source": "polytrader-worker", "status": "stopped",
"message": "Dienst planmäßig beendet" }
```
Der Evaluator lässt solche Monitore in Ruhe, bis wieder ein normaler Heartbeat
eintrifft.
## 12. Abhängigkeitsbewusste Alarmierung
Fällt ein Monitor aus, für den `parent_source` gesetzt ist, und ist der
übergeordnete Monitor selbst unten, wird der Alarm für das Kind unterdrückt.
Der Zustand bleibt im Dashboard sichtbar.
Vorher erzeugte ein ausgefallener Hypervisor mit zwölf VMs dreizehn Meldungen
für ein Problem.
Damit das greift, muss die Hierarchie gepflegt sein — im WebUI unter
**WatchDog → System-Hierarchie** über das Feld *Übergeordnete Entität*.
## 13. Optional: Webhooks
Ereignisgesteuerte Benachrichtigung statt Polling. Ziel direkt in der Datenbank
eintragen:
```sql
INSERT INTO dc_webhooks (name, url, project_slug, events, secret, enabled)
VALUES ('Telegram Alarm', 'https://n8n.example.com/webhook/dc',
NULL, 'bug.critical,monitor.down', 'geheimnis', 1);
```
Verfügbare Ereignisse: `bug.created`, `bug.critical`, `bug.resolved`,
`feature.created`, `monitor.down`, `monitor.recovered`, `release.published`,
oder `*` für alle.
Jede Zustellung trägt eine Signatur:
```
X-DC-Timestamp: 1754563200
X-DC-Signature: sha256=<hex(hmac_sha256(secret, timestamp + "." + body))>
```
Nach 20 Fehlversuchen in Folge deaktiviert sich ein Webhook selbst.
---
## 14. Umstellung auf 2.1
Diese Fassung korrigiert Fehler im .NET-SDK, ergänzt ein Feld am Heartbeat und
vervollständigt die OpenAPI-Beschreibung. **Keine Konfigurationsänderung nötig.**
### 14.1 Serverseitig
- [ ] Migration **008** ausführen (Spalte `app_version` in `watchdog_monitors`).
Läuft wie die übrigen über den Migrator, siehe [§4](#4-migration-ausführen).
- [ ] `/api/openapi.json` abrufen und prüfen, dass die Lizenz-Endpunkte und
`/api/watchdog/v1/event`, `/events`, `/status` jetzt enthalten sind.
Bestehende Agenten laufen unverändert weiter; alle neuen Felder sind optional.
### 14.2 Clientseitig (`Deploymentcenter.Client`)
Die folgenden Änderungen betreffen jede bestehende Integration:
| Änderung | Was zu tun ist |
|---|---|
| `unknown_error` entfällt | Wer diesen Status abfängt, prüft stattdessen `IsTransient` |
| Cache-Rückfall bei jedem HTTP-Fehler | Eigene Behelfslösungen für 429/5xx können entfallen |
| `cache_ttl_hours` wird ausgewertet | Offline-Gnadenfrist prüfen: Vorgabe je Projekt im WebUI |
| `app_version` ist Parameter | `LicenseClient.DefaultAppVersion` beim Start setzen |
| `X-Watchdog-Key` entfällt bei `DeactivateAsync` | Nur noch `Authorization: Bearer` |
| `BuildInfo.targets` erzeugt in `$(RootNamespace)` | Import ist jetzt möglich; ggf. `using` entfernen |
| API-Rückfall der Update-Prüfung liefert vollständige Daten | Eigene Umgehungen können entfallen |
Ausführlich: **[LICENSE_INTEGRATION_GUIDE §6](./LICENSE_INTEGRATION_GUIDE.md)**
und **[UPDATESERVICE_INTEGRATION_GUIDE §2B/§2C](./UPDATESERVICE_INTEGRATION_GUIDE.md)**.
### 14.3 Cache-Schema
`state.dat` trägt jetzt Schema 3. Schema 2 wird weiterhin gelesen — ein
SDK-Wechsel erzwingt **keine** erneute Online-Prüfung. Ein Rückschritt auf ein
älteres SDK dagegen schon: das kennt Schema 3 nicht und behandelt den Cache als
Fehltreffer.
---
## 15. Umstellung auf 2.2
Diese Fassung behebt eine Reihe von Fehlern im Update-Weg, die zusammengenommen
verhindert haben, dass für mehr als eine Plattform ausgeliefert werden konnte —
und die im Fehlerfall halb aktualisierte Installationen hinterließen.
**Diese Umstellung gehört vor das nächste Release**, nicht danach.
### 15.1 Was sich ändert
| Bereich | Vorher | Jetzt |
|---|---|---|
| Plattformen | nicht vorhanden — `win-x64` und `linux-x64` überschrieben sich | eigene Dimension `platform`, getrennte Ablage |
| Konfigurationsdateien | wurden beim Update überschrieben | `preservePatterns` schützt sie |
| Ausschlussmuster | nur `*.endung` und exakte Namen | echte Globs (`**`, `*`, `?`) |
| Abbruch beim Anwenden | halb aktualisierte Installation | Backup, automatischer Rollback |
| Entfernte Dateien | blieben für immer liegen | werden aufgeräumt |
| Agent im Paket | überschrieb sich selbst | wird zur Seite gelegt und ersetzt |
| Übergabe an den Agenten | Wettlauf, kein Neustart | PID-Handschlag und `--restart` |
| Versionsangabe | ungeprüft | wird gegen die Assembly gehalten |
| Paketherkunft | nur SHA256 aus derselben Quelle | RSA-Signatur des Servers |
| `manifest_json` | Spalte blieb immer leer | wird befüllt, API ist vollwertiger Rückfall |
### 15.2 Serverseitig
- [ ] Migration **009** ausführen (`platform`, `manifest_signature`, neuer
Unique-Key). Additiv; bestehende Releases erhalten `platform = 'any'` und
bleiben für alle Clients erreichbar.
- [ ] Signierschlüssel erzeugen und in `config/config.php` eintragen:
```bash
openssl genrsa -out /etc/dc/release-signing.pem 2048
chmod 600 /etc/dc/release-signing.pem
chown www-data:www-data /etc/dc/release-signing.pem
```
```php
'release_private_key' => dc_env('DC_RELEASE_SIGNING_KEY', '/etc/dc/release-signing.pem'),
```
- [ ] Prüfen, dass der öffentliche Schlüssel ausgeliefert wird:
```bash
curl https://dc.mhdf.de/api/updateservice/v1/pubkey
```
> **Bewusst RSA und kein HMAC.** Bei einem HMAC braucht der Prüfende denselben
> geheimen Schlüssel wie der Signierende. Der Agent läuft auf fremden Systemen;
> ein dort hinterlegter Schlüssel ließe sich auslesen und zum Signieren
> beliebiger Pakete verwenden — die Signatur wäre wertlos. Beim Lizenzmodul geht
> HMAC auf, weil dort der Server prüft.
Ohne Schlüssel bleibt alles funktionsfähig; Releases sind dann unsigniert, und
der Agent weist bei jedem Update darauf hin.
### 15.3 Packager
- [ ] `packager.config.json` um `preservePatterns` ergänzen (Vorlage in
`packager.config.example.json`).
- [ ] **Prüfen, welche Konfigurationsdateien im Publish-Verzeichnis liegen.**
Der Packager warnt jetzt von sich aus vor Dateien, die nach Zugangsdaten
aussehen und weder ausgeschlossen noch geschützt sind. Diese Warnung ernst
nehmen: eine `settings.json` mit Datenbankpasswort, die bisher mitgeliefert
wurde, hat beim Update die Konfiguration jedes Zielsystems überschrieben.
- [ ] Aufrufe um `--platform` ergänzen:
```bash
pack-and-deploy --project myapp --version 1.4.3 --channel prod \
--platform win-x64 \
--publish-dir ./bin/Release/net8.0/win-x64/publish
```
Ohne `--platform` versucht der Packager, die Kennung aus dem Publish-Pfad
abzuleiten. Gelingt das nicht, gilt das Release als plattformunabhängig und es
erscheint eine Warnung.
- [ ] Die Version wird jetzt gegen die Hauptassembly geprüft. Weicht sie ab,
bricht der Vorgang ab. Das ist der Fall, in dem `<Version>` nur in einem
der beteiligten Projekte steht — der Wert gehört in die
`Directory.Build.props`, damit alle denselben tragen. Notausgang für
bewusste Abweichungen: `--ignore-version-mismatch`.
### 15.4 Clientseitig
`UpdateClient.CheckForUpdateAsync` hat einen zusätzlichen Parameter `platform`
**vor** dem `CancellationToken`. Wer den Token bisher positionell übergeben hat,
bekommt einen Übersetzungsfehler — kein stilles Fehlverhalten.
Der Aufruf des Agenten sollte umgestellt werden:
```csharp
var agentPath = UpdateClient.ResolveAgentPath(); // plattformrichtiger Name
if (agentPath != null && checkResult.UpdateAvailable)
{
UpdateClient.LaunchUpdateAgent(
agentPath: agentPath,
projectId: "myapp",
channel: "prod",
currentVersion: BuildInfo.Version, // für Ziele ohne manifest.json
exitCurrentApp: true); // Neustart und PID-Handschlag
// sind jetzt Vorgabe
}
```
| Änderung | Wirkung |
|---|---|
| `ResolveAgentPath()` | Findet `update-agent` bzw. `update-agent.exe` — ein fest verdrahteter `.exe`-Name wird unter Linux nie gefunden |
| `--restart` wird übergeben | Die Anwendung startet nach dem Update wieder. Bisher blieb sie einfach zu |
| `--wait-for-pid` wird übergeben | Der Agent wartet auf das Ende der Anwendung, statt über gesperrte Dateien zu kopieren |
| `--platform` wird übergeben | Es kann kein Paket einer fremden Plattform mehr gezogen werden |
### 15.5 Den Agenten aus dem Paket nehmen
Liegt `update-agent` im Publish-Verzeichnis, wandert er ins Paket und wird beim
Update mit ausgeliefert. Das ist zulässig — der Agent legt seine laufende Datei
inzwischen zur Seite und ersetzt sie —, aber es lohnt sich, bewusst zu
entscheiden:
- **Mitliefern:** Der Agent aktualisiert sich selbst mit. Die alte Fassung bleibt
als `update-agent.exe.dc-old` liegen und wird beim nächsten Start entfernt.
- **Ausschließen:** `"update-agent*"` in `excludePatterns`. Dann wird der Agent
getrennt gepflegt.
### 15.6 Prüfen
- [ ] `curl "https://dc.mhdf.de/api/updateservice/v1/check?product=myapp&version=0.0.0&channel=prod&platform=win-x64"`
liefert das Windows-Paket, mit `platform=linux-x64` das Linux-Paket.
- [ ] Ohne `platform` kommen ausschließlich Releases mit `platform=any`. Das ist
Absicht: ein Client, der seine Kennung nicht nennt, soll lieber kein Update
bekommen als das falsche.
- [ ] Im WebUI zeigt die Release-Übersicht eine Spalte **Plattform** und
kennzeichnet unsignierte Releases.
- [ ] Ein Update auf einem Testsystem: `appsettings.json` behält danach die
eingerichteten Werte, und eine Datei, die es im neuen Release nicht mehr
gibt, ist verschwunden.