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
312 lines
13 KiB
Markdown
312 lines
13 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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 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.
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
cd crates/tauri-app
|
|
npm install @tauri-apps/plugin-updater @tauri-apps/plugin-process
|
|
```
|
|
|
|
```ts
|
|
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
|
|
|
|
```rust
|
|
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.
|