3ffbb1cb9f
With the repository variable MACOS_SIGN_IDENTITY set, the launcher's Horizon.app and .dmg and the game's three programs are signed with the hardened runtime and a timestamp, notarized and (app, dmg) stapled, from a keychain of the Mac mini's own that keychain.sh unlocks with its password from 1Password. Unset, everything is signed ad hoc as before. The launcher checks an updated bundle's signature before and after swapping it in, and puts the old one back when it doesn't hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
4.9 KiB
Bash
Executable File
100 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# The keychain the macOS builds are signed and notarized from: a keychain of its own (not the
|
|
# login keychain, which a runner's job can't unlock), holding only the "Developer ID
|
|
# Application" identity, its intermediate certificate and the notarytool profile. Its password
|
|
# is in 1Password (the Agents vault, read with the fleet's service account), so a job unlocks
|
|
# it without anyone typing anything. See packaging/macos/README.md.
|
|
#
|
|
# packaging/macos/keychain.sh unlock # a job's first macOS step: unlock, check, export the env
|
|
# packaging/macos/keychain.sh lock # its last
|
|
# packaging/macos/keychain.sh create # once: the keychain, with the password from 1Password
|
|
# packaging/macos/keychain.sh import P12 # once, from the Mac's own Terminal: the identity
|
|
# packaging/macos/keychain.sh notary # once: the notarytool profile from the App Store Connect key
|
|
#
|
|
# MACOS_SIGN_IDENTITY names the identity (empty: nothing is signed, see package.sh);
|
|
# MACOS_SIGN_KEYCHAIN the keychain; MACOS_NOTARY_PROFILE the profile in it; MACOS_KEYCHAIN_PASSWORD,
|
|
# when set, is the keychain's password instead of 1Password's.
|
|
set -euo pipefail
|
|
kc=${MACOS_SIGN_KEYCHAIN:-$HOME/Library/Keychains/horizon-signing.keychain-db}
|
|
profile=${MACOS_NOTARY_PROFILE:-horizon-notary}
|
|
identity=${MACOS_SIGN_IDENTITY:-Developer ID Application: Ludovic Marie (YYJU63HSD4)}
|
|
# The 1Password CLI, signed in as the fleet's agents (`op` elsewhere), and the items it reads.
|
|
op=${OP:-$HOME/fleet/op}
|
|
[[ -x "$op" ]] || op=op
|
|
password_ref=${MACOS_KEYCHAIN_PASSWORD_REF:-op://Agents/Horizon signing keychain/password}
|
|
# "App Store Connect API - GitHub CI (DISPO)": a team key; by id, as the name's parentheses
|
|
# can't be in a secret reference.
|
|
notary_item=${MACOS_NOTARY_ITEM:-wwweyy32dwt6oylm6ypcfivsxe}
|
|
|
|
# The keychain's password: $MACOS_KEYCHAIN_PASSWORD (a CI secret elsewhere), or from 1Password.
|
|
password() {
|
|
if [[ -n "${MACOS_KEYCHAIN_PASSWORD:-}" ]]; then
|
|
printf '%s' "$MACOS_KEYCHAIN_PASSWORD"
|
|
else
|
|
"$op" read "$password_ref" < /dev/null
|
|
fi
|
|
}
|
|
|
|
case "${1:-}" in
|
|
unlock)
|
|
security unlock-keychain -p "$(password)" "$kc"
|
|
security find-identity -v -p codesigning "$kc" | grep -F "\"$identity\"" > /dev/null ||
|
|
{ echo "::error::$kc has no valid \"$identity\" (packaging/macos/keychain.sh import)"; exit 1; }
|
|
xcrun notarytool history --keychain-profile "$profile" --keychain "$kc" > /dev/null ||
|
|
{ echo "::error::no notarytool profile $profile in $kc (packaging/macos/keychain.sh notary)"; exit 1; }
|
|
echo "Unlocked $kc: \"$identity\" and the notarytool profile $profile"
|
|
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
|
{ echo "MACOS_SIGN_KEYCHAIN=$kc"; echo "MACOS_NOTARY_PROFILE=$profile"; } >> "$GITHUB_ENV"
|
|
fi
|
|
;;
|
|
lock)
|
|
security lock-keychain "$kc"
|
|
;;
|
|
create)
|
|
[[ -e "$kc" ]] && { echo "$kc already exists"; exit 1; }
|
|
security create-keychain -p "$(password)" "$kc"
|
|
# Locks itself after two hours idle and when the Mac sleeps.
|
|
security set-keychain-settings -t 7200 -l "$kc"
|
|
echo "Created $kc"
|
|
;;
|
|
import)
|
|
p12=${2:?usage: keychain.sh import IDENTITY.p12}
|
|
pass=$(password)
|
|
security unlock-keychain -p "$pass" "$kc"
|
|
read -rsp "The password you gave the .p12 when exporting it: " p12pass && echo
|
|
security import "$p12" -k "$kc" -f pkcs12 -P "$p12pass" -T /usr/bin/codesign
|
|
unset p12pass
|
|
# The intermediate, so codesign builds the chain without the login keychain.
|
|
issuer=$(security find-certificate -c "$identity" -p "$kc" | openssl x509 -noout -issuer |
|
|
sed -n 's/.*CN *= *\([^,]*\).*/\1/p')
|
|
if [[ -n "$issuer" ]] && ! security find-certificate -c "$issuer" "$kc" > /dev/null 2>&1 &&
|
|
security find-certificate -c "$issuer" -p "$HOME/Library/Keychains/login.keychain-db" > "$kc.ca.pem"; then
|
|
security import "$kc.ca.pem" -k "$kc"
|
|
fi
|
|
rm -f "$kc.ca.pem"
|
|
# Only the one identity: anything else the .p12 held goes.
|
|
security find-identity -v -p codesigning "$kc" | sed -n 's/^ *[0-9]*) \([0-9A-F]*\) "\(.*\)"$/\1 \2/p' |
|
|
while read -r hash name; do
|
|
[[ "$name" == "$identity" ]] || security delete-identity -Z "$hash" "$kc"
|
|
done
|
|
# codesign may use the key without asking.
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$pass" "$kc" > /dev/null
|
|
rm -f "$p12"
|
|
security find-identity -v -p codesigning "$kc"
|
|
echo "Imported; $p12 is deleted."
|
|
;;
|
|
notary)
|
|
security unlock-keychain -p "$(password)" "$kc"
|
|
key=$(mktemp) && chmod 600 "$key"
|
|
trap 'rm -f "$key"' EXIT
|
|
"$op" read "op://Agents/$notary_item/private_key_p8" < /dev/null > "$key"
|
|
xcrun notarytool store-credentials "$profile" --key "$key" --keychain "$kc" \
|
|
--key-id "$("$op" read "op://Agents/$notary_item/key_id" < /dev/null)" \
|
|
--issuer "$("$op" read "op://Agents/$notary_item/issuer_id" < /dev/null)"
|
|
;;
|
|
*)
|
|
sed -n '/^set -e/q; 2,$s/^# \{0,1\}//p' "$0"
|
|
exit 1
|
|
;;
|
|
esac
|