The installed desktop app polls `desktop-latest/latest.json`. Previously every push to `main` (autotag -> v*.devN -> desktop.yml dispatch) rebuilt and republished `desktop-latest` as a DEV prerelease, so stable users were auto-updated onto unvetted dev builds, and any manual stable mirror was clobbered on the next merge. Split the streams so the app's channel only ever serves vetted stable: - Dev/rolling builds (v* autotag + manual workflow_dispatch) now publish to a new `desktop-edge` pre-release. The shipped app does not poll edge, so dev builds never auto-install onto stable users. - Stable `desktop-v*` builds publish the user-facing release as before, then a new `refresh-stable-channel` job copies that release's signed `latest.json` into `desktop-latest` (mirror; URLs already point at the desktop-v* assets). Cut a `desktop-v*` tag to ship an update. - `clean-release` now targets `desktop-edge`; `desktop-latest` is never wiped by CI. No app/tauri.conf.json change — the updater endpoint stays `desktop-latest`. Doc updated to describe the now-implemented stable/edge split. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.4 KiB
Desktop auto-update
The OpenJarvis desktop app ships with Tauri's updater plugin, which checks for new versions on launch and every 30 minutes. When a newer signed build is available, the app prompts the user to download and install it.
How it works
on launch / every 30 min
│
▼
GET https://github.com/open-jarvis/OpenJarvis/releases/download/desktop-latest/latest.json
│
▼
Parse manifest: { "version": "X.Y.Z", "platforms": { ... } }
│
▼
If manifest.version > installed_version:
download signed .dmg / .deb / .msi from manifest.platforms[target].url
verify against the minisign pubkey baked into the app
prompt user to install
The frontend code lives in
frontend/src/components/Desktop/UpdateChecker.tsx;
the Tauri wiring is in
frontend/src-tauri/tauri.conf.json
under plugins.updater.
How releases reach the update endpoint
The Desktop Build & Release GitHub Action
(.github/workflows/desktop.yml)
builds signed binaries plus a latest.json manifest with the
tauri-action step (includeUpdaterJson: true generates the manifest
automatically). Where it publishes depends on the trigger.
Three release streams exist:
desktop-latest(stable auto-update channel): this is the channel the installed app polls. It is not built directly — instead, when a stabledesktop-vX.Y.Zrelease is published, therefresh-stable-channeljob copies that release'slatest.jsonintodesktop-latest. So the app is only ever offered vetted stable builds, andlatest.jsonhere points at the currentdesktop-v*assets.desktop-vX.Y.Z(tagged stable): created when someone pushes adesktop-v*git tag. The user-facing stable release with full installers; also the source of truth the stable channel mirrors.desktop-edge(rolling pre-release): rebuilt on every push tomain(via theautotag→desktop.ymldispatch) and on manualworkflow_dispatch. Carries the most recent CI build for testers. The shipped app does not poll this stream, so dev builds never auto-install onto stable users.
This split means security and telemetry-policy fixes reach users on
the next stable desktop-v* tag — cut one to ship an update.
Edge builds are available for anyone who wants to test main ahead of
a stable tag, without risking the stable population.
Signing
Binaries are signed by tauri-action using the minisign key pair
referenced via these GitHub Actions secrets:
| Secret | Purpose |
|---|---|
TAURI_SIGNING_PRIVATE_KEY |
Private key (PEM-formatted minisign) |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Passphrase for the private key |
The matching public key is baked into the app at
tauri.conf.json:plugins.updater.pubkey. If you ever need to rotate
the key, replace the public key in the JSON file and update both
secrets atomically — mismatched keys cause every update download to
fail signature verification with no recovery path other than a manual
reinstall.
Disabling the updater locally
For frontend development, set VITE_OPENJARVIS_NO_UPDATER=1 in your
shell before running npm run tauri dev. Vite injects any
VITE_-prefixed env var into import.meta.env, and the
UpdateChecker.tsx component honors it to skip the 30-minute poll.
export VITE_OPENJARVIS_NO_UPDATER=1
npm run tauri dev
This is purely a dev escape hatch — it has no effect on production
builds (where import.meta.env.VITE_OPENJARVIS_NO_UPDATER will be
undefined unless you explicitly set it at build time).
Verifying a release manually
# Download the latest manifest and confirm it parses cleanly
curl -fsSL https://github.com/open-jarvis/OpenJarvis/releases/download/desktop-latest/latest.json | jq .
# Fields:
# version — semver string, must match the tag (without leading "v")
# notes — release notes string
# pub_date — RFC3339 timestamp
# platforms — map keyed by "<target>-<arch>" e.g. "darwin-aarch64"
# each entry has { signature: "...", url: "..." }
A 404 on the manifest URL means the most recent desktop CI run
didn't complete or didn't have signing secrets — check the
Desktop Build & Release workflow logs.