#!/usr/bin/env bash set -euo pipefail ts() { date +"%H:%M:%S"; } script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd -- "${script_dir}/../.." && pwd)" usage() { cat <<'EOF' Usage: ./build/macos/build_sign_notarize.sh # list local signing identities + notary profiles ./build/macos/build_sign_notarize.sh --run [options] # build, sign, notarize, staple Common options: --run Perform build/sign/notarize (otherwise list identities/profiles) --release | --debug Build config (default: Release) --clean Clean build dir before build --deploy Run CMake 'deploy' target before signing (recommended for distributing) --build-dir Override build directory Signing options: --identity "" Codesign identity (e.g. "Developer ID Application: Your Name (TEAMID)") --entitlements Optional entitlements plist for codesign Notarization options (recommended): --notary-profile notarytool keychain profile name (created via `xcrun notarytool store-credentials`) --skip-notarize Skip notarization Outputs: - Signed app: /strawberry.app - Zip for notarization: /strawberry-notarize.zip Notes: - This script is intended for Developer ID distribution (outside Mac App Store). - If you want Sparkle updates, you'll typically ship a notarized .zip + an appcast feed. EOF } list_identities_and_profiles() { echo "==> [$(ts)] macOS code signing identities (Keychain)" if command -v security >/dev/null 2>&1; then security find-identity -p codesigning -v || true else echo "security tool not found?" fi echo echo "==> [$(ts)] notarytool profiles (keychain profiles)" if xcrun notarytool --help >/dev/null 2>&1; then xcrun notarytool list-profiles 2>/dev/null || echo "(none; create one with: xcrun notarytool store-credentials)" else echo "xcrun notarytool not available. Install Xcode CLT: xcode-select --install" fi echo echo "==> [$(ts)] Provisioning profiles (macOS)" prof_dir="${HOME}/Library/MobileDevice/Provisioning Profiles" if [[ -d "${prof_dir}" ]]; then ls -la "${prof_dir}" | head -n 50 else echo "(none found at '${prof_dir}')" fi } if [[ "$(uname -s)" != "Darwin" ]]; then echo "Error: This script is for macOS only." >&2 exit 1 fi if ! command -v xcode-select >/dev/null 2>&1 || ! xcode-select -p >/dev/null 2>&1; then echo "Error: Xcode Command Line Tools not found." >&2 echo "Install them first: xcode-select --install" >&2 exit 1 fi do_run=0 config="Release" do_clean=0 do_deploy=0 build_dir="" identity="" entitlements="" notary_profile="" skip_notarize=0 while [[ $# -gt 0 ]]; do case "$1" in --run) do_run=1; shift ;; --release) config="Release"; shift ;; --debug) config="Debug"; shift ;; --clean) do_clean=1; shift ;; --deploy) do_deploy=1; shift ;; --build-dir) build_dir="${2:-}"; shift 2 ;; --identity) identity="${2:-}"; shift 2 ;; --entitlements) entitlements="${2:-}"; shift 2 ;; --notary-profile) notary_profile="${2:-}"; shift 2 ;; --skip-notarize) skip_notarize=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "Unknown arg: $1" >&2; usage; exit 2 ;; esac done if [[ "$do_run" -eq 0 ]]; then usage echo list_identities_and_profiles exit 0 fi if [[ -z "$build_dir" ]]; then # Keep consistent with build_app.sh output dir naming lc_config="$(echo "$config" | tr '[:upper:]' '[:lower:]')" build_dir="${repo_root}/cmake-build-macos-${lc_config}" fi app_path="${build_dir}/strawberry.app" bin_path="${app_path}/Contents/MacOS/strawberry" zip_path="${build_dir}/strawberry-notarize.zip" if [[ -z "$identity" ]]; then echo "Error: Missing --identity (Developer ID Application identity)." >&2 echo "Run without args to list identities, then pass e.g.:" >&2 echo " --identity \"Developer ID Application: Your Name (TEAMID)\"" >&2 exit 2 fi if [[ "$skip_notarize" -eq 0 && -z "$notary_profile" ]]; then echo "Error: Missing --notary-profile (or pass --skip-notarize)." >&2 echo "Create one with:" >&2 echo " xcrun notarytool store-credentials --keychain-profile --apple-id --team-id --password " >&2 exit 2 fi echo "==> [$(ts)] Building Strawberry" build_args=( "--release" ) if [[ "$config" == "Debug" ]]; then build_args=( "--debug" ); fi if [[ "$do_clean" -eq 1 ]]; then build_args+=( "--clean" ); fi if [[ -n "$build_dir" ]]; then build_args+=( "--build-dir" "$build_dir" ); fi if [[ "$do_deploy" -eq 1 ]]; then build_args+=( "--deploy" ); fi "${repo_root}/build/macos/build_app.sh" "${build_args[@]}" if [[ ! -x "$bin_path" ]]; then echo "Error: built app not found at: $app_path" >&2 exit 1 fi echo "==> [$(ts)] Codesigning (hardened runtime)" codesign_args=( --force --timestamp --options runtime --sign "$identity" ) if [[ -n "$entitlements" ]]; then codesign_args+=( --entitlements "$entitlements" ) fi # Sign everything inside the bundle, then the main app. find "$app_path" -type f \( -name "*.dylib" -o -name "*.so" -o -perm -111 \) -print0 | while IFS= read -r -d '' f; do codesign "${codesign_args[@]}" "$f" >/dev/null done find "$app_path/Contents/Frameworks" "$app_path/Contents/PlugIns" -type d -name "*.framework" -print0 2>/dev/null | while IFS= read -r -d '' fw; do codesign "${codesign_args[@]}" "$fw" >/dev/null done codesign "${codesign_args[@]}" "$app_path" >/dev/null echo "==> [$(ts)] Verifying codesign" codesign --verify --deep --strict --verbose=2 "$app_path" echo "==> [$(ts)] Creating zip for notarization" rm -f "$zip_path" ditto -c -k --sequesterRsrc --keepParent "$app_path" "$zip_path" if [[ "$skip_notarize" -eq 0 ]]; then echo "==> [$(ts)] Notarizing (this can take a few minutes)" xcrun notarytool submit "$zip_path" --keychain-profile "$notary_profile" --wait echo "==> [$(ts)] Stapling" xcrun stapler staple "$app_path" fi echo "==> [$(ts)] Gatekeeper assessment" spctl -a -vv --type execute "$app_path" || true echo echo "Done." echo "App: $app_path" echo "Zip: $zip_path"