Files
drouter/_dev/live-wait-probe.py
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

100 lines
4.1 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""分层定位终端长轮询为什么没挂起:shelld → helper → helpd → HTTP。"""
import json
import os
import socket
import subprocess
import sys
import time
SHELL_SOCK = '/run/drouter/shell.sock'
HELPD_SOCK = '/run/drouter/helper.sock'
def sock_call(path, req, timeout=30):
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.settimeout(timeout)
s.connect(path)
s.sendall((json.dumps(req) + '\n').encode())
buf = b''
while not buf.endswith(b'\n'):
c = s.recv(65536)
if not c:
break
buf += c
s.close()
return json.loads(buf.decode('utf-8', 'replace'))
for p in (SHELL_SOCK, HELPD_SOCK):
print('%-28s 存在=%s' % (p, os.path.exists(p)))
print('\n=== 1) 直连 shelld ===')
r = sock_call(SHELL_SOCK, {'op': 'connect', 'user': 'root', 'cols': 100, 'rows': 30})
sid = (r.get('data') or {}).get('sid')
print('connect ok=%s sid=%s' % (r.get('ok'), sid))
if sid:
for _ in range(5):
rr = sock_call(SHELL_SOCK, {'op': 'read', 'sid': sid, 'wait': 0})
if not ((rr.get('data') or {}).get('data') or ''):
break
t0 = time.time()
rr = sock_call(SHELL_SOCK, {'op': 'read', 'sid': sid, 'wait': 1.2})
print('wait=1.2 空读耗时 %.0f ms ok=%s data=%r'
% ((time.time() - t0) * 1000, rr.get('ok'),
((rr.get('data') or {}).get('data') or '')[:40]))
sock_call(SHELL_SOCK, {'op': 'disconnect', 'sid': sid})
print('\n=== 2) 走 helper 子进程 ===')
r = json.loads(subprocess.run(
['sudo', '-n', '/usr/bin/python3', '/opt/drouter/backend/drouter-helper.py',
'webshell', json.dumps({'op': 'connect', 'cols': 100, 'rows': 30})],
capture_output=True, text=True, timeout=60).stdout.strip().splitlines()[-1])
sid = (r.get('data') or {}).get('sid')
print('connect ok=%s sid=%s' % (r.get('ok'), sid))
if sid:
for _ in range(5):
out = subprocess.run(
['sudo', '-n', '/usr/bin/python3', '/opt/drouter/backend/drouter-helper.py',
'webshell', json.dumps({'op': 'read', 'sid': sid, 'wait': 0})],
capture_output=True, text=True, timeout=60).stdout.strip().splitlines()[-1]
if not ((json.loads(out).get('data') or {}).get('data') or ''):
break
t0 = time.time()
out = subprocess.run(
['sudo', '-n', '/usr/bin/python3', '/opt/drouter/backend/drouter-helper.py',
'webshell', json.dumps({'op': 'read', 'sid': sid, 'wait': 1.2})],
capture_output=True, text=True, timeout=60).stdout.strip().splitlines()[-1]
print('wait=1.2 空读耗时 %.0f ms' % ((time.time() - t0) * 1000))
subprocess.run(['sudo', '-n', '/usr/bin/python3',
'/opt/drouter/backend/drouter-helper.py', 'webshell',
json.dumps({'op': 'disconnect', 'sid': sid})],
capture_output=True, text=True, timeout=60)
print('\n=== 3) 走 helpd 常驻守护 ===')
if os.path.exists(HELPD_SOCK):
r = sock_call(HELPD_SOCK, {'action': 'webshell',
'payload': {'op': 'connect', 'cols': 100, 'rows': 30}})
sid = (r.get('data') or {}).get('sid')
print('connect ok=%s sid=%s' % (r.get('ok'), sid))
if sid:
for _ in range(5):
rr = sock_call(HELPD_SOCK, {'action': 'webshell',
'payload': {'op': 'read', 'sid': sid, 'wait': 0}})
if not ((rr.get('data') or {}).get('data') or ''):
break
t0 = time.time()
rr = sock_call(HELPD_SOCK, {'action': 'webshell',
'payload': {'op': 'read', 'sid': sid, 'wait': 1.2}})
print('wait=1.2 空读耗时 %.0f ms' % ((time.time() - t0) * 1000))
sock_call(HELPD_SOCK, {'action': 'webshell',
'payload': {'op': 'disconnect', 'sid': sid}})
else:
print('helpd socket 不存在')
print('\n=== 4) helpd 是否并发处理(单线程会串行阻塞)===')
print('检查 drouter-helpd 主线程模型:')
os.system("grep -n 'Threading\|Thread\|serve_forever\|fork' "
"/opt/drouter/backend/drouter-helpd.py | head -10")