Files
Gaze/DEPLOYMENT.md
2026-01-11 17:45:32 -05:00

7.5 KiB

Gaze Release Deployment Process

Overview

This document describes the complete process for building and deploying new releases of Gaze with Sparkle auto-update support.

Prerequisites

  • Xcode with Gaze project configured
  • create-dmg tool installed (brew install create-dmg)
  • Sparkle EdDSA signing keys in macOS Keychain (see Key Management below)
  • AWS credentials configured in .env file for S3 upload
  • Access to freno.me hosting infrastructure

Version Management

Version numbers are managed in Xcode project settings:

  • Marketing Version (MARKETING_VERSION): User-facing version (e.g., "0.1.1")
  • Build Number (CURRENT_PROJECT_VERSION): Internal build number (e.g., "1")

These must be incremented before each release and kept in sync with build_dmg script.

Release Checklist

1. Prepare Release

  • Update version numbers in Xcode:
    • Project Settings → General → Identity
    • Set Marketing Version (e.g., "0.1.2")
    • Increment Build Number (e.g., "2")
  • Update VERSION and BUILD_NUMBER in build_dmg script
  • Update CHANGELOG or release notes
  • Commit version changes: git commit -am "Bump version to X.Y.Z"
  • Create git tag: git tag v0.1.2

2. Build Application

# Build the app in Xcode (Product → Archive → Export)
# Or use the run script
./run build

# Verify the app runs correctly
open Gaze.app

3. Create DMG and Appcast

# Run the build_dmg script
./build_dmg

# This will:
# - Create versioned DMG file
# - Generate appcast.xml with EdDSA signature
# - Upload to S3 if AWS credentials are configured
# - Display next steps

4. Verify Artifacts

Check that the following files were created in ./releases/:

  • Gaze-X.Y.Z.dmg - Installable disk image
  • appcast.xml - Update feed with signature
  • Gaze-X.Y.Z.delta (optional) - Delta update from previous version

5. Upload to Hosting (if not using S3 auto-upload)

DMG File:

# Upload to: https://freno.me/downloads/
scp ./releases/Gaze-X.Y.Z.dmg your-server:/path/to/downloads/

Appcast File:

# Upload to: https://freno.me/api/Gaze/
scp ./releases/appcast.xml your-server:/path/to/api/Gaze/

6. Verify Deployment

Test that files are accessible via HTTPS:

# Test appcast accessibility
curl -I https://freno.me/api/Gaze/appcast.xml
# Should return: HTTP/2 200, content-type: application/xml

# Test DMG accessibility
curl -I https://freno.me/downloads/Gaze-X.Y.Z.dmg
# Should return: HTTP/2 200, content-type: application/octet-stream

# Validate appcast XML structure
curl https://freno.me/api/Gaze/appcast.xml | xmllint --format -

7. Test Update Flow

Manual Testing:

  1. Install previous version of Gaze
  2. Launch app and check Settings → General → Software Updates
  3. Click "Check for Updates Now"
  4. Verify update notification appears
  5. Complete update installation
  6. Verify new version launches correctly

Automatic Update Testing:

  1. Set SUScheduledCheckInterval to a low value (e.g., 60 seconds) for testing
  2. Install previous version
  3. Wait for automatic check
  4. Verify update notification appears

8. Finalize Release

  • Push git tag: git push origin v0.1.2
  • Create GitHub release (optional)
  • Announce release to users
  • Monitor for update errors in the first 24 hours

Hosting Configuration

Current Setup

  • Appcast URL: https://freno.me/api/Gaze/appcast.xml
  • Download URL: https://freno.me/downloads/Gaze-{VERSION}.dmg
  • Hosting: AWS S3 with freno.me domain
  • SSL: HTTPS enabled (required by App Transport Security)

AWS S3 Configuration

Create a .env file in the project root with:

AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_BUCKET_NAME=your_bucket_name
AWS_REGION=us-east-1

Note: .env is gitignored to protect credentials.

S3 Bucket Structure

your-bucket/
├── downloads/
│   ├── Gaze-0.1.1.dmg
│   ├── Gaze-0.1.2.dmg
│   └── ...
└── api/
    └── Gaze/
        └── appcast.xml

Key Management

EdDSA Signing Keys

Location:

  • Public Key: In Gaze/Info.plist as SUPublicEDKey
  • Private Key: In macOS Keychain as "Sparkle EdDSA Private Key"
  • Backup: ~/sparkle_private_key_backup.pem (keep secure!)

Current Public Key:

Z2RmohI1y2bgeGQQUDqO9F0HNF2AzFotOt8CwGB6VJM=

Security:

  • Never commit private key to version control
  • Keep backup in secure location (password manager, encrypted drive)
  • Private key is required to sign all future updates

Regenerating Keys (Emergency Only)

If private key is lost, you must:

  1. Generate new key pair: ./generate_keys
  2. Update SUPublicEDKey in Info.plist
  3. Release new version with new public key
  4. Previous versions won't be able to update (users must manually install)

Troubleshooting

Appcast Generation Fails

Problem: generate_appcast tool not found

Solution:

# Build the app first to generate Sparkle tools
xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Release

# Find Sparkle tools
find ~/Library/Developer/Xcode/DerivedData/Gaze-* -name "generate_appcast"

Update Check Fails in App

Problem: No updates found or connection error

Diagnostics:

# Check Console.app for Sparkle logs
# Filter by process: Gaze
# Look for:
# - "Downloading appcast..."
# - "Appcast downloaded successfully"
# - Connection errors
# - Signature verification errors

Common Issues:

  • Appcast URL not accessible (check HTTPS)
  • Signature mismatch (wrong private key used)
  • XML malformed (validate with xmllint)
  • Version number not higher than current version

DMG Not Downloading

Problem: Update found but download fails

Check:

  • DMG URL is correct in appcast
  • DMG file is accessible via HTTPS
  • File size in appcast matches actual DMG size
  • No CORS issues (check browser console)

Delta Updates

Sparkle automatically generates delta updates when multiple versions exist in ./releases/:

# Keep previous versions for delta generation
releases/
├── Gaze-0.1.1.dmg
├── Gaze-0.1.2.dmg
├── Gaze-0.1.1-to-0.1.2.delta  # Generated automatically
└── appcast.xml

Benefits:

  • Much smaller downloads (MB vs GB)
  • Faster updates for users
  • Generated automatically by generate_appcast

Note: First-time users still download full DMG.

Testing with Local Appcast

For testing without deploying:

  1. Modify Info.plist temporarily:
<key>SUFeedURL</key>
<string>file:///Users/mike/Code/Gaze/releases/appcast.xml</string>
  1. Build and run app
  2. Check for updates
  3. Revert Info.plist before committing

Release Notes

Release notes are embedded in appcast XML as CDATA:

<description><![CDATA[
    <h2>What's New in Version X.Y.Z</h2>
    <ul>
        <li>Feature 1</li>
        <li>Bug fix 2</li>
    </ul>
]]></description>

Tips:

  • Use simple HTML (h2, ul, li, p, strong, em)
  • No external resources (images, CSS, JS)
  • Keep concise and user-focused
  • Highlight breaking changes

References

Support

For issues with deployment:

  1. Check Console.app for Sparkle errors
  2. Verify appcast validation with xmllint
  3. Test with file:// URL first
  4. Check AWS S3 permissions and CORS