Files
Deploymentcenter/docs/UPGRADE.md
T
Deploymentcenter BotandClaude Opus 5 7a3a5dad69 fix(releases): Lizenzschluessel nicht mehr im Klartext, Selbsttest, Zielorte
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>
2026-08-13 11:47:26 +02:00

24 KiB

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.

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.

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.


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. 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:

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

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:

php public/install_db.php

Oder mit dem Shared Key:

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)

* * * * * /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:

* * * * * 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

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:

{ "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:

{ "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:

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.
  • /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 und UPDATESERVICE_INTEGRATION_GUIDE §2B/§2C.

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:
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
'release_private_key' => dc_env('DC_RELEASE_SIGNING_KEY', '/etc/dc/release-signing.pem'),
  • Prüfen, dass der öffentliche Schlüssel ausgeliefert wird:
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:
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:

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

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

update-agent --project myapp --action update \
             --license-key XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
# 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.gz401
  • 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)
  • curl -I https://dc.mhdf.de/releases/<produkt>/.htpasswd403
  • https://dc.mhdf.de/installer/update-agent-linux-x64200, 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.