287 lines
8.8 KiB
Markdown
287 lines
8.8 KiB
Markdown
# sub-provider
|
||
|
||
一个可部署的订阅聚合后端模板,目标是同时支持两种输出方式:
|
||
|
||
- **薄壳模式**:客户端拉取 `/clients/*.yaml`,配置内继续引用远程 `proxy-providers` / `rule-providers`
|
||
- **打包模式**:客户端拉取 `/bundle/*.yaml`,服务端把节点、策略组、规则全部铺开成单文件 YAML
|
||
|
||
这一版已经补上:
|
||
|
||
- 多机场源选择:`?sources=airport-a,airport-b,airport-c`
|
||
- 单机场或多机场时,**始终取第一个源**的 `Subscription-Userinfo` 返回给客户端
|
||
- `GET` 和 `HEAD` 都支持
|
||
- provider 单源输出、merged provider 输出、thin client 输出、bundle 输出
|
||
- 服务端内部继续解耦:抓取、配额头解析、provider 构建、规则加载、profile 组装分层处理
|
||
|
||
> 当前版本会自动识别上游输入格式,支持:
|
||
> 1. 已经能返回 Clash/Mihomo YAML `proxies:` 文件的地址
|
||
> 2. base64 编码的 URI 订阅
|
||
> 3. 明文 URI 订阅
|
||
>
|
||
> 对 URI 订阅还额外兼容了常见变种:外层 base64 解开后仍是 base64、以及整段 URI 文本再次经过 URL encode。
|
||
>
|
||
> 当前已兼容常见的 `anytls://`、`vless://`、`trojan://`、`ss://`、`vmess://`。
|
||
|
||
---
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
sub-provider/
|
||
app/
|
||
config.py
|
||
main.py
|
||
models.py
|
||
services/
|
||
cache.py
|
||
headers.py
|
||
loader.py
|
||
profiles.py
|
||
rules.py
|
||
subscriptions.py
|
||
config/
|
||
conf/
|
||
base.conf
|
||
profiles/
|
||
default.conf
|
||
lite.conf
|
||
sources.yaml
|
||
rules/
|
||
reject.yaml
|
||
direct.yaml
|
||
proxy.yaml
|
||
cn-ip.yaml
|
||
data/
|
||
cache/
|
||
sources/
|
||
.env.example
|
||
Dockerfile
|
||
docker-compose.yaml
|
||
requirements.txt
|
||
```
|
||
|
||
---
|
||
|
||
## 快速开始
|
||
|
||
1. 复制环境变量文件:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
2. 编辑 `.env`:
|
||
|
||
- `PUBLIC_PATH` 改成足够长的随机字符串
|
||
- `PUBLIC_BASE_URL` 建议填写你反代后的最终访问地址,例如 `https://sub.example.com`
|
||
- 如果上游订阅地址对默认 UA 返回 `406`,可在 `.env` 里设置 `UPSTREAM_USER_AGENT`,填成你客户端实际使用的 UA,例如 Clash Party 抓包出来的值
|
||
- `AIRPORT_A_URL` / `AIRPORT_B_URL` / `AIRPORT_C_URL` 都可以直接填订阅地址,项目会自动判断是 YAML 还是 URI 订阅
|
||
- 也可以直接填本地文件路径,例如 `/app/data/sources/airport-b.txt` 或 `file:///app/data/sources/airport-b.txt`
|
||
- 允许把其中一个留空;留空时这个机场会自动跳过
|
||
|
||
3. 启动:
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
4. 访问检查:
|
||
|
||
- 健康检查:`http://YOUR_HOST:18080/healthz`
|
||
- 单 provider:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/providers/airport-a.yaml`
|
||
- merged provider:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/providers/merged.yaml?sources=airport-a,airport-b,airport-c`
|
||
- Mihomo/OpenClash 薄壳入口:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/clients/mihomo.yaml?sources=airport-a,airport-b,airport-c`
|
||
- Stash 薄壳入口:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/clients/stash.yaml?sources=airport-a,airport-b,airport-c`
|
||
- Conf 薄壳入口:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/conf/clients/mihomo/default.yaml`
|
||
- Mihomo/OpenClash bundle:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/bundle/mihomo.yaml?sources=airport-a,airport-b,airport-c`
|
||
- Stash bundle:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/bundle/stash.yaml?sources=airport-a,airport-b,airport-c`
|
||
- Conf bundle:
|
||
`https://YOUR_DOMAIN/<PUBLIC_PATH>/conf/bundle/mihomo/default.yaml`
|
||
|
||
---
|
||
|
||
## 接口说明
|
||
|
||
### 1. 单 provider
|
||
|
||
```text
|
||
GET /<PUBLIC_PATH>/providers/{name}.yaml
|
||
HEAD /<PUBLIC_PATH>/providers/{name}.yaml
|
||
```
|
||
|
||
返回指定机场源的 provider 文件,并携带这个源的 `Subscription-Userinfo`(如果上游有)。
|
||
|
||
### 2. merged provider
|
||
|
||
```text
|
||
GET /<PUBLIC_PATH>/providers/merged.yaml?sources=airport-a,airport-b,airport-c
|
||
HEAD /<PUBLIC_PATH>/providers/merged.yaml?sources=airport-a,airport-b,airport-c
|
||
```
|
||
|
||
把多个 provider 合并成一个 `proxies:` 文件返回。响应头只取 `sources` 参数里**第一个源**的配额信息。
|
||
|
||
### 3. 薄壳客户端配置
|
||
|
||
```text
|
||
GET /<PUBLIC_PATH>/clients/mihomo.yaml?sources=airport-a,airport-b,airport-c
|
||
GET /<PUBLIC_PATH>/clients/stash.yaml?sources=airport-a,airport-b,airport-c
|
||
HEAD /<PUBLIC_PATH>/clients/mihomo.yaml?sources=airport-a,airport-b,airport-c
|
||
HEAD /<PUBLIC_PATH>/clients/stash.yaml?sources=airport-a,airport-b,airport-c
|
||
```
|
||
|
||
特点:
|
||
|
||
- 客户端收到的是轻量入口配置
|
||
- 节点更新依赖远程 `proxy-providers`
|
||
- 规则更新依赖远程 `rule-providers`
|
||
- 响应头同样只取第一个源的 `Subscription-Userinfo`
|
||
|
||
### 4. bundle 单文件配置
|
||
|
||
```text
|
||
GET /<PUBLIC_PATH>/bundle/mihomo.yaml?sources=airport-a,airport-b,airport-c
|
||
GET /<PUBLIC_PATH>/bundle/stash.yaml?sources=airport-a,airport-b,airport-c
|
||
HEAD /<PUBLIC_PATH>/bundle/mihomo.yaml?sources=airport-a,airport-b,airport-c
|
||
HEAD /<PUBLIC_PATH>/bundle/stash.yaml?sources=airport-a,airport-b,airport-c
|
||
```
|
||
|
||
特点:
|
||
|
||
- 服务端把节点、策略组、规则全部展开到一个 YAML 里
|
||
- 适合想直接给 Mihomo Party / Clash Party / Stash 一个最终链接的场景
|
||
- 响应头同样只取第一个源的 `Subscription-Userinfo`
|
||
- 生成后的完整 YAML 会缓存到 `output/bundle-cache/`,默认 600 秒内直接返回缓存
|
||
- 可用 `force_refresh=true` 强制跳过缓存并覆盖旧缓存文件
|
||
- 上游订阅原始内容也会按 TTL 落盘缓存到 `data/fetch-cache/`,默认 900 秒
|
||
- 如果你想固定使用本地文件订阅,可以把文件放到 `data/sources/`,再把 `AIRPORT_*_URL` 指向 `/app/data/sources/xxx.txt`
|
||
|
||
### 5. Conf 配置入口
|
||
|
||
```text
|
||
GET /<PUBLIC_PATH>/conf/clients/{client_type}/{profile_key}.yaml
|
||
GET /<PUBLIC_PATH>/conf/bundle/{client_type}/{profile_key}.yaml
|
||
GET /<PUBLIC_PATH>/conf/providers/{profile_key}/{name}.yaml
|
||
GET /<PUBLIC_PATH>/conf/rules/{profile_key}/{name}.yaml
|
||
```
|
||
|
||
特点:
|
||
|
||
- 旧 `sources.yaml` 路由保持不变,conf 路由独立存在
|
||
- 新链路读取 `config/conf/base.conf` 和 `config/conf/profiles/*.conf`
|
||
- 可以并行对比旧输出和 conf 输出,不影响日常使用
|
||
- conf 路由按请求懒加载配置;conf 写坏时不会影响旧接口启动
|
||
|
||
---
|
||
|
||
## Conf 配置说明
|
||
|
||
当前已落地一套 ACL4SSR-like 的 `.conf` 配置链路:
|
||
|
||
- `config/conf/base.conf`
|
||
负责 `source`、`selector`、`group`、`module`、`builtin` 等全局定义
|
||
- `config/conf/profiles/*.conf`
|
||
负责 `sources`、`include_modules`、`include_builtins`
|
||
以及 `override_policy`、`prepend_rule`、`append_rule`
|
||
|
||
这条链路目前的目标不是替换旧接口,而是:
|
||
|
||
- 保持旧 YAML 配置和旧路由继续可用
|
||
- 用新 conf 路由做并行验证
|
||
- 逐步把输出收敛到和旧链路一致
|
||
|
||
### 示例
|
||
|
||
`base.conf` 里一条规则模块:
|
||
|
||
```ini
|
||
module = apple,../rules/acl4ssr/Apple.yaml,🍎 苹果服务,80,true
|
||
```
|
||
|
||
`default.conf` 里启用模块:
|
||
|
||
```ini
|
||
include_modules = apple,openai
|
||
include_builtins = geoip_cn,final
|
||
```
|
||
|
||
---
|
||
|
||
## 对比脚本
|
||
|
||
项目内置了一个本地对比脚本:
|
||
|
||
```bash
|
||
python scripts/compare_conf_outputs.py
|
||
```
|
||
|
||
它会:
|
||
|
||
- 启动本地 `TestClient`
|
||
- 同时请求旧路由和 conf 路由
|
||
- 输出 `proxy-providers`、`rule-providers`、`proxy-groups`、`rules` 的差异摘要
|
||
|
||
如果要指定样例源文件:
|
||
|
||
```bash
|
||
python scripts/compare_conf_outputs.py --sample-source C:\path\to\sample.yaml
|
||
```
|
||
|
||
---
|
||
|
||
## 默认策略结构
|
||
|
||
当前默认生成的策略组是一个基础版:
|
||
|
||
- `☁️ 机场选择`
|
||
- `♻️ 自动选择`
|
||
- `🚀 手动切换`
|
||
- `🇭🇰 香港自动`
|
||
- `🇸🇬 新加坡自动`
|
||
- `🇯🇵 日本自动`
|
||
- `🇺🇸 美国自动`
|
||
- `节点选择`
|
||
|
||
其中:
|
||
|
||
- `☁️ 机场选择` 允许在“混合自动”和各机场单独自动组之间切换
|
||
- `节点选择` 是最终主策略组
|
||
- bundle 模式会把节点名全部展开
|
||
- thin 模式会保留 provider 引用关系
|
||
|
||
你后面要继续进阶的话,最值得加的是:
|
||
|
||
- `policies.yaml`:把 Telegram / AI / YouTube / Netflix 这类业务组模板化
|
||
- `regions.yaml`:把更多地区从 `sources.yaml` 独立出去
|
||
- URI/base64 原始订阅解析
|
||
- 鉴权层(例如前置 Caddy/Nginx Basic Auth 或仅 Tailscale 可访问)
|
||
|
||
---
|
||
|
||
## 配额头策略
|
||
|
||
为了避免聚合多个机场后“总流量怎么显示”语义混乱,这个版本统一采用:
|
||
|
||
- `sources` 只填一个机场源:返回这个机场源的配额信息
|
||
- `sources` 填多个机场源:**只取第一个**机场源的 `Subscription-Userinfo`
|
||
|
||
这样 Stash、Clash Party 这类客户端读取配置订阅头时,行为是稳定可预期的。
|
||
|
||
---
|
||
|
||
## 上游 `406` 处理
|
||
|
||
上游订阅抓取默认使用 `sub-provider/0.2` 作为 `User-Agent`。如果机场对这个 UA 返回 `406 Not Acceptable`,可以直接在 `.env` 里覆盖:
|
||
|
||
```env
|
||
UPSTREAM_USER_AGENT=Clash Party/<你的版本号或抓包值>
|
||
```
|
||
|
||
代码里也兼容旧变量名 `DEFAULT_USER_AGENT`,但建议后续统一用 `UPSTREAM_USER_AGENT`。
|