cnb-rs ext upgrade
cnb-rs ext upgrade [NAME] [options]升级已安装的 extension:比对 manifest.tag vs 最新 release,tag 不同则触发完整 install 流程(下载新 binary + SHA256 校验 + atomic move + 重写 manifest)。
参数
[NAME]:要升级的 extension 命令名(不含cnb-前缀)。省略时必须加--all
选项
--all:升级所有已装 extension(与<NAME>互斥)。默认跳过 pinned extension,加--force强制升级--force:强制升级 pinned extension。upgrade --all --force会把所有 pinned extension 一起拉到 latest--dry-run:仅 check 不升级。拉 latest release 比对 tag,输出哪些 extension 可以升级,但不动文件
继承的全局选项:
--domain <DOMAIN>:指定 CNB 域名,默认cnb.cool
行为
单个升级(upgrade <NAME>)
- 读
$EXTENSIONS_DIR/cnb-<NAME>/manifest.toml - 若
pinned = true且无--force→ 输出「⏭ pinned 跳过」后 exit 0 - 调 CNB API
GET /{owner}/{repo}/-/releases/latest拉 latest tag - 若 latest tag 与 manifest.tag 相同 → 输出「✓ 已是最新」后 exit 0
- 若
--dry-run→ 输出「📋 可升级」后 exit 0(不动文件) - 否则:触发完整 install 流程(重用
cnb-rs ext install的 12 步流程,force=true因为目录已存在)→ 输出「✓ 升级成功」
批量升级(upgrade --all)
- 调
list_installed()枚举所有 extension - 逐个调用单个升级流程(fail-soft:单个失败不阻断其它)
- 累积输出每个 extension 的状态,最后打印汇总表格
- 任一 extension 升级失败 → 整体 exit 1
无 manifest 的 extension
手工放置的 binary(Phase 3.4 之前 cp 进 extensions 目录的)没有 manifest,无法判断升级源头,输出「⚠ 无 manifest(手工放置 binary,无升级凭据)」后 exit 0 跳过。建议这类用户重装:cnb-rs ext install <repo> --force。
输出符号
| 符号 | 含义 |
|---|---|
✓ | 已是最新 / 升级成功 |
⏭ | 跳过(pinned) |
📋 | dry-run 可升级 |
⚠ | 无 manifest |
✗ | 升级失败 |
输出示例
升级单个
$ cnb-rs ext upgrade stats
⏬ 下载 cnb-stats-linux-amd64 (4521320 bytes) ...
✓ SHA256 校验通过
✓ cnb-stats: 升级成功(v0.1.0 → v0.1.1)已是最新
$ cnb-rs ext upgrade stats
✓ cnb-stats: 已是最新(v0.1.1)Pinned 跳过
$ cnb-rs ext upgrade chat
⏭ cnb-chat: pinned 跳过(v0.2.0;用 --force 强制升级)Pinned 强制升级
$ cnb-rs ext upgrade chat --force
⏬ 下载 cnb-chat-linux-amd64 (5901320 bytes) ...
✓ SHA256 校验通过
✓ cnb-chat: 升级成功(v0.2.0 → v0.3.0)dry-run
$ cnb-rs ext upgrade stats --dry-run
📋 cnb-stats: 可升级(v0.1.0 → v0.1.1)批量 dry-run
$ cnb-rs ext upgrade --all --dry-run
📋 cnb-stats: 可升级(v0.1.0 → v0.1.1)
✓ cnb-whoami: 已是最新(v1.2.0)
⏭ cnb-chat: pinned 跳过(v0.2.0;用 --force 强制升级)
⚠ cnb-legacy: 无 manifest(手工放置 binary,无升级凭据)
== 升级汇总 ==
已升级: 0
已是最新: 1
跳过 pinned: 1
dry-run 可升级: 1
无 manifest: 1批量升级
$ cnb-rs ext upgrade --all
✓ cnb-whoami: 已是最新(v1.2.0)
⏬ 下载 cnb-stats-linux-amd64 ...
✓ cnb-stats: 升级成功(v0.1.0 → v0.1.1)
⏭ cnb-chat: pinned 跳过(v0.2.0;用 --force 强制升级)
⚠ cnb-legacy: 无 manifest(手工放置 binary,无升级凭据)
== 升级汇总 ==
已升级: 1
已是最新: 1
跳过 pinned: 1
无 manifest: 1错误示例(友好提示)
缺少参数
$ cnb-rs ext upgrade
错误: 缺少 <NAME>,或加 --all 升级所有已装 extension参数冲突
$ cnb-rs ext upgrade stats --all
错误: 不能同时指定 <NAME> 和 --all。用 'cnb-rs ext upgrade --all' 升级所有,或 'cnb-rs ext upgrade <NAME>' 升级单个单个升级失败(fail-soft 不阻断 --all)
$ cnb-rs ext upgrade --all
✗ cnb-deleted-repo: 升级失败:获取 alice/cnb-deleted-repo 的 latest release 失败:HTTP 404 (...)
✓ cnb-stats: 升级成功(v0.1.0 → v0.1.1)
== 升级汇总 ==
已升级: 1
失败: 1
$ echo $?
124h 自动 check【Phase 6 Task C:detached 后台子进程】
以下两个场景会触发后台 update check,判断距上次 check 超过 24 小时后拉所有 extension 的 latest release tag 写入 $EXTENSIONS_DIR/state.toml、发现有新版本时把提示存入 pending_notices:
cnb-rs ext list调用时- 任意 extension dispatch 路径(
cnb-rs <ext-name> ...,如cnb-rs stats)启动时
下次任何 cnb-rs 命令启动时,main entry 会读取 state.toml 把 pending_notices 打印到 stderr 并清空:
$ cnb-rs auth status
💡 cnb-stats 有新版本可用(v0.1.0 → v0.1.1),运行 'cnb-rs ext upgrade stats' 升级
(设 CNB_NO_EXTENSION_NOTICE=1 禁用此提示)
✓ 已登录 user@cnb.cooldetach 机制:为什么不阻塞前台
触发点在前台进程内只走1-2 个轻量级动作(总处理时间 < 5ms):
- 读
state.toml看是否需要 refresh(本地文件读,无网络) - 抢先把
state.updates.last_check_at写到now(防止 spawn 失败时形成 retry storm) Command::new(current_exe()).arg("ext").arg("_check-upgrades")spawn 出一个 detached 子进程后立刻返回
子进程在后台异步跟 CNB API 拉每个 extension 的 latest release(本质是 cnb-rs ext _check-upgrades 这个 hidden subcommand),跟原 sync 实现逻辑一致,只是运行位置从主进程划出去。
平台特定 detach 实现:
| OS | 机制 | 合作 | 效果 |
|---|---|---|---|
| Windows | CREATE_NO_WINDOW | DETACHED_PROCESS creation flags | stdio 全 Stdio::null() | 子进程不弹 console 窗口、不继承 console、不污染前台输出 |
| Unix | process_group(0)(等价 setpgid(0,0)) | stdio 全 Stdio::null() | 子进程脱离父 process group,Ctrl-C 不会带倒后台 check |
初次体验:第一次进入 24h cycle 时,当下一次启动才会看到 pending notices。旧 sync 实现是一次启动内同步 check+同步打印,代价是 block 1-2s(24h 阈值下这一 cycle 延迟用户几乎感知不到)。
禁用提示
设以下任一环境变量即可永久禁用 update check 和提示(后台子进程也会被 gate 拦下):
CNB_NO_EXTENSION_NOTICE=1:明确禁用 extension 提示CI=true:CI 平台通用约定(GitHub Actions / GitLab / CNB Pipeline 等默认设)
Hidden subcommand _check-upgrades
cnb-rs ext _check-upgrades 是专门给 detached 子进程的入口 subcommand(clap hide = true、不出现在 --help 中),逻辑仅调 check_all_quiet(ctx) 后静默退出 0。不推荐用户手动调用,但 debug 后台 spawn 行为时可以直接执行它验证 check 逻辑本身没问题。
state.toml 结构
# $EXTENSIONS_DIR/state.toml
[updates]
last_check_at = "2026-05-19T07:00:00Z"
pending_notices = [
"💡 cnb-stats 有新版本可用(v0.1.0 → v0.1.1),运行 'cnb-rs ext upgrade stats' 升级",
]
[extensions.cnb-stats]
last_latest_tag = "v0.1.1"
last_check_at = "2026-05-19T07:00:00Z"state.toml 与 manifest.toml 职责区分:
- manifest.toml:每个 extension 自己的「安装凭据」,不可变,描述「安装时是哪个 tag / 用哪个 kind / 谁是源仓库」
- state.toml:全局可变「运行时缓存」,描述「最近一次 check 看到的远端 latest tag」+ 「待提示的消息」
退出码
0:所有指定的 extension 都成功处理(包括 UpToDate / SkippedPinned / DryRun / NoManifest)1:参数错误 / 任一 extension 升级失败
与 gh extension upgrade 的差异
| 特性 | cnb-rs ext upgrade | gh extension upgrade |
|---|---|---|
| 单个升级 | ✅ cnb-rs ext upgrade NAME | ✅ gh ext upgrade NAME |
| 批量升级 | ✅ --all | ✅ --all |
| pinned 跳过 | ✅ 默认跳过 | ✅ 默认跳过 |
| 强制升级 pinned | ✅ --force | ✅ --force |
| dry-run 模式 | ✅ --dry-run | ✅ --dry-run |
| 升级时 SHA256 校验 | ✅ 强校验(复用 install 流程) | ❌ 不校验 |
| 24h 自动 check | ✅ ext list 与 extension dispatch 都会触发,detached 子进程处理、前台 < 5ms | ✅ 通过 GH 后台 daemon |
| pending 提示打印 | ✅ stderr,下次启动消费 | ✅ 类似 |
| fail-soft batch upgrade | ✅ 单个失败不阻断其它 | ✅ 同 |
另请参阅
- cnb-rs ext
- cnb-rs ext install — upgrade 复用 install 流程
- cnb-rs ext list — list 会触发 24h 后台 check
- 指南:cnb-rs extensions — 完整生命周期 + state.toml 设计动机