Skip to content

feat(gui): in-app auto-updater via tauri-plugin-updater#19

Merged
GregTheGreek merged 4 commits into
mainfrom
feat/in-app-updater
May 14, 2026
Merged

feat(gui): in-app auto-updater via tauri-plugin-updater#19
GregTheGreek merged 4 commits into
mainfrom
feat/in-app-updater

Conversation

@GregTheGreek
Copy link
Copy Markdown
Owner

Summary

Wires up the Tauri 2 in-app updater so an installed rompatch app
self-checks against GitHub Releases on launch and offers to install +
restart on the next version. Out of scope: CLI distribution, crates.io
publishing, version-bumping automation. Tags are still cut manually.

What's in here

Rust side

  • crates/rompatch-gui/Cargo.toml - add tauri-plugin-updater = "=2.10.1" + tauri-plugin-process = "=2.3.1"
  • crates/rompatch-gui/src/lib.rs - register both plugins on the builder
  • crates/rompatch-gui/tauri.conf.json
    • bundle.createUpdaterArtifacts: true so each build emits .app.tar.gz + .sig
    • plugins.updater block with the pubkey placeholder + endpoint pointing at releases/latest/download/latest.json (GitHub redirects this URL to whichever release is marked latest)
    • removed the hardcoded "version": "0.1.0" so Tauri reads from the workspace Cargo.toml (single source of truth)
  • crates/rompatch-gui/capabilities/default.json - grant updater:default + process:allow-restart

Frontend

  • ui/package.json - @tauri-apps/plugin-updater@2.10.1 + @tauri-apps/plugin-process@2.3.1
  • ui/src/lib/updater.ts - small useUpdater hook that wraps check() and exposes download progress
  • ui/src/components/UpdateBanner.tsx - banner at the top of the main panel with an Install + restart button
  • ui/src/App.tsx - mount the banner

CI (.github/workflows/ci.yml gui job)

  • Pass TAURI_SIGNING_PRIVATE_KEY + TAURI_SIGNING_PRIVATE_KEY_PASSWORD to both tauri build steps
  • New step builds latest.json from the signature file
  • Attach .app.tar.gz, .app.tar.gz.sig, and latest.json to the GitHub Release
  • Extend the sigstore attestation subject-path to cover the tarball

Required setup before the first release after merge

Important

The current pubkey in tauri.conf.json is a placeholder. CI will fail on the first tag push until the steps below are done. Generate the keypair locally, commit the real pubkey, and add the two secrets - then tag.

  1. Generate the Tauri ed25519 keypair locally:
    cargo install tauri-cli --version 2.11.1 --locked   # if not already installed
    cargo tauri signer generate -w ~/.tauri/rompatch.key
    • Stash the password in 1Password.
  2. Add two repo secrets at https://github.com/GregTheGreek/rompatch-rs/settings/secrets/actions:
    • TAURI_SIGNING_PRIVATE_KEY = full contents of ~/.tauri/rompatch.key
    • TAURI_SIGNING_PRIVATE_KEY_PASSWORD = the password chosen above
  3. Replace REPLACE_WITH_TAURI_UPDATER_PUBLIC_KEY in crates/rompatch-gui/tauri.conf.json with the public key printed by step 1.
  4. Commit + push, then merge this PR.
  5. Push a tag like v0.2.0 - the existing gui job will build the signed updater artifacts and attach everything to the release.

Risks / gotchas

  • The Tauri signing key is separate from the Apple Developer ID cert. Two signing systems run in parallel: Apple signs the .app for Gatekeeper, Tauri signs the .app.tar.gz for the in-app updater. Both are required for a smooth post-install launch.
  • Apple signing must stay on for releases users will update from. If a tag is cut from the unsigned fallback path, the post-update .app will be Gatekeeper-quarantined and silently fail to launch.
  • The public key is embedded in the binary. Rotating it requires shipping a release through the old key first (or asking users to re-download). Pick once, back up the key file.

Test plan

  • cargo check -p rompatch-gui --release - clean
  • cargo clippy -p rompatch-gui --all-targets -- -D warnings - clean
  • cargo fmt --all -- --check - clean
  • pnpm typecheck - clean
  • cargo tauri info - resolves both new plugins (Rust + JS sides)
  • After secrets + real pubkey land: tag v0.2.0, confirm .app.tar.gz, .app.tar.gz.sig, and latest.json attach to the release
  • Install the prior version .dmg, launch the v0.2.0-aware build, confirm the banner appears and Install & restart downloads + relaunches

Adds the Tauri 2 updater + process plugins so an installed rompatch app
self-checks against GitHub Releases on launch, surfaces an "Install &
restart" banner, downloads the signed .app.tar.gz, verifies it against
an embedded ed25519 pubkey, and relaunches.

CI now builds and attaches the updater artifacts (.app.tar.gz, .sig)
and a latest.json manifest alongside the existing .dmg on every tag.
Build provenance attestation is extended to cover the tarball.

Out of scope: CLI distribution, crates.io publishing, automated
version bumping. Tags are still cut manually.

Setup before the first release: see the PR description.

Co-Authored-By: Claude
@GregTheGreek GregTheGreek marked this pull request as ready for review May 14, 2026 10:18
Two CI fixes for the updater PR:

* webpki-root-certs, pulled in transitively by the new
  tauri-plugin-updater -> reqwest -> rustls-platform-verifier path, ships
  under CDLA-Permissive-2.0. Add it to the deny.toml allowed list.
* The gui job's Swatinem/rust-cache step uses a static `key: gui` and
  was restoring a stale ~/.cargo/bin from a prior run, clobbering the
  proxies that dtolnay/rust-toolchain freshly installed. cargo then
  invoked rustup-init and failed with "unexpected argument 'check'".
  Set `cache-bin: false` so the cache only handles registry + target.

Co-Authored-By: Claude
The gui job got `cache-bin: false` in the previous commit. Applying it
to test, fmt+clippy, coverage, and fuzz too: any rust-cache step paired
with dtolnay/rust-toolchain can restore a stale ~/.cargo/bin on a cache
hit and clobber the freshly-installed proxies. macos-latest just hit
this in the test matrix.

Co-Authored-By: Claude
Replaces the placeholder pubkey in tauri.conf.json with the real
minisign public key. The matching private key + password are stored
as repo secrets (TAURI_SIGNING_PRIVATE_KEY and
TAURI_SIGNING_PRIVATE_KEY_PASSWORD) and used by the gui job to sign
the .app.tar.gz updater artifact at release time.

Co-Authored-By: Claude
@GregTheGreek GregTheGreek merged commit cce5040 into main May 14, 2026
7 checks passed
@GregTheGreek GregTheGreek deleted the feat/in-app-updater branch May 14, 2026 11:38
@GregTheGreek GregTheGreek mentioned this pull request May 14, 2026
3 tasks
GregTheGreek added a commit that referenced this pull request May 14, 2026
## Summary

Bumps the workspace version from `0.1.0` to `0.2.0` so the first release
that ships the in-app updater (PR #19) is detectable as "new" by
installed `v0.1.0` copies.

Touches:
- `Cargo.toml` `workspace.package.version` (picked up by all three
crates via `version.workspace = true`)
- `crates/rompatch/Cargo.toml` and `crates/rompatch-gui/Cargo.toml`
explicit `rompatch-core` deps (both pinned)
- `crates/rompatch-gui/ui/package.json` (cosmetic; Tauri reads the .app
version from `Cargo.toml`)
- `Cargo.lock` regenerated

No code changes.

## After merge

1. `git -c core.sshCommand="ssh -i ~/.ssh/gwm-claude" push origin
v0.2.0` (after tagging the merge commit)
2. The `gui` job fires on `refs/tags/v*` and attaches `.dmg`,
`.app.tar.gz`, `.app.tar.gz.sig`, and `latest.json` to the release.
3. Installed `v0.1.0` apps see the update banner on next launch.

## Test plan

- [x] `cargo check --workspace --exclude rompatch-gui` clean
- [x] `cargo check -p rompatch-gui --release` clean
- [ ] After merge: tag `v0.2.0`, confirm release assets + `latest.json`
reachable at `releases/latest/download/latest.json`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant