-
Notifications
You must be signed in to change notification settings - Fork 3
Release
The maintainer runbook for cutting a build and feeding the Sparkle update channels. Most of this is scripted; the parts that need Apple credentials or the Sparkle signing key are called out. For just installing or building, see Installation.
-
CFBundleShortVersionString(marketing version) — semver, with a pre-release suffix per channel:- Stable:
1.0.0 - Beta:
1.0.0-beta.1,1.0.0-beta.2, … - Nightly:
1.0.0-nightly.<build>+<shortsha>(set automatically by CI)
- Stable:
-
CFBundleVersion(build number) — a monotonic integer;scripts/bundle.shderives it fromgit rev-list --count HEAD. Sparkle orders updates by this, so it must only ever increase.
Set the marketing version for a manual build with VERSION=… ./scripts/bundle.sh.
One appcast (docs/appcast.xml, served by GitHub Pages) carries all three channels:
-
stable — items with no
<sparkle:channel>tag. -
beta — items tagged
<sparkle:channel>beta</sparkle:channel>. -
nightly — items tagged
<sparkle:channel>nightly</sparkle:channel>.
The app's Settings → Updates picker maps to Sparkle's allowedChannels: Stable → {}, Beta → {beta}, Nightly → {beta, nightly} (cumulative).
-
Sparkle EdDSA keys — run Sparkle's
generate_keysonce. It stores the private key in your login keychain and prints the public key. Put the public key inSUPublicEDKey(inscripts/bundle.sh's Info.plist block). Never commit the private key. For CI, export it as theSPARKLE_ED_PRIVATE_KEYrepo secret. -
Developer ID — a "Developer ID Application" certificate in your keychain (local signing) and, for CI, its
.p12base64-encoded asDEVELOPER_ID_CERT_P12+CERT_PASSWORD. -
Notarization — an App Store Connect API key; locally store it with
xcrun notarytool store-credentialsand pass the profile name; for CI addNOTARYTOOL_API_KEY/NOTARYTOOL_KEY_ID/NOTARYTOOL_ISSUER. -
GitHub Pages — Pages serves
/docsonmain, so the appcast resolves athttps://tdeverx.github.io/contained-app/appcast.xml(theSUFeedURL).
VERSION=1.0.0 ./scripts/release.sh # build → codesign → DMG → notarize → staple
./scripts/appcast.sh /path/to/Sparkle/bin updates # sign DMG(s) + (re)generate docs/appcast.xmlThen:
- Create a GitHub release tagged
v1.0.0(orv1.0.0-beta.N, with Pre-release checked for betas); upload the.dmgas a release asset. The appcast's enclosure URLs point at these assets via--download-url-prefix. - Commit the updated
docs/appcast.xml. Pages serves it; clients on the matching channel get offered the update.
.github/workflows/nightly.yml builds the latest green main on every push (newest commit wins via concurrency: cancel-in-progress), signs + notarizes, refreshes the rolling nightly pre-release with the new DMG, regenerates the nightly appcast item, and commits docs/appcast.xml. It skips the sign/notarize/publish steps cleanly when the secrets above are absent, so the workflow stays green on a fresh public repo until you add them.
.github/workflows/release.yml is the manual (workflow_dispatch) equivalent for stable/beta tags.
Note: GitHub doesn't yet offer macOS 26 / Xcode 26 runners. Both workflows detect the runner's Xcode version and skip the build (staying green) until a macOS 26 image is available — bump
runs-onthen.
- Sparkle update integrity is the EdDSA signature on the appcast — that is the security boundary and works regardless of Apple notarization. Notarization is about Gatekeeper on first install; ship notarized builds so users aren't warned.
- The private Sparkle key and the Developer ID cert never live in the repo — only as keychain entries (local) or encrypted repo secrets (CI).
Contained — source-available under PolyForm Noncommercial 1.0.0. Free for non-commercial use.
Developing