This document explains how to build, sign, notarize, and distribute Geisterhand.
# Build and create unsigned app bundle
make app
# Create unsigned DMG for testing
make dmg-unsigned# Set credentials
export DEVELOPER_ID="Developer ID Application: Your Name (TEAMID)"
export APPLE_ID="your@email.com"
export APPLE_TEAM_ID="TEAMID"
export NOTARIZE_PASSWORD="xxxx-xxxx-xxxx-xxxx" # App-specific password
# Run full release pipeline
make release- Enroll in the Apple Developer Program ($99/year)
- Create a Developer ID Application certificate in Certificates, Identifiers & Profiles
- Download and install the certificate in your Keychain
For notarization, create an app-specific password:
- Go to appleid.apple.com
- Sign in and go to Security > App-Specific Passwords
- Generate a new password for "Geisterhand Notarization"
Optionally, store it in Keychain for convenience:
xcrun notarytool store-credentials "AC_PASSWORD" \
--apple-id "your@email.com" \
--team-id "TEAMID" \
--password "xxxx-xxxx-xxxx-xxxx"Then use NOTARIZE_PASSWORD="@keychain:AC_PASSWORD" in the Makefile.
| Target | Description |
|---|---|
make build |
Build release binaries with Swift |
make app |
Create app bundle structure |
make sign |
Code sign with Developer ID |
make dmg |
Create DMG installer |
make notarize |
Submit to Apple for notarization |
make staple |
Staple notarization ticket to DMG |
make release |
Full pipeline (sign → dmg → notarize → staple) |
make clean |
Remove build artifacts |
make verify |
Verify signatures and Gatekeeper assessment |
make list-identities |
Show available signing identities |
make dmg-unsigned |
Create unsigned DMG for testing |
The release workflow (.github/workflows/release.yml) automates the entire process.
Configure these in your repository settings (Settings > Secrets and variables > Actions):
| Secret | Description |
|---|---|
DEVELOPER_CERTIFICATE_BASE64 |
Base64-encoded .p12 certificate |
DEVELOPER_CERTIFICATE_PASSWORD |
Password for the .p12 file |
KEYCHAIN_PASSWORD |
Temporary password for CI keychain |
DEVELOPER_ID |
Full signing identity string |
APPLE_ID |
Apple ID email |
APPLE_TEAM_ID |
Team ID (10-character string) |
NOTARIZE_PASSWORD |
App-specific password |
# Find your certificate
security find-identity -v -p codesigning
# Export from Keychain Access:
# 1. Open Keychain Access
# 2. Find "Developer ID Application: Your Name"
# 3. Right-click > Export
# 4. Save as .p12 with a password
# Convert to base64 for GitHub secrets
base64 -i certificate.p12 | pbcopy
# Paste into DEVELOPER_CERTIFICATE_BASE64 secret# Tag and push
git tag v1.0.0
git push origin v1.0.0The workflow will:
- Build the release binary
- Create and sign the app bundle
- Create a DMG
- Notarize with Apple
- Create a GitHub Release with the DMG
-
Create a new repository:
homebrew-geisterhand -
Add the cask formula:
homebrew-geisterhand/ └── Casks/ └── geisterhand.rb -
Copy
homebrew/geisterhand.rbto your tap and update:- The SHA256 hash (from GitHub Actions output or
shasum -a 256 Geisterhand-1.0.0.dmg) - The GitHub repository URL
- The SHA256 hash (from GitHub Actions output or
-
Optionally add the source formula:
homebrew-geisterhand/ ├── Casks/ │ └── geisterhand.rb └── Formula/ └── geisterhand.rb
After each release:
-
Get the SHA256 from the GitHub Actions log or calculate it:
shasum -a 256 Geisterhand-X.Y.Z.dmg
-
Update the cask formula:
version "X.Y.Z" sha256 "new_sha256_hash"
-
Commit and push to your tap repository
# First time
brew tap geisterhand-io/tap
brew install --cask geisterhand
# Or one command
brew install --cask geisterhand-io/tap/geisterhand
# CLI only (compiles from source)
brew install geisterhand-io/tap/geisterhandcodesign --verify --deep --strict --verbose=2 Geisterhand.appspctl --assess --verbose=4 --type execute Geisterhand.app
spctl --assess --verbose=4 --type install Geisterhand-1.0.0.dmgxcrun stapler validate Geisterhand-1.0.0.dmgThe app hasn't been notarized or stapled properly. Run:
make notarize stapleList available identities:
make list-identitiesEnsure your Developer ID Application certificate is installed in Keychain.
Check the submission status:
xcrun notarytool log <submission-id> \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$NOTARIZE_PASSWORD"Common issues:
- Hardened runtime not enabled (fixed in entitlements)
- Unsigned nested code (fixed by signing each binary)
- Missing entitlements
Verify your secrets are correctly set:
- Certificate must be base64-encoded
- DEVELOPER_ID must match the certificate exactly
- Keychain password can be any strong password