ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

macOS 签名与公证实战:oh-my-pi 如何让 omp 二进制通过 Gatekeeper 并满足 Homebrew 提交前提

macOS 签名与公证实战:oh-my-pi 如何让 omp 二进制通过 Gatekeeper 并满足 Homebrew 提交前提 macOS 签名与公证实战oh-my-pi 如何让 omp 二进制通过 Gatekeeper 并满足 Homebrew 提交前提【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本篇技术指南围绕 oh-my-pi 仓库的 macos-signing-notarization.md 展开完整讲解项目如何为编译产物ompmacOS 二进制接入 AppleDeveloper ID Application证书签名与notarytool 公证使其满足 Gatekeeper 要求并成为官方 Homebrew 提交的前置条件。读完本文你将掌握签名/公证在 CI 中的完整流水线、为什么这三条 entitlements 缺一不可、裸 Mach-O 无法 stapling 的边界约束、五个APPLE_*GitHub Secrets 的生成与无泄漏上传方式以及本地复现整套签公证流程的干跑方法。背景为什么 omp 需要签名与公证oh-my-pi 的 macOS 产物是编译后的单文件omp可执行文件发布在 GitHub Releases 上。用户拿到它之后macOS 的Gatekeeper会根据代码签名与 Apple 公证票据判断是否允许运行。要让用户体验顺畅双击不弹无法验证开发者需要同时满足两件事用Developer ID Application证书签名而不是仅 ad-hoc 签名通过 Apple 的notarization公证让 Apple 服务器为这个二进制签发票据。文档明确指出签名 公证后的产物Gatekeeper-acceptable并且是官方 Homebrew 提交的前提对应仓库 issue #776。也就是说这一步不仅是质量工程还直接关系到分发渠道的准入。整体流程CI 中三步完成签名、公证与验证签名发生在 CI 中release_binary_hosted矩阵的 Darwin 分支工作流定义见 .github/workflows/ci.yml。整个流程分为三步ci:release:build-binaries构建并做 ad-hoc 签名ad-hoc 签名让二进制能在构建 runner 上直接运行构建机没有 Developer ID 身份。scripts/ci-macos-sign.sh做正式签名与公证将 Developer ID 证书导入一个临时钥匙链throwaway keychain使用codesign --options runtime --timestamp重新签名启用 hardened runtime 安全时间戳并附加--entitlements scripts/macos-entitlements.plist在新签名下运行--version与--smoke-test快速失败fail fast通过notarytool submit --wait提交公证。release_github_verify对已发布的产物做最终验证重新下载发布的 arm64 资产执行codesign --verify --strict和两次启动检查当签名 Secrets 已配置时还会断言签名不是ad-hoc。CI 的自动跳过机制在 .github/workflows/ci.yml 中工作流通过环境变量MACOS_SIGNING控制签名步骤是否执行env: MACOS_SIGNING: ${{ secrets.APPLE_CERTIFICATE_P12 ! secrets.APPLE_CERTIFICATE_PASSWORD ! secrets.APPLE_API_KEY_ID ! secrets.APPLE_API_ISSUER_ID ! secrets.APPLE_API_KEY ! }}只有当全部五个APPLE_*仓库 Secret 都已配置时MACOS_SIGNING才为true签名步骤才会运行- name: Sign and notarize macOS binary (Developer ID) if: matrix.platform darwin env.MACOS_SIGNING true run: bash scripts/ci-macos-sign.sh ${{ matrix.binary_path }}因此没有配置凭据时发布依然照常进行产物保持 ad-hoc 签名配置了凭据后签名自动接棒。需要特别区分的是工作流会跳过但脚本本身不会跳过——scripts/ci-macos-sign.sh在缺少任何一个必需环境变量时直接报错退出见下文。深入 ci-macos-sign.sh临时钥匙链、签名与公证的实现scripts/ci-macos-sign.sh 是签名/公证的落地脚本使用方式为scripts/ci-macos-sign.sh path-to-binary它要求OSTYPE为darwin*必须在 macOS 上运行并要求二进制文件存在。脚本最关键的工程决策是全程使用一次性临时钥匙链WORKDIR$(mktemp -d) KEYCHAIN$WORKDIR/omp-signing.keychain-db KEYCHAIN_PASSWORD$(openssl rand -hex 24)流程细节如下导入凭据将 base64 的.p12与.p8解码到临时目录创建并配置临时钥匙链security create-keychain创建钥匙链set-keychain-settings -lut 21600设置 6 小时自动重锁作为安全网脚本 EXIT trap 会提前删除它随后把该钥匙链前置到用户搜索列表保留 runner 原有钥匙链让codesign能解析到身份导入证书并授权 codesignsecurity import $CERT_PATH -P $APPLE_CERTIFICATE_PASSWORD -k $KEYCHAIN \ -T /usr/bin/codesign -T /usr/bin/security security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD $KEYCHAINset-key-partition-list这一步让codesign可以非交互地访问导入的私钥否则 CI 环境会在签名时卡在钥匙串解锁弹窗上。自动选择身份脚本从钥匙链中取第一个Developer ID Application身份无需把身份字符串或 Team ID 存为 SecretIDENTITY$(security find-identity -v -p codesigning $KEYCHAIN \ | awk -F /Developer ID Application/ {print $2; exit})重新签名hardened runtime 时间戳 entitlementscodesign --force --timestamp --options runtime \ --entitlements $ENTITLEMENTS \ --sign $IDENTITY \ $BINARY签名后立即验证codesign --verify --strict --verbose4并打印Authority/TeamIdentifier/flags/Timestamp便于排障。在隔离 HOME 中做启动冒烟脚本使用HOME$run_home XDG_DATA_HOME$run_home/xdg运行--version和--smoke-test。这是特意在公证慢路径之前做的 fail-fast——hardened runtime 下缺失 entitlement 的二进制依然能签成功但会在启动时崩溃。公证提交先ditto -c -k --keepParent打包 zipnotarytool接受压缩包再提交并等待结果xcrun notarytool submit $ZIP_PATH \ --key $API_KEY_PATH \ --key-id $APPLE_API_KEY_ID \ --issuer $APPLE_API_ISSUER_ID \ --wait \ --timeout 30m \ --output-format json脚本解析 JSON 输出仅当状态为Accepted才成功否则拉取xcrun notarytool log submission-id打印失败原因并退出非零。为什么必须做--smoke-test启动检查--smoke-test不是普通 CLI 开关它是omp分发形态的最小端到端自检。从 packages/coding-agent/src/cli.ts 的注释与实现可以看到它会拉起各类 bundled workerssync/stats 统计、tiny-title、STT 语音转写、JS eval、computer 工具、TTS、mnemopi 向量嵌入、daemon broker、LSP mux、blob broker、terminal-output worker本地起一个 stats dashboard 服务并请求首页 HTML全部通过后输出smoke-test: ok。--version与普通 help 路径不会 spawn 这些 worker 模块因此--smoke-test是唯一能证明编译产物 运行时提取的原生模块在 hardened runtime 下真正可用的快速探测——这正是签名后必须先跑它的原因。为什么这三条 Entitlements 是强制项omp是Bun 单文件可执行程序运行时依赖 JavaScriptCore。entitlements 定义见 scripts/macos-entitlements.plist三条缺一不可Entitlement原因com.apple.security.cs.allow-jitJavaScriptCore 在运行时执行 JIThardened runtime 会杀掉MAP_JIT页面没有该 entitlement 程序无法启动。com.apple.security.cs.allow-unsigned-executable-memoryJSC 的可执行内存页需要此权限。com.apple.security.cs.disable-library-validationomp会把原生 addonpi_natives.triple.node及其他可选 dylib 解压到运行时缓存并dlopen()加载这些库与主二进制不共享 Team ID没有该 entitlement 时 hardened runtime 会报mapping process and mapped file have different Team IDs导致几乎所有命令直接崩溃。最后一条最隐蔽没有disable-library-validation时签名和公证都能成功但产物在第一次真实使用时必定失败。这正是ci-macos-sign.sh在公证前执行--smoke-test的目的——让这类问题在进入 Apple 公证队列之前就被拦截。上述原生 addon 的运行时提取与加载机制与仓库中 crates/pi-natives 的 natives 架构对应详见 docs/natives-architecture.md。Stapling 限制重要边界一个裸 Mach-O 可执行文件无法被 stapling——stapler只支持.app/.pkg/.dmg。这意味着二进制确实是真公证过的notarytool返回Accepted票据按 cdhash 存在 Apple 服务器上但票据必须在线获取而不是从可执行文件内直接读取。release_github_verify会打印spctl -a -t exec -vv的结果用于可见性但不会以它作为发布门禁未 stapling 的裸二进制在在线票据不可用时spctl可能返回非零——这本身不代表签名或凭据失败。对实际分发路径的含义curl ... | sh管道安装如curl https://omp.sh/install | shcurl不设置 quarantine隔离位Gatekeeper 根本不参与无需票据Homebrew formula 安装Homebrew 不对 formula 文件设置 quarantineGatekeeper 不参与任何会设置 quarantine 的方式浏览器下载、Homebrew cask需要 Apple 在线票据查询。如果要产出可离线分发的工件应把二进制包装成可 stapling 且已公证的.pkg或.dmg用xcrun stapler staple但这对curl/formula 两条路径并非必需。五个 GitHub Secrets字段与含义在Settings → Secrets and variables → Actions中添加仓库 Secret。五个证书 密码 API Key 三元组必须全部存在签名才会生效Secret含义APPLE_CERTIFICATE_P12导出的 Developer ID Application.p12证书 私钥的 base64。APPLE_CERTIFICATE_PASSWORD导出.p12时设置的密码。APPLE_API_KEY_IDApp Store Connect APIKey ID。APPLE_API_ISSUER_IDApp Store Connect APIIssuer IDUUID。APPLE_API_KEYApp Store Connect.p8私钥的 base64。生成凭据文件把凭据文件放进一个工作目录默认~/omp-signing文件生成方式*.p12钥匙串访问Keychain Access→ 右键你的Developer ID Application: …身份展开后带私钥的那个证书条目→导出…→ 存为.p12并设置密码。p12-password.txt刚才给.p12设置的密码。AuthKey_KEYID.p8App Store Connect →Users and Access → Integrations → App Store Connect API→ 创建密钥Account Holder角色也可创建 API 证书Developer角色对公证已足够→仅可下载一次不可恢复。issuer-id.txt密钥列表上方显示的Issuer IDUUID。key-id.txt可选—— Key ID不提供则从.p8文件名读取。有一个关键事实值得强调App Store Connect API key 是唯一无法从 CLI 铸造的凭据——它是 API 本身的引导凭据.p8只能下载一次其余凭据都可以在本地生成。无泄漏上传凭据到 GitHub Actionsscripts/ci-macos-upload-secrets.sh 负责把凭据上传为 GitHub Secrets设计目标是任何 Secret 值都不出现在终端、argv 或 shell 历史中。它先做本地校验用find_one确保目录里恰好一个*.p12和一个*.p8通过security import把.p12导入一次性钥匙链确认里面确有 Developer ID Application 身份——脚本注释特别说明刻意不用openssl pkcs12因为 OpenSSL 3.x 读不了钥匙串访问仍在用的旧 RC2-40-CBC 算法而security import可以检查.p8是否为 PEM 私钥grep BEGIN PRIVATE KEY未提供key-id.txt时从文件名AuthKey_KEYID.p8推导 Key ID。随后通过gh secret setstdin管道逐个上传值永远不会进入进程参数scripts/ci-macos-upload-secrets.sh ~/omp-signing --dry-run # 先校验不上传 scripts/ci-macos-upload-secrets.sh ~/omp-signing # 上传全部五个 gh secret list --repo can1357/oh-my-pi # 确认证书续期后重跑该脚本即可。脚本也支持OMP_REPO环境变量指定目标仓库、OMP_SIGNING_DIR指定凭据目录。查找签名身份 / Team ID自查security find-identity -v -p codesigning # 例如 Developer ID Application: Your Name (TEAMID1234)ci-macos-sign.sh会自动选择第一个Developer ID Application身份因此不需要把身份字符串或 Team ID 存为 Secret。本地干跑完整签名 公证流程无需改动 CI可在 macOS 本机用真实证书 API Key 完整走一遍签名与公证链路先构建二进制再导出五个环境变量并执行签名脚本RELEASE_TARGETSdarwin-arm64 bun run ci:release:build-binaries APPLE_CERTIFICATE_P12… APPLE_CERTIFICATE_PASSWORD… \ APPLE_API_KEY_ID… APPLE_API_ISSUER_ID… APPLE_API_KEY… \ bash scripts/ci-macos-sign.sh packages/coding-agent/binaries/omp-darwin-arm64注意脚本会检查OSTYPE必须以darwin开头且目标二进制路径必须真实存在凭据以 base64 传入与 CI 中 GitHub Secrets 的注入方式完全一致。发布后的线上验证release_github_verify在 .github/workflows/ci.yml 中release_github_verifyjob 在 macOS runner 上执行发布后验证从 GitHub Releases 重新下载已发布的omp-darwin-arm64codesign -dvvv打印签名详情codesign --verify --strict --verbose4严格校验在隔离 HOME 下运行--version与--smoke-test确认发布产物可启动当签名 Secrets 已配置时额外断言if codesign -dvvv ./omp-darwin-arm64 21 | grep -qE flags.*adhoc|Signatureadhoc; then echo published binary is still ad-hoc signed (Developer ID signing did not run) 2 exit 1 fi这一步把签名步骤是否真的发生了变成硬性门禁——防止出现配置了 Secret 但发布出去的仍是 ad-hoc 二进制这类静默回归。最后运行spctl -a -t exec -vv做 Gatekeeper 评估结果仅作展示注释明确说明这是 informational不 gate 发布。小结oh-my-pi 的 macOS 签名与公证体系可以概括为四个关键词自动跳过凭据齐备才启用、fail-fast公证前先--smoke-test拦截 entitlement 问题、临时钥匙链凭据用完即毁不污染 runner、发布后反证release_github_verify断言产物非 ad-hoc。对维护者而言本文涉及的全部脚本与配置都在仓库内可复现签名脚本 scripts/ci-macos-sign.sh、entitlements scripts/macos-entitlements.plist、上传脚本 scripts/ci-macos-upload-secrets.sh、CI 编排 .github/workflows/ci.yml以及--smoke-test的实现 packages/coding-agent/src/cli.ts。若你的项目同样分发 Bun 单文件 macOS 可执行文件这套Developer ID hardened runtime 在线票据 双阶段验证的模板可以直接照搬。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表