Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m45s
Phase A of the desktop Release + install path (M10 / tasks 2014, 1998): - .forgejo/workflows/desktop.yml: tag-gated "Publish release" step + contents:write. On a v* tag the build now publishes a Fabled-Git Release with the de-bundled AppImage + .deb attached — a stable, versioned fetch target (Actions artifacts are ephemeral/test-only). Dormant on dev/main. - desktop/packaging/publish-release.sh: creates/reuses the Release via the Forgejo API using the runner-injected token; idempotent asset replace. - desktop/packaging/install.sh: curl|sh one-command installer — native .deb on Debian/Ubuntu, de-bundled AppImage everywhere else (installed to ~/Applications/ThoughtSync.AppImage, matching src/integration.rs so the app sees itself integrated). AppImage path needs no sudo. Plumbing only — no release cut (rule 2); activates on the operator's first v* tag. In-app self-update (tauri-plugin-updater + signed latest.json) is Phase B, gated on the operator's signing key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
112 lines
5.0 KiB
Bash
Executable File
112 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Publish the built desktop bundles as assets on a Fabled-Git (Forgejo) Release.
|
|
#
|
|
# WHY: CI Actions artifacts are ephemeral, per-run, and auth-gated — useless as a
|
|
# distribution/fetch target. The install script (install.sh) and the in-app
|
|
# updater both need a STABLE, versioned URL. A Release attached to the pushed
|
|
# `v*` tag is that target: `/releases/latest` always points at the newest one,
|
|
# and each asset has a permanent browser_download_url.
|
|
#
|
|
# WHEN: CI-only, and ONLY on a `v*` tag build (the workflow gates this step with
|
|
# `if: startsWith(github.ref, 'refs/tags/v')`). Cutting the tag is the operator's
|
|
# action (rule 2) — this script never creates a tag, it only publishes a Release
|
|
# for a tag that already exists.
|
|
#
|
|
# The build + de-bundle steps run first; this consumes their output:
|
|
# desktop/src-tauri/target/release/bundle/appimage/*.AppImage (de-bundled)
|
|
# desktop/src-tauri/target/release/bundle/deb/*.deb
|
|
#
|
|
# Instance-agnostic: server + repo come from the runner's github.* context
|
|
# (Forgejo populates them for compatibility), so nothing is hardcoded to one host.
|
|
#
|
|
# Idempotent: re-running for the same tag reuses the existing Release and
|
|
# replaces same-named assets, so a re-run (or workflow_dispatch retry) is safe.
|
|
set -euo pipefail
|
|
|
|
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required (runner-injected; needs contents:write)}"
|
|
: "${GITHUB_SERVER_URL:?GITHUB_SERVER_URL is required}"
|
|
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required (owner/repo)}"
|
|
: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required (the tag, e.g. v0.1.0)}"
|
|
|
|
API="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY"
|
|
TAG="$GITHUB_REF_NAME"
|
|
AUTH=(-H "Authorization: token $GITHUB_TOKEN")
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
BUNDLE_ROOT="$REPO_ROOT/desktop/src-tauri/target/release/bundle"
|
|
|
|
# --- collect the assets to upload -------------------------------------------
|
|
shopt -s nullglob
|
|
ASSETS=(
|
|
"$BUNDLE_ROOT"/appimage/*.AppImage
|
|
"$BUNDLE_ROOT"/deb/*.deb
|
|
)
|
|
if [ ${#ASSETS[@]} -eq 0 ]; then
|
|
echo "ERROR: no bundles under $BUNDLE_ROOT — did the tauri build run?" >&2
|
|
exit 1
|
|
fi
|
|
echo "==> Publishing release $TAG with ${#ASSETS[@]} asset(s):"
|
|
for a in "${ASSETS[@]}"; do echo " $(basename "$a") ($(du -h "$a" | cut -f1))"; done
|
|
|
|
# curl wrapper that returns the response body on stdout and fails the script on
|
|
# an HTTP >=400 that we didn't explicitly allow (via ALLOW_CODES).
|
|
api() {
|
|
local method="$1" url="$2"; shift 2
|
|
local out code
|
|
out="$(curl -sS -X "$method" "${AUTH[@]}" -w $'\n%{http_code}' "$url" "$@")"
|
|
code="${out##*$'\n'}"
|
|
out="${out%$'\n'*}"
|
|
if [ "$code" -ge 400 ] && [[ " ${ALLOW_CODES:-} " != *" $code "* ]]; then
|
|
echo "ERROR: $method $url -> HTTP $code" >&2
|
|
echo "$out" >&2
|
|
return 1
|
|
fi
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
# First integer value of a "id": <n> pair — the release id is the first "id" in
|
|
# the release object. Avoids a jq dependency (not guaranteed in the CI image).
|
|
first_id() { grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+'; }
|
|
|
|
# --- create (or reuse) the release for the tag ------------------------------
|
|
echo "==> Creating release for $TAG"
|
|
BODY=$(cat <<JSON
|
|
{"tag_name":"$TAG","name":"ThoughtSync $TAG","draft":false,"prerelease":false,
|
|
"body":"ThoughtSync desktop $TAG. Linux AppImage (de-bundled graphics — renders on any GPU/Wayland setup) + .deb.\n\nInstall / update:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/main/desktop/packaging/install.sh | sh\n\`\`\`"}
|
|
JSON
|
|
)
|
|
# 409 = a release for this tag already exists (re-run) — fall through to lookup.
|
|
release="$(ALLOW_CODES=409 api POST "$API/releases" -H "Content-Type: application/json" -d "$BODY")"
|
|
RELEASE_ID="$(printf '%s' "$release" | first_id || true)"
|
|
|
|
if [ -z "${RELEASE_ID:-}" ]; then
|
|
echo " release exists; fetching it by tag"
|
|
release="$(api GET "$API/releases/tags/$TAG")"
|
|
RELEASE_ID="$(printf '%s' "$release" | first_id)"
|
|
fi
|
|
[ -n "${RELEASE_ID:-}" ] || { echo "ERROR: could not resolve release id" >&2; exit 1; }
|
|
echo " release id = $RELEASE_ID"
|
|
|
|
# Existing assets (name -> id), so a re-run replaces rather than duplicates.
|
|
existing="$(api GET "$API/releases/$RELEASE_ID/assets")"
|
|
|
|
# --- upload each asset ------------------------------------------------------
|
|
for asset in "${ASSETS[@]}"; do
|
|
name="$(basename "$asset")"
|
|
# If an asset with this name already exists, delete it first (Forgejo rejects
|
|
# a duplicate name). Match the "id" that precedes this asset's "name".
|
|
old_id="$(printf '%s' "$existing" \
|
|
| grep -oE "\"id\"[[:space:]]*:[[:space:]]*[0-9]+[^}]*\"name\"[[:space:]]*:[[:space:]]*\"$name\"" \
|
|
| first_id || true)"
|
|
if [ -n "${old_id:-}" ]; then
|
|
echo "==> Replacing existing asset $name (id $old_id)"
|
|
api DELETE "$API/releases/$RELEASE_ID/assets/$old_id" >/dev/null
|
|
fi
|
|
echo "==> Uploading $name"
|
|
api POST "$API/releases/$RELEASE_ID/assets?name=$name" -F "attachment=@$asset" >/dev/null
|
|
done
|
|
|
|
echo "==> Done. Release $TAG published with $(printf '%s\n' "${ASSETS[@]##*/}" | tr '\n' ' ')"
|