Tracking issue for Stage 2 of the in-app updater (Stage 1 landed in #99).
Stage 1 wired Sparkle as an opt-in SPM dependency with a Check for Updates menu item. When no SUFeedURL is set the menu opens the GitHub Releases page; when one is set Sparkle starts. Stage 2 is making the signed-appcast path actually work end-to-end.
Required pieces
1. EdDSA key pair (ed25519)
- Generate locally with
./bin/generate_keys from Sparkle's distribution.
- Public key (
SUPublicEDKey) goes into Info.plist (committed).
- Private key (
ed25519_priv.pem or keychain item) goes to a GitHub Actions secret. Document the secret name (SPARKLE_ED25519_PRIVATE_KEY proposed) in scripts/SECRETS.md.
2. Info.plist additions
SUFeedURL — https://momenbasel.github.io/PureMac/appcast.xml (proposed; GitHub Pages publishes alongside the repo) or a release-asset URL like https://github.com/momenbasel/PureMac/releases/latest/download/appcast.xml.
SUPublicEDKey — base64-encoded public key.
SUEnableInstallerLauncherService, SUEnableDownloaderService — defaults OK; document if we deviate.
SUScheduledCheckInterval — default 86400 (daily). Make sure this matches user expectation.
3. Appcast generation in release.yml
- After notarized DMG lands, sign it with
./bin/sign_update <dmg> (Sparkle helper) — emits a sha256 + signature.
- Generate or update
appcast.xml with the new <item> (version, sparkle:edSignature, sparkle:shortVersionString, release notes URL, minimumSystemVersion=13.0).
- Commit the updated
appcast.xml back to the repo / Pages branch, or upload as a release asset that SUFeedURL points at.
4. Sparkle helper-bundle code-signing
- Sparkle ships
Sparkle.framework/Versions/B/Resources/Updater.app, Autoupdate.app, and XPC helper bundles. The current release workflow only signs the main app — codesign --deep --force -s … on the outer .app typically picks them up, but verify with codesign --verify --deep --strict --verbose=4 and add explicit signing steps if it doesn't.
- Notarization needs all bundled helpers signed by the same Developer ID. Confirm
notarytool submit accepts the bundle on a test run.
5. Hardened Runtime / entitlements
- Sparkle requires
com.apple.security.cs.allow-jit or similar in some configurations. Re-check PureMac.entitlements against Sparkle 2.x sandboxing guide. If we need any extra entitlements they go in here.
6. Test plan before release
- Build a dummy
appcast.xml advertising a higher version, point SUFeedURL at it, build v2.3.0 with the current Info.plist values, verify the in-app update dialog appears and the download verifies against the EdDSA signature.
- Verify a tampered DMG is rejected (delete a byte from the signed DMG, rehost, watch Sparkle refuse).
7. UX
- The Releases-page fallback in
UpdateService stays as the no-config branch (useful for local dev builds without an appcast).
- Consider adding a Settings → Updates section exposing 'Check automatically' (
SUEnableAutomaticChecks) and 'Check interval' so users can opt out.
Out of scope (separate follow-ups)
- Delta updates (
sparkle:enclosure patch entries).
- Beta/staging channel (
sparkle:channel).
- In-app changelog rendering vs opening the release notes URL externally.
Closes the Stage 2 ask in #94 once shipped.
Refs #99, #94.
Tracking issue for Stage 2 of the in-app updater (Stage 1 landed in #99).
Stage 1 wired Sparkle as an opt-in SPM dependency with a
Check for Updatesmenu item. When noSUFeedURLis set the menu opens the GitHub Releases page; when one is set Sparkle starts. Stage 2 is making the signed-appcast path actually work end-to-end.Required pieces
1. EdDSA key pair (ed25519)
./bin/generate_keysfrom Sparkle's distribution.SUPublicEDKey) goes into Info.plist (committed).ed25519_priv.pemor keychain item) goes to a GitHub Actions secret. Document the secret name (SPARKLE_ED25519_PRIVATE_KEYproposed) inscripts/SECRETS.md.2. Info.plist additions
SUFeedURL—https://momenbasel.github.io/PureMac/appcast.xml(proposed; GitHub Pages publishes alongside the repo) or a release-asset URL likehttps://github.com/momenbasel/PureMac/releases/latest/download/appcast.xml.SUPublicEDKey— base64-encoded public key.SUEnableInstallerLauncherService,SUEnableDownloaderService— defaults OK; document if we deviate.SUScheduledCheckInterval— default 86400 (daily). Make sure this matches user expectation.3. Appcast generation in
release.yml./bin/sign_update <dmg>(Sparkle helper) — emits a sha256 + signature.appcast.xmlwith the new<item>(version, sparkle:edSignature, sparkle:shortVersionString, release notes URL, minimumSystemVersion=13.0).appcast.xmlback to the repo / Pages branch, or upload as a release asset thatSUFeedURLpoints at.4. Sparkle helper-bundle code-signing
Sparkle.framework/Versions/B/Resources/Updater.app,Autoupdate.app, and XPC helper bundles. The current release workflow only signs the main app —codesign --deep --force -s …on the outer.apptypically picks them up, but verify withcodesign --verify --deep --strict --verbose=4and add explicit signing steps if it doesn't.notarytool submitaccepts the bundle on a test run.5. Hardened Runtime / entitlements
com.apple.security.cs.allow-jitor similar in some configurations. Re-checkPureMac.entitlementsagainst Sparkle 2.x sandboxing guide. If we need any extra entitlements they go in here.6. Test plan before release
appcast.xmladvertising a higher version, pointSUFeedURLat it, build v2.3.0 with the current Info.plist values, verify the in-app update dialog appears and the download verifies against the EdDSA signature.7. UX
UpdateServicestays as the no-config branch (useful for local dev builds without an appcast).SUEnableAutomaticChecks) and 'Check interval' so users can opt out.Out of scope (separate follow-ups)
sparkle:enclosurepatch entries).sparkle:channel).Closes the Stage 2 ask in #94 once shipped.
Refs #99, #94.