一套跑在 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
35 lines
1.6 KiB
JavaScript
35 lines
1.6 KiB
JavaScript
// 校验 PAGES 的 key 是否都在 VIEWS 里有映射。
|
||
// tag:'soon' 的占位页不需要渲染函数(点进去显示「等待作者完善」),自动排除。
|
||
const fs = require('fs');
|
||
// 以脚本自身位置定位,避免依赖运行时的当前目录(否则换个目录跑就 ENOENT)。
|
||
const WEB = require('path').join(__dirname, '..', 'web', 'app.js');
|
||
const path = process.argv[2] || WEB;
|
||
const s = fs.readFileSync(path, 'utf8');
|
||
|
||
const nav = s.match(/const NAV_GROUPS = \[([\s\S]*?)\n\];/);
|
||
if (!nav) { console.log('✘ 未找到 NAV_GROUPS'); process.exit(1); }
|
||
|
||
const items = [];
|
||
for (const m of nav[1].matchAll(/\{([^{}]*?k:\s*'([a-z0-9]+)'[^{}]*?)\}/g)) {
|
||
const body = m[1];
|
||
const key = m[2];
|
||
const soon = /tag:\s*'soon'/.test(body);
|
||
items.push({ key, soon });
|
||
}
|
||
|
||
const block = s.match(/const VIEWS = \{([\s\S]*?)\n\};/);
|
||
if (!block) { console.log('✘ 未找到 VIEWS 定义'); process.exit(1); }
|
||
const mapped = [];
|
||
for (const m of block[1].matchAll(/^\s*([a-z0-9_]+):/gm)) mapped.push(m[1]);
|
||
|
||
const real = items.filter((x) => !x.soon);
|
||
const soon = items.filter((x) => x.soon);
|
||
const missing = real.filter((x) => !mapped.includes(x.key)).map((x) => x.key);
|
||
const keys = items.map((x) => x.key);
|
||
const extra = mapped.filter((k) => !keys.includes(k));
|
||
|
||
console.log('菜单项:', items.length, '(已实现', real.length, '/ 规划中', soon.length, ')');
|
||
console.log('缺失映射:', missing.length ? missing.join(', ') + ' ✘' : '无 ✔');
|
||
console.log('多余映射:', extra.length ? extra.join(', ') : '无 ✔');
|
||
process.exit(missing.length ? 1 : 0);
|