forked from viewit/OrcaSlicer-KX
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>
129 lines
5.4 KiB
Bash
Executable File
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
|