# VibeTunnel Release Process This document describes the complete release process for VibeTunnel, including all prerequisites, steps, and troubleshooting information. ## Table of Contents 1. [Prerequisites](#prerequisites) 2. [Pre-Release Checklist](#pre-release-checklist) 3. [Release Process](#release-process) 4. [Post-Release Steps](#post-release-steps) 5. [Troubleshooting](#troubleshooting) 6. [Lessons Learned](#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](#sparkle-key-management)) ### Environment Variables ```bash # Required for notarization export APP_STORE_CONNECT_API_KEY_P8="" export APP_STORE_CONNECT_KEY_ID="" export APP_STORE_CONNECT_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. #### Key Storage - **Private Key**: `private/sparkle_private_key` (NEVER commit this!) - **Public Key**: `VibeTunnel/sparkle-public-ed-key.txt` (committed to repo) #### Generating New Keys If you need to generate new keys: ```bash # 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: ```bash # 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: 1. **Version Configuration** - [ ] Update `MARKETING_VERSION` in `VibeTunnel/version.xcconfig` - [ ] Increment `CURRENT_PROJECT_VERSION` (build number) - [ ] Ensure version follows semantic versioning 2. **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` 3. **Documentation** - [ ] Update `CHANGELOG.md` with release notes - [ ] Version header format: `## X.Y.Z (YYYY-MM-DD)` - [ ] Include sections: Features, Improvements, Bug Fixes 4. **Authentication** - [ ] GitHub CLI authenticated: `gh auth status` - [ ] Signing certificates valid: `security find-identity -v -p codesigning` - [ ] Notarization credentials set (environment variables) ## Release Process ### Automated Release The easiest way to create a release is using the automated script: ```bash # 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: 1. Run pre-flight checks 2. Build the application 3. Sign and notarize 4. Create DMG 5. Upload to GitHub 6. Update appcast files ### Manual Release Steps If you need to run steps manually: 1. **Run Pre-flight Checks** ```bash ./scripts/preflight-check.sh ``` 2. **Build Application** ```bash # For stable release ./scripts/build.sh --configuration Release # For pre-release IS_PRERELEASE_BUILD=YES ./scripts/build.sh --configuration Release ``` 3. **Sign and Notarize** ```bash ./scripts/sign-and-notarize.sh --sign-and-notarize ``` 4. **Create DMG** ```bash ./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app ``` 5. **Notarize DMG** ```bash ./scripts/notarize-dmg.sh build/VibeTunnel-X.Y.Z.dmg ``` 6. **Create GitHub Release** ```bash # 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 ``` 7. **Update Appcast** ```bash ./scripts/generate-appcast.sh git add appcast*.xml git commit -m "Update appcast for vX.Y.Z" git push ``` ## Post-Release Steps 1. **Verify Release** - [ ] Check GitHub release page - [ ] Download and test the DMG - [ ] Verify auto-update works from previous version 2. **Clean Up** ```bash # Clean build artifacts (keeps DMG) ./scripts/clean.sh --keep-dmg # Restore development version if needed git checkout -- VibeTunnel/version.xcconfig ``` 3. **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: 1. Ensure correct private key in `private/sparkle_private_key` 2. Regenerate appcast: `./scripts/generate-appcast.sh` 3. Commit and push appcast changes #### Build Number Already Exists Error: "Build number X already exists in appcast" Solution: 1. Increment `CURRENT_PROJECT_VERSION` in `version.xcconfig` 2. Each release must have a unique build number #### Notarization Fails Common causes: - Invalid or expired certificates - Missing API credentials - Network issues Solution: 1. Check credentials: `xcrun notarytool history` 2. Verify certificates: `security find-identity -v` 3. Check console logs for specific errors #### Xcode Project Version Mismatch If build shows wrong version: 1. Ensure Xcode project uses `$(CURRENT_PROJECT_VERSION)` 2. Not hardcoded values 3. Clean and rebuild ### Verification Commands ```bash # 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 1. **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 2. **Timestamp All Code Signatures** - Required for Sparkle components - Use `--timestamp` flag on all codesign operations - Prevents "update isn't properly signed" errors 3. **Version Management** - Use xcconfig for centralized version control - Never hardcode versions in Xcode project - Increment build number for every release 4. **Pre-flight Validation** - Always run pre-flight checks - Ensure clean git state - Verify all credentials before starting 5. **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.sh` to 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 | ` [output_path]` | | `notarize-dmg.sh` | Notarize DMG | `` | | `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: ```bash # 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.