跳转至

发布 OpenBot

对于通过 GitHub Actions 上传到 TestFlight 的 iOS 构建,请参阅 移动端发布指南。对于 上传到 Google Play 的 Android 构建,请参阅 Google Play 发布指南。 移动端工作流与下文所述的桌面端标签发布是相互独立的。

OpenBot 更新通过 GitHub Releases 发布,并使用 electron-updater 安装。 macOS 要求每个可自动更新的构建都必须使用 Developer ID Application 证书签名。 发布工作流还会在发布 macOS 应用之前对其进行公证和装订。Windows x64 标签发布使用 Azure Artifact Signing 签名(请参阅 Windows 签名)。Linux 发布包不带签名,Linux AppImage 同样不携带任何签名。 所有三个平台都必须通过后,才会发布一个版本。发布还需要固定的 Sunshine 和 Moonlight Web 运行时工件。GitHub Actions 会下载这些工件,校验 SHA-256,并 在最终 OpenBot 包中验证它们的原生可执行文件。发布包不会在开发者机器上构建。

Codex、Claude 和 Grok 运行时是可选的下载项。它们不属于应用程序包的一部分。 发布 CI 会下载固定的 macOS、Windows 和 Linux 提供商工件作为控制工件。它会 校验这些工件的 SHA-256 值、版本、许可证和供应商签名,但不会将它们复制到 OpenBot 中。Linux 没有可校验的代码签名契约,因此其提供商工件仅通过 SHA-256 和版本进行验证。

已安装的应用不会为了获得新的提供商 CLI 而等待发布:它们会提供最新的上游 版本(请参阅 提供商 CLI 更新)。固定版本是 首次安装时的回退版本。要阻止有问题的上游版本,请将其版本添加到 provider-runtime-blocklist.json 中相应提供商的列表中,并将其合并到 main 分支。应用会在 下次检查时读取该列表。被阻止的版本不再提供,但它仍会保留在已经安装了它的计算机上。

一次性 GitHub 设置

在 nightly-labs/openbot 中创建 release 环境,然后添加以下环境密钥:

  • CSC_LINK — 一个 base64 编码的 Developer ID Application .p12 文件。
  • MAC_PROVISIONING_PROFILE — base64 编码的 Developer ID 配置文件,用于 app.openbot.desktop,包含 applinks:openbot.run 授权。
  • CSC_KEY_PASSWORD — 该应用程序 .p12 文件的导出密码。
  • CSC_INSTALLER_LINK — 一个 base64 编码的 Developer ID Installer .p12 文件,适用于团队 ZTRDTUL87R。
  • CSC_INSTALLER_KEY_PASSWORD — 安装程序 .p12 文件的导出密码。
  • APPLE_ID — 用于公证的 Apple 账户。
  • APPLE_APP_SPECIFIC_PASSWORD — 用于 notarytool 的专用应用专用密码。
  • APPLE_TEAM_ID — Apple Developer 团队 ID。

不要使用 Apple Development 证书。直接分发和原生 macOS 更新需要 Developer ID Application 证书。切勿将签名凭据提交到仓库中。

Docker 包访问

Docker 镜像不需要机密信息:docker-publish 作业使用 GITHUB_TOKEN 推送。 GHCR 会将新包设为私有, 即使源仓库是公开的也是如此。推送成功并不代表公开可访问。

首次推送后,包管理员必须打开 OpenBot 包:

  1. 打开 Package settings(包设置),将 Change visibility(更改可见性)设置为 Public(公开)。
  2. 检查该包是否已链接到 nightly-labs/openbot。Dockerfile 会设置 org.opencontainers.image.source 标签来实现此链接。
  3. 在 Manage Actions access(管理 Actions 访问权限)下,检查 nightly-labs/openbot 是否具有写入权限。

如果 Public(公开)被组织管理员禁用,则组织所有者必须在 组织包设置的 Package creation(包创建)下启用 Public(公开)。 将 OpenBot 设为公开,然后恢复之前的组织策略。当此创建权限再次被禁用时, 现有的公开包仍保持公开。

如果某个版本已推送其镜像但用户无法拉取,请先检查这些设置。更改 可见性会使现有标签变为公开;无需重新构建。标签 0.30.0 和 0.31.0 没有 v 前缀。后续版本还会发布 v<version> 作为别名。

使用空的 Docker 配置检查访问权限,这样已保存的登录信息就不会掩盖故障:

anonymous_config=$(mktemp -d)
docker --config "$anonymous_config" manifest inspect ghcr.io/nightly-labs/openbot:latest
docker --config "$anonymous_config" manifest inspect ghcr.io/nightly-labs/openbot:0.30.0
rm -rf "$anonymous_config"

发布工作流会检查对版本、v<version> 和 latest 标签的匿名访问, 包括两种 Linux 架构。失败时,GitHub Release 和已推送的镜像会保持 原样。请更正包设置,然后重新运行失败的作业。

托管服务器快照

GitHub 发布之后,Publish boat server snapshot 会将 Linux x64 发布版构建为一个名为 snapshot 的 boat(openbot-server-production-<version>,点号替换为连字符),然后将其 选为新的生产服务器。它使用带标签的托管脚本、 已发布的 SHA256SUMS-linux.txt 以及 https://api.openbot.run。构建器会检查 AppImage 校验和与已安装版本,并在保存快照之前要求主机配置文件为空。 现有的服务器和测试 Worker 不会改变。

在使用此作业进行首次发布之前,请配置 cloudflare-production GitHub 环境:

  • 添加 BOAT_TEMPLATE_API_KEY,这是一个独立的 boat 密钥,具有沙盒、文件、命令和命名快照 访问权限。将权限受限的 BOAT_API_KEY 保留在 Worker 上。
  • 保留 CLOUDFLARE_PRODUCTION_DEPLOY_TOKEN 和部署所使用的 CLOUDFLARE_ACCOUNT_ID 变量。
  • 除了 main 分支之外,还允许标签模式 v*.*.*。发布作业在标签上运行。

release:preflight 会检查这些设置。托管作业与生产 Worker 部署共享一个并发组。该作业在构建前和选择前都会检查 GitHub 的最新稳定版本。它只写入 Worker 的 HOSTED_SERVER_TEMPLATE 机密。普通 CI 和本地生产部署会保留它;旧的 GitHub 变量被忽略。hosting:setup 现在只设置计费和 webhook,不再接受 --template。

托管作业失败后,GitHub Release 仍保持已发布状态,之前的模板仍处于选中状态。在纠正原因后重新运行失败的作业。已就绪的快照会被复用;正在保存的快照会被轮询;失败快照需要操作员检查。构建器永远不会替换现有快照。即使 boat 允许超过该数量的付费存储,它也会在 10 个快照处停止。

构建前,作业会移除旧版本的生产快照。它保留最新的 2 个就绪快照,因此连同新版本在内会保留 3 个发布;其中最新者会保持选中状态,直到新版本将其替换。旧版本中失败的快照也会被移除。它会保留正在保存或状态未知的快照、任何比该发布更新的快照,以及每个未删除且尚无沙箱的生产服务器的 provider_template,因为重试创建时,其首次请求会被再次发送。作业从生产 D1 数据库读取这些信息,若无法读取则停止。已从被移除快照创建出的服务器会继续运行。没有 openbot-server-production- 前缀的名称永远不会被移除;手动移除前,请确认 Worker 和任何待处理的创建操作都不需要该名称。

成功的作业会在其 Actions 摘要中报告所选版本。通过创建临时生产服务器并检查其初始安装版本来验证首次推出。将结果记录在 .openbot-build/ 下;检查后仅移除该临时服务器。此远程检查独立于本地检查,并且需要生产访问权限。

Windows 签名

标签构建使用 Azure Artifact Signing 对 Windows 版本进行签名。它不需要任何机密:

  • Artifact Signing 账户 synthetifyartifactsign(East US,https://eus.codesigning.azure.net/)持有 Public Trust 证书配置文件 SYNTHETIFY,其颁发对象为 SYNTHETIFY LABS SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ。Microsoft 每天颁发一张具有相同主题的短期证书。
  • Entra 应用注册 openbot-release-signing 仅在该配置文件上拥有 Artifact Signing Certificate Profile Signer 角色。其联合凭据信任主题 repo:nightly-labs@100160810/openbot@1332461149:environment:release,因为此仓库使用 GitHub 的不可变 OIDC 主题格式。试运行没有环境,因此无法签名。
  • windows 作业使用 azure/login 登录,为签名服务缓存令牌,并运行 bun run dist:win --config electron-builder.windows-signing.yml,然后运行 az account clear,因此构建后的步骤没有 Azure 会话。该 overlay 添加了 win.azureSignOptions 和 forceCodeSigning。electron-builder 会签署 OpenBot.exe、来自 extraResources 的每个 .exe(远程桌面运行时和 Computer Use 驱动)、NSIS 安装程序及其卸载程序。它不会签署语音运行时的 .node 和 .dll 文件,因为该 overlay 未设置 win.signExts。provider CLI 未在 Windows 上打包,因此其供应商签名不变。
  • verify:package:win --require-signature 要求这些可执行文件中的每一个都具有与 app-update.yml 发布者一致的有效签名,下一步要求安装程序使用相同的签名者。试运行则要求 NotSigned。
  • -f mode=windows-signing 会在不发布版本的情况下检查登录和签名服务(命令请参见 发布版本)。

electron-builder 将 publisherName 写入 app-update.yml。已安装的签名构建只有在安装程序证书具有该 CN 时才接受更新。因此,法定名称或身份验证的任何变更都会停止所有签名安装的自动更新:应在证书变更前发布一个在 publisherName 中同时列出两个名称的版本。未签名安装没有 publisherName,因此会接受第一个签名更新。

签名会消除“未知发布者”警告。SmartScreen 仍可能发出警告,直到新证书建立起下载信誉。

构建远程桌面运行时

native-runtime.lock.json 通过完整提交哈希和源码归档 SHA-256,将 Sunshine v2026.516.143833 和 Moonlight Web v2.10.0 的上游源码固定下来。每个条目还记录了应用于该源码的可审查 OpenBot 补丁。在目标平台上构建:

bun run build:remote-desktop-runtime
bun run verify:remote-desktop-runtime

该命令将二进制文件、静态 Moonlight viewer、GPL-3.0 许可证、对应源代码元数据和 SHA-256 校验和写入 build/remote-desktop-runtime/<platform>/<arch>。每次二进制发布都必须随附这两个 GPL 组件的精确对应源代码。如果缺少二进制文件、许可证、源代码清单、校验和、平台签名或公证结果,发布必须停止。

此源码构建仅用于生成或复现某个运行时版本。当配方或某个固定输入发生变化时,.github/workflows/remote-desktop-runtime.yml 工作流会构建 macOS ARM64、macOS x64、Windows x64 和 Linux x64。它会发布一个名为 remote-desktop-runtime-<input-digest> 的不可变 GitHub 预发布。该预发布包含四个确定性归档、SPDX SBOM、构建来源证明和 remote-desktop-runtime-manifest.json。它不是 OpenBot 应用更新,并且绝不能包含 latest.yml。

PR 推送不会取消正在进行的运行时构建。当同一 PR 和平台的原生输入及构建工具未改变时,下一次运行会复用成功的原生构建。它仍会针对当前检出运行验证以及 macOS 或 Linux 冒烟测试。缓存未命中时会重新构建运行时。推送到 main 和手动触发不会使用 PR 构建缓存。

发布后,工作流会打开一个草稿 PR,将发布标签和 SHA-256 值添加到 native-runtime.lock.json。该任务仅从 main 分支运行,因为它的固定以检出的锁文件为基准:输入摘要由磁盘上的配方得出,而基于不同配方构建的清单会被拒绝。

因此,修改配方的分支必须手动固定;否则合并后会使 main 处于未固定的锁状态——这不是降级状态,而是损坏状态,因为 prepare-remote-desktop-runtime.ts 会抛出异常,并使所有 package、dist:mac、dist:win 和 dist:linux 运行随之失败。在分支上执行:

gh workflow run remote-desktop-runtime.yml --ref <branch>
# once Publish and Verify published are green:
gh release download remote-desktop-runtime-<input-digest> --pattern remote-desktop-runtime-manifest.json
bun scripts/pin-remote-desktop-runtime.ts remote-desktop-runtime-manifest.json

若下载或 CI 设置失败后需要重复验证,且不替换已发布的工件,请运行 gh workflow run remote-desktop-runtime.yml --ref <branch> -f verify_only=true。此模式要求当前输入摘要已有对应发布,并执行安装、运行时验证、macOS 与 Linux 冒烟测试以及应用打包。它不会构建或发布。

将重写后的 native-runtime.lock.json 提交到分支,并与配方一起合并,这样 main 永远不会看到两者分离。固定不会改变输入摘要——它涵盖 recipeVersion、两个源条目和 targets,而不是工件——因此不会使刚固定的发布失效。

常规 CI 和应用发布任务随后使用:

bun run install:remote-desktop-runtime
bun run verify:remote-desktop-runtime

安装程序仅接受锁文件中精确对应的预发布版本和资源。它会拒绝已更改的清单、已更改的压缩包、不安全的压缩包路径以及不匹配的源清单。不要替换现有运行时预发布中的资源。当构建过程发生变化时,请增加 recipeVersion。

Sunshine 安全补丁移植(运行时配方 12)

Sunshine 上游基线保持为 v2026.516.143833,以保留经过测试的 macOS 输入后端。OpenBot 补丁包含以下上游安全更改和回归测试:

  • 1583e7c4a7e99538c7700315a1d2a2101c6d2812:在排队和分发之前验证输入数据包(GHSA-26q2-58j6-qmvv 和 GHSA-6w33-pjh7-p77c)。
  • 82bccdf69894ee03ac422cc787f1ac9654da359d:拒绝短 ENet 控制数据包(GHSA-c428-87f8-rrv5)。
  • ccf97e38796be6cfcbff0ef248a684d39e181eba:将配对批准绑定到显式且会过期的请求 ID(GHSA-36ff-frg7-492f)。
  • 4d768847fcd88cc94ac745c4611715c67d7d67e1:要求精确匹配已启用的客户端证书,并规范化存储的证书身份(GHSA-6jvv-jqr7-m6m3)。

补丁移植保留了较旧的平台 API 和测试夹具。原生 CI 仅构建并运行相关的数据包、配对、REST 授权和证书回归测试。本地 Moonlight 客户端使用随机配对名称,并仅批准其匹配的回环请求 ID。Moonlight 现在从相同的上游提交构建,带有现有的 OpenBot 补丁以及一个修复,该修复发送配置的配对名称,而不是上游硬编码的名称。已发布的运行时使用新的配方/输入摘要;不会替换任何现有发布资源。Linux GUI 功能公告 GHSA-fp6g-27w5-489j 不适用:Linux 运行时在关闭托盘的情况下构建,且 OpenBot 未赋予 Sunshine 任何文件能力。

Linux 运行时(配方 16)

linux-x64 目标在 Ubuntu 24.04 上使用 GCC 14 构建,仅启用 X11 采集和软件编码(CUDA、DRM、KWin、VAAPI、Vulkan、Wayland、portal 和托盘均关闭)。OpenBot 补丁添加了 XTest 输入后端,因为 Xvfb 无法读取 uinput 设备。它还在关闭 DRM 时链接 libcap,并添加了 vaMapBuffer2 垫片,因为预构建的 FFmpeg 需要 libva 2.21,而 Ubuntu 24.04 只有 2.20。运行时使用 Ubuntu 24.04 中 Ubuntu 26.04 也具备的系统库;托管服务器模板会安装它们(scripts/hosting/packages.txt 中的远程桌面部分)。自配方 16 起,Boost.Locale 不再有 ICU 后端,运行时随附 lib/libminiupnpc.so.17 及其 Debian 许可证,因为 Ubuntu 26.04 只有 libminiupnpc21。Sunshine 通过其 RUNPATH $ORIGIN/lib 找到它。构建任务使用 ldd 检查每个可执行文件在 Ubuntu 26.04 上是否都能找到其所有库。Linux arm64 仅为本地开发构建,没有发布工件。

固定 Gemini 服务器

native-runtime.lock.json 手动固定 Gemini 提供方的 Google Antigravity ACP 服务器:版本取自 ACP 注册表条目 antigravity-acp,每个目标对应 zip 名称、其 SHA-256 和大小,以及其中 agy_acp_server 和 localharness_external 的 SHA-256。从注册表的 archive URL 下载每个 zip,对 zip 和两个文件计算哈希,并将 installedBytes 设置为大于解压后的大小。不要提交 zip:Google 的许可证不允许再分发。

固定 Cursor CLI

native-runtime.lock.json 手动固定 Cursor CLI。阅读 ACP 注册表条目 cursor:其 version 为日期,每个 archive URL 包含构建号、日期和提交哈希,如 2026.09.28-64d2043。将 version 设置为构建号。对于每个目标,下载 downloads.cursor.com/lab/<build>/<os>/<arch>/agent-cli-package.tar.gz(Windows 上为 .zip),并设置压缩包的 SHA-256 和大小、installedBytes(大于解压后的大小),以及 files 中每个文件的 SHA-256。files 中的路径相对于 dist-package/。不要提交压缩包:Cursor 的条款不允许再分发。

固定 Cline CLI

native-runtime.lock.json 手动固定 Cline CLI。阅读 @cline/cli-darwin-arm64 的 npm latest 版本并设置 version。对于每个目标,从 registry.npmjs.org 下载平台包 @cline/cli-<os>-<arch>(Windows 为 windows-x64),并设置 tarball 的 SHA-256 和大小、bin/cline(Windows 上为 bin/cline.exe)和 extensions/plugin-sandbox-bootstrap.js 的 SHA-256,以及 installedBytes(大于解压后的大小)。npm 包没有许可证文件,因此将 licenseSha256 设置为 github.com/cline/cline 的 cli-v<version> 标签处 LICENSE 的 SHA-256。

固定 OpenCode CLI

native-runtime.lock.json 还固定了 OpenBot 为 OpenCode 提供方下载的 OpenCode CLI(在首次更新检查应答之前),通过 @opencode/cli-* npm 平台包(OpenCode 2)、资源 SHA-256、解压后二进制的 SHA-256、字节数和 MIT 许可证文件。OpenCode 2 的 tarball 不携带许可证,因此锁文件在 licensePackage 中指定一个 opencode-ai(OpenCode 1)发布版本,应用会从中安装相同的 MIT 文件。Codex、Claude 和 Grok 在同一个文件中手动固定;OpenCode 有一个脚本,因为版本、全部五个平台包和许可证必须保持一致:

bun run pin:opencode-runtime                # the newest published release
bun run pin:opencode-runtime 2.1.1          # one exact version
bun run pin:opencode-runtime 2.1.1 1.19.3   # and one exact `opencode-ai` release for the license

该脚本会下载全部五个平台 tarball,检查 package/package.json 是否命名了锁文件所声称的包和版本,对解压后的二进制文件和许可证进行哈希,然后以 --version 运行与主机匹配的目标的解压后二进制文件,并拒绝任何不是固定值的输出。最后一项检查正是保护 verifyInstalledRuntime 的机制,后者会将已安装版本与固定版本进行精确相等比较。脚本会打印出待审查的块,而不是重写锁文件,因此请将其粘贴到 opencode 条目上,并用该确切版本重新运行:当提交的块逐字节完全匹配时,命令会报告 already pins OpenCode <version>。

仅在版本升级时运行。升级还需要 OpenCode 说明 中的 Windows 检查:win32-x64 值来自在 macOS 上读取的已发布 tarball,因此必须先在 Windows 上确认暂存的 opencode.exe --version 输出,然后才能发布。

固定 Computer Use 驱动

native-runtime.lock.json 通过发布标签、资源 SHA-256 以及 OpenBot 发布的每个文件的 SHA-256,固定了 cua-driver——这个第三方二进制文件为每个提供方提供 Computer Use 能力。与提供方 CLI 不同,该驱动是打包发布而非按需下载,因此发布版本直接携带它,用户无需安装任何内容。

bun run pin:cua-driver 0.34.0

该脚本下载全部四个 -binary 发布资源,对每个随附文件进行哈希,并拒绝任何重命名资源或丢失文件的发布版本。它会打印出待审查的块,而不是重写锁文件,因此请将其粘贴到 cuaDriver 条目上。请使用带版本号的 cua-driver-rs-v* 标签;nightly-cua-driver-rs-v* 标签每天重建,不能作为固定点。

然后,bun run prepare:cua-driver 会根据固定信息写入 build/cua-driver/<platform>/<arch>,在安装前后验证每个摘要,electron-builder.yml 会将该目录复制到 resources/cua-driver/<platform>/<arch>。每次运行 package、package:*、dist:* 和 dist:release 都会先执行此操作。每个安装程序只携带其自身目标的驱动,包验证器会同时检查驱动是否存在以及是否没有任何其他平台的驱动。macOS 发布任务直接调用 electron-builder,而不是通过 dist:mac,因此它会在自己的 Install and verify native runtimes 步骤中安装驱动;Windows 和 Linux 任务则从 dist:win 和 dist:linux 获取驱动。

在 macOS 上,该驱动由 Cua AI 签名,带有 hardened runtime、安全时间戳和 Automation 授权。mac.signIgnore 保留该签名:如果使用 OpenBot 继承的授权重新签名,将去掉该授权并破坏驱动的 Automation 路由。公证接受由另一个 Developer ID 团队签名的嵌套二进制文件,如果 Cua AI 证书颁发机构或 hardened runtime 标志丢失,verify-macos-package.ts 将失败。

固定 Bun 工具运行时

native-runtime.lock.json 还固定了 Bun,它不是提供方 CLI:它是在没有 Node 的计算机上启动 STDIO MCP 服务器所用的运行时,暂存布局会在其旁边放置第二个名称 bunx,以便 npx -y <package> 有可响应的对象。出于与 OpenCode 相同的原因,该固定也有自己的脚本——版本、五个平台包和 MIT 许可证必须保持一致:

bun run pin:bun-runtime         # the newest published release
bun run pin:bun-runtime 1.4.2   # one exact version

它的工作方式与 OpenCode 脚本类似:下载全部五个平台 tarball,将 package/package.json 与注册表元数据对照检查,对解压后的二进制文件和 LICENSE.md 进行哈希,在与主机匹配的目标上运行 --version,并打印出待审查的块而不是重写锁文件。请将其粘贴到 bun 条目上,并用该确切版本重新运行,以获得 already pins Bun <version>。

x64 条目是 baseline 构建。Bun 的普通 x64 构建需要 AVX2,在较旧机器上会以非法指令且无任何消息的方式响应 spawn,这最终会表现为一个永远无法启动的 MCP 服务器。

请在发布准备阶段、与发布升级安全审计一起进行此固定,而不是按计划定期进行:固定的运行时是 OpenBot 的供应链,Bun 的安全发布只能通过 OpenBot 发布才能到达用户。每个发布只包含一个经过审查的提交。

发布说明

CHANGELOG.md 是公共站点上 /changelog 页面的文本。每个拉取请求都在自己的文件中写入说明,即 changelog.d/<branch>.md(其中 - 替代 /),这样两个拉取请求就不会修改相同的行。版本升级会将每个文件中的条目按拉取请求合并的顺序移入 CHANGELOG.md,并删除这些文件。仍在 CHANGELOG.md 的 ## [Unreleased] 下的条目会排在前面。

  • 将用户能看到的每个变更放入文件中的以下分组之一: ### Added、### Changed、### Deprecated、### Removed、### Fixed、### Security。
  • 为每个变更写一个 - 条目,面向用户而非开发者。说明变更内容。对于修复,说明之前的状况。
  • 将用户升级后必须执行的步骤写成以句号结尾的粗体句子,例如 **Sign in again after you upgrade.**。页面会在“升级后你需要执行的操作”下显示这些条目。不要在其他句子中使用粗体。

iPhone 应用在 /changelog 的 Mobile 标签页中有自己的版本和说明。按照同样的规则,将 iPhone 应用的变更说明写入 apps/mobile/changelog.d/<branch>.md。如果某个变更同时涉及桌面应用或 Web 客户端,以及 iPhone 应用,则需要在每个文件夹中各写一个文件。bun run mobile:release:patch、mobile:release:minor 和 mobile:release:major 会将这些文件移入 apps/mobile/CHANGELOG.md,并在 apps/mobile/app.json、apps/mobile/package.json 以及 bun.lock 中对应的副本里设置版本。当一次提交更改了 app.json 的版本时,pre-commit 钩子会检查新章节。新章节为 ## [x.y.z] - In review,/changelog 在商店提供该构建之前不会显示它。每个商店自行审核构建。当一个商店拥有它时,运行 bun run mobile:release:published ios(或 android)会写入该商店的日期,例如 ## [x.y.z] - 2026-10-14 (Android)。对另一个商店再运行一次:## [x.y.z] - 2026-10-14 (Android), 2026-10-16 (iOS)。没有商店名称的日期表示两个商店的日期。/changelog 会显示每个商店,并将尚未拥有该版本的商店显示为“In review”。

当章节缺失、为空或出现两次,或包含未知分组、没有条目的分组、没有文本或位于分组之外的条目、诸如 TODO 之类的占位符,或没有真实日期的标题时,scripts/check-release-notes.ts 会阻止发布。以下步骤会运行它:

  • bun run release:patch、release:minor 和 release:major 在写入新章节之前会对其进行检查。当一行不在某个条目中时,它们会停止且不删除任何文件。
  • pre-commit 钩子使用 --fragments 检查每个已暂存的 changelog.d 文件,并在提交更改 package.json 版本时检查已暂存的 CHANGELOG.md。
  • Changelog 工作流检查每个 changelog.d 文件。如果某个拉取请求既没有添加 changelog.d 文件,也没有更改 CHANGELOG.md,则该工作流会使其失败,除非该拉取请求带有 no-changelog 标签。当用户看不到任何变更时,请使用此标签。
  • bun run release:preflight 和标签工作流会检查 package.json 版本对应的章节。

更新后说明

桌面端对话框使用 src/renderer/src/features/updates/whats-new-releases.json 中已发布 CHANGELOG.md 的编辑摘录。在发布桌面版本之前,请在此处添加其版本、日期和选定的说明。保留早期条目,以照顾跳过版本的用户。使用简短、平实的句子,说明用户现在可以做什么。将必需的升级步骤放在 notices 中。对其他条目使用 added、changed 和 fixed。不要复制提交消息、开发者设置、内部路径、凭据或未发布的计划。在打标签之前,请结合发布说明审阅这段文本。如果某个版本没有可展示的内容,则可以没有对话框条目。

应用会打包这段文本,并在需要时加载,因此阅读它不会发送数据,也不需要网络访问。“完整更新日志”按钮会打开现有的公开页面。更改打包的文本需要发布新的应用版本。缺失条目不会导致网络回退。

已安装的桌面版本来自 AppInfo,而不是可用的更新或远程服务器。在开发环境中,AppInfo 使用 package.json;否则 Electron 会报告自身的版本。本地应用配置文件会在 openbot:whats-new-version 中记录已消费的最高版本。在首次启动此功能时,已有的 openbot:analytics-app-version 可以提供之前的版本;该对话框不会启用分析功能。没有先前版本的配置文件会获得一个静默基线,因此新用户引导不会被发布说明覆盖。稳定的 x.y.z 版本会参与;未知版本和预发布构建不会推进标记。降级不会降低该标记。标记会在自动显示之前保存,即使发生加载错误也会保存;通过“设置 > 更新”可以再次打开说明。空的更新会在不打开的情况下被消费。如果无法保存标记,自动显示会被禁用;手动打开仍然有效。

移动端有自己的商店版本和 apps/mobile/CHANGELOG.md。如果在那里添加更新后说明,则必须使用原生界面和自己的本地标记。桌面端说明不得使用移动端版本或所连接服务器的版本。此桌面功能不会改变移动端、公共 Web、Signal、数据库 schema 或 Team API 契约。

Discord 公告

每个桌面版本都会在 OpenBot Discord 中发布一条消息。在发布桌面版本之前,请从该版本的 CHANGELOG.md 章节中选择对用户影响最大的 3 到 5 项变更,并将其与版本一起写入 .github/release-announcement.json:

{ "version": "x.y.z", "highlights": ["...", "...", "..."] }

将每条亮点写成一个简短、平实的句子,告诉用户他们现在可以做什么。如有必需的升级步骤,请将其放在最前面。遵循更新后说明的规则:不要复制提交消息、开发者设置、内部路径、凭据或未发布的计划。替换上一个版本的亮点;该文件只保存一个版本。

scripts/discord-release-announcement.ts 会构建消息:提及 Release Pings 角色、标题 OpenBot vx.y.z is out、亮点,以及 /changelog 上该版本的链接。它只通知该角色,不通知 @everyone 或 @here。运行 bun scripts/discord-release-announcement.ts check x.y.z 可查看该消息。

当文件不是针对发布版本的、亮点少于 3 个或多于 5 个、存在空的或包含占位符、换行符或提及的亮点,或消息长度超过 2000 个字符时,标签工作流和 bun run release:preflight 会停止。当 DISCORD_RELEASE_ROLE_ID 仓库变量未设置或不是 Discord 角色 ID 时,它们也会停止。在 GitHub Release、Docker 镜像和 boat 快照发布之后,discord-announce 作业会通过 release 环境中 DISCORD_RELEASE_WEBHOOK_URL 密钥所指定的频道 webhook,以 OpenBot 的身份发布该消息。当 DISCORD_RELEASE_ROLE_ID 未设置时,该作业会失败,并且不会发布任何内容。发布失败不会撤销发布:请重新运行该作业。在成功发布后重新运行,会再次发布该消息。

一次性设置:

  1. 在 Discord 中,打开公告频道的设置,然后进入 集成 > Webhooks。创建一个名为 OpenBot 的 webhook,并使用 OpenBot 图标。将其 URL 添加为 release 环境的机密 DISCORD_RELEASE_WEBHOOK_URL。
  2. 在 Discord 中,开启开发者模式,打开服务器设置 > 角色,右键点击 Release Pings 并选择“复制角色 ID”。将该 ID 添加为仓库变量 DISCORD_RELEASE_ROLE_ID。

移动应用拥有自己的帖子、文件、webhook 和工作流。它会一直等到两个商店都上架该版本。参见移动端 Discord 公告。

发布版本

从一个干净且最新的 main 分支开始。对于首次发布,package.json 和 CHANGELOG.md 已准备为 0.1.0;CI 通过后,直接创建其附注标签:

git tag -a v0.1.0 -m "OpenBot v0.1.0"
git push origin v0.1.0

对于后续发布,发布说明位于 changelog.d/ 中。选择适当的语义化版本升级:

bun run release:patch
# or: bun run release:minor
# or: bun run release:major

该命令会更新 package.json,将 changelog.d 中的条目以及 Unreleased 下的条目移动到新的带日期版本标题下,并删除 changelog.d 文件。在创建标签之前,请审查并提交该预准备内容:

git add package.json CHANGELOG.md changelog.d .github/release-announcement.json
git commit -m "release: prepare vX.Y.Z"
git push origin main
bun run release:preflight
git tag -a vX.Y.Z -m "OpenBot vX.Y.Z"
# When Signal changed since the previous tag, deploy and check Signal from
# vX.Y.Z now (preflight item 16). Push the tag only after that.
git push origin vX.Y.Z

推送版本标签会运行 .github/workflows/release.yml。标签工作流在 https://openbot.run/join 和 Apple 关联文件返回直接的 200 响应之前不会构建任何内容:/join 返回 Cache-Control: no-store,关联文件以 application/json 返回,并包含准确的应用 ID 和路径:桌面应用为 /join,移动应用为 /join 和 /agents/*。

该工作流:

  1. 验证标签与 package.json 匹配;
  2. 安装并验证固定的远程桌面运行时,且无需 CMake 或 Cargo;
  3. 运行完整的离线仓库检查;
  4. 在 GitHub Apple 芯片运行器上构建已签名并经公证的 ARM64 DMG 和 ZIP 工件,外加一个单独签名/公证的 Host PKG;并在 GitHub Intel 运行器(macos-15-intel)上构建已签名并经公证的 x64 DMG 和 ZIP 工件。每种架构都在各自的运行器上构建,因为语音运行时和远程桌面运行时是按构建机器的架构准备的。Host PKG 仅适用于 ARM64。当 hdiutil create 失败并显示 "Device not configured" 或 "Resource busy",或网络连接断开时,每种架构的应用构建会重新开始,最多尝试 3 次,因为这些运行器错误并非由应用引起;
  5. 在 GitHub Windows 运行器上构建已签名的 Windows x64 NSIS 安装程序(dry run 中为未签名);
  6. 在每种架构的 GitHub Ubuntu 24.04 运行器上构建未签名的 Linux x64 和 arm64 AppImage,并在 xvfb-run 下进行启动检查。进一步的检查仅使用 fusermount3 挂载每个 AppImage,因为其静态运行时必须在没有 libfuse2 的系统(如 Arch Linux 和 Omarchy)上启动;
  7. 验证所有三个解包后的应用、更新元数据、附带的运行时、提供程序控制工件、许可证、校验和、平台签名合约、启动行为以及更新工件的大小限制;
  8. 为所有三个平台生成 SPDX SBOM 和 GitHub 构建来源证明;
  9. 仅在所有平台任务通过后发布一个非草稿 GitHub Release。它使用 scripts/merge-mac-update-manifests.ts 合并 ARM64 和 x64 的 latest-mac.yml 文件:electron-updater 在 Apple 芯片上选择名称中包含 arm64 的 ZIP,在 Intel 上选择另一个 ZIP。
  10. 在对应架构的运行器上,先根据 SHA256SUMS 文件检查 AppImage,再从中构建 Docker 镜像(docker/Dockerfile)。它使用 docker/seccomp.json 启动每个镜像,等待 openbot status,然后将其停止,该程序必须以 0 退出;
  11. GitHub Release 发布后,将这两个镜像推送到 ghcr.io/nightly-labs/openbot,使用标签 <version>-amd64 和 <version>-arm64,并将它们合并到 <version>、v<version>、latest 和 sha-<commit> 下,同时认证该镜像的构建来源。然后检查两种架构的匿名访问。参见 Docker。

用户可以使用 gh attestation verify <file> --repo nightly-labs/openbot 验证下载的工件。

打标签之前,先以不发布的方式运行发布流程:

gh workflow run release.yml --ref <branch> -f mode=dry-run

dry run 会运行标签验证(不进行标签和 main 检查)、Windows、Linux 和 Docker 构建,以及它们的所有验证步骤。它不会运行 macOS 任务或发布任务,也不会生成任何证明:macOS 任务需要只有标签运行才会收到的 release 环境的机密,而对此公共仓库的证明是一份公开的 Sigstore 记录。在 tag 引用上使用 -f mode=host-signing 来检查 macOS Host 签名钥匙串,在 tag 引用上使用 -f mode=windows-signing 来检查 Windows 签名。--ref 选择工作流和 overlay,因此标签必须指向要测试的提交。使用临时的 v0.0.0-* 标签。它符合 release 环境策略。推送它会启动一次发布运行,但 Validate release tag 会停止该运行,因为该标签永远不会匹配 package.json 版本:

git tag v0.0.0-windows-signing-test <commit>
git push origin v0.0.0-windows-signing-test
gh workflow run release.yml --ref v0.0.0-windows-signing-test -f mode=windows-signing
# After the run:
git tag -d v0.0.0-windows-signing-test
git push origin :refs/tags/v0.0.0-windows-signing-test

它会登录到 Azure,然后立即对一次性可执行文件签名一次,20 分钟后再签名一次,模拟发布构建中登录与签名之间的延迟。它不会上传或发布任何内容。在更改 Azure 设置或 electron-builder.windows-signing.yml 之后、下一次打标签之前,运行一次即可。

已安装的 OpenBot 构建会在启动后不久以及每四分钟检查一次更新。当 自动下载更新 开启时(这是默认设置,并按用户持久化保存在 openbot-update-preference-v1.json 中),新版本会自动下载;关闭该设置后,只有用户操作才会开始下载。侧边栏中的更新通知和 设置 > 更新 会显示当前状态,并让用户下载可用版本,然后重启进入新版本。重新启动操作在下载完成后立即出现;如果没有该明确操作,任何平台都不会安装,因为 autoInstallOnAppQuit 保持关闭,因此关闭准备流程始终会运行。用户等待的每个阶段都有超时限制,并记录在 logs/update/update.log 中,因此失败的检查、下载或重新启动会报告可操作的错误,并且可以在原地重试。

语音运行时(来自固定版本 sherpa-onnx-* npm 包的 sherpa-onnx N-API 插件和 ONNX Runtime)是 macOS、Windows 和 Linux 应用程序的一部分,位于 voice/runtime 中。每个 Linux AppImage 都携带其架构对应的运行时。Linux arm64 AppImage 不附带远程桌面运行时。NVIDIA Parakeet TDT 0.6B v3 int8 模型不属于应用程序或更新产物的一部分。OpenBot 会在首次使用语音时下载它的四个固定文件,检查每个文件的大小和 SHA-256,并将验证过的文件保存在用户数据目录的 runtimes/parakeet-tdt-0.6b-v3-int8 中,以供日后离线使用。可选的 Whisper large-v3 turbo int8 模型(使用固定的听写语言)仅在用户在 设置 > 语音 中选择 下载 时才会以相同方式下载。它的三个固定文件存放在 runtimes/whisper-turbo-int8 中。可选的 Underdog Bark 3.0 Plus int8 模型(仅识别英语)会在用户在 设置 > 语音 中选择 下载 时以相同方式下载。它的四个固定文件存放在 runtimes/bark-3.0-plus-int8 中。它们来自非官方的 sherpa-onnx 转换;许可证位于 build/licenses/Underdog-Bark-3.0-Plus-LICENSE。启动时,打包的应用程序会删除旧的 runtimes/whisper 目录(如果存在)。Supertonic 3 int8 语音模型(用于大声朗读回复)仅在用户打开 朗读回复 或选择 语音回复 部分中的 下载 时才会以相同方式下载。它的八个固定文件存放在 runtimes/supertonic-3-int8 中。语音宿主 voice-speech-host.js 是一个独立的实用程序进程,加载相同的插件。其权重采用 OpenRAIL-M 许可;许可证位于 build/licenses/Supertone-Supertonic-3-LICENSE。

如果 macOS 更新 ZIP、Windows NSIS 安装程序或 Linux AppImage 大于 700 MiB,或 DMG 大于 750 MiB,发布工作流会停止。如果更新元数据的大小或 SHA-512 错误、存在语音模型,或应用程序包含第二个原生 Claude 运行时,它也会停止。

如果某个发布版本有问题,请发布更新的补丁版本。不要用不同的二进制文件替换已发布的版本。

在执行此检查清单之前,请使用 .agents/skills/release-upgrade-safety/ 技能审计自上次发布标签以来的更改:该技能涵盖已安装用户无法撤销的升级和数据丢失风险——就地数据库迁移、没有任何备份的 userData 文件、冻结的 Team API 适配器,以及与自身部署竞争的 D1 迁移。

预检检查清单

在创建第一个标签或任何后续发布之前:

  1. 为适配器注册表中保留的每个协议运行 Team API 兼容性矩阵。矩阵必须覆盖:旧客户端搭配新主机、新客户端搭配旧主机、版本匹配、无共享协议、能力缺失、未知的可选事件以及格式错误的已知事件。不要因为某个协议很旧或许多应用程序版本将两端分开而缩小此矩阵。确认每个受支持的协议仍然具有未更改的客户端和主机夹具;

  2. 运行 bun run release:preflight 并解决所有报告的发布机密或仓库门禁;

  3. 确认 release 环境包含上述全部八个 macOS 机密;Windows 通过 Azure 联合凭据签名,不需要机密;Linux 保持未签名;
  4. 确认生产环境的 /join 页面和 Apple 关联文件通过 CI 中的部署检查;
  5. 从干净的克隆中运行 bun install --frozen-lockfile 和 bun run check;
  6. 在 macOS 上运行 bun run package:verify;Windows 和 Linux 的打包与启动验证在发布运行器上运行;
  7. 确认锁文件包含全部六个提供者产物、它们的下载和安装大小,并且它们的安装检查通过;
  8. 对登录/设置、聊天流式传输、队列、附件、代理消息传递、浏览器控制、上下文压缩和更新弹出框进行冒烟测试;
  9. 在 macOS ARM64、macOS x64、Windows x64 和 Linux x64 上,从最后一个公开版本更新,并确认检查、下载、准备、明确重启、新版本、本地代理、对话和队列。Linux 构建只有在通过 AppImage 运行时才会自动更新,运行时通过 APPIMAGE 报告这一点;
  10. 测试首次语音使用、下载进度、停止下载后的重试、转录以及缓存的离线使用;
  11. 构建一个已签名并经过公证的 canary 版本,并在 macOS 26 上使用单独的 userData 目录测试从官方 0.1.21 应用程序更新;
  12. 确认 canary 更新不会在 CFURLConnectionSynchronous 中崩溃,能保留数据,能启动新版本,并且能在受限内存下运行三个提供者下载;
  13. 在 Windows 10 和 11 x64 上,确认正常退出、重启和注销不会启动 NSIS,而 Restart and install 会启动它,不显示安装程序页面,并重新打开 OpenBot。在 OpenBot 已关闭和已打开两种情况下,确认在 /join 页面上点击 Open OpenBot 会打开 OpenBot 并只显示一次邀请,并且卸载会删除 HKCU\Software\Classes\openbot。从上一个版本更新,然后安装到第二个磁盘上的文件夹。确认安装程序没有“所有用户”页面,更新会安装到同一文件夹,icacls 在该文件夹上只列出用户、SYSTEM 和 Administrators,并且卸载程序只删除该文件夹。选择 %USERPROFILE%\OpenBot(其中包含代理工作区),确认 OpenBot 安装到 %USERPROFILE%\OpenBot\OpenBot,并且卸载会保留工作区。使用第二个账户创建一个空的 D:\OpenBot。使用第一个账户选择它,确认 OpenBot 安装到 D:\OpenBot\OpenBot,并且第二个账户无法在其中写入;
  14. 在安装了来自 build/linux/openbot.apparmor 的 AppArmor 配置文件的 Ubuntu 24.04 x64 上,确认 AppImage 在沙箱开启的情况下启动,xdg-open 'openbot://join?...' 会聚焦正在运行的应用程序,提供者可以在应用内下载,页面侧栏会被绘制,并且麦克风控件不存在;
  15. 确认 CHANGELOG.md 和 .github/release-announcement.json 描述该版本,并且工作树是干净的;
  16. 仅在 CI 在 main 上通过后,创建并推送版本提交,并在本地创建标签;
  17. 从 curl -fsS https://signal.openbot.run/health/live 读取正在运行的 Signal 提交。如果它缺失或是 unknown,或者 git diff --quiet <running commit> <new tag> -- remote packages/contracts/src/signal-protocol 发现更改,请在推送标签之前从本地标签部署 Signal。要与正在运行的提交比较,而不是与上一个标签比较:较早的发布可能让 Signal 处于落后状态。推送标签会启动发布工作流,而其中没有任何步骤会等待 Signal。使用 remote-session-deployment.md 中的仅 Signal 流程;.agents/skills/signal-deploy/ 技能会在此流程之前和之后添加检查。确认 curl -fsS https://signal.openbot.run/health/live 显示标签提交。在此之前,需要更新 Signal 的客户端会一直失败:v0.33.0 包含了 webhook 例程,而 Signal 还早于 #1520,因此每个 webhook 路由都返回 404(#1661);
  18. 推送标签。

macOS ZIP 必须小于 800,000,000 字节,并且小于官方 0.1.21 ZIP。当任一大小门槛未通过、0.1.21 canary 更新崩溃,或 Windows 在关机或注销期间启动 NSIS 时,请勿发布。

未签名的本地 macOS 包是开发产物。它不能证明 Gatekeeper、公证或自动更新就绪状态。这些只能通过签名发布工作流中的 codesign、spctl 和 stapler 检查来证明。

发布 v0.1.0 后,保留一份已安装副本,并使用第一个签名补丁(v0.1.1)作为端到端更新器验收测试:检查、下载、重启,并确认版本已更改且未丢失本地代理或队列。这无法通过未签名的开发构建来证明,因为 macOS 更新器要求两个版本共享有效的 Developer ID 签名。

托管主机发布包

常规 DMG 仍然是桌面应用。可选的 Host PKG 仅安装托管主机基础设施。两者都使用已验证的 v<VERSION> 标签和完全相同的源提交。如果 Host 编译、签名、负载校验、公证、钉扎或校验和生成失败,macOS 任务将失败;它绝不会在 Host 包被静默省略的情况下发布版本。

预期的 macOS 产物:

OpenBot-<VERSION>-arm64.dmg
OpenBot-<VERSION>-arm64.zip
OpenBot-Host-<VERSION>-arm64.pkg
latest-mac.yml
SHA256SUMS-macos.txt
OpenBot-<VERSION>-macos.spdx.json
OpenBot-Host-<VERSION>-macos.spdx.json
OpenBot-<VERSION>-macos.sigstore.json
OpenBot-<VERSION>-x64.dmg
OpenBot-<VERSION>-x64.zip
SHA256SUMS-macos-x64.txt
OpenBot-<VERSION>-macos-x64.spdx.json
OpenBot-<VERSION>-macos-x64.sigstore.json

macOS 校验和文件涵盖 DMG、ZIP 和 Host PKG。现有的来源证明步骤会使用该文件,因此所有三个产物都针对相同的标签、提交和发布运行进行证明。发布任务下载 release-macos 和 release-macos-x64,并将 PKG 与其他产物一起发布。SHA256SUMS-macos-x64.txt 涵盖 x64 DMG 和 ZIP。latest-mac.yml 仍然仅描述 Electron 应用更新;.pkg 引用会被拒绝。

在验证 OpenBot.app 之后,macOS 任务:

  1. 运行本机账户测试,不创建用户;
  2. 将应用证书和安装程序证书导入临时、隔离的钥匙串;
  3. 使用 Bun 编译独立的 ARM64 host-manager 和 openbot-host 可执行文件,并使用 Swift 编译本机账户助手;无需目标机器的运行时或编译器;
  4. 使用 Developer ID Application、hardened runtime 和时间戳对所有可执行文件进行签名,并检查其固定标识符和团队 ZTRDTUL87R;
  5. 创建固定的 root:wheel 包负载,并使用 Developer ID Installer 签名 PKG;
  6. 展开它并拒绝多余文件、符号链接、不安全的模式/所有者、版本不匹配、更改的安装程序脚本、意外目标位置或非系统动态运行时依赖;
  7. 通过 notarytool 提交 PKG,等待 Accepted,进行钉扎,然后要求 pkgutil --check-signature、spctl --assess --type install 和 stapler validate 成功;
  8. 生成校验和和 Host 负载 SBOM,进行来源证明,并上传 PKG。

Bun 可执行文件仅需要 com.apple.security.cs.allow-jit 用于 JavaScriptCore 的 ARM64 JIT。本机账户助手没有特殊 entitlements。库验证、可执行页面保护和 hardened runtime 保持启用。CI 使用仅含系统目录的 PATH 启动已签名的独立二进制文件,并在发布前运行热 JavaScript 循环。不要复制会禁用这些保护的宽泛 Bun 示例 entitlements。如果固定的 Bun 版本无法通过这些检查,请停止发布并调查;不要削弱标志以获取签名。

发布钥匙串和导入的 .p12 文件在步骤退出时被删除。安装程序身份是强制性的,且与应用身份不同;在打标签之前添加这两个新机密。绝不发布未签名的 Host 包。测试夹具包是临时的、未签名的、从不安装,也从不作为发布资产上传。

CI 不会在 runner 上安装 root 守护进程或创建租户账户。包展开、BOM 所有权/模式验证、plist/签名检查、独立冒烟检查和模拟 CLI 检查在那里运行。实际的签名 PKG 安装/升级、新账户登录、私有主目录隔离和双用户 Aqua 重启仍然是目标主机验收门。不进行租户数据备份。基础设施更新需要管理员安装更新的签名 Host PKG;守护进程永远不会替换自身。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 openbo/openbot