This document describes the release process for Pindrop, including code signing and update distribution via Sparkle.
Pindrop uses Sparkle for automatic updates. Updates are signed using EdDSA (Ed25519) for security.
- Public Key: Embedded in
Pindrop/Info.plistasSUPublicEDKey - Private Key: Stored securely in the macOS Keychain (automatically managed by Sparkle)
IMPORTANT: The private key is NEVER committed to the repository. It is stored only in the macOS Keychain of the machine that generated it.
TCU0MwULuIK6y0ubIossVr+61PGh/wHZfFrRFc9F2Is=
This key is already configured in Pindrop/Info.plist:
<key>SUPublicEDKey</key>
<string>TCU0MwULuIK6y0ubIossVr+61PGh/wHZfFrRFc9F2Is=</string>If you need to regenerate the signing keys (e.g., if the private key is lost):
-
Download the Sparkle release:
curl -L -o Sparkle.tar.xz "https://github.com/sparkle-project/Sparkle/releases/download/2.8.1/Sparkle-2.8.1.tar.xz" tar -xf Sparkle.tar.xz -
Run the key generator:
./bin/generate_keys
-
The tool will:
- Generate a new EdDSA keypair
- Store the private key in your macOS Keychain
- Output the public key to stdout
-
Update
Pindrop/Info.plistwith the new public key:<key>SUPublicEDKey</key> <string>YOUR_NEW_PUBLIC_KEY_HERE</string>
-
CRITICAL: Users with older versions will NOT be able to update to versions signed with the new key. This should only be done for major releases or security incidents.
- macOS development machine with the private key in Keychain
- Xcode installed
justcommand runner:brew install just
-
Update version numbers in Xcode project settings
-
Build and sign the release:
just release 1.0.0This will:
- Clean build artifacts
- Build the release version
- Sign the app with your Developer ID
- Create a DMG in
dist/Pindrop.dmg - Notarize the DMG with Apple
- Staple the notarization ticket to the DMG
- Render version-specific release notes HTML from
release-notes/vX.Y.Z.md - Generate
appcast.xmlfrom the final stapled DMG and attach release-notes links
- Upload the release:
- Upload
dist/Pindrop.dmgto GitHub Releases - Upload
appcast.xmlas a GitHub Release asset (or host it at your feed URL) - Upload the rendered
dist/release-notes-vX.Y.Z.htmlasset so Sparkle can show notes in the update window
- Tag the release:
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0The just appcast command automates the appcast generation process:
- Validates the DMG exists at the specified path
- Downloads Sparkle tools (if not already present):
- Downloads Sparkle 2.8.1 release
- Extracts
generate_appcastandsign_updatetobin/
- Renders release notes HTML from
release-notes/vX.Y.Z.mdintodist/release-notes-vX.Y.Z.html - Generates the appcast:
- Copies DMG to a temporary directory
- Runs
generate_appcastto create signatures - Injects
sparkle:releaseNotesLinkandsparkle:fullReleaseNotesLink - Outputs
appcast.xmlin the project root
# Generate appcast for the default DMG location
just appcast dist/Pindrop.dmg
# The appcast.xml will be created in the current directoryThe generated appcast.xml follows Sparkle's RSS format:
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>Pindrop Updates</title>
<item>
<title>Version 1.0.0</title>
<link>https://github.com/watzon/pindrop/releases/tag/v1.0.0</link>
<sparkle:version>100</sparkle:version>
<sparkle:shortVersionString>1.0.0</sparkle:shortVersionString>
<sparkle:releaseNotesLink>https://github.com/watzon/pindrop/releases/download/v1.0.0/release-notes-v1.0.0.html</sparkle:releaseNotesLink>
<sparkle:fullReleaseNotesLink>https://github.com/watzon/pindrop/releases/tag/v1.0.0</sparkle:fullReleaseNotesLink>
<enclosure url="..."
sparkle:edSignature="SIGNATURE"
length="SIZE"
type="application/octet-stream"/>
</item>
</channel>
</rss>If you need to manually edit appcast.xml:
- Use a generated appcast file as a starting point
- Update version numbers, release notes links, and download URL
- Generate the EdDSA signature using:
./bin/sign_update /path/to/Pindrop.dmg
- Add the signature to the
<enclosure>element
The appcast can be hosted:
- GitHub Pages: Commit
appcast.xmltogh-pagesbranch - GitHub Releases: Upload as a release asset
- Custom server: Any HTTPS URL accessible to users
Update SUFeedURL in Pindrop/Info.plist to point to your hosted appcast.
- Never share the private key
- Never commit the private key to version control
- The private key is tied to your Mac's Keychain and cannot be exported easily
- If you lose the private key, you will need to generate new keys and users will need to manually download updates
This means the update was signed with a different key than what's in the app's Info.plist. Ensure:
- You're using the correct private key (check Keychain)
- The public key in
Info.plistmatches the private key used for signing
If you've lost the private key:
- Generate new keys using
generate_keys - Update
Info.plistwith the new public key - Notify users they'll need to manually download the update
- Future updates will work normally with the new key