|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Code-sign and notarize macOS artifacts for LocalAI. |
| 3 | +# Every sub-command is a no-op (exit 0) when its required secret is unset, |
| 4 | +# so unsigned builds (forks, local dev, PRs) keep working. |
| 5 | +set -euo pipefail |
| 6 | + |
| 7 | +ENTITLEMENTS="contrib/macos/Launcher.entitlements" |
| 8 | +KEYCHAIN="localai-ci.keychain-db" |
| 9 | + |
| 10 | +cmd_import_cert() { |
| 11 | + if [ -z "${MACOS_CERTIFICATE:-}" ]; then |
| 12 | + echo "[sign] MACOS_CERTIFICATE unset: skipping cert import (unsigned build)" |
| 13 | + return 0 |
| 14 | + fi |
| 15 | + local certfile keychain_pwd default_keychain |
| 16 | + certfile="$(mktemp).p12" |
| 17 | + keychain_pwd="${MACOS_CI_KEYCHAIN_PWD:?MACOS_CI_KEYCHAIN_PWD required when signing}" |
| 18 | + echo "$MACOS_CERTIFICATE" | base64 --decode > "$certfile" |
| 19 | + security create-keychain -p "$keychain_pwd" "$KEYCHAIN" |
| 20 | + security set-keychain-settings -lut 21600 "$KEYCHAIN" |
| 21 | + security unlock-keychain -p "$keychain_pwd" "$KEYCHAIN" |
| 22 | + security import "$certfile" -k "$KEYCHAIN" -P "${MACOS_CERTIFICATE_PWD:?}" \ |
| 23 | + -T /usr/bin/codesign -T /usr/bin/security |
| 24 | + security set-key-partition-list -S apple-tool:,apple:,codesign: \ |
| 25 | + -s -k "$keychain_pwd" "$KEYCHAIN" >/dev/null |
| 26 | + default_keychain="$(security default-keychain | tr -d ' "')" |
| 27 | + security list-keychains -d user -s "$KEYCHAIN" "$default_keychain" |
| 28 | + rm -f "$certfile" |
| 29 | + echo "[sign] certificate imported into $KEYCHAIN" |
| 30 | +} |
| 31 | + |
| 32 | +cmd_sign() { |
| 33 | + local target="$1" |
| 34 | + if [ -z "${MACOS_SIGN_IDENTITY:-}" ]; then |
| 35 | + echo "[sign] MACOS_SIGN_IDENTITY unset: skipping codesign of $target" |
| 36 | + return 0 |
| 37 | + fi |
| 38 | + case "$target" in |
| 39 | + *.app) |
| 40 | + # Hardened runtime + entitlements are required for notarizing the app bundle. |
| 41 | + codesign --deep --force --options runtime --timestamp \ |
| 42 | + --entitlements "$ENTITLEMENTS" \ |
| 43 | + --sign "$MACOS_SIGN_IDENTITY" "$target" |
| 44 | + ;; |
| 45 | + *) |
| 46 | + # A disk image carries no entitlements/runtime; just sign the container. |
| 47 | + codesign --force --timestamp --sign "$MACOS_SIGN_IDENTITY" "$target" |
| 48 | + ;; |
| 49 | + esac |
| 50 | + codesign --verify --strict --verbose=2 "$target" |
| 51 | + echo "[sign] signed $target" |
| 52 | +} |
| 53 | + |
| 54 | +cmd_notarize() { |
| 55 | + local dmg="$1" |
| 56 | + if [ -z "${MACOS_NOTARY_KEY:-}" ]; then |
| 57 | + echo "[notarize] MACOS_NOTARY_KEY unset: skipping notarization of $dmg" |
| 58 | + return 0 |
| 59 | + fi |
| 60 | + local keyfile |
| 61 | + keyfile="$(mktemp).p8" |
| 62 | + echo "$MACOS_NOTARY_KEY" | base64 --decode > "$keyfile" |
| 63 | + xcrun notarytool submit "$dmg" \ |
| 64 | + --key "$keyfile" \ |
| 65 | + --key-id "${MACOS_NOTARY_KEY_ID:?}" \ |
| 66 | + --issuer "${MACOS_NOTARY_ISSUER_ID:?}" \ |
| 67 | + --wait |
| 68 | + rm -f "$keyfile" |
| 69 | + xcrun stapler staple "$dmg" |
| 70 | + xcrun stapler validate "$dmg" |
| 71 | + echo "[notarize] notarized and stapled $dmg" |
| 72 | +} |
| 73 | + |
| 74 | +main() { |
| 75 | + local sub="${1:-}"; shift || true |
| 76 | + case "$sub" in |
| 77 | + import-cert) cmd_import_cert ;; |
| 78 | + sign) cmd_sign "$@" ;; |
| 79 | + notarize) cmd_notarize "$@" ;; |
| 80 | + *) echo "usage: $0 {import-cert|sign <path>|notarize <dmg>}" >&2; exit 2 ;; |
| 81 | + esac |
| 82 | +} |
| 83 | + |
| 84 | +main "$@" |
0 commit comments