SideKick ships auto-updates with Sparkle. Updates are
distributed through GitHub Releases, and the app reads a signed appcast.xml
served from main:
SUFeedURL = https://raw.githubusercontent.com/synth-inc/onit/main/appcast.xml
SUPublicEDKey = Exm1gtRzTJItzeYn3oywxb3r0W75AVEhdCEHK5A1mec= (Info.plist)
⚠️ The feed is served byraw.githubusercontent.com, so the repository must be public for clients to fetch updates.
Each app has its own EdDSA key pair. The private key lives in the macOS Keychain
under the account onit-sidekick and is also backed up (outside any repo) at
~/.sparkle-keys/onit-sidekick_eddsa.private. The public key is in Info.plist
(SUPublicEDKey) and must never change once users are in the wild — rotating it
breaks updates for everyone on an older build.
To inspect or re-import the key:
# print the public key associated with the account
.../Sparkle/bin/generate_keys -p --account onit-sidekick
# re-import the private key on a new machine / CI
.../Sparkle/bin/generate_keys -f ~/.sparkle-keys/onit-sidekick_eddsa.private --account onit-sidekickFor CI, store the contents of onit-sidekick_eddsa.private as a GitHub Actions secret
and import it before signing.
- Bump the version: set
MARKETING_VERSION(and incrementCURRENT_PROJECT_VERSION) in the Xcode project. - Make sure notarization credentials are exported (used by
build_and_notarize.sh):export NOTARY_KEY_PATH=/path/to/AuthKey_XXXX.p8 export NOTARY_KEY_ID=XXXXXXХXXX export NOTARY_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- Authenticate the GitHub CLI once:
gh auth status(orgh auth login). - Run the release script from
macos/:cd macos ./release.sh # uses the project's MARKETING_VERSION # or: ./release.sh 4.1.0 --notes "What's new…"
The script: builds & notarizes the app → publishes a GitHub Release vX.Y.Z with
SideKick-X.Y.Z.dmg → regenerates and EdDSA-signs appcast.xml (preserving past
items, pointing enclosures at the release asset) → commits and pushes appcast.xml
to main. Sparkle clients pick it up at their next check.
| Flag | Effect |
|---|---|
--skip-build |
Reuse the existing build/SideKick.dmg (skip build + notarize). |
--no-push |
Generate the signed appcast locally only — no Release, no commit, no push. Good for a dry run. |
--notes "…" |
Custom release notes (otherwise auto-generated from commits). |
generate_appcast hashes each .dmg, signs it with the private key, and writes the
sparkle:edSignature/length enclosure attributes into appcast.xml. The app
verifies that signature against the bundled SUPublicEDKey before installing —
so a tampered or unsigned build is rejected.
The app is also distributed as a Homebrew cask in
synth-inc/homebrew-tap:
brew install --cask synth-inc/homebrew-tap/onit-sidekickrelease.sh updates the cask automatically on every release — it computes the new
sha256, bumps version, points the url at the GitHub Release asset, and pushes to the
tap. The tap defaults to ~/SynthInc/homebrew-tap (override with TAP_DIR=…). The cask
becomes installable only after the first real release fills in its version/sha256.
- Repository is public (required for the raw appcast URL).
- Rotate any leaked secrets first (see the open-source audit).
- Confirm
SUPublicEDKeyinInfo.plistmatchesgenerate_keys -p --account onit-sidekick. - Cut the first release with
./release.shand verify the appcast enclosure URL resolves (the uploaded.dmgis downloadable).