Logue Release Build Guide
July 19, 2026 · View on GitHub
Complete guide to set up code signing, notarization, and build a distributable DMG.
Prerequisites
- macOS with Apple Silicon
- Xcode installed (with macOS 26 SDK)
- Apple Developer Program membership ($99/year)
1. Install Build Tools
brew install xcodegen create-dmg
xcodebuild -downloadComponent MetalToolchain
2. Create a Developer ID Certificate
You need a Developer ID Application certificate to sign apps distributed outside the Mac App Store.
2a. Generate a Certificate Signing Request (CSR)
- Open Keychain Access (Applications > Utilities)
- Menu: Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority
- Fill in:
- User Email Address: your Apple ID email
- Common Name: your name or company name
- Request is: select Saved to disk
- Click Continue and save the
.certSigningRequestfile
2b. Create the Certificate on Apple Developer Portal
- Go to Apple Developer > Certificates
- Click the + button
- Under Software, select Developer ID Application
- Click Continue
- Upload the
.certSigningRequestfile from step 2a - Click Continue, then Download the
.cerfile
2c. Install the Certificate
- Double-click the downloaded
.cerfile — it opens in Keychain Access - It will be installed in your login keychain
2d. Verify Installation
security find-identity -v -p codesigning | grep "Developer ID"
You should see output like:
1) ABCDEF123456... "Developer ID Application: Your Name (XXXXXXXXXX)"
The 10-character code in parentheses is your Team ID.
3. Set Up Notarization Credentials
Apple notarization requires an app-specific password (not your regular Apple ID password).
3a. Create an App-Specific Password
- Go to appleid.apple.com
- Sign in, go to Sign-In and Security > App-Specific Passwords
- Click Generate an app-specific password
- Label it
Logue Notarize - Copy the generated password (format:
xxxx-xxxx-xxxx-xxxx)
3b. Store Credentials in Keychain
xcrun notarytool store-credentials "Logue-Notarize" \
--apple-id "your@email.com" \
--team-id "XXXXXXXXXX" \
--password "xxxx-xxxx-xxxx-xxxx"
Replace:
your@email.comwith your Apple IDXXXXXXXXXXwith your Team ID from step 2dxxxx-xxxx-xxxx-xxxxwith the app-specific password from step 3a
3c. Verify Stored Credentials
xcrun notarytool history --keychain-profile "Logue-Notarize"
This should authenticate successfully (empty history is fine for a new account).
4. Build the Release
Quick Test (skip notarization)
./scripts/build_release.sh --skip-notarize
Full Release Build
./scripts/build_release.sh
With Explicit Options
./scripts/build_release.sh \
--version 1.0.0 \
--build 1 \
--team-id XXXXXXXXXX \
--keychain-profile "Logue-Notarize"
Script Options
| Flag | Description | Default |
|---|---|---|
--version X.Y.Z | App version number | Read from project.yml |
--build N | Build number | Read from project.yml |
--team-id XXXXXXXXXX | Apple Team ID | Auto-detected from Keychain |
--keychain-profile NAME | Notarization credential profile | Logue-Notarize |
--skip-notarize | Skip notarization step | Off |
5. Build Output
After a successful build, you'll find:
build/
Logue.xcarchive # Xcode archive
export/
Logue.app # Signed app bundle
Logue-1.0.0.dmg # Final distributable DMG (signed + notarized)
6. What the Build Script Does
- Checks prerequisites (xcodegen, create-dmg, certificate)
- Generates Xcode project from
project.yml - Resolves Swift Package Manager dependencies
- Archives the app (Release, arm64)
- Exports the archive with Developer ID signing
- Deep-signs the app bundle with hardened runtime + entitlements
- Creates a styled DMG (drag-to-Applications installer)
- Signs the DMG
- Submits to Apple for notarization and waits for approval
- Staples the notarization ticket to the DMG
7. CI/CD (GitHub Actions)
The release workflow at .github/workflows/release.yml automates the full pipeline. Push a version tag to trigger it:
git tag v1.0.0
git push origin v1.0.0
Required GitHub Secrets
Set these in Settings > Secrets and variables > Actions:
| Secret | How to Get It |
|---|---|
APPLE_CERTIFICATE_BASE64 | Export cert from Keychain as .p12, then base64 -i cert.p12 | pbcopy |
APPLE_CERTIFICATE_PASSWORD | Password you set when exporting the .p12 |
APPLE_TEAM_ID | 10-char Team ID from step 2d |
APPLE_ID | Your Apple ID email |
APPLE_APP_PASSWORD | App-specific password from step 3a |
Export Certificate as .p12 for CI
- Open Keychain Access
- Find your Developer ID Application certificate
- Expand it to see the private key
- Select both the certificate and the private key
- Right-click > Export 2 items...
- Save as
.p12, set a strong password - Base64-encode it:
base64 -i certificate.p12 | pbcopy
- Paste as the
APPLE_CERTIFICATE_BASE64secret in GitHub
8. Sparkle Auto-Update Setup
Logue uses Sparkle 2 for automatic updates. Sparkle checks an appcast.xml feed, downloads the update ZIP, verifies its EdDSA signature, and installs it — all with built-in native UI.
How It Works
- CI builds and codesigns the app, creates a ZIP
- CI signs the ZIP with an EdDSA private key (Sparkle's
sign_update) - CI publishes the ZIP + DMG to GitHub Releases and updates
appcast.xmlin the repo - The app reads the appcast from
raw.githubusercontent.comand downloads ZIPs straight from GitHub Releases — no backend - Sparkle in the app downloads the update and prompts the user to restart
Generate EdDSA Key Pair (One-Time)
Download Sparkle and run the key generator:
# Download Sparkle release
SPARKLE_VERSION="2.9.1"
curl -L -o /tmp/sparkle.tar.xz \
"https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
mkdir -p /tmp/sparkle-tools
tar xf /tmp/sparkle.tar.xz -C /tmp/sparkle-tools
# Generate key pair
/tmp/sparkle-tools/bin/generate_keys
This prints:
A] Public key to embed in your app's Info.plist as SUPublicEDKey:
<base64 public key>
B] Private key saved to ~/Library/Sparkle/ed25519.key
Back up this file! You'll need it to sign future updates.
Configure the Keys
⚠️ Never commit the private key. This is a public repo. Keep the private key in your password manager /
~/Library/Sparkle/ed25519.keybackup only.
- Public key goes in
project.yml(SUPublicEDKey) — embedded inInfo.plistonxcodegen generate. - Private key must be added as a GitHub Actions secret:
- Go to repo Settings > Secrets and variables > Actions
- Create secret
SPARKLE_PRIVATE_KEYwith the private key value.
- If keys are regenerated, update both
project.yml(SUPublicEDKey) and the GitHub secret.
Required GitHub Secrets (Updated)
| Secret | Description |
|---|---|
APPLE_CERTIFICATE_BASE64 | Developer ID cert (see section 7) |
APPLE_CERTIFICATE_PASSWORD | Password for the .p12 |
APPLE_TEAM_ID | 10-char Apple Team ID |
APPLE_ID | Apple ID email |
APPLE_APP_PASSWORD | App-specific password for notarization |
SPARKLE_PRIVATE_KEY | EdDSA private key from generate_keys |
Testing the Update Flow Locally
- Build the app with a low version (e.g.
0.0.1) - Create a test
appcast.xmlwith a higher version pointing to a local/remote ZIP - Set
SUFeedURLto point to your test appcast (via env override or debug build) - Launch the app — Sparkle will detect the "update" and show its install dialog
Appcast Management
The appcast.xml in the repo root is updated automatically by CI on every release. To manually add an entry:
python3 scripts/update_appcast.py \
--version 1.2.0 \
--build 42 \
--signature "BASE64_SIGNATURE" \
--length 12345678 \
--min-os 26.0 \
--notes "Bug fixes and performance improvements"
Troubleshooting
"No Developer ID Application certificate found"
Your certificate isn't installed. Follow step 2 above.
Notarization fails with "invalid credentials"
Re-run xcrun notarytool store-credentials with the correct app-specific password. Regular Apple ID passwords don't work.
Build fails with "Unable to find module dependency"
Clean the package cache and retry:
rm -rf build/SourcePackages
./scripts/build_release.sh --skip-notarize
DMG icon positioning looks off
Add a custom background image at scripts/dmg_background.png (660x400px). Without it, the DMG uses the default macOS style.
Sparkle: "Update signature is invalid"
The EdDSA signature doesn't match the public key in the app. Ensure:
SUPublicEDKeyinproject.ymlmatches the private key used to sign- The
SPARKLE_PRIVATE_KEYGitHub secret is correct and not truncated - You haven't regenerated keys without updating both the secret and Info.plist
Sparkle: No update prompt appears
- Verify
SUFeedURLis reachable (trycurl https://raw.githubusercontent.com/bitwize-ai/Logue/main/appcast.xml) - Check the appcast has an
<item>with a version higher than the running app - Ensure the enclosure
urlpoints at an existing GitHub Release asset - Sparkle only checks once per session by default — restart the app to re-check