Vier Befunde aus einer externen Durchsicht der 2.4-Integration.
1. Die .htpasswd war eine Klartext-Kundenliste
Das htpasswd-Format hasht nur die Passwortspalte. Benutzername UND Passwort
waren der Lizenzschluessel - der Schluessel stand also im Klartext direkt
neben seinem eigenen bcrypt-Hash, und der Hash war Dekoration. Geschuetzt
hat das Ganze nur die FilesMatch-Regel in derselben Datei.
Der Benutzername wird jetzt abgeleitet: lic_<sha256(schluessel), 16 Hex>.
Die Datei enthaelt damit nur noch eine Einwegableitung und einen Hash ueber
einen hochentropen Schluessel.
Server und SDK muessen dabei zeichengenau uebereinstimmen; ein Test prueft
die C#-Ableitung gegen die PHP-Formel.
2. Ein Formatwechsel blieb unbemerkt liegen
Beim Umbau auf 1. faellt auf: reconcile() sah keinen Anlass zur
Neuerzeugung, die Dateien behielten das alte Format, waehrend die Clients
bereits das neue schickten. Die erzeugten Dateien tragen deshalb jetzt eine
Formatkennung; weicht sie ab, wird neu erzeugt.
3. Doku beschrieb Nginx, der Schutz ist Apache-only
.htaccess wird von Nginx ignoriert - dort waeren die Verzeichnisse offen und
die .htpasswd oeffentlich abrufbar. Die Statusanzeige pruefte nur, ob die
Dateien existieren, und haette in dem Fall "GESCHUETZT" gemeldet.
Neu: ein echter Selbsttest ruft die eigene Paket-Adresse OHNE Zugangsdaten
ab und erwartet 401. Er laeuft beim manuellen Erzeugen und nach jeder
automatischen Neuerzeugung; das Ergebnis steht in der Oberflaeche, ein
Fehlschlag im Log. Er findet nebenbei auch abgeschaltetes AllowOverride und
Tippfehler in der erzeugten Datei. Doku korrigiert, Nginx-Vorlage ergaenzt.
4. Erstinstallation schrieb an einen Ort, an dem Linux-Anwendungen nicht lesen
setup.json-Ziele waren immer installationsrelativ. Eine Anwendung, die sich
unter Linux richtig verhaelt, liest aus $XDG_CONFIG_HOME - /opt/<app> ist
fuer den Dienstbenutzer meist nicht schreibbar. Der Installer legte die
Datei also dorthin, wo nie jemand nachsieht.
Ziele haben jetzt ein "location": install (Vorgabe), config, data, home,
plus ${VAR}- und %VAR%-Ersetzung in "file". Unbekannte Variablen bleiben
stehen statt leer zu werden - ein Platzhalter faellt auf, ein falscher Pfad
nicht. Der Installer gibt den aufgeloesten Pfad aus, weil bei config das
Konto entscheidet, unter dem er laeuft.
Ausserdem
- Doku zeigte "status": "ok" fuer update/delete; Http::ok() erzeugt
"status": "success".
- UPGRADE §16.1 deckte Neuprodukte nicht ab: Fuer ein Produkt ohne Release
existiert /releases/<slug>/ nicht und wird uebersprungen. Das Verzeichnis
entsteht erst mit dem ersten Upload, der naechste Tick schuetzt es. Der erste
ausgelieferte Build muss die Zugangsdaten also schon mitbringen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
591 lines
24 KiB
Markdown
591 lines
24 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)**.
|
|
>
|
|
> **Version 2.4** stellt die Release-Ablage hinter einen Zugangsschutz.
|
|
> **Reihenfolge beachten:** erst das SDK ausliefern, dann scharfschalten —
|
|
> sonst bekommen bestehende Installationen keine Updates mehr.
|
|
> **[§16 Umstellung auf 2.4](#16-umstellung-auf-24)**.
|
|
|
|
---
|
|
|
|
## 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.
|
|
|
|
---
|
|
|
|
## 16. Umstellung auf 2.4
|
|
|
|
Die Release-Ablage liegt jetzt hinter HTTP-Basic-Auth. Zugang hat, wer einen
|
|
gültigen Lizenzschlüssel für das Produkt besitzt — oder ein Installationskonto.
|
|
|
|
Ausführlich: **[UPDATESERVICE_INTEGRATION_GUIDE §5A](./UPDATESERVICE_INTEGRATION_GUIDE.md#5a-zugangsschutz-der-release-verzeichnisse)**
|
|
|
|
### 16.1 Reihenfolge — das ist der kritische Teil
|
|
|
|
> **Erst ausliefern, dann scharfschalten.** Der Zugangsschutz sperrt jede
|
|
> Anwendung aus, die noch mit dem alten SDK gebaut ist: Sie schickt keine
|
|
> Zugangsdaten und bekommt ab dem Moment nur noch 401. Andersherum sperrst du
|
|
> deine eigene Installationsbasis aus.
|
|
|
|
1. [ ] SDK auf 2.4 heben und `licenseKey` an `CheckForUpdateAsync` und
|
|
`LaunchUpdateAgent` übergeben.
|
|
2. [ ] Ein Release mit dem neuen SDK bauen und veröffentlichen.
|
|
3. [ ] Warten, bis die Installationen dieses Release gezogen haben.
|
|
4. [ ] **Erst dann** den Schutz erzeugen — WebUI → *UpdateService →
|
|
🔒 Zugangsschutz → Zugangsschutz jetzt neu erzeugen*.
|
|
|
|
Läuft `cli/tick.php` als Cron, erzeugt es den Schutz beim ersten Lauf nach dem
|
|
Deployment **von selbst**. Wer die Reihenfolge einhalten will, spielt den
|
|
Serverteil also erst dann ein, wenn Schritt 3 erledigt ist.
|
|
|
|
> **Neue Produkte: die Reihenfolge oben gilt für sie nicht.**
|
|
>
|
|
> `ReleaseGuard` überspringt Verzeichnisse, die es nicht gibt — für ein Produkt
|
|
> ohne Release ist `/releases/<slug>/` schlicht nicht vorhanden und damit auch
|
|
> nicht geschützt. Das Verzeichnis entsteht erst mit dem **ersten** Upload, und
|
|
> der nächste `tick.php`-Lauf legt den Schutz darin an.
|
|
>
|
|
> Es gibt also kein Zeitfenster, in dem sich ein noch ungeschütztes Release
|
|
> herunterladen ließe, um dann das SDK nachzuziehen. **Der erste ausgelieferte
|
|
> Build muss die Zugangsdaten bereits mitbringen** — sonst fällt die Tür hinter
|
|
> ihm zu.
|
|
>
|
|
> Für Neuprodukte lautet die Reihenfolge daher schlicht: SDK 2.4 einbinden,
|
|
> `licenseKey` übergeben, *dann* das erste Release veröffentlichen.
|
|
|
|
### 16.2 Was wo eingetragen wird
|
|
|
|
Je Produktverzeichnis eine `.htpasswd` mit den aktiven, nicht abgelaufenen
|
|
Lizenzen dieses Produkts (Benutzername = Passwort = Schlüssel) und allen
|
|
Installationskonten. Bestehende Lizenzen werden dabei automatisch übernommen —
|
|
es ist nichts von Hand nachzutragen.
|
|
|
|
### 16.3 Neue Aufrufe
|
|
|
|
```bash
|
|
update-agent --project myapp --action update \
|
|
--license-key XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
|
|
```
|
|
|
|
```bash
|
|
# Erstinstallation: der Installer fragt die Zugangsdaten ab und benutzt sie
|
|
# auch für den Paketabruf. Der Installer-Download selbst bleibt offen.
|
|
wget -qO- https://dc.mhdf.de/installer/install.sh | sh
|
|
./update-agent --action install
|
|
```
|
|
|
|
### 16.4 Prüfen
|
|
|
|
- [ ] **Selbsttest** im WebUI unter *UpdateService → 🔒 Zugangsschutz* — er ruft
|
|
die eigene Paket-Adresse ohne Zugangsdaten ab und erwartet 401. Die
|
|
Anzeige „GESCHÜTZT" allein beweist nichts: Sie prüft nur, ob die Dateien
|
|
existieren, nicht ob der Server sie auswertet.
|
|
- [ ] `curl -I https://dc.mhdf.de/releases/<produkt>/prod/<rid>/<version>/package.tar.gz`
|
|
→ **401**
|
|
- [ ] Mit `-u "$(printf 'lic_%s' "$(printf %s "<schlüssel>" | sha256sum | cut -c1-16)"):<schlüssel>"` → **200**
|
|
(Benutzername ist die Ableitung, nicht der Schlüssel — siehe
|
|
[§5A](./UPDATESERVICE_INTEGRATION_GUIDE.md#5a-zugangsschutz-der-release-verzeichnisse))
|
|
- [ ] `curl -I https://dc.mhdf.de/releases/<produkt>/.htpasswd` → **403**
|
|
- [ ] `https://dc.mhdf.de/installer/update-agent-linux-x64` → **200**, weiterhin offen
|
|
- [ ] Eine Lizenz widerrufen und erneut mit ihr laden → **401**
|
|
|
|
### 16.5 Wenn etwas klemmt
|
|
|
|
Ein 401 im Client heißt **Lizenz**, nicht Netzwerk. `UpdateCheckResult.Unauthorized`
|
|
unterscheidet beides; der Agent gibt `UNAUTHORIZED: …` aus und liefert
|
|
Rückgabewert 2.
|
|
|
|
Zum Abschalten die erzeugten `.htaccess`-Dateien in den Produktverzeichnissen
|
|
löschen. Sie entstehen beim nächsten Auslöser neu.
|