首页
/ OpenViking 发版工程实践:多产物 tag 约定、GitHub Actions 发布流水线与补发策略

OpenViking 发版工程实践:多产物 tag 约定、GitHub Actions 发布流水线与补发策略

2026-09-05 20:59:55作者:董宙帆

本文围绕 OpenViking 仓库的发版说明文档,系统拆解其“一次发版、多产物联动”的发布体系:主包、Python SDK、Docker 镜像、Rust CLI/npm 包、TOS 下载资产与 ClawHub 插件各自的 tag 命名空间与触发链路,并逐条对照仓库中的 GitHub Actions workflow 与包配置,说明构建、发布、验证与补发的完整操作路径,帮助维护者按规范完成一次正式发版并正确处理发布失败场景。

发版目标:一组相互关联的资产,而非单一产物

OpenViking 一次正式发版需要同时覆盖多个分发渠道,各产物面向不同使用入口:

  • openviking Python 主包:面向本地运行时、服务端、CLI 及完整功能用户;
  • Python SDK openviking-sdk:面向只通过 HTTP 调用已有 OpenViking 服务的轻量客户端用户;
  • Docker 镜像:面向容器化部署,同时发布到 GHCR 和 Docker Hub;
  • TOS 发布资产:面向源码包、安装脚本和稳定下载路径;
  • Rust CLI / npm 包:面向通过 npm 安装 ov CLI 的用户;
  • OpenClaw / ClawHub 插件:面向 OpenClaw 插件分发渠道;
  • VikingBot:当前随 openviking[bot] extra 和官方 Docker 镜像分发,不再作为独立 PyPI 包维护。

从根目录 pyproject.toml 可以确认这一结构:包名为 openviking、版本为 dynamic,由 setuptools_scm 解析;[tool.setuptools.packages.find] 的查找范围是 [".", "bot"]、包含 openviking*vikingbot* 包——即 VikingBot 的源码确实被打进主包一起发布。

发版的核心纪律是:正式主版本发版时,Python 主包、Docker 镜像和 TOS 资产必须使用同一个主版本 tag;SDK、CLI、ClawHub 插件则使用各自独立的 tag 或 version 命名空间。这一约定在仓库的多个 workflow 里通过 tag 前缀过滤得到强制落地(后文逐一展开)。

版本与 tag 约定

产物 推荐 tag / version 说明
openviking 主包 vX.Y.Z 主 release tag,例如 v0.3.26
openviking-sdk python-sdk@X.Y.Z SDK 专用 tag,例如 python-sdk@0.1.3
Rust CLI / npm CLI cli@X.Y.Z CLI 专用 tag,例如 cli@0.2.0
ClawHub 插件 latest YYYY.M.DYYYY.M.D-N 由 workflow 自动生成或手动指定
ClawHub 插件 dev YYYY.M.D-dev.N dev channel 使用

tag 约定不只是文档规范,而是被 setuptools_scm 配置直接执行的:

  • 主包:pyproject.toml[tool.setuptools_scm] 设置 tag_regex = "^v(?P<version>[0-9]+(?:\\.[0-9]+)*)$"write_to = "openviking/_version.py"git_describe_command 使用 --match v[0-9]*。即主包版本v 前缀 tag 解析。
  • SDK:sdk/python/pyproject.toml[tool.setuptools_scm] 设置 root = "../.."(以根仓库为版本源)、tag_regex = "^python-sdk@(?P<version>...)$"git_describe_command 匹配 python-sdk@*。由于 setuptools_scm 的 tag 匹配互斥,主包 tag v0.3.26 不会干扰 SDK 版本解析,反之亦然。

这种“同一仓库、多 tag 命名空间、正则隔离”的设计,是理解后续所有 workflow 触发条件的关键。

正式主包发版流程

主包正式发版走根目录 GitHub Release,由 release.yml(workflow 名 03. Release)驱动。完整流程:

  1. 确认待发布改动已合入目标分支,且 PR / main 分支检查通过;
  2. 创建主包 tag,例如 v0.3.26
  3. 在 GitHub 上基于该 tag 发布 Release;
  4. 03. Release workflow 在 Release published 事件时触发——注意其 build job 带有条件 github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'v')只有 v 前缀的主包 tag 才会走这条流水线python-sdk@*cli@* 等组件 tag 会被直接跳过;
  5. build job 通过 uses: ./.github/workflows/_build.yml 复用 15. _Build Distribution,构建 sdist 和多平台 wheel;
  6. publish-pypi job 将构建产物发布到 PyPI;
  7. Docker 镜像由独立的 tag 推送构建(见下文),release workflow 中的 verify-docker job 负责轮询等待并校验镜像确实落库;
  8. release-tos.yml20. Release TOS Upload)在同一 Release published 事件下上传源码 zip 和安装脚本到 TOS。

正式主发版的发布目标汇总:

渠道 目标
PyPI openviking
GHCR ghcr.io/<owner>/<repo>
Docker Hub <dockerhub-user>/openviking
TOS 版本化 release 路径和可选 latest 稳定路径

几个从 workflow 源码中可以确认的实现细节:

  • 发布采用 Trusted Publishingpublish-pypi job 只申请 id-token: write 权限,使用 pypa/gh-action-pypi-publish@release/v1 并设置 skip-existing: true——同版本重复触发不会报错,而是幂等跳过。
  • 手动 dispatch 有权限门禁permission-check job 通过 getCollaboratorPermissionLevel 校验触发者至少具有 admin/maintain/write 权限,防止无权限者触发发布。
  • 构建矩阵可配:默认 os_json["ubuntu-24.04", "ubuntu-24.04-arm", "macos-14", "macos-15-intel", "windows-latest"](对应 Linux x86_64/aarch64、macOS arm64/x86_64、Windows x86_64),默认 python_json["3.10"]
  • Linux wheel 在 glibc 2.31 环境中构建_build.ymlbuild-linux job 运行在 ubuntu:20.04 容器内,从源码编译指定 CPython,再用 auditwheel repair 修复二进制依赖,保证 wheel 在较老 glibc 环境可安装。构建前还会把 Rust CLI ov 二进制打进 openviking/bin/,即 pip 装主包同时获得 ov 命令。
  • 内置冒烟测试:构建完成后 workflow 会实际 pip install 该 wheel,校验 RAGFS 绑定客户端、向量引擎过滤 ABI 符号以及 Web Studio 静态资源是否随包安装,任何一项缺失即构建失败。

verify-docker job 还揭示了一个值得注意的时序问题:tag push 与 Release published 几乎同时发生,release workflow 启动时镜像可能尚未构建完成,因此该 job 会每 30 秒轮询一次 build-docker-image.yml 在该 tag 上的运行记录(最多 60 次),并进一步用 docker buildx imagetools inspect 断言 GHCR 与 Docker Hub 上版本 tag 与 latest tag 的 digest 一致,不一致则整个 release 判定失败。

主包手动构建、测试发布与补发

release.yml 同样支持 workflow_dispatch 手动触发,输入参数 target 可选:

  • none:只构建,不发布;
  • testpypi:发布到 TestPyPI;
  • pypi:发布到 PyPI;
  • both:同时发布 TestPyPI 和 PyPI。

此外还有 build_sdistbuild_wheelsos_jsonpython_json 四个参数,可用于只构建部分平台的 wheel 或缩减构建范围,适合发版前验证。

如果需要基于已有构建产物补发 Python 包(例如构建成功但发布步骤失败),应使用 _publish.yml16. _Publish Distribution):手动 dispatch 时必填 build_run_id(从对应 Build 运行 URL 中获取)和目标渠道,workflow 通过 actions/download-artifact 的跨 run 能力(run-id + GITHUB_TOKEN)拉取原构建产物的 python-package-distributions-* 工件,再走相同的 pypa/gh-action-pypi-publish 流程。该流程适合发布失败后的补发,不建议作为正常主发版入口——正常发版一律走 03. Release,以保证 PyPI、Docker、TOS 三者的口径一致。

Docker 镜像发布与补发

Docker 镜像由 build-docker-image.ymlBuild and Push Docker Image)构建,触发条件有三类:

  • main 分支 push:产出 main tag 镜像;
  • v*.*.* tag push:产出版本 tag 镜像,并同时打上 latest tagtype=raw,value=latest,enable=${{ github.ref_type == 'tag' }});
  • workflow_dispatch 手动触发:version 输入框必填,重建指定版本镜像。

从 workflow 源码看,其执行结构是:

  1. 按矩阵在 ubuntu-24.04(amd64)与 ubuntu-24.04-arm(arm64)上分别 buildx 构建并推送,同时推 GHCR(ghcr.io/<owner>/<repo>,镜像名统一小写化)和 Docker Hub(docker.io/<DOCKERHUB_USERNAME>/openviking),各自输出 push-by-digest digest;
  2. create-manifest job 汇总两个架构的 digest artifact,用 docker buildx imagetools create 为每个 tag 创建多架构 manifest;
  3. 版本解析分三种来源:手动触发用输入值,tag 触发用 tag 名,main 分支则调用 build_support/versioning.pyresolve_openviking_version() 动态解析;解析结果为空或 0.0.0 时直接以退出码 2 失败,避免打出无版本镜像。

需要特别理解的口径问题:正式主发版时,Docker 镜像实际就是由这条独立 workflow 在 tag push 时构建的,而 03. Release workflow 只做等待与校验(verify-docker-image 轮询的正是 build-docker-image.yml 的运行记录)。文档中“正式主发版时 Docker 镜像由主 release workflow 自动构建并发布”的表述,从源码结构看更准确的描述是:release 流水线与镜像构建流水线由同一 tag 事件并行触发、release 流水线负责验证镜像发布结果。

使用建议与文档一致:正式版本优先走主 release 流程(tag + Release);只有镜像补发(例如某个 tag 的 manifest 损坏)或特殊验证时,才单独使用 workflow_dispatch 指定版本重建,且应保留已发布版本 tag 的可追溯性,避免与正式 release 产物口径混淆。

TOS 发布资产

release-tos.yml 由 Release published 事件(仅 v 前缀 tag)或手动 dispatch 触发,手动补发时必填 tag(如 v0.3.24),update_latest 默认为 true。

workflow 会 checkout 到对应 tag,生成并上传以下资产到 TOS(通过 AWS CLI 以 S3 协议访问 TOS endpoint):

  • releases/<tag>/openviking-<tag>-source.zip:由 git archive 生成的源码包,带 immutable 缓存头,视为不可变资产
  • releases/<tag>/memory-plugin-marketplace.zipmemory-plugins.git:memory 插件的瘦身市场包与 dumb HTTP git 仓库,供 Claude Code / Codex 等客户端离线安装;
  • Claude Code memory plugin、Codex memory plugin 及 shared 安装脚本(各含 install.shtos-install.sh)的对应 TOS 版本副本;
  • update_latest=true 时,上述内容还会服务端拷贝/同步到 releases/latest/ 与根路径稳定位置(带 no-store 缓存头)。

两个重要的容错设计:

  1. TOS secrets 未配置时不失败:workflow 先检查 TOS_ACCESS_KEYTOS_SECRET_KEYTOS_REGIONTOS_RELEASE_BUCKETTOS_ENDPOINT 五项 secrets,任一缺失则跳过上传,并在 step summary 中写明跳过原因——这保证 TOS 渠道的故障不会阻塞 PyPI/Docker 主发布。
  2. 上传清单可审计:所有成功上传的 key 会写入 $GITHUB_STEP_SUMMARY,run 页面可直接看到本次发版落库了哪些资产、稳定安装脚本是否被更新。

Python SDK 发版流程

Python SDK 位于 sdk/python,PyPI 包名为 openviking-sdk,使用独立 tag 命名空间 python-sdk@X.Y.Z。对应 python-sdk-release.ymlPython SDK Release):

  1. 合入 SDK 相关改动;
  2. 创建并推送 tag,例如 python-sdk@0.1.3
  3. workflow 的 build-sdk job 由 startsWith(github.event.release.tag_name, 'python-sdk@') 条件门控——主包 v* tag 触发的 Release 不会误发 SDK;
  4. job 在 sdk/python 目录内运行 python -m setuptools_scm 解析版本(其 tag 正则只匹配 python-sdk@*);
  5. 强校验 tag 与解析版本一致EXPECTED_TAG="python-sdk@${SDK_VERSION}",不相等直接 exit 1。这一步确保“tag 写的是 0.1.3、但 git describe 解析出别的版本”这类错误在发布前被拦截;
  6. python -m build 构建后,按事件类型决定目标:Release 触发固定发 PyPI,手动 dispatch 可选 testpypi/pypi/both,最终同样用 pypa/gh-action-pypi-publish + skip-existing 幂等发布。

Rust CLI / npm 发版流程

Rust CLI 对应 rust-cli.ymlRust CLI Build),由 cli@* tag push 触发(另监听 mainfeat/rust-cli 分支的 crates 路径变更做 CI 构建,但不发布)。

构建矩阵覆盖 5 个平台,并各自打包成一个 npm 平台包:

构建目标 平台包
x86_64-unknown-linux-musl @openviking/cli-linux-x64
aarch64-unknown-linux-musl @openviking/cli-linux-arm64
x86_64-apple-darwin @openviking/cli-darwin-x64
aarch64-apple-darwin @openviking/cli-darwin-arm64
x86_64-pc-windows-msvc @openviking/cli-win32-x64

源码中可以看到几个关键工程决策:

  • Linux 产物采用 musl 静态构建:workflow 安装 Zig + cargo-zigbuild 完成 musl 交叉编译,使二进制不依赖运行环境的 glibc 版本(注释明确说明这是为了兼容 CentOS 7 / RHEL 8 等旧 glibc 发行版);
  • 版本注入:tag 中的版本号通过 sed 注入 crates/ov_cli/Cargo.toml(源文件中版本占位为 0.0.0),非 tag 构建则使用 0.0.0-dev
  • 平台包按需生成:每个平台的 package.json 由 workflow 现场生成,写入 os/cpu 约束和 bin 文件,npm 用户只需安装 wrapper 包 npm/cli
  • 幂等发布npm-publish job 在 tag 事件下先 npm view <pkg>@<version> 检查,已存在的版本直接跳过,随后把 wrapper 包 @openviking/cliversion 与全部 optionalDependencies 对齐到该版本再发布。

OpenClaw / ClawHub 插件发布

OpenClaw 插件通过 clawhub-dev-release.ymlOpenViking OpenClaw plugin release)发布,打包对象为 examples/openclaw-plugin。触发方式:main 分支上 examples/openclaw-plugin/** 路径变更自动触发,或手动 dispatch。

手动触发的输入参数:

  • version:可选;留空时由 workflow 按日期自动生成(YYYY.M.D / YYYY.M.D-N / YYYY.M.D-dev.N);
  • channelautodevlatest
  • package_ref:指定打包的 git ref,默认 main
  • changelog:本次插件发布说明;
  • publish_clawhub / publish_npm:分别控制是否发布到 ClawHub 和 npm(@openviking/openclaw-plugin)。

从 workflow 结构看,其 guard job 负责解析 channel 与 version:结合 CLAWHUB_UPSTREAM_REPOSITORYCLAWHUB_LATEST_REPOSITORIESCLAWHUB_DEV_REPOSITORIESCLAWHUB_RELEASE_TIMEZONE 等仓库变量决定 latest 与 dev 的发布来源,并通过 concurrency: openclaw-plugin-release(不取消进行中的运行)串行化发版,避免连续合并时日期版本号解析互相竞争。发布形态上,workflow 同时保留向 ClawHub 发布 legacy zip 工件(兼容旧版 OpenClaw 的 /download 哈希机制)和向 npm 发布打包 tarball 两条路径。

推荐做法:正式渠道使用 latestauto,开发验证使用 dev;手动指定 version 时确保符合对应 channel 的格式要求(YYYY.M.D 系列)。

VikingBot 发布说明

VikingBot 当前不再作为推荐的独立 PyPI 包发版路径维护,现行分发方式是随主包发布:

  • Python 安装入口:pip install "openviking[bot]"
  • 源码开发入口:uv pip install -e ".[bot]"
  • 官方 Docker 镜像默认已包含 VikingBot,可通过 --without-botOPENVIKING_WITH_BOT=0 关闭。

仓库状态与这一结论互相印证:bot/ 目录下没有 pyproject.tomlsetup.pysetup.cfg(已核实不存在),因此无法从本仓库将其作为独立 Python 包发布。同时,bot/.github/workflows/release.yml 位于 bot 子目录内,应视为历史 bot 子项目或拆分仓库的发布参考,不是根仓库当前可直接触发的 GitHub Actions workflow。如需恢复独立 vikingbot 包,需要先在 bot/ 下补齐独立的 Python 包配置、版本策略和发布凭证策略。

发版前检查清单

发版前建议逐项确认:

  • 待发布改动已合入目标分支;
  • CI / PR 检查已通过;
  • 版本号未在 PyPI、npm 或 Docker registry 中发布过;
  • tag 命名符合对应产物约定(v* / python-sdk@* / cli@* / YYYY.M.D*);
  • Python 包依赖、构建配置和 README 已同步更新;
  • Docker Hub、PyPI/TestPyPI、TOS、npm、ClawHub 等发布所需 secrets 或 trusted publishing 配置可用;
  • Release notes 已准备好,且说明破坏性变更、迁移步骤和重要修复。

发版后验证清单

发版后建议验证:

  • PyPI / TestPyPI 上的包版本与 tag 一致;
  • pip install openviking==<version>pip install openviking-sdk==<version> 可成功安装;
  • Docker registry 中存在版本 tag 和预期的 latest / main tag;
  • 多架构 Docker manifest 可正常拉取(03. Releaseverify-docker job 会自动做 digest 级校验);
  • TOS 版本化路径和稳定路径可访问;
  • npm 上存在对应 CLI 平台包(5 个)和 wrapper 包 @openviking/cli
  • ClawHub 插件 channel 和 version 符合预期。

故障处理与补发原则

  • PyPI 和 npm 已发布版本通常不可覆盖:如包内容有误,应发布新版本;两条流水线的 skip-existing / npm view 检查正是为此设计的幂等防线。
  • Docker 的 latestmain 和手动指定 tag 可通过独立 Docker workflow 重建,但应保留已发布版本 tag 的可追溯性。
  • TOS 的版本化路径应视为不可变资产(上传时显式写入 immutable 缓存头);稳定路径可通过手动 workflow 的 update_latest 覆盖。
  • 构建成功但发布失败时,优先使用补发 workflow(16. _Publish Distribution 传入 build_run_id)或手动 dispatch,避免重新创建不同内容的同名 tag。
  • tag 命名错误时,优先删除错误 tag 并重新创建正确 tag,前提是该 tag 尚未触发不可逆发布(PyPI 上传属于不可逆操作)。

综上,OpenViking 的发布体系核心是“tag 命名空间隔离 + workflow 前缀门控 + 幂等发布 + 独立的构建/发布分层”:主包、SDK、CLI 各自用正则有界的 tag 解析版本,发布动作全部走 skip-existing 幂等通道,构建(15. _Build Distribution)与发布(16. _Publish Distribution)解耦,使任何一次发布失败都能在不重建产物的前提下安全补发。

登录后查看全文
热门项目推荐
相关项目推荐