mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat(release): publish latest.json for Tauri auto-updater on production
The updater was wired client-side (prepareTauriConfig.js sets plugins.updater.endpoints to https://github.com/tinyhumansai/openhuman/releases/latest/download/latest.json and embeds UPDATER_PUBLIC_KEY) but the manifest itself was never generated after we moved off tauri-action — so installed apps would fetch 404 and believe they were up to date forever. Add scripts/release/publish-updater-manifest.sh which: - Lists the release's assets via gh CLI - Finds the updater bundles produced by createUpdaterArtifacts=true (.app.tar.gz for macOS, .AppImage.tar.gz for Linux, -setup.nsis.zip for Windows) for each of darwin-aarch64 / darwin-x86_64 / linux-x86_64 / windows-x86_64 - Reads the matching .sig files (minisign base64 payloads) - Composes latest.json per the Tauri v2 static-manifest schema with version, pub_date, notes, and a platforms map - Uploads it to the release via gh release upload --clobber Wired as a new production-only job `publish-updater-manifest` that runs after the build-desktop matrix. publish-release now waits on it, and the asset-validation step requires /^latest\.json$/ so releases can't ship without the manifest. cleanup-failed-release also tears down if the manifest step fails. Staging builds are deliberately skipped — they shouldn't poison the public updater endpoint.
This commit is contained in:
@@ -786,6 +786,37 @@ jobs:
|
||||
# org.opencontainers.image.source=https://github.com/tinyhumansai/openhuman
|
||||
# org.opencontainers.image.revision=${{ needs.prepare-release.outputs.sha }}
|
||||
|
||||
# =========================================================================
|
||||
# Phase 3c: Generate and upload latest.json for the Tauri auto-updater.
|
||||
# Runs after every platform has uploaded its updater artifact (.sig files
|
||||
# from createUpdaterArtifacts) so the manifest can reference all four
|
||||
# platform entries. Production-only — staging builds don't feed the public
|
||||
# updater endpoint.
|
||||
# =========================================================================
|
||||
publish-updater-manifest:
|
||||
name: Publish updater manifest (latest.json)
|
||||
needs: [prepare-build, create-release, build-desktop]
|
||||
if: >-
|
||||
needs.prepare-build.outputs.release_enabled == 'true'
|
||||
&& needs.build-desktop.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
environment: Production
|
||||
steps:
|
||||
- name: Checkout build ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare-build.outputs.build_ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Generate and upload latest.json
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ needs.prepare-build.outputs.tag }}
|
||||
VERSION: ${{ needs.prepare-build.outputs.version }}
|
||||
REPO: tinyhumansai/openhuman
|
||||
run: bash scripts/release/publish-updater-manifest.sh
|
||||
|
||||
# =========================================================================
|
||||
# Phase 4: Publish the draft release (waits for ALL build phases)
|
||||
# =========================================================================
|
||||
@@ -793,11 +824,12 @@ jobs:
|
||||
name: Publish draft release
|
||||
runs-on: ubuntu-latest
|
||||
environment: Production
|
||||
needs: [prepare-build, create-release, build-desktop, build-docker]
|
||||
needs: [prepare-build, create-release, build-desktop, build-docker, publish-updater-manifest]
|
||||
if: >-
|
||||
needs.prepare-build.outputs.release_enabled == 'true'
|
||||
&& needs.build-desktop.result == 'success'
|
||||
&& needs.build-docker.result == 'success'
|
||||
&& needs.publish-updater-manifest.result == 'success'
|
||||
steps:
|
||||
- name: Validate required installer assets exist
|
||||
uses: actions/github-script@v7
|
||||
@@ -819,6 +851,9 @@ jobs:
|
||||
/OpenHuman_.*_amd64\.AppImage$/,
|
||||
/OpenHuman_.*_amd64\.deb$/,
|
||||
/(OpenHuman_.*_x64-setup\.exe$|OpenHuman_.*_x64.*\.msi$)/,
|
||||
// Auto-updater manifest — without this, installed clients can't
|
||||
// discover new releases via plugins.updater.endpoints.
|
||||
/^latest\.json$/,
|
||||
];
|
||||
|
||||
const missing = requiredPatterns.filter((pattern) => !names.some((name) => pattern.test(name)));
|
||||
@@ -875,13 +910,14 @@ jobs:
|
||||
name: Remove release and tag if build failed
|
||||
runs-on: ubuntu-latest
|
||||
environment: Production
|
||||
needs: [prepare-build, create-release, build-desktop, build-docker]
|
||||
needs: [prepare-build, create-release, build-desktop, build-docker, publish-updater-manifest]
|
||||
if: >-
|
||||
always()
|
||||
&& needs.prepare-build.outputs.release_enabled == 'true'
|
||||
&& needs.create-release.result == 'success'
|
||||
&& (needs.build-desktop.result == 'failure' || needs.build-desktop.result == 'cancelled'
|
||||
|| needs.build-docker.result == 'failure' || needs.build-docker.result == 'cancelled')
|
||||
|| needs.build-docker.result == 'failure' || needs.build-docker.result == 'cancelled'
|
||||
|| needs.publish-updater-manifest.result == 'failure' || needs.publish-updater-manifest.result == 'cancelled')
|
||||
steps:
|
||||
- name: Delete GitHub release
|
||||
uses: actions/github-script@v7
|
||||
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate and upload latest.json for the Tauri auto-updater.
|
||||
#
|
||||
# Tauri's updater fetches a JSON manifest at a fixed endpoint (configured in
|
||||
# app/src-tauri/tauri.conf.json via `plugins.updater.endpoints`), reads the
|
||||
# `version` field, compares to the running app, and — if newer — downloads the
|
||||
# platform-specific `url` and verifies it against `signature`.
|
||||
#
|
||||
# We host the manifest on the GitHub release itself. The endpoint in
|
||||
# `prepareTauriConfig.js` resolves to
|
||||
# `https://github.com/<repo>/releases/latest/download/latest.json`, which
|
||||
# github permanently redirects to the asset on the newest non-draft release.
|
||||
#
|
||||
# Required env:
|
||||
# TAG — the release tag (e.g. `v0.52.21`)
|
||||
# VERSION — bare version (e.g. `0.52.21`)
|
||||
# REPO — `owner/name` on github
|
||||
# GITHUB_TOKEN — with release write scope (for `gh release`)
|
||||
#
|
||||
# Signature files (`.sig` — base64 minisign detached signatures produced by
|
||||
# the Tauri bundler when `createUpdaterArtifacts = true`) are downloaded from
|
||||
# the release; the matching bundle URLs use the stable
|
||||
# `/releases/download/<tag>/<file>` form so the manifest is self-describing.
|
||||
set -euo pipefail
|
||||
|
||||
: "${TAG:?TAG required (e.g. v0.52.21)}"
|
||||
: "${VERSION:?VERSION required (e.g. 0.52.21)}"
|
||||
: "${REPO:?REPO required (e.g. tinyhumansai/openhuman)}"
|
||||
: "${GITHUB_TOKEN:?GITHUB_TOKEN required}"
|
||||
|
||||
WORKDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORKDIR"' EXIT
|
||||
|
||||
echo "[updater] Fetching asset list for $REPO $TAG"
|
||||
gh release view "$TAG" --repo "$REPO" --json assets \
|
||||
--jq '.assets[].name' > "$WORKDIR/asset-names.txt"
|
||||
|
||||
asset_url() {
|
||||
printf 'https://github.com/%s/releases/download/%s/%s\n' "$REPO" "$TAG" "$1"
|
||||
}
|
||||
|
||||
# Find a single asset matching the given extended regex on the release; echo
|
||||
# its name on stdout, or empty if none / multiple. We prefer unambiguous
|
||||
# matches and surface the asset list on failure.
|
||||
find_asset() {
|
||||
local pattern="$1"
|
||||
local matches
|
||||
matches=$(grep -E "$pattern" "$WORKDIR/asset-names.txt" || true)
|
||||
local count
|
||||
count=$(printf '%s\n' "$matches" | grep -c . || true)
|
||||
if [ "$count" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$count" -gt "1" ]; then
|
||||
echo "[updater] WARN: pattern '$pattern' matched $count assets:" >&2
|
||||
printf ' %s\n' "$matches" >&2
|
||||
echo "[updater] WARN: using the first match" >&2
|
||||
fi
|
||||
printf '%s\n' "$matches" | head -1
|
||||
}
|
||||
|
||||
# Download a .sig for an asset and echo the signature payload. The .sig file
|
||||
# is a single base64-encoded minisign signature — no trimming needed beyond
|
||||
# the trailing newline.
|
||||
read_sig() {
|
||||
local name="$1"
|
||||
local sig_name="${name}.sig"
|
||||
if ! grep -Fxq "$sig_name" "$WORKDIR/asset-names.txt"; then
|
||||
echo "[updater] ERROR: signature asset '$sig_name' not on release — did createUpdaterArtifacts produce it?" >&2
|
||||
return 1
|
||||
fi
|
||||
gh release download "$TAG" --repo "$REPO" --pattern "$sig_name" \
|
||||
--dir "$WORKDIR" --clobber >&2
|
||||
# minisign sig format is two lines: an untrusted comment then the base64
|
||||
# payload. Tauri expects the whole file verbatim.
|
||||
local path="$WORKDIR/$sig_name"
|
||||
if [ ! -s "$path" ]; then
|
||||
echo "[updater] ERROR: downloaded sig is empty: $path" >&2
|
||||
return 1
|
||||
fi
|
||||
cat "$path"
|
||||
}
|
||||
|
||||
# Platform mapping. Tauri's updater consults these exact keys; see
|
||||
# https://v2.tauri.app/plugin/updater/#static-json-file
|
||||
#
|
||||
# darwin-aarch64 — macOS Apple Silicon
|
||||
# darwin-x86_64 — macOS Intel
|
||||
# linux-x86_64 — Linux glibc x64 (AppImage)
|
||||
# windows-x86_64 — Windows x64 (NSIS setup)
|
||||
#
|
||||
# Naming conventions emitted by tauri-bundler with createUpdaterArtifacts:
|
||||
# darwin : <AppName>_<version>_<arch>.app.tar.gz
|
||||
# linux : <AppName>_<version>_amd64.AppImage.tar.gz
|
||||
# windows : <AppName>_<version>_x64-setup.nsis.zip
|
||||
MAC_AARCH64=$(find_asset "^OpenHuman(_| ).*aarch64\.app\.tar\.gz$")
|
||||
MAC_X86_64=$(find_asset "^OpenHuman(_| ).*(x64|x86_64)\.app\.tar\.gz$")
|
||||
LINUX_X86_64=$(find_asset "^OpenHuman(_| ).*(amd64|x86_64)\.AppImage\.tar\.gz$")
|
||||
WIN_X86_64=$(find_asset "^OpenHuman(_| ).*x64-setup\.nsis\.zip$")
|
||||
|
||||
echo "[updater] Resolved updater bundles:"
|
||||
echo " darwin-aarch64 = ${MAC_AARCH64:-<missing>}"
|
||||
echo " darwin-x86_64 = ${MAC_X86_64:-<missing>}"
|
||||
echo " linux-x86_64 = ${LINUX_X86_64:-<missing>}"
|
||||
echo " windows-x86_64 = ${WIN_X86_64:-<missing>}"
|
||||
|
||||
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
||||
|
||||
# Assemble manifest incrementally so a missing platform degrades gracefully
|
||||
# rather than producing invalid JSON. jq reads env vars we set here.
|
||||
MANIFEST="$WORKDIR/latest.json"
|
||||
jq -n \
|
||||
--arg version "$VERSION" \
|
||||
--arg pub_date "$PUB_DATE" \
|
||||
--arg notes "See https://github.com/$REPO/releases/tag/$TAG" \
|
||||
'{version: $version, notes: $notes, pub_date: $pub_date, platforms: {}}' \
|
||||
> "$MANIFEST"
|
||||
|
||||
add_platform() {
|
||||
local key="$1" name="$2"
|
||||
[ -z "$name" ] && return 0
|
||||
local sig url
|
||||
sig=$(read_sig "$name")
|
||||
url=$(asset_url "$name")
|
||||
jq --arg key "$key" --arg sig "$sig" --arg url "$url" \
|
||||
'.platforms[$key] = {signature: $sig, url: $url}' \
|
||||
"$MANIFEST" > "$MANIFEST.tmp"
|
||||
mv "$MANIFEST.tmp" "$MANIFEST"
|
||||
echo "[updater] + $key → $name"
|
||||
}
|
||||
|
||||
add_platform "darwin-aarch64" "$MAC_AARCH64"
|
||||
add_platform "darwin-x86_64" "$MAC_X86_64"
|
||||
add_platform "linux-x86_64" "$LINUX_X86_64"
|
||||
add_platform "windows-x86_64" "$WIN_X86_64"
|
||||
|
||||
# Require at least one platform so we don't publish an empty manifest that
|
||||
# would mislead installed clients into thinking no update is ever available.
|
||||
platforms=$(jq -r '.platforms | keys | length' "$MANIFEST")
|
||||
if [ "$platforms" = "0" ]; then
|
||||
echo "[updater] ERROR: no platforms resolved — refusing to publish empty manifest" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[updater] Final manifest:"
|
||||
cat "$MANIFEST"
|
||||
|
||||
gh release upload "$TAG" "$MANIFEST" --repo "$REPO" --clobber
|
||||
echo "[updater] Uploaded latest.json to $TAG"
|
||||
Reference in New Issue
Block a user