mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
- Updated the signing workflow to focus solely on signing the entire .app bundle with hardened runtime, eliminating unnecessary sidecar signing steps. - Enhanced diagnostic output to list the contents of the app bundle before signing, improving visibility during the process. - Improved comments for clarity on the new streamlined signing approach and its compliance with Apple notarization requirements.
226 lines
8.1 KiB
Bash
Executable File
226 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build and codesign a macOS Tauri release (.app + .dmg).
|
|
#
|
|
# Usage:
|
|
# ./scripts/build-macos-signed.sh # release build
|
|
# ./scripts/build-macos-signed.sh --debug # debug build
|
|
# ./scripts/build-macos-signed.sh --skip-notarize # sign but skip notarization
|
|
#
|
|
# Required environment variables (or export before running):
|
|
# APPLE_CERTIFICATE_BASE64 - base64-encoded .p12 developer certificate
|
|
# APPLE_CERTIFICATE_PASSWORD - password for the .p12 certificate
|
|
# APPLE_SIGNING_IDENTITY - e.g. "Developer ID Application: Your Name (TEAMID)"
|
|
# APPLE_ID - Apple ID email for notarization
|
|
# APPLE_PASSWORD - app-specific password for notarization
|
|
# APPLE_TEAM_ID - 10-char Apple Developer team ID
|
|
#
|
|
# Optional:
|
|
# TAURI_SIGNING_PRIVATE_KEY - Tauri updater private key (for update signatures)
|
|
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD - password for the updater key
|
|
|
|
set -euo pipefail
|
|
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
BUILD_MODE="release"
|
|
SKIP_NOTARIZE=false
|
|
BUNDLE_TARGETS="app,dmg"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--debug) BUILD_MODE="debug"; shift ;;
|
|
--skip-notarize) SKIP_NOTARIZE=true; shift ;;
|
|
--bundles) BUNDLE_TARGETS="$2"; shift 2 ;;
|
|
-h|--help)
|
|
sed -n '2,/^$/s/^# //p' "$0"
|
|
exit 0
|
|
;;
|
|
*) echo "Unknown flag: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ── Load .env if present ─────────────────────────────────────────────
|
|
if [[ -f .env ]]; then
|
|
echo "Loading .env..."
|
|
set -a; source .env; set +a
|
|
fi
|
|
|
|
# Also try ci-secrets.json for local CI parity
|
|
if [[ -f scripts/ci-secrets.json ]] && command -v jq >/dev/null 2>&1; then
|
|
echo "Loading secrets from scripts/ci-secrets.json..."
|
|
eval "$(jq -r '.secrets // {} | to_entries[] | select(.value | length > 0) | "export \(.key)=\"\(.value)\""' scripts/ci-secrets.json 2>/dev/null || true)"
|
|
eval "$(jq -r '.vars // {} | to_entries[] | select(.value | length > 0) | "export \(.key)=\"\(.value)\""' scripts/ci-secrets.json 2>/dev/null || true)"
|
|
fi
|
|
|
|
# ── Validate required vars ───────────────────────────────────────────
|
|
MISSING=()
|
|
for var in APPLE_CERTIFICATE_BASE64 APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY; do
|
|
[[ -z "${!var:-}" ]] && MISSING+=("$var")
|
|
done
|
|
if ! $SKIP_NOTARIZE; then
|
|
for var in APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID; do
|
|
[[ -z "${!var:-}" ]] && MISSING+=("$var")
|
|
done
|
|
fi
|
|
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
|
echo "ERROR: Missing required environment variables:" >&2
|
|
printf ' %s\n' "${MISSING[@]}" >&2
|
|
echo >&2
|
|
echo "Set them in .env, scripts/ci-secrets.json, or export them before running." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── Import certificate into a temporary keychain ─────────────────────
|
|
KEYCHAIN_NAME="build-$(date +%s).keychain-db"
|
|
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"
|
|
CERT_PATH="$(mktemp /tmp/cert-XXXXXX.p12)"
|
|
|
|
cleanup_keychain() {
|
|
echo "Cleaning up keychain..."
|
|
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
|
|
rm -f "$CERT_PATH"
|
|
}
|
|
trap cleanup_keychain EXIT
|
|
|
|
echo "Importing signing certificate..."
|
|
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$CERT_PATH"
|
|
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
|
|
security set-keychain-settings -lut 21600 "$KEYCHAIN_NAME"
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
|
|
|
|
security import "$CERT_PATH" \
|
|
-k "$KEYCHAIN_NAME" \
|
|
-P "$APPLE_CERTIFICATE_PASSWORD" \
|
|
-T /usr/bin/codesign \
|
|
-T /usr/bin/security
|
|
|
|
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
|
|
|
|
# Prepend build keychain so codesign finds the cert
|
|
security list-keychains -d user -s "$KEYCHAIN_NAME" $(security list-keychains -d user | tr -d '"')
|
|
|
|
echo "Verifying signing identity..."
|
|
security find-identity -v -p codesigning "$KEYCHAIN_NAME" | head -5
|
|
echo
|
|
|
|
# ── Build (signing only, no notarization) ─────────────────────────────
|
|
# We hide APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID from Tauri so it signs
|
|
# but does NOT attempt notarization. We'll fix the sidecar signature
|
|
# and notarize ourselves afterwards.
|
|
echo "Building Tauri app (mode=$BUILD_MODE, bundles=$BUNDLE_TARGETS)..."
|
|
|
|
BUILD_ARGS=(--bundles "$BUNDLE_TARGETS")
|
|
if [[ "$BUILD_MODE" == "debug" ]]; then
|
|
BUILD_ARGS+=(--debug)
|
|
fi
|
|
|
|
# Tauri picks up signing identity from env
|
|
export APPLE_SIGNING_IDENTITY
|
|
|
|
# Save and unset notarization vars so Tauri doesn't try to notarize
|
|
_SAVED_APPLE_ID="${APPLE_ID:-}"
|
|
_SAVED_APPLE_PASSWORD="${APPLE_PASSWORD:-}"
|
|
_SAVED_APPLE_TEAM_ID="${APPLE_TEAM_ID:-}"
|
|
unset APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID
|
|
|
|
env | grep -E 'APPLE|TAURI|VITE' || true
|
|
|
|
cd app
|
|
echo "Building now... ${BUILD_ARGS[@]}"
|
|
npx tauri build "${BUILD_ARGS[@]}"
|
|
echo "Done building"
|
|
cd ..
|
|
|
|
# Restore notarization vars
|
|
export APPLE_ID="$_SAVED_APPLE_ID"
|
|
export APPLE_PASSWORD="$_SAVED_APPLE_PASSWORD"
|
|
export APPLE_TEAM_ID="$_SAVED_APPLE_TEAM_ID"
|
|
|
|
# ── Locate artifacts ─────────────────────────────────────────────────
|
|
if [[ "$BUILD_MODE" == "debug" ]]; then
|
|
BUNDLE_DIR="app/src-tauri/target/debug/bundle"
|
|
else
|
|
BUNDLE_DIR="app/src-tauri/target/release/bundle"
|
|
fi
|
|
|
|
APP_PATH="$(find "$BUNDLE_DIR/macos" -name '*.app' -maxdepth 1 | head -1)"
|
|
|
|
if [[ -z "$APP_PATH" ]]; then
|
|
echo "ERROR: No .app bundle found in $BUNDLE_DIR/macos/" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "App bundle: $APP_PATH"
|
|
|
|
# ── Sign the .app bundle with hardened runtime ───────────────────────
|
|
# The .app contains only the main Tauri binary (openhuman) — no separate
|
|
# sidecar binary. We just need to sign the whole bundle with hardened
|
|
# runtime + entitlements for notarization.
|
|
ENTITLEMENTS="app/src-tauri/entitlements.sidecar.plist"
|
|
|
|
echo
|
|
echo "Bundle contents:"
|
|
ls -la "$APP_PATH/Contents/MacOS/"
|
|
|
|
echo
|
|
echo "Signing .app bundle with hardened runtime..."
|
|
codesign --force --options runtime \
|
|
--entitlements "$ENTITLEMENTS" \
|
|
--sign "$APPLE_SIGNING_IDENTITY" \
|
|
--timestamp \
|
|
"$APP_PATH"
|
|
|
|
echo
|
|
echo "Verifying code signature..."
|
|
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
|
echo "Signature OK."
|
|
|
|
# ── Notarize ──────────────────────────────────────────────────────────
|
|
if $SKIP_NOTARIZE; then
|
|
echo
|
|
echo "Skipping notarization (--skip-notarize)."
|
|
else
|
|
NOTARIZE_FILE="$(mktemp /tmp/OpenHuman-XXXXXX.zip)"
|
|
echo
|
|
echo "Creating zip for notarization..."
|
|
ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_FILE"
|
|
|
|
echo "Submitting for notarization..."
|
|
xcrun notarytool submit "$NOTARIZE_FILE" \
|
|
--apple-id "$APPLE_ID" \
|
|
--password "$APPLE_PASSWORD" \
|
|
--team-id "$APPLE_TEAM_ID" \
|
|
--wait
|
|
|
|
rm -f "$NOTARIZE_FILE"
|
|
|
|
echo
|
|
echo "Stapling notarization ticket..."
|
|
xcrun stapler staple "$APP_PATH"
|
|
|
|
# Re-create DMG after stapling if dmg was in bundle targets
|
|
DMG_PATH="$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -maxdepth 1 2>/dev/null | head -1)"
|
|
if [[ -n "$DMG_PATH" ]]; then
|
|
echo "Re-creating DMG with stapled .app..."
|
|
DMG_TEMP="$(mktemp /tmp/OpenHuman-XXXXXX.dmg)"
|
|
hdiutil create -volname "OpenHuman" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_TEMP"
|
|
mv "$DMG_TEMP" "$DMG_PATH"
|
|
xcrun stapler staple "$DMG_PATH"
|
|
fi
|
|
|
|
echo "Notarization complete."
|
|
fi
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────
|
|
echo
|
|
echo "===== Build complete ====="
|
|
echo " App: $APP_PATH"
|
|
[[ -n "$DMG_PATH" ]] && echo " DMG: $DMG_PATH"
|
|
echo
|
|
echo "To install:"
|
|
echo " cp -R \"$APP_PATH\" /Applications/"
|
|
echo " # or open \"$DMG_PATH\""
|