前言
在上篇文章「macOS App 自动化分发 App Store 探索与实践」中讲解了如何通过 Shell 脚本实现 macOS App 自动化分发 App Store。相信,看过的同学都或多或少对 macOS App 构建、分发 App Store 相关的知识都具备了一定的认知。
而对于开发者来说,我们更多情况下可能会选择在网络上分发 App。但是,站在使用者的角度,如果下载的 App 没有经过 Apple Notary Service 公证(Notarizate)过,这在安装的时候系统则会提示“无法打开 xxx App,因为无法验证开发者”:
那么,这个时候的解决方法就是修改系统偏好——>安全性与隐私的设置,选择仍要打开该 App:
虽然,这样可以让使用者安装 App,但是,这并不是真正在解决这个问题的本质,从根上解决应该是让我们要分发到网络上的 App 通过 Apple Notary Service 公证(Notarizate),这样一来他人下载安装我们应用的时候则不会出现无法打开的提示,而是:
所以,今天本文也将围绕「macOS App 公证」展开如何通过手动或者自动化(Shell、工具)实现公证(Notarizate)过程。
1. 手动公证
首先,我们先了解下如何手动公证?手动公证过程可以通过 Xcode 提供的 GUI 界面操作完成。同样地,首先我们需要构建 .xarchive 文件:
构建完后,Xcode 会弹出窗口让你选择 Distribute App 或 Validate App,这里我们选择前者:
接着,不同于之前分发 App Store,我们需要选择 Developer ID,它表示的是在 App Store 之外分发 App:
然后,我们需要选择 Upload ——> Development Team -> Manually manage signing,此时需要选择 Develop ID 对应的 Distribution 证书和 Provisioning Profile:
最后,则选择 Next ——> Upload,然后则会将我们的 App 上传到 Apple Notary Service 进行公证,通过则会提示我们可以分发 App。
可以看到,使用 Xcode 公证 App 的过程和 App Store 分发大同小异,所以,在通过简单了解使用 Xcode 手动实现公证过程后,接下来,我们来认识下如何自动化实现公证的过程?
2. 自动化公证
自动化公证则指的是我们通过代码实现前面介绍到的使用 GUI 手动实现公证的过程,然后再根据实际情况集成到现有的 CI/CD 过程中。而现在社区中实现的可以对 macOS App 自动化公证的方式主要有这 4 种:
- altool --notarize-app 使用的旧的 Apple Notary Service,适用于 Xcode 12 以及更早的版本,但是需要注意的是Apple 将会在 2023 年秋季停止对它的支持
- notarytool 使用的新的 Apple Notary Service,只适用于 Xcode 13 及以上的版本,相比较
altool --notarize-app
快了 10 倍 - electron-notarize 用 JavaScript 实现的用于公证的工具,支持
notarytool
和altool --notarize-app
等 2 种公证方式 - fastlane 用 Ruby 实现的一个可以便捷地帮你完成证书管理、代码签名和发布等相关的工具,适用于 iOS、macOS 和 Android 应用
所以,下面将会一一对这 4 个的使用做对应的展开介绍,首先是 altool --notarize-app
。
2.1 altool --notarize-app
在上篇文章「macOS App 自动化分发 App Store 探索与实践」中,我们介绍到可以使用 altool 对应用进行分发 App Store 和公证处理。
而 altool --notarize-app
也是最早大家使用的实现自动化公证应用的方式,这在社区中也可以看到大量基于它的实践。
altool --notarize-app
主要是将应用上传到 Apple Notary Service,但是并不会告知你公证成功与否,所以通常需要结合 altool --notarization-info
命令一起使用(核对公证成功与否),整个公证的过程会是这样:
可以看到,首先我们需要使用 altool --notarize-app
将应用上传公证,然后获取本次公证的 UUID:
xcrun altool --notarize-app \
# .app 的压缩包或 .dmg
--file ./Output/Apps/FEKit.zip \
--primary-bundle-id "com.xxxx.xxxx" \
# Apple ID
--username "xxxxx" \
# 应用专用密码
--password "xxxxx" \
然后,终端中则会输出本次公证上传操作的信息和 RequestUUID
,前者用于表示本次操作执行是否成功,后者可以用于 altool --notarization-info
命令查询本次公证过程的信息:
No errors uploading 'Output/Apps/FEKit.zip'.
RequestUUID = xxxxxxx-xxx-xxxx-xxxx-xxxxx
进行完公证上传操作后,Apple Notary Service 则会对本次公证执行相关的操作,而这需要一定的时间,所以我们需要(定时)轮询执行 altool --notarization-info
命令实时地获取公证成功失败与否的信息:
# 加载 Apple ID($user)和 App 专用密码($pwd)
source "./Build/app_store_user_pwd.sh"
# 标识公证执行过程成功失败与否,失败 1,0 成功
success=1
i=0
while true; dolet i+=1echo "Checking notarize progress...$i"# 获取 altool --notarization-info 执行的输出信息process=$(xrun altool --notarization-info $uuid --username $user --password $pwd 2>&1)echo "${progress}"# 如果上次命令执行结果 $? 不等于 0(表示失败),或者命令输出信息中包含 Invalidif [ $? -ne 0 ] || [[ "${progress}" =~ "Invalid"]] thenecho "Error with notarization. Exiting"breakif# 如果命令输出信息中包含 success 表示成功if [[ "${progress}" =~ "success"]]; thensuccess=0breakelseecho "Not completed yet. Sleeping for 30 seconds.\n"fisleep 30
done
if [ $success -eq 0 ]; thenecho "Notarize successed."
if
其中,$uuid
则是执行 altool --notarize-app
命令后获取的返回结果:
echo xcrun altool --notarize-app \
--file xxxx \
--primary-bundle-id "com.xxxx.xxxx" \
--username $user \
--password $pwd \
2>&1 | grep RequestUUID |awk '{print $3}'
这里我们来看下大家比较陌生的 2>&1
、grep RequestUUID
、awk '{print $3}'
等 3 个命令的作用:
2>&1
是为了将标准错误stderr
输出重定向到标准输出stdout
grep RequestUUID
匹配标准输出中所有包含RequestUUID
的行awk '{print $3}'
打印出第 3 列的结果,在RequestUUID = xxxxxxx-xxx-xxxx-xxxx-xxxxx
就是RequestUUID
的值xxxxxxx-xxx-xxxx-xxxx-xxxxx
|
管道,用于将上个命令的输出通过管道输入到下一个命令
2.2 notarytool
相比较 altool --notarization-info
而言,notarytool
使用起来心智负担少一些,并且快于前者很多,我们只需要记忆一些 Option,使用一行命令 xcrun notarytool
则可以实现上传公证和过程信息获取的过程:
xcrun notarytool submit ./Output/Apps/FEKit.zip \
# Apple ID
--apple-id $user \
--team-id $teamId \
# 应用专用密码
--password $pwd \
-v \
-f "json"
其中,--team-id
指的是用户 ID(由数字和字母组成),这可以在本地 KeyChain 的证书中查看(或者 Apple 证书后台),-v
是 --verbose
的缩写,指的是输出公证过程的信息,-f "json"
则是表示最终结果以 JSON 的格式输出,例如:
{"path":"\/Users\/wujingchang\/Documents\/project\/demo\/FEKit\/Output\/Apps\/FEKit.zip","message":"Successfully uploaded file","id":"xxxxxxxxxxxxxxxxxxxx"
}
需要注意的是这里使用的是 Appple ID 和应用专用密码的方式做与公证服务的请求认证 Authentication,此外你还可以通过以下 3 种 Option 来进行认证:
--keychain-profile <keychain-profile>
,使用xcrun notarytool store-credentials
预先在本地钥匙串中新建一个应用程序密码,例如叫AC_PASSWORD
,那么在使用notarytool submit
命令的时候则可以直接使用--keychain-profile AC_PASSWORD
代替之前的--apple-id $user --team-id $teamId --password $pwd
--keychain <keychain>
,不同于前者--keychain-profile
这里是输入的AC_PASSWORD
文件所在的位置--key <key-id> --key-id <key-id> --issuer <issuer>
,这使用的是 App Store Connect API keys 的方式进行认证,本质上是生成一个和 App Store Connect 约定好的 JWT,然后每次请求的时候携带上它,从而通过认证
2.3 electron-notarize
electron-notarize 则是一个用 JavaScript 实现的公证工具,它的原理则是使用的 child_process
执行前面我们提及的 altool --notarization-info
和 notarytool
这 2 个命令。
electron-notarize
具名导出了 notarize
函数,我们只需要使用它以及指定的 Option 则可以完成公证的过程,这里我们来看下其函数的实现(伪代码):
// src/index.ts
export async function notarize({ appPath, ...otherOptions }: NotarizeOptions) {if (otherOptions.tool === 'notarytool') {// ...await notarizeAndWaitForNotaryTool({appPath,...otherOptions,});} else {// ....const { uuid } = await startLegacyNotarize({appPath,...otherOptions,});// ...await delay(10000);// 获取公证过程信息await waitForLegacyNotarize({ uuid, ...otherOptions });}await stapleApp({ appPath });
}
可以看到,notarize
函数会根据 Option 中传入的 tool
为 notarytool
或 legacy
来执行不同的命令来完成公证,这里前者是 notarytool
后者则是 altool --notarization-info
。所以,如果我们要用 notarytool
的方式进行公证会是这样:
import { notarize } from "electron-notarize"
await notarize({appPath: "./Output/Apps/FEKit.zip",// Apple IDappleId: "xxxxxx",// 应用专用密码appleIdPassword: "xxxxxxxx",teamId: "xxxxxxxxxxx",tool: "notarytool"
})
其中,如果你不希望密码直接明文暴露在代码中的话,electron-notarize
也支持了前面我们说的 --keychain
、--keychain-profile
等 3 个 Option,你可以根据自己的需要选择对应的认证方式。
2.4 fastlane
fastlane 是一个可以便捷地帮你完成证书管理、代码签名和发布等相关的工具,适用于 iOS、macOS 和 Android 应用。那么,我们也就可以使用 fastlane 来完成 macOS App 的公证。
首先,肯定是安装 fastlane,关于这方面的介绍官方文档讲解的很是详尽,这里就不重复论述。而当你安装好 fastlane,则可以在应用项目的根目录执行 fastlane init
来初始化它相关的配置,在初始化的过程会让你选择使用 fastlane 的方式,这里我们选择手动配置即可。
然后,它会在项目根目录下创建一个 fastlane/Fastfile
目录和文件,后续我们在执行 fastlane xxx
命令的时候则会根据该文件的代码实现执行具体的操作,默认生成的 Fastfile 文件的配置会是这样:
default_platform(:ios)
platform :ios dodesc "Description of what the lane does"lane :custome_lane do# add actions here: https://docs.fastlane.tools/actionsend
end
其中,default_platform
用于定义一个默认的平台 Platform,例如当我们有 2 个平台(iOS 和 macOS)的时候,它的的配置需要这样:
default_platform(:ios)
platform :ios dodesc "Description of what the lane does"lane :custome_lane do# add actions here: https://docs.fastlane.tools/actionsend
end
platform :mac dodesc "Description of what the lane does"lane :custome_lane do# add actions here: https://docs.fastlane.tools/actionsend
end
此时,如果我们执行 fastlane custome_lane
,由于这里平台默认为 ios
,所以则会执行 platorm:ios
下的 custome_lane
,反之执行 fastlane mac custome_lane
,则是 platform :mac
下的 custome_lane
。那么,对于前面我们这个例子而言只需要 platform:mac
:
default_platform(:ios)
platform :mac dodesc "Description of what the lane does"lane :custome_lane do# add actions here: https://docs.fastlane.tools/actionsend
end
接着,则可以在 platform:mac
写我们需要实现的自动化分发 App Store 相关的代码。fastlane 便捷之处在于它实现了很多开箱即用的 Action,这里我们需要使用 notarize 这个 Action,它可以用于完成 macOS App 的公证:
default_platform(:mac)
platform :mac dodesc "Notarizes a macOS app"lane :notarize_app donotarize(package: "./Output/Apps/FEKit.zip",use_notarytool: "xcrun notarytool",bundle_id: "com.xxxx.xxxx",username: "xxxxxxxxxxxxxx",verbose: true,)end
end
其中,在使用 notarize
的时候需要注意的是,这里只是声明了你 App Store 的用户名 username
,而专用密码需要预先在系统环境变量中添加 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
(其他认证方式,有兴趣的同学可以自行了解),然后 fastlane 在执行 notarize_app
Action 时会去读取该环境变量,从而进行并完成后续的公证过程。
结语
看到这里,我想可能会有同学会问:“这几种实现公证的工具,选择哪个比较好?”,这里比较建议的是选择 fastlane,因为,除开前面提及它的使用方式非常便捷的优点,它具备的能力也很多,不仅仅可以做 App 公证,还可以做 App Store 分发、证书和版本管理等,所以,选择 fastlane 将来也可以支持我们别的诉求,何乐不为呢?
最后,如果文中存在表达不当或错误的地方,欢迎各位同学提 Issue ~
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享