sing-box 控制台(sing-box-panel)使用与设计
~/pkgbuild-source/packages/sing-box-panel/,包名sing-box-panel,与sing-box-ebpf解耦(TUN / eBPF 两种模式都能用)。
后端 Python3 标准库,前端 单页无构建,运行状态页 内嵌 zashboard(MIT,随包分发,不联网加载)。
1. 它解决什么问题
MetaCubeXD / zashboard 这类面板只能读 Clash API,也就是”看”运行状态。你自己的需求里有三件事它们做不到:
| 需求 | Clash 类面板 | 本面板 |
|---|---|---|
| 运行状态(连接/流量/延迟/日志) | ✅ | 内嵌 zashboard + 自己的服务卡片与 eBPF 探针 |
| 节点 / 订阅 | ❌ | 抓取 → 解析 → 写入 outbounds → 校验 → 重启 |
| 分应用策略 | ❌ | 写 eBPF 入站的 include_uid / exclude_uid |
| 服务控制 / eBPF 能力预检 | ❌ | systemd + sing-box tools ebpf status |
| 配置安全应用 | ❌ | 备份 → check → 原子替换 → 重启 → 健康检查 → 失败自动回滚 |
2. 安装与启动
# 从私有仓库(CI 构建产物)
sudo pacman -S sing-box-panel
# 或者用本地构建的包
sudo pacman -U sing-box-panel-0.1.0-5-any.pkg.tar.zst
sudo systemctl enable --now sing-box-panel
sudo cat /var/lib/sing-box-panel/token # 访问令牌(0600;服务首次启动时自动生成,PANEL_TOKEN 可覆盖)
然后浏览器打开 http://127.0.0.1:9096/,把上面的令牌粘进去。令牌存在浏览器 localStorage,点右上角「锁定」可清除。
面板只监听 127.0.0.1;所有
/api/*请求都要求X-Panel-Token。静态页面本身不含任何机密。
3. 四个页签
3.1 运行状态
- 服务卡片:active/enabled、PID、启动时间;启动/重启/重载/停止按钮(
systemctl)。 - 核心卡片:sing-box 版本、入站模式(eBPF / TUN)、配置文件路径与修改时间。
- 实时流量:连接数、累计上/下行(轮询 Clash API
/connections,3s 一次)。 - eBPF 内核能力预检:点一下就跑
sing-box tools ebpf status --mode all --json,
逐条列出 PASS / FAIL / UNKNOWN。这是”改配置前先确认内核吃不吃得下”的按钮,
它不挂载程序、不改动任何状态。要测虚拟机共享路径就在输入框填下游接口(例如virbr0)。 - 日志:
journalctl -u sing-box最近 200 行。 - 集成卡片:Clash API 地址、密钥(一键复制),以及三个入口 ——
zashboard(内嵌,深链#/setup?...&secret=…自动带密钥,点开即配好)、官方 dashboard(127.0.0.1:9091/dashboard/,讲 sing-box 原生 API,排障用)、
MetaCubeXD(只在 sing-box 配置里启用clash_api.external_ui时才出现,见 §9)。
3.2 节点与订阅
- 订阅:填 URL → 添加并拉取。支持
- base64 编码的链接列表 / 纯链接列表:
ss://(SIP002 与 legacy)、vmess://、vless://、trojan://、hysteria2:///hy2://、tuic:// - Clash YAML(
proxies:);装了python-yaml用它,没装则用内置子集解析器 - 暂不支持的类型(如
snell)会被跳过并在提示里列出,不会污染配置
- base64 编码的链接列表 / 纯链接列表:
- 写入配置:把启用订阅的节点写入
outbounds,并把它们加进托管策略组。 - 策略组与节点:从 Clash API 实时读;点节点即切换,点「测」做单点延迟测试,「整组测速」批量测。
- 订阅抓取默认走本机代理口(
subscription_proxy,默认http://127.0.0.1:7892),
这样机场订阅在国内也能拉到;不需要就把它设成空串。
托管契约(很重要):面板只碰自己上次写进去的东西——managed.json 记录 outbound_tags(自己加的节点)与 group_members(自己往组里加的成员)。
重新写入时先删掉这些、再加新的,不会动你手工维护的节点和组成员(例如你的 Download 组、
地区组里的手写成员)。默认托管组是 Proxy(selector)与 Auto(urltest),可在面板配置里改。
3.3 分应用策略
- 模式:关闭 / 黑名单(选中的不走代理)/ 白名单(只有选中的走代理)。
- 应用列表来自
.desktop条目 +/proc扫描,按 UID 聚合(显示进程数与可执行文件)。 - 也可以手工加 UID。点「应用策略」写回 eBPF 入站的
local.include_uid/exclude_uid,
然后走安全管线重启核心。 - Linux 上策略粒度就是 UID(同一 UID 的多个进程一起生效);
include_package是 Android 专用字段,桌面不要用。
3.4 配置与服务
- 配置编辑器:整份
config.json,可「仅校验」(sing-box check)或「应用并重启」。 - 应用流程:JSON 解析 →
sing-box check→ 备份 → 原子替换 →systemctl restart→ 健康检查
(is-active,TUN 模式再等tun0,可选再经代理口探一个 URL)→ 任何一步失败就写回旧配置并重启。 - 备份与回滚:每次应用前自动备份到
/etc/sing-box/backups/(保留最近backup_keep份,默认 30),
列表里可一键恢复。恢复本身也走同一条安全管线。
4. 配置文件:/etc/sing-box/panel.json(可选)
面板在缺少该文件时用内置默认值。示例见包里的 /usr/share/sing-box-panel/panel.json.example:
| 键 | 默认 | 说明 |
|---|---|---|
listen / port |
127.0.0.1 / 9096 |
监听地址与端口 |
sing_box_config |
/etc/sing-box/config.json |
被管理的 sing-box 配置 |
service |
sing-box |
systemd 单元名 |
sing_box_bin |
/usr/bin/sing-box |
用于 check / tools ebpf |
clash_api |
http://127.0.0.1:9090 |
Clash API 地址(密钥自动从 sing-box 配置的 experimental.clash_api.secret 读取) |
state_dir |
/var/lib/sing-box-panel |
令牌、subscriptions.json、managed.json |
subscription_proxy |
http://127.0.0.1:7892 |
拉订阅走的代理;空串=直连 |
managed_group / managed_urltest |
Proxy / Auto |
托管组名;设空串则不动组 |
restart_timeout |
20 |
重启后等健康检查的秒数 |
health_probe_url |
空 | 设了就用它做端到端探测(经本机代理口 curl);空=只查服务状态 |
backup_keep |
30 |
保留的备份份数 |
5. 安全模型
- 只监听本机 + 令牌鉴权(
secrets.compare_digest常量时间比较);令牌文件 0600。 - 以 root 运行(systemd 单元里写明原因):写
/etc/sing-box/、systemctl、以及sing-box check遇到 eBPF 入站时会真的创建 BPF map(非 root 必然失败)。 - 没有任意命令执行接口:只有固定的几类操作(服务控制、配置读写、订阅、策略、探针、日志)。
- 静态目录做了路径穿越防护(必须是
ui_dir/zashboard_dir的子路径)。 - 写配置永远先校验、先备份,失败自动回滚——这条是本面板存在的主要理由。
6. 排障
| 现象 | 原因 / 处理 |
|---|---|
面板起不来,日志 Address already in use |
端口被占;改 panel.json 的 port |
| 打开面板一直要求令牌 | 令牌在 /var/lib/sing-box-panel/token;sudo cat 查看 |
「应用并重启」提示 check 未通过 … operation not permitted |
面板不是以 root 运行(eBPF 配置的 check 需要建 BPF map)。用 systemd 单元启动,别用普通用户前台跑 |
| 订阅更新失败 | 检查 subscription_proxy 是否可达(默认走 127.0.0.1:7892),或把机场域名加进直连规则 |
| zashboard 里没有数据 | 面板里的入口现在会自动带上密钥(#/setup?…&secret=…)。若你手动输地址,密钥是 experimental.clash_api.secret(你这里是 <clash-api-secret>) |
| 想让面板管理别的组名 | 改 managed_group / managed_urltest,再点一次「写入配置」 |
| 手工改过配置后想同步 | 「配置与服务」里点「重新载入文件」 |
7. 已经做过的验证(本机实测)
| 验证项 | 结果 |
|---|---|
| 后端语法 + dry-run 启动 | ✅ py_compile;--dry-run 下不碰 systemd,配置写 /tmp |
| 令牌门 | ✅ 无令牌 /api/state → 401;/api/ping 免鉴权仅报告版本 |
| 静态服务 | ✅ /、/app.js、/style.css 200;/dash/ 与 /dash/assets/*.js 200(zashboard 相对路径直接用) |
| 订阅解析 | ✅ ss(SIP002/legacy)、vmess、vless(reality+grpc)、trojan(ws)、hysteria2(obfs)、tuic、Clash YAML 全部解析;snell 正确跳过 |
| 发现并修复的解析 bug | ✅ trojan 链接缺 security= 时丢失 SNI → 现在保留 |
| 订阅写入 + 幂等 | ✅ 出站 24→24(不重复堆叠),Proxy 17→19、Auto 11→13,managed.json 只记自己加的东西 |
| 分应用写回 | ✅ 白名单 → include_uid(去重)、黑名单 → exclude_uid、关闭 → 清空 |
| 打包 | ✅ makepkg --printsrcinfo 与 .SRCINFO 一致;仓库自带 check-package.sh 通过;容器内 makepkg 构建 + 安装 + 运行 --version 全通 |
| namcap | ⚠️ 只剩 2 条误报(退出码 0,不会中断 CI):python3 未声明(Arch 的 python 本身就 Provides: python3);sing-box 可能不需要(面板通过 sing-box check / tools ebpf status 调用它,namcap 看不到) |
构建产物(2026-10-02 晚复核并更新版本号):~/.cache/pkgbuild-out/sing-box-panel-0.1.0-5-any.pkg.tar.zst、~/.cache/pkgbuild-out/sing-box-ebpf-1.14.2.ref1nd-2-x86_64.pkg.tar.zst。
8. 第一次装上后建议这样验证
sudo pacman -U ~/.cache/pkgbuild-out/sing-box-ebpf-*.pkg.tar.zst # 会用 eBPF 版顶掉官方 sing-box
sudo pacman -U ~/.cache/pkgbuild-out/sing-box-panel-*.pkg.tar.zst
sudo systemctl daemon-reload
sudo systemctl restart sing-box # 先仍用 TUN 也没关系,面板两种模式都能管
sudo systemctl enable --now sing-box-panel
sudo cat /var/lib/sing-box-panel/token # 浏览器打开 http://127.0.0.1:9096/ 后粘贴
确认面板三个只读动作正常:运行状态卡片有数据 → eBPF 预检 能跑出 PASS → zashboard ↗ 能打开并显示节点。
然后才是把 config.json 里的 tun 入站换成 ebpf(面板「配置与服务」里直接改,校验+回滚都有)。
追注(2026-10-02 晚):本节写于 TUN 时代,上面这条”把 tun 入站换成 ebpf”的迁移已经完成 ——
当前/etc/sing-box/config.json只有mixed+ebpf两个入站(local.data_plane: cgroup、shared.data_plane: packet_rewriteonvirbr0,TCX 挂载),没有tun入站,tun0设备也不存在。
首次装机的验证流程本身仍然适用,只是”先仍用 TUN”这句已成为历史。
9. 运行时面板:只留 zashboard
决定(2026-10-02):保留 zashboard,去掉 MetaCubeXD。
| zashboard | MetaCubeXD | |
|---|---|---|
| 活跃度 | ★3.5k,2026-09-30,v3.29.1 | ★4.4k,2026-09-30 |
| 观感 | 更现代(PWA、连接 GeoIP/地球视图、多后端 Clash+dae) | 成熟但界面偏旧 |
| 在你这套里的形态 | 随面板离线内嵌(2.9 MB,dist-no-fonts),深链自动带密钥,启动不依赖网络 | 由 sing-box 启动时联网下载到 /var/lib/sing-box/ui,多一个动态件 |
| API | Clash(+ dae) | Clash |
面板里的 MetaCubeXD 入口是数据驱动的:只有配置里存在 clash_api.external_ui 才显示,
所以摘掉配置后按钮自动消失,不用改面板代码。
摘掉 MetaCubeXD(先备份配置、check 通过才重启):
sudo python3 - <<'INNER'
import json, shutil, time
p = '/etc/sing-box/config.json'
shutil.copy2(p, f'/etc/sing-box/config.json.bak.{time.strftime("%Y%m%d-%H%M%S")}')
d = json.load(open(p))
ca = d.get('experimental', {}).get('clash_api', {}) or {}
gone = {k: ca.pop(k) for k in ('external_ui', 'external_ui_download_url', 'external_ui_download_detour') if k in ca}
json.dump(d, open(p, 'w'), ensure_ascii=False, indent=2)
print('已从 clash_api 移除:', gone)
INNER
sudo /usr/bin/sing-box check -C /etc/sing-box && sudo systemctl restart sing-box
sudo rm -rf /var/lib/sing-box/ui # 删掉已下载的 MetaCubeXD 静态文件
验证:curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9090/ui/ 不再是 200;
面板刷新后 MetaCubeXD 按钮消失;zashboard 仍正常(密钥由面板深链自动带上)。
顺带:摘掉后
http_clients与route.default_http_client就没有使用者了(当初只为下载面板而生),
留着无害,想干净也可以一并删掉。更正(2026-10-02 晚实测):上面这句不成立 —— 摘掉
external_ui后这两个仍然有使用者:
当前配置里route.default_http_client = "proxy-rules",且services[api].dashboard.http_client = "proxy-rules"
(官方 dashboard 用它取静态资源)。所以不要跟着删,删了 dashboard 会失去出站。
可核对:curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9091/dashboard/→200;
而/var/lib/sing-box/ui目录确实已不存在(ls /var/lib/sing-box/ui→ 没有那个文件或目录)。










