For the complete documentation index, see llms.txt. This page is also available as Markdown.

发布策略

发布节奏、版本策略、OAuth 和安装程序规则。发布如何运作。

本操作手册说明我们如何避免用户完成 OAuth (包括 Gmail)在 过时的桌面安装程序上 而标准流程是 最新 版本。

分发

  • GitHub Releases 作为 tinyhumansai/openhuman 是桌面构建的主要来源。

  • 这个 Tauri 更新器 端点(参见 scripts/prepareTauriConfig.js 以及发布工作流)应将用户指向当前发布产物。

  • 淘汰旧的稳定产物: 当停止维护某个发布线时,请移除或隐藏位于 GitHub Releases的过时安装程序资源,并更新 网站 / CDN 下载链接为 releases/latest (或 current),刷新 更新器清单 (例如 Gist / latest.json)以免将用户指向弃用的构建,并抽查旧的直接 URL 是否 被重定向、返回 404 或 410 (视情况而定)。验证:尝试文档或书签中的已知旧资源 URL,并确认它们不再提供主要安装路径。

OAuth 的最低应用版本

生产环境网页构建会嵌入一个 最低支持的应用 SemVer 版本 位于 构建时 因此 OAuth 深度链接无法在已弃用的二进制文件上完成。每个安装程序都携带其构建时设置的最低版本;若要为从不升级的用户提高该门槛,则需要 新的 他们安装的发布版本(或应用内更新)。可选的后续工作:通过 运行时 API 强制执行动态最低版本,而打包值仅作为回退。

变量
目的

VITE_MINIMUM_SUPPORTED_APP_VERSION

例如 0.51.0 — 桌面应用必须是 此版本或更高,才能完成 openhuman://oauth/success.

VITE_LATEST_APP_DOWNLOAD_URL

可选;默认值为 https://github.com/tinyhumansai/openhuman/releases/latest。当门控阻止 OAuth 时会打开。

将这些配置为 GitHub Actions 变量。它们必须同时存在于 这两者中: 独立的 pnpm build 步骤以及 tauri-apps/tauri-action 步骤环境变量中,位于 .github/workflows/build-desktop.yml (由 release-production.yml / release-staging.yml调用的可复用矩阵),这样随安装程序发布的 Vite bundle 才会包含该门控。对本地开发请保持 VITE_MINIMUM_SUPPORTED_APP_VERSION 未设置 ,用于本地开发(门控禁用)。

实现: app/src/utils/oauthAppVersionGate.ts, app/src/utils/desktopDeepLinkListener.ts.

Gmail / Google Cloud OAuth

  • 重定向 URI 在 Google Cloud Console 中必须与 current 后端 + 隧道回调路径一致。

  • 桌面方案(openhuman://)是稳定的; 已安装的二进制文件 在设置 VITE_MINIMUM_SUPPORTED_APP_VERSION 时必须满足最低版本要求。

发布清单(避免回归)

  1. 递增 app/package.json 以及 app/src-tauri/tauri.conf.json (以及根目录 Cargo.toml / core)按照现有版本工作流。

  2. 当停止支持旧安装时,请将 VITE_MINIMUM_SUPPORTED_APP_VERSION 设置为新的最低版本 之前并在 该发布版本中一并更新(仓库 Actions 变量 + 上述两个工作流步骤)。

  3. 移除、重定向或淘汰旧的稳定安装程序和过时的 更新器 条目,移出面向用户的界面(GitHub Release 资源、网站、CDN、更新器订阅源)。确认默认安装/更新流程无法访问已弃用的产物。

  4. 冒烟测试 Gmail 连接 在来自以下来源的新安装上: releases/latest.

  5. 完成 手动冒烟检查清单,然后将已完成的签字确认块(逐字粘贴,且保留所有已勾选项)作为 GitHub 提交评论,发布到 v<version>-staging 带标签的提交 ,即 QA 已验证的提交(在晋升流程中没有 release PR)。在批准生产运行之前, Release-Approval 必需审核人需确认:(a) 签字确认评论存在于带 staging 标签的提交上,且 (b) 生产运行确实以该已验证内容为目标——将带 staging 标签的 SHA 作为 commit_sha,或确认除了 [skip ci] 之外没有其他内容将其与运行目标分开。若存在超出版本递增提交的其他提交,就表示有新的内容尚未经过 QA 冒烟:请先重新运行 staging。

分支模型和 CI 车道

两个长期存在的分支,两个 CI 车道:

  • main — 所有功能/修复 PR 都合并到这里。每个 PR(以及推送到 main)都会运行 CI Lite (ci-lite.yml):按变更区域进行质量检查,并对变更文件范围内的单元测试进行运行,由 PR CI Gate 检查门控到覆盖率 ≥ 80%。

  • release — 由维护者晋升的 main 快照,发布版本从这里切出。目标为 release 以及每次推送到 release 运行 CI Full (ci-full.yml):完整单元测试套件、Rust mock-backend E2E、Playwright Web E2E,以及在 Linux/macOS/Windows 上的完整桌面 E2E 矩阵。 CI Full Gate 检查会汇总所有车道 ,除了 Playwright 规格运行,它目前只是非阻塞信号(continue-on-error,在 CI 争用下不稳定 — #3615):绿色门控并不代表 Playwright 规格已通过,因此在切版前请检查该车道在此次运行中的结果。只有 Playwright 产物 构建 受到门控。

流程:

  1. 维护者触发 promote-main-to-release.yml,它会推送一个 来自 mainrelease 的合并提交(没有 PR)。重新触发会用 release 主分支最新内容刷新,同时保留已经存在于 release上的修复提交;当 release 已经包含 main 时,这将不会产生任何操作。

  2. CI Full 会在晋升推送上运行。如果发现故障,任何拥有写权限的人都可以直接打开一个 针对 release的修复 PR;修复 PR 会运行两个车道——CI Lite 用于快速提供 lint/覆盖率反馈,CI Full 作为阻止合并的 CI Full Gate 检查——而合并后的推送会在合并结果上重新运行 CI Full。

  3. 一旦 CI Full 在 release HEAD 上变绿,就使用 release-production.yml切生产。staging 也可以改为从 main 触发,当 QA 需要在晋升前验证 main 时。发布工作流不会查询或强制执行 CI Full Gate;操作人员在切版前自行验证相关 CI 证据。

  4. 源自 release 的切版会回合并到 releasemain (scripts/release/merge-release-into-main.sh:能快进则快进,否则创建一个带版本号的合并提交,例如 chore(release): merge release v1.2.4 back into main),这样版本递增提交和修复提交都会流回去。源自 main 的 staging 切版无需回合并。版本递增提交包含 [skip ci].

此模型所需的 GitHub 设置(仓库 设置 → 规则): main 要求 PR CI Gate PR 上的状态检查; release 要求非绕过角色必须通过 PR,且 CI Full Gate 需要状态检查(它在目标为 release 的 PR 上运行);release GitHub App 的身份被加入这两个规则集的绕过列表,因此 promote/release 工作流可以直接推送。

工作流:staging vs. production

两个一等公民 GitHub Actions 工作流,每个环境一个。请按意图选择,而不是切换标志。Staging 遵循所选的 mainrelease 触发 ref;production 始终检出 release,不受 GitHub 工作流 UI 显示的触发 ref 影响。

Workflow
分支
递增项
推送的标签
并发组
适用场景

mainrelease

补丁 接入

v<version>-staging

release-staging

从所选分支为 QA 切出一个 staging 构建。

release

补丁 / 次版本 / 主版本 (release_type (输入)

v<version>

release-production

从已验证的 release HEAD(或一个固定的 commit_sha).

两个流程共用的矩阵构建 / 签名 / Sentry-DIF / 产物上传流水线位于 .github/workflows/build-desktop.yml ,作为 workflow_call 可复用工作流。上面的两个顶层工作流负责 ref 解析、版本递增、打标签以及发布/清理;构建本身是共享的。

Android / Google Play

Android 发布由单独的 .github/workflows/android-compile.yml 工作流处理,它会构建一个发布用 Android App Bundle(.aab),使用 Play 上传密钥签名,并在启用发布时上传到 Google Play。该工作流会保留未签名和已签名的 AAB 作为 Actions 产物,供审计/调试。

手动 Android 上传使用同一工作流:

所需的 GitHub Actions 密钥:

密钥
目的

ANDROID_UPLOAD_KEYSTORE_BASE64

Base64 编码的 Play 上传 keystore(.jks)。请使用上传密钥,而不是 Google 应用签名密钥。

ANDROID_UPLOAD_KEY_ALIAS

上传密钥的 keystore 别名。

ANDROID_UPLOAD_KEYSTORE_PASSWORD

Keystore 密码。

ANDROID_UPLOAD_KEY_PASSWORD

密钥密码。

GOOGLE_PLAY_SERVICE_ACCOUNT_JSON

Play Console 服务账号的原始 JSON,具有以下应用的发布权限: com.openhuman.app.

可选的 GitHub Actions 变量:

变量
默认
目的

ANDROID_PLAY_TRACK

internal

上传到的 Play 轨道(internal, alpha, beta,或 production).

ANDROID_PLAY_STATUS

已完成

Play 发布状态(已完成, draft, inProgress, halted).

Google Play 要求每次上传都使用单调递增的 Android versionCode。发布递增脚本会更新 app/src-tauri-mobile/tauri.conf.json, app/src-tauri-mobile/Cargo.toml,以及 app/src-tauri-mobile/Cargo.lock 以及桌面文件,从而使生成的 Android tauri.properties 会随每次发布而更新。

切出 staging 构建

  1. 运行 发布(Staging) 中通过 workflow_dispatch 来自 release (可选地固定一个可达发布的 commit_sha; create_tag = false create_tag = false,用于递增并提交,但不打标签或构建)。

  2. 该工作流会递增 补丁release,提交 chore(staging): vX.Y.Z [skip ci],推送,并创建一个不可变的 vX.Y.Z-staging 标签,指向该提交。

  3. 构建矩阵从 标签 运行(不是 release HEAD),因此即使 release 已经前进,重跑也会重新构建出字节完全相同的内容。

  4. 版本递增提交(以及在 release上的其他任何内容)会回合并到 main.

  5. 失败时,staging 标签会自动删除;位于 release 上的递增提交会保留,因此下一次切版会从 vX.Y.(Z+1).

没有单独的 staging 分支——staging 切版和生产发布都位于 release。二者仅通过标签后缀(-staging 与无后缀)以及创建该标签的工作流来区分。

发布生产版本

  1. 运行 发布生产 中通过 workflow_dispatch 并使用所需的 release_type (补丁 / 次版本 / 主版本),从 release HEAD 或一个固定的可达发布的 commit_sha.

  2. 此次运行首先停在 review-approval 作业(环境:Release-Approval); 必需审核人 必须先批准,任何内容才会被推送。只有获得批准后, prepare-build 才会运行递增并打标签流程:在 release上递增,提交 chore(release): vX.Y.Z [skip ci]、推送、打标签 vX.Y.Z,构建,发布。

  3. release 会回合并到 main ,就在切版之后。

标签策略与回滚

  • 命名。 Staging 标签使用 SemVer 预发布后缀 -staging (v1.2.4-staging),因此它们的排序 之前 排在对应的生产标签之前。

  • 冲突。 如果目标标签已在本地或在 origin上存在,则两个工作流都会快速失败。可通过删除过期标签(仅组织维护者)或递增跳过它来解决。

  • 回滚(生产)。 构建矩阵失败会触发 cleanup-failed-release,它会同时删除草稿状态的 GitHub Release 和 v<version> 标签。

  • 回滚(staging)。 staging 构建失败会删除 v<version>-staging 标签。所选源分支上的递增提交会保留;下一次 staging 切版会从新的补丁版本号继续,而不是重复使用它(我们接受补丁版本号中存在一个小的“空档”,也不愿与并发合并竞争)。

  • 谁可以删除标签。 与以下对象相同的写权限: main。由工作流驱动的清理由工作流通过 actions/github-script 执行(GitHub App token 仅用于 prepare-build 来进行版本递增提交 + 标签推送);手动删除(git push --delete origin <tag>)需要同等的维护者权限。

Release App 令牌:审批门控与轮换

release-production.yml 递增版本, 提交到 release 并回合并到 main,使用 GitHub App 令牌推送这些提交 + 标签(secrets.XGITHUB_APP_ID / secrets.XGITHUB_APP_PRIVATE_KEY绕过分支保护。同一个 App 还会将 staging 变更推送到所选的 mainrelease 源(release-staging.ymlpromote-main-to-release.yml)以及 promotion 合并提交(CWE-250:以不必要权限执行)。有两个控制措施限制了该影响范围。

人工审批门

这个 review-approval 作业运行 之前 prepare-build 并将每个生产运行停放在 Release-Approval GitHub 环境中,因此在发生任何推送之前都必须有人审批。

一次性设置(仓库 设置 → Environments):

  1. 创建一个名为 Release-Approval (精确名称:工作流会逐字引用它)。

  2. 部署保护规则中,启用 必需的审阅者 并添加允许授权生产推送到 main不能 批准自己的运行,除非 禁止自审 未启用;对于发布门禁,最好保留第二位审批者。

  3. (可选)设置一个较短的 等待计时器 为 0。这个门禁是人为决策,不是延迟。

当生产运行开始时,它会显示 “等待中” 在该 review-approval 作业上;审批者打开该运行并点击 审查部署 → 批准。拒绝(或取消)会使 prepare-build 被跳过,因此不会推送任何内容。

季度密钥轮换

轮换 XGITHUB_APP_PRIVATE_KEY 每个季度 (以及在任何疑似泄露时立即轮换)。计划:在 3 月 / 6 月 / 9 月 / 12 月.

  1. 在 GitHub App 设置 (Org → Settings → Developer settings → GitHub Apps → 发行 App)中,在 私钥 点击 生成私钥。下载新的 .pem.

  2. 更新仓库密钥: 设置 → Secrets and variables → Actions → XGITHUB_APP_PRIVATE_KEY → 粘贴完整的新密钥(包括 -----BEGIN/END----- 行)。 XGITHUB_APP_ID 保持不变。

  3. 触发一次低风险验证运行(例如 发布(Staging))并确认 Generate GitHub App token 步骤成功且推送通过身份验证。不要使用 发布生产 用于此检查,除非你有意要生成一个真实的 bump 提交:即使在 create_release = false, prepare-build 仍然会提升版本并提交到 release (staging,使用 create_tag = false 也会这样,但会跳过标签/构建,因此是风险更低的探测方式)。

  4. 回到 App settings → Private keys, 删除旧密钥 ,这样只有新签发的密钥仍然有效。

  5. 记录轮换日期(PR 描述、运维日志,或 docs/OPERATIONS.md),以便下一季度的负责人能看到上次发生的时间。

轮换会使可能泄露的旧密钥副本失效,将暴露窗口限制在一个季度内。

最后更新于