Commit Graph
7 Commits
Author SHA1 Message Date
liuzhuohua cd5ef7411f fix: 清「假输入框 / 隐身字段」这批前后端缺陷,版本号 1.0.4
把全量代码(后端 9 模块 + 前端单文件 SPA,约 4.5 万行)过了一遍,用 AST 抽出
渲染器读取的每个配置键,与页面上输入框绑定的字段做双向差集,专门找两类问题:
前端发了后端从不读(假输入框)、后端读了前端没有入口(隐身字段)。

会写坏系统的:
- render_network 违反 render() 契约返回字典,调用方按二元组解包后把字典的键
  当成路径和内容 —— 迁移 networkd 会生成文件名叫 name、内容写着 content 的垃圾文件
- DMZ 规则方向写反:注释写「排除到本机自身」,条件写成反面,管理口流量也一并 DNAT

五个「填了等于没填」的输入框:
- WAN 的 MTU 与「是否接受上游 DNS」后端零消费,现真正写入 MTUBytes= / UseDNS=
- DHCPv6 的「子网 ID」绑的是不存在的键,真生效字段是 sla_id
- Samba WINS 要求一个从不回填的字段同时为真,勾了永不生效
- 网桥「启用 STP」未绑定事件,勾完保存不进去
- 主题设计器「按主色生成配色」按钮无 handler

其余:
- RA 页补齐 rdnss_life / dnssl_life 输入框(1.0.3 修了错位却没给入口)
- 迁移预览只发 lan,必报「WAN 网卡不能为空」且网桥从不出现 → 补发 wan + bridge
- dnsmasq / miniupnpd / ppp / dhcpcd IPv6 / RA 地址池 / NFS 等插值零校验
- chrony 服务器校验只认域名和 IPv4,IPv6 NTP 服务器被误拒
- v_int 改用 try/except:上标数字能通过 isdigit() 却让 int() 抛异常导致 500
- ensure_cert 删掉硬编码的开发机 IP,改为本机地址 + 默认路由推导;补 openssl 失败检测
- DDNS 面板 XSS(记录名/服务商未转义);主题 CSS 注释注入(前后端各一处)

测试:render 返回值契约断言、CSS 注释注入回归、模板孤儿控件守卫,
_dev/t-*.py 42/42、前端三查 + md 链接检查全绿。
2026-10-02 00:23:30 +08:00
liuzhuohua 79f24aa264 fix: 部署静默回退用户设置 + radvd RDNSS 未校验等 5 处缺陷
1.0.3 为纯修复版本,五个问题全部来自真机(192.168.7.3)深度排查:
翻 journal 与统一日志的 error 记录、全盘扫描文件名控制字符、把现行配置库
里每个模块的渲染结果重新交给对应真守护进程做语法校验。

- render(radvd): RDNSS 字段原先完全不校验(同段代码里 prefix 校验网段、
  DNSSL 校验域名,唯独漏了它),填 IPv4 地址会原样写进 radvd.conf 导致
  radvd 起不来,界面只回一句 radvd.conf:18 error: syntax error。
  现逐项按 IPv6 地址校验并给中文提示;多值支持逗号/空格并统一按空格输出
  (逗号形式 radvd 本就不认);DNSSL 同样支持多域名;修正 DNSSL 生命期
  误读 rdnss_life 的字段错位。

- cleanup: 自动清理的「每天 00:00」根本存不下来——前端 `Number(v) || 4`
  与后端 `int(cfg.get('hour') or 4)` 都把 0 当成「没填」,叠加后保存即变
  04:00 且改不回来。两处改为显式判断 None/''。执行时刻判定由「正好等于」
  放宽为「不早于」,避免计划时刻恰好关机/重启导致当天彻底跳过。

- deploy.sh: 三处无条件覆写改为「只在文件不存在时初始化」——
  active-theme(自定义主题每次部署被打回默认)、generated/isp-dns.conf
  (PPPoE 上游 DNS 被擦、解析变空直到下次拨号)、cleanup enabled=true
  (唯一会真删文件的模块被静默重新打开)。另补兜底:生效主题指向已不
  存在的主题时回退 default 并打印说明。

- drouter-web: socketserver 默认 handle_error 把任何异常连整段 traceback
  打进 journal,浏览器切页/关标签抛的 BrokenPipeError / ConnectionReset
  属「对面先挂了」,三日攒数十条足以淹没真报错。新增 _Server 子类只静默
  这几类,其余照旧抛出。

- devtools: 新增 fix-crlf-names.py 清理 CRLF 环境残留的名字带 \r 脏条目
  (保守:默认只预演、仅删有同名兄弟者、目录需相对文件集合为子集、
  跳过 snapshots/);sync.sh 发布预检增加「文件名不得含控制字符」。
2026-10-01 17:43:28 +08:00
liuzhuohua a49b4d7771 fix(dnsmasq): 合并重复的 option 3/6,并诊断 /dev 节点缺失
DHCP 页有「专属字段」和「自定义 Options 表」两处能写同一个 option code。
两处都填 3/6 时会各自渲染一遍,而 dnsmasq 对列表型 option 是追加语义,
于是客户端收到重复的网关,DNS 列表也被搅乱(公网 DNS 排在本机前面,
本机 dnsmasq 形同虚设)。

改为按 code 合并成一行并顺序去重;带「强制」的 3/6 语义不同,仍单独输出
为 dhcp-option-force;其它 code(43/121…)完全不受影响。新建配置也不再
预置与专属字段重复的 code 3/6 行。

顺带:
- code 6 的占位符原本写上游 DNS,容易误导 —— option 6 是下发给客户端的
  「去哪台机器查 DNS」,应填本机。占位符与说明已改正。
- dnsmasq 预检失败时不再直抛英文原文。像
  「failed to seed the random number generator」其实与配置无关,是宿主机
  /dev 设备节点缺失,现在会分诊成系统层面的中文提示。

版本号 1.0.1 → 1.0.2。注意区分「产物版本」与「功能引入版本」的表述:
容器里无 systemd 时回退子进程拉起守护,是 1.0.1 引入的,文案保持 1.0.1。

新增 _dev/t-render.py 对 3/6 合并的回归断言(含 force、留空、顺序、
其它 code 不受影响、预览路径一致性)。全量回归 42/42 通过。
2026-10-01 16:49:49 +08:00
liuzhuohua 7d646b5f2b fix(helper): dnsmasq 预检失败时诊断出「/dev 节点缺失」
用户在 DHCP 页点「保存并应用」,弹出:
  配置语法检查未通过:dnsmasq: failed to seed the random number
  generator: 没有那个文件或目录

根因不是配置写错,而是目标机的 /dev 标准设备节点被误删过。

/dev 是 devtmpfs,由内核维护,但**已删除的节点内核不会重建**。
2026-09-30 一次 chroot 的 `mount --bind /dev` 变量写错,把宿主机 /dev
挂到了自己身上,随后的清理清空了整个 /dev —— 当时只补回了 /dev/null
一个节点。urandom / random / zero / full / tty / console / kmsg 以及
全部块设备(sda、sda1、sda2、zram0)和 /dev/net/tun 一直缺到今天,
直到 dnsmasq 预检要读随机数才暴露。

内核那句话里既没有 "dev" 也没有「设备」字样,用户只会以为是自己把
地址池填错了。新增 _missing_dev_nodes() 做分诊:命中该错误时返回中文
提示,指明这是系统层面的 /dev 问题、不是配置问题。

- backend/drouter-helper.py:新增 _missing_dev_nodes();_verify 的
  dnsmasq 分支做错误分诊(其它语法错误仍走原路径)
- devtools/fix-dev-nodes.sh:新增。幂等地补齐被删的标准设备节点,
  设备号分别取自内核 devices.txt(字符设备)、/proc/partitions
  (块设备)、/sys/class/input(输入设备);已存在的一律不动
- _dev/t-verify.py:新增 15 项回归,覆盖「节点齐全不误报」「缺谁报谁」
  「随机数错误被翻译」「其它错误不受影响」「合法配置通过」五条路径
- devtools/sync.sh:预检链挂上 t-verify.py

目标机 192.168.7.3 实测:
- fix-dev-nodes.sh 新建 32 个节点、0 失败;二次执行 32 跳过(幂等)
- dnsmasq --test 从「failed to seed the random number generator」
  变为「syntax check OK.」(退出码 0)
- 网络基线零变化:网关仍 192.168.7.2、ip_forward=1、
  5900 仍在听、网卡集合不变、dnsmasq 仍 inactive/disabled、
  nft 表 0 个
2026-10-01 16:01:45 +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 2859705cee fix(webshell): 修复终端满员自锁 + 去掉滚动条并加清屏按钮
一、修 PTY 不可用(Web 终端报「创建终端失败:out of pty devices」)
   根因是目标机上 /dev/ptmx 这个符号链接丢了(devpts 里 /dev/pts/ptmx
   还在,但入口没了),于是 pty.openpty() 抛出误导性的「out of pty devices」。
   - 新增 ensure_ptmx():探测 -> ln -sf pts/ptmx /dev/ptmx -> mknod 兜底
   - 新增 _open_pty():首次失败先自愈再重试一次,仍失败就把真实原因
     (_pty_diagnose() 的结论)包进异常,不再只留一句天书
   - 服务启动时也自愈一次,开机即修,不等用户踩坑
   - 前端识别 PTY_FAIL 并给出可照抄的修复命令

二、修会话满员时的死锁(连到第 9 个会话整个终端服务冻死)
   connect 需要「看是否满员 -> 满了就回收 -> 再看一次」,这个序列必然
   在持锁时调到内部也要拿锁的 _reap_idle()。而 _lock 是普通 Lock,
   同线程二次 acquire 永久阻塞 —— 不报错、不超时,直接锁死;而且锁
   一直不放,其他会话的 read/write 也一起堵住,整个 Web 终端报废。
   - _lock 改为 RLock
   - connect 不再把 _reap_idle() 放在 with _lock 里面
   - _reap_idle() 锁内只做登记摘除,close() 移到锁外:
     close() 要等子进程退出(SIGHUP 后最多轮询 3 秒),持锁做同样会
     把并发请求堵死

三、终端外观(按需求)
   - 去掉右侧滚动条(scrollbar-width:none + ::-webkit-scrollbar),
     但保留滚动能力 —— 直接关掉 overflow 会让人再也翻不了历史
   - 高度改为 clamp(260px,54vh,560px) 随视口自适应;内容多了自动跟随
     最新输出。刻意不做「高度随内容增长」:wsMeasure() 用 box.h 算行数,
     内容撑高容器会反过来改变 PTY 行数,形成反馈环
   - 标题栏加「清屏」小按钮;滚上去离开底部时另出现「↓ 最新」——
     滚动条没了,总得留个回到底部的入口
   - 跟随阈值收敛成唯一的 WS_TAIL_GAP:wsAutoScroll 与 wsSyncTailBtn
     各写一份会出现「按钮没出现、输出也不跟着滚了」的死角

四、顺带清理
   - 删掉 app.css 里整套失效的旧终端样式(.tdot/.tt/.term-in/.tk-*,
     app.js 中 0 引用),它们已被新结构完全覆盖,留着只会让人改错地方
   - 合并 .term-wrap / .term-body 各自重复的第二条声明

测试:
   _dev/t-pty.py 扩到 37 项,含「不得自锁」的结构检查 + _reap_idle
   行为验证(摘除/关闭/返回条数);_dev/t-webshell.py 加 16 项外观契约。
   两者都挂进 devtools/sync.sh 预检链。
   新增 devtools/preview-webshell.py:用真实 app.css + 从 viewWebShell()
   抠出的 DOM 生成离线预览页,不手抄样式以免预览与线上不一致。

真机验收(192.168.7.3):
   8 个会话满员后第 9 个 0.01s 干净返回 BUSY(修复前是永久卡死);
   8 个会话 0.87s 全部断开、残留 0(修复前残留 8 个且后续全卡);
   /dev/pts 与 pty/nr 回落到 0,无 fd 泄漏;root shell 正常执行命令。
2026-10-01 01:29:09 +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