Files
OrcaSlicer-KX/scripts/macos_codesign_local.sh
Walter Almada B 6cc082f02e fix(macos): stable local codesign to stop repeated permission prompts
Local arm64 builds are only ad-hoc/linker-signed, so macOS identifies the
app by its cdhash. That hash changes on every rebuild, invalidating TCC
(folder access) grants and Keychain ACLs, so macOS re-prompts for folder
access and the keychain password on every launch/redeploy.

Add scripts/macos_codesign_local.sh: creates a stable, machine-local,
self-signed code-signing identity in the user's login keychain (idempotent,
no admin password) and re-signs the app top-level. The Designated Requirement
becomes certificate-based ("... and certificate leaf = H\"...\"") instead of
cdhash, so it is identical across rebuilds and permissions granted once
persist. Document the workflow in BUILD_MAC_KX.md.

The certificate/private key are per-machine and never leave the login
keychain; only the script is shared.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 10:05:49 -07:00

129 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# macos_codesign_local.sh — stop macOS from re-prompting for folder (TCC) and
# Keychain access on every launch/rebuild of a locally-built OrcaSlicer.
#
# WHY THIS IS NEEDED
# On Apple Silicon the linker automatically applies an *ad-hoc* code signature
# to every binary. macOS then identifies the app by the exact hash (cdhash) of
# its contents, so its "Designated Requirement" looks like:
# designated => cdhash H"...."
# macOS binds TCC (folder access) grants and Keychain ACLs to that requirement.
# Because the cdhash changes on every rebuild / redeploy, macOS treats each new
# build as a brand-new unknown app and asks again for folder access and the
# keychain password. Permissions never persist.
#
# WHAT THIS DOES
# Signs the app with a STABLE, self-signed, machine-local code-signing
# certificate. The Designated Requirement then becomes certificate-based:
# designated => identifier "com.orcaslicer.OrcaSlicer" and certificate leaf = H"...."
# which is identical across every rebuild. Grant folder access + click
# "Always Allow" in the keychain ONCE and they persist forever after.
#
# SHARING / PRIVACY
# This script is safe to commit and share (e.g. via Gitea). It is per-machine:
# the certificate and its PRIVATE KEY are generated locally and live only in
# YOUR login keychain — they are never written to the repo. Each developer runs
# this once on their own Mac to create their own local identity. Do NOT export
# or share the certificate/private key; share the script, not the cert.
#
# USAGE
# scripts/macos_codesign_local.sh [APP_BUNDLE ...]
# With no arguments it signs the installed app and the local build output if
# they exist. Run it as the LAST step, AFTER copying the app to /Applications
# (any change to the bundle after signing invalidates the signature).
#
set -euo pipefail
CERT_CN="OrcaSlicer Local Codesign"
KEYCHAIN="$HOME/Library/Keychains/login.keychain-db"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENTITLEMENTS="$REPO_ROOT/src/dev-utils/platform/osx/entitlements.plist"
BUNDLE_ID="com.orcaslicer.OrcaSlicer"
# ---------------------------------------------------------------------------
# 1) Ensure a stable local code-signing identity exists (create it if missing).
# ---------------------------------------------------------------------------
if security find-identity -p codesigning 2>/dev/null | grep -qF "$CERT_CN"; then
echo "✓ Local signing identity '$CERT_CN' already present — reusing it."
else
echo "→ Creating local self-signed code-signing identity '$CERT_CN' ..."
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
cat > "$TMP/req.cnf" <<EOF
[req]
distinguished_name = dn
x509_extensions = v3
prompt = no
[dn]
CN = $CERT_CN
[v3]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = critical,codeSigning
EOF
openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \
-keyout "$TMP/key.pem" -out "$TMP/cert.pem" -config "$TMP/req.cnf" >/dev/null 2>&1
# Import key + cert separately (avoids PKCS#12 algorithm mismatches between
# OpenSSL 3.x and the macOS `security` importer). -A lets codesign use the key
# without a per-signature keychain prompt.
security import "$TMP/key.pem" -k "$KEYCHAIN" -T /usr/bin/codesign -A >/dev/null
security import "$TMP/cert.pem" -k "$KEYCHAIN" -T /usr/bin/codesign -A >/dev/null
echo "✓ Identity created (self-signed, local only, not exported)."
# Note: the certificate is intentionally left untrusted (CSSMERR_TP_NOT_TRUSTED).
# codesign can still sign with it, and that is all we need for a stable
# Designated Requirement — no admin password / trust settings required.
fi
# ---------------------------------------------------------------------------
# 2) Collect target bundles.
# ---------------------------------------------------------------------------
TARGETS=("$@")
if [ ${#TARGETS[@]} -eq 0 ]; then
for candidate in \
"/Applications/OrcaSlicer-KX.app" \
"/Applications/OrcaSlicer.app" \
"$REPO_ROOT/build/arm64/OrcaSlicer/OrcaSlicer.app"; do
[ -d "$candidate" ] && TARGETS+=("$candidate")
done
fi
if [ ${#TARGETS[@]} -eq 0 ]; then
echo "!! No app bundle found to sign. Pass one explicitly:"
echo " $0 /path/to/OrcaSlicer.app"
exit 1
fi
# ---------------------------------------------------------------------------
# 3) Sign each target with the stable identity.
# Top-level only (no --deep): nested/third-party dylibs keep their own
# signatures, which is fine because the app is not hardened-runtime.
# ---------------------------------------------------------------------------
ENT_ARG=()
[ -f "$ENTITLEMENTS" ] && ENT_ARG=(--entitlements "$ENTITLEMENTS")
for app in "${TARGETS[@]}"; do
echo "→ Signing: $app"
codesign --force --sign "$CERT_CN" --identifier "$BUNDLE_ID" "${ENT_ARG[@]}" "$app"
dr="$(codesign -d -r- "$app" 2>&1 | grep -i designated || true)"
echo " $dr"
if echo "$dr" | grep -q "certificate leaf"; then
echo " ✓ Certificate-based requirement — permissions will now persist across rebuilds."
else
echo " !! Unexpected: requirement is not certificate-based."
fi
done
cat <<'EOF'
Done. One last time, on the NEXT launch:
• allow the folder-access prompts, and
• click "Always Allow" on any keychain password prompt.
After that macOS will remember them across future rebuilds. Re-run this script
after each build/redeploy (it reuses the same identity).
EOF