Files
drouter/_dev/t-render.py
T
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

55 lines
2.7 KiB
Python

# -*- coding: utf-8 -*-
"""read_render_text 逻辑测试(本地模拟,helper 因 pwd 无法在 Windows 导入)。"""
import sys, re, io
sys.path.insert(0, r"C:/Users/lyrz-pve-win10/WorkBuddy/2026-09-27-19-57-00/router-build/backend")
import render
src = io.open(r"C:/Users/lyrz-pve-win10/WorkBuddy/2026-09-27-19-57-00/router-build/backend/drouter-helper.py",
encoding='utf-8').read()
m = re.search(r'^def read_render_text\(.*?(?=\n\ndef act_apply)', src, re.S | re.M)
assert m, 'read_render_text 未找到'
NS = {'render': render, 'ValidateError': render.ValidateError, 'str': str}
exec('def ok(data=None, msg="ok", code="OK"):\n return {"ok": True, "code": code, "msg_cn": msg, "data": data}\n', NS)
exec('def fail(msg, code="ERR", data=None):\n return {"ok": False, "code": code, "msg_cn": msg, "data": data}\n', NS)
exec(m.group(0), NS)
fn = NS['read_render_text']
fails = 0
def chk(label, cond, extra=''):
global fails
if not cond: fails += 1
print('[%s] %s %s' % ('OK' if cond else 'FAIL', label, extra))
cfg = {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
'portfwd': {'enable': True, 'rules': [{'name': 'NAS', 'proto': 'tcp', 'ext_port': 5000,
'int_ip': '192.168.7.10', 'int_port': 5000, 'enable': True}],
'dmz': {'enable': True, 'host': '192.168.7.50'}}}
r = fn({'module': 'nft_v4', 'cfg': cfg})
chk('nft_v4 预览成功', r['ok'])
lines = [x for x in r['data']['text'].split('\n') if 'dnat' in x or '放通转发' in x]
for x in lines: print(' ', x.strip())
chk('含 dnat 规则', any('dnat to 192.168.7.10' in x for x in lines))
chk('含 DMZ 规则', any('DMZ' in x for x in lines))
chk('含放通规则', any('放通转发' in x for x in lines))
r2 = fn({'module': 'nft_v4', 'cfg': {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
'portfwd': {'enable': True, 'rules': [{'name': 'x', 'proto': 'tcp', 'ext_port': 80,
'int_ip': '1.2.3.999', 'int_port': 80}]}}})
chk('非法 IPv4 返回中文错误', (not r2['ok']) and 'IPv4' in r2['msg_cn'], '→ ' + r2['msg_cn'])
r3 = fn({'module': 'dnsmasq', 'cfg': {'lan_iface': 'ens18', 'domain': 'lan'}})
chk('dnsmasq 预览成功', r3['ok'] and len(r3['data']['text']) > 50,
'长度 %s' % (len(r3['data']['text']) if r3['ok'] else '-'))
r4 = fn({'module': 'radvd', 'cfg': {'iface': 'ens18', 'prefix': '2408:8207:1234::/64'}})
chk('radvd 预览成功', r4['ok'] and r4['data']['text'])
r5 = fn({})
chk('空模块名被拒绝', (not r5['ok']) and r5['code'] == 'NO_MODULE', '→ ' + r5['msg_cn'])
r6 = fn({'module': 'nonsense'})
chk('未知模块返回中文错误', (not r6['ok']) and '未知' in r6['msg_cn'], '→ ' + r6['msg_cn'])
print('\n结果: %s' % ('全部通过' if fails == 0 else '%d 项失败' % fails))
sys.exit(1 if fails else 0)