Zum Hauptinhalt springen

Vertrag: Releases und Update-Check

Gemeinsame Schnittstelle zwischen der App (/, nativ auf Debian) und der Homepage (site/, Docker, https://disk-tester.znbw.de). Änderungen nur abgestimmt und abwärtskompatibel (Felder nur hinzufügen).

1. Release-Paket (erzeugt von der CI bei Tag vX.Y.Z)​

Das Repo bleibt privat. Die CI baut bei jedem Tag v* in einem Lauf das App-Paket und das Homepage-Image; das Paket wird in das Image eingebaut. Die Homepage braucht dadurch keinen GitHub-Zugriff und keinen Token. Zusätzlich legt die CI ein GitHub-Release mit denselben Dateien als Archiv an.

Dateien:

AssetInhalt
disk-tester-vX.Y.Z-linux-x64.tar.gzfertig gebaute App, kein Build auf dem Zielsystem nötig
disk-tester-vX.Y.Z-linux-x64.tar.gz.sha256eine Zeile im sha256sum-Format: <hex> disk-tester-vX.Y.Z-linux-x64.tar.gz

Inhalt des Tarballs (oberstes Verzeichnis disk-tester-vX.Y.Z/):

disk-tester-vX.Y.Z/
server.js, .next/, node_modules/, public/ Next.js standalone output inkl. .next/static
package.json version = X.Y.Z
deploy/install.sh erkennt, dass es aus einem Release läuft (kein npm ci/build)
deploy/update.sh
deploy/disk-tester.service
VERSION Inhalt "X.Y.Z\n"

Release-Notes = Abschnitt ## [X.Y.Z] aus CHANGELOG.md (Markdown).

Im Homepage-Image liegen unter /app/releases/: der Tarball, die .sha256-Datei und release.json ({ version, tag, publishedAt, notes, asset, sha256, sizeBytes }). Es ist immer genau das neueste Release enthalten.

2. Update-Check-API (bereitgestellt von der Homepage)​

GET https://disk-tester.znbw.de/api/latest → 200 application/json, CORS *, Cache max. 5 min:

{
"name": "disk-tester",
"version": "0.3.0",
"tag": "v0.3.0",
"publishedAt": "2026-11-01T12:00:00Z",
"notes": "Markdown der Release-Notes",
"notesUrl": "https://disk-tester.znbw.de/download#v0.3.0",
"download": {
"url": "https://disk-tester.znbw.de/download/v0.3.0/disk-tester-v0.3.0-linux-x64.tar.gz",
"sha256": "<64 hex>",
"sizeBytes": 12345678
}
}
  • 404 {"error": "..."} wenn das Image kein Release enthält.
  • GET /api/releases → { "releases": [ <gleiches Objekt wie oben> ] } (nur das enthaltene, neueste Release).
  • GET /download/<tag>/<asset> → liefert die Datei aus /app/releases/ (Stream, Content-Length, Content-Disposition: attachment). Nur die enthaltenen Dateien, sonst 404.
  • GET /install.sh → Bootstrap-Skript für Debian (root): lädt /api/latest, lädt den Tarball, prüft SHA256, entpackt nach einem Temp-Verzeichnis und führt deploy/install.sh daraus aus.

3. Verhalten der App​

  • Basis-URL per Env DISK_TESTER_UPDATE_URL (Default https://disk-tester.znbw.de), DISK_TESTER_UPDATE_CHECK=0 schaltet ab.
  • Prüft höchstens alle 6 h GET <base>/api/latest (nach einem fehlgeschlagenen Abruf erneut nach 30 min); Versionsvergleich per SemVer gegen die eigene Version; Fehler werden still ignoriert.
  • Die Fußzeile zeigt bei einer neueren Version „Update verfügbar: vX“ mit Link auf notesUrl und dem Befehl /appdata/disk-tester/app/deploy/update.sh.
  • deploy/update.sh [latest|vX.Y.Z|--rollback] [--force] lädt über download.url, prüft sha256, installiert nach /appdata/disk-tester/releases/<version>, schaltet /appdata/disk-tester/app (Symlink) um, startet den Dienst neu. --rollback schaltet den Symlink auf die zuvor installierte Version zurück.
  • Die Oberfläche kann das Update selbst starten (POST /api/update, startet update.sh per systemd-run --unit=disk-tester-update), wenn die Installation das Release-Layout nutzt. Optional installiert die App neue Versionen automatisch (Settings.autoUpdate), sobald nichts läuft.
  • Installation per Einzeiler: curl -fsSL https://disk-tester.znbw.de/install.sh | sudo bash.

4. Laufzeit-Schnittstellen der App​

  • GET /api/health enthält zusätzlich activeJobs (Anzahl laufender Jobs) und autoPending (offene Automatik-Countdowns). update.sh verweigert den Neustart, wenn eine der Zahlen größer als 0 ist (--force überschreibt). Fehlen die Felder (ältere App) oder antwortet der Dienst nicht, läuft das Update weiter.
  • update.sh schreibt <DISK_TESTER_DATA_DIR>/update-status.json (Standard /appdata/disk-tester/data, atomar, Modus 0600):
{ "state": "running", "from": "0.3.0", "to": "0.4.0", "at": "2026-11-01T12:00:00Z", "detail": null }

state: running (beim Start), danach succeeded, failed, rolled-back (neue Version nicht gesund, alte wiederhergestellt) oder refused (Jobs/Countdowns aktiv). detail ist ein kurzer englischer Grund. Ein running-Eintrag, der älter als 30 min ist, gilt als Überbleibsel eines abgebrochenen Laufs.

  • Gleichzeitige Läufe verhindert flock auf <base>/update.lock.