OpenClaw macOS 应用开发与签名实战指南:从快速开发到分发打包
2026/9/14 21:33:35 网站建设 项目流程

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)下脚本做了三件关键的事:

  1. 设置ALLOW_ADHOC_SIGNING=1SIGN_IDENTITY="-",让打包脚本走 ad-hoc 签名;
  2. ~/.openclaw/disable-launchagent写入标记文件,禁止应用写 launchd 代理;
  3. 通过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 ApplicationApple DistributionApple 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/OpenClaw

Profile 命名规则

Profile 名称必须是 1–64 位小写字母、数字、下划线或连字符,且必须以字母或数字开头。以下名称有特殊语义:

  • default:普通应用(等价于未设置 Profile);
  • gatewaymacnode:保留的 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.tsresolveGatewayPort保持字节级一致,确保应用与 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-lockfilepnpm 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.sh

package-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()按以下优先级自动选择签名身份:

  1. Developer ID Application
  2. Apple Distribution
  3. Apple Development
  4. 第一个可用的有效签名身份

若一个都找不到:默认直接报错;设置ALLOW_ADHOC_SIGNING=1SIGN_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-OTeamIdentifier。只要有一个内嵌二进制 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=1SKIP_DMG=1SKIP_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询