Files
drouter/_dev/t-cachebust.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

82 lines
2.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""入口页资源版本号注入(破浏览器缓存)的纯逻辑测试。
从 drouter-web.py 抽出 _ASSET_RE / _inject_asset_version 两个片段,
在隔离命名空间里执行,不导入整个后端(那会连数据库、读证书)。
"""
import ast
import os
import re
import sys
import tempfile
SRC = os.path.join(os.path.dirname(__file__), '..', 'backend', 'drouter-web.py')
code = open(SRC, encoding='utf-8').read()
tree = ast.parse(code)
want = {'_ASSET_RE', '_inject_asset_version'}
picked = []
for node in tree.body:
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id in want:
picked.append(node)
break
elif isinstance(node, ast.FunctionDef) and node.name in want:
picked.append(node)
found = set()
for node in picked:
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name):
found.add(t.id)
else:
found.add(node.name)
missing = want - found
assert not missing, '未能从源码抽取:%s' % ', '.join(sorted(missing))
# 建一个假 WEB_DIR,里面放两个已知 mtime 的文件
tmp = tempfile.mkdtemp()
for name in ('app.js', 'app.css'):
p = os.path.join(tmp, name)
open(p, 'w', encoding='utf-8').write('/* %s */' % name)
os.utime(p, (1_700_000_000, 1_700_000_000))
ns = {'os': os, 're': re, 'WEB_DIR': tmp}
exec(compile(ast.Module(body=picked, type_ignores=[]), SRC, 'exec'), ns)
inject = ns['_inject_asset_version']
HTML = ('<!doctype html>\n'
'<link rel="icon" href="/logo.svg" type="image/svg+xml">\n'
'<link rel="stylesheet" href="/app.css">\n'
'<link rel="stylesheet" href="/theme.css?v=123">\n'
'<script src="/app.js"></script>\n')
out = inject(HTML.encode('utf-8')).decode('utf-8')
fails = []
def ck(name, cond, extra=''):
print(' %-42s %s %s' % (name, 'OK ' if cond else 'FAIL', extra))
if not cond:
fails.append(name)
print('资源版本号注入:')
ck('app.css 追加了 ?v=', '/app.css?v=1700000000' in out)
ck('app.js 追加了 ?v=', '/app.js?v=1700000000' in out)
ck('已带查询串的 theme.css 不被改动', '/theme.css?v=123' in out and '/theme.css?v=123?v=' not in out)
ck('非 js/css 的 logo.svg 不动', '/logo.svg' in out and '/logo.svg?v=' not in out)
ck('不会重复追加(幂等)',
inject(out.encode('utf-8')).decode('utf-8').count('/app.js?v=') == 1)
ck('输出仍是合法 utf-8 且保留 doctype', out.startswith('<!doctype html>'))
ck('单引号属性也能处理',
"/app.css?v=" in inject(b"<link href='/app.css'>").decode('utf-8'))
print('\n结果:%d 项,失败 %d 项' % (7, len(fails)))
raise SystemExit(1 if fails else 0)