1#!/usr/bin/env bash
2# One-time: create a stable self-signed code-signing identity in a dedicated
3# keychain so the Screen Recording grant survives rebuilds.
4#
5# Why a dedicated keychain (not login): it can be created, unlocked, and
6# imported into entirely over SSH with a known password — no GUI, no touching
7# your login keychain. TCC keys the Screen Recording grant on the app's
8# *designated requirement* (bundle id + cert leaf), which stays identical across
9# rebuilds, so you grant once and never get re-prompted.
10#
11# The cert is self-signed and untrusted; that's fine — Gatekeeper is bypassed
12# for locally-built, non-quarantined apps, and TCC matching doesn't need trust.
13#
14# Safe to re-run; it's idempotent. The keychain password is local-only and has
15# nothing to do with your macOS login password.
16set -euo pipefail
17
18CN="Clover Code Signing"
19KC="$HOME/Library/Keychains/clover-signing.keychain-db"
20KCPW="${CLOVER_KEYCHAIN_PW:-clover}"
21P12="$HOME/.clover-code-signing.p12" # backup so the identity survives keychain loss
22
23ensure_searchlist() {
24 local existing
25 existing=$(security list-keychains -d user | sed -e 's/^ *//' -e 's/"//g')
26 case "$existing" in
27 *clover-signing*) ;;
28 *) security list-keychains -d user -s "$KC" $existing ;;
29 esac
30}
31
32if [[ -f "$KC" ]] && security find-identity -p codesigning "$KC" 2>/dev/null | grep -q "$CN"; then
33 security unlock-keychain -p "$KCPW" "$KC" 2>/dev/null || true
34 ensure_searchlist
35 echo "✅ '$CN' already present in $KC"
36 exit 0
37fi
38
39TMP="$(mktemp -d)"
40trap 'rm -rf "$TMP"' EXIT
41
42if [[ -f "$P12" ]]; then
43 echo "==> reusing saved identity from $P12 (keeps the same TCC requirement)"
44 cp "$P12" "$TMP/cs.p12"
45else
46 echo "==> generating new self-signed code-signing certificate"
47 cat > "$TMP/cs.conf" <<EOF
48[ req ]
49distinguished_name = dn
50x509_extensions = v3
51prompt = no
52[ dn ]
53CN = $CN
54[ v3 ]
55keyUsage = critical, digitalSignature
56extendedKeyUsage = critical, codeSigning
57basicConstraints = critical, CA:false
58EOF
59 openssl req -x509 -newkey rsa:2048 -keyout "$TMP/cs.key" -out "$TMP/cs.crt" \
60 -days 3650 -nodes -config "$TMP/cs.conf" >/dev/null 2>&1
61 openssl pkcs12 -export -inkey "$TMP/cs.key" -in "$TMP/cs.crt" -out "$TMP/cs.p12" \
62 -passout pass:clover -name "$CN" >/dev/null 2>&1
63 cp "$TMP/cs.p12" "$P12"
64 chmod 600 "$P12"
65fi
66
67echo "==> (re)creating dedicated keychain $KC"
68security delete-keychain "$KC" 2>/dev/null || true
69security create-keychain -p "$KCPW" "$KC"
70security set-keychain-settings "$KC" # no auto-lock timeout
71security unlock-keychain -p "$KCPW" "$KC"
72security import "$TMP/cs.p12" -k "$KC" -P clover -A -T /usr/bin/codesign
73security set-key-partition-list -S apple-tool:,apple:,unsigned: -s -k "$KCPW" "$KC" >/dev/null 2>&1 || true
74ensure_searchlist
75
76echo
77if security find-identity -p codesigning "$KC" | grep -q "$CN"; then
78 echo "✅ '$CN' ready in $KC. build.sh will sign with it automatically."
79else
80 echo "⚠️ identity not found after setup — check the output above."
81 exit 1
82fi