Auto-update options for D-Patch (macOS, Swift, distributed as bare DMGs)
Short answer: Sparkle 2 — the de-facto standard, and basically the only serious native option for a non-sandboxed, non-App-Store app.
The options
| Option | What it is | Used by |
|---|---|---|
| Sparkle 2 | Framework: checks an appcast.xml feed, downloads, EdDSA-verifies, installs & relaunches. Delta updates, "install to /Applications", Check-for-Updates menu UX |
The default for indie/OSS Mac apps for ~20 years |
| Hand-rolled GitHub Releases check | GET /repos/<owner>/<repo>/releases/latest, compare against CFBundleVersion, alert + link to DMG |
Small OSS apps that don't want the machinery |
| Homebrew cask | Zero code — users get brew upgrade |
Complements either, for the CLI crowd |
| Squirrel.Mac | Electron's updater guts | Electron apps; effectively dead as a native Swift choice |
| App Store / Software Update | Apple's own path | Not applicable — a patcher app will never pass review/sandboxing |
Sparkle 2 specifics for D-Patch
- SwiftPM dependency,
Sparklemodule, ~10 lines of app code (SPUStandardUpdaterController+ a Check-for-Updates menu item). - Keys: one-time
generate_keys→ EdDSA keypair; public key in Info.plist, private key in CI secret. - Appcast:
appcast.xmlwith per-release entries; at release timesign_update <dmg>produces the signature + length for the entry. Can be committed to a branch and served via raw.githubusercontent/Pages — no server needed. - build.sh gets ~5 lines: Sparkle ships two XPC services (
Autoupdate,Updater) that must be copied intoContents/XPCServicesbefore signing. Framework + XPC, noBundle.moduleaccessor — doesn't touch the resource-bundle accessor minefield escaped in the 0.4.4 fix. - release.yml gets one step: sign the DMG, append the appcast entry.
The ad-hoc signing question (verify up front)
D-Patch is ad-hoc signed. Sparkle's integrity story with ad-hoc hosts rides on the EdDSA key rather than matching code signatures — OSS projects (IINA et al.) ship Sparkle this way. Still worth a 30-minute spike: test appcast, 0.4.4 → fake-0.4.5 update on a throwaway copy under /var/folders, confirm it installs cleanly.
Why it's disproportionately nice here
Sparkle's installer replaces the bundle itself — the downloaded update never goes through first-launch Gatekeeper assessment, so the "app is damaged / remove quarantine attr" dance users currently do disappears for every future update. That alone justifies it for an app distributed as bare DMGs.
Parallel unlock: a real Developer ID certificate ($99/yr Apple Developer Program) fixes the first-install Gatekeeper experience too, and makes Sparkle's validation story trivial. Nice-to-have, not a prerequisite.
Recommendation
- Minimum viable: hand-rolled checker — an afternoon, zero infra, users still manually install.
- The proper answer: Sparkle 2 — about half a day including the build.sh/release.yml changes and a dry-run update test. This is the one "most people" use.
Concrete changes if taking the Sparkle route
Package.swift: addsparkle-project/Sparkle(2.x).build.sh: copy the two XPC services intoContents/XPCServicesbefore codesign.DPatchApp: wireSPUStandardUpdaterController, add Check-for-Updates menu item; Info.plist keysSUFeedURL+SUPublicEDKey.- CI: store the EdDSA private key as a repo secret; release.yml runs
sign_updateon the DMG and appends the entry toappcast.xml. - Spike: end-to-end test update on a throwaway install before wiring it into a real release.