feat: configure macOS hardened runtime, entitlements, and build environment variables for notarization
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
APP_NAME=Openscreen
|
||||||
|
BUNDLE_ID=com.siddharthvaddem.openscreen
|
||||||
|
|
||||||
|
APPLE_ID=
|
||||||
|
TEAM_ID=
|
||||||
|
SIGN_IDENTITY="Developer ID Application: Samir Patil ()"
|
||||||
|
CSC_NAME="Samir Patil ()"
|
||||||
|
|
||||||
|
NOTARY_PROFILE=OpenScreen-notary
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD=
|
||||||
@@ -12,6 +12,7 @@ dist
|
|||||||
dist-electron
|
dist-electron
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
.env
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
@@ -28,7 +28,10 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"mac": {
|
"mac": {
|
||||||
"hardenedRuntime": false,
|
"notarize": false,
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"entitlements": "macos.entitlements",
|
||||||
|
"entitlementsInherit": "macos.entitlements",
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
"target": "dmg",
|
"target": "dmg",
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- Required for Electron's V8 JIT compilation -->
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- Required for Electron's native module loading -->
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- Required for loading Electron's bundled frameworks/dylibs -->
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- Audio input (microphone / system audio capture) -->
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<!-- Camera (webcam capture) -->
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Executable
+216
@@ -0,0 +1,216 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# OpenScreen macOS Build Script
|
||||||
|
# Produces: release/<version>/OpenScreen-Mac-<arch>-<version>.dmg
|
||||||
|
#
|
||||||
|
# Usage: chmod +x scripts/build_macos.sh && ./scripts/build_macos.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Load .env ─────────────────────────────────────────────────────────
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
ENV_FILE="${PROJECT_ROOT}/.env"
|
||||||
|
|
||||||
|
if [ -f "$ENV_FILE" ]; then
|
||||||
|
set -a
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
else
|
||||||
|
echo "ERROR: .env file not found at ${ENV_FILE}"
|
||||||
|
echo "Create one with APP_NAME, SIGN_IDENTITY, NOTARY_PROFILE, etc."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Config ────────────────────────────────────────────────────────────
|
||||||
|
VERSION=$(node -p "require('${PROJECT_ROOT}/package.json').version")
|
||||||
|
RELEASE_DIR="${PROJECT_ROOT}/release/${VERSION}"
|
||||||
|
ENTITLEMENTS="${PROJECT_ROOT}/macos.entitlements"
|
||||||
|
ARCHS=("arm64" "x64")
|
||||||
|
|
||||||
|
# ── Colors ────────────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
BOLD='\033[1m'
|
||||||
|
|
||||||
|
print_step() { echo -e "\n${CYAN}${BOLD}▸ $1${NC}"; }
|
||||||
|
print_ok() { echo -e "${GREEN}✓ $1${NC}"; }
|
||||||
|
print_warn() { echo -e "${YELLOW}⚠ $1${NC}"; }
|
||||||
|
print_err() { echo -e "${RED}✗ $1${NC}"; }
|
||||||
|
|
||||||
|
# ── Preflight ─────────────────────────────────────────────────────────
|
||||||
|
echo -e "\n${BOLD}╔══════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BOLD}║ ${APP_NAME} macOS Build Script v${VERSION} ║${NC}"
|
||||||
|
echo -e "${BOLD}╚══════════════════════════════════════════╝${NC}"
|
||||||
|
|
||||||
|
print_step "Checking prerequisites..."
|
||||||
|
|
||||||
|
if [[ "$(uname)" != "Darwin" ]]; then
|
||||||
|
print_err "This script must be run on macOS."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Running on macOS ($(uname -m))"
|
||||||
|
|
||||||
|
if ! command -v node &> /dev/null; then
|
||||||
|
print_err "Node.js not found. Please install Node.js first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Node.js found: $(node -v)"
|
||||||
|
|
||||||
|
if ! command -v npm &> /dev/null; then
|
||||||
|
print_err "npm not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "npm found: $(npm -v)"
|
||||||
|
|
||||||
|
# Check signing identity
|
||||||
|
if ! security find-identity -v -p codesigning | grep -q "$SIGN_IDENTITY"; then
|
||||||
|
print_err "Signing identity not found: ${SIGN_IDENTITY}"
|
||||||
|
print_err "Run 'security find-identity -v -p codesigning' to see available identities."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Signing identity found: ${SIGN_IDENTITY}"
|
||||||
|
|
||||||
|
# Check notary profile
|
||||||
|
if ! xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" &> /dev/null; then
|
||||||
|
print_err "Notary profile '${NOTARY_PROFILE}' not found in keychain."
|
||||||
|
print_err "Run: xcrun notarytool store-credentials \"${NOTARY_PROFILE}\" --apple-id \"${APPLE_ID}\" --team-id \"${TEAM_ID}\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Notary profile found: ${NOTARY_PROFILE}"
|
||||||
|
|
||||||
|
# Check entitlements
|
||||||
|
if [ ! -f "$ENTITLEMENTS" ]; then
|
||||||
|
print_err "Entitlements file not found: ${ENTITLEMENTS}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Entitlements file found"
|
||||||
|
|
||||||
|
# ── Clean ─────────────────────────────────────────────────────────────
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
print_step "Cleaning previous build artifacts..."
|
||||||
|
rm -rf dist dist-electron "${RELEASE_DIR}"
|
||||||
|
print_ok "Clean complete"
|
||||||
|
|
||||||
|
# ── Install Dependencies ─────────────────────────────────────────────
|
||||||
|
print_step "Installing dependencies..."
|
||||||
|
npm ci
|
||||||
|
print_ok "Dependencies installed"
|
||||||
|
|
||||||
|
# ── Build Vite + Electron ────────────────────────────────────────────
|
||||||
|
print_step "Building Vite + Electron... (this may take a minute)"
|
||||||
|
npx tsc && npx vite build
|
||||||
|
print_ok "Vite + Electron build complete"
|
||||||
|
|
||||||
|
# ── Package, Sign, Notarize per Architecture ─────────────────────────
|
||||||
|
for ARCH in "${ARCHS[@]}"; do
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${BOLD} Building for: ${ARCH}${NC}"
|
||||||
|
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
|
||||||
|
# ── Package with electron-builder ─────────────────────────────
|
||||||
|
print_step "[${ARCH}] Packaging with electron-builder..."
|
||||||
|
|
||||||
|
# Build .app only (--dir), electron-builder handles codesigning
|
||||||
|
# with hardenedRuntime + entitlements from electron-builder.json5
|
||||||
|
CSC_NAME="$CSC_NAME" npx electron-builder --mac --${ARCH} --dir
|
||||||
|
|
||||||
|
# Find the .app bundle
|
||||||
|
APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | grep -i "${ARCH}\|mac" | head -n1)
|
||||||
|
if [ -z "$APP_BUNDLE" ]; then
|
||||||
|
# Fallback: find any .app in the output
|
||||||
|
APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | head -n1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$APP_BUNDLE" ]; then
|
||||||
|
print_err "[${ARCH}] Could not find .app bundle in ${RELEASE_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "[${ARCH}] App bundle: $(basename "$APP_BUNDLE")"
|
||||||
|
|
||||||
|
# ── Verify codesign on .app ───────────────────────────────────
|
||||||
|
print_step "[${ARCH}] Verifying .app code signature..."
|
||||||
|
codesign --verify --deep --strict "$APP_BUNDLE" 2>&1 || print_warn "[${ARCH}] Deep verify had warnings (may be expected pre-notarization)"
|
||||||
|
print_ok "[${ARCH}] .app signature verified"
|
||||||
|
|
||||||
|
# ── Create DMG ────────────────────────────────────────────────
|
||||||
|
DMG_NAME="${APP_NAME}-Mac-${ARCH}-${VERSION}.dmg"
|
||||||
|
DMG_OUTPUT="${RELEASE_DIR}/${DMG_NAME}"
|
||||||
|
DMG_STAGING="${RELEASE_DIR}/dmg-staging-${ARCH}"
|
||||||
|
|
||||||
|
print_step "[${ARCH}] Creating DMG..."
|
||||||
|
|
||||||
|
rm -f "$DMG_OUTPUT"
|
||||||
|
rm -rf "$DMG_STAGING"
|
||||||
|
|
||||||
|
# Stage: app + Applications shortcut for drag-to-install
|
||||||
|
mkdir -p "$DMG_STAGING"
|
||||||
|
cp -R "$APP_BUNDLE" "$DMG_STAGING/"
|
||||||
|
ln -s /Applications "$DMG_STAGING/Applications"
|
||||||
|
|
||||||
|
hdiutil create \
|
||||||
|
-srcfolder "$DMG_STAGING" \
|
||||||
|
-volname "${APP_NAME}" \
|
||||||
|
-fs HFS+ \
|
||||||
|
-fsargs "-c c=64,a=16,e=16" \
|
||||||
|
-format UDBZ \
|
||||||
|
"$DMG_OUTPUT"
|
||||||
|
|
||||||
|
print_ok "[${ARCH}] DMG created: ${DMG_NAME}"
|
||||||
|
rm -rf "$DMG_STAGING"
|
||||||
|
|
||||||
|
# ── Sign DMG ──────────────────────────────────────────────────
|
||||||
|
print_step "[${ARCH}] Signing DMG..."
|
||||||
|
codesign --force --sign "$SIGN_IDENTITY" --timestamp "$DMG_OUTPUT"
|
||||||
|
print_ok "[${ARCH}] DMG signed"
|
||||||
|
|
||||||
|
# ── Notarize DMG ──────────────────────────────────────────────
|
||||||
|
print_step "[${ARCH}] Notarizing DMG with Apple... (this may take several minutes)"
|
||||||
|
xcrun notarytool submit "$DMG_OUTPUT" \
|
||||||
|
--keychain-profile "$NOTARY_PROFILE" \
|
||||||
|
--wait
|
||||||
|
print_ok "[${ARCH}] DMG notarized"
|
||||||
|
|
||||||
|
# ── Staple ────────────────────────────────────────────────────
|
||||||
|
print_step "[${ARCH}] Stapling notarization ticket..."
|
||||||
|
xcrun stapler staple "$DMG_OUTPUT"
|
||||||
|
print_ok "[${ARCH}] Ticket stapled"
|
||||||
|
|
||||||
|
# ── Validate ──────────────────────────────────────────────────
|
||||||
|
print_step "[${ARCH}] Validating stapled DMG..."
|
||||||
|
xcrun stapler validate "$DMG_OUTPUT"
|
||||||
|
print_ok "[${ARCH}] Validation passed"
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Clean up unpacked dirs (keep only DMGs) ───────────────────────────
|
||||||
|
print_step "Cleaning up intermediate directories..."
|
||||||
|
find "${RELEASE_DIR}" -maxdepth 1 -type d ! -name "$(basename "$RELEASE_DIR")" -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
print_ok "Cleanup complete"
|
||||||
|
|
||||||
|
# ── Done ──────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}${BOLD}════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${GREEN}${BOLD} Build & Notarization Complete!${NC}"
|
||||||
|
echo -e "${GREEN}${BOLD}════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for ARCH in "${ARCHS[@]}"; do
|
||||||
|
DMG_NAME="${APP_NAME}-Mac-${ARCH}-${VERSION}.dmg"
|
||||||
|
DMG_PATH="${RELEASE_DIR}/${DMG_NAME}"
|
||||||
|
if [ -f "$DMG_PATH" ]; then
|
||||||
|
DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1)
|
||||||
|
echo -e " 📦 ${BOLD}${ARCH}:${NC} ${DMG_PATH}"
|
||||||
|
echo -e " 📏 ${BOLD}Size:${NC} ${DMG_SIZE}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e " ${GREEN}All DMGs are fully signed, notarized, and stapled!${NC}"
|
||||||
|
echo -e " ${GREEN}Ready for distribution outside the Mac App Store.${NC}"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user