一直以来,PicGo 并没有做 macOS 的签名和公证,因为一直没注册苹果开发者账号(其实学生时代需要每年出 $99 确实有点贵),所以就会导致用户下载了 PicGo 之后,会遇到这个问题:

这个其实也有办法绕过去。不过近几年的 macOS 更新之后,对于应用签名是越来越严格,以前的一行命令还不够,还需要到系统设置里放行比如所有来源的应用之类的。总之门槛越来越高。同时,Homebrew 将在 2026 年 9 月 1 日开始,对于没有通过签名校验的应用,将无法再通过 brew cask 下载安装。趁着这次打算给 PicGo 做点商业化的机会,我也决定注册苹果开发者账号,然后给 PicGo 上签名了。本篇文章就记录一下 Electron 应用在 macOS 上做签名和公证的过程。
1. 获取 Team ID
首先你得注册一个苹果开发者账号,而开发者账号的注册是另外一个话题了(我简单发了篇小红书笔记可以参考),我是用我一直以来的苹果账号注册的,没啥问题,12 月 24 日下午申请注册,24 日晚上就收到通过邮件了。
通过之后,可以在开发者账号页面 找到 Team ID,记录下来,后面有用。
2. 获取证书
我们最终的目标,是导出一个被认证的 p12 文件。
- 创建一个 csr 文件
- 上传 csr 文件,获得一个 cer 证书
- 将证书导入钥匙串,导出 p12 文件。

然后邮件地址我都填的我苹果开发者账号注册的邮箱地址,request 选择存到本地,注意勾上最下面那个指定密钥对!
然后登录 苹果开发者官网 申请证书,我暂时还没打算上架 Mac App Store,先选择 Developer ID Application。
然后导入刚刚的 csr 文件,就能下载这个 cer 证书,证书是有有效期的,意味着到期之后要重新生成。
如果是申请的 Mac Store App 分发的证书,这里将会只有一年的有效期。
然后就可以双击这个文件导入你的证书到 keychain(钥匙链),注意导入到 login(登录) 中。如果发现导入后遇到证书不信任的问题,说明还有一些额外的证书需要先导入。你可以到刚刚在苹果开发者网站申请证书的底部找到这些证书,下载,双击安装到 login 中。

然后把你刚刚那个提示证书不信任的证书,删掉,然后重新导入。重新导入后,你在 Certifacates 或者 My Certificates 里就能看到已经确认的证书信息(注意有个小三角,点开,下面展示是你的私钥,这个很重要,我们需要用它导出 P12 文件)
然后右键证书,导出 P12 文件,这个导出的时候会要求你输入一个密码,这个密码你可以自己定,是用来保护这个 P12 文件的。
导出 P12 文件之后,将其 Base64 字符串导出到剪贴板里,可以复制到某个地方,后文会用到。1
base64 -i certificate.p12 | pbcopy
3. 创建标识符 && App 专有密码
前往苹果开发者账号页面,创建标识符。
目前来说暂时不用申请什么权限,如果之后需要的话可以再加入就行。输入 Description 和 Bundle ID 即可继续注册。Bundle ID 自己取,独一无二即可,通常的格式页面里也告诉你了。
然后去苹果官方的账号管理页面,申请一个 App Specific Passwords:
申请完成后,需要自己找个地方好好存起来,只会展示一次:
4. 签名和公证
签名和公证在很多时候会被混为一谈,实际上它们是有区别的:
- 签名 (Code Signing):证明“这是我写的,且没被篡改过”。
- 公证 (Notarization):证明“苹果查过了,这软件没毒”。
如果没有公证的话,就会遇到经典的 PicGo.app 已损坏 的弹窗。同时,公证的前提是需要有合法的签名。因此注册一个苹果开发者账号,交 $99 年费之后看来是每年必备了。
接下来就是整理和收集上面的各种信息,把它们放到环境变量里,并进行签名和公证了。我自己是在 PicGo 项目的本地创建了一个 .env 文件,内容如下1
2
3
4
5
6
7
8
9
10# 第 1 步获取的 TEAM ID
APPLE_TEAM_ID=xxx
# 这个是你注册苹果开发者账号的邮箱
APPLE_ID=xxx
# 这个是第 3 步获取的 APP 特定密码
APPLE_APP_SPECIFIC_PASSWORD=xxx
# 这个是第 2 步在导出 P12 文件的时候要求输入的密码
CSC_KEY_PASSWORD=xxx
# 这个是第 2 步导出的 P12 文件的 Base64 格式的字符串
CSC_LINK=xxx
然后因为我用的是 electron-builder,所以只要环境变量里有 CSC_KEY_PASSWORD 和 CSC_LINK 的话,构建的时候就会自动签名。
而公证需要在签名之后,把签名后的 app 文件上传到苹果服务器验证一圈,通过之后你的软件才不会被 gatekeeper 给拦截下来。所以公证我们需要利用 electron-builder config 里的一个 afterSign 的属性,执行一个公证的脚本(利用 electron/notarize 这个包提供的能力),同时根据 electron/notarize 的文档,还需要开启 hardenedRuntime 和一些权限,如下:1
2
3
4
5
6
7
8
9
10
11const config = {
// ...
afterSign: 'scripts/notarize.js' // 具体公证代码的脚本需要根据各自项目目录决定
mac: {
// ...
hardenedRuntime: true, // 苹果要求,必须开启强化运行时
entitlements: "build/entitlements.mac.plist", // 必须配合 entitlements
entitlementsInherit: "build/entitlements.mac.plist" // 必须配合 entitlements
}
}
export default config
然后公证的脚本如下,核心就是调用 @electron/notarize 提供的能力,然后这里需要用到我们刚刚放到 .env 文件里的环境变量:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// scripts/notarize.js
require('dotenv').config()
const { notarize } = require('@electron/notarize')
const { APPLE_ID, APPLE_TEAM_ID, APPLE_APP_SPECIFIC_PASSWORD } = process.env
const APP_BUNDLE_ID = 'com.molunerfinn.picgo'
async function main(context) {
const { electronPlatformName, appOutDir, packager } = context
if (
electronPlatformName !== 'darwin' ||
!APPLE_ID ||
!APPLE_APP_SPECIFIC_PASSWORD ||
!APPLE_TEAM_ID
) {
console.log('Skip notarization.')
return
}
const appName = packager.appInfo.productFilename
const appPath = `${appOutDir}/${appName}.app`
const now = Date.now()
console.log('Starting Apple notarization for', appPath)
await notarize({
appPath,
appBundleId: APP_BUNDLE_ID,
appleId: APPLE_ID,
appleIdPassword: APPLE_APP_SPECIFIC_PASSWORD,
teamId: APPLE_TEAM_ID
})
console.log('Finished Apple notarization for', appPath, `in ${(Date.now() - now) / 1000}s`)
}
module.exports = main
同时还需要准备一个权限文件 entitlements.mac.plist,内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<plist version="1.0">
<dict>
<!-- 允许 JIT (Electron 必须) -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<!-- 允许加载未签名的动态库 (插件、原生模块必须) -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- 允许执行内存中可写的页 (部分 Electron 版本需要) -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
然后就可以正常构建了,构建完成之后,electron-builder 会自动帮我们进行签名和公证。整个过程大概需要 10 分钟左右,具体时间取决于苹果服务器的响应速度。
最后,本地跑通后,将它们集成到 GitHub Actions 里即可,GitHub Actions 里需要把上面的环境变量都配置好即可。这样做我们就是完成了签名和公证,这样用户从网络上下载 PicGo 就不会再被拦下了:
后续应该会考虑一下如何上架到 Mac App Store,到时候再继续补充吧。


