7.9 KiB
VibeTunnel Release Process
This guide explains how to create and publish releases for VibeTunnel, a macOS menu bar application using Sparkle 2.x for automatic updates.
🎯 Release Process Overview
VibeTunnel uses an automated release process that handles all the complexity of:
- Building and code signing
- Notarization with Apple
- Creating DMG disk images
- Publishing to GitHub
- Updating Sparkle appcast files
🚀 Creating a Release
Step 1: Pre-flight Check
./scripts/preflight-check.sh
This validates your environment is ready for release.
Step 2: Create/Update CHANGELOG.md
Before creating any release, ensure the CHANGELOG.md file exists and contains a proper section for the version being released. If this is your first release, create a CHANGELOG.md file in the project root:
# Changelog
All notable changes to VibeTunnel will be documented in this file.
## [1.1.0] - 2025-06-10
### 🎨 UI Improvements
- **Enhanced feature** - Description of the improvement
...
CRITICAL: The appcast generation relies on the local CHANGELOG.md file, NOT the GitHub release description. The changelog must be added to CHANGELOG.md BEFORE running the release script.
Step 3: Create the Release
# For stable releases:
./scripts/release.sh stable
# For pre-releases (beta, alpha, rc):
./scripts/release.sh beta 1 # Creates version-beta.1
./scripts/release.sh alpha 2 # Creates version-alpha.2
./scripts/release.sh rc 1 # Creates version-rc.1
IMPORTANT: The release script does NOT automatically increment build numbers. You must manually update the build number in VibeTunnel.xcodeproj before running the script, or it will fail the pre-flight check.
The script will:
- Validate build number is unique and incrementing
- Build, sign, and notarize the app
- Create a DMG
- Publish to GitHub
- Update the appcast files with EdDSA signatures
- Commit and push all changes
Step 4: Verify Success
- Check the GitHub releases page
- Verify the appcast was updated correctly with proper changelog content
- Test updating from a previous version
- Important: Verify that the Sparkle update dialog shows the formatted changelog, not HTML tags
⚠️ Critical Requirements
1. Build Numbers MUST Increment
Sparkle uses build numbers (CFBundleVersion) to determine updates, NOT version strings!
| Version | Build | Result |
|---|---|---|
| 1.0.0-beta.1 | 100 | ✅ |
| 1.0.0-beta.2 | 101 | ✅ |
| 1.0.0-beta.3 | 99 | ❌ Build went backwards |
| 1.0.0 | 101 | ❌ Duplicate build number |
2. Required Environment Variables
export APP_STORE_CONNECT_KEY_ID="YOUR_KEY_ID"
export APP_STORE_CONNECT_ISSUER_ID="YOUR_ISSUER_ID"
export APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_CONTENT
-----END PRIVATE KEY-----"
3. Prerequisites
- Xcode 16.4+ installed
- GitHub CLI authenticated:
gh auth status - Apple Developer ID certificate in Keychain
- Sparkle tools in
~/.local/bin/(sign_update, generate_appcast)
🔐 Sparkle Configuration
Sparkle Requirements for Non-Sandboxed Apps
VibeTunnel is not sandboxed, which simplifies Sparkle configuration:
1. Entitlements (VibeTunnel.entitlements)
<!-- App is NOT sandboxed -->
<key>com.apple.security.app-sandbox</key>
<false/>
<!-- Required for code injection/library validation -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
2. Info.plist Configuration
"SUEnableInstallerLauncherService": false, // Not needed for non-sandboxed apps
"SUEnableDownloaderService": false, // Not needed for non-sandboxed apps
3. Code Signing Requirements
The notarization script handles all signing correctly:
- Do NOT use --deep flag when signing the app
- Sign the app with hardened runtime and entitlements
The notarize-app.sh script should sign the app:
# Sign the app WITHOUT --deep flag
codesign --force --sign "Developer ID Application" --entitlements VibeTunnel.entitlements --options runtime VibeTunnel.app
Common Sparkle Errors and Solutions
| Error | Cause | Solution |
|---|---|---|
| "You're up to date!" when update exists | Build number not incrementing | Check build numbers in appcast are correct |
| "Update installation failed" | Signing or permission issues | Verify app signature and entitlements |
| "Cannot verify update signature" | EdDSA key mismatch | Ensure sparkle-public-ed-key.txt matches private key |
📋 Update Channels
VibeTunnel supports two update channels:
-
Stable Channel (
appcast.xml)- Production releases only
- Default for all users
-
Pre-release Channel (
appcast-prerelease.xml)- Includes beta, alpha, and RC versions
- Users opt-in via Settings
🐛 Common Issues and Solutions
Appcast Shows HTML Tags Instead of Formatted Text
Problem: Sparkle update dialog shows escaped HTML like <h2> instead of formatted text.
Root Cause: The generate-appcast.sh script is escaping HTML content from GitHub release descriptions.
Solution:
- Ensure CHANGELOG.md has the proper section for the release version BEFORE running release script
- The appcast should use local CHANGELOG.md, not GitHub release body
- If the appcast is wrong, manually fix the generate-appcast.sh script to use local changelog content
Build Numbers Not Incrementing
Problem: Sparkle doesn't detect new version as an update.
Solution: Always increment the build number in the Xcode project before releasing.
🛠️ Manual Process (If Needed)
If the automated script fails, here's the manual process:
1. Update Build Number
Edit the project build settings in Xcode:
- Open VibeTunnel.xcodeproj
- Select the project
- Update CURRENT_PROJECT_VERSION (build number)
2. Clean and Build
rm -rf build DerivedData .build
./scripts/build.sh --configuration Release
3. Sign and Notarize
./scripts/notarize-app.sh build/Build/Products/Release/VibeTunnel.app
4. Create DMG
./scripts/create-dmg.sh
5. Sign DMG for Sparkle
export PATH="$HOME/.local/bin:$PATH"
sign_update build/VibeTunnel-X.X.X.dmg
# Copy the sparkle:edSignature value
6. Create GitHub Release
gh release create "v1.0.0-beta.1" \
--title "VibeTunnel 1.0.0-beta.1" \
--notes "Beta release 1" \
--prerelease \
build/VibeTunnel-1.0.0-beta.1.dmg
7. Update Appcast
./scripts/update-appcast.sh
git add appcast*.xml
git commit -m "Update appcast for v1.0.0-beta.1"
git push
🔍 Troubleshooting
Debug Sparkle Updates
# Monitor VibeTunnel logs
log stream --predicate 'process == "VibeTunnel"' --level debug
# Check XPC errors
log stream --predicate 'process == "VibeTunnel"' | grep -i -E "(sparkle|xpc|installer)"
# Verify XPC services
codesign -dvv "VibeTunnel.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
Verify Signing and Notarization
# Check app signature
./scripts/verify-app.sh build/VibeTunnel-1.0.0.dmg
# Verify XPC bundle IDs (should be org.sparkle-project.*)
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
"VibeTunnel.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc/Contents/Info.plist"
Appcast Issues
# Verify appcast has correct build numbers
./scripts/verify-appcast.sh
# Check if build number is "1" (common bug)
grep '<sparkle:version>' appcast-prerelease.xml
📚 Important Links
Remember: Always use the automated release script, ensure build numbers increment, and test updates before announcing!