Skip to content

feat: in-app auto-updates via Sparkle#17

Merged
GregTheGreek merged 1 commit into
mainfrom
feat/auto-updates
Jul 10, 2026
Merged

feat: in-app auto-updates via Sparkle#17
GregTheGreek merged 1 commit into
mainfrom
feat/auto-updates

Conversation

@GregTheGreek

Copy link
Copy Markdown
Owner

Summary

Adds in-UI auto-updates: a "Check for Updates…" item and an "Automatically check for updates" toggle in the menu, backed by Sparkle. Updates are EdDSA-signed and served from GitHub Releases.

  • Vendored on demand: make sparkle fetches a pinned, checksum-verified Sparkle.framework into gitignored third_party/. No binary blob in git.
  • Signing: the framework is embedded and its nested XPC services / Updater.app / Autoupdate are re-signed inside-out with the Developer ID (hardened runtime + timestamp) before the app is sealed. codesign --verify --deep --strict passes.
  • Test build stays clean: import Sparkle is behind canImport, so the SwiftPM/swift test build compiles a no-op stub and needs no framework.
  • Feed: appcast.xml lives on a pinned appcast release (stable URL); enclosures point at each version's own vX.Y.Z release. make appcast refreshes + signs it; make sparkle-keys does one-time key setup. Cask gets auto_updates true.

Notes for reviewer

  • Branched off main (pre-dmg), so it wires the appcast/enclosure to the current .zip artifact. It will need a trivial rebase after feat: distribute a notarized .dmg instead of a .zip #16 (dmg) merges - $(ZIP)$(DMG) in the enclosure/cask URL. Sparkle updates from either equally.
  • SUPublicEDKey in Info.plist was generated during setup; the private key is in the release machine's keychain. See "One-time setup" below.

Test plan

  • make bundle builds, embeds, and signs Sparkle; codesign --verify --deep --strict clean
  • app launches with no dyld/Sparkle init errors; rpath resolves @rpath/Sparkle.framework
  • swift build + swift test (9 tests) pass via the no-op stub
  • generate_appcast verified to preserve prior versioned entries when appending a new one
  • one-time: gh release create appcast …, then a real make release && make appcast cycle end-to-end

Add a "Check for Updates..." item and an "Automatically check for updates"
toggle to the menu, backed by Sparkle. Updates are EdDSA-signed and pulled
from GitHub Releases.

Sparkle is vendored on demand: `make sparkle` fetches a pinned, checksum-
verified Sparkle.framework into gitignored third_party/. The framework is
embedded in the bundle and its nested XPC services / Updater.app / Autoupdate
are re-signed inside-out with the Developer ID (hardened runtime + timestamp)
before the app is sealed. `import Sparkle` is guarded by canImport, so the
SwiftPM/test build compiles a no-op stub and stays free of the binary framework.

The appcast (appcast.xml) lives on a pinned `appcast` GitHub release (stable
URL); enclosures point at each version's own release. `make appcast` refreshes
and signs it; `make sparkle-keys` handles one-time key setup. The cask gets
`auto_updates true` so brew defers to the in-app updater.

Co-Authored-By: Claude
@GregTheGreek
GregTheGreek marked this pull request as ready for review July 10, 2026 16:39
@GregTheGreek
GregTheGreek merged commit b457170 into main Jul 10, 2026
1 check passed
@GregTheGreek
GregTheGreek deleted the feat/auto-updates branch July 10, 2026 16:40
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