7004b5b020
- ci.yml: Secrets TAURI_SIGNING_PRIVATE_KEY/_PASSWORD als env nur an den beiden tauri-build-Schritten des Jobs desktop (kein Job-env, kein --no-sign); alles andere strukturgleich - desktop-collect.sh: liest <bundle>.sig (genau eine Base64-Zeile) als files.<p>.signature ins Manifest, schreibt updateVersion (X.Y.Z bzw. X.Y.Z-beta.g<sha7>); fehlende .sig bricht auf main/Tag oder bei gesetztem Schluessel ab, warnt sonst (dev, lokal --no-sign) - desktop-stamp.sh check: kein Cache-Stand ohne updateVersion und Signaturen beider Plattformen - .gitignore: *.key Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
221 lines
8.2 KiB
Bash
Executable File
221 lines
8.2 KiB
Bash
Executable File
#!/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 `<bundle>.sig` ab
|
|
# (minisign, eine Base64-Zeile). Dieses Skript traegt deren INHALT als
|
|
# `files.<plattform>.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<sha7> 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 <bundle-pfad>: schreibt den Inhalt von <bundle-pfad>.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)"
|