OpenClaw macOS 应用开发与签名实战指南:从快速开发到分发打包
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
本篇指南以 apps/macos/README.md 为核心,系统讲解 OpenClaw 官方 macOS 桌面应用在开发、签名、打包、测试与发布全流程中的工程实践。你将掌握restart-mac.sh快速开发循环、命名 Profile 多实例隔离、原生测试的安全运行方式、开发包与分发产物的构建差异,以及代码签名、Team ID 审计、Sparkle 更新库校验等 macOS 特有的踩坑点与绕过方案。
快速开发运行:restart-mac.sh
在仓库根目录执行一个命令即可完成"杀掉旧实例 → 重建 → 重新打包 → 重新启动 → 验证存活"的完整开发循环:
scripts/restart-mac.sh从 restart-mac.sh 的源码结构可以看到,该脚本依次执行:获取进程级互斥锁(防止并发重启互相干扰)、停止所有已知的 OpenClaw 实例(包括dist/OpenClaw.app、/Applications/OpenClaw.app以及 Swift 构建产物),打包插件资源(pnpm plugins:assets:build),在临时目录中完成应用打包与签名验证(codesign --verify --deep --strict),最后以最小化环境变量启动应用并确认进程存活。
常用启动选项
| 选项 | 作用 | 适用场景 |
|---|---|---|
--no-sign | 跳过正式代码签名,走 ad-hoc 签名 | 最快开发路径,但 TCC 权限(如辅助功能、屏幕录制)不会持久保留 |
--sign | 强制代码签名 | 需要真实证书时才用,没有证书会直接失败 |
--background-only | 保持服务运行但不弹出任何自动窗口 | 无人值守 / 后台任务场景 |
--attach-only | 跳过 launchd 安装,仅以附加模式启动应用 | 外部进程已托管本地 Gateway 时 |
--wait/-w | 等待其他正在进行的重启完成而不是直接退出 | 并发触发重启脚本时 |
--target-only | 只重启当前 checkout 的 dist 应用,若其他 OpenClaw 实例活跃则失败 | 需要精确定位本仓库构建产物时 |
几个选项可以组合使用,例如:
scripts/restart-mac.sh --no-sign # 最快的开发路径 scripts/restart-mac.sh --sign # 强制代码签名(需要证书) scripts/restart-mac.sh --background-only # 后台运行,不弹窗 scripts/restart-mac.sh --attach-only --background-only # 外部托管 Gateway 时的无人值守模式注意--sign与--no-sign不能同时使用,脚本会直接报错。
无签名模式的内部机制
无签名模式(--no-sign)下脚本做了三件关键的事:
- 设置
ALLOW_ADHOC_SIGNING=1与SIGN_IDENTITY="-",让打包脚本走 ad-hoc 签名; - 在
~/.openclaw/disable-launchagent写入标记文件,禁止应用写 launchd 代理; - 通过
node openclaw.mjs daemon install --force --runtime node安装并重启 Gateway 守护进程,使 Gateway LaunchAgent 指向仓库 CLI,随后读取~/.openclaw/openclaw.json中的gateway.port(默认 18789)并验证端口在监听。
无签名恢复命令
开发过程中如果进入了无签名状态,可以用以下命令恢复:
node openclaw.mjs daemon install --force --runtime node node openclaw.mjs daemon restart若需要重置无签名的覆盖配置(恢复 launchd 代理写入),删除标记文件即可:
rm ~/.openclaw/disable-launchagent自动签名检测
默认行为是自动检测签名密钥:若系统中存在Developer ID Application、Apple Distribution或Apple Development证书则自动签名,否则回退到--no-sign。检测逻辑位于 restart-mac.sh 的check_signing_keys(),通过security find-identity -p codesigning -v实现。
应用 Profile:多实例隔离运行
OpenClaw 支持通过环境变量启动一个独立配置的应用实例,Profile 名称与 CLI 使用的 profile 同名:
OPENCLAW_PROFILE=work /Applications/OpenClaw.app/Contents/MacOS/OpenClawProfile 命名规则
Profile 名称必须是 1–64 位小写字母、数字、下划线或连字符,且必须以字母或数字开头。以下名称有特殊语义:
default:普通应用(等价于未设置 Profile);gateway、mac、node:保留的 LaunchAgent 身份标识,不可用作普通 Profile。
Profile 隔离了什么
从 AppProfile.swift 的实现可以确认,命名 Profile 会建立一套完整的隔离边界:
- 状态目录:
~/.openclaw-<name>; - 应用默认配置域(
UserDefaultssuite):ai.openclaw.<name>.profile.<name>; - Keychain 服务:附加
.profile.<name>后缀; - 单实例锁:
/tmp/openclaw-<uid>-app-instances/ai.openclaw.mac.profile.<name>.lock; - CLI 管理的 Gateway 服务:
ai.openclaw.<name>。
稳定的 Profile 端口推导
默认网关端口 18789 仅在 default Profile 下使用。命名 Profile 会基于名称哈希推导一个稳定端口(20000–59999 区间),其哈希算法在 AppProfile.swift 中与src/config/paths.ts的resolveGatewayPort保持字节级一致,确保应用与 CLI 能连到同一个 Profile Gateway。当然,通过配置或环境变量显式指定端口总是优先的。
Profile 模式的限制
- restart-mac.sh 明确拒绝处理命名 Profile(
restart-mac.sh cannot safely target one app profile),因为其打包清理是主机全局的;请正常构建/打包后,用上面的命令直接启动命名 Profile; - Profile 模式下禁用应用重定位、Sparkle 更新以及更新后的服务修复;更新已安装应用请走默认 Profile 的正常流程;
- Profile 模式不会安装或修改主机全局的 Mac 节点服务或 OpenClaw 登录项,运行时子节点仍照常在进程内运行。
注意:Profile 不是测试沙箱
PortGuardian 会在所有应用实例间共享隧道账本与孤儿清理逻辑,端口预留也会检查其他 Profile 的 Gateway 服务占用(见 ProfileGatewayPortReservation.swift)。当验证场景不允许访问或改动操作者状态时,请使用干净的测试账户或虚拟机,而非命名 Profile。
原生测试:如何安全地跑通 AppKit / WebKit 套件
测试安全性的核心原则
OpenClaw 的原生测试涉及偏好设置、Keychain、AppKit 窗口与 WebKit 辅助进程,仅靠测试过滤或临时HOME远远不够。官方建议只在一次性的 macOS CI 或虚拟机(无操作者凭据、无活动 Gateway)中运行完整测试套件;本地子集测试同样需要经过验证的 OS 沙箱以及测试专用的资源。
CI 中的隔离机制
macos-swiftCI 任务通过 scripts/test-macos-native.mts 运行测试:完整套件保留默认 Profile 行为,命名 Profile 的 AppState 隔离测试单独运行。每个测试进程都获得:
- 独立的私有 home 目录与 config/state 路径;
- 短时
TMPDIR(对遵守该变量的工具生效,Foundation 则仍使用 Darwin 每用户临时目录,TestIsolationfixture 会在其中自建并清理独有目录); - 一个未加锁的一次性 Keychain,被设为用户域默认与搜索列表,使目录迁移无需弹出创建登录钥匙串的提示;该 Keychain 只在测试资源上禁用自动锁定,测试进程组退出后即被删除;
- 失败的或未经验证的清理会保留资源并导致启动失败(fail-closed)。
注意:launcher 本身并不是沙箱;本地运行 scripts/prepush-ci.sh 会保留 Swift lint/构建检查,但会将原生测试证据标记为"不完整",并要求提供精确 commit 对应的macos-swiftCI 结果。
打包流程:开发包与分发产物的区别
开发包(签名但不公证)
scripts/package-mac-app.sh该脚本构建并组装dist/OpenClaw.app,通过 scripts/codesign-mac-app.sh 签名。从 package-mac-app.sh 可以看到它做了相当多的工作:先跑pnpm install --frozen-lockfile与pnpm build,校验 JS 构建来源(dist/build-info.json的 version/commit/buildAt 必须匹配),调用 scripts/build-mac-swift.mts 构建 Swift 二进制,随后组装 Info.plist、复制 macOS 控制 CLI(openclaw-mac)、MLX TTS 本地语音助手、Sparkle.framework、CUA 驱动、cloudflared、CLI 安装器、原生 Node worker、Control UI 资源与 SwiftPM 资源包等,最后签名并校验。开发包不是分发产物。
关键的打包环境变量:
BUNDLE_ID:默认ai.openclaw.mac.debug;debug 后缀会清空SUFeedURL并关闭 Sparkle 自动检查;BUILD_CONFIG:默认debug;release 强制要求构建元数据并做源代码校验(apple-release-source-check.sh);BUILD_ARCHS:release 默认all(即 arm64 + x86_64 通用二进制),debug 默认当前机器架构;OPENCLAW_SKIP_MLX_TTS=1:跳过 MLX 语音助手(release 构建禁止,公证会校验它);OPENCLAW_PACKAGE_APP_ROOT:指定输出 bundle 根,但必须位于dist/下。
分发产物(ZIP + DMG,含公证)
scripts/package-mac-dist.shpackage-mac-dist.sh 在开发包基础上继续产出三类分发产物(输出到dist/):
OpenClaw-<version>.zip:Sparkle 更新用 ZIP;OpenClaw-<version>.dmg:给用户的磁盘映像;OpenClaw-<version>.dSYM.zip:符号文件。
该脚本默认 release 配置与发布 Bundle IDai.openclaw.mac,通过 scripts/notarize-mac-artifact.sh 走 Apple 公证(notarization)并回填 staple。它内置了不可中断的恢复检查点:若公证中途失败,可用--resume-notarization从断点恢复;每次新构建前若检测到未完成的检查点,会强制要求先恢复或清理。release 构建还会强制校验CFBundleVersion不低于 Sparkle 规范构建号下限,防止更新回退。
无人值守的 Peekaboo 提权宿主(内部工作流)
对于无人值守的 Peekaboo 提权宿主,使用闭源 Foundation 签名 profile 与按源码地址寻址(source-addressed)的 ZIP 工作流。package是内部发布操作者命令,需要 OpenClaw Foundation 签名身份与公证凭据,其归档不是通用下载产物:
scripts/mac-elevation-host.sh package \ --peekaboo-source-commit <full-peekaboo-sha> cd dist/elevation-host export PREFIX="OpenClaw-<full-openclaw-sha>-Peekaboo-<full-peekaboo-sha>-stable" export INSTALLER_SHA256="<authenticated-installer-sha256>" export RECEIPT_SHA256="<authenticated-receipt-sha256>" [[ "$(shasum -a 256 "$PREFIX-installer.sh" | awk '{print $1}')" == "$INSTALLER_SHA256" ]] || exit 1 shasum -a 256 -c "$PREFIX.zip.sha256" shasum -a 256 -c "$PREFIX-installer.sh.sha256" ./"$PREFIX-installer.sh" verify \ --archive "$PREFIX.zip" \ --receipt "$PREFIX.json" \ --receipt-sha256 "$RECEIPT_SHA256" ./"$PREFIX-installer.sh" migration-plan \ --migrate-launch-agent "$HOME/Library/LaunchAgents/ai.openclaw.node.plist" ./"$PREFIX-installer.sh" install \ --archive "$PREFIX.zip" \ --receipt "$PREFIX.json" \ --receipt-sha256 "$RECEIPT_SHA256" \ --migrate-launch-agent "$HOME/Library/LaunchAgents/ai.openclaw.node.plist" ./"$PREFIX-installer.sh" status --state-dir "<existing-state-dir>"该提权包的关键特性:
- 仅 ZIP、已公证并 staple,内容恰好是
OpenClaw.app,不含 Apple Events entitlement; - 记录不可变 receipt(immutable receipt),并校验一份全新解压的副本;
- 同一套按源码地址寻址的产物包含从精确 Git commit 拷贝的可移植安装器,外加独立的归档与安装器校验和文件;
- 目标 Mac 不需要源码 checkout;发布操作者必须通过已验证的交接通道交付 receipt SHA-256,与单独验证的安装器摘要配合,构成内部工作流信任边界的一部分(可移植安装器不在应用代码签名覆盖范围内,因此这种显式双摘要交接是必要的)。
安装前提与行为:
- 安装要求存在应用可读的远程 Gateway 配置,以及在选定状态目录中已配对的 macOS 节点身份;
- 修改 CLI 管理的节点 LaunchAgent 前先用
migration-plan;当前正在后台运行且没有 LaunchAgent 的应用,使用显式的--adopt-running-app计划/安装选项; - 安装器不复制任何 token 或密码:只保留状态与配置的属主路径,并要求同一节点身份以新应用版本和 computer-use 能力重连为
openclaw-macos/node后才提交; - 安装会单独拥有
ai.openclaw.mac.elevation-host这个 launchd 任务(RunAtLoad+KeepAlive),拒绝替换或抢占常规的登录时ai.openclaw.mac任务; recover在切换失败后恢复记录的旧 bundle;uninstall只移除提权任务,保留应用、状态、Keychain、TCC 与恢复 receipt;- 安装只有在 launchd 托管的进程同时达到 Bridge-ready 并作为期望的 computer-use 节点重连 Gateway 后才算成功;缺失 TCC 授权在
status中显示为降级状态; - 托管升级使用代际唯一的 plist 与 receipt 备份;回滚后
status与同产物重装仍然有效。
签名行为与身份选择
签名身份自动选择顺序
codesign-mac-app.sh 的select_identity()按以下优先级自动选择签名身份:
- Developer ID Application
- Apple Distribution
- Apple Development
- 第一个可用的有效签名身份
若一个都找不到:默认直接报错;设置ALLOW_ADHOC_SIGNING=1或SIGN_IDENTITY="-"可回退到 ad-hoc 签名。ad-hoc 签名下脚本会输出醒目警告:macOS 将 TCC 权限(辅助功能、屏幕录制等)绑定到代码签名、Bundle ID 与路径,而 ad-hoc 每次构建都会生成新签名,导致系统把应用当作新二进制、遗忘已授予的权限,需要每次重启后重新授权,个别权限甚至要重启 macOS 才重新出现。
签名细节
- 非 ad-hoc 签名一律附加
--options runtime(Hardened Runtime)与时间戳; CODESIGN_TIMESTAMP支持auto|on|off:auto 模式仅在身份是 Developer ID Application 时启用时间戳,ad-hoc 强制--timestamp=none;- 时间戳服务偶发失败时自动重试(
CODESIGN_TIMESTAMP_RETRY_ATTEMPTS=8、退避延迟CODESIGN_TIMESTAMP_RETRY_DELAY_SECONDS=5); - 标准应用 entitlements 包含 Apple Events、音频输入、摄像头、定位(
com.apple.security.automation.apple-events等),而 elevation-host 变体使用空 entitlements 且禁止Apple Events; - 嵌入式 Node worker 中的
node与 Claude Agent SDK CLI 使用带 JIT 权限的专属 entitlements(com.apple.security.cs.allow-jit等); - 签名顺序是"先内后外":控制 CLI、MLX TTS 助手、CUA 驱动、cloudflared、node-worker、Sparkle 框架、其余 framework/dylib,最后才签整个 bundle;
- 所有原生可执行文件与库必须持有真实 Mach-O 签名,通用签名(generic signatures)会被拒绝。
Team ID 审计:Sparkle 更新不匹配的守门员
签名完成后,脚本会读取应用 bundle 的 Team ID,并比对 bundle 内每一个 Mach-O的TeamIdentifier。只要有一个内嵌二进制 Team ID 不同,签名即失败。这正是 Sparkle 更新机制所要求的:更新框架加载的代码必须与宿主应用属于同一团队。
跳过审计(不推荐用于发布):
SKIP_TEAM_ID_CHECK=1 scripts/package-mac-app.sh注意即使跳过,原生格式检查仍然保留。elevation-host 变体禁止跳过 Team ID 检查,并且会在签名阶段就验证 Team ID 与 Authority 必须是Developer ID Application: OpenClaw Foundation (FWJYW4S8P8),同时拒绝 bundle 内出现 CUA 驱动,从而把身份失败挡在 Apple 公证提交之前。
库校验绕过方案(仅限开发)
Sparkle 的 Team ID 不匹配会阻塞框架加载,这在 Apple Development 证书(而非 Developer ID)下尤为常见。开发时可选择加入:
DISABLE_LIBRARY_VALIDATION=1 scripts/package-mac-app.sh该开关会向应用 entitlements 注入com.apple.security.cs.disable-library-validation。只用于本地开发,发布构建必须保持关闭;elevation-host 变体直接禁止该开关。非开发构建遇到 Team ID 不匹配时,正确的做法是重新签名内嵌框架,而不是关闭库校验。
常用环境变量速查
以下环境变量贯穿打包与签名流程,按用途分类:
签名身份与开关
SIGN_IDENTITY="Apple Development: Your Name (TEAMID)":显式指定签名身份(也支持 40 位证书哈希);ALLOW_ADHOC_SIGNING=1:无身份时回退 ad-hoc 签名(TCC 权限不持久,见上文警告);CODESIGN_TIMESTAMP=off:离线调试时关闭时间戳;DISABLE_LIBRARY_VALIDATION=1:开发专用的 Sparkle 库校验绕过;SKIP_TEAM_ID_CHECK=1:绕过 Team ID 一致性审计。
打包控制
BUILD_CONFIG=debug|release:构建配置,release 强制来源校验与元数据;BUILD_ARCHS=all|arm64|x86_64:目标架构,release 默认通用二进制;BUNDLE_ID=ai.openclaw.mac.debug|ai.openclaw.mac:Bundle ID,.debug后缀关闭 Sparkle 更新;SKIP_NOTARIZE=1、SKIP_DMG=1、SKIP_DSYM=1:跳过公证 / DMG / 符号产物(package-mac-dist.sh);OPENCLAW_SKIP_MLX_TTS=1:跳过 MLX TTS 助手(release 禁止);OPENCLAW_PACKAGE_APP_ROOT=<path>:指定打包输出目录(须在dist/下)。
总结
OpenClaw 的 macOS 工程链路是一条"开发循环 → 实例隔离 → 安全测试 → 打包签名 → 公证分发"的完整流水线:restart-mac.sh 让开发者一条命令完成重建重载,命名 Profile 借助 AppProfile.swift 实现状态、Keychain 与端口的全隔离,原生测试通过一次性 Keychain 与私有 home 满足 AppKit/WebKit 测试的隔离要求,而 package-mac-app.sh、codesign-mac-app.sh 与 package-mac-dist.sh 则把签名审计、Team ID 一致性检查、Sparkle 兼容与公证恢复全部固化进脚本。理解这套机制后,无论是日常开发调试、多实例并行,还是准备一次可公证的 macOS 发布,都能在正确的路径上少走弯路。
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考