一套跑在 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
94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""依赖自检(#5)真机验收 —— 必须在目标机上跑(BASE 是 127.0.0.1:8443)。
|
||
|
||
只看三件事:
|
||
1. 清单里新加的项都能被正确探测(有就是有、没有就是没有,别因为 PATH 少
|
||
/usr/sbin 而把已装的 mkfs.* 报成缺失);
|
||
2. 缺失的可选大件(samba / nfs / docker)不会出现在「一键安装」的范围里;
|
||
3. 页面能拿到完整清单。
|
||
"""
|
||
import json
|
||
import ssl
|
||
import sys
|
||
import urllib.request
|
||
|
||
BASE = 'https://127.0.0.1:8443'
|
||
CTX = ssl.create_default_context()
|
||
CTX.check_hostname = False
|
||
CTX.verify_mode = ssl.CERT_NONE
|
||
|
||
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))
|
||
|
||
|
||
def call(path, body=None, token=None):
|
||
data = None
|
||
headers = {}
|
||
if body is not None:
|
||
data = json.dumps(body).encode('utf-8')
|
||
headers['Content-Type'] = 'application/json'
|
||
if token:
|
||
headers['X-Token'] = token
|
||
req = urllib.request.Request(BASE + path, data=data, headers=headers,
|
||
method='POST' if body is not None else 'GET')
|
||
with urllib.request.urlopen(req, context=CTX, timeout=60) as r:
|
||
return json.loads(r.read().decode('utf-8'))
|
||
|
||
|
||
def main():
|
||
r = call('/api/login', {'username': 'admin', 'password': 'admin123'})
|
||
chk('登录成功', r.get('ok'), r.get('msg_cn'))
|
||
if not r.get('ok'):
|
||
return 1
|
||
tk = r['data']['token']
|
||
|
||
r = call('/api/deps/check', None, tk)
|
||
chk('自检接口返回成功', r.get('ok'), r.get('msg_cn'))
|
||
d = r.get('data') or {}
|
||
items = d.get('items') or []
|
||
by = {x['key']: x for x in items}
|
||
s = d.get('summary') or {}
|
||
print(' 总计 %s 项:已就绪 %s / 必需缺失 %s / 可选未装 %s'
|
||
% (s.get('total'), s.get('ok'), s.get('missing'), s.get('optional')))
|
||
|
||
for k in ('util-linux', 'tc', 'journalctl', 'mtr', 'samba', 'nfs-server',
|
||
'docker', 'docker-compose', 'exfatprogs', 'ntfs-3g', 'dosfstools',
|
||
'btrfs-progs', 'xfsprogs', 'f2fs-tools', 'etherwake', 'tcpdump',
|
||
'nmcli', 'networkctl'):
|
||
chk('清单含 %s' % k, k in by)
|
||
|
||
# PATH 陷阱:这些命令都在 /sbin / /usr/sbin,helper 必须已归一化 PATH
|
||
for k in ('util-linux', 'tc'):
|
||
x = by.get(k) or {}
|
||
chk('%s 被正确探测为已安装(PATH 含 /sbin)' % k, x.get('ok') is True,
|
||
'→ %s' % (x.get('where') or x.get('target')))
|
||
|
||
# 本机确实没装的,应如实报缺失(而不是靠猜)
|
||
for k in ('samba', 'nfs-server', 'docker'):
|
||
x = by.get(k) or {}
|
||
chk('%s 如实报为未安装' % k, x.get('ok') is False)
|
||
|
||
# 危险项不给安装包
|
||
for k in ('nmcli', 'networkctl', 'systemd', 'vnc'):
|
||
x = by.get(k) or {}
|
||
chk('%s 不提供一键安装(pkg 为空)' % k, not (x.get('pkg') or ''))
|
||
|
||
# 可选大件绝不能是「必需」,否则会被一键安装带上
|
||
for k in ('samba', 'nfs-server', 'docker', 'xfce', 'xorg'):
|
||
x = by.get(k) or {}
|
||
chk('%s 是可选(不进一键安装)' % k, x.get('required') is False)
|
||
|
||
print('\n结果: %s' % ('全部通过' if fails == 0 else '%d 项失败' % fails))
|
||
return 1 if fails else 0
|
||
|
||
|
||
if __name__ == '__main__':
|
||
sys.exit(main())
|