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
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:default → allow-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 --version → tauri-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 durchdarwin-aarch64.{{arch}}liefert die Architektur (x86_64,aarch64,i686,armv7,riscv64).{{bundle_type}}liefert den laufenden Bundle-Typ:app,msi,nsis,appimage,deboderrpm— 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}.os∈linux|darwin|windows,arch∈i686|x86_64|armv7|aarch64|riscv64,installer∈appimage|deb|rpm|app|msi|nsis. Die Kurzformdarwin-aarch64reicht also; die lange Formwindows-x86_64-nsisist nur nötig, wenn man MSI und NSIS gleichzeitig ausliefert. versionwird als Semver geparst. Ein Update wird nur angeboten, wennrelease.version > current_version— die Version kommt aus dem Feldversionintauri.conf.json(heute0.1.0). Ein Release ohne Versionsbump wird stillschweigend ignoriert.signatureist der Inhalt der zugehörigen.sig-Datei, nicht deren URL.notesundpub_datesind optional;pub_dateals RFC 3339.- Ein dynamischer Endpoint darf mit
204 No Contentantworten, wenn es kein Update gibt. Dann liefert er statt derplatforms-Map ein flaches Objekt mitversion,urlundsignaturefü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
versioninsrc-tauri/tauri.conf.jsonerhöhen (Semver; der Vergleich entscheidet, ob ein Update angeboten wird).package.jsonundsrc-tauri/Cargo.tomlder Konsistenz halber mitziehen.- Auf jedem Plattform-Host mit gesetzten
TAURI_SIGNING_PRIVATE_KEY*-Variablen und dem Release-Overlay bauen. - Installer und Updater-Artefakte einsammeln,
.sig-Inhalte auslesen. latest.jsonschreiben und zusammen mit den Artefakten unter einerhttps-URL veröffentlichen. Die URL muss zumendpoints-Eintrag passen.- 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.jsonerzeugt (scripts/ist leer). - Keine Code-Signierung/Notarisierung für macOS und keine Authenticode-Signatur
für Windows konfiguriert (
bundleenthält wedermacOS.signingIdentitynochwindows.certificateThumbprint). Der Tauri-Updater-Schlüssel ersetzt das nicht — ohne Notarisierung meldet Gatekeeper die App als nicht verifiziert. - Die Felder
activeunddialoginplugins.updatersind v1-Reste ohne Wirkung (siehe oben) und sollten beim Scharfschalten entfernt werden.