This guide covers code signing, notarization, and distribution of the Quip Node Manager Tauri app on macOS.
- Apple Developer Program membership ($99/year) -- developer.apple.com/programs
- Xcode CLI tools installed:
xcode-select --install - An Apple ID enrolled in the Developer Program
- An app-specific password generated at
appleid.apple.com (under Sign-In and Security
App-Specific Passwords)
- Open developer.apple.com/account/resources/certificates.
- Click the + button to create a new certificate.
- Select Developer ID Application (for distributing outside the App Store).
- Generate a Certificate Signing Request (CSR) using Keychain Access:
- Open Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority.
- Enter your email, select "Saved to disk", and save the
.certSigningRequestfile.
- Upload the CSR and download the resulting
.cerfile. - Double-click the
.cerfile to install it into your login keychain. - Verify installation:
security find-identity -v -p codesigning
# Should list: "Developer ID Application: TEAM_NAME (TEAM_ID)"Build the Tauri app for release:
cd /path/to/quip-node-manager
bun run buildThe built .app bundle is located at:
src-tauri/target/release/bundle/macos/Quip Node Manager.app
Sign the .app bundle with your Developer ID certificate:
codesign \
--deep \
--force \
--verify \
--verbose \
--sign "Developer ID Application: TEAM_NAME (TEAM_ID)" \
"src-tauri/target/release/bundle/macos/Quip Node Manager.app"Verify the signature:
codesign --verify --deep --strict --verbose=2 \
"src-tauri/target/release/bundle/macos/Quip Node Manager.app"
spctl --assess --type execute --verbose \
"src-tauri/target/release/bundle/macos/Quip Node Manager.app"Package the signed app into a .dmg for distribution:
hdiutil create -volname "Quip Node Manager" \
-srcfolder "src-tauri/target/release/bundle/macos/Quip Node Manager.app" \
-ov -format UDZO \
"Quip-Node-Manager.dmg"Sign the DMG itself:
codesign \
--force \
--sign "Developer ID Application: TEAM_NAME (TEAM_ID)" \
"Quip-Node-Manager.dmg"Submit the DMG to Apple for notarization:
xcrun notarytool submit "Quip-Node-Manager.dmg" \
--apple-id "your-email@example.com" \
--team-id "TEAM_ID" \
--password "APP_SPECIFIC_PASSWORD" \
--waitThe --wait flag blocks until notarization completes (typically 2--15 minutes).
Check notarization status if needed:
xcrun notarytool log <submission-id> \
--apple-id "your-email@example.com" \
--team-id "TEAM_ID" \
--password "APP_SPECIFIC_PASSWORD"Attach the notarization ticket to the DMG so Gatekeeper can verify it offline:
xcrun stapler staple "Quip-Node-Manager.dmg"Verify stapling:
xcrun stapler validate "Quip-Node-Manager.dmg"Add signing identity settings to src-tauri/tauri.conf.json:
{
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: TEAM_NAME (TEAM_ID)",
"providerShortName": "TEAM_ID"
}
}
}With this configuration, bun run build will automatically sign the app
bundle during the build process.
To produce a single binary that runs natively on both Apple Silicon and Intel Macs:
- Add both Rust targets:
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin- Build for each architecture:
cd src-tauri
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin- Combine with
lipo:
lipo -create \
target/aarch64-apple-darwin/release/quip-node-manager \
target/x86_64-apple-darwin/release/quip-node-manager \
-output target/release/quip-node-manager-universal- Re-bundle and sign the universal binary using the steps above.
To sign builds in CI, import the signing certificate into a temporary keychain on the macOS runner:
build-macos:
tags: [macos]
variables:
KEYCHAIN_NAME: build.keychain
KEYCHAIN_PASSWORD: $CI_KEYCHAIN_PASSWORD
before_script:
# Decode the base64-encoded .p12 certificate from CI variable
- echo "$MACOS_CERTIFICATE_P12" | base64 --decode > certificate.p12
# Create a temporary keychain
- security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
- security default-keychain -s "$KEYCHAIN_NAME"
- security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
- security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME"
# Import the certificate
- >
security import certificate.p12
-k "$KEYCHAIN_NAME"
-P "$MACOS_CERTIFICATE_PASSWORD"
-T /usr/bin/codesign
-T /usr/bin/security
# Allow codesign to access the keychain without prompting
- >
security set-key-partition-list
-S apple-tool:,apple:
-s -k "$KEYCHAIN_PASSWORD"
"$KEYCHAIN_NAME"
# Verify the identity is available
- security find-identity -v -p codesigning "$KEYCHAIN_NAME"
script:
- bun install
- bun run build
# Notarize
- >
xcrun notarytool submit
"src-tauri/target/release/bundle/dmg/Quip Node Manager.dmg"
--apple-id "$APPLE_ID"
--team-id "$APPLE_TEAM_ID"
--password "$APPLE_APP_SPECIFIC_PASSWORD"
--wait
# Staple
- >
xcrun stapler staple
"src-tauri/target/release/bundle/dmg/Quip Node Manager.dmg"
after_script:
# Clean up the temporary keychain
- security delete-keychain "$KEYCHAIN_NAME"
- rm -f certificate.p12
artifacts:
paths:
- src-tauri/target/release/bundle/dmg/*.dmg
expire_in: 30 days| Variable | Description |
|---|---|
MACOS_CERTIFICATE_P12 |
Base64-encoded .p12 export of the Developer ID certificate |
MACOS_CERTIFICATE_PASSWORD |
Password for the .p12 file |
CI_KEYCHAIN_PASSWORD |
Arbitrary password for the temporary CI keychain |
APPLE_ID |
Apple ID email address |
APPLE_TEAM_ID |
10-character Team ID from Apple Developer portal |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password for notarytool |
Store all of these as masked, protected CI/CD variables.