跳转到内容

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>

  1. $EXTENSIONS_DIR/cnb-<NAME>/manifest.toml
  2. pinned = true 且无 --force → 输出「⏭ pinned 跳过」后 exit 0
  3. 调 CNB API GET /{owner}/{repo}/-/releases/latest 拉 latest tag
  4. 若 latest tag 与 manifest.tag 相同 → 输出「✓ 已是最新」后 exit 0
  5. --dry-run → 输出「📋 可升级」后 exit 0(不动文件)
  6. 否则:触发完整 install 流程(重用 cnb-rs ext install 的 12 步流程,force=true 因为目录已存在)→ 输出「✓ 升级成功」

批量升级(upgrade --all

  1. list_installed() 枚举所有 extension
  2. 逐个调用单个升级流程(fail-soft:单个失败不阻断其它)
  3. 累积输出每个 extension 的状态,最后打印汇总表格
  4. 任一 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 $?
1

24h 自动 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.cool

detach 机制:为什么不阻塞前台

触发点在前台进程内只走1-2 个轻量级动作(总处理时间 < 5ms):

  1. state.toml 看是否需要 refresh(本地文件读,无网络)
  2. 抢先把 state.updates.last_check_at 写到 now (防止 spawn 失败时形成 retry storm)
  3. 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机制合作效果
WindowsCREATE_NO_WINDOW | DETACHED_PROCESS creation flagsstdio 全 Stdio::null()子进程不弹 console 窗口、不继承 console、不污染前台输出
Unixprocess_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 结构

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 upgradegh extension upgrade
单个升级cnb-rs ext upgrade NAMEgh 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✅ 单个失败不阻断其它✅ 同

另请参阅

Released under the MIT License.