--- phase: 18-desktop-client-fertigstellen plan: 05 type: execute wave: 3 depends_on: ["18-02", "18-04"] files_modified: - .gitea/workflows/ci.yml - .gitea/scripts/desktop-collect.sh - .gitea/scripts/publish-release.sh - apps/desktop/src-tauri/Cargo.toml - apps/desktop/src-tauri/Cargo.lock autonomous: false requirements: [DESK-01, DESK-04, DESK-05] user_setup: [] estimate: tokens: 70000 raw_tokens: 70000 tasks: 3 confidence: low must_haves: truths: - "Der CI-Job desktop baut auf dem Linux-Runner zusaetzlich den Windows-Installer per Cross-Bau (cargo-xwin, NSIS) und sammelt `Tessera-Setup-X.Y.Z.exe` neben `Tessera-X.Y.Z.AppImage` ein; das Manifest traegt beide Plattformen (D-04, D-05)." - "Ein Push auf main endet mit einem gruenen Lauf: Job desktop mit beiden Dateien, Job publish mit Abbildern, die die Pakete tragen (D-06, D-08)." - "Jeder Fehlschlag der Pipeline wird gelesen, der Job angepasst, erneut gepusht — hoechstens drei Runden, jede als normaler Commit (D-16)." artifacts: - path: ".gitea/workflows/ci.yml" provides: "Windows-Cross-Bau-Schritte im Job desktop, Einsammeln mit --require linux,windows" contains: "cargo-xwin" key_links: - from: ".gitea/workflows/ci.yml (Schritt Windows NSIS Cross-Bau)" to: ".gitea/scripts/desktop-collect.sh" via: "Bundle-Verzeichnis target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe -> Tessera-Setup-X.Y.Z.exe" pattern: "x86_64-pc-windows-msvc" - from: ".gitea/workflows/ci.yml (desktop)" to: ".gitea/workflows/ci.yml (publish)" via: "actions/cache Schluessel desktop-dist-${{ gitea.sha }} (aus 18-02)" pattern: "desktop-dist-" --- Der Windows-Installer entsteht im selben CI-Job wie das AppImage — als Cross-Bau auf dem Linux-Runner (`cargo tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis`, NSIS aus dem Ubuntu-Paket). Weil dieser Bau nur in der Pipeline beweisbar ist (kein Windows-Werkzeug auf dem Entwicklungsrechner, siehe RESEARCH), enthaelt der Plan die in D-16 vorgesehene Iterationsschleife: pushen, Protokoll lesen, Job anpassen, erneut pushen — hoechstens drei Runden. Purpose: D-04, D-05, D-06 und D-16 aus 18-CONTEXT.md; Erfolgskriterium 1 (bis auf den Release-Anhang, der erst beim naechsten Freigabe-Tag sichtbar wird — der Upload-Pfad selbst ist in 18-02 gebaut und per Probelauf geprueft). Output: Erweiterter Job `desktop`, gruener Pipeline-Lauf mit beiden Dateien, Beta-Abbilder mit Paketen. **Rollen:** Der Executor pusht nie. Der Orchestrator pusht (`git push`; die Push-Adresse zeigt auf `localhost:3002`), beobachtet den Lauf in Gitea und meldet Status und Protokollauszug zurueck. Der Executor liest, behebt, committet. ## Artifacts this phase produces Dieser Plan: `.gitea/workflows/ci.yml` (Schritte "Windows-Werkzeuge", "Windows-Installer bauen (Cross-Bau)", erweiterte Cache-Pfade, Einsammeln mit `--require linux,windows`); bei Bedarf Korrekturen an `.gitea/scripts/desktop-collect.sh`, `.gitea/scripts/publish-release.sh` (`GITEA_API`-Umgehung) und `apps/desktop/src-tauri/Cargo.toml` (`rustls-tls`-Ausweichlösung). Gesamtliste der Phase: siehe 18-01-PLAN.md. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/18-desktop-client-fertigstellen/18-CONTEXT.md @.planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md @.planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md @.planning/phases/18-desktop-client-fertigstellen/18-02-SUMMARY.md @.planning/phases/18-desktop-client-fertigstellen/18-04-SUMMARY.md @.gitea/workflows/ci.yml @.gitea/scripts/desktop-collect.sh @.gitea/scripts/publish-release.sh @apps/desktop/src-tauri/Cargo.toml Task 1: Windows-Cross-Bau in den Job desktop einbauen Reine Workflow-Schritte; Rueckbau ist ein Commit, kein Zustand ausserhalb des Runners ausser dem Cache. .gitea/workflows/ci.yml .gitea/workflows/ci.yml (Job desktop aus 18-02), .gitea/scripts/desktop-collect.sh (Windows-Zweig: Bundle-Pfad und Zielname), .planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md (Abschnitte "Pattern 1", "Standard Stack: Installation", "Common Pitfalls 2-5", "Open Questions 2-3"), .planning/phases/18-desktop-client-fertigstellen/18-CONTEXT.md (Abschnitt "Specific Ideas": Runner 8 Kerne/15 GB, nacheinander im selben Job) Im Job `desktop` (Datei `.gitea/workflows/ci.yml`) folgende Aenderungen, Schrittnamen deutsch: 1. Schritt "Systemabhaengigkeiten": die apt-Liste um `lld llvm clang nsis` erweitern (alle vier am 2026-09-16 im Runner-Abbild per `apt-cache policy` bestaetigt: lld/llvm/clang 18, nsis 3.09). 2. Neuer Schritt "Windows-Werkzeuge" nach "Rust-Toolchain": `rustup target add x86_64-pc-windows-msvc` und `command -v cargo-xwin >/dev/null 2>&1 || cargo install --locked cargo-xwin` (Legitimitaetspruefung in RESEARCH: `OK`, rust-cross/cargo-xwin, 0.23.1). 3. Schritt "Cargo-Zwischenspeicher": `path` um `~/.cargo/bin/cargo-xwin`, `~/.cache/cargo-xwin` (Windows-SDK-Ablage von cargo-xwin, mehrere hundert MB, soll nur einmal geladen werden) und `~/.local/share/tauri` (NSIS-Plugins, die der Tauri-Bundler beim ersten Windows-Bau laedt) erweitern. 4. Schritt "Alte Bundles entfernen": zusaetzlich `rm -rf apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle`. 5. Neuer Schritt "Windows-Installer bauen (Cross-Bau)" **nach** dem AppImage-Schritt (nacheinander, ein Job, ein Cache — CONTEXT "Specific Ideas"): `pnpm --filter @tessera/desktop exec tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis`. 6. Schritt "Pakete einsammeln": `--require linux,windows`. 7. Kopfkommentar des Jobs: zwei Saetze zum Cross-Bau und zum Grund, warum die Version rein numerisch bleibt (Pitfall 2). Keine `-j`-Begrenzung und keine `CARGO_BUILD_JOBS`-Vorgabe im ersten Anlauf; beides ist eine Ausweichlösung der Schleife (Task 3), falls der Runner den Speicher ausschoepft. `desktop-collect.sh` braucht keine Aenderung, wenn der Windows-Zweig aus 18-01 (desktop-collect.sh) den Pfad `target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe` bereits kennt — pruefen, sonst nachziehen. - `grep -c 'cargo-xwin' .gitea/workflows/ci.yml` ergibt mindestens 3 (Install, Cache-Pfad, Bauschritt). - `grep -c -- '--target x86_64-pc-windows-msvc --bundles nsis' .gitea/workflows/ci.yml` ergibt 1. - `grep -c 'desktop-collect.sh --require linux,windows' .gitea/workflows/ci.yml` ergibt 1; `grep -c 'desktop-collect.sh --require linux$' .gitea/workflows/ci.yml` ergibt 0. - Die apt-Zeile enthaelt `nsis`, `lld`, `llvm` und `clang` (`grep -E 'lld llvm clang nsis|nsis' .gitea/workflows/ci.yml`). - `grep -c 'x86_64-pc-windows-msvc/release/bundle/nsis' .gitea/scripts/desktop-collect.sh` ergibt mindestens 1. - `sh -n .gitea/scripts/desktop-collect.sh` endet mit 0. cd /home/vicolab/projects/tessera-ctl && test "$(grep -c 'cargo-xwin' .gitea/workflows/ci.yml)" -ge 3 && grep -q -- '--target x86_64-pc-windows-msvc --bundles nsis' .gitea/workflows/ci.yml && grep -q 'desktop-collect.sh --require linux,windows' .gitea/workflows/ci.yml && grep -q 'nsis' .gitea/workflows/ci.yml && grep -q 'x86_64-pc-windows-msvc/release/bundle/nsis' .gitea/scripts/desktop-collect.sh && sh -n .gitea/scripts/desktop-collect.sh && node -e "const y=require('fs').readFileSync('.gitea/workflows/ci.yml','utf8');const d=y.indexOf('\n desktop:'),p=y.indexOf('\n publish:');if(d===-1||p===-1||d>p)process.exit(1);const job=y.slice(d,p);if(job.indexOf('--bundles appimage')>job.indexOf('--bundles nsis'))process.exit(2)" && echo WINDOWS-STEPS-OK Ein Kennzeichen fehlt, das Einsammeln fordert Windows nicht, das Sammel-Skript kennt den NSIS-Pfad nicht, oder der Windows-Schritt steht vor dem AppImage-Schritt (Exit 2) — `WINDOWS-STEPS-OK` fehlt. Der Job `desktop` installiert Windows-Werkzeuge, baut nach dem AppImage den NSIS-Installer per Cross-Bau, cached SDK und NSIS-Plugins und sammelt beide Dateien ein. Commit liegt bereit fuer den Push. Task 2: Push und CI-Lauf beobachten — gruen mit beiden Dateien? Der Commit aus Task 1 (bzw. aus der letzten Runde von Task 3) liegt lokal auf `main`; der Gitea-Runner `gitea-runner` laeuft (Container aktiv), das Secret `REGISTRY_TOKEN` ist gesetzt. Den Stand nach Gitea pushen und den Pipeline-Lauf "Tessera CI/CD" beobachten — der Executor darf nicht pushen (Projektregel: Push nur durch Orchestrator/Nutzer, Push-Adresse zeigt dauerhaft auf `localhost:3002`, nie ueber `git.vicolab.de`). Der Executor hat den Job `desktop` um den Windows-Cross-Bau erweitert, Skripte und Workflow statisch geprueft und committet. Was jetzt nur der Orchestrator kann: `git push` auf `main` und den Lauf in Gitea verfolgen (Gitea-MCP oder Weboberflaeche). Der erste Lauf dauert deutlich laenger als bisher (Rust-Toolchain, zwei Release-Baue, Windows-SDK-Download); erst mit warmem Cache sinkt die Zeit. Zurueckmelden — je nach Ausgang: **Gruen:** Aus dem Job `desktop`, Schritt "Pakete einsammeln", die beiden Ausgabezeilen (`windows: Tessera-Setup-1.1.0-beta.{sha}.exe (…)` und `linux: Tessera-1.1.0-beta.{sha}.AppImage (…)`), dazu Status des Jobs `publish` (gruen) und die Zeile mit den gepushten Etiketten. **Rot:** Name des gescheiterten Jobs und Schritts sowie die letzten rund 60 Protokollzeilen dieses Schritts (mit der eigentlichen Fehlermeldung — bei Rust die Zeilen ab `error:` bzw. `error[E…]`, bei apt die Zeile `E:`, bei curl den HTTP-Code und die Antwort). Diese Runde zaehlt (Runde 1 von hoechstens 3). Der Lauf ist gruen; Schritt "Pakete einsammeln" nennt genau eine `.exe` und genau ein `.AppImage`; der Job `publish` hat die Beta-Abbilder gepusht. Der Executor prueft nach der Rueckmeldung zusaetzlich `git status --porcelain` (leer) und dass `git log -1 --format=%H` dem vom Orchestrator genannten Lauf-Commit entspricht. Antworte mit "gruen" plus den beiden Dateizeilen — oder mit "rot" plus Job, Schritt und Protokollauszug. Der Lauf ist gruen; Schritt "Pakete einsammeln" nennt genau eine `.exe` und genau ein `.AppImage`; der Job `publish` hat die Beta-Abbilder gepusht. Rueckmeldung liegt vor. Bei "gruen" ist der Plan fertig (Task 3 entfaellt). Bei "rot" geht es mit Task 3 weiter. Task 3: Iterationsschleife — Fehler lesen, Job anpassen, erneut pushen (hoechstens drei Runden) .gitea/workflows/ci.yml, .gitea/scripts/desktop-collect.sh, .gitea/scripts/publish-release.sh, apps/desktop/src-tauri/Cargo.toml, apps/desktop/src-tauri/Cargo.lock Der vom Orchestrator gelieferte Protokollauszug, .planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md (Abschnitte "Common Pitfalls 1-5", "Open Questions", "Assumptions Log"), .gitea/workflows/ci.yml, .gitea/scripts/desktop-collect.sh Nur ausfuehren, wenn Task 2 "rot" gemeldet hat. Je Runde: Ursache aus dem Protokoll bestimmen, **eine** gezielte Aenderung machen, lokal pruefen (`sh -n` fuer Skripte, `cargo check` bei Cargo-Aenderungen), committen mit `ci(desktop): Runde N — {Ursache in fuenf Woertern}`, dann zurueck zu Task 2 (der Orchestrator pusht und meldet). Nach der dritten roten Runde **stoppen** und dem Nutzer den Stand mit dem letzten Protokollauszug vorlegen (kein vierter Versuch ohne Ruecksprache). Bekannte Fehlerbilder und die jeweils vorgesehene Aenderung (in dieser Reihenfolge pruefen): | Signatur im Protokoll | Ursache | Aenderung | |---|---|---| | `E: Unable to locate package …` | Paketname falsch/umbenannt | Namen mit `docker run --rm gitea/runner-images:ubuntu-latest sh -c 'apt-get update -qq; apt-cache policy {name}'` pruefen und in der apt-Zeile korrigieren | | `The system library '…' required by crate '…' was not found` (pkg-config) | dev-Paket fehlt | fehlendes `lib…-dev` in die apt-Zeile aufnehmen (Pitfall 5) | | `failed to run custom build command for openssl-sys` beim Ziel `x86_64-pc-windows-msvc` | TLS-Backend zieht OpenSSL fuer das Windows-Ziel | in `Cargo.toml` `reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }` (Pitfall 3), lokal `cargo check`, `Cargo.lock` mit committen | | `makensis: not found` / `NSIS … not installed` | NSIS fehlt im PATH | `nsis` in der apt-Zeile pruefen; sonst Pfad `/usr/bin/makensis` per `which makensis` im Protokoll ausgeben lassen | | `failed to download NSIS plugin` / `nsis_tauri_utils` | Netz/GitHub | gleicher Stand, erneut pushen (leerer Commit `ci(desktop): Runde N — erneuter Lauf`) | | `llvm-rc` / `rc.exe` / `winres` / `embed-resource` | Ressourcen-Compiler nicht gefunden | `sudo ln -sf /usr/bin/llvm-rc-18 /usr/bin/llvm-rc` im Schritt "Windows-Werkzeuge" oder `env: RC: llvm-rc-18` am Bauschritt | | `xwin` / `Failed to download` / `manifest` beim ersten Cross-Bau | Windows-SDK-Download | erneut pushen; falls wiederholt: `env: XWIN_ARCH: x86_64` und `XWIN_CACHE_DIR: ${{ github.workspace }}/.xwin-cache` (dann `.xwin-cache` in die Cache-Pfade) | | `optional build metadata in app version must be numeric-only` | Version nicht numerisch | `git describe`-Ausgabe im Protokoll pruefen; `desktop-version.sh` haette abbrechen muessen — Regex im Skript nachziehen | | `genau eine Datei erwartet` (Sammel-Skript) | Bundle-Pfad oder Altbestand | Pfad mit `find apps/desktop/src-tauri/target -name '*.exe' -path '*bundle*'` im Protokoll ermitteln und im Skript anpassen; Aufraeum-Schritt pruefen | | `Cache service responded with 4xx/5xx` / `Failed to save` / `fail-on-cache-miss` obwohl gespeichert | actions/cache-Version vs. Cache-Server | `actions/cache/save@v3` und `actions/cache/restore@v3` (Open Question 3); bleibt es rot: `target` aus den Cache-Pfaden nehmen (zu gross) | | `Killed` / `signal: 9` / `memory` waehrend `rustc` | Speicher | `env: CARGO_BUILD_JOBS: 4` am Job (CONTEXT "Specific Ideas") | | Job-Zeitueberschreitung | Baudauer plus Cache-Sicherung | `target` aus den Cache-Pfaden nehmen, `~/.cargo/registry` und xwin-Ablage behalten | | `docker build` scheitert an `COPY desktop-dist` | Verzeichnis fehlt im Kontext | Cache-Restore-Pfad und `test -f`-Schritt in `publish` pruefen | | Release-Upload `413` (nur bei Tags) | Proxy-Groessengrenze vor `git.vicolab.de` | am Release-Schritt `env: GITEA_API: http://172.18.0.1:3002/api/v1` (Host-Adresse, ueber die der Runner auch seinen Cache-Server erreicht) | | Release-Upload `400` mit "file type" (nur bei Tags) | Gitea `[repository.release] ALLOWED_TYPES` eingeschraenkt | nicht im Repository loesbar — dem Nutzer melden (Server-Einstellung); Voreinstellung der Instanz laesst alle Typen zu (geprueft 2026-09-16) | | `cargo clippy` Fehler | Code | Stelle beheben, `cargo clippy` lokal gruen | Jede Runde im SUMMARY festhalten: Signatur, Ursache, Aenderung, Commit. Trifft keine Signatur zu, die Ursache aus dem Protokoll ableiten und die kleinste plausible Aenderung waehlen; im Zweifel zuerst mehr Protokoll anfordern (z. B. `RUST_BACKTRACE=1` oder `--verbose` am Bauschritt), statt zu raten. - Jede Runde ist genau ein Commit mit Praefix `ci(desktop): Runde N —` (`git log --oneline -5 | grep -c 'ci(desktop): Runde'` entspricht der Rundenzahl). - Nach jeder Aenderung: `sh -n` fuer geaenderte Skripte endet mit 0; bei Cargo-Aenderungen endet `cargo check` in `apps/desktop/src-tauri` mit 0. - Es gibt nie mehr als drei Runden; nach der dritten roten Runde wird der Stand dem Nutzer vorgelegt statt weiter zu pushen. - Am Ende: Task 2 hat "gruen" mit genau einer `.exe`- und einer `.AppImage`-Zeile gemeldet. cd /home/vicolab/projects/tessera-ctl && sh -n .gitea/scripts/desktop-collect.sh && sh -n .gitea/scripts/publish-release.sh && sh -n .gitea/scripts/desktop-version.sh && (cd apps/desktop/src-tauri && cargo check 2>&1 | tail -1 | grep -q Finished) && test "$(git rev-list --count --grep='ci(desktop): Runde' HEAD~6..HEAD)" -le 3 && echo ROUND-OK Ein Skript hat einen Syntaxfehler, `cargo check` scheitert nach einer Cargo-Aenderung, oder es gibt mehr als drei Runden-Commits — `ROUND-OK` fehlt. Der Orchestrator bestaetigt nach der letzten Runde einen gruenen Lauf mit beiden Dateizeilen (Task 2). Ein gruener Pipeline-Lauf auf `main` mit `Tessera-Setup-1.1.0-beta.{sha}.exe` und `Tessera-1.1.0-beta.{sha}.AppImage` im Schritt "Pakete einsammeln" und gruenem `publish`; hoechstens drei dokumentierte Runden. ## Trust Boundaries | Boundary | Description | |----------|-------------| | Runner -> Internet (rustup, crates.io, Microsoft-SDK ueber xwin, NSIS-Plugins von GitHub) | Der Cross-Bau laedt Werkzeuge und das Windows-SDK aus dem Netz. | | Runner-Cache -> Bau | Aus dem Cache wiederhergestellte Artefakte (SDK, target/) fliessen in das Paket ein. | | Gebautes `.exe` -> Anwender-PC | Unsigniert; SmartScreen warnt (D-09). | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| | T-18-15 | Tampering | Werkzeugketten-Download (cargo-xwin, SDK, NSIS-Plugins) | medium | mitigate | `cargo install --locked cargo-xwin` (Lockfile des Werkzeugs), rustup von der offiziellen Adresse, SDK ueber cargo-xwin (prueft Microsoft-Manifest-Hashes), NSIS aus dem Ubuntu-Archiv; Tauri laedt seine NSIS-Plugins mit hinterlegten Pruefsummen. | | T-18-16 | Tampering | Cache-Vergiftung (`target/`, xwin-Ablage) | low | accept | Der Cache-Server laeuft nur lokal fuer diesen Runner (`172.18.0.1`), keine fremden Schreiber; Schluessel haengt am `Cargo.lock`-Hash. | | T-18-17 | Repudiation | Iterationsschleife | low | mitigate | Jede Runde ist ein eigener Commit mit Ursache im Titel und im SUMMARY dokumentiert. | | T-18-18 | Information Disclosure | Protokollauszuege (Token) | low | mitigate | Gitea maskiert Secrets im Log; `publish-release.sh` gibt das Token nie aus (T-18-03). | | T-18-SC | Tampering | `cargo install --locked cargo-xwin` (crates.io) | low | mitigate | Legitimitaetspruefung in RESEARCH: `OK` (rust-cross/cargo-xwin, seit 2022, 63k Downloads/Woche); kein `[ASSUMED]`/`[SUS]`, keine Sperr-Freigabe noetig. | 1. Statische Pruefung des Workflows (Kennzeichen, Reihenfolge AppImage vor NSIS, Einsammeln mit beiden Plattformen). 2. Gruener Pipeline-Lauf auf `main` (Rueckmeldung des Orchestrators) mit beiden Dateizeilen und gruenem `publish`. 3. Hoechstens drei dokumentierte Runden. 4. Nach dem Lauf traegt das Beta-Abbild die Pakete — sichtbar, sobald der Nutzer den Testserver auf den neuen Stand zieht (18-06). - Der Job `desktop` erzeugt auf dem Linux-Runner `Tessera-Setup-X.Y.Z[…].exe` und `Tessera-X.Y.Z[…].AppImage` in einem Lauf. - Der Lauf ist gruen; `publish` hat Beta-Abbilder mit Paketen gepusht. - Die Iterationsschleife ist dokumentiert und endete spaetestens nach drei Runden. Create `.planning/phases/18-desktop-client-fertigstellen/18-05-SUMMARY.md` when done. Im SUMMARY festhalten: Dauer des ersten und (falls vorhanden) eines zweiten Laufs mit warmem Cache, Groesse beider Dateien laut Sammel-Schritt, und die Tabelle der Runden (Signatur, Ursache, Aenderung, Commit).