4 Commits
Author SHA1 Message Date
liuzhuohua b7ca9c89e8 feat(i18n/layout): 全站中英双语 + 四轮排版修复收官 + 修 boot_id 重启检测
v1.0.10 两件大事:

1. 界面中英双语(英文界面无中文残留)
   - 新增 web/i18n.js(约 4200 条词条),零依赖零构建
   - 后端下发中文数据走 bt4() 查表翻译;独立模块(upstream/netdetail/
     realtime/update)纳入翻译并注册切语言重绘钩子
   - 后端「状态/聚合」接口只回稳定枚举+原始数值,不下发中文文案
   - 顺带修一个真实 XSS:顶层 const 不挂 window,独立脚本 esc 静默退化成零转义

2. 修掉「文字竖着排/错位」(多分辨率反复暴露)
   - 单元格 min-content 只有 1 字宽 → word-break:keep-all + 表头 nowrap
   - 依赖卡片正文被标签/按钮挤成 27px → .dep-body{flex:1 1 200px} + flex-wrap
   - 裸 flex:1(下限为0)给正文 → 一律改 flex:1 1 <px>
   - .dep-item 横向容器直接塞块级子项 → 退回块级
   - 关键陷阱:auto-fill 布局屏越宽列数越多单卡越窄,窄屏反而测不出来,
     真机审计推进到「逐视图点按钮 + 1920/2560 宽视口 + 中英双语」
   - 守卫 t-layout.py 扩到 35 条,12 处反向验证全红

3. 修真缺陷:_link_since() 重启检测被误改成 if False:(注释还写着「已修」),
   uptime 归零后相减得负数 → 恢复比对 boot_id

附带:新增 25 个检查器(t-*.py)、排版审计工具链(shot-*/ocr-*/live-*)、
版本号兜底值同步到 1.0.10、.gitignore 收纳 i18n/排版排查的一次性脚本。
2026-10-10 04:50:32 +08:00
liuzhuohua ed5eb4428b 1.0.9 静态资源传输层优化 + 页脚版本号 + logo 回概述
## 性能:瓶颈在传输,不在代码

先量后改。helper 冷启动 0.44s(编译 22173 行)看着像瓶颈,但
drouter-helpd 是常驻守护,请求走 Unix socket,正常路径根本不 fork;
实测各 API 已是毫秒级。app.js 语法编译 18ms、顶层执行 18ms
(393 个顶层函数 / 13401 行)—— JS 执行不是瓶颈。

真瓶颈是 app.js 690,482 字节每次重传。**决定不拆包**:JS 执行只 18ms,
拆包引入首屏 waterfall 的风险大于收益。改走三条:

1. 缓存策略按「有没有指纹」区分(不是按扩展名)
   - 带 ?v=<mtime> → public, max-age=31536000, immutable
   - 没指纹 → 仍 no-cache(否则部署后用户拿到旧文件)
   - index.html → 永远 no-store(每次都要重新注入)
2. 编码协商 br > gzip > 原文,**逐级降级**
3. 压缩结果按 (path, mtime, size, enc) 缓存

真机实测:首次 690,482 → 200,137 字节,第二次打开 0 字节(304)。

## 逐级降级不是可选项

目标机**没装 python3-brotli**(只有 C 库 libbrotli1)。最初写的
_brotli_cached 在模块缺失时 `return data, None`,而 None 是 encoding,
调用方理解为「压不动,原样输出」—— 于是 583KB 裸传,**比优化前更慢**。
改成 (None, None) 明确表示「这条路不通」,由 _encode_body 继续试 gzip。

同时修:Accept-Encoding 探测必须认 q=0(br;q=0 是「明确不要」,
子串匹配会正好发给解不开的客户端 → 满屏乱码);编码必须进缓存键
(共用键会互相覆盖);brotli 固定 q5(q11 只省 10KB 却要 1.5-3s)。

## 宽 except 把 bug 装成了特性

兜底只接 ImportError 这类**真·预期**的缺失,不接 TypeError/ValueError
(「str 传给要 bytes 的接口」也是 TypeError,接了等于没接)。
早先的 `except Exception` 吞掉了一个 NameError(漏注入 _comp_lock),
表现是压缩功能完好但从不生效,页面照常能开、只看响应头的判据完全发现不了。

## 版本号:建立单一真源 packaging/VERSION

原来版本号在 4 个构建脚本各写死一个默认值 + openapi 里硬编码。
漏改一处就产出「文件名 1.0.9、control 里 1.0.8」的包 —— apt 按
control 认版本,dpkg -l 永远显示旧版且升不上去。

index.html 的 ETag 并入版本号:它发出去的是注入后的内容,
ETag 却按原文件算,只改 VERSION 文件时 ETag 不变,客户端换到 304
继续用旧副本,页面版本号永远停在旧版。

## logo 回概述

用 <a> 而不是 <div onclick>:Tab 能聚焦、Enter 能触发、读屏会念「链接」。
已在 dash 页时不重复 go()(go 会 stopPageTimers + 重拉数据)。
CSS 显式去 text-decoration/color(UA 样式表不声明仍生效),
:focus-visible 焦点环保留(纯键盘用户唯一的路标)。

## 顺带修掉一个已发布版本的真 bug

_vpn_env 里 os.path.join('/lib/modules', ...) 被留成了 '/nonexistent'
(反向验证脚本被 SIGTERM 打断,finally 没执行,注入留在源码里),
于是 mod_file 永远为空、**need_module 整个状态从四态里消失** ——
这正是 v1.0.8 release notes 声称修好的那个 bug(PVE 的 KVM 虚机内核
明明有 wireguard.ko.xz 却报 unsupported,前端配一句写死的
「通常说明跑在精简容器里」把排查方向带偏)。

新增 _dev/t-worktree-clean.py 扫注入哨兵,放在 sync.sh 所有判据之前 ——
基线被污染时,后面每一条红都不可信。

## 判据

新增 t-static-perf.py(42 项静态)、t-encoding-runtime.py(26 项**真跑**,
覆盖 9 种 Accept-Encoding 组合、brotli 缺失降级、两种编码不互相覆盖、
压不动直出、内部错误不被吞)、t-worktree-clean.py。
三个都做了反向验证,注入后确实变红。

反向验证本身也踩了坑:判据里 len(body) 在 body 为 None 时先崩 ——
退出码非 0 但没有 FAIL 行,看着像「跑了」,实际没人知道它验出了什么。
取长度一律过 _sz() 包一层。

全量回归 54 个判据 0 失败;真机验收 22 项全通过;
前端三查 47 视图 0 异常、26 项移动端适配全过。
2026-10-04 10:27:02 +08:00
liuzhuohua 147a402eca fix(webshell): 修复 PTY 故障与终端满员自锁,补容器形态守护拉起
Web 终端「任何账号都连不上」的两个真实故障,以及容器形态下终端完全打不开。

- shelld: 根因是 /dev/ptmx 符号链接丢失(/dev/pts/ptmx 还在),
  pty.openpty() 找不到入口,内核却报 "out of pty devices" ——
  把「少一个设备节点」说成了「设备耗尽」,排查方向被带偏。
  新增 ensure_ptmx():启动时与创建失败时都探测,先补 symlink,
  不行再 mknod 兜底;仍失败则回 PTY_FAIL 并给出可照抄的命令

- shelld: 会话锁由 threading.Lock 改为 RLock。原锁不可重入,而
  connect 在持锁状态下调用 _reap_idle() 会二次取同一把锁 ——
  同线程二次 acquire 不报错不超时、直接睡死,锁永不释放,
  第 9 个连接起整个终端服务冻死且日志无痕。
  connect 不再持锁调回收;_reap_idle() 的 close() 移到锁外

- helper: 容器里没有 systemd,拉起终端守护原来只有 systemctl 一条路径,
  导致容器形态 Web 终端永远「终端守护未运行」。新增 _shelld_spawn() 回退:
  直接以子进程启动守护(start_new_session 脱离父进程);
  错误文案改为「容器形态无 systemd,重启容器即可」

- 前端: 终端去掉滚动条(仅隐藏外观,滚动能力保留)、
  高度改为随视口 clamp 自适应;新增「清屏」与「↓ 最新」按钮

- 打包: build-deb.sh 与 Dockerfile 不再整体拷 docs/ ——
  install/COPY 撞上 docs/screenshots/ 目录会报错(install 遇目录返回非 0,
  在 set -e 下直接中断整个打包);截图本就不该进 deb/镜像

- 修正两处不一致: index.html 页脚误写「GPL v3」→ MIT(与 LICENSE 对齐);
  README 中 deb 体积 421 KB → 427 KB(实测 437584 字节)

- 版本号 1.0.0 → 1.0.1(源码、打包脚本、文档、镜像标签、编排文件)

测试: _dev/t-pty.py 扩到 45 项,含「_lock 必须是 RLock」的行首断言
(原先用子串匹配被 self.buf_lock 误伤);t-webshell.py 补 16 项外观契约。
真机 192.168.7.3 实测:第 9 个连接 0.01s 干净返回 BUSY,
断开 8 会话 0.87s 全清、残留 0,PTY/fd 回落基线;
自愈验证(删掉 /dev/ptmx 重启服务可自动补回)。
容器冒烟 12/0:无 systemd 环境下守护成功拉起、
root shell 建会话并跑通命令回显。41 个单测文件全绿。
2026-10-01 03:09:19 +08:00
liuzhuohua 64df3cf331 feat: Drouter 1.0.0 —— Debian 13 软路由管理系统
一套跑在 Debian 13 (trixie) 上的软路由管理系统,提供 43 个功能页面的
Web 管理面板:WAN 接入、PPPoE 多拨、DHCP/DNS、IPv6、防火墙、QoS 限速、
访问控制、DDNS、文件共享、Docker 管理等。

核心设计原则:不重新实现任何协议
  PPPoE 交给 ppp,DHCP 交给 dnsmasq,IPv6 RA 交给 radvd,NAT 交给 nftables。
  Web 面板只做三件事:渲染原生配置文件 → 语法预检 → 原子写入并 reload。
  面板删掉后,系统仍是一台配置完好的 Debian 路由器。

安全默认值
  · 安装后不接管网络:不改默认路由、不开 IP 转发、不启动 DHCP 服务
  · 所有改网络的动作都需要用户在面板上亲手点「应用」
  · 避开 5900 端口,不触碰 VNC / X 会话
  · 配置写入前先做语法预检,失败则回滚

架构
  · backend/  Python 3 标准库,零第三方依赖(9 个模块)
  · web/      原生 JS/CSS,无构建步骤
  · 常驻 helper 通过 Unix socket 提供 75 个动作,socket 不可用时回退 subprocess
  · 鉴权走 X-Token 请求头

打包
  · packaging/build-deb.sh            → drouter_1.0.0_all.deb(含 6 个 systemd 单元)
  · scripts/make-offline-deps.sh      → 236 个依赖 .deb + 本地仓库索引
  · packaging/build-offline-bundle.sh → 自包含离线包,目标机零外网可装
  · packaging/build-docker.sh         → 容器镜像(体验用)

测试
  40 套单元测试 + 3 个前端检查脚本(菜单映射 / 离线渲染 / 手机适配),
  全部挂在 devtools/sync.sh 的预检链里。

许可:MIT
2026-09-30 19:26:24 +08:00