Files
maarcadetweet/docs/tauri-release.md
T
tomdeboneandClaude Opus 5 ec8fe187fe docs: Deployment-, Architektur- und Release-Doku; README aktualisiert
docs/ war leer. Jetzt drei Dateien, jede Behauptung am Code verifiziert:

* deployment.md — docker compose, Migrationsweg, vollständige Env-Referenz
  mit den Fallstricken (P-256-taugliches PDS_JWT_SECRET, Pflicht-aber-tot
  S3_BUCKET_APPVIEW), Release-Build, systemd-Units, Reverse-Proxy inkl. des
  Hinweises, dass die AppView CORS Any liefert und der Proxy den Header
  ersetzen statt ergänzen muss, Health-Checks und Cursor-Verhalten beim
  Neustart.
* architecture.md — Crate-Verantwortlichkeiten, ASCII-Datenfluss, beide
  DB-Schemata. Hält fest, was die Topologie erklärt: die eigene PDS speist
  keinen Firehose, eigene Records erreichen die AppView nur über den
  Best-Effort-Push.
* tauri-release.md — Signing-Keys, v2-Updater-Config, latest.json, Build pro
  Plattform. Der _comment in tauri.conf.json war irreführend: active/dialog
  sind v1-Reste, die der v2-Updater ignoriert; dass nichts passiert, liegt
  daran, dass niemand check() aufruft und das Plugin nicht installiert ist.

README bekommt Phase 8, eine Doku-Übersicht, korrigierte Testanleitung
(src-tauri ist ein eigener Workspace und wird von cargo test --workspace
nicht erfasst) und einen Abschnitt "Bekannte Lücken" statt der bisher
lückenlosen Erfolgsmeldung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
2026-09-09 21:37:12 +02:00

13 KiB

Tauri-Release und Auto-Update

Der Desktop-Client ist auf Auto-Update vorbereitet, aber nicht scharf geschaltet. Dieses Dokument beschreibt den vollständigen Weg von den Signing-Keys bis zum ausgelieferten latest.json — und was dafür im Repo noch fehlt.

Alle Pfade sind relativ zu crates/tauri-app/, sofern nicht anders angegeben.

Ist-Zustand

Baustein Stand
Rust-Plugin tauri_plugin_updater::Builder::new().build() ist in src-tauri/src/lib.rs registriert
Capability src-tauri/capabilities/default.json enthält updater:defaultallow-check, allow-download, allow-install, allow-download-and-install für das main-Window
Config src-tauri/tauri.conf.json, Block plugins.updater: leerer pubkey, Platzhalter-Endpoint https://releases.maarcadetweet.local/{{target}}/{{arch}}/{{current_version}}
Updater-Artefakte bundle.createUpdaterArtifacts ist nicht gesetzt (Default: aus) — der Build erzeugt keine .sig-Dateien und kein .app.tar.gz
Aufruf im Client Fehlt. Weder das npm-Paket @tauri-apps/plugin-updater noch ein check()-Aufruf existieren (weder in src/ noch in src-tauri/src/)
CLI @tauri-apps/cli ist als devDependency installiert, lokal geprüft: npx tauri --versiontauri-cli 2.11.4
Plugin-Version tauri-plugin-updater 2.10.1 laut src-tauri/Cargo.lock

Wichtig: Die Felder active und dialog in plugins.updater stammen aus Tauri v1. Der v2-Updater kennt sie nicht — seine Config besteht aus endpoints, pubkey, windows, dangerousInsecureTransportProtocol, dangerousAcceptInvalidCerts, dangerousAcceptInvalidHostnames. Unbekannte Schlüssel werden beim Deserialisieren still ignoriert. "active": false schaltet den Updater also nicht ab; dass heute nichts passiert, liegt allein daran, dass niemand check() aufruft. Wer den Updater wirklich deaktivieren will, entfernt den plugins.updater-Block und die tauri_plugin_updater-Registrierung.

1. Signing-Keys erzeugen

Der Updater akzeptiert nur signierte Artefakte. Das Schlüsselpaar wird einmal erzeugt und danach nie wieder gewechselt — ein neuer Public Key macht alle installierten Clients update-unfähig.

cd crates/tauri-app
npx tauri signer generate -w ~/.tauri/maarcadetweet.key

Optionen laut npx tauri signer generate --help:

Flag Bedeutung
-w, --write-keys <PFAD> Privaten Schlüssel in eine Datei schreiben (sonst nur stdout)
-p, --password <PW> Passwort für den privaten Schlüssel setzen
-f, --force Vorhandene Datei überschreiben
--ci Keine interaktiven Rückfragen

Ergebnis: ~/.tauri/maarcadetweet.key (privat, niemals ins Repo) und ~/.tauri/maarcadetweet.key.pub (öffentlich, base64). Der Inhalt der .pub-Datei ist genau das, was in pubkey gehört.

Für den Build-Rechner bzw. die CI:

export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/maarcadetweet.key)"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="…"

TAURI_SIGNING_PRIVATE_KEY akzeptiert den Schlüsselinhalt oder einen Pfad. Fehlt die Variable beim Build mit aktivierten Updater-Artefakten, bricht das Bundling ab.

2. Konfiguration — ohne den Dev-Zustand zu brechen

Die laufende src-tauri/tauri.conf.json soll unangetastet bleiben (leerer pubkey, keine Updater-Artefakte). Die CLI kann Konfigurationen zur Build-Zeit zusammenführen:

-c, --config <CONFIG>   JSON strings or paths to JSON, JSON5 or TOML files to
                        merge with the default configuration file

Der saubere Weg ist deshalb eine Release-Overlay-Datei, die nur die Release-Unterschiede enthält. Anlegen als crates/tauri-app/src-tauri/tauri.release.conf.json (existiert noch nicht):

{
  "bundle": {
    "createUpdaterArtifacts": true
  },
  "plugins": {
    "updater": {
      "endpoints": [
        "https://releases.example.org/maarcadetweet/latest.json"
      ],
      "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6…HIER_DER_INHALT_VON_.key.pub…",
      "windows": {
        "installMode": "passive"
      }
    }
  }
}

Build damit:

cd crates/tauri-app
npx tauri build --config src-tauri/tauri.release.conf.json

Feldbedeutungen (geprüft an tauri-plugin-updater 2.11 / tauri-utils 2.9):

Feld Bedeutung
bundle.createUpdaterArtifacts true erzeugt die Updater-Artefakte (.sig, unter macOS zusätzlich das .app.tar.gz). Alternativ "v1Compatible" für v1-Clients
plugins.updater.pubkey Public Key aus Schritt 1. Pflichtfeld — fehlt es ganz, scheitert das Deserialisieren der Plugin-Config
plugins.updater.endpoints Liste von URLs, der Reihe nach probiert. Müssen https sein: in Release-Builds bricht ein http-Endpoint mit InsecureTransportProtocol ab (im Debug-Build nur eine Warnung), es sei denn dangerousInsecureTransportProtocol ist gesetzt
plugins.updater.windows.installMode passive (Default), basicUi oder quiet

Endpoint-Platzhalter

In der Endpoint-URL werden ersetzt: {{current_version}}, {{target}}, {{arch}} und {{bundle_type}}. Achtung auf einen Fallstrick — der bereits in der Config stehende Platzhalter-Endpoint benutzt genau diese Form:

  • {{target}} wird in der URL nur durch das Betriebssystem ersetzt (linux, darwin, windows) — nicht durch darwin-aarch64.
  • {{arch}} liefert die Architektur (x86_64, aarch64, i686, armv7, riscv64).
  • {{bundle_type}} liefert den laufenden Bundle-Typ: app, msi, nsis, appimage, deb oder rpm — bzw. unknown, wenn er nicht bestimmbar ist.

Ein statisches latest.json (eine Datei für alle Plattformen) ist der einfachere Weg und kommt ohne Platzhalter aus.

3. latest.json

Der Updater versteht zwei Formate. Für einen einfachen Static-File-Server ist das statische Format richtig:

{
  "version": "0.2.0",
  "notes": "Thread-Ansicht, schnellere Timeline",
  "pub_date": "2026-01-15T10:00:00Z",
  "platforms": {
    "darwin-aarch64": {
      "signature": "<Inhalt von maarcadetweet.app.tar.gz.sig>",
      "url": "https://releases.example.org/maarcadetweet/0.2.0/maarcadetweet_aarch64.app.tar.gz"
    },
    "darwin-x86_64": {
      "signature": "…",
      "url": "https://releases.example.org/maarcadetweet/0.2.0/maarcadetweet_x64.app.tar.gz"
    },
    "linux-x86_64": {
      "signature": "…",
      "url": "https://releases.example.org/maarcadetweet/0.2.0/maarcadetweet_0.2.0_amd64.AppImage"
    },
    "windows-x86_64": {
      "signature": "…",
      "url": "https://releases.example.org/maarcadetweet/0.2.0/maarcadetweet_0.2.0_x64-setup.exe"
    }
  }
}

Regeln, die der Plugin-Code vorgibt:

  • Schlüssel der platforms-Map: Der Updater sucht in dieser Reihenfolge nach {os}-{arch}-{installer} und danach nach {os}-{arch}. oslinux | darwin | windows, archi686 | x86_64 | armv7 | aarch64 | riscv64, installerappimage | deb | rpm | app | msi | nsis. Die Kurzform darwin-aarch64 reicht also; die lange Form windows-x86_64-nsis ist nur nötig, wenn man MSI und NSIS gleichzeitig ausliefert.
  • version wird als Semver geparst. Ein Update wird nur angeboten, wenn release.version > current_version — die Version kommt aus dem Feld version in tauri.conf.json (heute 0.1.0). Ein Release ohne Versionsbump wird stillschweigend ignoriert.
  • signature ist der Inhalt der zugehörigen .sig-Datei, nicht deren URL.
  • notes und pub_date sind optional; pub_date als RFC 3339.
  • Ein dynamischer Endpoint darf mit 204 No Content antworten, wenn es kein Update gibt. Dann liefert er statt der platforms-Map ein flaches Objekt mit version, url und signature für genau die anfragende Plattform.

4. Build pro Plattform

tauri build ist ein Cross-Build nur eingeschränkt — jede Plattform wird auf ihrem eigenen Host (oder Runner) gebaut.

cd crates/tauri-app

# macOS (Apple Silicon)
npx tauri build --config src-tauri/tauri.release.conf.json

# macOS Universal (beide Rust-Targets müssen installiert sein)
npx tauri build --target universal-apple-darwin \
                --config src-tauri/tauri.release.conf.json

# Windows / Linux jeweils auf dem passenden Host, gleicher Aufruf

Ablauf laut build.beforeBuildCommand in tauri.conf.json: erst npm run build (Vite → crates/tauri-app/dist, entspricht frontendDist: "../dist"), dann der Cargo-Release-Build, dann das Bundling. bundle.targets steht auf "all".

Nützliche Flags (aus npx tauri build --help):

Flag Zweck
-t, --target <TRIPLE> Ziel-Triple, oder universal-apple-darwin
-b, --bundles <LISTE> Nur bestimmte Bundle-Typen erzeugen
--no-bundle Nur das Binary, kein Installer
-c, --config <PFAD> Config-Overlay (siehe oben)
--ci Keine interaktiven Rückfragen

5. Wo die Artefakte landen

crates/tauri-app/src-tauri/Cargo.toml deklariert ein eigenes [workspace], der Build-Output liegt deshalb nicht im Repo-Root-target/, sondern unter crates/tauri-app/src-tauri/target/release/bundle/. Mit --target <triple> schiebt sich das Triple dazwischen: src-tauri/target/<triple>/release/bundle/.

Plattform Pfad unter bundle/ Für den Updater relevant
macOS macos/maarcadetweet.app macos/maarcadetweet.app.tar.gz + .tar.gz.sig
macOS dmg/maarcadetweet_<version>_<arch>.dmg nein (nur für die Erstinstallation)
Windows msi/maarcadetweet_<version>_<arch>_<lang>.msi .msi + .msi.sig
Windows nsis/maarcadetweet_<version>_<arch>-setup.exe .exe + .exe.sig
Linux appimage/maarcadetweet_<version>_<arch>.AppImage .AppImage + .AppImage.sig
Linux deb/…deb, rpm/…rpm nein

Die .sig-Dateien entstehen nur, wenn createUpdaterArtifacts aktiv ist und die Signing-Env-Variablen gesetzt sind. Für das latest.json wird der Dateiinhalt der .sig gebraucht, das Artefakt selbst wird nicht ausgeliefert.

6. Was im Client noch fehlt

Config und Signaturen allein bewirken nichts — der Client fragt nirgends nach Updates. Es gibt zwei Wege:

a) Vom Frontend aus

cd crates/tauri-app
npm install @tauri-apps/plugin-updater @tauri-apps/plugin-process
import { check } from "@tauri-apps/plugin-updater";
import { relaunch } from "@tauri-apps/plugin-process";

const update = await check();
if (update) {
  await update.downloadAndInstall();
  await relaunch();
}

Die Capability erlaubt check, download, install und downloadAndInstall bereits. Zusätzlich nötig: tauri-plugin-process als Rust-Dependency, tauri_plugin_process::init() in lib.rs und "process:default" in capabilities/default.json — sonst schlägt relaunch() fehl.

b) Vom Rust-Layer aus

use tauri_plugin_updater::UpdaterExt;

if let Ok(Some(update)) = app.updater()?.check().await {
    update.download_and_install(|_, _| {}, || {}).await?;
}

Ohne npm-Abhängigkeit, dafür ohne UI-Kontrolle im Frontend. Der Aufruf gehört in den setup-Hook oder hinter ein Tauri-Command, das die Svelte-Seite aufrufen kann.

Welcher Weg genommen wird, ist noch offen — im Repo existiert keiner von beiden.

7. Release-Checkliste

  1. version in src-tauri/tauri.conf.json erhöhen (Semver; der Vergleich entscheidet, ob ein Update angeboten wird). package.json und src-tauri/Cargo.toml der Konsistenz halber mitziehen.
  2. Auf jedem Plattform-Host mit gesetzten TAURI_SIGNING_PRIVATE_KEY*-Variablen und dem Release-Overlay bauen.
  3. Installer und Updater-Artefakte einsammeln, .sig-Inhalte auslesen.
  4. latest.json schreiben und zusammen mit den Artefakten unter einer https-URL veröffentlichen. Die URL muss zum endpoints-Eintrag passen.
  5. Mit einer älteren installierten Version gegenprüfen, dass check() das Update findet und die Signaturprüfung durchgeht.

8. Offene Punkte

  • Kein Release-Overlay im Repo — die Datei aus Abschnitt 2 muss angelegt werden. Der Endpoint https://releases.maarcadetweet.local/… in der aktuellen Config ist ein Platzhalter und existiert nicht.
  • Kein Update-Server, kein CI-Workflow, kein Skript, das latest.json erzeugt (scripts/ ist leer).
  • Keine Code-Signierung/Notarisierung für macOS und keine Authenticode-Signatur für Windows konfiguriert (bundle enthält weder macOS.signingIdentity noch windows.certificateThumbprint). Der Tauri-Updater-Schlüssel ersetzt das nicht — ohne Notarisierung meldet Gatekeeper die App als nicht verifiziert.
  • Die Felder active und dialog in plugins.updater sind v1-Reste ohne Wirkung (siehe oben) und sollten beim Scharfschalten entfernt werden.