Manual macOS Signing Instructions

Use this guide when you need direct control over signing, notarization, or debugging a signing failure.

For the higher-level overview, see the Signing Guide.


Prerequisites

  • Xcode command-line tools (xcode-select --install)
  • Apple Developer account with the required permissions
  • A Developer ID Application certificate installed in Keychain (or provided as a .p12)
  • Chatons build artifacts already produced locally

Verify Signing Identities

security find-identity -v -p codesigning

1. Build Chatons

npm run build
npx electron-builder --mac dmg --publish never

This produces artifacts under release/:

  • release/mac/Chatons.app (app bundle)
  • release/Chatons-latest-arm64.dmg (or similar, depending on architecture)

2. Sign the App Bundle

Replace the identity with your actual certificate name:

SIGNING_IDENTITY="Developer ID Application: Your Name (ABCDE12345)"

codesign --deep --force --verify --verbose \
  --options runtime \
  --entitlements build/entitlements.mac.plist \
  --sign "$SIGNING_IDENTITY" \
  release/mac/Chatons.app

Notes:

  • --deep is needed because Electron bundles contain nested binaries and frameworks
  • --options runtime enables hardened runtime (required for notarization)
  • The entitlements file must match build/entitlements.mac.plist

Verify the Signature

codesign --verify --deep --strict release/mac/Chatons.app
spctl -a -t exec -vv release/mac/Chatons.app

3. Recreate the DMG

After signing the app, rebuild the DMG:

# Remove the old DMG if needed
rm -f release/Chatons-*.dmg

# Rebuild
npx electron-builder --mac dmg --publish never \
  --config.mac.identity="$SIGNING_IDENTITY"

The default DMG naming pattern is ${productName}-latest-${arch}.${ext} (e.g. Chatons-latest-arm64.dmg). Semver does not appear in the filename by default.


4. Notarize (Optional)

If you need to notarize outside the CI workflow:

Submit for Notarization

xcrun notarytool submit release/Chatons-latest-arm64.dmg \
  --apple-id "your@email.com" \
  --password "your-app-specific-password" \
  --team-id "ABCDE12345" \
  --wait

Or using the older altool flow:

xcrun altool --notarize-app \
  --primary-bundle-id "com.thibaut.chaton" \
  --username "your@email.com" \
  --password "your-app-specific-password" \
  --file release/Chatons-latest-arm64.dmg

Check Status (altool)

xcrun altool --notarization-info UUID \
  -u "your@email.com" \
  -p "your-app-specific-password"

Staple the Ticket

xcrun stapler staple release/Chatons-latest-arm64.dmg

The CI workflow includes retry logic for stapling because notarization tickets may not propagate immediately.


5. Final Verification

# Verify the signed DMG
spctl -a -v -t install release/Chatons-latest-arm64.dmg

# Verify the app inside the DMG
# (mount the DMG first, then verify the enclosed .app)
codesign --verify --deep --strict /Volumes/Chatons/Chatons.app

# Verify stapler ticket
xcrun stapler validate release/Chatons-latest-arm64.dmg

Common Failures

code object is not signed at all

One or more nested binaries were not signed. Fix:

  • Re-run codesign with --deep
  • Check nested frameworks and helper apps inside the Electron bundle

resource fork, Finder information, or similar detritus not allowed

Clear extended attributes:

xattr -cr release/mac/Chatons.app

Entitlements problems

Verify you are using the correct entitlements file:

codesign -d --entitlements - release/mac/Chatons.app

Compare against build/entitlements.mac.plist.

Notarization failure

Check:

  • Apple ID credentials are correct
  • Using an app-specific password (not your account password)
  • Bundle ID matches com.thibaut.chaton
  • App was properly signed before submission

When to Prefer the CI Flow

Manual signing is useful for debugging, but the automated workflow is safer for real releases. It handles:

  • Consistent artifact production
  • Certificate import into temporary keychain
  • Post-build validation
  • Notarization checks and stapling retries
  • Version stamping

On this page