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
This commit is contained in:
co-authored by
Claude Opus 5
parent
31880e1005
commit
ec8fe187fe
@@ -0,0 +1,311 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user