Incus 脚本审阅与验收 · 第 6 / 8 篇
一个脚本“修复版”能绕过眼前错误,并不代表它已经可靠。现存文档记录了不支持 --format 的问题,当前代码改用 incus query 读取 JSON,也把菜单输入转向 /dev/tty。这些变化有明确方向;继续审核时,我们又发现了与输出结构和重试状态有关的问题。

先区分历史记录与重新复现
旧版完整源码与当时的终端日志没有出现在这批文件里。因此,关于旧版报错,本系列只能表述为“手册记录了 unknown flag 问题”。我们能重新核对的是现存版本如何读取数据,不能补写当时花了多久、哪次执行终于成功等故事。
当前重装代码通过 /1.0/instances/实例名 获取 JSON,用 devices 与 expanded_devices 分别识别本地设备和最终生效设备。这比在终端表格中截取列更适合程序处理。仍要检查字段、返回码和目标唯一性;JSON 并不会自动让错误处理变完整。
第一个可离线复现的问题:设备名并没有那两个空格
部署脚本用这个表达式判断 SSH 代理是否存在:
incus config device show "$NAME" | grep -qE "^ ssh${SSH_PORT}:$"
Incus 6.0.6 官方 config device show 实现直接把设备映射序列化为 YAML。设备名称位于顶层,而不是完整配置中 devices: 下的缩进层。6.0.6 官方源码
把代表性输入交给同一个正则,就能复现误判,无需操作任何服务器:
printf 'ssh2201:\n type: proxy\n' | grep -qE '^ ssh2201:$'
echo "$?"
# 预期返回 1:已有顶层设备未被表达式匹配
这意味着第二次部署可能把已有设备误当作不存在,尝试重复添加并因 strict mode 提前退出。这里已经证明的是匹配逻辑缺陷;是否在某台服务器触发、最终报错文字是什么,还需要实机重跑记录。
建议改成查询明确字段,例如检查 incus config device get vps1 ssh2201 type 的成功状态及结果,并对继承设备使用展开后的 API 视图。这个建议是下一版修复方向,本系列没有暗示原文件已被修改。
第二个问题:比较设备的脚本同样依赖缩进
菜单版想忽略根盘变化,只比较其余设备,使用了匹配两个前导空格的 awk 逻辑。顶层设备名不符合这个模式,反而可能让嵌套属性决定分组。结果可能把应忽略的根盘变更也计入比较,出现误报;更重要的是,这种比较不能证明“所有非根盘设备结构完全一致”。
下一版可保存 API 的 devices,用真实根盘设备名移除对应键,排序后比较 JSON;如果还要检查 profile 继承造成的实际差异,应另比较 expanded_devices。每个比较都应说明范围,不能用一个 PASS 同时代表本地配置、继承配置和业务可用性。
第三个问题:两份密码文件不是同一份事实
初装记录写入 /root/incus-vps-passwords.txt;重装新密码写入 /root/incus-reinstall-passwords.txt。部署脚本重试时只看到初装文件里有记录,就保留旧记录,不会核对容器当前密码。重装后再看初装清单,读者可能拿到过期凭据。
这不是应该通过“把密码都打印出来”解决的问题。下一版应维护一个当前凭据的受保护存储,另留不含明文密码的变更审计;重装成功后明确更新当前记录,历史条目标记失效。凭据写入与配置完成的先后顺序也需要处理,防止密码已经变更却没保存成功。
还有哪些结论不能被绿色输出掩盖
六份 Bash 脚本本次均通过 bash -n,端口管理器内嵌 Python 也通过语法解析。离线用例还确认了两种协议的设备数量和确认选项。这些结果证明“解析器接受”和“特定纯逻辑成立”,不证明 apt 成功、配额限制有效、远程 SSH 登录成功或重装可恢复。
部署脚本的根盘检查使用文本搜索,还把 3GiB 与 3GB 都视为通过;两者不是严格相等的容量单位。菜单版只在整个展开 YAML 搜索 size: 3GiB,也可能匹配到别的磁盘。应按根盘的结构化路径、实际字节值与目标单位验证。
怎样让一次修复成为一次可靠演进
每个问题记录触发条件、最小复现、修复差异、回归用例和仍未覆盖的边界。部署重跑、网络中断、已有同名设备、继承根盘、重装后凭据轮换,都应进入回归表。这样的系列会比“终极完美版”更可信:读者能看见为什么改,也能独立判断是否改对。