mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
- Updated the signing workflow to sign non-main binaries (sidecars) before the main .app bundle, improving compliance with Apple notarization requirements. - Added diagnostic output to display the main executable name and list contents of the app bundle for better visibility during the signing process. - Improved comments for clarity on the new signing steps and their significance in the overall workflow.
257 lines
9.1 KiB
Bash
Executable File
257 lines
9.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 .app contents and bundle ─────────────────────────────────────
|
|
ENTITLEMENTS="app/src-tauri/entitlements.sidecar.plist"
|
|
MAIN_EXE="$(defaults read "$APP_PATH/Contents/Info.plist" CFBundleExecutable 2>/dev/null || echo "OpenHuman")"
|
|
|
|
echo
|
|
echo "Bundle contents:"
|
|
ls -la "$APP_PATH/Contents/MacOS/"
|
|
echo "Main executable (from plist): $MAIN_EXE"
|
|
|
|
# Sign all non-main binaries (sidecars) first
|
|
for bin in "$APP_PATH/Contents/MacOS/"*; do
|
|
[[ -f "$bin" && -x "$bin" ]] || continue
|
|
BASENAME="$(basename "$bin")"
|
|
[[ "$BASENAME" == "$MAIN_EXE" ]] && continue
|
|
echo " Signing sidecar: $BASENAME"
|
|
codesign --force --options runtime \
|
|
--entitlements "$ENTITLEMENTS" \
|
|
--sign "$APPLE_SIGNING_IDENTITY" \
|
|
--timestamp \
|
|
"$bin"
|
|
done
|
|
|
|
# Sign sidecars in Resources/ if any
|
|
for bin in "$APP_PATH/Contents/Resources/"openhuman-core-*; do
|
|
[[ -f "$bin" ]] || continue
|
|
echo " Signing resource sidecar: $(basename "$bin")"
|
|
codesign --force --options runtime \
|
|
--entitlements "$ENTITLEMENTS" \
|
|
--sign "$APPLE_SIGNING_IDENTITY" \
|
|
--timestamp \
|
|
"$bin"
|
|
done
|
|
|
|
# Sign the .app bundle (signs main exe + updates seal)
|
|
echo " Signing .app bundle..."
|
|
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 with stapled .app, notarize the DMG, then staple it
|
|
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"
|
|
|
|
echo "Notarizing DMG..."
|
|
xcrun notarytool submit "$DMG_PATH" \
|
|
--apple-id "$APPLE_ID" \
|
|
--password "$APPLE_PASSWORD" \
|
|
--team-id "$APPLE_TEAM_ID" \
|
|
--wait
|
|
|
|
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\""
|