Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
27 KiB
Tessera CI/CD Setup
Runbook fuer die Einrichtung der Gitea-basierten CI/CD-Pipeline.
Voraussetzungen
- Docker und Docker Compose installiert
- Gitea-Instanz mit aktivierten Actions
- Zugriff auf die Gitea-Administrationsoberflaeche
1. Gitea-Repository einrichten
- In Gitea ein neues Repository erstellen (z.B.
tessera-ctl) - Das Repository als Git-Remote hinzufuegen:
git remote add origin https://<gitea-url>/<user>/tessera-ctl.git
- In den Repository-Einstellungen unter Settings > Actions sicherstellen, dass Actions aktiviert ist.
2. act_runner registrieren
Der act_runner fuehrt Gitea Actions Workflow-Jobs in Docker-Containern aus.
Runner-Token generieren
- In Gitea navigieren zu: Repository > Settings > Actions > Runners (oder Admin-Panel > Actions > Runners fuer globale Runner)
- Create registration token klicken
- Token kopieren
Runner starten
Die Umgebungsvariablen setzen (z.B. in .env im Projektverzeichnis oder
direkt in der Shell):
export GITEA_INSTANCE_URL=https://git.vicolab.de
export GITEA_RUNNER_REGISTRATION_TOKEN=<token-aus-schritt-oben>
Runner starten:
docker compose -f docker-compose.ci.yml up -d
Runner-Status pruefen:
docker compose -f docker-compose.ci.yml logs act_runner
Der Runner registriert sich automatisch bei Gitea und ist bereit, Jobs auszufuehren.
Bestehender Runner
Wenn bereits ein act_runner als separater Docker-Container laeuft (z.B.
gitea-runner), kann docker-compose.ci.yml als Dokumentation und Vorlage
fuer eine Neueinrichtung verwendet werden. Der bestehende Runner muss nicht
ersetzt werden.
Bestehenden Runner pruefen:
docker ps --filter name=gitea-runner
3. Erforderliche Konfiguration
Umgebungsvariablen
| Variable | Beschreibung | Wo setzen |
|---|---|---|
GITEA_INSTANCE_URL |
URL der Gitea-Instanz | .env oder Systemumgebung |
GITEA_RUNNER_REGISTRATION_TOKEN |
Runner-Registrierungstoken | .env oder Systemumgebung |
Gitea Secrets (fuer die CI-Pipeline)
In Gitea unter Repository > Settings > Actions > Secrets werden die Secrets fuer die Pipeline konfiguriert. Benoetigt werden drei:
| Secret | Beschreibung |
|---|---|
REGISTRY_TOKEN |
Gitea-Zugangstoken (Access Token) mit Schreibrecht auf Pakete (package: write) und zusaetzlich auf das Repository (repository: write, fuer Releases). Wird im Job publish fuer docker login localhost:3002 --password-stdin verwendet und im Release-Schritt ueber env als GITEA_TOKEN an .gitea/scripts/publish-release.sh gereicht -- nie als Argument. |
TAURI_SIGNING_PRIVATE_KEY |
Inhalt der privaten Schluesseldatei des Tauri-Updaters (eine Base64-Zeile, erzeugt mit tauri signer generate). Wird ausschliesslich an den zwei tauri build-Schritten des Jobs desktop als env gesetzt; die Tauri-CLI signiert damit das AppImage und den Windows-Installer (.sig neben dem Bundle). Der oeffentliche Gegenpart steht in apps/desktop/src-tauri/tauri.conf.json (plugins.updater.pubkey). |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Passwort zu diesem Schluessel; gleiche Stelle, gleicher Umfang. |
Das Token erscheint nie im Log: es wird per --password-stdin uebergeben und
Gitea maskiert Secret-Werte in der Job-Ausgabe. Das Veroeffentlichungs-Skript
.gitea/scripts/publish-images.sh kennt das Token nicht; der Login bleibt im
Workflow.
Der Signierschluessel wird ebenfalls nie ausgegeben: die Skripte kennen ihn
nicht (desktop-collect.sh prueft nur, OB die Variable gesetzt ist, um die
Signatur zur Pflicht zu machen), nur die beiden Bau-Schritte sehen ihn --
weder pnpm install, apt-get, cargo install cargo-xwin noch die
Cache-Schritte. Die Sicherung des Schluessels ausserhalb der Pipeline
(~/.tessera/desktop-updater/ auf dem Entwicklungsrechner) beschreibt das
Betriebshandbuch, Kapitel 10.
Der Push geht ueber localhost:3002 (Gitea laeuft auf demselben Rechner wie der
Runner), weil der Nginx Proxy Manager vor git.vicolab.de grosse Image-Blobs
blockt. Das Pullen auf den Servern laeuft ueber git.vicolab.de
(docker-compose.prod.yml).
Weitere Secrets bei Bedarf:
- In Gitea Settings > Actions > Secrets den Secret anlegen
- In
.gitea/workflows/ci.ymlueber${{ secrets.SECRET_NAME }}referenzieren
Wichtig: Secrets niemals in Logs ausgeben oder in Workflow-Dateien hardcoden.
4. Pipeline-Ueberblick
Die CI/CD-Pipeline (.gitea/workflows/ci.yml) laeuft bei jedem Push auf die
Zweige main und live sowie bei jedem Tag v* (z. B. v1.0.0) und besteht
aus fuenf Jobs (vier bauen aufeinander auf, der fuenfte ist ein reiner Bericht):
- quality -- Lint und TypeScript Type-Check (Lint ist derzeit ein Leerlauf, siehe WINDOWS #35; der Type-Check ist echt)
- test -- Vitest Unit- und Integrationstests
- desktop -- Desktop-Pakete fuer Windows und Linux bauen (nur auf
mainund bei Tagsv*, siehe unten) - publish -- Docker Images mit Versionsstempel bauen und in die Gitea-Registry veroeffentlichen
- security -- Sicherheitspruefung (nur Bericht): nach
publish, nur aufmainund bei Tagsv*; bricht nie ab, blockiert nie etwas, bekommt kein Secret
Ablauf: quality -> test -> desktop -> publish -> security (jeder Job nur
bei Erfolg des vorherigen; security ist nur ein Bericht und steht in keinem
needs eines anderen Jobs; desktop selbst laeuft nur, wenn die if-Bedingung zutrifft
-- auf einem Push nach live ohne Tag entfaellt der Job, publish startet in
diesem Fall trotzdem, weil needs: desktop bei einem uebersprungenen Job nicht
blockiert). Der Job publish besteht aus sechs Schritten:
actions/checkout@v4 mit fetch-depth: 0 (volle Historie samt Tags, sonst
liefert git describe nichts), der Wiederherstellung der Desktop-Pakete aus
dem Zwischenspeicher, einer harten Pruefung des Manifests, Login in die
Registry (siehe Abschnitt 3), der Aufruf von .gitea/scripts/publish-images.sh
und der Aufruf von .gitea/scripts/publish-release.sh (legt bei Tags v* den
Gitea-Release aus dem CHANGELOG-Abschnitt an und haengt die Desktop-Pakete als
Dateien an; auf main endet er mit "nichts zu tun").
Job desktop: Windows- und Linux-Pakete auf dem Linux-Runner
Der Job desktop baut auf demselben ubuntu-latest-Runner nacheinander (ein
Cache, ein Runner, siehe 18-CONTEXT.md Specific Ideas) sowohl das
Linux-AppImage als auch -- per Cross-Bau -- den Windows-Installer:
Ueberspringen bei unveraendertem Desktop (quick-260917-jdh): Direkt nach
dem Checkout berechnet desktop-stamp.sh stamp den Stempel <Version>-<SHA>
(Version aus desktop-version.sh --print; SHA = letzter Commit an
apps/desktop, desktop-version.sh, desktop-collect.sh,
desktop-stamp.sh, ci.yml -- Konstante DESKTOP_PATHS; pnpm-lock.yaml
bewusst nicht, weil die Tauri-CLI-Version an apps/desktop/package.json
haengt und der Bau keine Datei ausserhalb von apps/desktop liest) und gibt
skip_allowed=true nur fuer refs/heads/main aus. Danach actions/cache/restore@v4
mit dem Schluessel desktop-dist-stamp-<Stempel> -- ohne restore-keys, weil
act_runner auch den Hauptschluessel per Praefix sucht und ein aelterer
Stand nie als Treffer gelten darf. Anschliessend desktop-stamp.sh check:
cache-hit, Manifest, Kanal beta, Version, beide Dateien mit Groesse und
sha256 laut Manifest -> reuse=true; sonst raeumt es desktop-dist/ auf und
gibt reuse=false. Alle Bau-Schritte (setup-node bis Pakete einsammeln)
tragen if: steps.reuse.outputs.reuse != 'true'. Nach einem echten Bau legt
actions/cache/save@v4 die Pakete zusaetzlich unter dem Stempel-Schluessel ab
(nur auf main). Die Uebergabe an publish (desktop-dist-<sha>) laeuft in
beiden Faellen; publish ist unveraendert. Bei Tags v* wird weder gesucht
noch abgelegt. Lokale Probe: DESKTOP_TAG=v1.2.0 GITHUB_REF=refs/heads/main sh .gitea/scripts/desktop-stamp.sh stamp.
- Systemabhaengigkeiten (
apt-get install): WebKit/Tauri-Pakete (libwebkit2gtk-4.1-devusw.) fuer den Linux-Bau, dazulld llvm clang nsisfuer den Windows-Cross-Bau (der NSIS-Bundler ruftmakensisaus genau diesem Paket auf). - Rust-Toolchain per
rustup(kein Rust im Runner-Abbild), inklusiverustup component add clippy----profile minimalinstalliertclippysonst nicht mit (siehe Fehlerbehebung unten). - Cargo-Zwischenspeicher (
actions/cache@v4, Schluessel ueber den Hash vonCargo.lock):~/.cargo/registry,~/.cargo/git,~/.cargo/bin/cargo-xwin,~/.cache/cargo-xwin(die voncargo-xwinheruntergeladene Windows-SDK-Ablage -- soll nur einmal geladen werden),~/.local/share/tauri(NSIS-Plugins) undapps/desktop/src-tauri/target. - Windows-Werkzeuge -- bewusst NACH dem Cache-Wiederherstellungsschritt:
rustup target add x86_64-pc-windows-msvcundcommand -v cargo-xwin || cargo install --locked cargo-xwin. Stuende dieser Schritt vor der Cache-Wiederherstellung, wuerdecargo-xwinbei jedem Lauf neu gebaut, selbst wenn der Cache es bereits enthaelt. - Version setzen (
desktop-version.sh), Rust pruefen (cargo check/cargo clippy, D-16), Bau des Linux-AppImage (tauri build --bundles appimage), dann des Windows-Installers per Cross-Bau (tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis). Beide Bau-Schritte tragen die SecretsTAURI_SIGNING_PRIVATE_KEY/_PASSWORDalsenvund signieren die Bundles (quick-260917-kgc): die Tauri-CLI legt.sig-Dateien neben-setup.exeund.AppImageab -- host-unabhaengig, also auch im Cross-Bau. Kein--no-signim CI. - Pakete einsammeln (
desktop-collect.sh --require linux,windows) -- schreibtmanifest.json(mitupdateVersionund je Plattform dersignatureaus der.sig-Datei) und schlaegt fehl, wenn eine der beiden Dateien fehlt oder aufmain/Tags eine.sigfehlt. - Uebergabe an
publishperactions/cache/save@v4mit dem Schluesseldesktop-dist-${{ gitea.sha }}(ein neuer Schluessel je Commit, damitpublishgarantiert die Pakete des gerade gebauten Standes bekommt, nicht einen aelteren Cache-Treffer). Dieser Schritt laeuft auch dann, wenn der Bau uebersprungen wurde -- er sichert in diesem Fall den aus dem Stempel-Cache restaurierten Stand unter dem neuen SHA.
Warum actions/cache und nicht upload-artifact: Auf dieser
Gitea-Instanz ist actions/upload-artifact/download-artifact unzuverlaessig
(Erfahrungswert aus 18-02) -- die Uebergabe zwischen desktop und publish
laeuft deshalb bewusst ueber actions/cache/save und
actions/cache/restore mit fail-on-cache-miss: true, nicht ueber
Artefakt-Uploads.
Das Release-Skript spricht die Gitea-API NIE ueber die oeffentliche Adresse
(GITHUB_API_URL/GITHUB_SERVER_URL = https://git.vicolab.de hinter dem
Proxy, der grosse Uploads abbricht -- so blieb Release 1.2.0 am 2026-09-17
zunaechst ohne Anhaenge). Im Job-Container ist localhost:3002 nicht der
Host; das Skript ermittelt deshalb das Host-Gateway aus /proc/net/route
(z. B. 172.17.0.1) und ruft http://<gateway>:3002/api/v1 auf -- derselbe
Weg wie der Registry-Push. Lokal nimmt es http://localhost:3002/api/v1;
GITEA_API bleibt als expliziter Override. Mit --dry-run --tag vX.Y.Z
laesst sich die gewaehlte Adresse ohne Netzaufruf und ohne Token pruefen.
Zwei Kanaele: Etiketten je Anlass
Das Skript .gitea/scripts/publish-images.sh entscheidet allein anhand
GITHUB_REF, ob und unter welchen Etiketten veroeffentlicht wird:
| Anlass | Kanal (APP_CHANNEL) |
Etiketten in der Registry |
|---|---|---|
Push auf main |
beta |
beta und latest (latest ist nur ein Alias fuer beta und entfaellt spaeter) |
Tag vX.Y.Z |
live |
live und vX.Y.Z + Gitea-Release Tessera X.Y.Z mit dem CHANGELOG-Abschnitt |
Push auf live ohne Tag |
-- | keine; der Lauf prueft nur (quality, test), das Skript endet mit "nichts zu tun" |
Das Kanalmodell fuer den Betrieb (welcher Server welches Etikett zieht, Freigabe,
Hotfix) steht in docs/anleitung-betrieb.md, Kapitel 9.
Versionsstempel
Das Skript berechnet vier Werte und gibt sie als --build-arg an beide
Dockerfiles (apps/web/Dockerfile, apps/api/Dockerfile):
| Build-Arg | Quelle |
|---|---|
APP_VERSION |
git describe --tags --always (ohne Tag: kurzer Commit-SHA) |
APP_CHANNEL |
beta oder live, siehe Tabelle oben |
APP_COMMIT |
git rev-parse --short HEAD |
APP_BUILD_TIME |
date -u, ISO-Format |
Die API liest die Werte zur Laufzeit (GET /health/version, Startzeile im Log).
Das Web-Image bettet NEXT_PUBLIC_APP_* beim Build in das Browser-Bundle ein --
deshalb laeuft die builder-Stufe des Web-Images jetzt bei jedem Pipeline-Lauf
neu, die Laufzeit liegt eher bei 4-6 statt 2 Minuten.
Lokale Probe ohne Docker-Aufruf:
GITHUB_REF=refs/tags/v1.2.3 sh .gitea/scripts/publish-images.sh --print-plan
Hinweis: Die fruehere Entscheidung D-13 (Images lokal bauen, keine Registry) ist
ueberholt -- seit der Einfuehrung der Gitea-Registry werden Images gepusht und von
den Servern per docker compose pull geholt.
Job security: Sicherheitspruefung (nur Bericht)
Der Job security (quick-261009-p0m) laeuft nach publish, damit er die Veroeffentlichung
nie verzoegert oder verhindert. Er laeuft nur auf main und bei Tags v*; die
Bedingung steht ausdruecklich am Job, weil ein uebersprungenes needs in Gitea nicht
blockiert (sonst liefe er auch nach einem Push auf live). Er hat continue-on-error,
ein Zeitlimit von 30 Minuten (ein haengender Scan haelt den einzigen Runner nicht fest)
und kein Secret. Das Skript haelt sich selbst an ein Zeitbudget von 25 Minuten (siehe
"Zeitbudget" unten). Alles steckt in einem Skript, das auch lokal laeuft:
GITHUB_REF=refs/heads/main sh .gitea/scripts/security-scan.sh --print-plan # ohne Netz
GITHUB_REF=refs/heads/main sh .gitea/scripts/security-scan.sh all
Was geprueft wird: gitleaks ueber die gesamte Git-Historie (Zugangsdaten), pnpm audit --prod und osv-scanner ueber pnpm-lock.yaml und die Desktop-Cargo.lock (bekannte
Schwachstellen), Semgrep (statische Code-Analyse) und Trivy ueber den Quellstand sowie
ueber die frisch gebauten Abbilder api und web (main -> :beta, Tag v* -> :live).
Die Abbilder liegen nach publish im Docker-Daemon des Hosts (Socket-Mount, Abschnitt 5)
und werden direkt von dort gelesen.
Quellstand: Die Quellpruefungen lesen einen git archive HEAD-Export in einem
temporaeren Ordner, gitleaks nur die Git-Historie. Es wird also genau der eingecheckte
Stand geprueft; nicht eingecheckte Dateien eines Arbeitsordners erreichen nie ein
Werkzeug, einen Bericht oder ein Artefakt.
Angepinnte Werkzeuge: gitleaks 8.30.1, Trivy 0.75.0, osv-scanner 2.6.0, Semgrep 1.180.0,
pnpm 9.15.0. Die drei Programme werden von der offiziellen
GitHub-Veroeffentlichung geladen und vor jeder Benutzung gegen eine SHA256-Summe im Skript
geprueft (auch das zwischengespeicherte Archiv); stimmt die Summe nicht oder fehlt das
Netz, wird nur dieses Werkzeug uebersprungen. Semgrep laeuft als Container aus dem
offiziellen Abbild semgrep/semgrep, angepinnt per Digest (SEMGREP_IMAGE im Skript):
Docker prueft beim Laden, dass der Inhalt zum Digest passt. Es gibt bewusst keine
Installation ueber pipx/PyPI mehr -- deren frei aufgeloeste Abhaengigkeiten liefen sonst
in einem Job, der den Docker-Socket des Hosts sieht. Der Quellstand geht per docker cp
in den Container und der Bericht per docker cp zurueck (kein Ordner-Mount, denn der
Arbeitsordner liegt im Job-Container und nicht auf dem Docker-Rechner). Das Abbild belegt
rund 1,5 GB im Docker-Daemon des Runners und bleibt dort zwischengespeichert; ohne Docker
wird Semgrep mit kein-docker uebersprungen. Marktplatz-Aktionen fuer Scanner werden
bewusst nicht benutzt (veraenderliche Etiketten). Version anheben: im Kopf von
security-scan.sh die Version, die URL und die SHA256-Summe aus der offiziellen
Pruefsummendatei der Veroeffentlichung gemeinsam austauschen und einmal lokal
sh .gitea/scripts/security-scan.sh all laufen lassen. Bei Semgrep stattdessen
SEMGREP_VERSION und den Digest in SEMGREP_IMAGE tauschen (den Digest zeigt
docker pull semgrep/semgrep:<Version> in der Zeile Digest:). Werkzeuge und die Trivy-Datenbank
liegen im persistenten Zwischenspeicher des Runners (/opt/hostedtoolcache/tessera-security);
der Layer-Zwischenspeicher von Trivy wird nach jedem Lauf geloescht, damit die Platte nicht
vollaeuft.
Ausnahmelisten: .gitleaks.toml (geprueft harmlose Treffer: Testschluessel des
Zertifikatsmanagers, Schluessel-Ausschnitte in Tests und Notizen) und .semgrepignore
(Tests, Testdaten, Planungsnotizen). Ein neuer Treffer wird nie vorsorglich freigegeben,
sondern erst geprueft.
Zeitbudget: Der Job hat 30 Minuten (timeout-minutes). Das Skript rechnet mit einem
Gesamtbudget von 1500 Sekunden (25 Minuten) und jedes Werkzeug hat ein eigenes Limit
(Variablen T_* im Kopf des Skripts): Laden der drei Programme je 60 s, Semgrep-Abbild laden
150 s, pnpm 45 s, gitleaks 180 s, pnpm audit 90 s, osv-scanner 120 s, Semgrep 300 s, Trivy ueber
den Quellstand 180 s, je Abbild 120 s -- zusammen hoechstens 1485 Sekunden. Zusaetzlich kuerzt das
Skript jedes Limit auf die noch verbleibende Gesamtzeit; ist nichts mehr uebrig, werden die
restlichen Werkzeuge mit skipped reason=gesamtbudget gemeldet. Ein Werkzeug, das sein
Limit reisst, erscheint als skipped reason=zeitgrenze. Die fuenf Minuten Reserve bis zu den
30 Minuten decken Checkout und Artefakt-Schritt ab. Jedes Werkzeug gibt seine Zeile aus,
sobald es fertig ist -- bricht der Runner den Job trotzdem einmal ab, stehen die bis dahin
fertigen Ergebnisse im Protokoll.
Wo man das Ergebnis sieht: Im Protokoll des Jobs stehen je Werkzeug eine Zeile
SECURITY-SUMMARY ... (nur Zahlen), die Zeile SECURITY-SUMMARY dauer=... und am Ende
SECURITY-SUMMARY fertig (nur Bericht, Exit 0); dieselben Zeilen stehen in summary.txt.
Prueft das Skript in einem flachen Klon (ohne volle Historie), steht bei gitleaks
unvollstaendig (flacher Klon ...) -- dann ist das Ergebnis nicht "sauber", sondern nur fuer die sichtbaren
Commits gueltig (die Pipeline holt mit fetch-depth: 0 immer die volle Historie). Die Rohberichte (JSON) haengen,
soweit der Runner es zulaesst, als Artefakt sicherheitsberichte (30 Tage) am Lauf. Das
Artefakt ist nur "best effort" (Gitea lehnt upload-artifact@v4 ab, es laeuft @v3);
verlassen Sie sich auf die Zeilen im Protokoll. Der Job braucht auf dem
Entwicklungsrechner mit warmem Zwischenspeicher etwa eine Minute; beim allerersten Lauf auf einem
frischen Runner kommen das Laden der Programme und des Semgrep-Abbilds (rund 1,5 GB) und die
Trivy-Datenbank dazu. Mehr als 25 Minuten laufen nie (siehe "Zeitbudget").
5. Sicherheitshinweise
Docker Socket
Der act_runner mountet den Host-Docker-Socket (/var/run/docker.sock), um Jobs
in Docker-Containern ausfuehren zu koennen. Das bedeutet:
- Der Runner hat Zugriff auf den Docker-Daemon des Hosts
- Er kann Container starten, stoppen und inspizieren
Risikobewertung: Akzeptabel fuer interne CI, da:
- Nur Claude pusht manuell bei Meilensteinen (D-11)
- Kein externer Code wird ausgefuehrt
- Ephemeral Mode ist aktiviert (siehe unten)
Ephemeral Runner
GITEA_RUNNER_EPHEMERAL=1 sorgt dafuer, dass der Runner seine Credentials
nach jedem Job widerruft und sich neu registriert. Das verhindert:
- Persistente Zugriffstokens im Runner-Container
- Kompromittierung ueber alte Job-Artefakte
- Seitliche Bewegung zwischen Jobs
Workflow-Dateien
Workflow-Dateien in .gitea/workflows/ werden direkt aus dem Repository
geladen. Da nur vertrauenswuerdiger Code gepusht wird (D-11, Claude als
einziger Committer), ist das Risiko einer manipulierten Pipeline minimal.
Ein Tag-Push v* ist der Freigabe-Hebel fuer Live: wer ihn setzen darf, kann
das live-Etikett neu belegen. Heute hat nur das Konto schalli Schreibrecht
(0 Kollaborateure, keine Branch-Regeln). Kommen weitere Konten dazu, in Gitea
unter Repository > Settings > Branches / Tags eine Tag-Schutzregel fuer v*
und einen Branch-Schutz fuer live anlegen (T-KU1-04).
6. Fehlerbehebung
Runner registriert sich nicht
- Pruefen ob
GITEA_INSTANCE_URLkorrekt und erreichbar ist - Token ggf. neu generieren (Token sind einmalig verwendbar bei Ephemeral Mode)
- Runner-Logs pruefen:
docker compose -f docker-compose.ci.yml logs act_runner
Pipeline startet nicht
- In Gitea pruefen ob Actions fuer das Repository aktiviert ist
- Pruefen ob der Runner als "online" angezeigt wird (Gitea > Actions > Runners)
- Sicherstellen dass die Workflow-Datei unter
.gitea/workflows/liegt - YAML-Syntax validieren
Docker-Build schlaegt fehl
- Sicherstellen dass
docker-compose.ymlim Arbeitsverzeichnis des Runners erreichbar ist - Docker Daemon Status pruefen:
docker info - Disk Space pruefen:
df -h
Stempel zeigt dev oder nur eine Kurzkennung statt des Tags
- Im Job
publishpruefen, dassactions/checkout@v4mitfetch-depth: 0auscheckt -- ohne Tags liefertgit describe --tags --alwaysnur den SHA - Pruefen, ob der Tag wirklich gepusht wurde:
git ls-remote --tags origin devbedeutet: das Image wurde ohne Build-Args gebaut (lokal statt ueber das Skript) -- das ist fuer lokale Builds normal
Job desktop schlaegt fehl
cargo clippymeldet'cargo-clippy' is not installed for the toolchain-- der Schritt "Rust-Toolchain" installiert mit--profile minimal, dasclippynicht mitbringt. Behoben durchrustup component add clippydirekt nach der Toolchain-Installation (siehe oben); bei einem aehnlichen Fehlerbild in Zukunft pruefen, ob diese Zeile noch vorhanden ist.- apt-Paketname unbekannt /
pkg-configfindet eine Bibliothek nicht -- Paketnamen aendern sich gelegentlich zwischen Ubuntu-Versionen des Runner-Abbilds; den fehlenden.pc/.so-Namen aus der Fehlermeldung inapt-cache searchnachschlagen und die Paketliste im Schritt "Systemabhaengigkeiten" ergaenzen. openssl-sysscheitert beim Windows-Cross-Ziel -- OpenSSL laesst sich fuerx86_64-pc-windows-msvcvon Linux aus nicht ohne Weiteres cross-kompilieren; falls eine neue Abhaengigkeit das ueberopenssl-sysstattrustls-tlseinzieht, das Feature/die Abhaengigkeit aufrustls-tlsumstellen (in diesem Job bislang nicht aufgetreten,reqwestist bereits aufrustls-tlskonfiguriert).- NSIS-Plugin-Download schlaegt fehl -- Tauris NSIS-Bundler laedt beim
ersten Bau zusaetzliche Plugins nach
~/.local/share/tauri; ein Netzwerkfehler dort bricht den Bauschritt "Windows-Installer bauen (Cross-Bau)" ab. Lauf erneut anstossen; bleibt der Cache warm, entfaellt der Download beim naechsten Mal. - Runner-Speicher/-Zeit reicht nicht -- der Rust-Bau laeuft auf einem
begrenzten Runner (8 Kerne/15 GB, siehe
18-CONTEXT.md); bei SpeicherdruckCARGO_BUILD_JOBS(z. B. auf4) als Umgebungsvariable im Job setzen, um die parallele Uebersetzung zu drosseln.
Job desktop: "A public key has been found, but no private key"
Die Tauri-CLI bricht den Bau ab, weil plugins.updater.pubkey in
tauri.conf.json gesetzt ist, aber TAURI_SIGNING_PRIVATE_KEY in der
Umgebung fehlt. Ursache sind fast immer fehlende oder umbenannte Secrets: in
Gitea unter Repository > Settings > Actions > Secrets pruefen, ob
TAURI_SIGNING_PRIVATE_KEY und TAURI_SIGNING_PRIVATE_KEY_PASSWORD unter
genau diesen Namen existieren, und ob beide Bau-Schritte in ci.yml den
env-Block tragen. Niemals --no-sign in ci.yml eintragen -- damit
entstuenden unsignierte Pakete, die kein Client als Update annimmt
(desktop-collect.sh bricht auf main/Tags ohne .sig ohnehin ab).
desktop baut, obwohl nichts geaendert wurde -- oder uebernimmt trotz Aenderung
Baut trotzdem:
- Erster Lauf nach einer Aenderung unter den Desktop-Pfaden -- der Stempel ist neu, das ist erwartet.
- Ein neuer Freigabe-Tag ist gesetzt -- die Version im Stempel hat sich geaendert, die Beta-Pakete muessen einmal neu entstehen.
- Der Eintrag ist vom Runner-Cache weggeraeumt worden --
act_runnerraeumt ungenutzte Eintraege nach einigen Tagen, aeltere nach etwa einem Monat weg. ci.ymloder eines der Desktop-Skripte wurde geaendert -- beides gehoert selbst zur PfadlisteDESKTOP_PATHS.desktop-stamp.sh checkhat den gefundenen Eintrag verworfen -- der Grund steht im Log des Schritts "Gefundene Pakete pruefen".
Uebernimmt trotz Aenderung: Die Aenderung liegt ausserhalb der Pfadliste
(zum Beispiel nur pnpm-lock.yaml). Entweder zusaetzlich etwas unter
apps/desktop/ aendern, oder DESKTOP_PATHS in .gitea/scripts/desktop-stamp.sh
um den betroffenen Pfad erweitern -- diese Erweiterung loest selbst einen
Neubau aus, weil desktop-stamp.sh Teil der eigenen Pfadliste ist.
publish: cache miss
actions/cache/restore@v4 mit fail-on-cache-miss: true bricht den Job
publish hart ab, wenn kein Eintrag unter dem Schluessel
desktop-dist-${{ gitea.sha }} existiert. Wahrscheinlichste Ursachen:
- Der Job
desktopist fehlgeschlagen oder uebersprungen worden (sieheif-Bedingung oben) -- im Gitea-Actions-Lauf pruefen, obdesktoptatsaechlich gruen war. - Der Zwischenspeicher-Server des
act_runnerist nicht erreichbar oder nicht aktiviert -- Runner-Konfiguration pruefen (Abschnitt 2,[cache] enabledmuss gesetzt sein). - Der Commit-SHA im Schluessel weicht zwischen den Jobs ab (sollte bei
gitea.shainnerhalb desselben Laufs nicht vorkommen) -- bei Verdacht die Job-Logs beider Schritte (Uebergabe an publishindesktop,Desktop-Pakete aus dem Zwischenspeicher holeninpublish) auf den verwendeten Schluessel vergleichen.
Release-Upload 413
Bricht der Datei-Upload in publish-release.sh mit HTTP 413 oder
curl: (92) HTTP/2 ... PROTOCOL_ERROR ab, laeuft er ueber den Proxy vor
git.vicolab.de -- genau das ist am 2026-09-17 bei Release 1.2.0 passiert
(AppImage, 82 MB). Seitdem geht das Skript von selbst ueber das Host-Gateway
(siehe Abschnitt 4); der Fehler kann nur noch auftreten, wenn GITEA_API
ausdruecklich auf die oeffentliche Adresse gesetzt wird. Fehlende Anhaenge
lassen sich jederzeit vom Host nachtragen:
GITEA_TOKEN=... DESKTOP_DIST=<Ordner mit manifest.json> sh .gitea/scripts/publish-release.sh --tag vX.Y.Z
(die Pakete liegen im API-Abbild unter /app/desktop-dist).
Job security ist rot oder gelb
Der Job ist ein reiner Bericht und beeinflusst weder Abbilder noch Freigaben (er laeuft
erst nach publish und steht in keinem needs). Rot oder gelb heisst deshalb nie, dass
etwas nicht ausgeliefert wurde. Zur Ursache im Job-Protokoll die Zeilen
SECURITY-SUMMARY <werkzeug> skipped reason=... lesen: download oder checksum (Werkzeug
nicht ladbar bzw. Summe stimmt nicht -- Netz pruefen, nie die Summe ohne Pruefung
austauschen), kein-docker / install (Semgrep: Docker fehlt bzw. das Abbild liess sich nicht
laden), kein-pnpm, abbild-fehlt (das Abbild
liegt nicht im Docker-Daemon des Hosts), zeitgrenze (das Werkzeug hat sein eigenes Limit
gerissen), gesamtbudget (die 25 Minuten waren aufgebraucht), fehler (Werkzeug ist
abgestuerzt oder hat nichts geschrieben), kein-bericht. Ein haengender Lauf endet nach 30
Minuten von selbst. Fehlt nur das Artefakt, ist das erwartbar (best effort); die Zahlen
stehen trotzdem im Protokoll.