name: Release Desktop Client # Baut den Tauri-Client aus crates/tauri-app fuer Windows und Linux, laedt die # Bundles als Job-Artefakte hoch und haengt sie an ein Gitea-Release zum Tag. # # ACHTUNG — Verzeichnis-Falle (hat im Nachbarprojekt lserver einen Tag CI # gekostet): Sobald `.gitea/workflows/` existiert, ignoriert Gitea # `.github/workflows/` KOMPLETT. In diesem Repo gibt es kein `.github/`, also # ist heute nichts betroffen — wer aber spaeter einen Workflow unter # `.github/workflows/` anlegt, bekommt keinen Lauf und auch keinen roten # Fehler, sondern schlicht Stille. Alle Workflows gehoeren hierher. # # VOR DEM TAG VERSION BUMPEN (analog zu lserver, wo package.json gebumpt wird): # Die Version im Release kommt aus dem Tag, die Version IM Artefaktnamen aus # der Config. Beide muessen zusammenpassen, sonst heisst die Datei zu einem # Tag v0.2.0 weiterhin `maarcadetweet_0.1.0_x64-setup.exe`: # * crates/tauri-app/src-tauri/tauri.conf.json -> "version" # * crates/tauri-app/src-tauri/Cargo.toml -> [package] version # * crates/tauri-app/package.json -> "version" (Konsistenz) # Details: docs/tauri-release.md, Abschnitt "Release-Checkliste". # # macOS: DAFUER GIBT ES KEINEN RUNNER. Weder Gitea-Instanz noch Infrastruktur # haben einen macOS-Host; .dmg/.app werden lokal gebaut und von Hand an das # hier erzeugte Release gehaengt: # cd crates/tauri-app && npm ci && npm run tauri -- build --ci # # Artefakte: src-tauri/target/release/bundle/dmg/*.dmg und macos/*.app # # Universal-Build: npm run tauri -- build --ci --target universal-apple-darwin # Danach im Gitea-Release "Edit release" -> Dateien anhaengen. Ohne # Notarisierung meldet Gatekeeper die App als nicht verifiziert (siehe # docs/tauri-release.md, Abschnitt 9 "Offene Punkte"). on: push: tags: - "v*.*.*" workflow_dispatch: jobs: windows: name: Windows (MSI + NSIS) # Label `windows` = act_runner auf der Build-VM winbuild (192.168.1.69), # Win11, cargo 1.98.1, Node 24, Tauri-CLI 2.11.4. Laut Infrastruktur-Doku # on-demand — laeuft die VM nicht, wird der Job nie geplant (Gitea zeigt # dann gar keinen Lauf an, keinen fehlgeschlagenen). runs-on: windows timeout-minutes: 60 defaults: run: shell: powershell steps: - name: Checkout uses: actions/checkout@v4 # package-lock.json liegt unter crates/tauri-app/ -> npm ci (reproduzierbar). # Kein `npm install`: das wuerde den Lock im Build veraendern. - name: Install frontend dependencies working-directory: crates/tauri-app run: npm ci # package.json definiert `"tauri": "tauri"` — der npm-Umweg nutzt die # @tauri-apps/cli-devDependency aus dem Lock (2.x) statt einer global # installierten `cargo tauri`-Version, ist also an das Repo gebunden. # `cargo tauri build --ci` waere gleichwertig, haengt aber an dem, was # gerade auf dem Runner installiert ist. # bundle.targets in tauri.conf.json steht auf "all" -> unter Windows # heisst das msi + nsis. - name: Build Tauri bundles working-directory: crates/tauri-app run: npm run tauri -- build --ci - name: Bundles auflisten working-directory: crates/tauri-app run: | $bundle = "src-tauri\target\release\bundle" Get-ChildItem -Path $bundle -Recurse -Include *.msi, *.exe | ForEach-Object { Write-Host "$($_.FullName) ($([math]::Round($_.Length / 1MB)) MB)" } - name: Upload bundles uses: actions/upload-artifact@v4 with: name: maarcadetweet-windows path: | crates/tauri-app/src-tauri/target/release/bundle/msi/*.msi crates/tauri-app/src-tauri/target/release/bundle/nsis/*.exe retention-days: 30 - name: Gitea-Release anlegen und Bundles anhaengen if: startsWith(github.ref, 'refs/tags/v') env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | $ErrorActionPreference = "Stop" $api = "$env:GITHUB_SERVER_URL/api/v1/repos/$env:GITHUB_REPOSITORY" $tag = $env:GITHUB_REF_NAME $auth = "Authorization: token $env:GITEA_TOKEN" # Release-Notes aus dem passenden CHANGELOG-Abschnitt ziehen. # CHANGELOG.md existiert in diesem Repo noch nicht — sobald es # angelegt wird (Format `## [0.2.0] - ...` wie bei lserver), landet # der Abschnitt automatisch im Release. $notes = "Automatisch gebaut aus $env:GITHUB_SHA." $version = $tag.TrimStart("v") if (Test-Path CHANGELOG.md) { # Ausdruecklich als UTF-8 lesen: Windows PowerShell 5.1 nimmt sonst # die ANSI-Codepage und macht aus "Aenderungen" Buchstabensalat. $lines = [IO.File]::ReadAllText((Resolve-Path CHANGELOG.md), [Text.UTF8Encoding]::new($false)) -split "`r?`n" $start = ($lines | Select-String -Pattern "^## \[$([regex]::Escape($version))\]" | Select-Object -First 1) if ($start) { $from = $start.LineNumber $rest = $lines[$from..($lines.Count - 1)] $next = ($rest | Select-String -Pattern "^## \[" | Select-Object -First 1) $take = if ($next) { $next.LineNumber - 2 } else { $rest.Count - 1 } if ($take -ge 0) { $notes = ($rest[0..$take] -join "`n").Trim() } } } $body = @{ tag_name = $tag; name = "maarcadetweet $tag"; body = $notes } | ConvertTo-Json -Depth 3 $bodyFile = Join-Path $env:RUNNER_TEMP "release.json" [IO.File]::WriteAllText($bodyFile, $body, [Text.UTF8Encoding]::new($false)) # Beide Jobs (windows + linux) haengen an DASSELBE Release und laufen # parallel. Deshalb: anlegen versuchen, und wenn das scheitert (der # andere Job war schneller, oder es ist ein Re-Run), das vorhandene # Release per Tag holen. $created = curl.exe -s -X POST -H $auth -H "Content-Type: application/json" --data-binary "@$bodyFile" "$api/releases" | ConvertFrom-Json if (-not $created.id) { $created = curl.exe -s -H $auth "$api/releases/tags/$tag" | ConvertFrom-Json } if (-not $created.id) { throw "Konnte kein Release fuer $tag anlegen oder finden." } $bundle = "crates\tauri-app\src-tauri\target\release\bundle" $files = Get-ChildItem -Path $bundle -Recurse -Include *.msi, *.exe if (-not $files) { throw "Keine Windows-Bundles unter $bundle gefunden." } foreach ($f in $files) { $name = [Uri]::EscapeDataString($f.Name) curl.exe -s -o NUL -w "Asset-Upload $($f.Name): HTTP %{http_code}`n" -X POST -H $auth -F "attachment=@$($f.FullName)" "$api/releases/$($created.id)/assets?name=$name" } Write-Host "Release: $env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/releases/tag/$tag" linux: name: Linux (deb + rpm + AppImage) # NICHT `ubuntu-latest` — auf diesem Runner (VM ci-runner, 192.168.1.72) # ist dieses Label auf `docker://node:22-bookworm` gemappt, also einen # Container mit Node, aber ohne Rust und ohne GTK. Der Job braeche dort # bei `cargo` ab. Nachgesehen in /var/lib/gitea-runner/.runner: # labels: ['ubuntu-latest:docker://node:22-bookworm', 'linux-amd64:host'] # `linux-amd64` ist das Host-Label, und auf dem Host liegen Rust # (/root/.cargo/bin, auch ohne Login-Shell im PATH), Node 22 und die # Tauri-GTK-Deps. Der Runner-Dienst laeuft als root. runs-on: linux-amd64 timeout-minutes: 60 defaults: run: shell: bash env: # linuxdeploy/appimagetool werden als AppImage aus ~/.cache/tauri # gestartet und brauchen sonst FUSE, was in der VM nicht zuverlaessig # funktioniert (siehe MAARCADE-INFRASTRUKTUR.md: "AppImage scheitert an # linuxdeploy/FUSE"). Mit dieser Variable entpacken sie sich selbst. APPIMAGE_EXTRACT_AND_RUN: "1" # Das Release-Profil in src-tauri/Cargo.toml strippt bereits selbst; # linuxdeploys eigener strip-Lauf ist dann nur eine weitere Fehlerquelle. NO_STRIP: "true" steps: - name: Checkout uses: actions/checkout@v4 - name: Toolchain melden run: | node --version npm --version cargo --version || echo "cargo fehlt im PATH — ggf. ~/.cargo/env sourcen" - name: Install frontend dependencies working-directory: crates/tauri-app run: npm ci # bundle.targets in tauri.conf.json ist "all" -> unter Linux sind das # deb, rpm und appimage. Bewusst in zwei Aufrufe getrennt: der # AppImage-Schritt ist der fragile (Downloads von linuxdeploy + # appimagetool beim ersten Lauf, FUSE), und wenn er faellt, sollen deb # und rpm trotzdem im Release landen. Der zweite Aufruf ist billig — der # Cargo-Release-Build ist dann schon im target/-Cache. - name: Build Tauri bundles (deb + rpm) working-directory: crates/tauri-app run: npm run tauri -- build --ci --bundles deb,rpm - name: Build Tauri bundle (AppImage) working-directory: crates/tauri-app continue-on-error: true run: npm run tauri -- build --ci --bundles appimage - name: Bundles auflisten run: | find crates/tauri-app/src-tauri/target/release/bundle \ \( -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' \) \ -printf '%p (%kK)\n' || true - name: Upload bundles uses: actions/upload-artifact@v4 with: name: maarcadetweet-linux path: | crates/tauri-app/src-tauri/target/release/bundle/deb/*.deb crates/tauri-app/src-tauri/target/release/bundle/rpm/*.rpm crates/tauri-app/src-tauri/target/release/bundle/appimage/*.AppImage # AppImage darf fehlen (siehe continue-on-error oben), deb/rpm nicht — # ein komplett leerer Upload soll auffallen. if-no-files-found: error retention-days: 30 - name: Gitea-Release anlegen und Bundles anhaengen if: startsWith(github.ref, 'refs/tags/v') env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" tag="${GITHUB_REF_NAME}" version="${tag#v}" # Gleiche CHANGELOG-Logik wie im Windows-Job. Die Datei existiert # heute nicht; ohne sie bleibt es beim Fallback-Text. notes="Automatisch gebaut aus ${GITHUB_SHA}." if [ -f CHANGELOG.md ]; then section="$(awk -v v="$version" ' /^## \[/ { if (found) exit; if ($0 ~ "^## \\[" v "\\]") { found = 1; next } } found { print } ' CHANGELOG.md)" if [ -n "$(printf '%s' "$section" | tr -d '[:space:]')" ]; then notes="$section" fi fi # jq ist auf dem Runner nicht garantiert, Node 22 schon. payload="${RUNNER_TEMP}/release.json" NOTES="$notes" TAG="$tag" node -e ' const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ tag_name: process.env.TAG, name: `maarcadetweet ${process.env.TAG}`, body: process.env.NOTES, })); ' "$payload" # Anlegen oder — falls der Windows-Job schneller war bzw. das Release # vom Re-Run schon existiert — das vorhandene holen. id="$(curl -s -X POST -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" --data-binary "@${payload}" \ "${api}/releases" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(String(JSON.parse(s).id||""))}catch{}})')" if [ -z "$id" ]; then id="$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ "${api}/releases/tags/${tag}" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(String(JSON.parse(s).id||""))}catch{}})')" fi if [ -z "$id" ]; then echo "Konnte kein Release fuer ${tag} anlegen oder finden." >&2 exit 1 fi bundle="crates/tauri-app/src-tauri/target/release/bundle" found=0 while IFS= read -r f; do found=1 name="$(basename "$f")" code="$(curl -s -o /dev/null -w '%{http_code}' -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -F "attachment=@${f}" \ "${api}/releases/${id}/assets?name=${name}")" echo "Asset-Upload ${name}: HTTP ${code}" done < <(find "$bundle" \( -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' \) | sort) [ "$found" -eq 1 ] || { echo "Keine Linux-Bundles unter ${bundle} gefunden." >&2; exit 1; } echo "Release: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${tag}"