diff --git a/README.md b/README.md
index b9f57a21..5c4fbea5 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,435 @@
-> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents
+> Sponsored by CodeX
-
-
-
+# sing-box Xboard Fork
----
+这是一个基于上游 [SagerNet/sing-box](https://github.com/SagerNet/sing-box) 的定制分支,主要面向 Xboard / UniProxy 面板联动部署场景。
-# sing-box
+它保留了上游 `sing-box` 内核能力,同时补充了:
-The universal proxy platform.
+- Xboard 动态入站服务
+- 多节点托管
+- 面板协议自动识别
+- VLESS / REALITY 面板字段映射
+- Shadowsocks 2022 用户同步与密钥处理
+- AnyTLS / Trojan / Hysteria / TUIC 的本地 TLS / ACME 支持
+- 安装脚本与分离式配置模板
+- PROXY protocol 客户端真实 IP 传递
-## Repository Notice
+如果你要查看官方功能基线、通用配置文档、原始内核行为,请优先参考上游:
-This repository is a customized fork based on the upstream [SagerNet/sing-box](https://github.com/SagerNet/sing-box) project.
+- 上游仓库:https://github.com/SagerNet/sing-box
+- 上游文档:https://sing-box.sagernet.org
-- Upstream project: `sing-box`
-- Upstream documentation: https://sing-box.sagernet.org
-- This repository may contain local modifications for Xboard integration, deployment scripts, and protocol handling behavior
+## 适用场景
-If you are looking for the official project, feature baseline, or upstream release notes, please refer to the upstream `sing-box` repository first.
+这个仓库更适合以下用途:
-[](https://repology.org/project/sing-box/versions)
+- 从 Xboard / UniProxy 面板拉取节点配置
+- 从面板拉取用户并动态更新
+- 单机运行多个节点
+- 给面板型机场节点准备安装脚本
+- 对接面板下发的 `protocol`、`cert_config`、`accept_proxy_protocol`、REALITY 字段等
-## Documentation
+## 主要特性
-https://sing-box.sagernet.org
+- `services.xboard` 动态服务
+- 支持单节点和多节点
+- 协议优先从面板返回的 `protocol` 识别
+- 支持面板回包控制 `accept_proxy_protocol`
+- 支持 VLESS REALITY 的 `server_name`、`public_key`、`private_key`、`short_id`
+- 支持 ACME:
+ - `auto_tls`
+ - `cert_mode = http`
+ - `cert_mode = dns`
+- 当前已支持的 ACME DNS provider:
+ - `cloudflare`
+ - `alidns`
+ - `tencentcloud`
+ - `dnspod`
+ - `acmedns`
+- 安装脚本默认生成:
+ - `/etc/sing-box/config.d/10-base.json`
+ - `/etc/sing-box/config.d/20-outbounds.json`
+- 安装后的服务名为:
+ - `singbox.service`
+
+## 仓库内关键文件
+
+- [install.sh](./install.sh)
+ Linux 安装脚本
+- [option/xboard.go](./option/xboard.go)
+ `services.xboard` 配置结构
+- [service/xboard/service.go](./service/xboard/service.go)
+ Xboard 动态服务实现
+- [configs](./configs)
+ 配置示例目录
+
+## 快速开始
+
+### 1. 编译并安装
+
+在 Linux 服务器上进入仓库目录:
+
+```bash
+chmod +x install.sh
+./install.sh
+```
+
+脚本会做这些事情:
+
+1. 检查 Go 环境
+2. 编译当前仓库代码
+3. 生成分离配置
+4. 创建 `singbox.service`
+5. 启动服务
+
+### 2. 安装过程会询问的内容
+
+- `Panel URL`
+- `Panel Token`
+- 一个或多个 `Node ID`
+- DNS 模式:
+ - `udp`
+ - `local`
+- 当前节点前面是否有发送 PROXY protocol 的四层代理
+
+### 3. 多节点输入规则
+
+- 安装脚本会持续要求输入 `Node ID`
+- 输入 `NO` 才结束
+- 至少要有一个节点
+
+## 运行与管理
+
+查看状态:
+
+```bash
+systemctl status singbox
+```
+
+查看日志:
+
+```bash
+journalctl -u singbox -f
+```
+
+重启服务:
+
+```bash
+systemctl restart singbox
+```
+
+手动运行:
+
+```bash
+sing-box -D /var/lib/sing-box -C /etc/sing-box/config.d run
+```
+
+## 推荐配置结构
+
+建议把配置拆成两部分。
+
+### `10-base.json`
+
+放这些内容:
+
+- 日志
+- DNS
+- `services`
+- 基础路由规则
+
+### `20-outbounds.json`
+
+放这些内容:
+
+- 全部出站
+- 出站标签
+- 你自己的分流依赖
+
+这样更方便:
+
+- 面板动态服务和你的自定义出站分离
+- 安装脚本生成的基础配置不容易被误改
+- 调整出站时不会影响 Xboard 服务主体
+
+示例已放在:
+
+- [configs/10-base.single-node.json](./configs/10-base.single-node.json)
+- [configs/10-base.multi-node.json](./configs/10-base.multi-node.json)
+- [configs/20-outbounds.example.json](./configs/20-outbounds.example.json)
+
+## `services.xboard` 配置说明
+
+配置结构定义见 [option/xboard.go](./option/xboard.go)。
+
+### 最小单节点示例
+
+```json
+{
+ "type": "xboard",
+ "panel_url": "https://panel.example.com",
+ "key": "replace-with-node-token",
+ "sync_interval": "1m",
+ "report_interval": "1m",
+ "node_id": 286
+}
+```
+
+### 多节点示例
+
+```json
+{
+ "type": "xboard",
+ "panel_url": "https://panel.example.com",
+ "key": "replace-with-node-token",
+ "sync_interval": "1m",
+ "report_interval": "1m",
+ "nodes": [
+ {
+ "node_id": 286
+ },
+ {
+ "node_id": 774
+ }
+ ]
+}
+```
+
+### 当前推荐做法
+
+- 只传 `panel_url`
+- 只传 `key`
+- 只传 `node_id` 或 `nodes`
+
+下面这些字段仍然保留兼容,但常规部署一般不需要:
+
+- `config_panel_url`
+- `user_panel_url`
+- `config_node_id`
+- `user_node_id`
+- `node_type`
+
+说明:
+
+- 当前逻辑会优先从面板回包中的 `protocol` 自动识别协议
+- `node_type` 更适合做历史兼容,不建议再依赖它做主配置
+
+## 面板配置回包约定
+
+### 1. 协议识别
+
+推荐面板显式返回:
+
+```json
+{
+ "protocol": "vless"
+}
+```
+
+当前服务会按如下顺序识别协议:
+
+1. `protocol`
+2. `node_type`
+3. 如果是 Shadowsocks 且存在 `cipher`,则回退识别为 `shadowsocks`
+
+### 2. 监听地址与端口
+
+推荐面板返回:
+
+```json
+{
+ "listen_ip": "0.0.0.0",
+ "server_port": 443
+}
+```
+
+### 3. PROXY protocol
+
+如果前面有四层代理,并且它会发送 PROXY protocol 头,需要面板返回:
+
+```json
+{
+ "accept_proxy_protocol": true
+}
+```
+
+如果用户是直连节点,不要开启这个字段,否则连接会失败。
+
+### 4. VLESS REALITY
+
+当前支持从面板获取这些字段:
+
+- `tls_settings.server_name`
+- `tls_settings.public_key`
+- `tls_settings.private_key`
+- `tls_settings.short_id`
+- 顶层 `server_name`
+- 顶层 `public_key`
+- 顶层 `private_key`
+- 顶层 `short_id`
+
+示例见:
+
+- [configs/panel-response.vless-reality.json](./configs/panel-response.vless-reality.json)
+
+### 5. Shadowsocks 2022
+
+推荐面板返回:
+
+- `protocol: "shadowsocks"`
+- `cipher`
+- `server_key`
+
+示例见:
+
+- [configs/panel-response.shadowsocks2022.json](./configs/panel-response.shadowsocks2022.json)
+
+### 6. AnyTLS / Trojan / Hysteria / TUIC 的证书要求
+
+这些协议通常要求本地具备可用 TLS 证书。当前支持:
+
+- 文件证书
+- 证书内容直传
+- ACME 自动签发
+
+如果面板没有下发可用证书,也没有有效 ACME 配置,常见报错是:
+
+```text
+Xboard setup error: missing certificate
+```
+
+示例见:
+
+- [configs/panel-response.anytls-acme-dns.json](./configs/panel-response.anytls-acme-dns.json)
+
+## ACME 说明
+
+当前支持:
+
+- `auto_tls: true`
+- `cert_mode: "http"`
+- `cert_mode: "dns"`
+
+### DNS-01 provider
+
+已支持:
+
+- `cloudflare`
+- `alidns`
+- `tencentcloud`
+- `dnspod`
+- `acmedns`
+
+### DNSPod 说明
+
+仓库已经内置 DNSPod provider 适配,不再依赖旧版 `github.com/libdns/dnspod` 的接口兼容。
+
+你可以从面板下发:
+
+```json
+{
+ "cert_config": {
+ "cert_mode": "dns",
+ "dns_provider": "dnspod",
+ "dns_env": {
+ "DNSPOD_TOKEN": "id,token"
+ }
+ }
+}
+```
+
+也可以下发腾讯云凭据:
+
+```json
+{
+ "cert_config": {
+ "cert_mode": "dns",
+ "dns_provider": "tencentcloud",
+ "dns_env": {
+ "TENCENTCLOUD_SECRET_ID": "xxx",
+ "TENCENTCLOUD_SECRET_KEY": "xxx"
+ }
+ }
+}
+```
+
+## 配置示例目录
+
+[configs](./configs) 目录中已经提供了这些示例:
+
+- [configs/10-base.single-node.json](./configs/10-base.single-node.json)
+ 单节点基础配置
+- [configs/10-base.multi-node.json](./configs/10-base.multi-node.json)
+ 多节点基础配置
+- [configs/20-outbounds.example.json](./configs/20-outbounds.example.json)
+ 出站配置模板
+- [configs/panel-response.vless-reality.json](./configs/panel-response.vless-reality.json)
+ VLESS REALITY 面板回包
+- [configs/panel-response.shadowsocks2022.json](./configs/panel-response.shadowsocks2022.json)
+ Shadowsocks 2022 面板回包
+- [configs/panel-response.anytls-acme-dns.json](./configs/panel-response.anytls-acme-dns.json)
+ AnyTLS + ACME DNS 验证面板回包
+
+## 常见问题
+
+### `unsupported protocol: empty`
+
+原因通常是:
+
+- 面板没有返回 `protocol`
+- 兼容字段 `node_type` 也为空
+
+建议:
+
+- 面板显式返回顶层 `protocol`
+
+### `Xboard setup error: missing certificate`
+
+原因通常是:
+
+- AnyTLS / Trojan / Hysteria / TUIC 需要证书
+- 面板没有下发证书文件、证书内容或 ACME 参数
+
+### `TLS handshake: REALITY: processed invalid connection`
+
+多数情况下表示客户端参数和当前 REALITY 节点配置不匹配,例如:
+
+- `server_name` 错误
+- `public_key` 错误
+- `short_id` 错误
+- 客户端实际上不是按 REALITY 模式连入
+
+### `Server does not exist`
+
+通常是:
+
+- 面板里不存在该 `node_id`
+- `token` 不匹配
+- 拉取配置或拉取用户的节点 ID 写错
+
+### 真实 IP 没有正确获取
+
+请确认:
+
+1. 前置代理确实发送了 PROXY protocol
+2. 面板确实下发了 `accept_proxy_protocol: true`
+
+否则服务只能看到上游代理 IP。
+
+## 开发验证
+
+近期已验证通过的命令:
+
+```bash
+go test ./common/dnspod ./common/tls ./service/acme ./service/xboard
+go build -trimpath -tags 'with_quic,with_utls,with_clash_api,with_gvisor,with_acme' ./cmd/sing-box
+```
+
+如果你改动了 Xboard、协议映射、ACME 或证书相关逻辑,建议至少执行一次以上命令。
## License
-```
+```text
Copyright (C) 2022 by nekohasekai
This program is free software: you can redistribute it and/or modify
@@ -38,7 +439,7 @@ the Free Software Foundation, either version 3 of the License, or
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
diff --git a/configs/10-base.multi-node.json b/configs/10-base.multi-node.json
new file mode 100644
index 00000000..185fe0d3
--- /dev/null
+++ b/configs/10-base.multi-node.json
@@ -0,0 +1,50 @@
+{
+ "log": {
+ "level": "info",
+ "timestamp": true
+ },
+ "experimental": {
+ "cache_file": {
+ "enabled": true,
+ "path": "/var/lib/sing-box/cache.db"
+ }
+ },
+ "dns": {
+ "servers": [
+ {
+ "tag": "dns-local",
+ "type": "local"
+ }
+ ]
+ },
+ "services": [
+ {
+ "type": "xboard",
+ "panel_url": "https://panel.example.com",
+ "key": "replace-with-node-token",
+ "sync_interval": "1m",
+ "report_interval": "1m",
+ "nodes": [
+ {
+ "node_id": 286
+ },
+ {
+ "node_id": 774
+ },
+ {
+ "node_id": 815
+ }
+ ]
+ }
+ ],
+ "inbounds": [],
+ "route": {
+ "rules": [
+ {
+ "protocol": "dns",
+ "action": "hijack-dns"
+ }
+ ],
+ "auto_detect_interface": true
+ }
+}
diff --git a/configs/10-base.single-node.json b/configs/10-base.single-node.json
new file mode 100644
index 00000000..749920cf
--- /dev/null
+++ b/configs/10-base.single-node.json
@@ -0,0 +1,42 @@
+{
+ "log": {
+ "level": "info",
+ "timestamp": true
+ },
+ "experimental": {
+ "cache_file": {
+ "enabled": true,
+ "path": "/var/lib/sing-box/cache.db"
+ }
+ },
+ "dns": {
+ "servers": [
+ {
+ "tag": "dns-upstream",
+ "type": "udp",
+ "server": "1.1.1.1",
+ "server_port": 53
+ }
+ ]
+ },
+ "services": [
+ {
+ "type": "xboard",
+ "panel_url": "https://panel.example.com",
+ "key": "replace-with-node-token",
+ "sync_interval": "1m",
+ "report_interval": "1m",
+ "node_id": 286
+ }
+ ],
+ "inbounds": [],
+ "route": {
+ "rules": [
+ {
+ "protocol": "dns",
+ "action": "hijack-dns"
+ }
+ ],
+ "auto_detect_interface": true
+ }
+}
diff --git a/configs/20-outbounds.example.json b/configs/20-outbounds.example.json
new file mode 100644
index 00000000..5573444d
--- /dev/null
+++ b/configs/20-outbounds.example.json
@@ -0,0 +1,12 @@
+{
+ "outbounds": [
+ {
+ "type": "direct",
+ "tag": "direct"
+ },
+ {
+ "type": "block",
+ "tag": "block"
+ }
+ ]
+}
diff --git a/configs/panel-response.anytls-acme-dns.json b/configs/panel-response.anytls-acme-dns.json
new file mode 100644
index 00000000..347f717d
--- /dev/null
+++ b/configs/panel-response.anytls-acme-dns.json
@@ -0,0 +1,33 @@
+{
+ "protocol": "anytls",
+ "listen_ip": "0.0.0.0",
+ "server_port": 45365,
+ "network": null,
+ "networkSettings": null,
+ "server_name": "code.example.com",
+ "accept_proxy_protocol": false,
+ "padding_scheme": [
+ "stop=8",
+ "0=30-30",
+ "1=100-400",
+ "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
+ "3=9-9,500-1000",
+ "4=500-1000",
+ "5=500-1000",
+ "6=500-1000",
+ "7=500-1000"
+ ],
+ "cert_config": {
+ "cert_mode": "dns",
+ "domain": "code.example.com",
+ "dns_provider": "tencentcloud",
+ "dns_env": {
+ "TENCENTCLOUD_SECRET_ID": "replace-with-secret-id",
+ "TENCENTCLOUD_SECRET_KEY": "replace-with-secret-key"
+ }
+ },
+ "base_config": {
+ "push_interval": 60,
+ "pull_interval": 60
+ }
+}
diff --git a/configs/panel-response.shadowsocks2022.json b/configs/panel-response.shadowsocks2022.json
new file mode 100644
index 00000000..509e8e2d
--- /dev/null
+++ b/configs/panel-response.shadowsocks2022.json
@@ -0,0 +1,16 @@
+{
+ "protocol": "shadowsocks",
+ "listen_ip": "0.0.0.0",
+ "server_port": 30009,
+ "network": null,
+ "networkSettings": null,
+ "cipher": "2022-blake3-aes-256-gcm",
+ "plugin": null,
+ "plugin_opts": null,
+ "server_key": "NjQzMWVlNjVmYTkwODk0OTMyOTg3MzZmYzczMmFlMTI=",
+ "accept_proxy_protocol": false,
+ "base_config": {
+ "push_interval": 60,
+ "pull_interval": 60
+ }
+}
diff --git a/configs/panel-response.vless-reality.json b/configs/panel-response.vless-reality.json
new file mode 100644
index 00000000..4eb129f6
--- /dev/null
+++ b/configs/panel-response.vless-reality.json
@@ -0,0 +1,21 @@
+{
+ "protocol": "vless",
+ "listen_ip": "0.0.0.0",
+ "server_port": 18443,
+ "network": "tcp",
+ "tls": 2,
+ "flow": "xtls-rprx-vision",
+ "accept_proxy_protocol": false,
+ "tls_settings": {
+ "server_name": "git.example.com",
+ "server_port": "443",
+ "public_key": "replace-with-client-visible-public-key",
+ "private_key": "replace-with-server-private-key",
+ "short_id": "0123456789abcdef",
+ "allow_insecure": false
+ },
+ "base_config": {
+ "push_interval": 60,
+ "pull_interval": 60
+ }
+}
diff --git a/install.sh b/install.sh
index 18195257..53f814a5 100644
--- a/install.sh
+++ b/install.sh
@@ -95,10 +95,6 @@ build_sing_box() {
echo -e "${RED}Failed to download Go modules.${NC}"
exit 1
fi
- if ! go get github.com/libdns/dnspod@v0.0.3 github.com/libdns/tencentcloud@v1.4.3; then
- echo -e "${RED}Failed to download ACME DNS provider modules.${NC}"
- exit 1
- fi
if ! go mod tidy; then
echo -e "${RED}Failed to refresh Go module metadata.${NC}"
exit 1