首页
/ Open Notebook 发布操作手册:版本镜像构建、清单验证、RC 栈验证与 GitHub 发布的完整命令参考

Open Notebook 发布操作手册:版本镜像构建、清单验证、RC 栈验证与 GitHub 发布的完整命令参考

2026-09-05 12:22:30作者:舒璇辛Bertina

本文基于 Open Notebook 仓库中的发布运行手册 runbook.md 展开,系统讲解稳定版发布流程中"切版 → 推镜像 → 验证 → 发布 → 清理"各阶段(Phase 5–8)的可复制命令。读完本文,你将掌握如何用 CI 构建双架构版本镜像、如何用 docker manifest inspect 验证推送产物、如何用 rc-stack.sh 在本地起一套带真实开发数据副本的 RC 栈做人工走查,以及 GitHub Release 发布后如何自动推进 v1-latest 标签。该手册的"已知坑位"(Known Gotchas)维护在 .github/RELEASE_PROCESS.md,本文一并结合仓库内 Makefilerc-stack.sh 等源码对其中的命令做逐条拆解。

一、runbook 在发布流程中的定位

Open Notebook 采用流程驱动的发布模型:工作从 ready 标签的 issue 进入 PR,PR 合入 main 后由维护者在分支积累足够且经过验证的变更时切版。完整的发布过程(changelog 审计、风险测试矩阵、镜像门禁、修复循环)定义在 .github/RELEASE_PROCESS.md,其设计依据见 ADR-005

  • Patch 版本:向后兼容的修复;
  • Minor 版本:向后兼容的功能与改进;
  • Major 版本:包含破坏性变更或需要用户配合的迁移时,配合里程碑提前规划;
  • 变更标签约定:in-dev-build 表示变更已进入开发镜像,released 表示已随版本发布。

runbook.md 明确自我定位为"命令参考":踩坑经验写在 RELEASE_PROCESS.md 的 Known Gotchas,本文对应文件只负责给出"确切可执行的命令"。它覆盖发布流水线的四个执行阶段:

阶段 内容 对应命令族
Phase 5 通过 CI 构建并推送版本镜像 gh workflow run / gh run watch
Phase 6 用开发数据副本起 RC 栈人工验证 docker exec surreal export / make release-stack
Phase 7 发布 GitHub Release(需显式 GO 之后) gh release create
Phase 8 清理本地环境 make release-stack-down 及临时文件清理

二、Phase 5:通过 CI 构建版本镜像

runbook 给出的原始命令:

gh workflow run build-and-release.yml --ref main -f push_latest=false
gh run list --workflow=build-and-release.yml --limit 1     # grab the id
gh run watch <run-id> --exit-status                        # background it

逐条说明:

  1. gh workflow run build-and-release.yml --ref main -f push_latest=false:从 main 分支手动触发 Build and Release 工作流,并通过 -f push_latest=false 声明式输入指定"只推版本标签、不动 latest"。之所以走 CI 而不是本地构建,.github/RELEASE_PROCESS.md 解释得很直接:CI 持有镜像仓库的推送凭据(Docker Hub 与 GitHub Container Registry 两端)。本地 make docker-push 同样可行,但要求你在两个 registry 上分别 docker login
  2. gh run list --workflow=... --limit 1:拿到本次触发的 run id,供下一条命令使用。
  3. gh run watch <run-id> --exit-status:阻塞式观察构建,--exit-status 让进程退出码反映工作流结果,方便挂到后台或脚本中做断言。

Makefile 的本地等价目标可以看出 CI 推送的完整产物面(第 78–104 行 docker-push 目标):

  • 常规镜像:lfnovo/open_notebook:<ver>ghcr.io/lfnovo/open-notebook:<ver>
  • 单容器镜像(--target single):<ver>-single 双 registry 各一份;
  • 平台固定为 linux/amd64,linux/arm64(Makefile 中 PLATFORMS 变量)。

两个 registry、两种镜像变体、两个架构——这就是下一步清单验证要核对的 3 个 ref 的由来。

三、验证已推送的镜像清单(manifest)

runbook 的命令:

for ref in lfnovo/open_notebook:<ver> lfnovo/open_notebook:<ver>-single ghcr.io/lfnovo/open-notebook:<ver>; do
  docker manifest inspect "$ref" | python3 -c "import json,sys; d=json.load(sys.stdin); print(sorted(set(m['platform']['architecture'] for m in d.get('manifests',[]) if m['platform']['architecture']!='unknown')))"
done
# expect ['amd64', 'arm64'] for each; repeat with v1-latest after publication

这条循环值得细读:

  • docker manifest inspect 拉取 manifest list(多架构索引)的 JSON;
  • 内嵌的 Python 一行程序从 manifests 数组中收集 platform.architecture过滤掉 unknown——manifest list 中常有一个无平台信息的摘要项,不过滤会污染结果;
  • 对三个 ref 各执行一次,期望输出均为 ['amd64', 'arm64']
  • 注释明确要求:在正式发布(Phase 7)之后,还要对 v1-latest 标签重复同样的检查,确保 latest 指针也指向双架构产物。

这一步对应 RELEASE_PROCESS.md 中的"bucket C 最终门禁"思想:main 上绿灯不等于镜像可用,必须验证推送产物本身。仓库内配套的自动化版本是 release-image-test.sh,它通过 make release-test TAG=<new> OLD_TAG=<previous> 触发,对真实容器跑 fresh install 与 upgrade 两个场景(详见该脚本注释与 docker-compose.release-test.yml),属于切版前的镜像门禁;而 runbook 这条 manifest 检查则是 CI 推送后的轻量核对。

四、Phase 6:用开发数据副本起 RC 栈人工验证

这是 runbook 中信息密度最高的段落,原文注释完整继承如下(# 开头的注释一并保留):

# 1. Find which SurrealDB instance dev actually uses — read SURREAL_URL in .env
#    (multiple instances may run locally; the repo-compose one on :8000 may NOT
#    be it), and note SURREAL_DATABASE.
# 2. Consistent export from the RUNNING instance (originals untouched):
docker exec <that-container> /surreal export --conn http://localhost:8000 \
  --user root --pass root --ns open_notebook --db <that-db> /dev/stdout > /tmp/dev-dump.surql
# 3. Boot (rc-stack.sh docker-pulls the pushed tag by default, so a local
#    build can't shadow the registry artifact):
make release-stack TAG=<ver> DUMP=/tmp/dev-dump.surql
#    To exercise the opt-in heavy runtimes (Docling + Crawl4AI) on the pushed
#    image with this data, append the flag (first boot installs them, ~minutes):
#    bash scripts/release-test/rc-stack.sh up <ver> /tmp/dev-dump.surql --with-runtimes
# 4. Sanity: credentials decrypt (uses the dev encryption key from .env):
curl -s http://localhost:15055/api/credentials | python3 -c "import json,sys; c=json.load(sys.stdin); print(len(c), 'creds,', sum(1 for x in c if x.get('decryption_error')), 'decrypt errors')"
# 5. Opt-in gating is only meaningful on this fresh image (a dev venv may have
#    the runtimes installed out-of-band): GET /api/capabilities should report
#    both false until --with-runtimes installs them.

以及紧随其后的提醒:

提醒 owner:容器内指向宿主服务的凭据必须使用 http://host.docker.internal:<port>(Ollama、LM Studio 等)。

4.1 为什么先读 SURREAL_URL

runbook 第一条注释对应 RELEASE_PROCESS.md 的一个已知坑:本地可能同时跑着多个 SurrealDB 实例,开发 .envSURREAL_URL 指向的那个才是真数据所在,repo-compose 起的 :8000 未必是它。所以导出前必须先确认目标实例,并记下 SURREAL_DATABASE

4.2 一致的导出:不动原始数据

/surreal export运行中的实例导出,原始库只读不受影响,导出结果重定向到 /tmp/dev-dump.surql。这与 rc-stack.sh 头部注释给出的用法完全一致(脚本第 18–21 行)。

4.3 rc-stack.sh 的内部机制(源码佐证)

make release-stack 只是薄封装,Makefile 第 59–61 行将其转发为 bash scripts/release-test/rc-stack.sh up <TAG> [DUMP]。读 rc-stack.sh 可以确认 runbook 注释背后的一串关键设计:

  • 默认 docker pull 推送过的 tag(脚本第 60 行):这正是 runbook 注释"rc-stack.sh docker-pulls the pushed tag by default"的出处。本地 make docker-build-local 打的 tag 与 registry 产物同名(lfnovo/open_notebook:<ver>),若不先 pull,验证的就可能是自己的本地构建而非注册表产物——这是 v1.13.0 留下的教训(见 RELEASE_PROCESS.md Known Gotchas)。pull 失败仅告警不致命,因此纯本地 tag 也能用。
  • 复用开发加密密钥(脚本第 39 行):从 .env 读取 OPEN_NOTEBOOK_ENCRYPTION_KEY 注入 RC 栈,这样从开发库导出的加密凭据才能解密——对应 runbook 第 4 步的 sanity check:GET /api/credentials 后统计 decryption_error,期望为 0。
  • 数据库名必须与 dump 来源一致(脚本第 40、46 行):默认取 .envSURREAL_DATABASE
  • 导入前的两处 sed 处理(脚本第 65–70 行):SurrealDB 导入有两个实战坑,脚本都已处理——OVERWRITE 必须放在类型关键字之后(DEFINE FIELD OVERWRITE …,因为 RELATION 表会自动定义 in/out 字段);导出器可能把自己的一行日志(以 ESC 控制符开头)泄进 dump 文件,用 sed -E $'/^\x1b/d' 剔除。
  • API_URL 必须显式设置docker-compose.release-test.yml 第 14–18 行注释说明,不设置时前端 /config 会把浏览器指向 host:5055——在开发机上那就是开发 API,会造成静默的数据串环境。脚本固定注入 RC_API_URL="http://localhost:15055"
  • host.docker.internal 映射(compose 第 32–37 行):通过 extra_hosts: host-gateway 让 Linux 下容器也能解析 host.docker.internal,对应 runbook 末尾关于 Ollama / LM Studio 凭据的提醒,脚本启动后也会把这条 NOTE 打到终端。
  • 端口布局:UI http://localhost:18502、nginx 代理 http://localhost:18080、API http://localhost:15055(compose 中三个端口均绑定 127.0.0.1,不对外暴露)。
  • --with-runtimes 标志:把 OPEN_NOTEBOOK_ENABLE_DOCLING / OPEN_NOTEBOOK_ENABLE_CRAWL4AI 置为 true(脚本第 47 行),让推送出去的镜像在首次启动时真正执行可选重型引擎(Docling + Crawl4AI)的安装路径,首启需要数分钟。

4.4 为什么要在"干净镜像"上判断 opt-in 门控

runbook 第 5 条注释(GET /api/capabilities--with-runtimes 安装前两者应为 false)对应 RELEASE_PROCESS.md 的 v1.13.0 教训:开发 venv 可能已经通过非 opt-in 途径装过 crawl4ai/docling,此时 capabilities 接口会误报"可用"、UI 会点亮引擎开关,但这不能代表精简默认镜像的真实状态。只有在 RC 栈(拉取的推送镜像)上验证,opt-in 门控的判定才有效。

五、Phase 7:发布 GitHub Release(显式 GO 之后)

runbook 原文命令:

gh release create v<ver> --title "v<ver> — <theme>" --notes-file <notes.md> --latest
# publication (non-prerelease) triggers the workflow that pushes v1-latest
gh run list --workflow=build-and-release.yml --limit 1 && gh run watch <id> --exit-status

机制拆解(结合 RELEASE_PROCESS.md 的 "Docker Image Publishing (reference)" 表):

命令/事件 行为 是否更新 latest
CI Build and Releasepush_latest=false 用 CI 凭据推送版本 tag
非 pre-release 的 GitHub Release 发布 再次触发工作流,推送版本 + v1-latest
make docker-push / docker-push-latest 本地等价物(需 docker login 否 / 是
make tag 创建并推送与 pyproject.toml 一致的 git tag

要点:

  • 发布必须发生在 Phase 6 验证通过且 release owner 明确 GO 之后——按 ADR-005 的原则"自动化做准备,人扣扳机"。
  • 发布的 release 必须不是 pre-release,因为正是"non-prerelease 发布"这一事件触发工作流去推 v1-latest(含 v1-latestv1-latest-single 四个 tag,见 Makefile 第 106–136 行 docker-push-latest 的本地等价逻辑)。
  • 发布后回到第三节的 manifest 检查,对 v1-latest 再验一遍双架构。

RELEASE_PROCESS.md 还给出了 Release Notes 的结构约定(以 v1.11.0 为参考样板):一行结论 + 升级建议;Security / Features / Performance / Notable fixes 分节;对自托管者的行为变更必须显式提醒(任何可能要求升级时改配置的内容);致谢部分必须列出每位贡献者的 handle 及其交付物(通过 git log <last-tag>..<tag>gh pr view 收集),不可省略。

六、给已交付的 issue 打 released 标签

runbook 原文命令:

# only actual closed ISSUES (changelog refs mix issues and PR numbers):
for n in <numbers>; do
  STATE=$(gh api "repos/lfnovo/open-notebook/issues/$n" --jq 'if .pull_request then "pr" else .state end')
  [ "$STATE" = "closed" ] && gh issue edit "$n" --add-label released
done

两个细节:

  • changelog 引用混杂 issue 号与 PR 号(GitHub issues API 对 PR 同样返回),所以先用 if .pull_request 过滤出纯 issue;
  • 只对 closed 状态的 issue 打标签,PR 与未关闭的 issue 一律跳过。

另一个流程细节(RELEASE_PROCESS.md Release Model 一节):released 标签曾在 v1.12.0 的标签体系整理中被误删,之后重建。原则是——如果文档引用的标签缺失,重建它,而不是跳过这一步

七、Phase 8:清理

runbook 原文命令:

make release-stack-down
rm -f /tmp/dev-dump.surql; rm -rf /tmp/onrel-*
docker ps --format '{{.Names}}' | grep onrel   # must be empty
git status --short                              # must be clean on main

对应源码行为:

  • make release-stack-downrc-stack.shdown 分支:compose down -v 移除数据卷并 rm -rf /tmp/onrel-rc-data,保证数据目录一并清掉;
  • rm -rf /tmp/onrel-* 兜底清理 release-image-test.sh 各阶段用的 mktemp 目录(/tmp/onrel-fresh-XXXX/tmp/onrel-upg-XXXX,见该脚本第 89、118 行);
  • 最后两条是必须为空/干净的断言:没有任何 onrel 前缀容器残留(release-image-test.sh 特意给每个阶段分配独立端口,就是防止泄漏容器替错误的栈应答),且 main 分支 git status --short 无未提交变更——后者呼应 Known Gotchas 的第一条铁律(见下节)。

八、Known Gotchas 中与 runbook 直接相关的坑位

.github/RELEASE_PROCESS.md 的 Known Gotchas 是 runbook 各阶段的配套防线,摘出与上述命令强相关者:

  1. 版本 bump 绝不允许未提交地留在分支上:切版时编辑 pyproject.toml / CHANGELOG.md 后切换分支,未暂存变更会被带走,下一次 git add -A 会把版本提升悄悄卷进无关的 fix PR——版本号会藏在 fix(...) 提交里发布。切版永远是最后一步、独立分支、立即提交(v1.14.0 教训)。这也解释了 Phase 8 为什么要求 git status --short 干净。
  2. cut 之后落的修复需要完整重新切版,而不是挪 tag:若已打 tag 但未发布(无 GitHub release、无 v1-latest)且 bucket C 发现阻断问题,tag 必须移到新提交版本镜像必须重建——否则陈旧 tag 或陈旧 registry 镜像会被发布动作提升为 v1-latest
  3. 本地 docker-build-local 的 tag 会遮蔽推送镜像:两者同名,Phase 6 可能验证的是自己的本地构建。rc-stack.sh up 现已默认 pull;若用其他方式启动镜像,先 pull(v1.13.0 教训)。
  4. RC 栈在非默认端口时需要 API_URL,否则浏览器会打到 host:5055——在开发机上那是开发 API(数据串环境)。rc-stack.sh 已设好;自定义部署时自行记住。
  5. SurrealDB 导入坑OVERWRITE 位置、导出器日志泄漏——rc-stack.sh 的 sed 已处理(见 4.3)。
  6. 开发机端口可能属于其他项目:启动或杀进程前先查 3000/5055/8000 的占用者(lsof -nP -iTCP:<port> -sTCP:LISTEN 加进程 cwd);前端换端口跑冒烟(PORT=3001 npm run dev)完全没问题。
  7. 测试套件在加载了开发 .env 时会打到真实开发库:bucket A 期间要快照各表记录数,前后有差异说明测试泄漏写操作(v1.12.0 曾捕获 48 条泄漏的 Test 凭据)。
  8. opt-in 运行时门控必须在干净镜像上判定(v1.13.0 教训,已并入 4.4)。

九、适用前提与限制

  • 本文所有命令以当前仓库快照为准:镜像名为 lfnovo/open_notebook(Docker Hub)与 ghcr.io/lfnovo/open-notebook,平台 linux/amd64,linux/arm64,版本源头是 pyproject.tomlMakefile 第 7 行从中取 VERSION);
  • runbook 引用的 Build and Release 工作流(build-and-release.yml)在当前快照的 .github/workflows/ 目录中未包含对应文件,从文件清单看该目录仅有 docs-links.ymltest.yml——可以推断该工作流由发布方在远端仓库维护或按需启用,本地执行前请以远端实际可用的 workflow 名称为准;
  • gh 命令要求已登录并具备仓库写权限与 release 管理权限;make docker-push 本地路线则要求双 registry 的 docker login
  • RC 栈全部端口仅绑定 127.0.0.1,面向本机验证;升级场景测试还要求历史版本镜像在 registry 上持续可用(ADR-005 Consequences 一节明确列出该依赖);
  • runbook 是"命令参考"性质,流程语义与判断标准(风险矩阵分桶、re-test 策略、沟通模板、复盘)以 .github/RELEASE_PROCESS.mdADR-005 为准,两者配合使用才构成完整的发布操作闭环。
登录后查看全文
热门项目推荐
相关项目推荐