#!/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" </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