- Updated generate-appcast.sh with prominent warnings about using -f flag - Added critical warnings to release documentation (RELEASE.md and release-guide.md) - Created validate-sparkle-signature.sh script to verify signatures before release - Added pre-release checklist items for signature validation - Documented the beta 8 incident and resolution This ensures we always use the correct file-based private key and never accidentally use the keychain key that produces incompatible signatures.
9.2 KiB
VibeTunnel Release Process
This document describes the complete release process for VibeTunnel, including all prerequisites, steps, and troubleshooting information.
Table of Contents
- Prerequisites
- Pre-Release Checklist
- Release Process
- Post-Release Steps
- Troubleshooting
- Lessons Learned
Prerequisites
Required Tools
- Xcode (latest stable version)
- GitHub CLI (
brew install gh) - Apple Developer Account with valid certificates
- Sparkle EdDSA Keys (see Sparkle Key Management)
Environment Variables
# Required for notarization
export APP_STORE_CONNECT_API_KEY_P8="<your-api-key>"
export APP_STORE_CONNECT_KEY_ID="<your-key-id>"
export APP_STORE_CONNECT_ISSUER_ID="<your-issuer-id>"
# Optional - will be auto-detected if not set
export SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"
Sparkle Key Management
VibeTunnel uses EdDSA signatures for secure updates via Sparkle framework.
⚠️ CRITICAL: Multiple Keys Warning
Your system may have multiple Sparkle private keys:
- File-based key at
private/sparkle_private_key(CORRECT) - Keychain key (WRONG - may produce incompatible signatures)
ALWAYS use the -f flag when signing:
# ✅ CORRECT
sign_update -f private/sparkle_private_key file.dmg
# ❌ WRONG - may use keychain key
sign_update file.dmg
Key Storage
- Private Key:
private/sparkle_private_key(NEVER commit this!) - Public Key: In Info.plist as
SUPublicEDKey - Expected Public Key:
AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=
Generating New Keys
If you need to generate new keys:
# Generate keys using Sparkle's tool
./build/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_keys
# This creates a key pair - save the private key securely!
Restoring Keys
To restore keys from backup:
# Create private directory
mkdir -p private
# Copy your private key (base64 encoded, no comments)
echo "YOUR_PRIVATE_KEY_BASE64" > private/sparkle_private_key
# Ensure it's in .gitignore
echo "private/" >> .gitignore
Pre-Release Checklist
Before starting a release, ensure:
-
Version Configuration
- Update
MARKETING_VERSIONinVibeTunnel/version.xcconfig - Increment
CURRENT_PROJECT_VERSION(build number) - Ensure version follows semantic versioning
- Update
-
Code Quality
- All tests pass:
npm run test(in web/) and Swift tests - Linting passes:
npm run lint - Typechecking passes:
npm run typecheck - No uncommitted changes:
git status
- All tests pass:
-
Documentation
- Update
CHANGELOG.mdwith release notes - Version header format:
## X.Y.Z (YYYY-MM-DD) - Include sections: Features, Improvements, Bug Fixes
- Update
-
Authentication
- GitHub CLI authenticated:
gh auth status - Signing certificates valid:
security find-identity -v -p codesigning - Notarization credentials set (environment variables)
- GitHub CLI authenticated:
-
Sparkle Signature Validation
- Private key exists at
private/sparkle_private_key - Run validation script:
./scripts/validate-sparkle-signature.sh - Confirm NO keychain key conflicts (script will warn)
- Private key exists at
Release Process
Automated Release
The easiest way to create a release is using the automated script:
# For stable release
./scripts/release.sh stable
# For pre-release (beta, alpha, rc)
./scripts/release.sh beta 1 # Creates 1.0.0-beta.1
./scripts/release.sh rc 2 # Creates 1.0.0-rc.2
The script will:
- Run pre-flight checks
- Build the application
- Sign and notarize
- Create DMG
- Upload to GitHub
- Update appcast files
Manual Release Steps
If you need to run steps manually:
-
Run Pre-flight Checks
./scripts/preflight-check.sh -
Build Application
# For stable release ./scripts/build.sh --configuration Release # For pre-release IS_PRERELEASE_BUILD=YES ./scripts/build.sh --configuration Release -
Sign and Notarize
./scripts/sign-and-notarize.sh --sign-and-notarize -
Create DMG
./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app -
Notarize DMG
./scripts/notarize-dmg.sh build/VibeTunnel-X.Y.Z.dmg -
Create GitHub Release
# Create tag git tag -a "vX.Y.Z" -m "Release X.Y.Z" git push origin "vX.Y.Z" # Create release gh release create "vX.Y.Z" \ --title "VibeTunnel X.Y.Z" \ --notes "Release notes here" \ build/VibeTunnel-X.Y.Z.dmg -
Update Appcast
./scripts/generate-appcast.sh git add appcast*.xml git commit -m "Update appcast for vX.Y.Z" git push
Post-Release Steps
-
Verify Release
- Check GitHub release page
- Download and test the DMG
- Verify auto-update works from previous version
-
Clean Up
# Clean build artifacts (keeps DMG) ./scripts/clean.sh --keep-dmg # Restore development version if needed git checkout -- VibeTunnel/version.xcconfig -
Announce Release
- Update website/documentation
- Send release announcement
- Update issue tracker milestones
Troubleshooting
Common Issues
"Update isn't properly signed" Error
This indicates an EdDSA signature mismatch. Causes:
- Wrong private key used for signing
- Appcast not updated after DMG creation
- Cached signatures from different key
Solution:
- Ensure correct private key in
private/sparkle_private_key - Regenerate appcast:
./scripts/generate-appcast.sh - Commit and push appcast changes
Build Number Already Exists
Error: "Build number X already exists in appcast"
Solution:
- Increment
CURRENT_PROJECT_VERSIONinversion.xcconfig - Each release must have a unique build number
Notarization Fails
Common causes:
- Invalid or expired certificates
- Missing API credentials
- Network issues
Solution:
- Check credentials:
xcrun notarytool history - Verify certificates:
security find-identity -v - Check console logs for specific errors
Xcode Project Version Mismatch
If build shows wrong version:
- Ensure Xcode project uses
$(CURRENT_PROJECT_VERSION) - Not hardcoded values
- Clean and rebuild
Verification Commands
# Check signing
codesign -dv --verbose=4 build/VibeTunnel.app
# Check notarization
spctl -a -t exec -vv build/VibeTunnel.app
# Verify DMG
hdiutil verify build/VibeTunnel-X.Y.Z.dmg
# Test EdDSA signature
./build/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \
build/VibeTunnel-X.Y.Z.dmg \
-f private/sparkle_private_key
Lessons Learned
Critical Points
-
Always Use Correct Sparkle Keys
- The private key must match the public key in the app
- Store private key securely, never in version control
- Test signature generation before release
-
Timestamp All Code Signatures
- Required for Sparkle components
- Use
--timestampflag on all codesign operations - Prevents "update isn't properly signed" errors
-
Version Management
- Use xcconfig for centralized version control
- Never hardcode versions in Xcode project
- Increment build number for every release
-
Pre-flight Validation
- Always run pre-flight checks
- Ensure clean git state
- Verify all credentials before starting
-
Appcast Synchronization
- Push appcast updates immediately after release
- GitHub serves as the appcast host
- Users fetch from raw.githubusercontent.com
Best Practices
- Automate Everything: Use the release script for consistency
- Test Updates: Always test auto-update from previous version
- Keep Logs: Save notarization logs for debugging
- Document Issues: Update this guide when new issues arise
- Clean Regularly: Use
clean.shto manage disk space
Script Reference
| Script | Purpose | Key Options |
|---|---|---|
release.sh |
Complete automated release | stable, beta N, alpha N, rc N |
preflight-check.sh |
Validate release readiness | None |
build.sh |
Build application | --configuration Release/Debug |
sign-and-notarize.sh |
Sign and notarize app | --sign-and-notarize |
create-dmg.sh |
Create DMG installer | <app_path> [output_path] |
notarize-dmg.sh |
Notarize DMG | <dmg_path> |
generate-appcast.sh |
Update appcast files | None |
verify-appcast.sh |
Verify appcast validity | None |
clean.sh |
Clean build artifacts | --all, --keep-dmg, --dry-run |
lint.sh |
Run code linters | None |
Environment Setup
For team members setting up for releases:
# 1. Install dependencies
brew install gh
npm install -g swiftformat swiftlint
# 2. Authenticate GitHub CLI
gh auth login
# 3. Set up notarization credentials
# Add to ~/.zshrc or ~/.bash_profile:
export APP_STORE_CONNECT_API_KEY_P8="..."
export APP_STORE_CONNECT_KEY_ID="..."
export APP_STORE_CONNECT_ISSUER_ID="..."
# 4. Get Sparkle private key from secure storage
# Contact team lead for access
For questions or issues, consult the script headers or create an issue in the repository.