#!/bin/sh # desktop-collect.sh -- Desktop-Pakete aus dem Tauri-Bau einsammeln, unter # kanonischem Namen ablegen und manifest.json schreiben (Phase 18, D-08). # # Kanalmodell (identisch zu publish-images.sh, an GITHUB_REF entschieden, # damit lokale Proben ohne Runner pruefbar sind): # refs/tags/v* -> Kanal live, kein Namenssuffix # refs/heads/main -> Kanal beta, Suffix -beta.{7-stelliger SHA} am Dateinamen # alles andere -> Kanal dev, kein Suffix (lokale Proben tragen den # Freigabe-Namen, damit desktop-version.sh/desktop-collect.sh # ohne Pipeline durchgespielt werden koennen) # # Aufruf: sh .gitea/scripts/desktop-collect.sh --require linux[,windows] # # Umgebung: # GITHUB_REF Kanalentscheidung (siehe oben) # DESKTOP_DIST Zielordner fuer die Pakete (Vorgabe: desktop-dist) # TAURI_DIR Tauri-Projektordner (Vorgabe: apps/desktop/src-tauri) # # Die Version kommt aus tauri.conf.json (von desktop-version.sh geschrieben # oder als eingecheckte Basislinie vorhanden) -- dieses Skript liest sie nur, # es schreibt sie nicht. Dieses Skript kennt kein Secret. # # Signatur (quick-260917-kgc): Die Tauri-CLI legt beim Bau mit # `createUpdaterArtifacts` neben jedem Bundle eine `.sig` ab # (minisign, eine Base64-Zeile). Dieses Skript traegt deren INHALT als # `files..signature` ins Manifest -- die `.sig`-Datei selbst wird # nicht kopiert (die API liefert JSON). Pflichtregel: fehlt die `.sig`, # bricht das Skript ab, wenn TAURI_SIGNING_PRIVATE_KEY gesetzt ist ODER der # Kanal nicht `dev` ist (main/Tag -- dort sind Signaturen Pflicht, # `--no-sign` gibt es nur lokal); sonst Warnung und das Feld entfaellt # (lokaler Bau mit `tauri build --no-sign` bleibt moeglich, die API antwortet # auf /desktop/update dann 204). Neues Feld `updateVersion` = die SemVer-Form # fuer den Updater: X.Y.Z bei live/dev, X.Y.Z-beta.g bei beta (das # `g` ist Pflicht, ein rein numerischer SHA mit fuehrender Null waere kein # gueltiger SemVer-Identifier). Der Schluessel wird nur auf Gesetztsein # geprueft, nie gelesen oder ausgegeben. set -eu REQUIRE="" while [ $# -gt 0 ]; do case "$1" in --require) shift REQUIRE="${1:-}" ;; *) echo "Unbekanntes Argument: $1" >&2 exit 1 ;; esac shift done if [ -z "$REQUIRE" ]; then echo "Aufruf: $0 --require linux[,windows]" >&2 exit 1 fi DESKTOP_DIST="${DESKTOP_DIST:-desktop-dist}" TAURI_DIR="${TAURI_DIR:-apps/desktop/src-tauri}" REF="${GITHUB_REF:-}" case "$REF" in refs/tags/v*) CHANNEL=live SUFFIX="" ;; refs/heads/main) CHANNEL=beta SHA_SHORT="$(git rev-parse --short=7 HEAD)" SUFFIX="-beta.$SHA_SHORT" ;; *) CHANNEL=dev SUFFIX="" ;; esac VERSION="$(jq -r .version "$TAURI_DIR/tauri.conf.json")" case "$VERSION" in [0-9]*.[0-9]*.[0-9]*) if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then echo "Version '$VERSION' aus $TAURI_DIR/tauri.conf.json ist nicht rein numerisch (X.Y.Z)." >&2 exit 1 fi ;; *) echo "Version '$VERSION' aus $TAURI_DIR/tauri.conf.json ist nicht rein numerisch (X.Y.Z)." >&2 exit 1 ;; esac COMMIT="$(git rev-parse --short=7 HEAD)" BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" # SemVer-Form fuer den Updater (siehe Kopfkommentar): erst NACH der # Versionspruefung bilden, SUFFIX/SHA_SHORT sind aus der Kanalentscheidung # bekannt. UPDATE_VERSION="$VERSION" if [ "$CHANNEL" = beta ]; then UPDATE_VERSION="$VERSION-beta.g$SHA_SHORT" fi SIGN_REQUIRED=0 if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ] || [ "$CHANNEL" != dev ]; then SIGN_REQUIRED=1 fi # read_signature : schreibt den Inhalt von .sig # nach stdout (genau eine Base64-Zeile). Fehlt die Datei: bei # SIGN_REQUIRED=1 Fehler und exit 1, sonst Warnung und leere Ausgabe. read_signature() { SIG_FILE="$1.sig" if [ -f "$SIG_FILE" ]; then SIG_CONTENT="$(cat "$SIG_FILE")" # grep prueft zeilenweise -- die Zeilenzahl deshalb getrennt erzwingen. SIG_LINES="$(printf '%s\n' "$SIG_CONTENT" | wc -l | tr -d ' ')" if [ "$SIG_LINES" -ne 1 ] || ! printf '%s' "$SIG_CONTENT" | grep -qE '^[A-Za-z0-9+/=]+$'; then echo "Signaturdatei $SIG_FILE hat nicht die erwartete Form (genau eine Base64-Zeile)." >&2 exit 1 fi printf '%s' "$SIG_CONTENT" return 0 fi if [ "$SIGN_REQUIRED" = 1 ]; then echo "Signaturdatei $SIG_FILE fehlt. Im CI muessen TAURI_SIGNING_PRIVATE_KEY und TAURI_SIGNING_PRIVATE_KEY_PASSWORD an den tauri-build-Schritten gesetzt sein; --no-sign ist nur lokal erlaubt." >&2 exit 1 fi echo "Warnung: Signaturdatei $SIG_FILE fehlt (Bau ohne Schluessel, Kanal dev) -- Feld signature entfaellt, kein Update in der App." >&2 return 0 } mkdir -p "$DESKTOP_DIST" # Alte Pakete/Manifest entfernen, Platzhalter (.gitkeep) bleibt erhalten. rm -f "$DESKTOP_DIST"/*.AppImage "$DESKTOP_DIST"/*.exe "$DESKTOP_DIST/manifest.json" MANIFEST_ARGS="" TMP_MANIFEST="$(mktemp)" trap 'rm -f "$TMP_MANIFEST"' EXIT INT TERM LINUX_NAME="" LINUX_SIZE="" LINUX_SHA="" LINUX_SIG="" WINDOWS_NAME="" WINDOWS_SIZE="" WINDOWS_SHA="" WINDOWS_SIG="" case ",$REQUIRE," in *,linux,*) APPIMAGE_DIR="$TAURI_DIR/target/release/bundle/appimage" APPIMAGE_COUNT="$(find "$APPIMAGE_DIR" -maxdepth 1 -name '*.AppImage' 2>/dev/null | wc -l | tr -d ' ')" if [ "$APPIMAGE_COUNT" -ne 1 ]; then echo "Erwartet genau eine .AppImage-Datei in $APPIMAGE_DIR, gefunden: $APPIMAGE_COUNT" >&2 exit 1 fi APPIMAGE_SRC="$(find "$APPIMAGE_DIR" -maxdepth 1 -name '*.AppImage')" LINUX_NAME="Tessera-${VERSION}${SUFFIX}.AppImage" cp "$APPIMAGE_SRC" "$DESKTOP_DIST/$LINUX_NAME" LINUX_SIZE="$(stat -c %s "$DESKTOP_DIST/$LINUX_NAME")" LINUX_SHA="$(sha256sum "$DESKTOP_DIST/$LINUX_NAME" | cut -d' ' -f1)" LINUX_SIG="$(read_signature "$APPIMAGE_SRC")" [ -n "$LINUX_SIG" ] || [ "$SIGN_REQUIRED" = 0 ] || exit 1 if [ -n "$LINUX_SIG" ]; then LINUX_SIGNED="signiert"; else LINUX_SIGNED="ohne Signatur"; fi echo "linux: $LINUX_NAME (${LINUX_SIZE} Bytes, sha256 $LINUX_SHA, $LINUX_SIGNED)" ;; esac case ",$REQUIRE," in *,windows,*) NSIS_DIR="$TAURI_DIR/target/x86_64-pc-windows-msvc/release/bundle/nsis" NSIS_COUNT="$(find "$NSIS_DIR" -maxdepth 1 -name '*.exe' 2>/dev/null | wc -l | tr -d ' ')" if [ "$NSIS_COUNT" -ne 1 ]; then echo "Erwartet genau eine .exe-Datei in $NSIS_DIR, gefunden: $NSIS_COUNT" >&2 exit 1 fi NSIS_SRC="$(find "$NSIS_DIR" -maxdepth 1 -name '*.exe')" WINDOWS_NAME="Tessera-Setup-${VERSION}${SUFFIX}.exe" cp "$NSIS_SRC" "$DESKTOP_DIST/$WINDOWS_NAME" WINDOWS_SIZE="$(stat -c %s "$DESKTOP_DIST/$WINDOWS_NAME")" WINDOWS_SHA="$(sha256sum "$DESKTOP_DIST/$WINDOWS_NAME" | cut -d' ' -f1)" WINDOWS_SIG="$(read_signature "$NSIS_SRC")" [ -n "$WINDOWS_SIG" ] || [ "$SIGN_REQUIRED" = 0 ] || exit 1 if [ -n "$WINDOWS_SIG" ]; then WINDOWS_SIGNED="signiert"; else WINDOWS_SIGNED="ohne Signatur"; fi echo "windows: $WINDOWS_NAME (${WINDOWS_SIZE} Bytes, sha256 $WINDOWS_SHA, $WINDOWS_SIGNED)" ;; esac # manifest.json ausschliesslich ueber jq -n mit --arg/--argjson bauen (kein # manuelles String-Zusammenbauen von JSON). jq -n \ --arg version "$VERSION" \ --arg updateVersion "$UPDATE_VERSION" \ --arg channel "$CHANNEL" \ --arg commit "$COMMIT" \ --arg buildTime "$BUILD_TIME" \ --arg linuxName "$LINUX_NAME" \ --argjson linuxSize "${LINUX_SIZE:-null}" \ --arg linuxSha "$LINUX_SHA" \ --arg linuxSig "$LINUX_SIG" \ --arg windowsName "$WINDOWS_NAME" \ --argjson windowsSize "${WINDOWS_SIZE:-null}" \ --arg windowsSha "$WINDOWS_SHA" \ --arg windowsSig "$WINDOWS_SIG" \ '{ version: $version, updateVersion: $updateVersion, channel: $channel, commit: $commit, buildTime: $buildTime, files: ( {} + (if $linuxName != "" then { linux: ({ name: $linuxName, size: $linuxSize, sha256: $linuxSha } + (if $linuxSig != "" then { signature: $linuxSig } else {} end)) } else {} end) + (if $windowsName != "" then { windows: ({ name: $windowsName, size: $windowsSize, sha256: $windowsSha } + (if $windowsSig != "" then { signature: $windowsSig } else {} end)) } else {} end) ) }' > "$DESKTOP_DIST/manifest.json" echo "Manifest geschrieben: $DESKTOP_DIST/manifest.json (Version $VERSION, updateVersion $UPDATE_VERSION, Kanal $CHANNEL)"