🍎 Electron 桌面端应用合法性问题全流程解决指南(新手友好版)

本文目标 :帮助你把本地的 Electron 应用打包成 macOS 的 .dmg,并做到打开不再被 Gatekeeper 拦截(不再提示"来自身份不明的开发者/无法验证是否含有恶意软件")。

适用对象:个人开发者 & 小团队。


🧩 一、问题场景

当你满心欢喜地将精心开发的 Electron 应用打包分发给用户,却接到反馈:在 macOS 上无法打开,系统弹窗冷冰冰地提示"无法验证开发者",文件被直接移入废纸篓。

如果这个场景让你感同身受,那么你正遭遇 macOS 强大的 Gatekeeper 安全机制。别担心,这不是你的代码有问题,而是应用缺少一张合法的"身份证"------苹果的开发者签名。本指南将带你一步步为你的 Electron 应用完成代码签名,彻底告别"不安全软件"的提示。


🚀 二、从 0 到 1 的整体流程(必做)

阶段 目标 核心工具 产出
1. 证书准备 让 Apple 识别你的身份 Apple Developer、Keychain Developer ID Application 证书
2. 签名 .app 加数字签名 electron-builder/codesign 已签名的 .app/.dmg
3. 公证 让 Apple 云端扫描并背书 notarytool Accepted 的公证结果
4. staple 把公证票据装订回产物 xcrun stapler 可离线验证的 .app/.dmg
5. 验证 本地三板斧校验 codesign/spctl/stapler 放心分发

🧱 三、准备工作

1) Apple Developer 账号

  • 开通 developer.apple.com 开发者计划($99/年)。
  • 建议以组织(Organization)形式创建,便于分权协作。

2) 正确的证书类型

不要Mac Development/Apple Development。它们只用于调试。

  • ✅ 需要:Developer ID Application(给 .app/.dmg 签名用)
  • (可选):Developer ID Installer(制作 .pkg 安装器才用)

可能的问题:

  • 创建页里没有 "Developer ID Application" → 你不是 Account Holder/Admin 或选错了 Team。让 Account Holder 升级角色或代为创建。

3) 获取 Developer ID Application 证书的详细步骤

步骤 3.1:在本地生成证书签名请求(CSR)

  1. 打开 macOS 的 钥匙串访问 (Keychain Access)
  2. 菜单栏选择:钥匙串访问 → 证书助理 → 从证书颁发机构请求证书...
  3. 填写信息:
    • 用户电子邮件地址 :填写你的 Apple ID 邮箱(例如:developer@example.com
    • 常用名称 :填写你的名字或公司名(例如:Example Developer
    • CA 电子邮件地址:留空
    • 选择:存储到磁盘
    • 勾选:让我指定密钥对信息
  4. 点击 继续
  5. 在密钥对信息中:
    • 密钥大小:2048 位
    • 算法:RSA
  6. 保存 CSR 文件(例如:CertificateSigningRequest.certSigningRequest)到桌面或其他位置

⚠️ 注意 :生成 CSR 后,钥匙串中会自动生成一个对应的私钥,请勿删除!证书下载后需要与这个私钥配对使用。

步骤 3.2:在 Apple Developer 网站申请证书

  1. 登录 Apple Developer
  2. 进入:Certificates, Identifiers & Profiles → Certificates
  3. 点击左上角 + 按钮创建新证书
  4. 选择证书类型:
    • 滚动到 Production 区域
    • 选择 Developer ID Application
    • 点击 Continue
  5. 上传 CSR:
    • 点击 Choose File
    • 选择刚才生成的 .certSigningRequest 文件
    • 点击 Continue
  6. 下载证书:
    • 点击 Download 下载 .cer 文件(例如:developerID_application.cer

步骤 3.3:将证书导入钥匙串

  1. 双击下载的 .cer 文件

  2. 系统会自动打开钥匙串访问并导入证书

  3. 验证导入成功:

    • 打开 钥匙串访问
    • 选择 登录 钥匙串
    • 我的证书 分类中查找
    • 应该能看到类似 Developer ID Application: Your Name (TEAM_ID) 的证书
    • 展开证书,确认下方有对应的私钥(🔑 图标)
  4. 验证证书可用性:

bash 复制代码
# 列出所有可用的签名身份
security find-identity -v -p codesigning

# 应该能看到类似输出:
# 1) ABCD1234EFGH5678IJKL9012MNOP3456QRST7890 "Developer ID Application: Your Name (TEAM_ID)"

⚠️ 常见问题

  • 证书导入后没有私钥:说明 CSR 不是在本机生成的,或者私钥被删除了。需要重新生成 CSR 并申请新证书。
  • 无法在钥匙串中找到证书 :确认导入到了 登录 钥匙串而不是 系统 钥匙串。

4) App Store Connect API Key(用于公证)

步骤 4.1:创建 API Key

  1. 登录 App Store Connect
  2. 导航路径:Users and Access → Keys → App Store Connect API
  3. 点击页面中的 + 按钮(或 Generate API Key
  4. 填写信息:
    • Name :给 Key 起个名字(例如:Notarization KeyCI/CD Key
    • Access :选择 Developer(公证只需要 Developer 权限)
  5. 点击 Generate

🔐 权限说明

  • Admin:最高权限,可管理所有内容
  • Developer:可提交公证请求(推荐用于自动化)
  • 如果只做公证,选择 Developer 即可

步骤 4.2:下载和保存 API Key

  1. 创建成功后,页面会显示:

    • Key ID (例如:KEYID12345)← 记录此值
    • Issuer ID (页面顶部,例如:00000000-1111-2222-3333-444444444444)← 记录此值
    • Download API Key 按钮
  2. 立即下载 .p8 文件:

    • 点击 Download API Key
    • 文件名格式:AuthKey_XXXXXXXX.p8(例如:AuthKey_ABCD1234.p8
    • ⚠️ 此文件只能下载一次! 下载后妥善保管,丢失需要重新创建 Key
  3. 保存到项目目录:

bash 复制代码
# 建议的项目结构
your-electron-app/
├── build/
│   ├── AuthKey_ABCD1234.p8          # API Key 文件
│   ├── entitlements.mac.plist       # 权限配置
│   └── afterSign.js                 # 可选:自动补签脚本
├── electron-builder.yml
└── package.json
  1. 安全建议
    • .p8 文件添加到 .gitignore,避免提交到 Git
    • 在 CI/CD 中使用环境变量或加密存储
bash 复制代码
# .gitignore 示例
build/*.p8
*.p8

步骤 4.3:记录三项关键信息(mock 示例)

完成后,你应该拥有以下三项信息:

项目 说明 Mock 示例
API Key 文件 .p8 文件路径 build/AuthKey_ABCD1234.p8
Key ID 在 App Store Connect 中显示 KEYID12345
Issuer ID 在 App Store Connect 顶部显示 00000000-1111-2222-3333-444444444444

可能的问题:

  • App Store Connect看不到 Keys 菜单 → 你不是 Account Holder ;请让 Account Holder 创建并分享 .p8/KeyID/IssuerID 给你。
  • .p8 文件丢失 → 无法重新下载,只能删除旧 Key 并创建新的。
  • 权限不足 → 如果公证失败提示权限问题,尝试使用 Admin 权限的 Key。

⚙️ 四、项目配置(使用 electron-builder)

1) 完整的项目结构

bash 复制代码
your-electron-app/
├── build/                                # 构建资源目录
│   ├── AuthKey_ABCD1234.p8              # ⚠️ API Key(不要提交到 Git)
│   ├── entitlements.mac.plist           # macOS 权限配置
│   ├── afterSign.js                     # 可选:签名后处理脚本
│   └── icon.icns                        # 应用图标
├── src/                                  # 源代码
│   ├── main.js                          # Electron 主进程
│   └── renderer/                        # 渲染进程
├── scripts/
│   └── check-sign.sh                    # 签名自检脚本
├── .env                                  # ⚠️ 环境变量(不要提交到 Git)
├── .gitignore
├── electron-builder.yml                 # electron-builder 配置
└── package.json

2) package.json 配置(完整示例)

json 复制代码
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "My Electron App",
  "main": "src/main.js",
  "scripts": {
    "start": "electron .",
    "build": "electron-builder",
    "build:mac": "electron-builder --mac",
    "build:mac:dmg": "electron-builder --mac dmg",
    "build:dir": "electron-builder --mac --dir",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
    "check-sign": "bash scripts/check-sign.sh dist/mac/MyApp.app"
  },
  "build": {
    "appId": "com.example.myapp",
    "productName": "MyApp",
    "extends": "electron-builder.yml"
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.9.1"
  },
  "author": "Your Name",
  "license": "MIT"
}

💡 脚本说明

  • build:mac:构建 macOS 版本(包括 dmg 和 zip)
  • build:mac:dmg:只构建 dmg
  • build:dir:不打包,只构建目录(用于测试签名)
  • check-sign:运行签名自检脚本

3) electron-builder.yml 配置(三种方式)

方式 A:直接硬编码(适合个人本地开发)

electron-builder.yml(mock 示例)

yaml 复制代码
appId: com.example.myapp
productName: MyApp

mac:
  category: public.app-category.productivity
  target:
    - dmg
  hardenedRuntime: true
  gatekeeperAssess: false
  entitlements: build/entitlements.mac.plist
  entitlementsInherit: build/entitlements.mac.plist
  notarize:
    tool: "notarytool"
    appleApiKey: "build/AuthKey_ABCD1234.p8"        # mock:你的 .p8 文件路径
    appleApiKeyId: "KEYID12345"                     # mock:你的 Key ID
    appleApiIssuer: "00000000-1111-2222-3333-444444444444"  # mock:你的 Issuer ID

dmg:
  title: "${productName} ${version}"
  icon: "build/icon.icns"
  background: "build/background.png"               # 可选:DMG 背景图
  window:
    width: 540
    height: 380
  contents:
    - x: 140
      y: 200
      type: "file"
    - x: 400
      y: 200
      type: "link"
      path: "/Applications"

⚠️ 注意:此方式会把敏感信息写在配置文件中,不要提交到公开仓库!

方式 B:使用环境变量(推荐用于 CI/CD)

electron-builder.yml

yaml 复制代码
appId: com.example.myapp
productName: MyApp

mac:
  category: public.app-category.productivity
  target:
    - dmg
  hardenedRuntime: true
  entitlements: build/entitlements.mac.plist
  entitlementsInherit: build/entitlements.mac.plist
  notarize:
    tool: "notarytool"
    # 留空,通过环境变量传递

.env(本地开发,不要提交到 Git)

bash 复制代码
# Apple Developer 证书相关
APPLE_ID=developer@example.com
APPLE_ID_PASSWORD=your-app-specific-password

# App Store Connect API Key(推荐方式)
APPLE_API_KEY=build/AuthKey_ABCD1234.p8
APPLE_API_KEY_ID=KEYID12345
APPLE_API_ISSUER=00000000-1111-2222-3333-444444444444

# 或者使用 base64 编码的 .p8 内容(CI/CD 推荐)
# APPLE_API_KEY_BASE64=<base64-encoded-content>

使用环境变量的构建命令

bash 复制代码
# 方式 1:使用 dotenv
npm install dotenv-cli --save-dev
npx dotenv electron-builder --mac dmg

# 方式 2:直接导出环境变量
export APPLE_API_KEY_ID="KEYID12345"
export APPLE_API_ISSUER="00000000-1111-2222-3333-444444444444"
export APPLE_API_KEY="build/AuthKey_ABCD1234.p8"
npm run build:mac:dmg

方式 C:使用 electron-builder 的环境变量自动识别

electron-builder 会自动读取以下环境变量:

环境变量 说明 Mock 示例
APPLE_API_KEY API Key 文件路径或 base64 内容 build/AuthKey_ABCD1234.p8
APPLE_API_KEY_ID Key ID KEYID12345
APPLE_API_ISSUER Issuer ID 00000000-1111-2222-3333-444444444444
APPLE_ID Apple ID 邮箱(旧方式) developer@example.com
APPLE_APP_SPECIFIC_PASSWORD App-specific password(旧方式) xxxx-xxxx-xxxx-xxxx

💡 推荐:在 CI/CD 中使用环境变量方式,避免敏感信息泄露。

build/entitlements.mac.plist(建议最小集)

xml 复制代码
<?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>
    <key>com.apple.security.cs.allow-jit</key><true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
    <key>com.apple.security.cs.disable-library-validation</key><true/>
    <key>com.apple.security.app-sandbox</key><false/>
    <key>com.apple.security.network.client</key><true/>
  </dict>
</plist>

可能的问题:

  • hardenedRuntime 未启用会导致公证失败。
  • entitlements 缺项(JIT/unsigned-executable-memory)会导致 Electron 无法正常运行或公证失败。

4) CI/CD 集成示例

GitHub Actions 配置示例

.github/workflows/build-macos.yml

yaml 复制代码
name: Build macOS App

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: macos-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      # 准备签名证书(方式1:使用 .p12 文件)
      - name: Import signing certificate
        env:
          CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
          CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
        run: |
          # 创建临时 keychain
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          # 导入证书
          echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12
          security import certificate.p12 -k $KEYCHAIN_PATH -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          # 设置为默认 keychain
          security list-keychain -d user -s $KEYCHAIN_PATH

          # 清理
          rm -f certificate.p12

      # 准备 API Key
      - name: Prepare API Key
        env:
          APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
        run: |
          mkdir -p build
          echo $APPLE_API_KEY_BASE64 | base64 --decode > build/AuthKey.p8

      # 构建应用
      - name: Build and notarize
        env:
          APPLE_API_KEY: build/AuthKey.p8
          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
        run: npm run build:mac:dmg

      # 上传构建产物
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: macos-dmg
          path: dist/*.dmg

准备 GitHub Secrets

在 GitHub 仓库设置中添加以下 Secrets(Settings → Secrets and variables → Actions):

Secret Name 说明 如何获取
APPLE_API_KEY_BASE64 .p8 文件的 base64 编码 base64 -i AuthKey_ABCD1234.p8
APPLE_API_KEY_ID Key ID 从 App Store Connect 复制
APPLE_API_ISSUER Issuer ID 从 App Store Connect 复制
MACOS_CERTIFICATE_BASE64 证书 .p12 的 base64 base64 -i certificate.p12
MACOS_CERTIFICATE_PASSWORD .p12 文件的密码 导出证书时设置的密码

如何导出 .p12 证书:

bash 复制代码
# 1. 在钥匙串访问中找到证书
# 2. 右键点击证书 → 导出
# 3. 文件格式选择 "个人信息交换 (.p12)"
# 4. 设置密码(用于 CI/CD)
# 5. 保存后,转换为 base64:
base64 -i certificate.p12 | pbcopy  # 复制到剪贴板

🔐 五、安全管理最佳实践

1) .gitignore 配置(必做)

bash 复制代码
# .gitignore
# macOS 构建相关
*.p8
*.p12
*.cer
*.mobileprovision
build/*.p8
build/AuthKey_*.p8

# 环境变量
.env
.env.local
.env.production

# 构建产物
dist/
out/
release/

# 临时文件
*.dmg
*.zip
*.pkg

2) 敏感信息管理建议

场景 推荐方案 不推荐
本地开发 .env 文件 + .gitignore 硬编码在配置文件中
团队协作 1Password / Bitwarden 等密码管理器 通过聊天工具传递
CI/CD GitHub Secrets / GitLab Variables 明文存储在配置文件
多环境 不同的 API Key(dev/prod) 共用同一个 Key

3) API Key 权限最小化

yaml 复制代码
# 生产环境:只给 Developer 权限
production_key:
  access: Developer
  scope: Notarization only

# 开发环境:可以给更多权限方便调试
development_key:
  access: Developer
  scope: Notarization + TestFlight

4) 证书过期管理

bash 复制代码
# 检查证书有效期
security find-identity -v -p codesigning | grep "Developer ID Application"
openssl x509 -inform DER -in developerID_application.cer -noout -dates

# 证书有效期通常为 5-7 年,提前 1 个月续签

5) CI/CD 安全检查清单

  • ✅ 确保 .p8.p12 文件已添加到 .gitignore
  • ✅ 使用 base64 编码存储在 CI Secrets 中
  • ✅ 构建后自动清理临时 keychain
  • ✅ 不在日志中打印敏感信息
  • ✅ 使用短期有效的临时 keychain
  • ✅ 限制 CI/CD 的访问权限(只给必要的人员)

🏗️ 六、打包与自动公证

bash 复制代码
# 安装依赖(示例)
npm i -D electron-builder

# 一条龙:签名 + 提交公证 + staple
npx electron-builder --mac dmg

公证状态查看(任选其一)

bash 复制代码
# 查看历史
xcrun notarytool history \
  --key build/AuthKey_ABCD1234.p8 \
  --key-id KEYID12345 \
  --issuer 00000000-1111-2222-3333-444444444444

# 查看指定请求
xcrun notarytool info <REQUEST_ID> \
  --key build/AuthKey_ABCD1234.p8 \
  --key-id KEYID12345 \
  --issuer 00000000-1111-2222-3333-444444444444

当状态为 Accepted 后,若未自动 staple:

bash 复制代码
xcrun stapler staple "dist/mac/MyApp.app"
xcrun stapler staple "dist/mac/MyApp.dmg"

🔎 七、公证前自检脚本(强烈推荐)

在提交公证前,先扫描 .app 内所有 Mach-O 文件并验证签名,可大幅提高一次通过率。

scripts/check-sign.sh

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail

APP="${1:-dist/mac/MyApp.app}"

echo "== 1) 枚举 Mach-O 可执行体 =="
find "$APP" -type f -print0 \
| xargs -0 file \
| grep -E 'Mach-O|dynamically linked shared library|bundle with Mach-O' \
| sed 's/:.*$//' \
| tee /tmp/macho.list

echo
echo "== 2) 对每个 Mach-O 执行严格签名校验 =="
rc=0
while IFS= read -r f; do
  echo "-> $f"
  if ! codesign --verify --strict --verbose=4 "$f" >/dev/null 2>&1; then
    echo "⚠️ 未签名或签名无效: $f"
    rc=1
  fi
done < /tmp/macho.list

echo
echo "== 3) 校验 .app 整体签名 =="
if ! codesign --verify --strict --deep --verbose=4 "$APP"; then
  echo "⚠️ .app 整体签名校验失败"
  rc=1
fi

echo
echo "== 4) Gatekeeper 预检 =="
if ! spctl -a -vv "$APP"; then
  echo "⚠️ Gatekeeper 预检未通过"
  rc=1
fi

echo
echo "== 5) 列出解包原生模块(高风险区) =="
if [ -d "$APP/Contents/Resources/app.asar.unpacked" ]; then
  find "$APP/Contents/Resources/app.asar.unpacked" -type f \( -name "*.node" -o -name "*.dylib" -o -name "*.jnilib" -o -name "*.so" \) -print
fi

echo
echo "== 6) 检查主可执行体是否启用 Hardened Runtime =="
MAIN_BIN="$APP/Contents/MacOS/$(defaults read "$APP/Contents/Info" CFBundleExecutable 2>/dev/null || echo '')"
if [ -n "$MAIN_BIN" ] && ! codesign -d --verbose=4 "$MAIN_BIN" 2>&1 | grep -qi 'runtime'; then
  echo "⚠️ 未检测到 hardened runtime 选项(需要 mac.hardenedRuntime: true 且使用 --options runtime)"
  rc=1
fi

echo
if [ "$rc" -ne 0 ]; then
  echo "❌ 自检未通过,请根据上方提示修复后再提交公证"
  exit 1
else
  echo "✅ 自检通过,建议开始 Notarization"
fi

使用方式

bash 复制代码
chmod +x scripts/check-sign.sh
scripts/check-sign.sh dist/mac/MyApp.app

🛠️ 八、自动补签(afterSign.js,可选)

若第三方依赖解包出了 .dylib/.jnilib/.node 且未签名,可在 electron-builder.yml 中挂载:

yaml 复制代码
afterSign: build/afterSign.js

build/afterSign.js(简版示例)

js 复制代码
const { execFile } = require("child_process");
const fs = require("fs");
const path = require("path");

function run(cmd, args) {
  return new Promise((resolve, reject) => {
    execFile(cmd, args, (err, stdout, stderr) => {
      if (err) return reject(new Error(stderr || stdout || err?.message));
      resolve();
    });
  });
}

function walk(dir, exts, out) {
  if (!fs.existsSync(dir)) return;
  for (const name of fs.readdirSync(dir)) {
    const p = path.join(dir, name);
    const st = fs.lstatSync(p);
    if (st.isSymbolicLink()) continue;
    if (st.isDirectory()) walk(p, exts, out);
    else if (exts.some(e => p.endsWith(e))) out.push(p);
  }
}

module.exports = async (context) => {
  if (context.electronPlatformName !== "darwin") return;
  const appPath = path.join(context.appOutDir, `${context.packager.appInfo.productFilename}.app`);
  const entitlements = path.join(process.cwd(), "build/entitlements.mac.plist");
  const identity = "Developer ID Application";

  const targets = [];
  walk(path.join(appPath, "Contents/Frameworks"), [".dylib", ".jnilib", ".node", ".so"], targets);
  walk(path.join(appPath, "Contents/Resources", "app.asar.unpacked"), [".dylib", ".jnilib", ".node", ".so"], targets);

  for (const f of targets) {
    try {
      // 已签名会通过;未签名会抛异常,进入 catch 重新签名
      await run("codesign", ["--verify", "--strict", "--verbose=2", f]);
    } catch {
      console.log("🔧 重新签名:", f);
      await run("codesign", ["--force", "--sign", identity, "--timestamp", "--options", "runtime", "--entitlements", entitlements, f]);
    }
  }
};

🧪 九、三板斧验证(公证后)

bash 复制代码
codesign -dv --verbose=4 "dist/mac/MyApp.app"   # Authority 应为 Developer ID Application: ...
spctl -a -vv "dist/mac/MyApp.app"               # 应为 accepted
xcrun stapler validate "dist/mac/MyApp.dmg"     # The validate action worked!

🔁 十、何时需要重新公证?

  • 改动 .app 内任何内容 (包括图标 .icnsInfo.plist、资源、代码、Electron 版本等)→ ✅ 需要重新签名+公证
  • 只改 .dmg 的外观/背景 (不改 .app)→ ❌ 无需重公证,但建议对新 .dmg 再次 stapler staple

🧱 十一、常见坑位速查表

现象/报错 核心原因 处理
看不到 "Keys" 创建入口 不是 Account Holder 让 Account Holder 代建 API Key
-25294 无法导入证书 证书与私钥不匹配或钥匙串未解锁 重新用本机 CSR 申请,或导入 .p12
Archive contains critical validation errors 包内存在未签名的 .dylib/.jnilib/.node 自检脚本找出 → afterSign.js 补签或排除
does not have a ticket stapled 未装订公证票据 xcrun stapler staple.app/.dmg
长时间 In Progress 队列繁忙或网络慢 稍等几分钟或重试
首次运行仍弹提示 其实公证未通过/未装订 走三板斧验证,确保 Accepted + staple

🧭 十二、复盘总结

本文从问题场景 出发,按证书 → 签名 → 公证 → 装订 → 验证 的顺序,解决 Electron .dmg 在 macOS 上的合法性问题,并提供:

  • 详细的证书获取步骤:从生成 CSR、申请证书到导入钥匙串的完整流程;
  • API Key 获取指南:创建、下载和管理 App Store Connect API Key 的详细说明;
  • 三种 Electron 项目配置方式:硬编码、环境变量、CI/CD 集成的完整示例;
  • GitHub Actions CI/CD 配置:包括证书导入、秘密管理的生产级配置;
  • 安全管理最佳实践:.gitignore 配置、敏感信息管理、权限最小化等;
  • ✅ 可直接落地的 electron-builder.yml + entitlements.mac.plist 模板(均为 mock 示例);
  • 公证前自检脚本:在提交前发现未签名的原生库;
  • 自动补签afterSign.js(适用于第三方原生依赖);
  • ✅ 多个常见坑点与权限问题(Keys 不可见、证书导入失败等)的定性与修复。

按本文执行后,你的 .dmg 将具备签名 + 公证 + 票据装订 的完整链路,用户安装时不再被系统拦截。无论是本地开发还是 CI/CD 自动化,都能找到对应的解决方案。祝分发顺利!

相关推荐
醉方休1 天前
Web3.js 全面解析
前端·javascript·electron
qq_398586542 天前
Utools插件实现Web Bluetooth
前端·javascript·electron·node·web·web bluetooth
LateFrames2 天前
使用 Winform / WPF / WinUI3 / Electron 实现异型透明窗口
javascript·electron·wpf·winform·winui3
青木狐2 天前
关闭 macOS 屏幕捕捉 在录制或截图时显示的「录制指示浮窗」。
macos·mac
醉方休2 天前
开发一个完整的Electron应用程序
前端·javascript·electron
阿银3 天前
如何为 macOS 创建 Rust 通用二进制文件 (x86_64 & aarch64)
rust·electron
fruge3 天前
Vue项目中的Electron桌面应用开发实践指南
前端·vue.js·electron
烤奶要加冰4 天前
PyCharm 社区版全平台安装指南
ide·windows·python·pycharm·mac
一只大耗子。4 天前
Mac M系列芯片制作Oracle19镜像使用docker-compose运行
docker·docker-compose·mac·oracle19c