# 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///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, `Sparkle` module, ~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.xml` with per-release entries; at release time `sign_update ` 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 into `Contents/XPCServices` before signing. Framework + XPC, no `Bundle.module` accessor — 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 1. `Package.swift`: add `sparkle-project/Sparkle` (2.x). 2. `build.sh`: copy the two XPC services into `Contents/XPCServices` before codesign. 3. `DPatchApp`: wire `SPUStandardUpdaterController`, add Check-for-Updates menu item; Info.plist keys `SUFeedURL` + `SUPublicEDKey`. 4. CI: store the EdDSA private key as a repo secret; release.yml runs `sign_update` on the DMG and appends the entry to `appcast.xml`. 5. Spike: end-to-end test update on a throwaway install before wiring it into a real release.