版本号 1.0.7 → 1.0.8(19 个文件 55 处,逐条过;历史陈述不动)。
顺带补上1.0.7 漏掉的更新日志小节(那一版只改了版本号)。
## 本轮修的缺陷
**先说结论:Debian 13 虚拟机上跑 drouter,KVM 不是容器,
内核里一直有 WireGuard —— 是面板自己判错了。**
原 _vpn_installed() 只看两件事:/sys/module/wireguard 在不在(模块当前
已加载)、/usr/bin/wg 在不在(工具已安装)。**从没查过 /lib/modules 下
有没有模块文件** → 「模块文件在但没加载」被压进「内核真没这个模块」。
而 Debian 走 udev 按需加载,/etc/modules 与 /etc/modules-load.d 默认都
不写 wireguard,模块在磁盘上但没进内存。本机实测:内核 6.12.107+deb13-amd64,
wireguard.ko.xz 在,未加载;wireguard-tools 也没装。
前端还配了一句写死的文案把它坐实:「通常说明跑在精简容器里」——
而这台是 PVE 上的 KVM 虚拟机。排查方向被这句话整个带偏。
修法:
- 判据扩到四态(ready / need_module / need_tool / unsupported),
每态给可执行的下一步。unsupported 明确说装不了且不给一键修复。
- 模块文件名覆盖 .ko / .ko.xz / .ko.zst(Debian 默认压成 .ko.xz,
只认 .ko 会把正常机器也判成 unsupported)
- 新增「一键修复」:modprobe → 写 /etc/modules-load.d/ 开机自启 →
缺工具则 apt-get install,确认弹窗写清三步并告知不动网络
- apply 改为先自愈再报错,不再直接弹「内核不支持」把人挡在门外
- 老后端兜底 unknown 而非把旧的 no 猜成 unsupported(猜错=复活本 bug)
- 页面新增「运行环境」行;改掉「不需要装任何第三方软件」这句误导
## 测试基建:这轮抓出 3 条恒绿断言 + 1 处死分支
反向验证(把 bug 注入回去看它变红)逮到三条永远不会红的断言:
把探测路径改成 /nonexistent 仍全绿(docstring 里就写着那句话);
把 'env': _vpn_env() 注释掉仍全绿(我写的是 #注释,正则照样匹配);
把 if (!confirm( 改成 if (false && confirm( 仍全绿(提示文案里有那句话)。
根因:习惯在注释里引用被删掉的旧代码来解释 bug 来历,这些字面量让
「这段代码还在吗」类的判据永远成立。修法:t-107 统一走 py_code_only() /
js_code_only()(先按ast 判语义剥 docstring,再剥行注释),16 个注入全部被抓住。
死分支:用小锚点 Edit 只替换 if (d.installed === 'no') { 这一行时,
旧 body 与它的 } else if (!d.has_systemd) { 原样留在原地,拼出一个
「没有 systemd → 报内核不支持」的假分支——语法合法、静态检查也过。
防线:断言 } else if ( 的数量恰好等于分支数。
另有 4 处判据写错导致假红,共同形态是「脚本假设了一个修好之后就不成立
的前提」——比如「模块必须未加载」这条,fix 一跑完就假红。
## 验证
- t-107:126 → 167 项
- 反向验证 16/16 被抓住
- 新增 devtools/check-vpn-render.js 36 项(挂进 sync.sh 预检链)
- 新增 _dev/live-108-wg.py 真机验收 43 项;真机 _vpn_env 从 need_module
修到 ready,wg 装上(v1.0.20210914),modules-load.d 已写入
- t-*.py 51 个全绿 / 静态检查器 39 个全绿 / 前端四查全绿 / md 0 死链
- 容器冒烟新增第 14 节:镜像里真跑 _vpn_env() 验四态判定
(不 grep 字面量——注释里也会写这些词)
- deb 内 helper 与 app.js sha256 与本地逐字节一致
56 lines
2.2 KiB
Python
56 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""容器冒烟探针:在镜像里 import drouter-helper 并跑 _vpn_env(),把四态打出来。
|
|
|
|
为什么需要单独一个文件(而不是在冒烟脚本里内联 heredoc)
|
|
--------------------------------------------------------
|
|
冒烟脚本经 `$PODMAN exec` 跑,heredoc 要穿过两层引号 + 一层命令替换,
|
|
bash 很容易把它吃掉或者吞掉后面的行(写的时候就踩了:留了个残缺的
|
|
`$echo "$($PODMAN exec ... <<'PYEOF'` 出来)。拆成文件后 `podman cp`
|
|
进去直接跑,失败也只是这一个文件的问题。
|
|
|
|
为什么不能只 grep 源码里的 'unsupported' 字面量
|
|
--------------------------------------------
|
|
注释和 docstring 里也会写这些词 —— 1.0.8 那轮判恒绿就是这么来的:
|
|
「查的是 /lib/modules 下的模块文件」这句话在 docstring 里,
|
|
把探测路径改成 /nonexistent 断言照样全绿。真跑一次函数就没这个问题。
|
|
|
|
输出是 KEY=VALUE,逐行print,冒烟脚本按行取。
|
|
"""
|
|
import importlib.util
|
|
import sys
|
|
|
|
HELPER = '/opt/drouter/backend/drouter-helper.py'
|
|
|
|
|
|
def main():
|
|
try:
|
|
spec = importlib.util.spec_from_file_location('drouter_helper', HELPER)
|
|
if spec is None or spec.loader is None:
|
|
print('IMPORTERR 无法为 %s 建立 spec' % HELPER)
|
|
return 2
|
|
m = importlib.util.module_from_spec(spec)
|
|
# exec_module 会真的执行模块顶层代码。helper 的顶层只做常量定义
|
|
# 与 import,不连网、不起服务(守护都是被显式调用才起来的)。
|
|
spec.loader.exec_module(m)
|
|
except Exception as ex:
|
|
print('IMPORTERR %s: %s' % (type(ex).__name__, ex))
|
|
return 2
|
|
|
|
try:
|
|
env = m._vpn_env()
|
|
except Exception as ex:
|
|
print('CALLERR %s: %s' % (type(ex).__name__, ex))
|
|
return 3
|
|
|
|
for k in ('state', 'virt', 'fixable', 'autoload', 'kernel',
|
|
'mod_loaded', 'tool'):
|
|
print('%s=%s' % (k.upper(), env.get(k)))
|
|
# mod_file 单独打:容器里查不到时是空串,正好验「查不到也不抛异常」
|
|
print('MODFILE=%s' % (env.get('mod_file') or '(空)'))
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|