Snapzy Release Workflow
April 29, 2026 · View on GitHub
Prerequisites
- EdDSA private key in Keychain (generated via
generate_keys) - Preferred: Developer ID signed and notarized app
- GitHub repository with Releases enabled
Release Steps
1. Build & Archive
# In Xcode:
# Product > Archive > Distribute App > Developer ID
# Wait for notarization to complete
2. Create Update Archive
# Navigate to exported app location
cd /path/to/exported
# Create ZIP archive (preserves code signature)
zip -r ~/Snapzy-Updates/Snapzy-X.Y.Z.zip Snapzy.app
# Optional: Create release notes HTML
cat > ~/Snapzy-Updates/Snapzy-X.Y.Z.html << 'EOF'
<html>
<body>
<h2>What's New in X.Y.Z</h2>
<ul>
<li>Feature 1</li>
<li>Bug fix 2</li>
</ul>
</body>
</html>
EOF
3. Generate Appcast
# Locate Sparkle tools
SPARKLE_BIN=~/Library/Developer/Xcode/DerivedData/Snapzy-*/SourcePackages/artifacts/sparkle/Sparkle/bin
# Generate appcast (auto-signs and creates deltas)
$SPARKLE_BIN/generate_appcast ~/Snapzy-Updates
# Output:
# - appcast.xml (updated)
# - *.delta files (for incremental updates)
4. Upload to GitHub Releases
# Create and push tag
git tag -a vX.Y.Z -m "Version X.Y.Z"
git push origin vX.Y.Z
# Create release with assets
gh release create vX.Y.Z \
~/Snapzy-Updates/Snapzy-X.Y.Z.zip \
~/Snapzy-Updates/Snapzy-X.Y.Z.html \
--title "Snapzy X.Y.Z" \
--notes "See release notes for details"
# Upload appcast.xml to repo root or GitHub Pages
cp ~/Snapzy-Updates/appcast.xml ./appcast.xml
git add appcast.xml
git commit -m "chore: update appcast for vX.Y.Z"
git push
Key Management
Backup Private Key
$SPARKLE_BIN/generate_keys -x sparkle_private_key.pem
# Store securely (password manager, encrypted backup)
# NEVER commit to git!
Restore on New Machine
$SPARKLE_BIN/generate_keys -f sparkle_private_key.pem
View Public Key
$SPARKLE_BIN/generate_keys -p
SUFeedURL Configuration
Current URL in Info.plist:
https://raw.githubusercontent.com/duongductrong/Snapzy/master/appcast.xml
Update this value in Snapzy/Resources/Info.plist if your release repository changes.
Testing Updates
# Clear last check time to force update check
defaults delete com.trongduong.snapzy SULastCheckTime
# Run app and click "Check for Updates..."
Fallback Distribution
When Developer ID credentials are unavailable, the GitHub release workflow now produces an ad-hoc signed fallback app and verifies the bundle with codesign --verify --deep --strict.
- Move the app to
/Applicationsbefore first launch - Expect Gatekeeper/notarization limitations on these fallback builds
- If permissions were granted to an older bundle ID, macOS will require re-granting them
Release Notifications
Release notifications are handled by a separate workflow (release-notify.yml) that triggers automatically after release-publish.yml completes successfully. This keeps the publish workflow focused on build/sign/release, and makes it easy to add new notification channels.
Architecture:
release-publish.yml (build → sign → release)
↓ workflow_run trigger
release-notify.yml
├── prepare (fetch release metadata from GitHub API)
├── discord (parallel)
├── slack (parallel, add when needed)
└── telegram (parallel, add when needed)
Discord
1. Create a Discord Webhook
- Open your Discord server
- Go to Server Settings → Integrations → Webhooks
- Click New Webhook
- Choose the target channel for release announcements
- (Optional) Set the webhook name (e.g., "Snapzy Releases") and avatar
- Click Copy Webhook URL — it looks like:
https://discord.com/api/webhooks/123456789012345678/abcdefg...
2. Add the Secret to GitHub
- Go to your GitHub repository → Settings → Secrets and variables → Actions
- Click New repository secret
- Name:
DISCORD_WEBHOOK_URL - Value: paste the webhook URL from step 1
- Click Add secret
What Gets Posted
Each release notification includes:
- Title: version number with link to the GitHub release page
- Body: full changelog from
CHANGELOG.md(features, bug fixes, etc.) - Quick links: DMG download and release page
- Timestamp: when the release was published
If DISCORD_WEBHOOK_URL is not configured, the job is silently skipped — no failures.
Adding a New Channel
To add a notification channel (e.g., Slack, Telegram):
- Open
.github/workflows/release-notify.yml - Add a new job that depends on
prepare - Use
${{ needs.prepare.outputs.version }},.release_url,.download_url, and.bodyfor release data - Add the required secrets (e.g.,
SLACK_WEBHOOK_URL) to GitHub repository settings
See the commented examples at the bottom of release-notify.yml.
Troubleshooting
- Button always disabled: Check Info.plist has SUFeedURL and SUPublicEDKey
- Signature errors: Ensure private key matches public key in app
- No updates found: Verify appcast.xml sparkle:version > current CFBundleVersion
- Notification not sent: Verify the channel secret (e.g.,
DISCORD_WEBHOOK_URL) is set correctly in GitHub repository settings. Check therelease-notifyworkflow run logs for HTTP status warnings.