Fuenf von sechs Befunden einer externen Integration. Der sechste - unsignierte Lizenzurteile - ist bestaetigt, aber bewusst nicht Teil dieses Commits. 1. Lizenzschluessel stand in der Kommandozeile Der Agent nahm --license-key nur als Argument und las keine Umgebungsvariablen. "ps" zeigt argv jedem Benutzer der Maschine - exakt die Begruendung, mit der UPGRADE.md §5 den Crontab-Weg verwirft. Damit nahm das SDK einen bereits geloesten Sicherheitsbefund zurueck. Der Agent liest jetzt DC_LICENSE_KEY, DC_DOWNLOAD_USER und DC_DOWNLOAD_PASSWORD, Umgebung vor Argument. LaunchUpdateAgent uebergibt den Schluessel nicht mehr als Argument, sondern setzt die Variable auf dem eigenen Prozess: das Kind erbt den Umgebungsblock, danach wird sie wieder entfernt. Das funktioniert auch mit UseShellExecute=true, wo sich ProcessStartInfo.Environment nicht setzen laesst. 2. --wait-timeout war nicht durchgereicht Der Agent kannte den Parameter, LaunchUpdateAgent hatte keinen dafuer - es galten fest 60 Sekunden. Eine Anwendung, die allein fuer host.StopAsync 30 Sekunden braucht, kommt damit gefaehrlich nah an die Grenze. Neu: waitTimeoutSeconds. Ausserdem ist im Quelltext und in der Doku jetzt festgehalten, dass exitCurrentApp:true ueber Environment.Exit(0) laeuft und damit finally-Bloecke und IHostApplicationLifetime uebergeht - bei offenem Zustand die falsche Wahl. 3. ILicensePrompt war tot Der Konstruktor nahm es entgegen, legte es in _prompt ab und benutzte es nirgends. Wer darauf eine headless-Story aufbaute, baute auf Sand. Neu: EnsureLicensedAsync() - zwischengespeicherten Schluessel nehmen, sonst fragen, pruefen, bei Ablehnung erneut fragen. allowPrompt:false lehnt ohne Cache ab, statt auf eine Eingabe zu warten, die im Dienst nie kommt. Ein voruebergehender Netzfehler fuehrt nicht zur erneuten Abfrage - der Schluessel ist ja nicht falsch. 4. Der Packager warnte nur Er bricht jetzt ab. Anlass war ein echter API-Schluessel in einem oeffentlich abrufbaren Paket - und die Warnung war damals ausgerechnet unterdrueckt, weil die Datei auf der preserve-Liste stand. Zwei Stufen: Dateiname (appsettings.Local.json, master.key, *.pfx, *.db, server_settings.xml) und Inhalt (gefuelltes Password=, sk-, ghp_, dc_master_, AKIA, private Schluessel). Die Inhaltspruefung findet auch Dateien mit unverdaechtigem Namen. Platzhalter loesen bewusst nicht aus: "sk-DEIN-SCHLUESSEL-HIER" haette sonst jede ausgelieferte Vorlage blockiert, und --allow-secrets waere nach einer Woche Gewohnheit. Beim Erproben zuerst genau in diese Falle gelaufen. 5. BuildInfo.targets war nur per Pfad-Import zu haben Die Anleitung empfahl einen <Import> ins Nachbar-Repository - das setzt voraus, dass beide Arbeitskopien nebeneinander liegen und in derselben Fassung stehen. Das Client-Projekt ist jetzt packbar und legt das Target unter build/ ins Paket, wo NuGet es selbst importiert. Ausserdem: Unauthorized wurde nur im statischen Zweig erkannt, im API-Zweig kam ein 401 als gewoehnlicher HTTP-Fehler an. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
245 lines
8.4 KiB
Markdown
245 lines
8.4 KiB
Markdown
# Ein Projekt veröffentlichungsfähig machen
|
|
|
|
> Für Coding-Agenten, die den UpdateService in ein Projekt integrieren.
|
|
> Ergebnis: `./scripts/release.ps1 -Version 1.4.3` baut, packt, lädt hoch und
|
|
> meldet das Release beim Deploymentcenter an — für alle Zielplattformen.
|
|
|
|
Es gibt bereits ein Werkzeug, das den schwierigen Teil erledigt:
|
|
**`pack-and-deploy`**. Es berechnet Prüfsummen, erzeugt das Dateimanifest,
|
|
schreibt die `latest.json` fort und meldet das Release über die API an. **Baue
|
|
das nicht nach.** Ein zweiter Ort für dieselbe Logik ist ein zweiter Ort, an
|
|
dem dieselben Fehler wieder entstehen — und dieses Werkzeug hat sie bereits
|
|
hinter sich.
|
|
|
|
Was fehlt, ist nur die Orchestrierung: pro Zielplattform einmal
|
|
`dotnet publish`, dann `pack-and-deploy`. Genau das ist die Vorlage.
|
|
|
|
---
|
|
|
|
## 1. Einrichten
|
|
|
|
Drei Dateien, einmalig:
|
|
|
|
```bash
|
|
mkdir -p scripts .dc-tools
|
|
|
|
# Windows
|
|
curl -fsSL https://dc.mhdf.de/docs/release-template/release.ps1 -o scripts/release.ps1
|
|
|
|
# Linux / CI
|
|
curl -fsSL https://dc.mhdf.de/docs/release-template/release.sh -o scripts/release.sh
|
|
chmod +x scripts/release.sh
|
|
|
|
# in beiden Fällen
|
|
curl -fsSL https://dc.mhdf.de/docs/release-template/release.config.example.json \
|
|
-o scripts/release.config.json
|
|
|
|
echo '.dc-tools/' >> .gitignore
|
|
echo 'artifacts/' >> .gitignore
|
|
```
|
|
|
|
**Nur `release.config.json` wird angepasst.** Die Skripte selbst bleiben
|
|
unverändert — dann lassen sie sich bei einer neuen Fassung einfach ersetzen.
|
|
|
|
```json
|
|
{
|
|
"project": "myapp",
|
|
"csproj": "src/MyApp/MyApp.csproj",
|
|
"runtimes": ["win-x64", "linux-x64"],
|
|
"selfContained": true,
|
|
"setupJson": "setup.json"
|
|
}
|
|
```
|
|
|
|
`release.config.json` enthält **keine Zugangsdaten** und darf versioniert
|
|
werden.
|
|
|
|
### Zugangsdaten
|
|
|
|
Die kommen aus Umgebungsvariablen:
|
|
|
|
```bash
|
|
export DC_FTP_HOST=ftp.example.com
|
|
export DC_FTP_USER=...
|
|
export DC_FTP_PASS=...
|
|
export DC_TOKEN=dc_master_... # braucht das Recht updateservice:publish
|
|
```
|
|
|
|
Alternativ eine `packager.config.json` neben dem Werkzeug — die steht dann in
|
|
`.gitignore`. Ohne `DC_TOKEN` wird das Paket zwar gebaut und hochgeladen, aber
|
|
**nicht angemeldet und nicht signiert**; der Rückgabewert ist dann 2.
|
|
|
|
### Voraussetzungen
|
|
|
|
| | Windows | Linux |
|
|
|---|---|---|
|
|
| Skript | `release.ps1` (PowerShell 5.1 genügt) | `release.sh` |
|
|
| Nötig | .NET SDK | .NET SDK, `curl`, dazu `jq` **oder** Python |
|
|
|
|
`pack-and-deploy` holt sich das Skript beim ersten Lauf selbst von
|
|
`/installer/`, prüft die Prüfsumme und legt es unter `.dc-tools/` ab. Das
|
|
Verzeichnis gehört in die `.gitignore`.
|
|
|
|
---
|
|
|
|
## 2. Veröffentlichen
|
|
|
|
```powershell
|
|
.\scripts\release.ps1 -Version 1.4.3 -Changelog "Behebt den Login-Fehler."
|
|
```
|
|
|
|
```bash
|
|
./scripts/release.sh --version 1.4.3 --changelog "Behebt den Login-Fehler."
|
|
```
|
|
|
|
Ohne `-Version` wird sie aus `Directory.Build.props` oder der `.csproj`
|
|
gelesen. Weitere Schalter: `-Channel beta`, `-Critical`, `-WhatIf`
|
|
beziehungsweise `--channel`, `--critical`, `--dry-run`.
|
|
|
|
Je Laufzeitkennung entsteht ein eigenes Release. **Ohne Plattformangabe würden
|
|
sie sich gegenseitig überschreiben** — bis Version 2.2 war genau das der Fall,
|
|
und ein Linux-System zog sich das Windows-Paket.
|
|
|
|
---
|
|
|
|
## 3. Was du im Projekt vorbereiten musst
|
|
|
|
### `<Version>` in die `Directory.Build.props`
|
|
|
|
```xml
|
|
<Project>
|
|
<PropertyGroup>
|
|
<Version>1.4.3</Version>
|
|
</PropertyGroup>
|
|
</Project>
|
|
```
|
|
|
|
**Nicht in einzelne `.csproj`-Dateien.** `pack-and-deploy` liest die Version
|
|
aus der Hauptassembly und **bricht bei einer Abweichung ab**. Das ist Absicht:
|
|
Wird `1.0.1` als `1.0.2` veröffentlicht, aktualisieren alle Clients, melden
|
|
danach weiterhin `1.0.1`, halten das Release erneut für neu — und
|
|
aktualisieren bei jedem Start wieder. Eine Endlosschleife über die gesamte
|
|
Installationsbasis.
|
|
|
|
### Konfigurationsdateien schützen
|
|
|
|
Zwei verschiedene Dinge, und die Verwechslung hat schon einen echten
|
|
API-Schlüssel öffentlich gemacht:
|
|
|
|
| | `excludePatterns` | `preservePatterns` |
|
|
|---|---|---|
|
|
| Im Paket? | nein | ja |
|
|
| Erstinstallation | fehlt | wird geschrieben |
|
|
| Update | — | vorhandene Datei bleibt unangetastet |
|
|
|
|
Eine `appsettings.json` mit echten Zugangsdaten gehört in **keine** von beiden
|
|
Listen — sie gehört gar nicht erst ins Publish-Verzeichnis. Was ausgeliefert
|
|
wird, muss eine **Vorlage mit Platzhaltern** sein.
|
|
|
|
**`pack-and-deploy` bricht ab, wenn es Zugangsdaten findet** — geprüft werden
|
|
Dateinamen (`appsettings.Local.json`, `master.key`, `*.pfx`, `*.db`, …) und der
|
|
Inhalt (gefüllte `Password=`-Felder, `sk-…`, `dc_master_…`, private Schlüssel).
|
|
Platzhalter wie `sk-DEIN-SCHLUESSEL-HIER` lösen nicht aus.
|
|
|
|
Häufigste Ursache: eine `CopyToOutputDirectory`-Regel in der `.csproj`, die
|
|
eine lokale Konfigurationsdatei ins Ausgabeverzeichnis kopiert. Dann gehört sie
|
|
in `excludePatterns` — und die Regel auf den Prüfstand.
|
|
|
|
Notausgang: `--allow-secrets`. **Nur benutzen, wenn du weißt, warum.**
|
|
Release-Pakete liegen hinter einem Zugangsschutz, aber jeder lizenzierte Kunde
|
|
kann sie auspacken.
|
|
|
|
### `setup.json` anlegen
|
|
|
|
Beschreibt, was die Erstinstallation abfragen muss. Das Skript kopiert sie ins
|
|
Publish-Verzeichnis, sodass sie im Paket landet.
|
|
|
|
```json
|
|
{
|
|
"schema": 1,
|
|
"targets": [
|
|
{ "id": "app", "file": "myapp/Settings.json", "location": "config" }
|
|
],
|
|
"fields": [
|
|
{ "key": "ConnectionStrings:Main", "label": "Datenbank", "type": "secret" },
|
|
{ "key": "Deploymentcenter:Token", "source": "provision",
|
|
"scopes": ["watchdog:ping"] }
|
|
]
|
|
}
|
|
```
|
|
|
|
Vollständig beschrieben in `docs/SETUP_INTEGRATION_GUIDE.md` im
|
|
Deploymentcenter-Repository.
|
|
Ohne `setup.json` lässt sich die Anwendung installieren, aber nicht einrichten.
|
|
|
|
### Das SDK einbinden
|
|
|
|
```csharp
|
|
var check = await new UpdateClient().CheckForUpdateAsync(
|
|
baseUrl: "https://dc.mhdf.de", projectId: "myapp",
|
|
currentVersion: BuildInfo.Version, channel: "prod",
|
|
credentials: ReleaseCredentials.FromLicenseKey(meineLizenz));
|
|
```
|
|
|
|
**Der Lizenzschlüssel ist Pflicht.** Die Release-Ablage liegt hinter
|
|
HTTP-Basic-Auth; ohne ihn bekommt die Anwendung 401 und keine Updates mehr.
|
|
Details in `docs/UPDATESERVICE_INTEGRATION_GUIDE.md` §5A
|
|
(Zugangsschutz der Release-Verzeichnisse).
|
|
|
|
> **Für ein Produkt, das noch nie veröffentlicht hat, gilt eine besondere
|
|
> Reihenfolge.** `/releases/<slug>/` existiert noch nicht und ist deshalb auch
|
|
> nicht geschützt. Das Verzeichnis entsteht mit dem ersten Upload, und der
|
|
> nächste Abgleich schützt es. Es gibt also kein Zeitfenster, um ein
|
|
> ungeschütztes Release zu ziehen und danach das SDK nachzurüsten: **der erste
|
|
> ausgelieferte Build muss die Zugangsdaten schon mitbringen.**
|
|
|
|
---
|
|
|
|
## 4. Rückgabewerte
|
|
|
|
| Wert | Bedeutung |
|
|
|---|---|
|
|
| `0` | vollständig veröffentlicht |
|
|
| `1` | Konfigurationsfehler oder Versionskonflikt — **nichts wurde ausgeführt** |
|
|
| `2` | teilweise: Upload oder Registrierung fehlgeschlagen |
|
|
|
|
Bei `1` bricht das Skript sofort ab, statt die übrigen Plattformen ins Leere
|
|
laufen zu lassen. Bei `2` läuft es weiter und meldet am Ende, welche
|
|
Plattformen betroffen sind — dort ist zu prüfen, ob die bereits hochgeladenen
|
|
Dateien zusammenpassen.
|
|
|
|
---
|
|
|
|
## 5. Prüfen, ob es getragen hat
|
|
|
|
```bash
|
|
curl "https://dc.mhdf.de/api/updateservice/v1/check?product=myapp&version=0.0.0&channel=prod&platform=win-x64"
|
|
```
|
|
|
|
Erwartet: `update_available: true` mit der neuen Version und `"signed": true`
|
|
in der Antwort des Publish-Aufrufs. Im WebUI erscheint das Release unter
|
|
*UpdateService → Releases* mit Plattform-Spalte; steht dort **UNSIGNIERT**,
|
|
fehlt auf dem Server der Signierschlüssel.
|
|
|
|
Und der Zugangsschutz:
|
|
|
|
```bash
|
|
curl -I https://dc.mhdf.de/releases/myapp/prod/win-x64/1.4.3/package.tar.gz # 401 erwartet
|
|
```
|
|
|
|
Antwortet das mit **200**, ist das Paket öffentlich abrufbar — dann im WebUI
|
|
unter *UpdateService → 🔒 Zugangsschutz* den Selbsttest laufen lassen.
|
|
|
|
---
|
|
|
|
## 6. Häufige Stolperstellen
|
|
|
|
| Symptom | Ursache |
|
|
|---|---|
|
|
| `Versionskonflikt` beim Packen | `<Version>` steht nur in einem von mehreren Projekten — gehört in die `Directory.Build.props` |
|
|
| Rückgabewert 2, „Registrierung fehlgeschlagen" | `DC_TOKEN` fehlt oder hat nicht `updateservice:publish` |
|
|
| `unknown_project` | Der Slug ist im Deploymentcenter nicht angelegt |
|
|
| Client bekommt 401 statt Updates | Kein `licenseKey` übergeben, oder die Lizenz ist abgelaufen |
|
|
| Linux-Paket startet nicht | Unter Windows gebaut — der Agent setzt das Ausführungsbit beim Anwenden, ein von Hand entpacktes Archiv nicht |
|
|
| Update lädt endlos erneut | Veröffentlichte Version weicht von der einkompilierten ab |
|