Skip to content

Add a built-in update check with installer cleanup - #96

Merged
fuji-mak merged 7 commits into
fuji-mak:mainfrom
manovotny:feature/update-check
Sep 6, 2026
Merged

Add a built-in update check with installer cleanup#96
fuji-mak merged 7 commits into
fuji-mak:mainfrom
manovotny:feature/update-check

Conversation

@manovotny

@manovotny manovotny commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

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

  • "Check for Updates…" in the menu bar menu. When a newer version is known, the item reads "Update available — X.Y.Z", and that state survives a relaunch.
  • Automatic daily check, on by default, with an opt-out toggle in Advanced Settings under System Behavior. An hourly re-arming timer keeps the daily cadence honest in long-running sessions, failed checks are floored to one attempt per hour, and manual checks work regardless of the toggle.
  • Updating downloads Capsomnia-<version>.pkg to Capsomnia's own caches folder (~/Library/Caches/Capsomnia/), verifies with pkgutil --check-signature that 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.
  • The downloaded installer is removed automatically on the first launch after the update is installed; failed removals retry on the next launch, so nothing lingers on disk and the user is never asked about it.
  • Decision logic (version comparison, release parsing, check throttling, cleanup state) lives in UpdateCheck with unit tests; side effects live in UpdateController. 112 tests passing overall.
  • Localized for all four languages, and the "no network requests" statements in SECURITY.md and the four READMEs now describe the update check precisely: it reads GitHub's public release information, sends no telemetry or identifiers, and turns off in one toggle.

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 a Capsomnia-<version>.pkg asset 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

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>
@manovotny
manovotny marked this pull request as draft August 24, 2026 16:08
manovotny and others added 3 commits August 24, 2026 11:17
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>
@manovotny
manovotny marked this pull request as ready for review August 24, 2026 17:01

@fuji-mak fuji-mak left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thoughtful implementation. The overall direction looks good. Before merging, could you please address these two points?

  1. 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.
  2. 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>
@manovotny

Copy link
Copy Markdown
Contributor Author

Both addressed in 71fbe6d.

  1. Downloaded installers are now verified with pkgutil --check-signature before opening: the output must show a signed status and a Developer ID Installer certificate line for team ZJZ8627852, or the file is deleted and the update-failure alert shows. The check runs before quarantine and open, so an untrusted package never reaches Installer. I validated the parsing against the live v3.4.0 release package.

  2. downloadInstaller now has an in-flight guard. Repeated update actions are ignored while a download is running, and the flag clears on every completion path, so a failed download doesn't wedge future updates.

@fuji-mak

fuji-mak commented Sep 6, 2026

Copy link
Copy Markdown
Owner

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:

  • Group the automatic-check toggle and available update in an Updates card in the right column of Advanced Settings.
  • Show the available version, current version, and download action when an update is known, including when it is detected while Settings is open.
  • Add a compact Info link beside the version that opens that release's GitHub notes.
  • Keep automatic checks on by default and retain the opt-out. Downloading and installing remain user-initiated. Shorten the explanatory copy in all four languages.

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 fuji-mak left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both requested fixes are resolved. Reviewed the final maintainer UI follow-up and validated the local test suite and release build.

@fuji-mak
fuji-mak merged commit 5c3ba32 into fuji-mak:main Sep 6, 2026
4 checks passed
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.

2 participants