Files
Deploymentcenter/docs/SETUP_INTEGRATION_GUIDE.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

13 KiB

Deploymentcenter — Erstinstallation von Anwendungen

Neu in Version 2.3. Bis dahin gab es nur den Update-Weg: eine Anwendung musste bereits installiert und eingerichtet sein, damit sich etwas aktualisieren ließ. Die Erstinstallation auf einem neuen System war Handarbeit — Paket kopieren, Konfiguration abtippen, Token besorgen, Monitoring nachtragen.

Die Erstinstallation läuft jetzt über denselben update-agent, der auch Updates einspielt. Kein eigener Installer je Anwendung, kein MSI, kein Setup-Assistent im Anwendungscode.

update-agent --action install

Mehr braucht es nicht. Der Rest ist ein Dialog.


0. Den Installer besorgen

Im WebUI unter UpdateService → ⬇️ Installer, oder direkt vom Server.

Linux — lädt das zur Architektur passende Binary, prüft die Prüfsumme und legt es ab:

wget -qO- https://dc.mhdf.de/installer/install.sh | sh

Ohne Skript geht es genauso:

wget https://dc.mhdf.de/installer/update-agent-linux-x64 -O update-agent

Windows (PowerShell):

irm https://dc.mhdf.de/installer/install.ps1 | iex
Datei Plattform
/installer/update-agent-win-x64.exe Windows x64
/installer/update-agent-linux-x64 Linux x64
/installer/update-agent-linux-arm64 Linux ARM64
/installer/installer.json Version, Größen und Prüfsummen aller Binaries

Der Installer-Download selbst bleibt offen — die Binaries enthalten keine Zugangsdaten, und eine Anmeldung an dieser Stelle würde nur den Bootstrap verkomplizieren (Zugangsdaten in wget --user=… landen in der Shell-History).

Beim Ausführen fragt der Installer als Erstes nach Benutzername und Passwort. Dieselben Zugangsdaten öffnen auch die Release-Ablage, die seit Version 2.4 hinter HTTP-Basic-Auth liegt — bei einer Erstinstallation gibt es noch keinen Lizenzschlüssel, mit dem sich das Paket holen ließe. Siehe UPDATESERVICE_INTEGRATION_GUIDE §5A.

Zu jedem Binary liegt eine .sha256 daneben. Die Prüfsumme vergleichen — „Programm herunterladen und Zugangsdaten eingeben" ist genau das Muster, das Phishing nachahmt; die Prüfsumme ist der Grund, warum man es hier trotzdem tun kann. Die Bootstrap-Skripte tun das automatisch und brechen bei Abweichung ab.

Die Skripte richten nichts von selbst ein. Sie laden, prüfen, legen ab und sagen, wie es weitergeht — ein Skript aus dem Netz, das ungefragt eine Anwendung aufsetzt und dabei nach Zugangsdaten fragt, wäre genau das, wovor man Nutzer sonst warnt.

Rund 34 MB je Plattform: Das Binary bringt die .NET-Laufzeit mit, damit auf einem frisch aufgesetzten System nichts vorinstalliert sein muss. Bewusst ohne NativeAOT und ohne Trimming — Spectre.Console löst seine Eingabeaufforderungen über Reflexion auf; getrimmt baut das zwar, bricht aber erst beim Anwender.

Neu bauen und hochladen

powershell -File scripts/build_installer.ps1
python scripts/upload_installer.py ./artifacts/installer

deploy.py klammert client-dotnet bewusst aus — der Quelltext des Agenten gehört nicht auf den Webserver, die übersetzten Binaries schon. Deshalb der eigene Upload-Weg.


1. Der Ablauf

update-agent --action install
  ├ Deploymentcenter?  https://dc.mhdf.de
  ├ Benutzer / Passwort           → kurzlebiges Token (30 Minuten)
  ├ Auswahl aus dem Katalog       → nur, was auf dieser Plattform läuft
  ├ Kanal (prod / beta / dev)
  ├ Zielverzeichnis               → vorbelegt mit /opt/<slug> bzw. Programme\<slug>
  ├ Paket laden, Prüfsumme, Signatur, Entpacken   ← der reguläre Update-Weg
  ├ setup.json auflösen           → fragt nur, was übrig bleibt
  └ Konfiguration schreiben

Die Dateien kommen über denselben Pfad wie ein Update ins Zielverzeichnis: mit SHA256-Prüfung, Signaturprüfung, Staging und Rollback. Ein zweiter Download-Weg wäre ein zweiter Ort, an dem dieselben Fehler wieder entstehen.


2. Das Installationskonto

Für Zielsysteme gibt es die Rolle installer. Im WebUI unter System → 👤 Benutzer anzulegen.

Ein solches Konto kann genau zwei Dinge: sich über /api/setup/v1/login anmelden und Anwendungen einrichten. Am WebUI kann es sich nicht anmelden — der Versuch wird abgewiesen und protokolliert.

Das ist der Kern der Sache: Die Zugangsdaten werden auf jedem Zielsystem eingetippt, auf dem je etwas installiert wird. Mit einem Administratorkonto verteilte man damit den Zugang zu Tokens, Lizenzen, Monitoren und dem Bugtracker auf all diese Rechner.

admin installer
WebUI ja nein
/api/setup/v1/* ja ja
Token ausstellen alle Rechte nur watchdog:ping, watchdog:read, bugtracker:report, updateservice:read

Noch offen: der zweite Faktor. dc_users.totp_secret existiert als Spalte, wird aber von keinem Anmeldeweg ausgewertet — es gibt bislang keine TOTP-Prüfung im Deploymentcenter. Ein Konto mit hinterlegtem Geheimnis wird von der Setup-Anmeldung deshalb ausdrücklich abgelehnt (501 totp_not_supported), statt den zweiten Faktor stillschweigend zu übergehen. Bis das nachgezogen ist, tragen ein langes Passwort und die enge Rechtevergabe die Sicherheit.


3. setup.json

Die Datei beschreibt, was eine Anwendung zum Laufen braucht. Sie gehört in das Publish-Verzeichnis und landet damit im Paket, neben der manifest.json.

Bewusst im Paket und nicht zentral im Deploymentcenter. So ist die Beschreibung mit der Anwendung versioniert: Braucht Version 2.0 ein Feld mehr als 1.9, stimmt es automatisch. Eine zweite Pflegestelle liefe früher oder später auseinander.

Vollständige Vorlage: setup.example.json

{
  "schema": 1,
  "displayName": "Beispielanwendung",
  "targets": [
    { "id": "app", "file": "appsettings.json", "format": "json" }
  ],
  "fields": [
    { "key": "ConnectionStrings:Main", "label": "Datenbank", "type": "secret" },
    { "key": "Deploymentcenter:BaseUrl", "source": "detect:baseurl", "type": "url" },
    { "key": "Deploymentcenter:Token", "source": "provision",
      "scopes": ["watchdog:ping"] }
  ]
}

Fehlt die Datei, lässt sich die Anwendung trotzdem installieren — der Installer entpackt sie dann nur und fragt nichts ab.

Felder

Eigenschaft Bedeutung
key Schlüssel im Ziel. Doppelpunkte trennen Ebenen (ConnectionStrings:Main) — die Schreibweise von Microsoft.Extensions.Configuration
label Beschriftung der Frage
help Erläuterung, die darüber steht
type string, secret, url, int, bool, enum
required Vorgabe true
default Vorbelegung der Eingabe
source ask (Vorgabe), detect:…, provision
scopes Rechte, wenn source: "provision"
options Auswahl bei type: "enum"
validate Regulärer Ausdruck
target id des Ziels, wenn es mehrere gibt

Wohin geschrieben wird

Ein Ziel hat neben file eine Ortsangabe:

"targets": [
  { "id": "app",  "file": "appsettings.json",             "format": "json" },
  { "id": "user", "file": "clawddotnet/Settings.json",
    "location": "config", "format": "json" }
]
location Windows Linux / macOS
install (Vorgabe) Installationsverzeichnis Installationsverzeichnis
config %APPDATA% $XDG_CONFIG_HOME, sonst ~/.config
data %LOCALAPPDATA% $XDG_DATA_HOME, sonst ~/.local/share
home Benutzerprofil $HOME

Warum das nötig ist: Eine Anwendung, die sich unter Linux richtig verhält, legt ihre Konfiguration nicht neben das Programm — /opt/<app> ist für den Dienstbenutzer typischerweise nicht schreibbar. Ohne Ortsangabe schriebe der Installer die Datei dorthin, wo die Anwendung nie nachsieht.

In file sind Umgebungsvariablen in beiden Schreibweisen erlaubt: ${XDG_CONFIG_HOME}/app/Settings.json und %APPDATA%\app\Settings.json. Ein absoluter Pfad wird unverändert benutzt. Unbekannte Variablen bleiben stehen, statt zu einer leeren Zeichenkette zu werden — ein stehengebliebener Platzhalter fällt auf, ein stillschweigend falscher Pfad nicht.

Wer den Installer startet, entscheidet mit. config, data und home beziehen sich auf das Konto, unter dem der Installer läuft. Wird er als root oder Administrator gestartet, der Dienst aber unter einem eigenen Konto betrieben, landet die Datei im falschen Profil. Der Installer gibt deshalb den aufgelösten Pfad aus — prüfe ihn. Für Dienste ist ein absoluter Pfad oft die ehrlichere Angabe.

type bestimmt auch, wie geschrieben wird: int und bool landen als JSON-Zahl beziehungsweise -Wahrheitswert, nicht als Zeichenkette. Sonst scheitert die Bindung in der Anwendung.

Nur die wirklich fehlenden Informationen

Je Feld gilt die erste zutreffende Regel:

Regel fragt
Wert steht schon in der Zieldatei und ist kein Platzhalter nein
source: "detect:…" nein
source: "provision" nein
sonst ja, mit default vorbelegt

Erkannte Platzhalter (changeme, TODO, your-…, <…>, example.com und ähnliche) gelten nicht als eingerichteter Wert — sonst liefe die Anwendung mit der ausgelieferten Vorlage los.

Ableitbare Werte: detect:hostname, detect:platform, detect:installdir, detect:project, detect:baseurl, detect:username.

Das Token der Anwendung

source: "provision" lässt den Server ein Dauertoken ausstellen, das an das Projekt und den Rechnernamen gebunden ist. Der Installer schreibt es in das angegebene Feld; niemand muss es sehen oder kopieren.

Welche Rechte vergeben werden, entscheidet der Server, nicht die Anfrage. Die scopes aus der setup.json können nur einschränken. Andernfalls wäre der Umweg über ein kurzlebiges Setup-Token wirkungslos — wer einmal installieren darf, stellte sich sonst ein Token mit allen Rechten aus und behielte es.


4. Konfiguration schreiben

Vorhandene Inhalte bleiben erhalten. Eine appsettings.json enthält neben den abgefragten Werten fast immer Logging-Einstellungen, Feature-Schalter und anderes. Die Datei neu zu erzeugen wäre der bequemere Weg und verlöre all das — und zwar bei einer Neuinstallation, bei der niemand ein Backup hat.

  • JSON: Ebenen werden aus dem Schlüssel gebildet, bestehende Zweige bleiben stehen. Kommentare und nachgestellte Kommata werden beim Lesen toleriert.
  • env: bestehende Zeilen werden ersetzt, unbekannte Schlüssel angehängt. Kommentare und Reihenfolge bleiben erhalten. A:B wird zu A__B.

Ist eine vorhandene Datei kein gültiges JSON, wird sie als <name>.unlesbar-<zeitstempel> zur Seite gelegt statt überschrieben.

Zusammenspiel mit preservePatterns des Packagers: Die Vorlage wird ausgeliefert (Erstinstallation bekommt sie), ein Update ersetzt sie nicht, und der Installer schreibt die abgefragten Werte hinein. Siehe UPDATESERVICE_INTEGRATION_GUIDE §3A.


5. Nachträglich einrichten

update-agent --action configure --target-dir /opt/myapp --project myapp

Liest die setup.json der bestehenden Installation und fragt nur, was noch fehlt. Nach Zugangsdaten wird dabei nur gefragt, wenn ein Feld mit source: "provision" vorkommt — sonst braucht es den Server gar nicht.


6. Schnittstelle

Aufruf Zweck Authentifizierung
POST /api/setup/v1/login Benutzername + Passwort → Setup-Token keine
GET /api/setup/v1/catalog Was ist hier installierbar? setup:catalog
POST /api/setup/v1/token Dauertoken der Anwendung setup:install
curl -X POST https://dc.mhdf.de/api/setup/v1/login \
     -H "Content-Type: application/json" \
     -d '{"username":"installer","password":"…","hostname":"srv-07"}'
{ "status": "success", "setup_token": "dc_setup_…", "expires_in": 1800,
  "scopes": ["setup:install"], "role": "installer" }

Das Setup-Token lebt nur im Speicher des Installers, wird nirgends abgelegt und läuft nach 30 Minuten ab. Was auf dem System zurückbleibt, ist das Dauertoken der Anwendung mit genau den Rechten, die sie braucht.

Der Katalog zeigt ausschließlich, was zur Laufzeitkennung des anfragenden Systems passt. Ein Projekt, von dem es nur ein Windows-Paket gibt, taucht auf einem Linux-Rechner gar nicht erst auf — alles andere wäre eine Auswahl, die beim Anklicken fehlschlägt.


7. Was noch fehlt

Diese Fassung ist Stufe 1. Bewusst noch nicht enthalten:

Fehlt Bedeutet
Zentrale Geheimnisse Datenbankzugang und API-Schlüssel werden je Installation eingetippt. Stufe 2 hinterlegt sie verschlüsselt je (Projekt, Umgebung) im Deploymentcenter, sodass source: "bundle:…" sie automatisch zieht
Übernahme früherer Antworten Die zweite Installation derselben Anwendung fragt dasselbe noch einmal
Verbindungstests Ob die eingegebene Datenbankverbindung wirklich trägt, zeigt sich erst beim ersten Start
TOTP Siehe §2
Dienstregistrierung systemd-Unit beziehungsweise Windows-Dienst legt der Installer nicht an
Watchdog-Eintrag Der Monitor entsteht beim ersten Heartbeat, nicht schon bei der Installation