Ed25519-signed updates, checked via a fixed-tag latest.json manifest on Gitea Releases; scripts/release.sh automates build+sign+publish. Adds a manual "Nach Updates suchen" trigger plus a silent startup check.
174 lines
7.3 KiB
Bash
Executable File
174 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Builds, signs, and publishes a release to Gitea (git.kanjiv.at/KanjiG/mail-client).
|
|
#
|
|
# Usage: scripts/release.sh <version> [notes]
|
|
# scripts/release.sh 0.2.0 "Fixed X, added Y"
|
|
#
|
|
# Requires:
|
|
# - TAURI_SIGNING_PRIVATE_KEY_PATH pointing at the updater signing key
|
|
# (default: ~/.tauri/mail-client-update.key)
|
|
# - GITEA_TOKEN or GITEA_PASSWORD env var for authenticating to the Gitea API
|
|
# (GITEA_TOKEN preferred — a Gitea access token instead of the account password)
|
|
#
|
|
# Only builds for the platform it's run on. To publish Windows/macOS builds,
|
|
# run this same script on/from those platforms with the same version — it
|
|
# appends to the same release rather than overwriting it, and regenerates
|
|
# latest.json to include whichever platform artifacts are present so far.
|
|
|
|
set -euo pipefail
|
|
|
|
VERSION="${1:?Usage: scripts/release.sh <version> [notes]}"
|
|
NOTES="${2:-Release $VERSION}"
|
|
REPO_OWNER="KanjiG"
|
|
REPO_NAME="mail-client"
|
|
GITEA_URL="https://git.kanjiv.at"
|
|
TAG="v$VERSION"
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
export TAURI_SIGNING_PRIVATE_KEY_PATH="${TAURI_SIGNING_PRIVATE_KEY_PATH:-$HOME/.tauri/mail-client-update.key}"
|
|
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}"
|
|
|
|
if [ -z "${GITEA_TOKEN:-}" ] && [ -z "${GITEA_PASSWORD:-}" ]; then
|
|
echo "Set GITEA_TOKEN (preferred) or GITEA_PASSWORD before running this." >&2
|
|
exit 1
|
|
fi
|
|
AUTH_HEADER=()
|
|
if [ -n "${GITEA_TOKEN:-}" ]; then
|
|
AUTH_HEADER=(-H "Authorization: token $GITEA_TOKEN")
|
|
else
|
|
AUTH_HEADER=(-u "$REPO_OWNER:$GITEA_PASSWORD")
|
|
fi
|
|
|
|
echo "==> Syncing version to $VERSION in package.json / tauri.conf.json"
|
|
node -e "
|
|
const fs = require('fs');
|
|
for (const f of ['package.json', 'src-tauri/tauri.conf.json']) {
|
|
const j = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
j.version = '$VERSION';
|
|
fs.writeFileSync(f, JSON.stringify(j, null, 2) + '\n');
|
|
}
|
|
"
|
|
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" src-tauri/Cargo.toml
|
|
|
|
echo "==> Building frontend"
|
|
npm run build
|
|
|
|
echo "==> Building + signing Tauri bundle"
|
|
npx tauri build
|
|
|
|
BUNDLE_DIR="src-tauri/target/release/bundle"
|
|
|
|
# Figure out which platform we're on and where the updater-relevant artifact +
|
|
# signature landed. Tauri's updater only consumes specific bundle formats per
|
|
# platform (AppImage on Linux, NSIS on Windows, .app.tar.gz on macOS) — not
|
|
# .deb/.rpm, those are for manual installs only.
|
|
UNAME="$(uname -s)"
|
|
PLATFORM_KEY=""
|
|
ARTIFACT=""
|
|
case "$UNAME" in
|
|
Linux)
|
|
PLATFORM_KEY="linux-x86_64"
|
|
ARTIFACT="$(find "$BUNDLE_DIR/appimage" -name '*.AppImage' | head -1)"
|
|
;;
|
|
Darwin)
|
|
ARCH="$(uname -m)"
|
|
PLATFORM_KEY="darwin-$([ "$ARCH" = "arm64" ] && echo aarch64 || echo x86_64)"
|
|
ARTIFACT="$(find "$BUNDLE_DIR/macos" -name '*.app.tar.gz' | head -1)"
|
|
;;
|
|
MINGW*|MSYS*|CYGWIN*)
|
|
PLATFORM_KEY="windows-x86_64"
|
|
ARTIFACT="$(find "$BUNDLE_DIR/nsis" -name '*-setup.exe' | head -1)"
|
|
;;
|
|
*)
|
|
echo "Unrecognized platform: $UNAME" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$ARTIFACT" ] || [ ! -f "$ARTIFACT" ]; then
|
|
echo "Could not find the updater artifact for $PLATFORM_KEY under $BUNDLE_DIR" >&2
|
|
exit 1
|
|
fi
|
|
SIGNATURE="$(cat "$ARTIFACT.sig")"
|
|
FILENAME="$(basename "$ARTIFACT")"
|
|
|
|
echo "==> Ensuring release $TAG exists on Gitea"
|
|
RELEASE_JSON="$(curl -s "${AUTH_HEADER[@]}" "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/tags/$TAG")"
|
|
RELEASE_ID="$(echo "$RELEASE_JSON" | node -pe 'JSON.parse(require("fs").readFileSync(0)).id' 2>/dev/null || echo "")"
|
|
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "undefined" ]; then
|
|
RELEASE_JSON="$(curl -s "${AUTH_HEADER[@]}" -X POST "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(node -pe "JSON.stringify({tag_name: '$TAG', name: '$TAG', body: process.argv[1]})" "$NOTES")")
|
|
RELEASE_ID="$(echo "$RELEASE_JSON" | node -pe 'JSON.parse(require("fs").readFileSync(0)).id')"
|
|
fi
|
|
echo " release id: $RELEASE_ID"
|
|
|
|
echo "==> Uploading $FILENAME"
|
|
curl -s "${AUTH_HEADER[@]}" -X POST \
|
|
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/$RELEASE_ID/assets?name=$FILENAME" \
|
|
-F "attachment=@$ARTIFACT" > /dev/null
|
|
|
|
# latest.json lives on a SEPARATE, fixed-tag release ("latest-manifest"),
|
|
# independent of the versioned release tags above. The updater's endpoint in
|
|
# tauri.conf.json points at this fixed tag, not at "latest" — Gitea's support
|
|
# for a GitHub-style /releases/latest/download/ alias varies by version, so a
|
|
# fixed tag we fully control is more robust than depending on that.
|
|
MANIFEST_TAG="latest-manifest"
|
|
echo "==> Ensuring $MANIFEST_TAG release exists"
|
|
MANIFEST_RELEASE_JSON="$(curl -s "${AUTH_HEADER[@]}" "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/tags/$MANIFEST_TAG")"
|
|
MANIFEST_RELEASE_ID="$(echo "$MANIFEST_RELEASE_JSON" | node -pe 'JSON.parse(require("fs").readFileSync(0)).id' 2>/dev/null || echo "")"
|
|
if [ -z "$MANIFEST_RELEASE_ID" ] || [ "$MANIFEST_RELEASE_ID" = "undefined" ]; then
|
|
MANIFEST_RELEASE_JSON="$(curl -s "${AUTH_HEADER[@]}" -X POST "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(node -pe "JSON.stringify({tag_name: process.argv[1], name: 'Update manifest (do not download directly)', body: 'Holds latest.json for the in-app updater. Always points at the newest release.'})" "$MANIFEST_TAG")")
|
|
MANIFEST_RELEASE_ID="$(echo "$MANIFEST_RELEASE_JSON" | node -pe 'JSON.parse(require("fs").readFileSync(0)).id')"
|
|
fi
|
|
|
|
echo "==> Fetching existing latest.json (if any) to merge platforms"
|
|
EXISTING_LATEST="$(curl -sf "$GITEA_URL/$REPO_OWNER/$REPO_NAME/releases/download/$MANIFEST_TAG/latest.json" || echo '{}')"
|
|
|
|
TMP_LATEST="$(mktemp)"
|
|
node -e "
|
|
const fs = require('fs');
|
|
let existing = {};
|
|
try { existing = JSON.parse(\`$EXISTING_LATEST\`); } catch { existing = {}; }
|
|
const platforms = existing.platforms || {};
|
|
platforms['$PLATFORM_KEY'] = {
|
|
signature: \`$SIGNATURE\`,
|
|
url: '$GITEA_URL/$REPO_OWNER/$REPO_NAME/releases/download/$TAG/$FILENAME',
|
|
};
|
|
const manifest = {
|
|
version: '$VERSION',
|
|
notes: $(node -pe "JSON.stringify(process.argv[1])" "$NOTES"),
|
|
pub_date: new Date().toISOString(),
|
|
platforms,
|
|
};
|
|
fs.writeFileSync('$TMP_LATEST', JSON.stringify(manifest, null, 2));
|
|
"
|
|
|
|
# Remove any previous latest.json asset before re-uploading (Gitea won't
|
|
# overwrite an existing asset with the same name).
|
|
EXISTING_ASSET_ID="$(curl -s "${AUTH_HEADER[@]}" "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/$MANIFEST_RELEASE_ID" \
|
|
| node -pe "
|
|
const rel = JSON.parse(require('fs').readFileSync(0));
|
|
const a = (rel.assets || []).find(x => x.name === 'latest.json');
|
|
a ? a.id : ''
|
|
" 2>/dev/null || echo "")"
|
|
if [ -n "$EXISTING_ASSET_ID" ] && [ "$EXISTING_ASSET_ID" != "undefined" ]; then
|
|
curl -s "${AUTH_HEADER[@]}" -X DELETE \
|
|
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/$MANIFEST_RELEASE_ID/assets/$EXISTING_ASSET_ID" > /dev/null
|
|
fi
|
|
|
|
echo "==> Uploading latest.json to $MANIFEST_TAG"
|
|
curl -s "${AUTH_HEADER[@]}" -X POST \
|
|
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/$MANIFEST_RELEASE_ID/assets?name=latest.json" \
|
|
-F "attachment=@$TMP_LATEST" > /dev/null
|
|
rm -f "$TMP_LATEST"
|
|
|
|
echo "==> Done."
|
|
echo " Release: $GITEA_URL/$REPO_OWNER/$REPO_NAME/releases/tag/$TAG"
|
|
echo " Manifest URL: $GITEA_URL/$REPO_OWNER/$REPO_NAME/releases/download/$MANIFEST_TAG/latest.json"
|