Files
parking_solution/.gitea/workflows/release.yml
T
julian ae5c122980 ci(gitea): CI checks + tag-triggered signed Tauri desktop release
Mirror the house Gitea Actions pattern (cf. trm/processor): corepack pnpm +
frozen install on ubuntu-latest.

ci.yml — push/PR to dev → pnpm turbo run build lint + test (covers tsc, vite
build, i18n catalog type-parity, and the shared vitest suite).

release.yml — on a v* tag → install Tauri Linux deps (webkit2gtk-4.1, libsoup-3,
gtk-3, appindicator, rsvg, patchelf) + rustup, cache cargo/target, then
`pnpm --filter @parking/desktop bundle` signed with the updater key from Gitea
secrets (TAURI_SIGNING_PRIVATE_KEY + _PASSWORD). Collects .deb/.rpm/.AppImage +
their .sig, assembles latest.json (platform key linux-x86_64 — verified against
the tauri-plugin-updater target format), and publishes a Gitea Release via the
API with the built-in token (no marketplace release action needed).

Both workflows validated (PyYAML parse). No secret values committed — only
${{ secrets.* }} references.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-21 12:33:20 +02:00

150 lines
5.5 KiB
YAML

name: Release desktop
# Build the signed Tauri desktop installers on a version tag and publish them as
# a Gitea Release. The Tauri auto-updater (apps/web/src/lib/desktop-updater.ts)
# fetches these; latest.json + each installer + its .sig are what it needs.
#
# Trigger: push a tag like v0.1.0. The job builds .deb/.rpm/.AppImage, signs them
# with the updater key (Gitea secrets), assembles latest.json, and uploads
# everything to the Release for that tag.
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
bundle:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node 22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Enable pnpm
run: corepack enable && corepack prepare pnpm@10.24.0 --activate
- name: Install Tauri system deps
# ubuntu-latest runner has no GUI/webkit libs by default. These are the
# exact deps a Tauri v2 Linux build needs (verified locally): WebKitGTK
# 4.1 + libsoup-3 + the GTK/appindicator/rsvg stack + AppImage tooling.
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev \
libsoup-3.0-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
file \
build-essential \
curl \
wget
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo + target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
apps/desktop/src-tauri/target
key: ${{ runner.os }}-cargo-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build + sign desktop bundle
env:
# Updater signing key (Gitea repo/org secrets). Without these the
# bundle is unsigned and the updater would reject it.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: pnpm --filter @parking/desktop bundle
- name: Collect artifacts
id: collect
# Gather the installers + their .sig into a flat dist/ for upload.
run: |
set -e
BUNDLE=apps/desktop/src-tauri/target/release/bundle
mkdir -p dist
find "$BUNDLE" \( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \
-o -name '*.AppImage.sig' -o -name '*.deb.sig' -o -name '*.rpm.sig' \) \
-exec cp {} dist/ \;
echo "Artifacts:"; ls -la dist/
- name: Assemble latest.json
# The Tauri updater fetches a manifest describing the newest version, its
# notes, and per-target {signature, url}. We point the AppImage target at
# this release's asset URL. Adjust the platform keys you actually ship.
env:
SERVER_URL: ${{ github.server_url }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: |
set -e
VERSION="${TAG#v}"
APPIMAGE=$(cd dist && ls *.AppImage | head -1)
SIG=$(cat "dist/${APPIMAGE}.sig")
ASSET_URL="${SERVER_URL}/${REPO}/releases/download/${TAG}/${APPIMAGE}"
cat > dist/latest.json <<JSON
{
"version": "${VERSION}",
"notes": "Parking System ${TAG}",
"pub_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"platforms": {
"linux-x86_64": {
"signature": "${SIG}",
"url": "${ASSET_URL}"
}
}
}
JSON
echo "latest.json:"; cat dist/latest.json
- name: Create release + upload assets (Gitea API)
# Uses the built-in token; no marketplace release action required. Creates
# the release for this tag (idempotent-ish: ignores "already exists") and
# uploads every file in dist/ as an asset.
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
API: ${{ github.api_url }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: |
set -e
# Create the release (capture id; tolerate an existing one).
REL=$(curl -sS -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"draft\":false,\"prerelease\":false}" \
"${API}/repos/${REPO}/releases" || true)
REL_ID=$(printf '%s' "$REL" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
if [ -z "$REL_ID" ]; then
# Release may already exist for this tag — look it up by tag.
REL_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
"${API}/repos/${REPO}/releases/tags/${TAG}" \
| grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
fi
echo "release id: ${REL_ID}"
for f in dist/*; do
name=$(basename "$f")
echo "uploading ${name}"
curl -sS -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${f}" \
"${API}/repos/${REPO}/releases/${REL_ID}/assets?name=${name}" >/dev/null
done
echo "done"