Files
ClawdDotNet/docs/archiv/Deploymentcenter-2.4-Integrationsplan.md
T
RichardandClaude Opus 5 740649789e Eine Roadmap statt neun verteilter Listen
Der Stand lag ueber eine Bestandsaufnahme, drei Konzeptpapiere, vier
Umsetzungsplaene und zwei Deploymentcenter-Dokumente verteilt - jedes mit
eigener Reihenfolge, teils widersprueglich. Alle offenen Punkte daraus sind
in docs/Roadmap.md zusammengefuehrt.

Aufbau der neuen Roadmap
- Statusvokabular: erledigt / beschlossen-offen / Entscheidung noetig /
  zurueckgestellt / Idee. Damit steht das Geparkte sichtbar drin, statt in
  einem Konzeptpapier zu verschwinden.
- Herkunft-Spalte traegt das alte Kuerzel (S4, K2, B9, T4, F-A1, C1, DC1),
  damit die archivierten Papiere auffindbar bleiben, ohne sie zu lesen.
- Abschnitt 1 Reihenfolge, 2 offene Entscheidungen, 3 die Vorhaben nach
  Bereich, 4 Ideenspeicher, 5 Chronik des Erledigten, 6 Modell-Einstufung,
  7 Herkunftskarte.

Was dabei sichtbar wurde
- Neun Entscheidungen blockieren Arbeit, ohne dass sie Aufwand kosten -
  allen voran Matrix oder Rocket.Chat. Sie stehen jetzt gesammelt in
  Abschnitt 2 statt verstreut in den Diskussionsteilen der Konzepte.
- B8 (keine Tiefenbegrenzung bei AgentComm) ist unveraendert offen. Das ist
  keine Theorie: A haelt sein Gate, waehrend es auf B wartet - ruft B nun A,
  warten beide bis zum Timeout. Der Testfall A13 dafuer fehlt bis heute.
- Die vier Umsetzungsplaene vom 2026-08-05 sind alle unumgesetzt und waren
  in keiner Roadmap verzeichnet.

Archiv
Sechs Dokumente ziehen nach docs/archiv/: Bestandsaufnahme, Konzepte
Backup/Finanz/Analyse, Linux-Portierung-Analyse, Lizenz-HardwareId-v2
(gegenstandslos - LicenseLabrador ist ersetzt), Deploymentcenter-Review und
der 2.4-Integrationsplan. Sie bleiben als Begruendung lesbar, werden aber
nicht mehr fortgeschrieben; das README ordnet jedes einzeln ein und warnt,
dass ihre Quelltext-Verweise ins Leere gehen koennen.

Bauplan bleibt Bauplan
Taskboard, Audit, Staging, Memory, Agentenkommunikation, RocketChat und
Deploymentcenter-Integration bleiben in docs/ - sie sind die Detailvorgabe
fuer die Umsetzung, nicht Vorhabenlisten. Jedes bekommt oben eine Zeile,
die seine Rolle und den Umsetzungsstand nennt und auf die Roadmap zeigt.
Dasselbe fuer die vier Umsetzungsplaene: die Reihenfolge gilt in der
Roadmap, nicht im Plan.

Nebenbei repariert: Taskboard-Konzept und Teststrategie verwiesen auf
AgentScheduler und ToolJobScheduler, die seit der Scanner-Konsolidierung
geloescht sind. Alle Dokument-Verweise in docs/ sind geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:31:06 +02:00

16 KiB
Raw Blame History

Deploymentcenter 2.2 2.4: Was noch zu tun ist

Stand: 2026-08-13. Ergänzt Deploymentcenter-Integration (dort steht der Stand nach 2.1) um die drei neuen Ausbaustufen.

Fassung Was dazukam Betrifft uns
2.2 Plattform-Dimension, signierte Releases, Anwenden mit Rollback, preservePatterns Release-Strecke, Update-Anwendung
2.3 Erstinstallation über update-agent --action install, setup.json, Installationskonto Neu, siehe §4
2.4 Release-Ablage hinter HTTP-Basic-Auth, Zugang über den Lizenzschlüssel Erledigt, siehe §1

1. Zugangsschutz (2.4) — erledigt

CheckForUpdateAsync übergibt jetzt ReleaseCredentials.FromLicenseKey(...), und UpdateCheckResult.Unauthorized wird getrennt von einem Netzfehler behandelt.

Warum das nicht warten konnte: UPGRADE §16.1 empfiehlt „erst ausliefern, dann scharfschalten". Für ein Produkt, das noch nie veröffentlicht hat, geht diese Reihenfolge nicht auf. ReleaseGuard::regenerateForProject überspringt Verzeichnisse, die es nicht gibt — /releases/clawddotnet/ liefert derzeit 404, es ist also nichts geschützt. Sobald wir das erste Release hochladen, entsteht das Verzeichnis, und der nächste tick.php-Lauf legt den Schutz an. Der erste ausgelieferte Build muss die Zugangsdaten also bereits mitbringen, sonst schließt sich die Tür hinter dem ersten Release.


2. Plattform (2.2) — Release-Strecke steht

Erstes Paket veröffentlicht: 0.1.0, Kanal dev, Plattform win-x64, 116 Dateien, 25 MB, Rückgabewert 0. Die Update-Prüfung antwortet korrekt (0.0.9 → Update, 0.1.0 → keins).

pack-and-deploy --config deploy/packager.config.json \
                --project clawddotnet --version 0.1.0 \
                --channel dev --platform win-x64 \
                --publish-dir <dotnet-publish-Ausgabe>
  • Zugangsdaten in deploy/packager.config.json (per .gitignore ausgeschlossen), Vorlage ohne Werte in packager.config.example.json.
  • ftpRemoteBaseDir ist /releases, nicht /public_html/releases wie in der Packager-Vorlage: Auf diesem Server liegt die Release-Ablage auf der FTP-Wurzel.
  • Der Packager veröffentlicht mit einem Sub-Token, das nur updateservice:publish trägt — gezogen über /api/tokens/v1/provision. Das Master-Token gehört nicht in eine Konfigurationsdatei.
  • deploy.py ist dafür das falsche Werkzeug. Es spiegelt den Deploymentcenter-Projektbaum in die FTP-Wurzel und hat mit dem Veröffentlichen eines Anwendungspakets nichts zu tun.
  • Die Versionsgegenprobe des Packagers greift und passt: <Version> aus Directory.Build.props stimmt mit clawddotnet.dll überein.

Offen: linux-x64 (erst nach der Avalonia-Portierung) und prod.

Clientseitig ist nichts zu tun: Das SDK schickt die Kennung des laufenden Systems von selbst.

preservePatterns betrifft uns kaum

Unsere Konfiguration liegt seit der Linux-Portierung in AppPaths.ConfigDirectory (%APPDATA% bzw. XDG), nicht neben der Programmdatei. Ein Update kann sie also gar nicht überschreiben. Zu prüfen bleibt nur, dass keine leeren Arbeitsordner ins Paket wandern — die CreateWorkingDirectories-Targets in ClawdDotNet.csproj legen tools/, Logs/ und Instances/ unter OutputPath an, und die sind mit AppPaths ohnehin überholt.


3. Update anwenden — erledigt

Aus dem Hinweis ist eine Rückfrage geworden („Jetzt installieren" / „Später"), die den update-agent startet. Umgesetzt in DeploymentcenterService.StartUpdate und App.StartUpdateAsync.

Der Agent wird mitgeliefert — er muss es

Die Erstinstallation legt den Agenten nicht ins Zielverzeichnis: Sie läuft von dort, wo der Benutzer sie hingelegt hat. ResolveAgentPath() sucht ihn aber neben der Anwendung. Ohne Mitliefern fände die Anwendung nie einen Agenten und könnte sich nicht aktualisieren.

deploy/publish.py holt das ausgelieferte Binary von /installer/, prüft die SHA256 gegen installer.json und legt es plattformrichtig ab (update-agent.exe bzw. update-agent). Bewusst das offizielle statt eines selbst gebauten: Es ist dasselbe, das die Erstinstallation verwendet, und wird zentral gepflegt. Ein ungeprüfter Download wäre ausgerechnet auf dem Pfad, der später fremden Code ausführt, die falsche Sparsamkeit.

Kosten: rund 28 MB im gepackten Paket (25 → 53 MB).

Die Reihenfolge ist der eigentliche Inhalt

1. AnnounceUpdate(version)   → Watchdog meldet beim Beenden "maintenance"
2. AppHost.DisposeAsync()    → Datenbank, Scanner, Telegram, Abmeldung
3. StartUpdate(...)          → Agent starten, exitCurrentApp: false
4. desktop.Shutdown()        → wir beenden uns selbst

Die Verlockung wäre, LaunchUpdateAgent das Beenden zu überlassen. Das tut es aber über Environment.Exit und übergeht damit Schritt 2 vollständig: keine Abmeldung, keine geschlossene Instanzdatenbank. Deshalb exitCurrentApp: false und waitForCurrentProcess: true — der Agent bekommt unsere Prozesskennung und wartet, bis wir wirklich weg sind, statt über gesperrte Dateien zu kopieren.

maintenance statt stopped ist kein Schönheitsfehler: stopped heißt „bewusst beendet" und lässt den Monitor liegen, bis jemand ihn anfasst. Beim Update kommt die Instanz aber wieder.

Doppeltes Aufräumen war die Falle dabei: Nach Schritt 2 ruft desktop.Shutdown() die Behandlung, die erneut aufräumt — und dabei den gerade gesetzten Wartungszustand mit einer zweiten Abmeldung überschrieben hätte. AppHost.DisposeAsync sperrt sich jetzt selbst gegen den zweiten Durchlauf.


4. Erstinstallation (2.3) — setup.json steht

Der Konfigurationsort war der Blocker: setup.json-Ziele waren „relativ zum Installationsverzeichnis", unsere Konfiguration liegt aber in %APPDATA% bzw. $XDG_CONFIG_HOME — weil /opt/clawddotnet unter Linux für den Dienstbenutzer nicht schreibbar ist (Linux-Analyse).

Das Deploymentcenter hat daraufhin location am Ziel ergänzt (install, config, data, home) samt Variablenersetzung in file. Damit ist der Weg frei; setup.json liegt im Projekt und wird ins Ausgabeverzeichnis kopiert, landet also im Paket neben der manifest.json.

Der Ordnername ist bewusst kleingeschrieben

AppPaths legt das Verzeichnis plattformabhängig unterschiedlich an:

Plattform Pfad
Windows %APPDATA%\ClawdDotNet
Linux $XDG_CONFIG_HOME/clawddotnet (klein, Konvention)

Eine setup.json kennt nur eine Schreibweise. clawddotnet/Settings.json trifft unter Linux exakt und unter Windows ebenfalls, weil NTFS Groß- und Kleinschreibung nicht unterscheidet. Andersherum ginge es nicht: ClawdDotNet wäre unter Linux ein zweites, leeres Verzeichnis neben dem, aus dem die Anwendung liest.

Was dabei abfällt

Das Token stellt der Server aus (source: "provision"), die Server-Adresse kommt aus dem Installer (detect:baseurl). Damit entfällt der Absatz „bis die Avalonia-Einstellungs- ansicht steht, von Hand in Settings.json" aus der Integrationsbeschreibung — jedenfalls für frisch installierte Systeme.

Der Installer schreibt Lizenzschlüssel und Token im Klartext; er kennt unsere DPAPI-Hülle nicht. Das ist in Ordnung und abgesichert: SecretProtector.Unprotect gibt Klartext unverändert zurück, beim ersten Speichern wird verschlüsselt. Der Test dazu steht in SecretProtectorTests und nennt jetzt beide Gründe, damit ihn niemand als Altlast entfernt.

Zwei Grenzen bleiben

  • CLAWD_CONFIG_DIR kennt der Installer nicht. Wer den Ort per Umgebungsvariable verlegt, muss die Datei selbst verschieben.
  • Wer installiert, entscheidet mit (SETUP warnt selbst davor): config bezieht sich auf das Konto, unter dem der Installer läuft. Für einen systemd-Dienst mit eigenem Benutzer heißt das: als dieser Benutzer installieren, sonst landet die Konfiguration im falschen Profil.

Durchgespielt (2026-08-15)

Anmeldung mit dem Installationskonto und der gesamte Ablauf gegen den echten Server:

Schritt Ergebnis
POST /api/setup/v1/login 201, Rolle installer, Recht setup:install, Token 30 min gültig
GET /api/setup/v1/catalog?platform=win-x64 clawddotnet (dev=0.1.2) erscheint. Ohne platform leer — wie die Update-Prüfung, der Agent schickt PlatformId.Current
POST /api/setup/v1/token Anwendungstoken mit genau watchdog:ping + bugtracker:report
Rechteschranke Das ausgestellte Token kann kein updateservice:publish nachziehen (403 provision_denied)
SetupPaths.Resolve gegen unsere setup.json löst unter Windows auf %APPDATA%\ClawdDotNet\Settings.json auf (fileWindows greift)
Round-Trip SDK schreibt → SettingsManager liest trägt: camelCase-Keys treffen, der Klartext-Lizenzschlüssel geht durch den Entschlüsselungspfad (der Klartext unverändert durchreicht)

Damit ist der Weg vollständig: Ein frisch aufgesetztes System bekommt über den Installer Server-Adresse, Lizenzschlüssel und ein vom Server ausgestelltes Instanz-Token in die Settings.json geschrieben, die ClawdDotNet dann ohne Zutun lädt.

Befund am Rande: alte Felder bleiben stehen

Der SetupWriter merged in eine vorhandene Settings.json, statt sie zu ersetzen — richtig so, sonst gingen Logging-Einstellungen und Ähnliches verloren. Auf einem System mit einer alten Datei bleiben dabei Felder stehen, die es in der aktuellen AppSettings nicht mehr gibt (watchdogServerUrl, licensePublicKeyBase64 aus der LicenseLabrador-Zeit). Harmlos — SettingsManager ignoriert unbekannte Felder beim Laden —, aber tote Einträge in der Datei. Kein Handlungsbedarf; beim ersten Save der laufenden App verschwinden sie.

Nicht enthalten (SETUP §7): systemd-Unit und Windows-Dienst legt der Installer nicht an. Für den kopflosen Betrieb bleibt das unsere Aufgabe.


4a. Der Update-Weg ist durchgespielt

Am 2026-08-14 gegen den echten Server geprüft, nicht nur gebaut. Ausgangslage: das 0.1.0-Paket mit Lizenzschlüssel geladen und entpackt — also eine Installation, wie sie beim Kunden aussieht — plus eine selbst angelegte Datei, die in keinem Manifest steht.

Fall Ergebnis
0.1.0 → 0.1.1 RC 0. SHA256 des Pakets und 117 Manifest-Hashes geprüft. Fremde Datei unangetastet, setup.json da, update-agent.exe neu im Ziel
Rücksprung 0.1.1 → 0.1.0 RC 0. update-agent.exe als nicht mehr zum Release gehörig entfernt — und nur die, die fremde Datei blieb liegen
Ohne Lizenzschlüssel UNAUTHORIZED: … Erwartet wird der Lizenzschluessel dieser Installation, RC 2. Sauber von einem Netzfehler unterschieden
Abbruch mitten im Schreiben (Datei exklusiv gesperrt) RC 1, „Vorheriger Stand wurde wiederhergestellt". Version, Dateizahl und Inhalt unverändert — die Installation blieb lauffähig

Damit trägt die Zusage aus §4B des UpdateService-Handbuchs: Ein Abbruch hinterlässt keine halbe Installation, und verwaiste Dateien werden aufgeräumt, ohne fremde anzufassen.

Zwei Kleinigkeiten am Rand:

  • Nach dem gescheiterten Lauf blieb ein leeres .dc-update-backup/ zurück. Kein Speicherverlust — der Rollback hatte alles zurückgeholt —, und der nächste erfolgreiche Lauf hat es entfernt. Ein leeres Verzeichnis dieses Namens sieht für einen Betreiber aber nach „Update hängt" aus.
  • Der Agent weist bei jedem Lauf auf das unsignierte Release hin. Das ist richtig so und wird erst still, wenn §5 erledigt ist.

5. Signierte Releases (2.2) — Schlüssel steht, Prüfung getestet

Der Signierschlüssel ist seit dem 2026-08-14 serverseitig hinterlegt (RSA-SHA256, canonical-line-v1). 0.1.2 ist das erste signierte Release; 0.1.0 und 0.1.1 bleiben unsigniert, weil serverseitig beim Veröffentlichen signiert wird.

Am Testsystem durchgespielt:

Fall Ergebnis
Signiertes 0.1.2 mit --require-signature RC 0, kein Unsigniert-Hinweis mehr
Unsigniertes 0.1.1 mit --require-signature RC 1, Abbruch vor dem Herunterladen
Unsigniertes 0.1.1 ohne die Pflicht RC 0 mit Hinweis — wie dokumentiert

Der öffentliche Schlüssel wird beim ersten Lauf geholt und als dc-release-pubkey.pem neben dem Agenten festgehalten. Ein später abweichender Schlüssel fällt damit auf.

Offen: die Pflicht ist aus der Anwendung heraus nicht erreichbar

--require-signature gibt es nur als Kommandozeilenschalter. UpdateClient.LaunchUpdateAgent — der vom Handbuch empfohlene Weg, den auch wir benutzen — hat dafür keinen Parameter, und der Agent liest keine Umgebungsvariable dafür (Program.cs:66 liest ausschließlich HasFlag(args, "--require-signature")).

Damit läuft jede Anwendung, die den empfohlenen Weg geht, ohne Signaturprüfung, während derselbe Vorgang von Hand auf der Kommandozeile geschützt wäre. Wir könnten den Start selbst nachbauen — dann verlieren wir aber --restart, --wait-for-pid und --wait-timeout, also genau die Handgriffe, für die es die Hilfsmethode gibt.

Gemeldet. Sobald LaunchUpdateAgent einen Parameter dafür hat, setzen wir ihn: Alle unsere Releases ab 0.1.2 sind signiert, ein Rückschritt auf unsignierte Stände wäre danach kein Verlust.


6. Reihenfolge

  1. Zugangsschutz (§1) — erledigt, muss im ersten Release drin sein.
  2. setup.json (§4) — erledigt, wird mit dem ersten Paket ausgeliefert.
  3. Release-Strecke (§2): packager.config.json, erster Testlauf nach dev.
  4. Update anwenden (§3): Agent mitliefern, maintenance beim Update melden.
  5. Signatur scharf (§5), sobald der Serverschlüssel steht.

Schritt 3 ist die Voraussetzung für alles Weitere: Solange kein Release veröffentlicht ist, lässt sich weder Update noch Erstinstallation erproben — und die setup.json wirkt erst, wenn sie in einem Paket steckt.


7. Befunde vom 2026-08-13 — alle behoben

Zur Nachvollziehbarkeit, weil einige unsere Umsetzung geformt haben:

Befund Behoben durch
.htpasswd enthielt alle Lizenzschlüssel im Klartext (Benutzernamenspalte wird nicht gehasht) ReleaseGuard::licenseUsername() leitet lic_<sha256[0..16]> ab; ReleaseCredentials.UsernameForLicenseKey bildet dieselbe Ableitung nach. Die Datei enthält jetzt nur noch bcrypt über einen hochentropen Schlüssel
Doku beschrieb Nginx, der Schutz greift nur unter Apache; WebUI meldete „GESCHÜTZT" allein anhand vorhandener Dateien Echter HTTP-Selbsttest (ReleaseGuard::selfTest, erwartet 401), Warnhinweis und eigener Nginx-Abschnitt in der Doku
UPDATESERVICE §7 dokumentierte {"status":"ok"}, der Code liefert "success" Doku berichtigt
setup.json schrieb nur ins Installationsverzeichnis location-Angabe am Ziel plus Variablenersetzung in file

Offen aus dem ersten Release (2026-08-13)

Befund Wirkung
Veröffentlichen löst ReleaseGuard nicht aus. regenerateForProject läuft nur bei Lizenzänderungen, Projektlöschung, Kontoänderungen und im Sechs-Stunden-Turnus von cli/tick.php. Ein Produktverzeichnis entsteht aber erst beim ersten Upload /releases/clawddotnet/ war nach dem Upload ohne .htaccess — das frische Paket bis zum nächsten Turnuslauf für jeden ladbar. Der Turnus hat es inzwischen geschlossen (401 bestätigt). Ein Aufruf am Ende von /api/updateservice/v1/publish würde das Fenster ganz vermeiden; das Verzeichnis existiert dort bereits
Die Prüfvorschrift aus UPGRADE §16.4 meldet falsch grün. curl -I …/.htpasswd → 403 trifft auch dann zu, wenn die Datei gar nicht existiert: Apache sperrt .ht* global Wir hatten 403 auf .htpasswd und 200 auf package.tar.gz. Aussagekräftig ist nur der Paket-Abruf ohne Zugangsdaten
Kein Signierschlüssel auf dem Server. security.release_private_key ist nicht gesetzt (UPGRADE §15.2) Releases sind unsigniert, der Agent kann die Herkunft nicht prüfen. --require-signature ist damit unbenutzbar

Die Ableitung des Benutzernamens muss auf beiden Seiten zeichengenau übereinstimmen — lic_ plus die ersten 16 Hexzeichen des SHA-256 über den getrimmten Schlüssel. Wer eine Seite ändert, sperrt die gesamte Installationsbasis aus.