macOS Build & Notarization
Flamingo uses electron-builder to package and sign macOS distributables (DMG and ZIP). This guide covers the full build pipeline including code signing and notarization for distribution outside the Mac App Store.
Prerequisites
Section titled “Prerequisites”- macOS — building for macOS must be done on macOS
- Xcode — install from the Mac App Store, open once to accept the license
- Apple Developer account — enrolled in the Apple Developer Program ($99/year)
- Xcode Command Line Tools — install with
xcode-select --install
Certificates & Provisioning
Section titled “Certificates & Provisioning”electron-builder automatically finds the appropriate certificates from your system keychain.
Required Certificates
Section titled “Required Certificates”| Certificate | Type | Location |
|---|---|---|
Developer ID Application | Sign the app bundle | Keychain (login) |
Developer ID Installer | Sign the installer DMG | Keychain (login) |
Obtaining Certificates
Section titled “Obtaining Certificates”- Go to developer.apple.com/account → Certificates, IDs & Profiles
- Create a Developer ID Application certificate
- Create a Developer ID Installer certificate (if building DMG)
- Download and double-click both to install in Keychain
Configuration
Section titled “Configuration”The base macOS build config lives in client/package.json:
{ "build": { "mac": { "target": ["dmg", "zip"], "category": "public.app-category.utilities", "icon": "assets/icon.icns" } }}Adding Code Signing
Section titled “Adding Code Signing”electron-builder signs macOS builds automatically when valid Developer ID Application certificates are found. To pin a specific certificate identity:
{ "mac": { "target": ["dmg", "zip"], "category": "public.app-category.utilities", "icon": "assets/icon.icns", "identity": "Developer ID Application: Your Name (TEAMID)" }}Hardened Runtime & Entitlements
Section titled “Hardened Runtime & Entitlements”macOS requires the Hardened Runtime for notarization. Create client/build/entitlements.mac.plist:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> <key>com.apple.security.cs.disable-library-validation</key> <true/></dict></plist>Update package.json to reference the entitlements:
{ "mac": { "target": ["dmg", "zip"], "category": "public.app-category.utilities", "icon": "assets/icon.icns", "hardenedRuntime": true, "entitlements": "build/entitlements.mac.plist", "entitlementsInherit": "build/entitlements.mac.plist" }}Notarization
Section titled “Notarization”Notarization submits your app to Apple for security scanning.
Option A: Apple ID (simpler)
Section titled “Option A: Apple ID (simpler)”Set environment variables:
export APPLE_ID="your@appleid.com"export APPLE_ID_PASSWORD="@keychain:AC_PASSWORD"Store the app-specific password in Keychain:
xcrun notarytool store-credentials "AC_PASSWORD" --apple-id "your@appleid.com" --team-id "TEAMID" --password "app-specific-password"Option B: API Key (CI-friendly)
Section titled “Option B: API Key (CI-friendly)”Generate an API key at developer.apple.com/account → Certificates, IDs & Profiles → Keys.
export APPLE_API_KEY="path/to/AuthKey_XXXXX.p8"export APPLE_API_KEY_ID="XXXXX"export APPLE_API_ISSUER="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Install Notarization CLI
Section titled “Install Notarization CLI”electron-builder needs the @electron/notarize package:
npm install --save-dev @electron/notarizeafterSign Hook
Section titled “afterSign Hook”Create client/build/notarize.js:
const { notarize } = require('@electron/notarize')
exports.default = async function notarizing(context) { const { electronPlatformName, appOutDir } = context if (electronPlatformName !== 'darwin') return
const appName = context.packager.appInfo.productFilename
return await notarize({ appBundleId: 'com.flamingo-client.desktop', appPath: `${appOutDir}/${appName}.app`, appleId: process.env.APPLE_ID, appleIdPassword: process.env.APPLE_ID_PASSWORD, teamId: process.env.APPLE_TEAM_ID, })}Register the hook in package.json:
{ "build": { "afterSign": "build/notarize.js" }}Building
Section titled “Building”Standard Build
Section titled “Standard Build”npm run electron:macOutput appears in client/dist-electron/:
dist-electron/├── Flamingo-0.1.0.dmg├── Flamingo-0.1.0-mac.zip└── mac/ └── Flamingo.appPlatform-Specific Scripts
Section titled “Platform-Specific Scripts”| Script | Command | Output |
|---|---|---|
| macOS | npm run electron:mac | DMG + ZIP |
| Windows | npm run electron:win | NSIS installer |
| Linux | npm run electron:linux | AppImage + deb + rpm |
| All | npm run electron:all | Windows + Linux |
Verifying Notarization
Section titled “Verifying Notarization”After a successful build, verify the notarization status:
spctl --assess --verbose /path/to/Flamingo.appLook for accepted in the output.
Troubleshooting
Section titled “Troubleshooting””No matching provisioning profiles found”
Section titled “”No matching provisioning profiles found””Ensure a Developer ID Application certificate exists in your login keychain. This is not the same as a development certificate.
Notarization upload fails
Section titled “Notarization upload fails”- Check that the app is fully signed:
codesign -dvvv /path/to/Flamingo.app - Verify the hardened runtime is enabled
- Ensure the app version has increased since the last submission (Apple caches by version)
“The executable is not signed with a trusted timestamp”
Section titled ““The executable is not signed with a trusted timestamp””Add the --timestamp option by ensuring electron-builder is configured to include timestamps (default in recent versions).
electron-builder cannot find the certificate
Section titled “electron-builder cannot find the certificate”List available identities:
security find-identity -v -p basicPass the identity name explicitly in the mac.identity config field.
CI/CD Integration
Section titled “CI/CD Integration”For GitHub Actions, add the certificate (base64-encoded) as a repository secret and import it before building:
- name: Import certificate run: | echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12 security create-keychain -p temp temp.keychain security default-keychain -s temp.keychain security unlock-keychain -p temp temp.keychain security import certificate.p12 -k temp.keychain -P "$CERT_PASSWORD" -A security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k temp temp.keychainSee the electron-builder CI docs for detailed guidance.