18 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, estimate, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | estimate | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 18-desktop-client-fertigstellen | 02 | execute | 2 |
|
|
true |
|
|
|
Purpose: D-06, D-08 (Pipeline-Seite) und D-01 (Release-Dateien) aus
18-CONTEXT.md; Erfolgskriterium 1 (Linux-Haelfte und Release-Anhang).
Output: Job desktop, angepasster Job publish, Manifest-Pruefung in
publish-images.sh, Funktion upload_asset in publish-release.sh.
Externe Schnittstellen (Gitea REST): siehe 18-COVERAGE.md — neu sind
GET …/releases/{id}/assets, DELETE …/releases/{id}/assets/{asset_id} und
POST …/releases/{id}/assets?name= (multipart-Feld attachment); Gitea
1.26.2 laesst Release-Anhaenge standardmaessig ohne Typ-Beschraenkung bis
2048 MB zu.
Artifacts this phase produces
Dieser Plan: .gitea/workflows/ci.yml (Job desktop: Schritte
"Systemabhaengigkeiten", "Rust-Toolchain", "Cargo-Zwischenspeicher", "Version
setzen", "Rust pruefen", "Alte Bundles entfernen", "Linux-AppImage bauen",
"Pakete einsammeln", "Uebergabe an publish"; Job publish: "Desktop-Pakete
aus dem Zwischenspeicher holen", "Pakete pruefen"),
.gitea/scripts/publish-images.sh (Manifest-Pruefung),
.gitea/scripts/publish-release.sh (HDR_AUTH, upload_asset,
DESKTOP_DIST). Gesamtliste der Phase: siehe 18-01-PLAN.md.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.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-COVERAGE.md @.planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md@.gitea/workflows/ci.yml @.gitea/scripts/publish-images.sh @.gitea/scripts/publish-release.sh @.gitea/scripts/desktop-collect.sh @.gitea/scripts/desktop-version.sh
Task 1: Job desktop (Linux-AppImage) und Uebergabe an publish per actions/cache Job-Aufbau und Cache-Schluessel lassen sich jederzeit aendern; kein Zustand ausserhalb des Runners. .gitea/workflows/ci.yml, .gitea/scripts/publish-images.sh .gitea/workflows/ci.yml, .gitea/scripts/publish-images.sh, .gitea/scripts/desktop-collect.sh (Optionen und Ausgabe, aus 18-01), .planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md (Abschnitte "Code Examples 2", "Common Pitfalls 1 und 5", "Standard Stack: Installation"), docs/ci-cd-setup.md (Abschnitt 4 "Pipeline-Ueberblick") **Job `desktop` in `.gitea/workflows/ci.yml`** zwischen `test` und `publish` einfuegen, Kopfkommentar der Datei um einen Satz zu Phase 18 ergaenzen. `name: Desktop-Pakete bauen`, `runs-on: ubuntu-latest`, `needs: test`, `if: gitea.ref == 'refs/heads/main' || startsWith(gitea.ref, 'refs/tags/v')` (D-06). Schritte in dieser Reihenfolge, deutsche Schrittnamen wie im Rest der Datei: `actions/checkout@v4` mit `fetch-depth: 0` (Tags fuer `git describe`); `actions/setup-node@v4` (Node 24); corepack/pnpm wie in `test`; `pnpm install --frozen-lockfile`; "Systemabhaengigkeiten": `sudo apt-get update` und `sudo apt-get install -y --no-install-recommends` mit **vollstaendiger** Liste `libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libgtk-3-dev libssl-dev patchelf file xdg-utils` (Pitfall 5 — das Runner-Abbild hat davon nur `librsvg2-dev` und `file`; alle Paketnamen wurden am 2026-09-16 per `apt-cache policy` im Abbild `gitea/runner-images:ubuntu-latest` bestaetigt, ebenso `sudo`, `jq`, `curl` und `git`); "Rust-Toolchain": `curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable` und danach `echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"` (kein Rust im Runner-Abbild; bewusst kein Fremd-Action, gleiche Zurueckhaltung wie beim Verzicht auf die Artefakt-Aktionen); "Cargo-Zwischenspeicher": `actions/cache@v4` mit `path` `~/.cargo/registry`, `~/.cargo/git`, `~/.cache/tauri`, `apps/desktop/src-tauri/target`, `key: desktop-cargo-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}`, `restore-keys: desktop-cargo-` (der Cache-Server des Runners ist laut RESEARCH aktiv: `172.18.0.1:42641`); "Version setzen": `sh .gitea/scripts/desktop-version.sh`; "Rust pruefen": `cargo check` und `cargo clippy` mit `working-directory: apps/desktop/src-tauri` (D-16; Clippy ohne `-D warnings`, Fehler brechen ab, Warnungen nicht); "Alte Bundles entfernen": `rm -rf apps/desktop/src-tauri/target/release/bundle` (ein aus dem Cache wiederhergestelltes altes AppImage wuerde sonst neben dem neuen liegen und das Sammel-Skript zu Recht abbrechen); "Linux-AppImage bauen": `pnpm --filter @tessera/desktop exec tauri build --bundles appimage`; "Pakete einsammeln": `sh .gitea/scripts/desktop-collect.sh --require linux` (18-05 erweitert auf `linux,windows`); "Uebergabe an publish": `actions/cache/save@v4` mit `path: desktop-dist` und `key: desktop-dist-${{ gitea.sha }}` (Pitfall 1: bewusst **nicht** die Artefakt-Aktionen von GitHub — auf dieser Gitea-Instanz dokumentiert unzuverlaessig; im Workflow-Kommentar ebenfalls nur so umschreiben, damit das Negativ-Tor in `` nicht am Kommentartext scheitert).Job publish anpassen: needs: desktop statt needs: test. Nach dem
Checkout und vor dem Registry-Login zwei Schritte: "Desktop-Pakete aus dem
Zwischenspeicher holen" mit actions/cache/restore@v4, path: desktop-dist,
key: desktop-dist-${{ gitea.sha }}, fail-on-cache-miss: true; "Pakete
pruefen": test -f desktop-dist/manifest.json und jq . desktop-dist/manifest.json
(harter Abbruch, nie stillschweigend ein Abbild ohne Pakete). Der Schritt mit
publish-release.sh bleibt; die Pakete liegen fuer ihn unter desktop-dist/.
publish-images.sh: Im echten Bau-Pfad (nicht bei --print-plan) vor
der Schleife pruefen, dass desktop-dist/manifest.json existiert, sonst
Exit 1 mit Meldung — zweites Netz gegen Pitfall 1. Kopfkommentar um einen
Absatz ergaenzen (Phase 18: die Pakete kommen aus dem Job desktop, das
Dockerfile der API kopiert desktop-dist/). Weiterhin kein Secret.
<acceptance_criteria>
- grep -c '^ desktop:$' .gitea/workflows/ci.yml ergibt 1; grep -c 'needs: desktop' .gitea/workflows/ci.yml ergibt 1; grep -c 'fail-on-cache-miss: true' .gitea/workflows/ci.yml ergibt 1; grep -c 'desktop-dist-${{ gitea.sha }}' .gitea/workflows/ci.yml ergibt 2 (save und restore).
- grep -c 'upload-artifact' .gitea/workflows/ci.yml ergibt 0.
- grep -c 'libwebkit2gtk-4.1-dev' .gitea/workflows/ci.yml ergibt mindestens 1; grep -c 'desktop-collect.sh --require linux' .gitea/workflows/ci.yml ergibt 1; grep -c 'desktop-version.sh' .gitea/workflows/ci.yml ergibt 1.
- sh -n .gitea/scripts/publish-images.sh endet mit 0; GITHUB_REF=refs/tags/v1.1.0 sh .gitea/scripts/publish-images.sh --print-plan gibt weiterhin die vier push-Zeilen aus (Probelauf braucht kein Manifest).
- grep -c 'manifest.json' .gitea/scripts/publish-images.sh ergibt mindestens 1.
</acceptance_criteria>
cd /home/vicolab/projects/tessera-ctl && test "$(grep -c 'desktop-dist-${{ gitea.sha }}' .gitea/workflows/ci.yml)" = "2" && grep -q 'fail-on-cache-miss: true' .gitea/workflows/ci.yml && grep -q 'needs: desktop' .gitea/workflows/ci.yml && test "$(grep -c 'upload-artifact' .gitea/workflows/ci.yml)" = "0" && grep -q 'desktop-collect.sh --require linux' .gitea/workflows/ci.yml && grep -q 'desktop-version.sh' .gitea/workflows/ci.yml && node -e "const y=require('fs').readFileSync('.gitea/workflows/ci.yml','utf8');if(!/^ desktop:\n/m.test(y)||!/^ publish:\n/m.test(y))process.exit(1)" && echo CI-OK
<fails_when>Cache-Schluessel nicht genau zweimal, Restore ohne harten Abbruch, publish haengt nicht an desktop, ein upload-artifact-Schritt ist vorhanden, Skript-Schritte fehlen, oder die Job-Schluessel fehlen — CI-OK fehlt.</fails_when>
cd /home/vicolab/projects/tessera-ctl && sh -n .gitea/scripts/publish-images.sh && GITHUB_REF=refs/tags/v1.1.0 sh .gitea/scripts/publish-images.sh --print-plan | grep -c '^push ' | grep -qx 4 && grep -q 'manifest.json' .gitea/scripts/publish-images.sh && echo IMAGES-OK
<fails_when>Syntaxfehler, weniger als vier push-Zeilen im Probelauf, oder die Manifest-Pruefung fehlt im Skript — IMAGES-OK fehlt.</fails_when>
Der Workflow enthaelt den Job desktop (Linux-AppImage mit Tag-Version,
Cache, Uebergabe per actions/cache), publish haengt daran und bricht
ohne Manifest ab; publish-images.sh prueft das Manifest ebenfalls.
Bekannter Fallstrick fuer 18-05: Der Job-Container erreicht Gitea ueber
https://git.vicolab.de hinter dem Nginx Proxy Manager; das AppImage ist
rund 106 MB — falls der Proxy den Upload abweist (413), kann GITEA_API im
Workflow-Schritt auf die Host-Adresse http://172.18.0.1:3002/api/v1 gesetzt
werden (gleiche Route, ueber die der Runner seinen Cache-Server erreicht).
Das wird erst im CI-Lauf entschieden, nicht hier. Der Upload-Pfad selbst
laeuft erst beim naechsten Freigabe-Tag (ein Test-Tag wuerde den Live-Kanal
ausloesen) — deshalb ist der Probelauf mit --dry-run hier das Tor.
<acceptance_criteria>
- sh -n .gitea/scripts/publish-release.sh endet mit 0.
- sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0 gibt eine Zeile mit assets?name=Tessera-1.1.0.AppImage aus (Manifest aus 18-01 vorhanden) und endet mit 0; ohne Token, ohne Netzaufruf.
- grep -c 'HDR_AUTH' .gitea/scripts/publish-release.sh ergibt mindestens 3 (Anlegen, Schreiben, Verwendung); grep -c '^upload_asset()' .gitea/scripts/publish-release.sh ergibt 1.
- grep -c "files\[\].name" .gitea/scripts/publish-release.sh ergibt mindestens 1.
- Das Token wird nirgends als Argument uebergeben: grep -c 'token %s' .gitea/scripts/publish-release.sh ergibt genau 1 (die bestehende printf-Zeile in die Header-Datei) oder 2 (zweite Header-Datei), nie in einer curl-Zeile.
</acceptance_criteria>
cd /home/vicolab/projects/tessera-ctl && sh -n .gitea/scripts/publish-release.sh && sh .gitea/scripts/publish-release.sh --dry-run --tag v1.1.0 | grep -q 'assets?name=Tessera-1.1.0.AppImage' && test "$(grep -c 'HDR_AUTH' .gitea/scripts/publish-release.sh)" -ge 3 && grep -q '^upload_asset()' .gitea/scripts/publish-release.sh && grep -q 'files[].name' .gitea/scripts/publish-release.sh && test "$(grep -c 'curl.*GITEA_TOKEN' .gitea/scripts/publish-release.sh)" = "0" && echo RELEASE-OK
<fails_when>Syntaxfehler, der Probelauf nennt den AppImage-Upload nicht, die zweite Header-Datei oder die Funktion fehlt, die Dateinamen kommen nicht aus dem Manifest, oder das Token steht in einer curl-Zeile — RELEASE-OK fehlt.</fails_when>
Das Release-Skript laedt alle Manifest-Dateien idempotent hoch (vorhandene
Datei gleichen Namens wird ersetzt), das Token bleibt in Header-Dateien, der
Probelauf nennt die geplanten Uploads. Der echte Pipeline-Beweis folgt in
18-05 (gemeinsam mit Windows), der Release-Anhang beim naechsten Freigabe-Tag.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| CI-Runner -> Gitea-API (Release-Dateien) | Ausgehender Aufruf mit dem Zugriffstoken REGISTRY_TOKEN. |
Runner -> Cache-Server (actions/cache) |
Uebergabe der Pakete zwischen zwei Jobs desselben Laufs. |
| Runner -> Internet (rustup, crates.io, Tauri-Werkzeuge) | Der Job laedt Werkzeuge aus dem Netz. |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-18-03 | Information Disclosure | publish-release.sh (Token) |
high | mitigate | Token nur aus der Umgebung, nie als Argument, nur ueber Header-Dateien mit umask 077; keine Ausgabe des Tokens; zweite Header-Datei ohne JSON-Content-Type fuer multipart. Gate: keine curl-Zeile enthaelt GITEA_TOKEN. |
| T-18-06 | Tampering | publish ohne Pakete (Cache-Fehlschlag) |
medium | mitigate | fail-on-cache-miss: true plus expliziter test -f desktop-dist/manifest.json im Workflow und in publish-images.sh. |
| T-18-21 | Tampering | Cache-Uebergabe zwischen Jobs (desktop-dist-{sha}) |
low | accept | Cache-Server nur lokal fuer diesen Runner (172.18.0.1), Schluessel exakt am Commit-SHA, keine restore-keys-Fallbacks fuer die Uebergabe. |
| T-18-SC | Tampering | Paketinstallationen (actions/cache@v4, actions/checkout@v4, actions/setup-node@v4; Rust-Toolchain per rustup) |
low | mitigate | Nur GitHub-eigene Actions in der bereits genutzten Major-Version; rustup-Installer von der offiziellen Adresse; keine neuen npm/pip/cargo-Pakete in diesem Plan. |
| </threat_model> |
<success_criteria>
- Job
desktopbaut das AppImage mit Tag-Version und uebergibt es per Cache. publishkann kein Abbild ohne Pakete mehr bauen.- Release-Dateien werden bei Tags idempotent aus dem Manifest hochgeladen. </success_criteria>