Code Signing Guide for VibeTunnel

This comprehensive guide covers all aspects of code signing for VibeTunnel, from local development setup to release distribution.

Table of Contents

  1. Development Setup
  2. Release Signing & Notarization
  3. Troubleshooting
  4. Reference

Development Setup

Initial Team Configuration

VibeTunnel uses xcconfig files to manage developer team settings, allowing multiple developers to work without code signing conflicts.
  1. Copy the template file to create your local configuration:
    cp ../apple/Local.xcconfig.template ../apple/Local.xcconfig
    
  2. Edit ../apple/Local.xcconfig and add your development team ID:
    DEVELOPMENT_TEAM = YOUR_TEAM_ID_HERE
    
    Finding your team ID in Xcode:
    • Open Xcode → Settings (or Preferences)
    • Go to Accounts tab
    • Select your Apple ID
    • Look for your Team ID in the team details
  3. Open the project in Xcode - it will now use your personal development team automatically.

How xcconfig Works

  • VibeTunnel/Shared.xcconfig - Contains shared configuration and includes local settings
  • ../apple/Local.xcconfig - Your personal settings (ignored by git)
  • ../apple/Local.xcconfig.template - Template for new developers

Avoiding Keychain Dialogs During Development

VibeTunnel stores dashboard passwords in the keychain, which can trigger repeated authorization dialogs during development.

Debug Mode Behavior

In DEBUG builds, the app automatically skips keychain reads to avoid dialogs:
  • Password Setting: You can set passwords during the current session
  • Session Persistence: Passwords work normally until app restart
  • No Persistence: Passwords are “forgotten” on restart (not read from keychain)
  • No Dialogs: Prevents keychain authorization dialogs during development
When setting a password in debug mode, you’ll see:
Debug mode: Password saved to keychain but will not persist across app restarts. 
The password will only be available during this session to avoid keychain 
authorization dialogs during development.

Testing Password Persistence

To test actual password persistence:
  1. Build in Release mode:
    • Product → Scheme → Edit Scheme → Run → Build Configuration → Release
  2. Use Archive build:
    • Product → Archive (always uses Release configuration)

Release Signing & Notarization

Prerequisites

  1. Apple Developer Program membership ($99/year)
  2. Developer ID Application certificate in your Keychain
  3. App Store Connect API key for notarization

Setting Up Developer ID Certificate

  1. Go to Apple Developer Portal
  2. Create a new certificate → Developer ID → Developer ID Application
  3. Download and install the certificate in your Keychain

Environment Variables

Create a .env file in the project root (gitignored):
# Optional: Specify signing identity (otherwise uses first Developer ID found)
SIGN_IDENTITY="Developer ID Application: Your Name (TEAM123456)"

# App Store Connect API Key for notarization
APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
-----END PRIVATE KEY-----"
APP_STORE_CONNECT_KEY_ID="ABC123DEF4"
APP_STORE_CONNECT_ISSUER_ID="12345678-1234-1234-1234-123456789012"

Creating App Store Connect API Key

  1. Go to App Store Connect
  2. Click “Generate API Key”
  3. Set role to “Developer”
  4. Download the .p8 file
  5. Note the Key ID and Issuer ID

Usage

Sign Only (for development)

./scripts/sign-and-notarize.sh --sign-only

Sign and Notarize (for distribution)

./scripts/sign-and-notarize.sh --sign-and-notarize

Individual Scripts

# Just code signing
./scripts/codesign-app.sh build/Build/Products/Release/VibeTunnel.app

# Just notarization (requires signed app)
./scripts/notarize-app.sh build/Build/Products/Release/VibeTunnel.app

Script Options

# Show help
./scripts/sign-and-notarize.sh --help

# Sign and notarize with custom app path
./scripts/sign-and-notarize.sh --app-path path/to/VibeTunnel.app --sign-and-notarize

# Skip stapling (for CI environments)
./scripts/sign-and-notarize.sh --sign-and-notarize --skip-staple

# Don't create ZIP archive
./scripts/sign-and-notarize.sh --sign-and-notarize --no-zip

# Verbose output for debugging
./scripts/sign-and-notarize.sh --sign-and-notarize --verbose

CI/CD Setup (GitHub Actions)

Add these secrets to your GitHub repository:
  1. APP_STORE_CONNECT_API_KEY_P8 - The complete .p8 key content
  2. APP_STORE_CONNECT_KEY_ID - The Key ID
  3. APP_STORE_CONNECT_ISSUER_ID - The Issuer ID
The CI workflow automatically uses these for notarization when building on the main branch.

Troubleshooting

Development Issues

xcconfig Not Working

  • Ensure ../apple/Local.xcconfig exists
  • Check that the file isn’t committed to git
  • Verify the DEVELOPMENT_TEAM value is correct

Keychain Dialogs Still Appearing

  • Verify you’re running in Debug configuration
  • Check DashboardKeychain.swift implementation
  • Ensure you’re not in Release mode

Code Signing Issues

”No signing identity found”

  • Install Developer ID Application certificate
  • Check with: security find-identity -v -p codesigning

”User interaction is not allowed”

  • Unlock keychain: security unlock-keychain
  • Or use: security unlock-keychain -p <password> login.keychain

Notarization Issues

”Invalid API key”

  • Verify API key content, ID, and Issuer ID
  • Ensure .p8 key includes BEGIN/END lines

”App bundle not eligible for notarization”

  • Ensure proper code signing with hardened runtime
  • Check entitlements configuration

”Notarization failed”

  • Script shows detailed error messages
  • Common issues: unsigned binaries, invalid entitlements, prohibited code

Verification Commands

# Verify code signature
codesign --verify --verbose=2 VibeTunnel.app

# Test with Gatekeeper (should pass for notarized apps)
spctl -a -t exec -vv VibeTunnel.app

# Check if notarization ticket is stapled
stapler validate VibeTunnel.app

Reference

Build Configurations

  • Debug builds: Use personal development certificate
  • Release builds: Use Developer ID for distribution
  • CI builds: Use ad-hoc signing

File Structure After Signing

build/
├── Build/Products/Release/VibeTunnel.app  # Signed and notarized app
├── VibeTunnel-notarized.zip               # Distributable archive
└── VibeTunnel-1.0.0.dmg                   # DMG (if created)

Security Notes

  • Never commit signing certificates or API keys
  • Use environment variables or secure CI/CD secrets
  • The .env file is gitignored for security
  • API keys should have minimal permissions (Developer role)

Implementation Details

Debug keychain behavior is in DashboardKeychain.swift:
  • getPassword() returns nil in DEBUG builds
  • setPassword() saves but logs non-persistence
  • hasPassword() works normally

External Resources