4 Commits
Author SHA1 Message Date
liuzhuohua d0a72bbb5f fix(release): Release 附件版本号单一真源 + 堵住「注释掉整行代码」型注入残留
发 v1.0.9 前发现两个真问题,都会直接影响用户拿到的附件。

一、docker-compose.yml 里的镜像标签是硬编码的 1.0.8

这两个文件是**直接挂到 GitHub Release 上的附件**,用户照着抄命令。
早先它们硬编码具体版本号,每次发版要人工 sed 一遍 —— 这次就漏了:
附件里写着 `image: drouter:1.0.8`,而镜像 tar 加载后的标签是 `drouter:1.0.9`,
`docker compose up` 直接报「找不到镜像」。DOCKER-GUIDE.md 另有 14 处同样问题。

改成 __VERSION__ 模板,由 build-docker.sh 渲染(版本从 packaging/VERSION 读)。
渲染分两步,顺序不能反:先删 TEMPLATE-NOTE 标记块(那是给维护者看的
「这是模板」提示,留着用户会看到「1.0.9 是占位符」这种自相矛盾的话),
再替换占位符。先替换的话,标记块里的 __VERSION__ 也会被换掉。

retag-docker-image.py 同样问题:NEW_TAGS 硬编码。改完参数化。
这个 bug 更隐蔽 —— 拿 1.0.9 的镜像跑重标记会打上 1.0.8 的标签,
用户 load 完看到的标签与附件说明对不上。

t-packaging.py 补 12 条判据卡住这些(4 条已做反向验证:注入即红、还原即绿)。
判据只抓「紧挨 drouter 标识或产物文件名」的版本号 —— 一刀切拦所有 x.y 会
误伤「1.0.1 起 Web 终端绕开 init」这种历史沿革说明和「GPL-3.0」许可证名。

二、t-worktree-clean.py 抓不到「注释掉整行代码」型注入

跑回归时 t-107 红了 1 项,查出来是 t-107-inject-wg.py 把
`'env': _vpn_env(),` 注释掉了没还原(SIGTERM 打断,finally 没执行)——
和 v1.0.8 那次 `/nonexistent` 残留一模一样的坑,但**形态不同**:
不含任何哨兵字样,原判据的 4 个哨兵全都抓不到。

后果很实:_vpn_status 不返回 env,前端拿不到四态诊断,
VPN 页面的三个修复按钮全废。这正是 v1.0.8 声称修好的那个 bug。

补两条判据:
- 扫「被注释掉的整行代码」:去掉 # 后能 ast.parse 成功,且含调用/赋值/return/dict 项
- 扫 CRLF:Windows 工具写文件会整篇改行尾,sync.sh 预检才拦太晚

判据本身踩了三个坑,都记在注释里:
- dict 项 `'env': _vpn_env(),` 单独 ast.parse 会 SyntaxError(没大括号),
  真正的注入反而被放过 → 补大括号再解析
- dict 项的键**通常带引号**,正则只写 ^[A-Za-z_] 认不出来 → 改成同时认引号键
- 分隔线注释 `# ------ radvd` 里的 ----- 会被解析成负号表达式 → 按前缀排掉

双向验证:4 种注入形态(赋值/dict项/bool/列表)全部精确报到行号,
基线零误报(排掉了分隔线注释和「# module: (service, action, 中文名)」这类
长得像 dict 项的文档注释)。

三、_dev/dl-verify.py(新增)

从目标机往 Windows 拉大产物时发现的坑,记成脚本省得下次重踩:
- `ssh host 'cat f' > out` 在 Windows 上做**文本模式换行转换**,二进制被破坏
  (实测 deb 少 4 字节,sha256 直接对不上)→ 走 base64 再本地解码
- Windows Python 看不到 Git Bash 的 /tmp,必须给 C:/ 绝对路径
- scp 传完一定要判 sha256 —— scp/ssh 都可能「返回 0 却落地 0 字节」
- 落地走 .part + os.replace,避免写一半失败留下半个产物

顺带修了 build-docker.sh 里一处路径笔误
(packaging/docker/packaging/docker/DOCKER-GUIDE.md)和步骤编号 1/3→1/4。
2026-10-04 14:05:33 +08:00
liuzhuohua aab35d48a7 feat(ddns): 真实下发 + 「检测间隔」终于有消费方
原状:DDNS 页面「立即更新」只打印一份更新计划,DNS 记录纹丝不动;
「检测间隔(秒)」字段全仓没有任何定时器消费,纯装饰。

本次做两件事:

1) 真实下发
   - 新增 _http_open:区分「HTTP 层失败」与「业务失败」。
     这类接口的共同点是**用 HTTP 200 回一句 error 表达失败**,
     只判 HTTP 层的话页面显示「成功」而记录没动。
   - 接入 7 家:custom / dnspod / aliyun / cloudflare / noip /
     dyndns / dyndns2。各自的成功判据不同(DNSPod 看 status.code,
     阿里云看 Code,Cloudflare 看 success)。
   - huaweicloud / dnspod_tencent **如实报「暂未接入」**并给替代建议,
     不写「看起来对」的实现 —— 未验证过的签名算法不能假装能用。
   - update 分支三态:全成功 / 全失败(DDNS_UPDATE_FAILED) /
     部分成功(DDNS_UPDATE_PARTIAL)。部分成功不显示成全成功。
   - 下发器内部异常被兜住并如实上报,不把动作打断成 500。

2) 检测间隔有消费方
   - 新增 backend/drouter-ddnsd.py:读 interval → 未启用直接跳过 →
     失败退避 → 探测公网地址 → **只在地址变化时才下发**
     (IP 没变不敲 API,避免触发服务商频率限制)。
   - 失败退避 3 倍递增、上限 1 小时。退避期内秒退,不探测外网
     (一次完整探测 5 秒以上,失败时反复探测是自伤)。
   - _write_ddns_timer 由 save / on / off 三处调用,改了间隔立刻
     重写 timer,不会出现「页面显示已保存、实际还是旧周期」。
   - 守护不依赖 init:容器形态下由常驻守护代跑。

真机验收(192.168.7.3,Debian 13):
  - timer generated/enabled/active,OnUnitActiveSec=120s,跟随用户设定
  - 地址未变 → last_result='unchanged',不下发
  - 篡改 last_ip4 制造「地址变了」→ 真下发 → 失败如实记录并退避
  - 退避内第二次执行 0.136s 返回(首次 4.3s),确认没探测外网
  - HTTP 层失败与业务失败文案可区分
  - 验收后已恢复出厂默认(停用),timer 单元与退避文件已清理

顺带修掉 3 个真缺陷:
  - 阿里云更新参数对新建 dict 做 del up['Action'] → KeyError。
    UpdateDomainRecord 少了 Action/Format/Version 会被阿里云拒。
  - 自定义 URL 失败词表漏 badauth:返回 badauth 被判成功。
  - DNSPod 错误文本在 status.message 而非顶层 message,
    只读顶层时用户只看到「失败」没有原因。

判据:
  - 新增 _dev/t-111-ddns-push.py(44 项,12 组),全绿。
    真实验签不靠网络:_http_open 换成假实现按剧本返回不同正文,
    并在启动时掐死 socket.connect —— 判据测试打真实网络既慢又不确定,
    还会污染对方日志,且失败时看起来像「产品有 bug」。
  - t-packaging 补 2 条:deploy.sh 里每个后端模块都要出现。
    本次就踩到 —— build-deb 用 backend/*.py 通配复制(ddnsd 自动进包、
    判据全绿),可 deploy.sh 是**逐个 install 硬名单**,ddnsd 漏在里面,
    表现为「本地全绿、真机没有文件」,timer 起来报 203/EXEC。
    部署才是真正生效的那一步,两份清单都得查。已反向验证会红。

全量静态回归 43 个检查器 0 失败。
2026-10-03 21:45:43 +08:00
liuzhuohua 249cc3d8c6 feat(backup/alert/quota/vpn): 新增四个模块 + 深度自查修7 个真缺陷
功能
- 配置备份与还原(backupd):分级导出/预览/还原,敏感内容默认排除
- 告警中心(alertd):WAN掉线/延迟/丢包探测,冷却期+ 免打扰
- 流量配额(quotad):按 IP/服务/小时桶聚合,费用分摊
- VPN 页前端接线 + 前端字段契约补齐

自查发现并修复的缺陷(_dev/t-107.py,119 项)
1. 默认备份静默丢失大半配置(严重)
   _bk_files 把整个 /etc/drouter 标为 sensitive,默认导出时被整目录排除,
   而 BACKUP_SCOPE 恰恰宣称备份含 dcfg.json / kern.conf / vlans.json /
   Docker 栈文件 / 自定义主题。界面全程显示「已导出」,用户换机还原才发现。
   改为逐文件判定(_bk_is_sensitive)+ 敏感子树剪枝 + excluded 收集与展示。
2. VPN status 把全部客户端私钥明文送到浏览器
   _vpn_status 返回 'raw': d(完整配置),前端 4 个渲染函数一个都没读过它。
3. Python 3.13 下保存 VPN 配置静默清空所有 peer(隐蔽)
   'str' in ip_network() 在 3.12 静默 False,3.13 抛 AttributeError,
   被紧邻的 except 吞掉 → 所有客户端无声消失。Debian 13 默认就是 3.13。
4. VPN 私钥在 _vpn_mask 里被连同其他字段一起抹掉
5. 14 处前端属性插值未转义(备份/告警/VPN/配额页)
   type="number" 只过滤输入,不阻止 HTML 属性注入。
6. wireguard 未登记到 DEPS(VPN 有 5 处调 wg,自检页看不到缺失提示)
7. build-deb.sh 声明「后端 N 个」与实际清单不符 + ULOG_ARCHIVE 重复定义

测试基建
- _dev/t-107.py:字段契约双向对照 / op 三段对齐 / 高危路径端到端
  (真跑 _bk_walk、_vpn_norm、配额聚合,不用正则假装测过)
- _dev/run-static.py + scripts/test-static.sh:统一静态回归入口
  排除列表曾写成裸名导致交互脚本没被排除、长会话被 SIGTERM
- t-packaging 改为从 build-deb.sh 抽取模块清单,并反向校验无游离模块
2026-10-03 00:49:07 +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