Add a built-in update check with installer cleanup - #96
Conversation
Check the GitHub releases API for a newer version from "Check for Updates…" in the menu bar menu, and once a day automatically (opt-out in Advanced Settings). Updating downloads the signed installer package to the Downloads folder and opens it; on the first launch after the update, Capsomnia offers once to move the downloaded installer to the Trash. Only the latest version number is requested — no telemetry, no dependencies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Writing to ~/Downloads triggers the macOS Downloads-folder privacy prompt on the first update. Downloading to Capsomnia's own caches folder avoids the prompt entirely, and since the file never lives in a user-facing location, the post-update Trash prompt becomes an automatic silent removal on the first launch after the update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Quarantine downloaded installer packages before opening them so Installer runs them through Gatekeeper, and fail closed when the attribute cannot be set. - Keep the daily check honest: an hourly re-arming timer covers long-running sessions where the menu never opens, and an in-session attempt floor stops failed checks from retrying on every menu open. - Promote a manual "Check for Updates…" during an in-flight automatic check to user-initiated so its result still alerts. - Persist the last known release version so the "Update available" menu state survives a relaunch within the daily check window. - Surface installer-open failures instead of ignoring the result, and retry failed cache cleanups on the next launch instead of dropping the record. - Describe the network behavior precisely: the check reads GitHub's public release information and sends no telemetry or identifiers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Download, quarantine, and Installer-launch failures now show a dedicated "Update failed" message instead of the check-failed one, which wrongly claimed GitHub was unreachable after the check had already succeeded. The quarantine DataURL property now passes a URL, matching the documented CFURLRef type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fuji-mak
left a comment
There was a problem hiding this comment.
Thanks for the thoughtful implementation. The overall direction looks good. Before merging, could you please address these two points?
- Verify that the downloaded pkg is signed by the Capsomnia Developer ID Team ID ZJZ8627852 before opening it. Quarantine and Gatekeeper validation alone do not pin the installer to the expected signer.
- Prevent concurrent installer downloads. The existing isChecking guard covers release checks, but downloadInstaller can still be started more than once and each task writes to the same cache path. Please add an in-flight guard and ignore or disable repeated download actions until the current one finishes.
Verify with pkgutil that a downloaded installer is signed by team ZJZ8627852 before opening it, and delete it otherwise — Gatekeeper alone accepts any validly signed and notarized package, not necessarily ours. Also guard against concurrent installer downloads: repeated update actions are ignored while a download is in flight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both addressed in 71fbe6d.
|
|
Thanks, @manovotny — I re-reviewed 71fbe6d, and both requested fixes are addressed: the installer is pinned to Capsomnia's Developer ID team, and concurrent downloads are guarded against. After trying the UI locally, I added a small maintainer follow-up:
Local validation: 112 Swift tests, 2 skipped, no failures; Release build with warnings treated as errors; visual checks of the update card; and an actual click through to the version-specific GitHub release page. The published 3.4.0 pkg also passed the expected signer check. I have included the version and changelog updates for 3.5.0 and will merge once CI is green. Thanks for contributing the update checker and following through on the review! |
fuji-mak
left a comment
There was a problem hiding this comment.
Both requested fixes are resolved. Reviewed the final maintainer UI follow-up and validated the local test suite and release build.
Summary
New releases currently reach users only if they revisit the GitHub releases page — the app has no way to tell them an update exists. This adds an update check with zero dependencies and zero infrastructure: it reads the latest version from the GitHub releases API and compares it to the running one. Sparkle was considered and skipped on purpose — since Capsomnia updates through a signed, notarized .pkg with a privileged helper, the installer prompt is unavoidable either way, and Sparkle would add an embedded framework, an EdDSA key to manage, and appcast generation to the release process for little gain here.
Changes
Capsomnia-<version>.pkgto Capsomnia's own caches folder (~/Library/Caches/Capsomnia/), verifies withpkgutil --check-signaturethat it is signed by the Capsomnia Developer ID team (ZJZ8627852), applies the quarantine attribute so Installer runs it through Gatekeeper, and opens it — failing closed at every step. The app-owned location avoids the macOS Downloads-folder privacy prompt, no new signing material is introduced, and concurrent downloads are guarded against.UpdateCheckwith unit tests; side effects live inUpdateController. 112 tests passing overall.Notes for the maintainer
Nothing is required beyond review — no keys, no hosting, no release-process changes. The check does depend on two conventions RELEASING.md already documents: tags named
v<version>and aCapsomnia-<version>.pkgasset on each release. I verified both against the live API for v3.4.0.One behavior worth a conscious yes: the daily check is on by default (opt-out), which felt consistent with "Open at login" defaulting on. Happy to flip it to opt-in if you'd rather.
🤖 Generated with Claude Code