Sparkle (macOS)
For native macOS apps using Sparkle 2. Create the app with type Sparkle; the appcast returns 400 for other app types.
Feed URL
Set SUFeedURL in Info.plist to the app’s appcast, and SUPublicEDKey to your EdDSA public key:
<key>SUFeedURL</key>
<string>https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>YOUR_BASE64_PUBLIC_KEY</string>The appcast at https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml is RSS 2.0 with Sparkle extensions. It lists every enabled, non-draft release in the requested channel that has a macOS asset, newest version first. Each item carries the release notes, sparkle:minimumSystemVersion when you set one on the app, and an enclosure pointing at https://releaseflow.net/{username}/{slug}/updates/download/{version}/{fileName}, which streams the asset from GitHub (private repositories included).
Query parameters:
?channel=betaor?channel=alpha: include pre-releases, see Channels.?sync=1: start a GitHub sync now instead of waiting for the hourly refresh (owner only: signed in, or your API key inx-api-key; ignored for everyone else, at most one sync per app every 5 minutes).
Release assets
Attach a .zip of the app bundle to the GitHub release. When a release has both a .zip and a .dmg, the appcast uses the .zip; the .dmg is still offered on the download page. A .zip without mac/darwin in its name is treated as macOS when a matching .zip.sig sits next to it.
Signing
ReleaseFlow never holds your private key and does not sign builds. Sign the archive with Sparkle’s sign_update and upload the signature as <asset name>.sig; on sync, ReleaseFlow reads that file and writes it into the enclosure’s sparkle:edSignature.
# Once: create the key pair (private key goes to the login Keychain)
./bin/generate_keys
# Every release: sign the update archive and upload the .sig next to it
./bin/sign_update -p MyApp.zip > MyApp.zip.sig
gh release upload v1.2.0 MyApp.zip MyApp.zip.sig-pprints only the signature; the.sigfile must contain just that base64 string.- Sparkle checks the signature against
SUPublicEDKeyin the installed app. An item withoutsparkle:edSignatureis rejected, so upload the.sigbefore (or together with) publishing the release, or press Sync Releases after adding it. - Code-sign and notarize the app before zipping; EdDSA signs the archive, not the bundle.
Your app must ship SUPublicEDKey: it is what makes Sparkle verify updates, so a compromised feed or download cannot install a build you did not sign. ReleaseFlow cannot enforce this on the client.
Require EdDSA signature (app settings, on by default for new apps): a release whose update archive has no .sig is left out of the appcast, so the previous signed release stays the newest update. The app page lists unsigned releases either way, and the feed audit log records signature, download URL and enable/disable changes.
Check the result:
curl -s -H "x-api-key: $RELEASEFLOW_API_KEY" "https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml?sync=1" | head -40Versions
Sparkle compares sparkle:version with the installed app’s CFBundleVersion. ReleaseFlow derives it from the tag, and writes the tag version to sparkle:shortVersionString. The app’s version strategy decides the format:
| Tag | SEMVER (default) | NUMERIC_BUILD |
|---|---|---|
v1.2.3 | 1.2.3 | 10203 |
v1.3.0-beta.2 | 1.3.0b2 | 10300b2 |
v1.3.0-rc.1 | 1.3.0fc1 | 10300fc1 |
Set CFBundleVersion in the build to the same value. Use NUMERIC_BUILD if your build numbers are integers; it is set with sparkleVersionStrategy on PATCH https://releaseflow.net/api/rest/apps/{id} (see the API reference).
Beta builds
Enable the beta channel on the app, then let users opt in by returning the beta feed from your updater delegate:
import Sparkle
final class UpdaterDelegate: NSObject, SPUUpdaterDelegate {
func feedURLString(for updater: SPUUpdater) -> String? {
UserDefaults.standard.bool(forKey: "betaUpdates")
? "https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml?channel=beta"
: nil // nil = SUFeedURL from Info.plist
}
}Licensing
To gate the app behind a license key, add the ReleaseFlowLicensing Swift package; see License keys.