m-uising-box panel

架构

一个二进制跑四件事,共用一个 SQLite 文件。这里是短版;README 里有同样内容的时序图与 ER 图。

一台服务器里有什么

部分监听负责
面板:2053 /app/(代理面板 :2054 /dl/)会话、API、代理作用域。两个面板是同一套前端,会话决定能看到什么。
订阅服务:2056 /sub/三种格式、落地页、客户端下载页、临时共享、"订阅不可用"说明页。
数据面各线路端口内嵌 sing-box:每条线路一个入站、每个上游一个出站,流量与连接跟踪,按用户限制。
后台—每 10 秒统计,每分钟判配额,上游巡检(各机只测自己用到的),每小时清理日志,证书续期,备份,每 5 秒主副同步。

四者共用 m-ui.db(SQLite,WAL 模式)。面板写,数据面和订阅服务读,所以面板里点一下保存,各处立刻看到同一份数据。没有消息队列、没有缓存服务、没有第二个数据库要跑。

保存一次改动,发生了什么

  1. 字段校验与端口检查 —— 对照其它线路、面板端口,以及本机上任何已经在监听的程序。
  2. 开写事务(BEGIN IMMEDIATE)写入改动。
  3. 从库里渲染出整份配置,交给 sing-box 自己的解析器。解析不通过就回滚事务,请求带着原因失败。线上一动没动。
  4. 通过后按改动分级应用:改用户 → 换入站用户表(别人不断线);改上游 → 热换出站;改线路 → 重启数据面,新配置起不来(比如端口被别的进程抢走)就恢复上一份能用的。

一次订阅请求

GET /sub/<地址>:地址按用户名、随机订阅令牌、临时共享令牌(同一个用户,另一套凭据)依次匹配。停用的用户、到期的代理、对不上的地址都是 404 —— 浏览器看到一页说明,客户端拿到纯 404。有效用户:浏览器得到落地页;客户端按 ?format= 得到通用链接、Clash YAML 或 sing-box 配置。节点 = 用户已分配的线路 × 线路部署的服务器,再拼上外部节点与外部订阅。

主副机

主机每 5 秒把线路、上游、用户、凭据、代理和同步设置做成快照,哈希成修订号,并发推给所有副机,再拉回每台的报告(游标之后的流量、在线 IP、状态)。副机不判配额;主机停用的用户在下一份快照里自然消失。细节见多服务器。

数据模型

表存什么关系
lines协议、端口、TLS、传输、参数、部署到哪些服务器→ upstream(出口);↔ users(user_lines);↔ nodes
users各协议凭据、配额与用量、到期、设备与限速、订阅地址、共享令牌↔ lines;→ reseller(可选);← plans(建号时)
upstreams出站类型与参数← lines
nodes名称、域名 / 地址、API 地址与令牌、倍率↔ lines
resellers额度(流量、带宽、设备)、到期、自己的落地页文案、被授权的线路→ users、plans
stats、agent_counters、traffic_cursors时序与跨机计量
settings、admins、sub_logs、audit其余一切

后台节奏

周期做什么
5 秒主机 → 副机推快照、拉报告
10 秒从数据面取流量与连接,按用户 / 线路 / 上游写统计
10 秒判定限速规则(时段 / 突发),命中的状态随下一次快照推给副机
1 分钟配额、到期、周期重置;数据面看门狗
10 分钟WAL 检查点,运行中的库随时可安全复制
6 小时向 GitHub Releases 查一次新版本(只查)
1 小时按保留天数清理时序与日志;48 小时前的分钟样本并成小时桶
12 小时证书续期检查(续期后 sing-box 自动换证书,不重启)
每天日报

代码结构

目录职责
web/面板与 API、代理面板、外部 API、内嵌前端(零构建 ES 模块)
sub/订阅格式、落地页、客户端下载、共享
render/ core/线路 → sing-box 配置与干跑;内嵌数据面与跟踪
hub/快照推送、流量回收、在线聚合
runner/进程编排、三级重载与回滚、证书、备份
jobs/ monitor/统计、配额判定、巡检、告警
database/模型、SQLite 打开与迁移
selfupdate/ deploy/版本检查与就地更新、安装脚本

图:README → 架构。深入:为什么内嵌内核 · 热更新是怎么做的 · 同步是怎么做的。