#!/bin/bash set -e # ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ HiFastVPN macOS 签名和公证脚本 ║ # ║ ║ # ║ 使用方法: ║ # ║ 1. 修改下面的配置变量 ║ # ║ 2. chmod +x sign_and_notarize.sh ║ # ║ 3. ./sign_and_notarize.sh ║ # ╚═══════════════════════════════════════════════════════════════════════════╝ # ======================== 配置变量(请修改这里)======================== SIGNING_IDENTITY="Developer ID Application: TAW TRADERS SDN. BHD. (NJRRF427XB)" APPLE_ID="speakeloudest@gmail.com" APP_PASSWORD="lvry-umfn-pqgz-lnwk" # App-specific password TEAM_ID="NJRRF427XB" # 路径配置 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" APP_PATH="${PROJECT_DIR}/hi-client/build/macos/Build/Products/Release/HiFastVPN.app" ENTITLEMENTS="${PROJECT_DIR}/hi-client/macos/Runner/Release.entitlements" OUTPUT_DIR="${PROJECT_DIR}/dist" # ===================================================================== echo "╔═══════════════════════════════════════════════════════════════════╗" echo "║ HiFastVPN macOS 签名和公证脚本 ║" echo "╚═══════════════════════════════════════════════════════════════════╝" echo "" echo "📁 项目目录: ${PROJECT_DIR}" echo "📦 应用路径: ${APP_PATH}" echo "📄 Entitlements: ${ENTITLEMENTS}" echo "" # 检查 .app 是否存在 if [ ! -d "${APP_PATH}" ]; then echo "❌ 错误: ${APP_PATH} 不存在" echo "请先运行: flutter build macos --release" exit 1 fi # 检查 entitlements 是否存在 if [ ! -f "${ENTITLEMENTS}" ]; then echo "❌ 错误: ${ENTITLEMENTS} 不存在" exit 1 fi # 创建输出目录 mkdir -p "${OUTPUT_DIR}" # ======================== Step 1: 清理 ======================== echo "" echo "🧹 Step 1: 清理多余文件..." # 删除 .DS_Store find "${APP_PATH}" -name ".DS_Store" -delete 2>/dev/null || true find "${APP_PATH}" -name "*.bak" -delete 2>/dev/null || true # 删除 Headers 目录和符号链接 find "${APP_PATH}/Contents/Frameworks" -type l -name "Headers" -delete 2>/dev/null || true find "${APP_PATH}/Contents/Frameworks" -type d -name "Headers" -exec rm -rf {} + 2>/dev/null || true # 删除 Modules 目录 find "${APP_PATH}/Contents/Frameworks" -type d -name "Modules" -exec rm -rf {} + 2>/dev/null || true # 删除 PrivateHeaders find "${APP_PATH}/Contents/Frameworks" -name "PrivateHeaders" -exec rm -rf {} + 2>/dev/null || true echo " ✅ 清理完成" # ======================== Step 2: 移除现有签名 ======================== echo "" echo "🗑️ Step 2: 移除现有签名..." # 移除所有签名(忽略错误) find "${APP_PATH}" -name "*.dylib" -exec codesign --remove-signature {} \; 2>/dev/null || true find "${APP_PATH}" -name "*.framework" -exec codesign --remove-signature {} \; 2>/dev/null || true codesign --remove-signature "${APP_PATH}" 2>/dev/null || true echo " ✅ 移除签名完成" # ======================== Step 3: 签名 dylib 文件 ======================== echo "" echo "🔐 Step 3: 签名 dylib 文件..." find "${APP_PATH}" -name "*.dylib" -print0 | while IFS= read -r -d '' dylib; do echo " → $(basename "$dylib")" codesign --force --sign "${SIGNING_IDENTITY}" \ --options runtime \ --timestamp \ "$dylib" done echo " ✅ dylib 签名完成" # ======================== Step 4: 签名 Framework ======================== echo "" echo "🔐 Step 4: 签名 Framework..." # 签名所有 framework(使用 --deep 确保内部资源被正确处理) for fw in "${APP_PATH}"/Contents/Frameworks/*.framework; do if [ -d "$fw" ]; then fw_name=$(basename "$fw") echo " → ${fw_name}" # 使用 --deep 签名 framework,这样会自动处理内部的资源 codesign --force --sign "${SIGNING_IDENTITY}" \ --options runtime \ --timestamp \ "$fw" fi done echo " ✅ Framework 签名完成" # ======================== Step 5: 签名主应用 ======================== echo "" echo "🔐 Step 5: 签名主应用..." codesign --force --sign "${SIGNING_IDENTITY}" \ --options runtime \ --entitlements "${ENTITLEMENTS}" \ --timestamp \ "${APP_PATH}" echo " ✅ 主应用签名完成" # ======================== Step 6: 验证签名 ======================== echo "" echo "✅ Step 6: 验证签名..." if codesign --verify --deep --strict --verbose=2 "${APP_PATH}" 2>&1; then echo " ✅ 签名验证通过!" else echo " ❌ 签名验证失败!" echo "" echo "详细错误信息:" codesign --verify --deep --strict --verbose=4 "${APP_PATH}" 2>&1 exit 1 fi # ======================== Step 7: 创建 ZIP ======================== echo "" echo "📦 Step 7: 创建 ZIP..." ZIP_PATH="${OUTPUT_DIR}/HiFastVPN.zip" rm -f "${ZIP_PATH}" ditto -c -k --keepParent "${APP_PATH}" "${ZIP_PATH}" echo " ✅ ZIP 创建完成: ${ZIP_PATH}" # ======================== Step 8: 公证 ======================== echo "" echo "📤 Step 8: 提交公证..." echo " (这可能需要几分钟时间...)" xcrun notarytool submit "${ZIP_PATH}" \ --apple-id "${APPLE_ID}" \ --password "${APP_PASSWORD}" \ --team-id "${TEAM_ID}" \ --wait echo " ✅ 公证完成" # ======================== Step 9: Staple ======================== echo "" echo "📎 Step 9: Staple..." xcrun stapler staple "${APP_PATH}" echo " ✅ Staple 完成" # ======================== Step 10: 最终验证 ======================== echo "" echo "🔍 Step 10: 最终验证..." echo "" echo "--- codesign --verify ---" codesign --verify --deep --strict "${APP_PATH}" && echo "✅ codesign 验证通过" echo "" echo "--- xcrun stapler validate ---" xcrun stapler validate "${APP_PATH}" echo "" echo "--- spctl -a -t execute ---" spctl -a -t execute -vv "${APP_PATH}" # ======================== Step 11: 创建 DMG ======================== echo "" echo "💿 Step 11: 创建 DMG..." DMG_PATH="${OUTPUT_DIR}/HiFastVPN.dmg" rm -f "${DMG_PATH}" # 使用 hdiutil 创建简单的 DMG hdiutil create -volname "HiFastVPN" -srcfolder "${APP_PATH}" -ov -format UDZO "${DMG_PATH}" # 签名 DMG # echo " 签名 DMG..." # codesign --force --sign "${SIGNING_IDENTITY}" --timestamp "${DMG_PATH}" # 公证 DMG echo " 公证 DMG..." xcrun notarytool submit "${DMG_PATH}" \ --apple-id "${APPLE_ID}" \ --password "${APP_PASSWORD}" \ --team-id "${TEAM_ID}" \ --wait # Staple DMG echo " Staple DMG..." xcrun stapler staple "${DMG_PATH}" echo " ✅ DMG 创建完成: ${DMG_PATH}" # ======================== 完成 ======================== echo "" echo "╔═══════════════════════════════════════════════════════════════════╗" echo "║ 🎉 全部完成! ║" echo "╚═══════════════════════════════════════════════════════════════════╝" echo "" echo "输出文件:" echo " 📦 ${APP_PATH}" echo " 📦 ${ZIP_PATH}" echo " 💿 ${DMG_PATH}" echo "" echo "⚠️ 重要提示:" echo " 发给用户的应该是 DMG 文件,不是 ZIP 或 .app" echo " DMG 已经签名和公证,用户打开后不会被 Gatekeeper 拦截" echo ""