发布策略
发布节奏、版本策略、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时必须满足最低版本要求。
发布清单(避免回归)
递增
app/package.json以及app/src-tauri/tauri.conf.json(以及根目录Cargo.toml/ core)按照现有版本工作流。当停止支持旧安装时,请将
VITE_MINIMUM_SUPPORTED_APP_VERSION设置为新的最低版本 之前 或 并在 该发布版本中一并更新(仓库 Actions 变量 + 上述两个工作流步骤)。移除、重定向或淘汰旧的稳定安装程序和过时的 更新器 条目,移出面向用户的界面(GitHub Release 资源、网站、CDN、更新器订阅源)。确认默认安装/更新流程无法访问已弃用的产物。
冒烟测试 Gmail 连接 在来自以下来源的新安装上: releases/latest.
完成 手动冒烟检查清单,然后将已完成的签字确认块(逐字粘贴,且保留所有已勾选项)作为 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 产物 构建 受到门控。
流程:
维护者触发
promote-main-to-release.yml,它会推送一个 来自main到release的合并提交(没有 PR)。重新触发会用release主分支最新内容刷新,同时保留已经存在于release上的修复提交;当release已经包含main时,这将不会产生任何操作。CI Full 会在晋升推送上运行。如果发现故障,任何拥有写权限的人都可以直接打开一个 针对
release的修复 PR;修复 PR 会运行两个车道——CI Lite 用于快速提供 lint/覆盖率反馈,CI Full 作为阻止合并的CI Full Gate检查——而合并后的推送会在合并结果上重新运行 CI Full。一旦 CI Full 在
releaseHEAD 上变绿,就使用release-production.yml切生产。staging 也可以改为从main触发,当 QA 需要在晋升前验证 main 时。发布工作流不会查询或强制执行CI Full Gate;操作人员在切版前自行验证相关 CI 证据。源自
release的切版会回合并到release到main(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 遵循所选的 main 或 release 触发 ref;production 始终检出 release,不受 GitHub 工作流 UI 显示的触发 ref 影响。
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 构建
运行 发布(Staging) 中通过
workflow_dispatch来自release(可选地固定一个可达发布的commit_sha;create_tag = falsecreate_tag = false,用于递增并提交,但不打标签或构建)。该工作流会递增
补丁于release,提交chore(staging): vX.Y.Z [skip ci],推送,并创建一个不可变的vX.Y.Z-staging标签,指向该提交。构建矩阵从 标签 运行(不是 release HEAD),因此即使
release已经前进,重跑也会重新构建出字节完全相同的内容。版本递增提交(以及在
release上的其他任何内容)会回合并到main.失败时,staging 标签会自动删除;位于
release上的递增提交会保留,因此下一次切版会从vX.Y.(Z+1).
没有单独的 staging 分支——staging 切版和生产发布都位于 release。二者仅通过标签后缀(-staging 与无后缀)以及创建该标签的工作流来区分。
发布生产版本
运行 发布生产 中通过
workflow_dispatch并使用所需的release_type(补丁/次版本/主版本),从releaseHEAD 或一个固定的可达发布的commit_sha.此次运行首先停在
review-approval作业(环境:Release-Approval); 必需审核人 必须先批准,任何内容才会被推送。只有获得批准后,prepare-build才会运行递增并打标签流程:在release上递增,提交chore(release): vX.Y.Z [skip ci]、推送、打标签vX.Y.Z,构建,发布。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 变更推送到所选的 main 或 release 源(release-staging.yml)promote-main-to-release.yml)以及 promotion 合并提交(CWE-250:以不必要权限执行)。有两个控制措施限制了该影响范围。
人工审批门
这个 review-approval 作业运行 之前 prepare-build 并将每个生产运行停放在 Release-Approval GitHub 环境中,因此在发生任何推送之前都必须有人审批。
一次性设置(仓库 设置 → Environments):
创建一个名为
Release-Approval(精确名称:工作流会逐字引用它)。在 部署保护规则中,启用 必需的审阅者 并添加允许授权生产推送到
main。 不能 批准自己的运行,除非 禁止自审 未启用;对于发布门禁,最好保留第二位审批者。(可选)设置一个较短的 等待计时器 为 0。这个门禁是人为决策,不是延迟。
当生产运行开始时,它会显示 “等待中” 在该 review-approval 作业上;审批者打开该运行并点击 审查部署 → 批准。拒绝(或取消)会使 prepare-build 被跳过,因此不会推送任何内容。
季度密钥轮换
轮换 XGITHUB_APP_PRIVATE_KEY 每个季度 (以及在任何疑似泄露时立即轮换)。计划:在 3 月 / 6 月 / 9 月 / 12 月.
在 GitHub App 设置 (Org → Settings → Developer settings → GitHub Apps → 发行 App)中,在 私钥 点击 生成私钥。下载新的
.pem.更新仓库密钥: 设置 → Secrets and variables → Actions →
XGITHUB_APP_PRIVATE_KEY→ 粘贴完整的新密钥(包括-----BEGIN/END-----行)。XGITHUB_APP_ID保持不变。触发一次低风险验证运行(例如 发布(Staging))并确认 Generate GitHub App token 步骤成功且推送通过身份验证。不要使用 发布生产 用于此检查,除非你有意要生成一个真实的 bump 提交:即使在
create_release = false,prepare-build仍然会提升版本并提交到release(staging,使用create_tag = false也会这样,但会跳过标签/构建,因此是风险更低的探测方式)。回到 App settings → Private keys, 删除旧密钥 ,这样只有新签发的密钥仍然有效。
记录轮换日期(PR 描述、运维日志,或
docs/OPERATIONS.md),以便下一季度的负责人能看到上次发生的时间。
轮换会使可能泄露的旧密钥副本失效,将暴露窗口限制在一个季度内。
最后更新于