Sieben Rueckmeldungen aus einer laufenden Integration. Der schwerwiegendste Punkt ist ein Fehler von mir. D2 - Predictalytics ist ausgesperrt. Bestaetigt: /releases/predictalytics/ antwortet mit 401, waehrend die API weiter "Update verfuegbar" meldet. Jede ausgelieferte Installation laeuft damit in die Wand. Ursache ist nicht der Schutz an sich, sondern dass ich ihn scharfgeschaltet habe, ohne zu pruefen, ob die Verbraucher nachgezogen sind - genau der Fall, vor dem UPGRADE §16.1 warnt. Behoben wird die Klasse des Problems, nicht nur dieser Fall: Produkte lassen sich unter UpdateService -> Zugangsschutz einzeln ausnehmen. Damit ist der gestaffelte Rollout moeglich, der bisher fehlte: ausnehmen, Build mit Schluessel ausliefern, wieder einschalten. Ausgenommene Produkte sind in der Uebersicht deutlich als AUSGENOMMEN markiert und faerben den Selbsttest nicht gruen. D5 - BuildInfo.targets verhinderte inkrementelle Builds. BuildDateUtc trug die volle Uhrzeit, aenderte sich also bei jedem Build; WriteOnlyWhenDifferent griff nie, und jedes einbindende Projekt wurde jedes Mal neu uebersetzt. Jetzt tagesgenau. Das Commit-Datum waere stabiler, laesst sich aber nicht verlaesslich holen - die Formatangabe von git log ueberlebt MSBuild und cmd.exe nicht, wie ein Fehlversuch gezeigt hat. D4 - LicenseConfig war uneinheitlich und fuer Dienste unbrauchbar. SetStorageDirectory benutzte den Pfad roh, waehrend der Weg ueber die Umgebungsvariable <slug>/license anhaengte: zwei Produkte im selben Prozess schrieben in dieselbe state.dat. Und ohne $HOME - systemd User= ohne Heimatverzeichnis - landete der Rueckfall im Installationsverzeichnis, unter /opt nicht beschreibbar. Neu: einheitliches Anhaengen und ein Rueckfall auf /var/lib/<slug>, der vorher prueft, ob dort ueberhaupt geschrieben werden kann. D1 - Woher die Anwendung den Lizenzschluessel fuer den Update-Zugang nimmt, stand nirgends zusammenhaengend. Jetzt ein Beispiel in UPDATESERVICE §5A, das TryGetCachedKey und CheckForUpdateAsync verbindet. D3 - Fuer einen laufenden systemd-Dienst gab es keinen Update-Weg. Neu: SETUP §4A mit einer oneshot-Unit, die stoppt, aktualisiert und wieder startet - ohne --restart, weil der Agent sonst an systemd vorbei einen zweiten Prozess startet. Inklusive EnvironmentFile fuer den Schluessel und dem Hinweis auf die Dateirechte nach einem Lauf als root. D6 - Die Empfehlung Environment.Exit(1) passt fuer handelnde Systeme nicht. Ein neuer Abschnitt im Lizenz-Leitfaden beschreibt den Sperrbetrieb: abschalten, was neue Verpflichtungen eingeht; weiterlaufen lassen, was bestehende abwickelt. D7 - Die Drosselungsgrenzen aller Endpunkte stehen jetzt in docs/README.md. /api/errors/v1/report erlaubt 300 pro Minute, nicht 60; die Einstellung bugtracker.error_rate fehlte in der Beispielkonfiguration. Der zweite Teil des Befunds war veraltet: docs/README.md fuehrt die Release-Anleitung bereits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
439 lines
17 KiB
Markdown
439 lines
17 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
wget -qO- https://dc.mhdf.de/installer/install.sh | sh
|
|
```
|
|
|
|
Ohne Skript geht es genauso:
|
|
|
|
```bash
|
|
wget https://dc.mhdf.de/installer/update-agent-linux-x64 -O update-agent
|
|
```
|
|
|
|
**Windows** (PowerShell):
|
|
|
|
```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](./UPDATESERVICE_INTEGRATION_GUIDE.md#5a-zugangsschutz-der-release-verzeichnisse)**.
|
|
|
|
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
|
|
|
|
```bash
|
|
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](./setup.example.json)**
|
|
|
|
```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:
|
|
|
|
```json
|
|
"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.
|
|
|
|
#### Wenn die Schreibweise plattformabhängig ist
|
|
|
|
Konfigurationsordner heißen nicht überall gleich: `%APPDATA%\MeineAnwendung`
|
|
ist unter Windows üblich, `$XDG_CONFIG_HOME/meineanwendung` unter Linux klein.
|
|
|
|
Meistens genügt es, die **kleingeschriebene Form** anzugeben. Sie trifft unter
|
|
Linux exakt und unter Windows ebenfalls, weil NTFS die Schreibweise ignoriert.
|
|
Andersherum funktioniert es nicht — unter Linux entstünde ein zweites, leeres
|
|
Verzeichnis neben dem, aus dem die Anwendung liest.
|
|
|
|
Dieser Kniff trägt aber nur, solange das Dateisystem wirklich unempfindlich
|
|
ist. Auf APFS mit Beachtung der Schreibweise, auf einem per `fsutil`
|
|
umgestellten NTFS-Ordner oder wenn sich die Namen um **mehr als die
|
|
Schreibweise** unterscheiden, braucht es eine eigene Angabe:
|
|
|
|
```json
|
|
{
|
|
"id": "user",
|
|
"file": "meineanwendung/Settings.json",
|
|
"fileWindows": "MeineAnwendung/Settings.json",
|
|
"location": "config"
|
|
}
|
|
```
|
|
|
|
`fileLinux` und `fileMacOS` gibt es ebenso. Ohne Angabe gilt `file`.
|
|
|
|
### Dateirechte
|
|
|
|
Enthält ein Ziel mindestens einen als geheim gekennzeichneten Wert, wird die
|
|
geschriebene Datei auf den eigenen Benutzer beschränkt (`600` unter Linux und
|
|
macOS). Ohne das schriebe .NET mit der Standardmaske, unter Linux üblicherweise
|
|
`644` — und in dieser Datei stehen Lizenzschlüssel und Anwendungstoken.
|
|
|
|
Als geheim gilt jedes Feld mit `type: "secret"` **und** jedes mit
|
|
`source: "provision"` — ein so geholtes Token trägt oft den Typ `string`, ist
|
|
aber genauso schutzbedürftig.
|
|
|
|
Unter Windows geschieht nichts: Dateien in `%APPDATA%` erben die
|
|
Zugriffsrechte des Profils.
|
|
|
|
> **Der Installer verschlüsselt nicht.** Er schreibt Klartext; ob und wie
|
|
> Geheimnisse anschließend geschützt werden, entscheidet die Anwendung — etwa
|
|
> über DPAPI beim ersten Speichern. Das setzt voraus, dass eure
|
|
> Entschlüsselung Klartext unverändert durchreicht, statt an ihm zu scheitern.
|
|
> Wer eine solche Weiche hat, sollte sie im Test als **aktiv genutzt**
|
|
> beschreiben und nicht als Altlast — sonst wird sie eines Tages als tot
|
|
> entfernt und sperrt jede frisch installierte Instanz aus.
|
|
|
|
`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](./UPDATESERVICE_INTEGRATION_GUIDE.md#3a-ausschließen-oder-schützen)**.
|
|
|
|
---
|
|
|
|
## 4A. Einen laufenden systemd-Dienst aktualisieren
|
|
|
|
Der `update-agent` schreibt nach `--target-dir` — unter `/opt/<app>` hat der
|
|
Dienstbenutzer dort keinen Schreibzugriff. Als `root` gestartet würde er zwar
|
|
schreiben können, dann aber über `--restart` **an systemd vorbei** einen
|
|
zweiten Prozess starten.
|
|
|
|
Die Abfolge lautet deshalb: anhalten, aktualisieren, starten — und der Agent
|
|
startet **nichts** selbst.
|
|
|
|
```ini
|
|
# /etc/systemd/system/myapp-update.service
|
|
[Unit]
|
|
Description=Update für myapp einspielen
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
# Der Agent braucht Schreibrecht in /opt/myapp.
|
|
User=root
|
|
|
|
# Der Lizenzschlüssel gehört nicht in die Kommandozeile - "ps" zeigt sie
|
|
# jedem Benutzer der Maschine. EnvironmentFile mit 0600 und root:root.
|
|
EnvironmentFile=/etc/myapp/update.env
|
|
|
|
ExecStartPre=/usr/bin/systemctl stop myapp.service
|
|
ExecStart=/opt/myapp/update-agent \
|
|
--action update --project myapp --channel prod \
|
|
--target-dir /opt/myapp --version latest
|
|
ExecStartPost=/usr/bin/systemctl start myapp.service
|
|
```
|
|
|
|
```bash
|
|
# /etc/myapp/update.env chmod 600, chown root:root
|
|
DC_LICENSE_KEY=XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
|
|
```
|
|
|
|
Auslösen von Hand oder über einen Timer:
|
|
|
|
```bash
|
|
systemctl start myapp-update.service
|
|
```
|
|
|
|
**Kein `--restart` und kein `--wait-for-pid`.** Den Neustart übernimmt
|
|
`ExecStartPost`, das Anhalten `ExecStartPre` — der Dienst ist beim Anwenden
|
|
bereits beendet, es gibt keinen Prozess, auf den zu warten wäre.
|
|
|
|
Wird der Update-Weg dagegen **aus der Anwendung heraus** angestoßen, gilt
|
|
`restartPath: ""` und `exitCurrentApp: false`: Die Anwendung fährt sich selbst
|
|
geordnet herunter, systemd startet sie über `Restart=on-success` neu.
|
|
|
|
> **Dateirechte nach dem Update.** Der Agent läuft als `root` und legt die
|
|
> Dateien entsprechend an. Gehört der Dienst einem anderen Benutzer, gehört
|
|
> ein `ExecStartPost=/usr/bin/chown -R myapp:myapp /opt/myapp` davor — sonst
|
|
> startet der Dienst danach nicht mehr.
|
|
|
|
---
|
|
|
|
## 5. Nachträglich einrichten
|
|
|
|
```bash
|
|
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` |
|
|
|
|
```bash
|
|
curl -X POST https://dc.mhdf.de/api/setup/v1/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username":"installer","password":"…","hostname":"srv-07"}'
|
|
```
|
|
|
|
```json
|
|
{ "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](#2-das-installationskonto) |
|
|
| **Dienstregistrierung** | systemd-Unit beziehungsweise Windows-Dienst legt der Installer nicht an |
|
|
| **Watchdog-Eintrag** | Der Monitor entsteht beim ersten Heartbeat, nicht schon bei der Installation |
|