mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:35:42 +00:00
* Remove --keychain flag from codesign commands The --keychain flag was causing codesign to look for the private key in the build keychain, but the matching cert+key is in login.keychain. Since we now use the unambiguous SHA-1 hash, codesign will find the correct certificate and key pair in whichever keychain contains them. This fixes the intermittent errSecInternalComponent failures. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Use build keychain specifically for CI codesigning In CI, the workflow imports the signing certificate into a temporary build keychain. Previously, the script searched ALL keychains and picked the first certificate found, which could be an old certificate from login.keychain instead of the freshly imported one. Now when KEYCHAIN_NAME is set (CI environment), the script looks for certificates only in that specific keychain. For local dev (no KEYCHAIN_NAME), it still searches all keychains. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
142 lines
5.6 KiB
Bash
Executable File
142 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Code sign a macOS .app bundle for distribution
|
|
# Usage: codesign_mac_app.sh <app_path> [entitlements_path]
|
|
#
|
|
# Environment variables:
|
|
# SIGNING_IDENTITY - The signing identity (default: "Developer ID Application")
|
|
# KEYCHAIN_PASSWORD - Password to unlock the keychain (optional, CI only)
|
|
# KEYCHAIN_NAME - Name of the keychain containing the signing certificate (optional, CI only)
|
|
# When set, looks for certificate in this specific keychain.
|
|
# When not set, searches all keychains (local dev mode).
|
|
|
|
set -euxo pipefail
|
|
|
|
APP_PATH="$1"
|
|
ENTITLEMENTS_PATH="${2:-}"
|
|
SIGNING_IDENTITY="${SIGNING_IDENTITY:-Developer ID Application}"
|
|
# KEYCHAIN_NAME is set by CI workflow - don't set a default here so we can detect if we're in CI
|
|
|
|
if [ ! -d "$APP_PATH" ]; then
|
|
echo "ERROR: App not found at $APP_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
# Unlock keychain if password and keychain name are provided (CI environment)
|
|
if [ -n "${KEYCHAIN_PASSWORD:-}" ] && [ -n "${KEYCHAIN_NAME:-}" ]; then
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" || true
|
|
fi
|
|
|
|
# Get the SHA-1 hash of the signing certificate
|
|
# Using the hash avoids "ambiguous" errors when the same identity exists in multiple keychains
|
|
# We look in the build keychain specifically if KEYCHAIN_NAME is set (CI environment)
|
|
# Otherwise fall back to searching all keychains (local dev)
|
|
|
|
if [ -n "${KEYCHAIN_NAME:-}" ]; then
|
|
# CI environment: look for certificate in the build keychain specifically
|
|
KEYCHAIN_PATH="$HOME/Library/Keychains/${KEYCHAIN_NAME}-db"
|
|
echo "Looking for signing identity in build keychain: $KEYCHAIN_PATH"
|
|
echo "Available identities in build keychain:"
|
|
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
|
|
|
CERT_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
|
|
else
|
|
# Local dev: search all keychains
|
|
echo "Available codesigning identities:"
|
|
security find-identity -v -p codesigning
|
|
|
|
CERT_HASH=$(security find-identity -v -p codesigning | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
|
|
fi
|
|
|
|
if [ -z "$CERT_HASH" ]; then
|
|
echo "ERROR: No valid signing identity found"
|
|
exit 1
|
|
fi
|
|
echo "Using certificate hash: $CERT_HASH"
|
|
|
|
# Use the hash as the signing identity to avoid ambiguity
|
|
SIGNING_IDENTITY="$CERT_HASH"
|
|
|
|
echo "=== Signing nested components first ==="
|
|
|
|
# Sign all dylibs
|
|
find "$APP_PATH" -name "*.dylib" -print0 | while IFS= read -r -d '' item; do
|
|
echo "Signing dylib: $item"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
done
|
|
|
|
# Sign all bundles (plugins)
|
|
find "$APP_PATH" -name "*.bundle" -print0 | while IFS= read -r -d '' item; do
|
|
echo "Signing bundle: $item"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
done
|
|
|
|
# Sign XPC services (but skip ones inside Sparkle.framework - they're already signed)
|
|
find "$APP_PATH" -name "*.xpc" -print0 | while IFS= read -r -d '' item; do
|
|
if [[ "$item" == *"Sparkle.framework"* ]]; then
|
|
echo "Skipping Sparkle XPC service (pre-signed): $item"
|
|
continue
|
|
fi
|
|
echo "Signing XPC service: $item"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
done
|
|
|
|
# Sign nested apps (but skip ones inside Sparkle.framework - they're already signed)
|
|
find "$APP_PATH" -path "*/Frameworks/*.app" -print0 | while IFS= read -r -d '' item; do
|
|
if [[ "$item" == *"Sparkle.framework"* ]]; then
|
|
echo "Skipping Sparkle nested app (pre-signed): $item"
|
|
continue
|
|
fi
|
|
echo "Signing nested app: $item"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
done
|
|
|
|
# Sign standalone executables inside frameworks (but skip Sparkle.framework internals)
|
|
find "$APP_PATH" -path "*/Frameworks/*/Versions/*/Autoupdate" -type f -print0 | while IFS= read -r -d '' item; do
|
|
if [[ "$item" == *"Sparkle.framework"* ]]; then
|
|
echo "Skipping Sparkle executable (pre-signed): $item"
|
|
continue
|
|
fi
|
|
echo "Signing executable: $item"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
done
|
|
|
|
# Sign all frameworks (after their contents are signed)
|
|
# Use --deep for Sparkle.framework to handle its XPC services
|
|
find "$APP_PATH" -name "*.framework" -print0 | while IFS= read -r -d '' item; do
|
|
if [[ "$item" == *"Sparkle.framework" ]]; then
|
|
echo "Signing Sparkle framework with --deep: $item"
|
|
codesign --deep --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
else
|
|
echo "Signing framework: $item"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$item"
|
|
fi
|
|
done
|
|
|
|
echo "=== Signing main app bundle ==="
|
|
|
|
if [ -n "$ENTITLEMENTS_PATH" ] && [ -f "$ENTITLEMENTS_PATH" ]; then
|
|
echo "Using entitlements: $ENTITLEMENTS_PATH"
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--entitlements "$ENTITLEMENTS_PATH" \
|
|
--sign "$SIGNING_IDENTITY" "$APP_PATH"
|
|
else
|
|
codesign --force --verify --verbose --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$APP_PATH"
|
|
fi
|
|
|
|
echo "=== Verifying signature ==="
|
|
codesign --verify --verbose=4 "$APP_PATH"
|
|
|
|
echo "=== Checking Gatekeeper assessment ==="
|
|
spctl --assess --type exec -v "$APP_PATH" || echo "Note: Gatekeeper may reject until notarized"
|
|
|
|
echo "Code signing complete: $APP_PATH"
|