| 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. |
| 16 | set -euo pipefail |
| 17 | |
| 18 | CN="Clover Code Signing" |
| 19 | KC="$HOME/Library/Keychains/clover-signing.keychain-db" |
| 20 | KCPW="${CLOVER_KEYCHAIN_PW:-clover}" |
| 21 | P12="$HOME/.clover-code-signing.p12" # backup so the identity survives keychain loss |
| 22 | |
| 23 | ensure_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 | |
| 32 | if [[ -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 |
| 37 | fi |
| 38 | |
| 39 | TMP="$(mktemp -d)" |
| 40 | trap 'rm -rf "$TMP"' EXIT |
| 41 | |
| 42 | if [[ -f "$P12" ]]; then |
| 43 | echo "==> reusing saved identity from $P12 (keeps the same TCC requirement)" |
| 44 | cp "$P12" "$TMP/cs.p12" |
| 45 | else |
| 46 | echo "==> generating new self-signed code-signing certificate" |
| 47 | cat > "$TMP/cs.conf" <<EOF |
| 48 | [ req ] |
| 49 | distinguished_name = dn |
| 50 | x509_extensions = v3 |
| 51 | prompt = no |
| 52 | [ dn ] |
| 53 | CN = $CN |
| 54 | [ v3 ] |
| 55 | keyUsage = critical, digitalSignature |
| 56 | extendedKeyUsage = critical, codeSigning |
| 57 | basicConstraints = critical, CA:false |
| 58 | EOF |
| 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" |
| 65 | fi |
| 66 | |
| 67 | echo "==> (re)creating dedicated keychain $KC" |
| 68 | security delete-keychain "$KC" 2>/dev/null || true |
| 69 | security create-keychain -p "$KCPW" "$KC" |
| 70 | security set-keychain-settings "$KC" # no auto-lock timeout |
| 71 | security unlock-keychain -p "$KCPW" "$KC" |
| 72 | security import "$TMP/cs.p12" -k "$KC" -P clover -A -T /usr/bin/codesign |
| 73 | security set-key-partition-list -S apple-tool:,apple:,unsigned: -s -k "$KCPW" "$KC" >/dev/null 2>&1 || true |
| 74 | ensure_searchlist |
| 75 | |
| 76 | echo |
| 77 | if security find-identity -p codesigning "$KC" | grep -q "$CN"; then |
| 78 | echo "✅ '$CN' ready in $KC. build.sh will sign with it automatically." |
| 79 | else |
| 80 | echo "⚠️ identity not found after setup — check the output above." |
| 81 | exit 1 |
| 82 | fi |