Repository navigation
Detect new releases and update in-app on macOS, Linux and Windows - #31
Merged
Merged
Conversation
… stopping every Service first
… the Linux and Windows app
…o stop Services first
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…, and say a downloaded update installs on quit on macOS
… keys with --upload-only
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #29.
localhost now looks for new versions on GitHub Releases, offers them in a notice, and installs them in place. Before this, users had to download each release by hand.
What ships per platform
.appfrom the DMGappcast.xml, signed (SURequireSignedFeed)tauri-plugin-updater2.14latest.jsontauri-plugin-updaterlatest.jsontauri-plugin-updater, check onlylatest.jsonThe UX is the same on every platform:
SUScheduledCheckInterval21600 plus a check at launch).settings.json(automaticUpdateChecks, default on) and in Sparkle's own default on macOS.Both feeds come from
releases/latest/download/…, so drafts and pre-releases are never offered. An update reaches users when Marcel clicks Publish. In CI, the feed URLs are taken fromgithub.repository, so after the repository move (#40) new builds point to the new repository.Runs and the shared core: a decision for you to confirm
What I implemented: an update stops every Service first, the same way Quit does, and never does it silently. Clicking Restart to Update while anything is active opens a confirmation, "Stop N services and update?", which lists the active Services by Project. That includes Services an agent started through
localhost mcp. The buttons are Stop and Update and Cancel. After confirming, the app:stop_every_runand waits for every process tree to end;Retire, so the core exits as soon as its last client disconnects instead of after the 10 s idle timeout;Why I didn't keep the core and its Runs alive across the update:
localhost-app.exe --daemon, the same executable the installer must replace. The NSIS installer closes processes with that name, which is the risk noted in Windows: shared core over a named pipe, so the app and localhost mcp share Runs #28. Its Runs sit in a kill-on-close Job Object, so they would die without a graceful stop. Stopping them ourselves first is the only way to make that stop graceful and visible.CoreUnavailable). Without a bump it would keep running old core code for as long as the app stays open.The cost is that the user starts their Services again after the update. If you'd rather keep Runs alive across updates on macOS and Linux, that needs a core handover (version-aware reconnect). I'd treat that as a separate issue.
Edge case: if an agent is still connected when the app restarts, the retired core keeps serving that agent until it disconnects. It has no Runs by then, because everything was stopped. On Windows the installer closes it anyway. The agent's next call then starts a fresh core.
Setting up the signing keys (one-time, for Marcel)
These keys are separate from the code-signing certificates. No key is generated or committed in this PR. Each installed app trusts only the public keys it was built with, so create the keys once and keep them.
ghlogged in:gh:TAURI_SIGNING_PRIVATE_KEY,TAURI_SIGNING_PRIVATE_KEY_PASSWORD,SPARKLE_PRIVATE_KEYTAURI_UPDATER_PUBLIC_KEY,SPARKLE_PUBLIC_KEY~/localhost-updater-keysin a password manager or on an encrypted disk. GitHub can't give the secrets back. If they are lost, every user has to download the next version by hand.The keys go to
marcellosso/localhostexplicitly (LOCALHOST_REPO=owner/nameoverrides it), so a clone with several remotes works. If the keys already exist,scripts/create-updater-keys.sh ~/localhost-updater-keys --upload-onlystores them without generating new ones. It checks that all five files are there and that the Sparkle pair matches.To do it by hand instead, drop
--uploadand run the fivegh secret set/gh variable setlines in RELEASING.md → Updates.Missing keys fail the release, and that's deliberate. The first job checks all five and stops with "Missing the updater keys: … see RELEASING.md (Updates)" before anything is built. I chose failing over skipping with a warning because a release without signed feeds would install fine, but its users could never update in-app again. That's worse than a release that waits five minutes for the setup. The workflow also refuses mismatched keys:
SPARKLE_PUBLIC_KEY(scripts/sparkle-public-key.sh)..sigmust carry the public key's key id (scripts/updater-manifest.sh).That way a wrong secret fails the release instead of every user's update.
Other changes
Request::Retire(protocol 4 → 5),Targets::retire_shared_core, and its FFI/Swift wrapper. Tested ina_retired_core_exits_as_soon_as_its_last_client_leaves.createUpdaterArtifacts, the public key and the feed URL via--config. The workflow signs the.deb/.rpmitself if the bundler didn't. The.sigfiles are folded intolatest.jsonand kept out of the release assets. On macOS, Sparkle's tools are pinned by version and SHA-256; the workflow signs the DMG after stapling, then writes and signsappcast.xml.scripts/build-release.sh: addsSUPublicEDKeyandFEED_URLto Info.plist, and signs Sparkle's helpers (XPC services, Autoupdate, Updater.app) with hardened runtime before signing the app.linux-x86_64-appimage,-deb,-rpm,windows-x86_64-nsis) and never by platform alone, so a .deb install can never pick up the AppImage.requireSignedVersionis on.tauri-plugin-updater(without itszipfeature) and the Sparkle Swift package. AGENTS.md now mentions Sparkle as the macOS app's one package.How it was checked
Locally on macOS (Apple silicon):
cd core && cargo test --test shared && cargo clippy --all-targets -D warnings && cargo fmt --check. Includes the new retire test.apps/desktop:pnpm typecheck && pnpm test(85 tests, including new ones for the notice and Check for Updates), and insrc-tauri,cargo fmt --check && cargo clippy --all-targets -D warnings && cargo test. New tests cover: the shape oflatest.jsonparsed by the plugin's ownRemoteRelease, with no bundle-less entry; which packages install themselves; the release-notes fallback; the list of active Services; and the new setting's default and persistence.bindings.tsis regenerated.scripts/test-updater-scripts.shwith throwaway keys, also added to CI. It checks that latest.json is right, that a wrong key or a missing.sigfails, that the Sparkle public key is derived correctly, and that the appcast is right. Alsoshellcheck scripts/*.sh.scripts/create-updater-keys.shrun into/tmpwithout--upload. Its Sparkle key signs and verifies with Sparkle'ssign_update.scripts/build-release.shDMGs for 0.0.1, 0.0.2 and 0.0.3 (ad hoc, hardened runtime, throwaway Sparkle key kept in/tmp, feed served on 127.0.0.1). Each appcast was written withscripts/appcast.shand signed withsign_update. The app checked at launch, and the sidebar showed "localhost 0.0.2 is available". Update Now downloaded and verified it, and the notice changed to "is ready". With a Service started from the app, Restart to Update showed "Stop 1 service and update? … E2E: Sleeper". After Stop and Update, within about 2 s the Service had stopped and the old core had exited at once (no client for 0ns, the retire). Sparkle installed 0.0.3 and relaunched it with a new core. Check for Updates… in the app menu showed "localhost is up to date" when there was nothing newer.com.apple.security.cs.disable-library-validation; notarization accepts it. CI's macOS job builds Debug unsigned, so it can't catch this. Please run the release DMG once before publishing (RELEASING.md step 4 already says so).Not verified locally:
bundle_type()detection in a real .deb, and the bundler emitting.sigfiles. The PR's CI covers the Linux build, clippy and tests. The first tag after merging is the real test. RELEASING.md step 5 now includes installing an update with a Service running.Merge danger
CoreUnavailableuntil that core exits. Every earlier protocol bump has had the same effect.releases/latest/download/…. Builds made after the move use the new URL.