thurgio
thurgio 是一个代理配置管理工具——一个现代化的、基于 Rust 构建的 surgio 替代品。它可以聚合多个代理源,应用过滤和转换规则,并将结果渲染为 Clash、Surge、sing-box、V2RayN、SIP002 及其他代理客户端格式的输出文件。
主要特性
- 多源聚合 — 从 Clash、Clash.Meta、V2RayN、SIP002、Surge、QX、Loon、SIP008、SSD、Telegram 或本地文件获取代理(
ruleset.yaml远程规则集自动物化)。 - 协议支持 — 25 种代理协议(SS、SSR、VMess、VLESS、Trojan、Hysteria(2)、TUIC、WireGuard、SOCKS5、HTTP、Snell、AnyTLS、ShadowTLS、Juicity、Naive、Ssh、Mieru 等)。
- 过滤与重命名 — 关键字(前缀/后缀/包含/列表)、正则和代理类型过滤器 +
filter/rename模板过滤器(_rules上下文)+ruleset命名规则集;支持正则反向引用的重命名规则。 - 表情模式 — 根据检测到的国家/地区自动添加旗帜表情前缀,可全局或按订阅源配置(启用/禁用/去除)。
- 模板引擎 — Tera 模板(Jinja2/Django 语法),30+ 内置过滤器(
clash/surge/qx/loon/mellow/mixed/sibox/stash/clashr/js/sort_nodes/clash_groups/surge_groups/ruleset等)+get_env/now自定义函数;tpl=auto按 User-Agent 自适应输出。 - 热重载 — 配置文件(
thurgio.yaml+ 5 个 sibling)和模板目录变更时自动重新构建;ruleset远程 URL 条目更新管线自动拉取。 - HTTP API — 内置 Axum 服务器,支持按作用域(Read/Admin)划分的令牌认证、provider/snippet 渲染(
tpl=auto)、订阅商店/健康/流量元数据/延迟/UDP/GEO/解锁探测、SSE 构建通知和 Prometheus 指标。 - 安全性 — 作用域令牌、可选的 TLS、IP 速率限制、并发限制、CORS 配置(热重载)。
- 自包含 — 单一二进制文件,使用嵌入式 SQLite 数据库持久化(无需外部数据库服务器,WAL 多进程共享)。
快速开始
cargo install thurgio
thurgio init
thurgio build
详细说明请参阅快速开始指南。
链接
- 源代码: github.com/thurgio/thurgio
- Telegram: t.me/thurgio
- 发布版本: github.com/thurgio/thurgio/releases
- crates.io: crates.io/crates/thurgio
致谢
快速开始
从零开始运行 Thurgio 的教程。
前提条件
- 选项 A — 预编译二进制:从 GitHub Releases 页面下载适用于你平台的最新版本。
- 选项 B — 从源码构建:Rust 1.85+(2024 edition)。
安装
从源码安装(cargo)
从本地仓库安装(开发构建):
cargo install --path crates/thurgio-cli
或从 crates.io 安装最新发布版本:
cargo install thurgio
两种命令都会编译 thurgio 二进制文件并将其放置到 ~/.cargo/bin/。
从预编译二进制安装
- 从 Releases 页面下载适用于你操作系统/架构的压缩包。
- 解压并将
thurgio二进制文件放到$PATH中的某个目录(例如/usr/local/bin)。
验证
thurgio --version
快速初始化
使用一条命令生成最小可用配置:
thurgio init
这会在当前目录创建以下文件:
thurgio.yaml— 主配置(setting)provider.yaml/filter.yaml/rename.yaml/params.yaml/snippet.yaml— 各配置节(均可留空,按需填充)tpls/base.tpl与tpls/ui.tpl— 示例模板与 UI 模板
也可以指定目标目录或强制覆盖:
thurgio init --dir /path/to/dir
thurgio init --force # 覆盖已存在的文件
验证生成的配置:
thurgio check
你会看到配置是否有效、提供者/片段的数量、令牌总数、监听地址/端口、TTL,以及每个提供者来源(URL 格式、本地文件是否存在)和输出目录的检查结果。
你的第一个配置文件
创建工作目录和一个最小化的 thurgio.yaml:
mkdir ~/my-thurgio && cd ~/my-thurgio
# thurgio.yaml
setting:
tokens:
admin:
- "my-secret-token"
output:
provider:
"proxies.tpl":
- "output/proxies.yaml"
# provider.yaml
my_provider:
name: my_provider
type: clash
source: "https://example.com/proxy-config"
template: proxies.tpl
filename:
- output/proxies.yaml
该配置:
- 从 Clash 兼容的 URL 加载代理数据。
- 使用模板
tpls/proxies.tpl渲染结果。 - 将最终输出写入
output/proxies.yaml。
注意:
setting使用tokens配置(按作用域分组,至少配置一个)。旧的单值token字段已移除。
创建模板
mkdir tpls
{# tpls/proxies.tpl #}
# Generated by Thurgio — {{ now() | date(format="%Y-%m-%d %H:%M:%S") }}
proxies:
{%- for proxy in l.proxies %}
- {{ proxy | json_str(pretty=false) }}
{%- endfor %}
模板上下文提供以下变量:
l.proxies— provider 的代理对象列表。l.name— provider 名称。content— 片段内容(仅在 snippet 模板中)。p.*— 自定义参数(参见配置指南)。config— 完整配置对象(config.snippet、config.params等)。headers— 请求头映射(HTTP API 渲染时)。version— 版本字符串。now()— 当前时间戳(Tera 自定义函数)。get_env(name="VAR", default="...")— 读取环境变量(Tera 自定义函数)。
可用过滤器和模板变量的完整列表请参阅配置指南。
构建
获取订阅源数据并渲染产物:
thurgio build
成功后,输出文件 output/proxies.yaml 将包含渲染后的代理配置。
Thurgio 会自动在工作目录中创建 SQLite 数据库(thurgio.db,可用 DATABASE_URL 环境变量覆盖路径)来缓存订阅源响应。无需外部数据库服务器。
强制更新所有订阅源(忽略缓存状态):
thurgio build --force
构建完成时发送系统通知(Linux 桌面,依赖 notify-send):
thurgio build --notify
监听模式
当源文件变更时自动重新构建:
thurgio build --watch
进程将持续运行,在当前目录(递归)发生任何修改时触发新的构建。按 Ctrl+C 停止。
启动服务
启动 HTTP 服务器按需提供配置:
thurgio serve
服务器默认监听 0.0.0.0:10100。所有需要认证的端点位于 /app/ 命名空间下,令牌通过 ?token= 查询参数或 Authorization: Bearer 请求头传递。
测试 API
# 健康检查
curl http://localhost:10100/health
# 获取渲染后的 provider 配置(需要令牌)
curl "http://localhost:10100/app/read/l?name=my_provider&token=my-secret-token"
# 通过 Server-Sent Events 实时获取构建通知
curl -N "http://localhost:10100/app/read/events?token=my-secret-token"
SSE 在每次构建完成时推送
build_complete事件,支持实时更新的仪表盘或代理配置。
使用自定义设置启动服务
# 自定义地址和端口
thurgio serve --address 127.0.0.1 --port 8080
# 开发模式(每次请求重新加载模板)
thurgio serve --dev
# 空闲模式(30 秒无请求后自动退出,配合 systemd Restart=always 按需拉起)
thurgio serve --idle
# 空闲时清空数据缓存(默认开启;可显式关闭:--evict=false)
thurgio serve --evict
# 启用 TLS(同时提供明文 HTTP 时需指定不同端口)
thurgio serve --tls-port 8443 --tls-cert cert.pem --tls-cert-key key.pem
# 仅提供 HTTPS
thurgio serve --tls-only --tls-port 8443 --tls-cert cert.pem --tls-cert-key key.pem
# 启动时打开浏览器
thurgio serve --open
检查更新
thurgio upgrade
查询 GitHub Releases 检查是否有新版本(仅提示,不会自动安装)。
Shell 自动补全
为 bash、zsh 或 fish 生成 shell 自动补全脚本:
# Bash
thurgio generate --shell bash > /etc/bash_completion.d/thurgio
# Zsh
thurgio generate --shell zsh > /usr/local/share/zsh/site-functions/_thurgio
# Fish
thurgio generate --shell fish > ~/.config/fish/completions/thurgio.fish
也可以使用 -o/--output 直接写入文件。
下一步
- 配置指南 — 添加多个订阅源、过滤器、表情模式和 TLS 配置。
- 部署指南 — systemd 服务、Docker、反向代理和生产环境检查清单。
- API 参考 — 完整的 HTTP API 端点参考。
- 配置参考 — 所有配置节的字段说明。
架构
概述
Thurgio 组织为 23 个 crate(thurgio-bench 为仅开发用的基准 crate,fuzz 为 workspace 成员,共 23 crates/* + fuzz),分布在 5 层中,具有严格的依赖方向。上层依赖于下层的抽象;下层从不依赖上层。
关于详细的接口合约和 ADR,请参阅 design/architecture-detailed.md 和 design/technical-decision.md。
接口层 (thurgio-cli, thurgio-server)
↓ (仅通过 app facade)
应用层 (thurgio-app, thurgio-app-update, thurgio-app-build, thurgio-app-state, thurgio-event)
↓
数据访问层 (thurgio-db)
↓
领域层 (thurgio-domain, thurgio-proxy, thurgio-proxy-singbox, thurgio-formats,
thurgio-filter, thurgio-renderer, thurgio-renderer-api, thurgio-script,
thurgio-config, thurgio-config-api)
↓
基础设施层 (thurgio-types, thurgio-infra, thurgio-request, thurgio-test-utils)
基础设施层
thurgio-types— 基础类型(Ar<T>、Rw<T>、Fields、ThurgioError+ErrorKind、Udp、UpdateState)与包身份标识(identity模块:NAME、VERSION、GIT_BUILD、get_version(),由build.rs嵌入git describe快照)。thurgio-infra— 日志初始化(init_logger)、请求追踪(request_tracing)、HTTP 客户端工厂(create_client)、熔断器(CircuitBreaker/CircuitBreakerPool)、指标抽象(MetricsRecorder/NoopMetrics)、set_root_dir工作目录管理。thurgio-request— HTTP 响应包装(Res)、来源抽象(Source:File/Url)、AppHttpClienttrait。thurgio-event— 进程内部事件总线(EventBus、DomainEvent、EventHandler、Envelope)。使用tokio::sync::broadcast,慢订阅者会丢失事件。thurgio-test-utils— 集成测试工具(测试应用工厂、测试配置构建器)。
领域层
thurgio-domain— 核心业务类型:Provider/RawProvider、Snippet、Output/RawOutput(provider 具名输出),以及更新生命周期(UpdateState、Cache/Lifecycle/Parsabletrait,proxies_version版本化 +cached_json缓存)。thurgio-proxy— 25 种代理协议类型(SS、VMess、VLESS、Trojan、Hysteria(2)、TUIC、AnyTLS、ShadowTLS、Juicity、Naive、Ssh、Mieru 等),支持解析(ConfType:clash/meta/v2rayn/sip002/surge/qx/loon/singbox/sip008/ssd/telegram)和序列化;ProxyFormat<F>trait(markerV2rayN/Sip002/Surge/Qx/Loon)提供格式解析分发,formats.rs能力表为单一事实源。全部协议无条件编译。thurgio-formats— 共享行解析辅助(Surge/QX/Loon 行解析抽离,供 proxy 与 renderer 复用)。thurgio-proxy-singbox— Sing-box 特定的代理类型转换(CoreSingBox、ToSingBoxtrait,SProxyTy::supports单一能力源)。thurgio-filter— 代理过滤(Filter/RawFilter/Mode)、重命名(Rename)、emoji 模式(EmojiMap,aho-corasick)与规则集(RuleSet/RuleFormat,assets/rulesets/*.list内置表)。thurgio-script— 内联 JS 引擎(ScriptEngine::transform,boa_engine,js过滤器薄包装)。thurgio-renderer-api— 轻量级 API 合约:RenderTarget/Rendertrait +context::base_context/insert单一装配入口。零 Tera 依赖。thurgio-renderer— Tera 模板渲染:30+ 内置过滤器(general+proxy/qx/qx_config/loon/loon_config/mellow/mixed/stash/surfboard/clashr/singbox+rules/ruleset/sort_nodes/groups/js)+get_env/now;渲染线程池(Competing Consumers,EMA 平滑 backlog + 滞回控制 + FreeList,min=1 / max=64)。tpl=auto在 serverua.rs完成。thurgio-config— 配置加载、校验(模板存在性、来源 URL 格式、Setting::validate)与热重载(notify文件监听 +ArcSwap<T>无锁切换)。拆分读取provider.yaml/params.yaml/rename.yaml/filter.yaml/snippet.yaml/ruleset.yaml(后者支持http(s)://远程 URL 条目,更新管线物化)。thurgio-config-api— 轻量纯配置类型(Setting、Ttl、TokenScope、Tokens、EmojiMode、AdhocTplSetting),零领域/代理/过滤依赖。
数据访问层
thurgio-db— 基于 SQLite(sqlx,WAL 模式)的嵌入式持久化:KvStore(键值存储)、EntityStore/SqliteEntityStore(类型化实体存储)、Stored(领域对象 ↔ 存储模型关联)。
应用层
thurgio-app-state— 中央应用状态AppState:持有 SQLite 数据库连接、HTTP 客户端、渲染器、事件总线、熔断器池、构建/更新双锁(build_lock+update_lock,ADR-055)、data_generation(缓存降级用)、AdhocStore(临时资源内存+DB 缓存)与 dev/idle/evict 标志;render_context(build_render_context/build_base_context,ADR-057)为渲染装配单一入口。AppConfig为Ar<Config>新类型包装。thurgio-app— 外观(facade,分组 re-export:update/render/adhoc/config/admin,ADR-046):initialize三步初始化(Source → AppState → 熔断器恢复 → 事件处理器注册)。接口层 crate 仅经分组路径访问下层。thurgio-app-update— 更新管道编排(refresh_and_record统一入口,ADR-013;UpdateItemsStage<T: Stored>无界 fan-out,ADR-073;ensure_ready/refresh_in_background/refresh_and_wait_for分流,ADR-049/072;record私有ItemKind分发,ADR-032)。thurgio-app-build— 构建管道编排(build:update+record → 渲染 → 原子发布StagingDir,ADR-047)、渲染管道(RenderItemsStage,provider 主+具名输出平铺)、事件处理器(AutoBuildHandler/LoggingHandler/WebhookHandler/TelegramHandler)、模板目录监听(init_template_watcher)、并发任务工具(task::run_concurrent/try_join)。
接口层
thurgio-cli— CLI 二进制入口点(build/serve/init/check/diff nodes|file/config backup/restore/list/upgrade/generate/version,spawn_config_watcher统一监听thurgio.yaml+ 6 siblings +tpls/)。依赖thurgio-app外观 +thurgio-proxy::diff(节点级 diff)。thurgio-server— 基于 Axum 的 HTTP 服务器(build_router导出,12 read + 10 admin 端点,tpl=autoUA 自适应,SSE 事件流,限流/CORS/指标/认证中间件,静态资源include_str!嵌入)。
统一错误类型
ThurgioError(位于 thurgio-types)使用 thiserror 派生枚举,配套 ErrorKind 分类(Copy,用于状态码映射与重试决策):
ErrorKind | 含义 | HTTP | 可重试 |
|---|---|---|---|
Network | 网络请求失败(reqwest::Error) | 502 | ✅ |
Timeout | 请求超时 | 502 | ✅ |
Persistence | 数据库错误 | 500 | ❌ |
Parse | 数据解析失败 | 422 | ❌ |
Config | 配置错误 | 422 | ❌ |
Render | 模板渲染失败 | 500 | ❌ |
NotFound | 资源未找到 | 404 | ❌ |
Validation | 字段验证失败 | 422 | ❌ |
Unauthorized | 认证失败 | 401 | ❌ |
CircuitOpen | 熔断器打开 | 503 | ❌ |
Canceled | 操作取消 | 499 | ❌ |
Internal | 内部错误(anyhow::Error) | 500 | ❌ |
trace_id 通过外部包装 TracedError 携带,不嵌入 ThurgioError。详见 design/error-handling.md。
事件系统
事件总线实现解耦通信:
- 7 种事件:
RebuildRequested、ProviderUpdated、SnippetUpdated、BuildCompleted、RendererReloaded、ConfigReloaded(配置全量重解析并原子替换,ADR-048)、ServerEvent - 3 个默认订阅者:
AutoBuildHandler(RebuildRequested → 强制重建,仅重跑更新+渲染管线、不重载配置)、LoggingHandler(所有事件 → INFO/WARN)、WebhookHandler(BuildCompleted → 向THURGIO_WEBHOOK_URLPOST JSON,未设置时为空操作)
管道抽象
更新与渲染管道由阶段(stage)构成:
- 更新管道(
thurgio-app-update):UpdateItemsStage(providers)→UpdateItemsStage(snippets) - 渲染管道(
thurgio-app-build):providers(主输出 + 具名输出)与 snippets 两个阶段并发执行(共享不可变上下文,无共享可变状态)
特性门控
thurgio-proxy 不再按协议门控(25 种协议全部无条件编译),full/filters 拆分已移除(ADR-064),thurgio-domain 的 update/render-impl/db-impl 门控亦移除:
| 特性 | 说明 |
|---|---|
test-utils | 测试示例构造器(开发/测试用,仅 thurgio-proxy/thurgio-request 定义,thurgio-test-utils 消费 16 例) |
thurgio-cli 另有两个互斥的 TLS 后端特性:rustls-tls(默认)与 native-tls,通过 thurgio-app → thurgio-infra 转发到 reqwest。
依赖关系图
thurgio-cli ──> thurgio-app (facade)
├── thurgio-app-state ──> thurgio-config ──> thurgio-config-api
│ ├── thurgio-domain ──> thurgio-filter / thurgio-proxy / thurgio-proxy-singbox
│ ├── thurgio-renderer ──> thurgio-renderer-api
│ ├── thurgio-db / thurgio-request / thurgio-infra / thurgio-types
├── thurgio-app-update ──> thurgio-db / thurgio-infra / thurgio-request
├── thurgio-app-build ──> thurgio-renderer / thurgio-event
└── thurgio-event
thurgio-server ──> thurgio-app (facade)
关键 Trait
| Trait | Crate | 用途 |
|---|---|---|
Stored, EntityStore, SqliteEntityStore | thurgio-db | 存储模型关联 / 类型化实体存取 |
KvStore | thurgio-db | 键值持久化(SQLite) |
Proxy, ConfType, ProxyFormat | thurgio-proxy | 代理类型定义与格式解析分发(marker V2rayN/Sip002/Surge) |
CoreSingBox, ToSingBox | thurgio-proxy-singbox | Sing-box 转换 |
EventHandler, EventBus | thurgio-event | 事件订阅与分发 |
Render, RenderTarget | thurgio-renderer-api | 渲染数据合约(无 Tera 依赖) |
Renderable, Render, AppRenderEngine | thurgio-renderer | 模板渲染执行 |
设计原则
- 单一职责 — 每个 crate 有一个清晰的用途。
- 依赖反转 — 上层依赖于抽象。
- 关注点分离 — 各层干净分离。
- 外观模式 — 接口层 crate 通过
thurgio-app的重新导出访问下层。
配置指南
Thurgio 常见用例的实际配置模式。
最小工作配置
每个 Thurgio 设置至少需要一个 provider(提供者),在 provider.yaml 中定义获取、处理与渲染所需的一切:
# thurgio.yaml
setting:
tokens:
admin:
- "your-api-token" # 至少配置一个令牌
output:
provider:
"clash_list.tpl":
- "output/proxies.yaml" # 默认输出路径(按模板名映射,详见配置参考)
# provider.yaml
example:
name: example
type: clash
source: "https://example.com/clash-config"
template: clash_list.tpl
filename:
- output/proxies.yaml
管道流程为:Provider → 获取 → 解析代理 → pre_rename → filter → rename → 使用模板渲染 → 写入输出。
添加多个提供者
你可以从多个来源聚合代理:
# provider.yaml
vps_provider:
name: vps_provider
type: clash
source: "https://example.com/clash-config"
template: clash.tpl
filename:
- output/vps.yaml
rename:
- "add_vps_prefix"
free_provider:
name: free_provider
type: sip002
source: "https://example.com/sip002.txt"
template: sip002.tpl
filename:
- output/free.txt
local_backup:
name: local_backup
type: v2rayn
source: "backup/v2rayn.json"
template: v2rayn.tpl
filename:
- output/backup.txt
支持的提供者类型(provider.type):
| 类型 | 描述 |
|---|---|
clash | Clash YAML 格式 |
meta | Clash.Meta YAML 格式 |
v2rayn | V2RayN 分享链接格式(Base64 JSON 数组) |
sip002 | SIP002 URI 格式(逐行 ss:// 等) |
sip008 | SIP008 JSON 订阅格式(Shadowsocks Android,version+servers) |
surge | Surge 配置格式(解析 [Proxy] 段) |
qx | Quantumult X 节点行(shadowsocks=/vmess= 等 7 种) |
loon | Loon 节点行(10 种) |
ssd | SSD 订阅格式(ssd:// Base64 JSON 文档) |
singbox | sing-box 配置 JSON(解析 outbounds 数组,基础字段,含 wireguard/naive/ssh) |
telegram | TG 类 HTTP/SOCKS5 代理链接(tg://http / tg://socks / t.me/* → Http/Socks5) |
auto | 仅渲染侧 tpl=auto 的辅助值(provider type 仍需显式指定真实输入格式;auto 由服务端 UA 探测选模板) |
每个 provider 独立渲染自己的输出;同一个提供者可以通过 outputs 定义多个具名输出,用不同模板渲染同一份代理列表:
# provider.yaml
all_proxies:
name: all_proxies
type: clash
source: "https://example.com/clash-config"
template: clash.tpl
filename:
- output/clash.yaml
rename:
- "clean_names"
outputs:
v2rayn:
template: v2rayn.tpl
filename:
- output/v2rayn.txt
过滤代理
过滤规则按名称保留或剔除代理。在 filter.yaml 中定义过滤规则(声明式语法,详见配置参考):
filter:
# 简写:包含任一关键字的代理(忽略大小写)
only_uk_us: "英国|美国|UK|US|London|New York"
# 仅保留 ss 类型
ss_only:
- type: ss
# 正则
hk:
- regex: '\W+(US|us|United States)\W+'
在提供者中引用过滤规则:
# provider.yaml
my_list:
name: my_list
type: clash
source: "https://example.com/proxy-config"
template: clash.tpl
filter:
- "only_uk_us"
- "ss_only" # 多个过滤规则依次应用
匹配器
| 匹配器 | 值 | 匹配条件… |
|---|---|---|
contain | 字符串或字符串数组 | 名称包含任一关键字(数组 = OR;默认忽略大小写) |
start | 字符串 | 名称以关键字开头 |
end | 字符串 | 名称以关键字结尾 |
type | 字符串 | 代理类型(ss、vmess 等) |
regex | 字符串 | 代理名称匹配正则表达式 |
共享选项:ignore_case(缺省 true)、negate(缺省 false,命中则剔除)。列表(AND)仅支持 contain/start/end。
重命名代理
重命名规则使用正则替换(支持反向引用)清理或标准化代理名称:
rename:
add_vps_prefix:
- "^(.*)$"
- "VPS-$1"
clean_names:
- "(美国)"
- "🇺🇸 US"
- "(日本)"
- "🇯🇵 JP"
- "带宽.*"
- "Standard"
room_number:
- "(\\d+)-(\\d+)"
- "$1-$2" # 支持正则反向引用
在提供者中应用(pre_rename 在过滤前、rename 在过滤后):
# provider.yaml
my_provider:
name: my_provider
type: clash
source: "https://..."
template: clash.tpl
pre_rename: # 在过滤前应用(解析后立即执行)
- "add_vps_prefix"
rename: # 在过滤后应用
- "clean_names"
- "room_number"
每条规则是 [正则, 替换串] 列表,对代理名称执行 replace_all。
使用 Emoji 模式
Emoji 模式根据检测到的国家/地区自动在代理名称前添加旗帜 emoji。
全局设置
setting:
mode: Enable # 全局启用 emoji(默认值)
其他可选值:
| 值 | 效果 |
|---|---|
Disable | 不添加 emoji 前缀 |
Enable | 检测到国家/地区时添加旗帜 emoji 前缀(默认) |
Hide | 去除代理名称中已有的 emoji 前缀 |
按提供者覆盖
为特定提供者覆盖全局设置:
# provider.yaml
my_provider:
name: my_provider
type: clash
source: "https://..."
template: clash.tpl
emoji: false # 仅对此提供者禁用 emoji(默认 true)
模板示例
模板使用 Tera 引擎(语法类似 Jinja2/Django)。
上下文变量
| 变量 | 可用范围 | 描述 |
|---|---|---|
l.proxies | provider、具名输出 | 代理对象列表 |
l.name | provider、具名输出 | 当前 provider 的名称 |
l.rename | provider、具名输出 | 当前 provider 的重命名规则(键名) |
l.filter | provider、具名输出 | 当前 provider 的过滤规则(键名) |
s.response.content | snippet | 当前 snippet 的源内容(原样文本);adhoc snippet 绑定为 content |
p.* | 全部 | 来自配置的自定义参数(params.yaml + /app/admin/p 热更新) |
hp.* | 全部 | 本次 HTTP 请求的查询参数(与 p 分离;无请求参数时为 {}) |
config | 全部 | 完整配置对象(config.snippet、config.params、config.setting 等) |
headers | 全部 | 请求头映射(HTTP API 渲染时) |
version | 全部 | 版本字符串 |
now() | 全部 | 当前时间戳(Tera 函数) |
get_env(name=..., default=...) | 全部 | 读取环境变量(Tera 函数) |
自定义过滤器(30+,register_all 统一注册)
| 过滤器 | 描述 |
|---|---|
slugify(separator="-") | 字符串 slug 化 |
urlencode() | URL 编码 |
indent(width=2, first=false, blank=false) | 缩进内容 |
json() / yaml() | 将字符串解析为 JSON/YAML 值 |
json_str(pretty=true) | 将代理序列化为紧凑或美观的 JSON |
yaml_str() | 将代理列表序列化为 YAML |
to_yaml_vec() | 逐行转为 YAML 列表 |
clear(num=3, n=2) | 压缩连续空行 |
uniq() | 按行去重 |
base64(mode="encode", config=...) | Base64 编码/解码 |
clash(symbol=..., policy=...) | 格式化为 Clash 代理条目(单条规则文本输入) |
cla(meta=true) | Clash 代理条目(数组输入,V2rayN/Sip002 已按能力谓词过滤) |
surge(symbol=..., policy=...) | 格式化为 Surge 代理条目(单条规则文本输入) |
surge_proxy() | 代理数组 → Surge [Proxy] 段行(ss/trojan/vmess/snell/http/socks5/wireguard 8 种) |
qx() | 代理数组 → Quantumult X 节点行(ss/ssr/vmess/vless/trojan/http/socks5 7 种) |
qx_config(check_url=, dns=, group=) | 代理数组 → 完整 Quantumult X 配置四段([general]/[dns]/[server_local]/[policy]) |
loon() | 代理数组 → Loon 节点行(10 种) |
loon_config(dns=, group=) | 代理数组 → 完整 Loon 配置四段([General]/[Proxy]/[Proxy Group]/[Rule]) |
mellow(port=1080) | 代理数组 → Mellow/V2Ray-core JSON(socks inbound + per-proxy outbounds,ss/vmess/vless/trojan/http/socks5 + TCP/WS/TLS) |
mixed() | 代理数组 → 混合单链接列表(V2RayN 优先、SIP002 回退逐节点分发) |
stash() | 代理数组 → Stash(Clash 兼容)节点行 |
surfboard() | 代理数组 → Surfboard(Surge 兼容)节点行 |
clashr() | 代理数组 → ClashR 节点行 |
ssd() | 代理数组 → ssd:// 订阅(仅 ss) |
sip008() | 代理数组 → SIP008 JSON 订阅(仅 ss) |
shadowrocket() | 代理数组 → Shadowrocket 分享链接(复用 sip002/v2rayn,wg 跳过) |
quantumult(symbol=..., policy=...) | 格式化为 Quantumult X 规则集(host/host-suffix/host-keyword/ip-cidr/ip6-cidr/geoip/user-agent/final;MATCH→final) |
singbox(pretty=true, policy=...) | 格式化为 sing-box 代理条目(单条规则文本输入) |
sibox() / singbox() | 代理数组 → sing-box outbound 对象数组(12 变体,TryFrom<ProxyTy> 单向) |
clashbox(premium=..., meta=...) | clash 规则文本 → sing-box rule 对象数组 |
v2rayn() | 代理数组 → V2RayN 分享链接(V2RAYN_CAPABILITIES 5 种仅元类型) |
sip002() | 代理数组 → SIP002 URL(除 WireGuard 外全部) |
proxy_suffix(suffix=...) | 在代理名称后添加协议后缀 |
js(src="...") | 代理数组 → 内联 JS 变换($input 全局,boa_engine,须显式 return) |
sort_nodes(by=name/region/latency, order=asc/desc, latencies=...) | 代理数组 → 按 name/region/latency 排序 |
clash_groups / surge_groups | 代理数组 → Clash/Surge 策略组(select + 按地区 url-test) |
ruleset(name=, format=clash/surge, text=) | 命名规则集格式化(内联 text > _rules.ruleset > 内置 assets/rulesets/*.list) |
no_resolve(symbol=..., value=...) | 标记带 no-resolve 的代理 |
sort_domain_ip(symbol=...) | 按域名/IP 排序代理规则 |
rule(symbol=..., policy=...) | 应用路由规则 |
fil() | 应用额外的过滤(clash/surge/cla/v2rayn/sip002) |
filter(rules=...) | 按配置的过滤规则动态过滤代理(rules 为 filter.yaml 中的规则名,逗号分隔或数组,_rules.filter 表) |
rename(rules=...) | 按配置的重命名规则动态改名(rules 为 rename.yaml 中的规则名,_rules.rename 表) |
示例:带测试部分的 Clash 输出
{# tpls/clash.tpl #}
{%- set format = "json" -%}
{%- set pretty = false -%}
# {{ l.name }}
# 生成时间 {{ now() | date(format="%Y-%m-%d %H:%M:%S") }}
test: &test
url: {{ p.test.url }}
interval: {{ p.test.interval }}
proxies:
{% filter indent(width=2) %}{% if format == "json" %}
{%- for i in l.proxies %}
- {{ i | json_str(pretty=pretty) }}
{%- endfor %}{% elif format == "yaml" %}
{{ l.proxies | yaml_str() }}{% endif %}{% endfilter %}
示例:V2RayN 输出
{# tpls/v2rayn.tpl #}
{{ l.proxies | v2rayn() }}
示例:模板内动态应用过滤/重命名规则
模板可在 l.proxies 基础上按需应用 filter.yaml / rename.yaml 中的规则,多个过滤器可链式组合(顺序即应用顺序):
{# 只保留美国节点,去掉名称中的 "US-" 前缀,再输出 V2RayN 格式 #}
{{ l.proxies | filter(rules="us") | rename(rules="us_prefix") | v2rayn() }}
rules 参数接受逗号分隔的规则名字符串或名字数组;未定义的规则名会被静默忽略(与配置解析行为一致)。
示例:SIP002 输出
{# tpls/sip002.tpl #}
{{ l.proxies | sip002() }}
使用自定义参数
在配置中定义参数:
params:
test:
url: "http://www.gstatic.com/generate_204"
interval: 600
region: "asia"
然后在模板中使用:
test-url: {{ p.test.url }}
test-interval: {{ p.test.interval }}
region: {{ p.region }}
使用 config.snippet
片段可以为模板提供额外数据:
snippet:
ruleset:
source:
url: "https://example.com/rules.yaml"
p:
clash_url: "https://example.com/clash-rules.yaml"
在模板中:
{%- for k, v in config.snippet %}
rule-provider {{ k }}:
url: {{ v.p.clash_url }}
{%- endfor %}
TLS 设置
通过 TLS 提供 HTTPS 代理服务。
使用 CLI 标志
thurgio serve \
--tls-only \ # 仅提供 HTTPS(无明文 HTTP)
--tls-port 8443 \ # TLS 端口
--tls-cert /etc/ssl/cert.pem \ # 证书文件(-c)
--tls-cert-key /etc/ssl/key.pem # 私钥文件(-k)
TLS + 明文在不同端口上
thurgio serve \
--port 10100 \ # 明文 HTTP
--tls-port 8443 \ # 同时提供 HTTPS
--tls-cert /etc/ssl/cert.pem \
--tls-cert-key /etc/ssl/key.pem
注意:
--tls-port默认等于--port。同时提供明文与 HTTPS 时必须显式指定不同的 TLS 端口,否则两个服务会绑定同一地址导致启动失败。
使用环境变量
export TLS_CERT=/etc/ssl/cert.pem
export TLS_CERT_KEY=/etc/ssl/key.pem
export TLS_PORT=8443
export TLS_ONLY=true
thurgio serve
拆分配置为多个文件
Thurgio 解析 thurgio.yaml 后,会自动读取配置文件所在目录下的配套 YAML 文件并合并(模板目录同样相对该目录解析,ADR-066):
| 文件 | 配置段 | 缺失时 |
|---|---|---|
provider.yaml | provider | 合并空 |
params.yaml | params | 合并空 |
rename.yaml | rename | 报错(必须存在空或有内容) |
filter.yaml | filter | 报错 |
snippet.yaml | snippet | 合并空 |
ruleset.yaml | ruleset | 合并空(唯一缺失合法的 sibling,ADR-086):本地行列表或 http(s):// 远程 URL(更新管线物化进 _rules.ruleset) |
这对于组织大型配置很有用:
~/.config/thurgio/
├── thurgio.yaml # 主配置(setting)
├── provider.yaml # 所有提供者
├── filter.yaml # 所有过滤规则
├── rename.yaml # 所有重命名规则
├── snippet.yaml # 所有片段
└── tpls/ # 模板目录(THURGIO_TPL_DIR,默认 "tpls")
├── clash.tpl
├── surge.tpl
└── v2rayn.tpl
所有被引用的模板文件必须在模板目录中存在,否则配置解析失败。
命名规则集(ruleset.yaml)
可选 sibling 文件 ruleset.yaml 按名称定义命名规则集(本地行列表或 http(s):// 远程 URL),生成 _rules.ruleset 上下文面供 ruleset 过滤器消费({{ l.proxies | ruleset(name="ads") }})。远程条目由更新管线定时拉取并物化,本地声明优先于同名远程。
# ruleset.yaml
ads:
- DOMAIN-SUFFIX,ads.example.com
- DOMAIN-KEYWORD,tracker
cnlist: https://example.com/cn.list
过滤器调用:{{ l.proxies | ruleset(name="ads", format="clash") }} / surge;内联 text 参数优先于命名表,命名表优先于内置 assets/rulesets/*.list。
备份与回滚
编辑配置前可以用 config backup 把当前声明文件(thurgio.yaml、5 个 sibling YAML 与 tpls/ 模板目录,清单与配置热重载的监听一致;ruleset.yaml 不在清单内)整体复制到备份目录:
thurgio config backup
# ✔ Backup created: <root>/backups/20260814-153000
thurgio config list
# Available backups in <root>/backups:
# 20260814-153000 2026-08-14 15:30:00
默认备份到 <root>/backups/<YYYYMMDD-HHMMSS>/,可用 -d/--dir 指定其他位置;只复制实际存在的文件。
配置改坏后,用 list 找到备份名并恢复:
thurgio config restore 20260814-153000
restore 接受备份名(backups/ 下的子目录名)或备份目录路径(绝对路径,或相对 root 的路径)。恢复前会把当前状态自动再备份一份到 backups/(保险),然后覆盖写回声明文件与 tpls/;仅恢复备份中包含的内容,不会删除 root 中的其他文件。
数据库
Thurgio 使用嵌入式 SQLite 数据库(WAL 模式)进行持久化,无需外部数据库服务器。数据库文件(默认 thurgio.db,可用 DATABASE_URL 环境变量覆盖;兼容 sqlite:// 前缀写法)会在首次使用时自动创建于工作目录。持久化以下数据:
- Provider 响应 — 缓存的响应,避免每次构建时重新获取。
- Snippet 数据 — 获取的片段内容。
- 熔断器快照 — 重启后保持打开状态的熔断器。
- KV 存储 — 通用键值数据(
kv_store表)。
环境变量参考
| 变量 | 覆盖项 | 默认值 |
|---|---|---|
ROOT | 工作根目录(-r/--root) | 当前目录 |
FORCE | build 强制更新(-f/--force) | false |
WATCH | build 监听模式(-w/--watch) | false |
NOTIFY | build 完成时发送系统通知(-N/--notify) | false |
SHELL | generate 补全目标 shell(-s/--shell) | bash |
OUTPUT | generate 输出文件(-o/--output) | stdout |
ADDRESS | serve 监听地址(-a/--address) | 配置 setting.address(默认 0.0.0.0) |
PORT | serve 监听端口(-p/--port) | 配置 setting.port(默认 10100) |
DEV | serve 开发模式(-d/--dev) | false |
IDLE | serve 空闲模式(-i/--idle):无请求时自动退出 | false |
EVICT | serve 空闲时清空数据缓存(-e/--evict) | true |
TLS_ONLY | serve 仅提供 HTTPS(-s/--tls-only) | false |
TLS_PORT | serve TLS 端口(-t/--tls-port) | 同 PORT |
TLS_CERT | TLS 证书路径(-c/--tls-cert) | 无 |
TLS_CERT_KEY | TLS 私钥路径(-k/--tls-cert-key) | 无 |
OPEN | serve 启动时打开浏览器(-O/--open) | false |
THURGIO_CONCURRENCY_LIMIT | 并发限制(渲染/持久化任务) | 10 |
THURGIO_TPL_DIR | 模板目录 | tpls |
THURGIO_RENDERER_RELOAD_TIMEOUT | 渲染器重载超时(秒) | 3 |
THURGIO_RENDERER_MIN_THREADS | 渲染线程池最小线程数 | 1 |
THURGIO_RENDERER_MAX_THREADS | 渲染线程池最大线程数 | 64 |
THURGIO_RENDERER_IDLE_TIMEOUT | 渲染线程空闲退出时间(秒) | 5 |
THURGIO_RENDERER_IDLE_COOLDOWN | 缩容冷却次数 | 3 |
THURGIO_RENDERER_SCALE_UP_THRESHOLD | 扩容阈值(平滑 backlog 倍数) | 2.0 |
THURGIO_RENDERER_SCALE_DOWN_THRESHOLD | 缩容阈值 | 0.5 |
THURGIO_RENDERER_EMA_ALPHA | backlog EMA 平滑系数 | 300 |
THURGIO_WEBHOOK_URL | 构建完成通知 webhook(POST JSON) | 无(禁用) |
EVICT_TIMEOUT | 空闲后清空数据缓存的等待时间(秒,--evict 开启时) | 10 |
IDLE_TIMEOUT | 空闲模式退出等待时间(秒,--idle 开启时) | 30 |
DATABASE_URL | 数据库文件路径 | thurgio.db |
REQUEST_TIMEOUT | HTTP 客户端请求超时(秒) | 9 |
THURGIO_MAX_CONCURRENT_REQUESTS | 服务器最大并发请求数 | 600 |
THURGIO_RATE_LIMIT_REQUESTS | 每个 IP 时间窗内最大请求数 | 600 |
THURGIO_RATE_LIMIT_WINDOW_SECONDS | 速率限制时间窗(秒) | 60 |
THURGIO_MAX_RATE_LIMIT_ENTRIES | 速率限制条目上限 | 10000 |
RUST_LOG | 日志级别过滤(如 info,thurgio=debug) | info |
RUST_LOG_ANSI | 是否启用日志颜色(true/false) | 自动检测(stdout 为 TTY) |
RUST_LOG_FORMAT | 日志格式(json 启用 JSON 输出) | 文本 |
RUST_INVALID_TLS | 跳过 TLS 证书校验(仅测试用) | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP 导出端点(otlp 特性,gRPC) | http://localhost:4317 |
模板示例
Thurgio 的核心扩展面是 Tera 模板:每个 provider / snippet 通过 template 字段指定一个模板,渲染上下文携带代理列表(l.proxies)、配置(config)、参数(p)等数据,配合内置过滤器即可产出任意格式的订阅(Clash / sing-box / V2RayN / Surge / 纯文本 / Base64…)。
本页索引 fixtures/examples/tpls/ 中的示例模板,并给出过滤器快速参考。按输出格式分类的精选索引(用途 / 依赖过滤器 / 适用客户端)见 fixtures/examples/README.md。模板语法详见配置指南。
目录结构
fixtures/examples/
├── README.md # 精选模板索引(按输出格式分类)
├── thurgio.yaml # 主配置(setting)
├── provider.yaml # provider 定义(模板引用在这里)
├── snippet.yaml # snippet 定义
├── filter.yaml # 过滤规则
├── rename.yaml # 改名规则
├── params.yaml # 渲染参数
├── tpls/ # 模板目录(THURGIO_TPL_DIR,默认 "tpls")
├── prov/ # 订阅源(本地文件)
├── rules/ # 规则集(snippet 源)
└── prod/ # build 输出
模板清单
以下模板全部默认接线:
provider.yaml的outputs与snippet.yaml条目已引用它们,thurgio build一次产出所有格式。
| 模板 | 适用 | 输出格式 | 说明 |
|---|---|---|---|
base.tpl | provider | Clash YAML | 最小模板(thurgio init 生成),json_str 逐节点输出 |
clash.tpl | provider | Clash YAML | 紧凑 JSON 节点 + test 块 + rule-providers(遍历 config.snippet 的 p.clash_url) |
clash_groups.tpl | provider | Clash YAML | 按 p.groups 规则名动态生成 proxy-groups(filter(rules=...) 实时筛选成员) |
clash_list.tpl | provider | Clash YAML | clash.tpl 变体:头部注释打印 l.rename / l.filter |
surge.tpl | provider | Surge 配置 | 手写 [Proxy] 段(for 循环 + 字段展开,教学向) |
surge_proxy.tpl | provider | Surge 配置 | surge_proxy 过滤器版 [Proxy] 段({{ l.proxies | surge_proxy() }}) |
surge_full.tpl | provider | Surge 完整配置 | [General] / [Proxy] / [Proxy Group] / [Rule] 四段齐全,可直接订阅导入(surge_proxy 节点行 + select/url-test 组 + p.rules 规则聚合,首行 #!MANAGED-CONFIG) |
v2rayn.tpl | provider | 纯文本 | 每行一个 vmess:// 分享链接(v2rayn 过滤器) |
sip002.tpl | provider | 纯文本 | 每行一个 ss:// 分享链接(sip002 过滤器) |
sip008.tpl | provider | JSON | SS Android 官方订阅:version + servers 数组(sip008 过滤器,仅 ss 节点) |
ssd.tpl | provider | 纯文本 | ssd:// 订阅:UrlSafe Base64 JSON(ssd 过滤器,仅 ss 节点) |
shadowrocket.tpl | provider | 纯文本 | 每行一个分享链接(shadowrocket 过滤器,复用 sip002/v2rayn 转换,wg 跳过) |
base64_sub.tpl | provider | Base64 | 机场式订阅:v2rayn + sip002 链接整体 base64(mode="encode") 编码 |
plain_list.tpl | provider | 纯文本 | 人类可读节点清单:名称 | 类型 | 服务器:端口(for 循环 + 字段访问) |
singbox.tpl | provider | sing-box JSON | 完整 sing-box 配置:sibox 转 outbounds + direct/block |
singbox_rules.tpl | snippet | sing-box JSON | clash 规则文本 → sing-box rule 数组(clashbox 过滤器),与 singbox.tpl 组合成完整配置 |
qx.tpl | provider | QX 节点行 | 每行一个 QX 节点(qx 过滤器,ss/ssr/vmess/vless/trojan/http/socks5) |
qx_config.tpl | provider | QX 完整配置 | 完整 Quantumult X 配置:[general]/[dns]/[server_local]/[policy](qx_config 过滤器) |
loon.tpl | provider | Loon 节点行 | 每行一个 Loon 节点(loon 过滤器,10 种类型) |
loon_config.tpl | provider | Loon 完整配置 | 完整 Loon 配置:[General]/[Proxy]/[Proxy Group]/[Rule](loon_config 过滤器) |
mellow.tpl | provider | Mellow JSON | Mellow/V2Ray-core JSON:socks inbound + per-proxy outbounds(mellow 过滤器,ss/vmess/vless/trojan/http/socks5 + TCP/WS/TLS) |
mixed.tpl | provider | 混合分享链接 | 混合单链接列表:逐节点 V2RayN 优先、SIP002 回退,mixed 过滤器 |
rules_aggregate.tpl | snippet | Quantumult X 规则集 | 多规则集聚合:拼接 config.snippet 中各规则集内容 → uniq 去重 → sort_domain_ip 排序 → quantumult 输出(s.p.rules 指定要聚合的 snippet 名,s.p.policy 指定策略);换 clash / surge 过滤器即得对应格式规则列表 |
snippet.tpl | snippet | 原样 | 原样输出 snippet 内容(s.response.content) |
rules_pipeline.tpl | provider | 教学 | 演示模板内 filter → rename → v2rayn/sip002/cla 过滤器链 |
date.tpl | 片段 | 文本 | {% include %} 引入的生成时间注释 |
ui.tpl | — | HTML | Dashboard 页面(服务器自带,勿删除) |
auto/*.tpl | provider | UA 自适应 | tpl=auto 时按 User-Agent 选择的模板(auto/cla.tpl/surge.tpl/qx.tpl/loon.tpl/sibox.tpl/shadowrocket.tpl/v2rayn.tpl,见 examples/templates-auto/,复制到 tpls/auto/ 即用) |
配置引用方式:
provider.yaml中template: clash.tpl即相对模板目录的模板名;outputs下每个具名输出可指定不同模板,同一份代理列表渲染多种格式:
provider:
example:
type: clash
source: "prov/example.yaml"
template: clash_list.tpl
filename:
- prod/list/example.yaml
outputs:
base64_sub:
template: base64_sub.tpl
filename:
- prod/example_base64.txt
plain_list:
template: plain_list.tpl
filename:
- prod/example_plain.txt
surge_full.tpl — Surge 完整配置
产出可直接被 Surge 客户端订阅的完整配置([General] / [Proxy] / [Proxy Group] / [Rule] 四段齐全)。节点行来自 surge_proxy 过滤器;[Proxy Group] 含一个 select 组(Proxy)与一个 url-test 自动测速组(Auto);[Rule] 段把 p.rules 指定的规则集 snippet 聚合后经 surge 过滤器输出,末尾补 FINAL,Proxy 兜底。模板不硬编码任何服务器信息,全部来自上下文变量。
参数放在 params.yaml(渲染上下文 p 绑定来自全局参数):
# params.yaml
rules: # [Rule] 段聚合的 snippet 名(config.snippet 的键,缺省空)
- China
rule_policy: Proxy # [Rule] 默认策略名(缺省 Proxy)
test: # url-test 测速参数(缺省 http://cp.cloudflare.com/generate_204 / 300)
url: "http://cp.cloudflare.com/generate_204"
interval: 300
在 provider.yaml 的 outputs 中挂载(template / filename 字段):
provider:
example:
type: clash
source: "prov/example.yaml"
template: clash_list.tpl
filename:
- prod/list/example.yaml
outputs:
surge_full:
name: example
template: surge_full.tpl
filename:
- prod/example_surge_full.conf
首行 #!MANAGED-CONFIG 的拉取地址来自环境变量 SURGE_CONFIG_URL(未设置时输出占位 URL,需改成自己的订阅地址),Surge 客户端会按 interval=86400 定期更新。
[Proxy Group]的节点名列表与[Proxy]段保持一致:仅列出 Surge 支持的 ss/trojan/vmess/snell/http/socks5/wireguard 七种类型(与Surge::supports能力谓词同步)——vless/ssr/hysteria/hysteria2/tuic/anytls 会被surge_proxy跳过,组内引用必须匹配,避免引用不存在的节点。
跑通示例
cd fixtures/examples
thurgio -r . build # -r 是顶层选项,必须放在子命令前
或从仓库根目录:
cargo run -q -p thurgio-cli -- -r fixtures/examples build
构建成功后输出位于 fixtures/examples/prod/:
| 输出 | 模板 | 格式 |
|---|---|---|
prod/list/example.yaml | clash_list.tpl | Clash |
prod/example.yaml | clash.tpl | Clash |
prod/example_groups.yaml | clash_groups.tpl | Clash(动态 proxy-groups) |
prod/example_surge.txt | surge.tpl | Surge(手写 [Proxy] 段) |
prod/example_surge_proxy.txt | surge_proxy.tpl | Surge(过滤器版 [Proxy] 段) |
prod/list/example_v2rayn.txt / prod/example_v2rayn.txt | v2rayn.tpl | 分享链接 |
prod/list/example_sip002.txt / prod/example_sip002.txt | sip002.tpl | 分享链接 |
prod/example_sip008.json | sip008.tpl | SS Android JSON 订阅 |
prod/example_ssd.txt | ssd.tpl | ssd:// 订阅 |
prod/example_shadowrocket.txt | shadowrocket.tpl | Shadowrocket 分享链接 |
prod/example_base64.txt | base64_sub.tpl | Base64 订阅 |
prod/example_plain.txt | plain_list.tpl | 纯文本节点清单 |
prod/example_singbox.json | singbox.tpl | sing-box 完整配置 |
prod/example_singbox_rules.json | singbox_rules.tpl | sing-box 规则数组 |
prod/example_china.list | snippet.tpl | 原样规则集 |
prod/example_quantumult.txt | rules_aggregate.tpl | Quantumult X 规则集 |
挂载新模板:在 provider.yaml 的 outputs(或 snippet.yaml 条目)中按 key 复制现有条目,改 template / filename 即可。注意 clash.tpl 会遍历所有 snippet 读取 p.clash_url,新 snippet 也要带该参数。
fixtures/examples被集成测试锁定(ADR-060),修改声明文件后跑cargo test -p thurgio-app --test render_examples验证。
tpl=auto:按 User-Agent 自适应输出
服务端在 GET /app/read/l 收到 ?tpl=auto 时嗅探请求 User-Agent 选模板,按候选顺序取第一个存在于模板目录的 auto/<key>.tpl 渲染(对齐 subconverter 行为)。仅 provider 端点支持;snippet 与 /app/admin/adhoc 不支持。
工作原理:大小写不敏感子串匹配,先命中先赢;命中行的候选键按序探测文件存在性,首个存在的即为本次渲染模板。
UA 匹配表
行顺序即优先级(同时命中 clash 与 v2ray 时 clash 行先赢)。surge 行命中后会兜底追加默认顺序的剩余键;其余行仅尝试自身候选。
| UA 片段(小写) | 候选键 → 模板文件 | 说明 |
|---|---|---|
surge | surge → auto/surge.tpl,未命中则按默认顺序继续 cla / qx / loon / sibox / shadowrocket / v2rayn | Surge(含 Mac / iOS) |
quantumult | qx → auto/qx.tpl | Quantumult X |
loon | loon → auto/loon.tpl | Loon |
clash | cla → auto/cla.tpl | clash / clash-verge / clashmeta / Clash for Windows 等 |
mihomo | cla → auto/cla.tpl | mihomo |
stash | cla → auto/cla.tpl | Stash |
sing-box | sibox → auto/sibox.tpl | sing-box |
singbox | sibox → auto/sibox.tpl | singbox(无连字符) |
sfa | sibox → auto/sibox.tpl | sing-box Android 前端 |
sfi | sibox → auto/sibox.tpl | sing-box iOS 前端 |
sfm | sibox → auto/sibox.tpl | sing-box macOS 前端 |
shadowrocket | shadowrocket → auto/shadowrocket.tpl | Shadowrocket |
v2ray | v2rayn → auto/v2rayn.tpl | v2rayN / v2rayNG |
| (未知或缺失 UA) | cla → auto/cla.tpl → surge → qx → loon → sibox → shadowrocket → v2rayn | 默认顺序,依次探测 |
默认顺序见
crates/thurgio-server/src/ua.rs:16的DEFAULT_ORDER;匹配表见同文件UA_TABLE(crates/thurgio-server/src/ua.rs:44)。
部署步骤
examples/templates-auto/ 提供可直接复制的最小可用模板集(cla.tpl / surge.tpl / qx.tpl / loon.tpl / sibox.tpl / shadowrocket.tpl / v2rayn.tpl):
mkdir -p tpls/auto
cp examples/templates-auto/*.tpl tpls/auto/
# 按需删改:不需要的客户端模板可直接删除,剩余的即为该 UA 的唯一候选
模板目录由 THURGIO_TPL_DIR 决定(默认 tpls),tpl=auto 的探测根固定为该目录下的 auto/ 子目录(crates/thurgio-server/src/ua.rs:29 的 RENDERER_TPL_DIR)。
优先级与回退
- 显式
?tpl=优先:?tpl=auto才走 UA 探测;?tpl=clash.tpl等显式模板名直接透传,不做 UA 解析(crates/thurgio-server/src/ua.rs:115的apply_auto_template透传分支,见crates/thurgio-server/tests/app/auto_tpl.rs:118)。?tpl=为空时回退到 provider 配置的template。 - 候选内回退:同一 UA 的候选列表内按序探测,首个存在的
auto/<key>.tpl胜出(crates/thurgio-server/src/ua.rs:87的resolve_auto_template);仅surge行会扩展为全默认顺序,其余行仅尝试自身键(crates/thurgio-server/src/ua.rs:66的auto_template_candidates)。 - 无匹配即 400:全部候选都不存在时返回
400 Bad Request,错误信息形如no auto template exists (tried: auto/cla.tpl, auto/surge.tpl, ...)并列出本次已尝试的全部模板名(crates/thurgio-server/src/ua.rs:130的NoAutoTemplate,映射为AppError::bad_request于crates/thurgio-server/src/helpers/provider_request.rs:47)。
注意
provider.type 不能写 auto,auto 仅用于渲染侧 ?tpl=auto 的 UA 自适应选择;输入格式仍需显式指定真实类型(clash / v2rayn / sip002 等),见配置指南与配置参考。
过滤器快速参考
完整说明见配置指南,此处给出最常用的调用形式:
输出过滤器(代理数组 → 目标格式)
| 过滤器 | 参数 | 示例 |
|---|---|---|
v2rayn | — | {{ l.proxies | v2rayn() }} |
sip002 | — | {{ l.proxies | sip002() }} |
sip008 | — | {{ l.proxies | sip008() }} — SS Android JSON 订阅(仅 ss 节点) |
shadowrocket | — | {{ l.proxies | shadowrocket() }} — 每行一个分享链接(wg 跳过) |
ssd | — | {{ l.proxies | ssd() }} — ssd:// 订阅(仅 ss 节点) |
surge_proxy | — | {{ l.proxies | surge_proxy() }} — Surge [Proxy] 段行(ss/trojan/vmess/snell/http/socks5/wireguard) |
qx | — | {{ l.proxies | qx() }} — Quantumult X 节点行(7 种类型) |
qx_config | check_url=, dns=, group= | {{ l.proxies | qx_config(group="Proxy") }} — 完整 QX 配置四段 |
loon | — | {{ l.proxies | loon() }} — Loon 节点行(10 种类型) |
loon_config | dns=, group= | {{ l.proxies | loon_config(group="Proxy") }} — 完整 Loon 配置四段 |
mellow | port=1080 | {{ l.proxies | mellow(port=1080) }} — Mellow/V2Ray-core JSON(socks inbound + outbounds) |
mixed | — | {{ l.proxies | mixed() }} — 混合单链接列表(V2RayN 优先、SIP002 回退) |
stash | — | {{ l.proxies | stash() }} — Stash(Clash 兼容)节点行 |
surfboard | — | {{ l.proxies | surfboard() }} — Surfboard(Surge 兼容)节点行 |
clashr | — | {{ l.proxies | clashr() }} — ClashR 节点行 |
sibox | — | {{ l.proxies | sibox() | json_str(pretty=true) }} — sing-box outbound 对象数组 |
singbox | — | {{ l.proxies | singbox() }} — sing-box 完整 JSON(别名 sibox) |
cla | meta=true | {{ l.proxies | cla(meta=true) | json_str() }} — 过滤出 clash 兼容节点 |
proxy_suffix | suffix="..." | {{ l.proxies | proxy_suffix(suffix="HK") }} — 给节点名加后缀 |
js | src="..." | {{ l.proxies | js(src="return $input.map(p=>...)") }} — 内联 JS 变换(boa_engine) |
sort_nodes | by=name/region/latency, order=asc/desc, latencies= | {{ l.proxies | sort_nodes(by="latency", latencies=map) }} |
clash_groups | groups="a,b" | {{ l.proxies | clash_groups() }} — Clash/Mihomo 策略组 |
surge_groups | — | {{ l.proxies | surge_groups() }} — Surge 策略组 |
规则过滤器(模板内动态应用 filter.yaml / rename.yaml)
| 过滤器 | 参数 | 示例 |
|---|---|---|
filter | rules="a,b"(或数组) | {{ l.proxies | filter(rules="hk") | v2rayn() }} |
rename | rules="a,b"(或数组) | {{ l.proxies | rename(rules="rm_brackets") | v2rayn() }} |
未定义的规则名静默忽略;过滤器可链式组合,顺序即应用顺序。
规则文本过滤器(snippet 内容 / 规则集)
| 过滤器 | 参数 | 示例 |
|---|---|---|
clash | policy=..., symbol=... | {{ s.response.content | clash(policy="Proxy") }} — 规则文本 → clash 规则列表 |
surge | policy=..., symbol=... | {{ s.response.content | surge(policy="Proxy") }} |
quantumult | policy=..., symbol=... | {{ s.response.content | quantumult(policy="Proxy") }} — 规则文本 → Quantumult X 规则集(host/host-suffix/host-keyword/ip-cidr/ip6-cidr/geoip/user-agent/final;无对应类型的规则丢弃) |
clashbox | policy=..., premium=..., meta=..., pretty=true | {{ s.response.content | clashbox(policy="proxy") }} — clash 规则文本 → sing-box rule 对象 |
no_resolve | value=true/false | {{ s.response.content | no_resolve(value=true) }} |
sort_domain_ip | — | 域名/IP 规则排序 |
通用过滤器
| 过滤器 | 参数 | 示例 |
|---|---|---|
base64 | mode="encode"/"decode" | {{ lines | base64(mode="encode") }} |
json_str | pretty=false | {{ i | json_str(pretty=false) }} |
yaml_str | — | {{ l.proxies | yaml_str() }} |
indent | width=2 | {{ body | indent(width=2) }} |
uniq | — | 按行去重 |
clear | num=3, n=2 | 压缩连续空行 |
slugify / urlencode | separator="-" | 字符串处理 |
函数
| 函数 | 参数 | 示例 |
|---|---|---|
now | format="[year]-[month]-[day]...", tz="+08:00", utc=true, timestamp=true | {{ now() }}、{{ now(timestamp=true) }} |
get_env | name="VAR", default="..." | {{ get_env(name="HOME", default="") }} |
js() 过滤器:内联 JavaScript 变换
js 是代理数组的内联变换过滤器,经 thurgio-script(boa_engine 0.21)执行用户 JS 片段,适合在模板内完成 filter / rename 规则之外的临时加工与字段归一。
用法
{# 基础写法:src 内为完整 JS 函数体,$input 为本次输入 #}
{{ l.proxies | js(src="return $input.length;") }}
{# 带参数传递:模板变量可拼进 src,或经 tera 变量传入 #}
{% set script = "return $input.filter(p => p.type !== 'ss');" %}
{{ l.proxies | js(src=script) | v2rayn() }}
Tera 注册见 crates/thurgio-renderer/src/filters/js.rs:13 的 js 函数(register_all 统一注册),调用为 src="..." 关键字参数;缺失 src 时渲染报错(同文件 js:14 的校验)。
$input 结构
$input 为本次过滤器输入的 JSON 镜像(JsValue::from_json 注入,crates/thurgio-script/src/engine.rs:55),在 provider 渲染中即 l.proxies 的代理对象数组。每个元素为 ProxyTy 的序列化形态(crates/thurgio-proxy/src/proxy/mod.rs:436 的 Serialize for ProxyTy),常见顶层字段:
| 字段 | 说明 |
|---|---|
name / type / server / port | 必有基础字段(与模板中 l.proxies[].name 等一致) |
cipher / password / uuid / alterId | 协议相关字段(ss / vmess 等按类型出现) |
tls / network / ws-opts / grpc-opts | 传输与 TLS 字段(vmess / vless 等按需出现) |
udp | UDP 覆盖标志(可选) |
字段集合随代理类型不同而异,以实际序列化结果为准;模板中
{{ l.proxies | json_str(pretty=true) }}可直观查看当前数据的可用字段。
输入与输出均经 serde_json::Value 往返(crates/thurgio-script/src/engine.rs:74 的 to_json),未定义字段保留原样,返回结构可直接链给下游过滤器(如 | js(...) | v2rayn() / | cla() | json_str())。
约束
- 必须显式
return:脚本体以(function() { <src> })()形式求值(crates/thurgio-script/src/engine.rs:66的wrapped),undefined结果视为错误user script must return a value(同文件engine.rs:71),缺失return会导致渲染失败(ErrorKind::Render,见crates/thurgio-script/src/engine.rs:37与crates/thurgio-script/src/engine.rs:178的missing_return_is_err)。 - boa_engine 沙箱:每次调用新建独立
Context(crates/thurgio-script/src/engine.rs:53),$input突变不外泄、不跨调用共享(同文件engine.rs:187的input_not_shared_between_calls)。默认上下文仅提供 ECMAScript 内置对象(Array/Object/JSON/Math等),未挂载fetch/ 文件系统 / 进程等宿主能力,无网络与文件 I/O;但 v1 未提供执行超时与强沙箱隔离(crates/thurgio-script/src/lib.rs:11的信任模型),脚本来自受信模板,勿直接执行不可信第三方脚本(design/crate-notes.md:44)。 - 错误归类:语法错误、运行时
throw、输入/输出 JSON 转换失败均映射为ErrorKind::Render并以字符串透传给 Tera(crates/thurgio-script/src/engine.rs:67、crates/thurgio-renderer/src/filters/js.rs:23),渲染管线以 422/500 形式返回。
实用示例
示例 1 — 过滤 + 改名(筛掉 ss、其余加前缀):
{# tpls/filtered.tpl — 仅保留非 ss 节点并统一加前缀 #}
{{ l.proxies | js(src="
return $input
.filter(p => p.type !== 'ss')
.map(p => {
p.name = '[FILTERED] ' + p.name;
return p;
});
") | v2rayn() }}
等价于组合 filter / rename 规则的内联写法,适合一次性、客户端特定的临时逻辑,无需在 filter.yaml / rename.yaml 新增规则。
示例 2 — 字段归一(兼容历史字段):
{# 将历史数据中的 legacy_port 归一到 port,归一后走 clash 输出 #}
{{ l.proxies | js(src="
return $input.map(p => {
if (p.legacy_port) { p.port = p.legacy_port; delete p.legacy_port; }
return p;
});
") | cla() | json_str(pretty=true) | indent(width=2) }}
更复杂的变换可抽为独立 .js 片段经 include_str! 或模板变量拼装;链式组合时 js 可位于任意位置,注意后续过滤器的输入类型需匹配(如 cla 期望代理数组)。
渲染上下文速查
| 变量 | 可用范围 | 说明 |
|---|---|---|
l.proxies | provider | 代理对象数组(每个元素有 name / type / server / port 等字段) |
l.name / l.rename / l.filter | provider | 名称 / 配置层规则名 |
s.response.content | snippet | snippet 源内容(原样文本) |
p.* | 全部 | 自定义参数(params.yaml + /app/admin/p 热更新) |
hp.* | 全部 | 本次 HTTP 请求的查询参数(与 p 分离,ADR-081) |
config | 全部 | 完整配置对象(config.snippet、config.params…) |
headers / version | 全部 | 请求头 / 版本字符串 |
部署指南
使用 systemd、Docker 或反向代理在生产环境中运行 Thurgio。
从 GitHub Release 安装二进制
Thurgio 不发布包管理器制品;release 二进制由 CI 构建并上传到 GitHub Releases,同时发布私有 Docker 镜像(见 使用 Docker)。每个 release 包含各架构的二进制与配套校验文件,资产名带目标架构后缀:thurgio-<target> 与 thurgio-<target>.sha256。
| 目标架构 | 适合场景 | Release 资产 |
|---|---|---|
x86_64-unknown-linux-musl | 绝大多数 x86_64 Linux 服务器 | thurgio-x86_64-unknown-linux-musl |
aarch64-linux-android | Android 设备(不是服务器) | thurgio-aarch64-linux-android |
aarch64-unknown-linux-musl | ARM64 Linux 服务器(如树莓派) | 自行构建:just build-arm-linux |
Release 目前只附带
x86_64-unknown-linux-musl与aarch64-linux-android两个构建产物(见.github/workflows/ci.yml)。ARM64 服务器用户请用仓库内just build-arm-linux交叉编译,或查看 交叉编译 章节。
下载并校验(<tag> 替换为版本号,如 v0.18.0;下面以 x86_64 Linux 服务器为例,Android 设备把 ARCH 换成 aarch64-linux-android):
TAG=v0.18.0
ARCH=x86_64-unknown-linux-musl
curl -fLO "https://github.com/xylylab/thurgio/releases/download/${TAG}/thurgio-${ARCH}"
curl -fLO "https://github.com/xylylab/thurgio/releases/download/${TAG}/thurgio-${ARCH}.sha256"
# 校验 sha256;校验文件记录的文件名是构建时的 `thurgio`,先改名再校验,
# 输出 `thurgio: OK` 才说明二进制与 release 一致
mv thurgio-${ARCH} thurgio
sha256sum -c thurgio-${ARCH}.sha256
sudo install -m 0755 thurgio /usr/local/bin/thurgio
thurgio version
install 会把二进制安装为 root 所有(systemd 单元以专用用户运行二进制,见下节)。若仓库 fork 或迁移,替换上面 URL 中的 xylylab/thurgio 为实际仓库。
作为 systemd 服务
创建一个 systemd 单元来将 Thurgio 作为长期运行的后台服务管理。
1. 创建专用用户
sudo useradd -r -s /usr/sbin/nologin -m -d /var/lib/thurgio thurgio
2. 放置二进制文件
sudo cp thurgio /usr/local/bin/thurgio
sudo chown thurgio:thurgio /usr/local/bin/thurgio
3. 创建配置目录
sudo mkdir -p /etc/thurgio/tpls
sudo chown -R thurgio:thurgio /etc/thurgio
4. 编写配置文件
# /etc/thurgio/thurgio.yaml
setting:
tokens:
admin:
- "your-production-token" # 使用强随机令牌
address: "127.0.0.1" # 绑定到 localhost;反向代理处理公网访问
port: 10100
output:
provider:
"clash.tpl":
- "output/proxies.yaml"
# /etc/thurgio/provider.yaml
my_provider:
name: my_provider
type: clash
source: "https://example.com/clash-config"
template: clash.tpl
filename:
- output/proxies.yaml
5. 创建 systemd 单元
仓库提供了一份带注释与安全加固的单元文件,直接复制即可:
sudo install -m 644 deploy/systemd/thurgio.service /etc/systemd/system/thurgio.service
关键设置说明(完整注释见文件本身):
ExecStart=/usr/local/bin/thurgio -c /etc/thurgio/thurgio.yaml -r /var/lib/thurgio serve—serve在前台运行(无 daemon 模式),配合Type=simple由 systemd 直接管理进程;Restart=always+RestartSec=5s在崩溃或空闲退出(--idle)时自动拉起,StartLimitIntervalSec/StartLimitBurst防 crash 循环。-r /var/lib/thurgio会把工作目录切换到数据根目录:thurgio.db(DATABASE_URL默认值)与配置中的相对输出路径都相对它解析。- 拆分 YAML(
provider.yaml/params.yaml/rename.yaml/filter.yaml/snippet.yaml)与模板目录则相对thurgio.yaml所在目录 解析(即/etc/thurgio),与-r无关。 Environment=DATABASE_URL=/var/lib/thurgio/thurgio.db显式指定数据库位置;RUST_LOG=info控制日志级别(默认即 INFO);模板目录默认tpls(相对配置目录),只有模板不在默认位置时才需要设置THURGIO_TPL_DIR。- 硬化:
NoNewPrivileges、ProtectSystem=strict(仅ReadWritePaths=/var/lib/thurgio可写)、ProtectHome、PrivateTmp/PrivateDevices、RestrictSUIDSGID、RestrictAddressFamilies、UMask=0027等。 - 空闲退出(
--idle):最后一个请求完成 30 秒(IDLE_TIMEOUT)后无新请求时进程正常退出,Restart=always会在下一个请求到达时自动拉起——这正是 idle 模式的设计语义(配合缓存降级--evict,空闲 10 秒先清空数据缓存)。
6. 启动并启用
sudo systemctl daemon-reload
sudo systemctl enable --now thurgio
7. 验证与健康检查
sudo systemctl status thurgio
# 就绪探测:/ready 是公开端点(无需 token),数据库可应答时返回
# 200 {"status":"ready",...},否则 503;curl -f 对非 2xx 退出非零
curl -fsS http://127.0.0.1:10100/ready
/ready 与 Docker 镜像内置的 HEALTHCHECK 使用同一端点(见下文),适合接入负载均衡器或外部监控。/health 是另一个公开端点,返回版本、数据库状态与内存占用等更详细的信息。
8. 查看日志
服务日志输出到 stdout/stderr,由 journald 收集:
journalctl -u thurgio -f # 跟随最新日志
journalctl -u thurgio -n 200 # 最近 200 行
journalctl -u thurgio --since "1 hour ago"
需要结构化日志时,在单元中设置 Environment=RUST_LOG_FORMAT=json。
9. 日志轮转
journald 自带容量与轮转管理(journalctl --vacuum-* 可手动清理),无需额外配置。仅当你自行把日志重定向到文件时才需要 logrotate,示例:
# /etc/logrotate.d/thurgio
/var/log/thurgio/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}
10. 升级
# 1. 下载新版本二进制并校验(见上方「从 GitHub Release 安装二进制」)
sudo systemctl stop thurgio
sudo install -m 0755 thurgio /usr/local/bin/thurgio
sudo systemctl start thurgio
# 2. 确认新版本与健康状态
thurgio version
sudo systemctl status thurgio
curl -fsS http://127.0.0.1:10100/ready
数据库与输出文件都位于 /var/lib/thurgio,替换二进制不影响数据;/etc/thurgio 配置不动,无需迁移。也可以运行 thurgio upgrade 检查是否有新版本(它只检查并打印下载地址,不会自动安装)。
使用 Docker
仓库根目录提供多阶段 Dockerfile,可直接从源码构建;CI 也会把镜像发布为 GHCR 私有包 ghcr.io/xylylab/thurgio(默认 private,见 发布私有镜像):
build阶段 —rust:alpine(默认 musl 目标,直接产出静态二进制);先复制Cargo.toml/Cargo.lock/crates/以缓存依赖编译层,再以cargo build --release --locked -p thurgio-cli编译;runtime阶段 —alpine:latest+ca-certificates(HTTPS 抓取订阅需要根证书)+tzdata,仅复制二进制。
拉取私有镜像
GHCR 包默认私有,拉取前需要登录(一次性):
# GitHub CLI:输出登录 token 给 docker login
docker login ghcr.io --username <你的 GitHub 用户名>
# 提示输入密码时粘贴以下命令的输出:
gh auth token
# 或使用 PAT(需要 read:packages scope)
docker login ghcr.io --username <你的 GitHub 用户名> --password-stdin <<< "<PAT>"
登录后即可拉取:
docker pull ghcr.io/xylylab/thurgio:v0.18.0 # 或 :latest
从源码构建镜像
docker build -t thurgio:latest .
# 或 just docker-build
.dockerignore 已排除文档、设计文件与构建产物,上下文只包含构建所需的 Cargo.toml/Cargo.lock/crates/、fuzz/Cargo.toml 与 fixtures/examples/tpls/(thurgio-cli 编译期嵌入该模板)。
运行容器
镜像的目录约定:
| 路径 | 用途 |
|---|---|
/config(只读挂载) | 配置目录:thurgio.yaml 及其拆分的 provider.yaml/filter.yaml/rename.yaml/snippet.yaml、模板 tpls/ |
/data(卷) | 数据目录:SQLite 数据库(DATABASE_URL,默认 /data/thurgio.db)与渲染输出 |
# 私有镜像(已登录 GHCR):
docker run -d \
--name thurgio \
-p 127.0.0.1:10100:10100 \
-v "$PWD/config:/config:ro" \
-v thurgio-data:/data \
ghcr.io/xylylab/thurgio:latest
# 或本地构建的镜像:
docker run -d \
--name thurgio \
-p 127.0.0.1:10100:10100 \
-v "$PWD/config:/config:ro" \
-v thurgio-data:/data \
thurgio:latest
- 默认命令为
-r /config serve:-r会切换到配置根目录,thurgio.yaml(-c默认值)及其拆分文件、模板都相对/config解析。 - 输出路径:配置中的输出路径请使用
/data/...绝对路径(相对路径会落在/config只读卷上,导致写入失败)。 - 环境变量(CLI 参数均支持环境变量注入):
DATABASE_URL(数据库位置,默认/data/thurgio.db)、ROOT(等价于-r/--root)、ADDRESS/PORT(监听地址/端口)、TZ(时区)。注意-c/--config没有对应环境变量,配置文件路径只能通过命令行传入。 - 健康检查:镜像内置
HEALTHCHECK,每 30 秒用 busyboxwget探测公开端点http://127.0.0.1:10100/ready(DB ping 通过时返回 200 与{"status":"ready"},否则返回 503;wget对非 2xx 退出非 0);用docker inspect --format '{{.State.Health.Status}}' thurgio查看状态,或在docker run时通过--health-cmd覆盖。
注意:默认监听
0.0.0.0:10100。请通过-p 127.0.0.1:10100:10100仅暴露到本机,并使用反向代理处理外部访问;不要在没有 TLS 的情况下将 Thurgio 直接暴露到互联网。
使用 Docker Compose 运行
# docker-compose.yml
services:
thurgio:
image: ghcr.io/xylylab/thurgio:latest # 私有镜像;或本地构建的 thurgio:latest
container_name: thurgio
restart: unless-stopped
ports:
- "127.0.0.1:10100:10100"
volumes:
- ./config:/config:ro
- thurgio-data:/data
environment:
- TZ=Asia/Shanghai
volumes:
thurgio-data:
发布私有镜像
镜像发布到 GitHub Container Registry(GHCR)的私有包 ghcr.io/xylylab/thurgio,含 linux/amd64 与 linux/arm64 两个架构(manifest list)。包默认私有——只有已登录且有访问权的账号能拉取;如要公开,去 GitHub 仓库的 Packages 页面把该包的可见性改为 public。
CI 自动发布(.github/workflows/docker.yml):推送 v* 标签(即 just release 的 tag)时自动构建并推送 ghcr.io/xylylab/thurgio:vX.Y.Z 与 :latest;手动触发(Actions 页面 Run workflow)推送 dev-<sha> 快照。
手动发布(无需 CI):
# 一次性登录(需要 write:packages scope;GitHub CLI 输出登录 token)
docker login ghcr.io --username <你的 GitHub 用户名>
gh auth token # 输出到密码提示
# 构建并推送 amd64 + arm64
just docker-push tag=v0.18.0
# 覆盖默认 registry(如自建 registry:2):
THURGIO_REGISTRY=registry.example.com/thurgio just docker-push tag=v0.18.0
注意:镜像与 release 二进制一样是发布制品,构建后无法审计源码,请确保镜像与 tag 对应的 git 提交一致(CI 发布路径天然满足)。
自托管文档站点
docs/ 目录自带一份多阶段 Dockerfile:编译期安装最新 mdBook 构建本书(与 docs CI 工作流同一约定),运行期为 Caddy 静态站点(含 404 页与 gzip)。适合内网或离线环境查阅用户文档:
# 从仓库根目录构建(构建上下文即 docs/)
docker build -t thurgio-docs:latest docs/
docker run -d \
--name thurgio-docs \
-p 127.0.0.1:8080:80 \
thurgio-docs:latest
# 打开 http://127.0.0.1:8080/
反向代理
始终将 Thurgio 运行在反向代理(nginx、Caddy)之后,以实现 TLS 终止、速率限制和访问控制。
Nginx
# /etc/nginx/sites-available/thurgio
upstream thurgio_backend {
server 127.0.0.1:10100;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name thurgio.example.com;
ssl_certificate /etc/ssl/certs/thurgio.pem;
ssl_certificate_key /etc/ssl/private/thurgio-key.pem;
# 加固 TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# 速率限制
limit_req zone=thurgio:10m rate=30r/s;
limit_req_status 429;
# 代理所有请求到 Thurgio 后端
location / {
proxy_pass http://thurgio_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
# 可选:阻止对敏感端点的访问
location /favicon.svg {
proxy_pass http://thurgio_backend;
}
location /health {
proxy_pass http://thurgio_backend;
}
location /metrics {
# 将 metrics 限制为内部监控
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
proxy_pass http://thurgio_backend;
}
}
Caddy
# Caddyfile
thurgio.example.com {
reverse_proxy 127.0.0.1:10100
health_uri /health
health_interval 30s
}
# 可选:限制 metrics 端点
@metrics {
path /metrics
not remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
}
handle @metrics {
respond 403
}
rate_limit {
zone dynamic {
key {remote_host}
events 30
window 1s
}
}
}
Caddy 通过 Let’s Encrypt 自动配置 TLS 证书。
交叉编译
Thurgio 通过 cross 支持多平台交叉编译(见 justfile):
| 目标 | 命令 |
|---|---|
| Linux(musl,默认) | just build-linux(x86_64-unknown-linux-musl) |
| Windows | just build-win(x86_64-pc-windows-gnu) |
| Android | just build-android(aarch64-linux-android) |
| Linux ARM | just build-arm-linux(aarch64-unknown-linux-musl) |
CI(.github/workflows/ci.yml)会在推送时构建 x86_64-unknown-linux-musl 与 aarch64-linux-android 的 release 二进制并上传制品(含 SHA256)。
生产环境检查清单
上线前,请逐项确认:
安全
- API 令牌 已从默认值改为强随机值,并按作用域分组:
yaml setting: tokens: admin: - "admin-xyz789" # 管理操作(写、刷新、重载等) read: - "readonly-abc123" # 只读渲染 - TLS 已启用——通过 Thurgio 内置 TLS 或反向代理。
- 反向代理已配置——Thurgio 绑定到
127.0.0.1,而非0.0.0.0。 - CORS 来源已限制——将
["*"]替换为具体域名:yaml setting: cors_origins: - "https://example.com" - 速率限制已启用——Thurgio 内置限流器(默认每 IP 每 60 秒 600 次,可用
THURGIO_RATE_LIMIT_REQUESTS/THURGIO_RATE_LIMIT_WINDOW_SECONDS调整)、nginx 或 Caddy。
运维
- systemd 单元 包含
Restart=always和安全加固(使用 idle 模式时必须为always,on-failure不会拉起正常退出)。 - 服务日志 可通过
journalctl -u thurgio查看(journald 自带轮转;仅当重定向到文件时才需要 logrotate)。 - 输出目录已创建 且 Thurgio 用户可写入。
- 模板目录 已填充且可读。
- 数据库路径 已明确(
DATABASE_URL,默认thurgio.db),且所在目录可写。
监控
- 健康端点 已监控(
GET /health返回版本、数据库状态、内存使用等)。 - 就绪端点 用于负载均衡健康检查(
GET /ready)。 - 已通过 Prometheus 收集指标(
GET /metrics,包含请求指标、熔断器快照与渲染线程池指标)。 - 缓存 TTL 配置合理:
yaml setting: ttl: provider: 600 # Provider 缓存有效期(秒,最小 60) snippet: 86400 # Snippet 缓存有效期(秒,最小 300)
性能
- 并发限制已根据服务器调整:
yaml setting: concurrency_limit: 20 # 根据 CPU 核心数和网络情况调整(默认 10) - 渲染线程池参数已调整(如模板渲染量大):
THURGIO_RENDERER_MIN_THREADS/THURGIO_RENDERER_MAX_THREADS。 - 反向代理中已启用 Keepalive(见上方 nginx/Caddy 配置)。
示例:完整生产环境布局
/usr/local/bin/
└── thurgio
/etc/
└── thurgio/
├── thurgio.yaml
├── provider.yaml
├── filter.yaml
├── rename.yaml
├── snippet.yaml
└── tpls/
├── clash.tpl
└── v2rayn.tpl
/var/
└── lib/
└── thurgio/
├── output/ # 渲染的输出文件
└── thurgio.db # SQLite 数据库(自动创建)
/etc/systemd/system/
└── thurgio.service
/etc/nginx/sites-enabled/
└── thurgio.conf
升级与迁移
升级 Thurgio 二进制的标准流程,以及各版本破坏性变更的迁移说明。首次安装与 systemd/Docker 部署细节见部署指南。
升级前准备
-
备份配置声明文件:
thurgio config backup该命令把
thurgio.yaml、5 个 sibling YAML 与tpls/模板目录复制到<root>/backups/<YYYYMMDD-HHMMSS>;它不加载配置、不访问网络、不碰数据库——即使当前配置已经改坏也能运行。详见config backup/restore/list与备份与回滚。 -
检查 CHANGELOG:对照下方版本间迁移说明,确认目标版本是否有需要手动处理的破坏性变更。没有对应条目时,按标准流程直接升级即可。
-
记录当前版本,便于回滚时选择旧 tag:
thurgio version
数据面无需额外备份:SQLite 数据库与渲染输出都位于数据根目录(systemd 部署默认 /var/lib/thurgio),替换二进制不影响数据;/etc/thurgio 配置目录也不随升级变动。
标准升级步骤
流程:下载新版本 → 校验 sha256 → 替换二进制 → 重启服务 → 健康验证。
systemd 部署
下载与 sha256 校验的完整命令见部署指南 · 从 GitHub Release 安装二进制;拿到校验通过的新二进制后:
sudo systemctl stop thurgio
sudo install -m 0755 thurgio /usr/local/bin/thurgio
sudo systemctl start thurgio
# 确认新版本与健康状态
thurgio version
sudo systemctl status thurgio
curl -fsS http://127.0.0.1:10100/ready
完整说明见部署指南 · 升级。
Docker 部署
拉取新镜像并重建容器(卷挂载保持不变,/data 数据卷自动保留):
docker pull ghcr.io/xylylab/thurgio:v<新版本>
docker rm -f thurgio
# 以与原先相同的参数重新 docker run,参见部署指南「使用 Docker」
私有镜像的一次性登录见部署指南 · 使用 Docker。
thurgio upgrade只检查 GitHub 是否有新版本并打印下载地址,不会自动安装。
版本间迁移说明
以下内容均提取自 CHANGELOG 原文,只列真实存在的破坏性变更或需要用户动作的条目;无对应条目的版本按标准流程升级即可。
| 版本 | 变更 | 用户需要做什么 |
|---|---|---|
| Unreleased | HTTP 参数绑定拆分(ADR-081):p 只承载配置参数,请求级覆盖移入新的 hp 绑定 | 模板与调用方改用 hp 传请求级参数 |
| 0.18.0 | Token 作用域路由前缀(ADR-063):read 路由移至 /app/read/...,admin 路由移至 /app/admin/...(breaking URLs) | 更新客户端、脚本与监控中的端点路径 |
| 0.18.0 | 无路径 item 路由(ADR-062):/app/read/l、/app/read/r 经 ?name= 取名,/app/admin/dev 经 value=true|false 取值 | 把原路径中的参数改为查询参数 |
| 0.18.0 | Source 改为 untagged(ADR-065):反序列化为纯字符串,http(s) 前缀即 URL 源(breaking:!Url/!File 配置语法不再解析) | 删除 source: 字段的 YAML 标签写法 |
| 0.18.0 | Provider/NodeList 合并(ADR-058):Provider 成为唯一代理解析实体,nodelist.yaml 与 provider: 引用键删除 | 将 nodelist 配置迁入 provider.yaml |
| 0.17.0 | 配置校验收紧:concurrency_limit >= 1、token >= 4 字符、interval 最小值;client URL 格式改为配置解析期校验 | 升级前核对新约束,避免旧配置解析失败 |
Unreleased:hp 参数绑定(ADR-081)
p 不再接收本次 HTTP 请求的查询参数覆盖;请求级参数落在新的 hp 绑定中(dot 展开合并,无请求参数时为 {})。模板中原本依赖“用查询参数临时覆盖某个 p.key“的地方,改为读取 {{ hp.key }}。上下文变量说明见配置指南。
从 0.17.x 迁移到 0.18.0
按影响范围逐项检查:
1. 端点路径调整(ADR-063 / ADR-062)
所有受保护的 read 路由移到 /app/read/... 下,admin 路由移到 /app/admin/... 下,认证中间件显式作用于各子路由。item 类路由不再从路径取参:/app/read/l 与 /app/read/r 用 ?name= 指定资源名,/app/admin/dev 用 value=true|false 指定开关;缺失或非法参数现在返回显式 400(而非 404 或被忽略的路径段)。当前完整端点列表见 API 参考,逐一核对客户端与反向代理规则。
2. source 字段去掉标签(ADR-065)
Source 序列化为纯字符串,http(s):// 开头解析为 URL 源,其余解析为文件源。旧配置中的显式 YAML 标签会导致解析失败:
# 旧(不再解析):
source: !Url "https://example.com/clash-config"
# 新:
source: "https://example.com/clash-config"
3. nodelist 并入 provider(ADR-058)
nodelist.yaml 与 provider 内的 provider: 引用键已删除。把原 nodelist 条目作为普通 provider 写入 provider.yaml(补上 type、template、filename 等字段)。当前 provider 结构见配置参考。
4. 行为变化:解析锚点改为配置文件目录(ADR-066,非破坏性标记)
sibling YAML 与模板目录现在相对配置文件所在目录解析,而不是进程工作目录(这修复了 systemd 场景下的文件源定位问题)。升级后确认 tpls/ 与相对路径输出仍指向预期位置。
更早版本
0.16.0 及之前没有标记面向用户的破坏性变更;除上表 0.17.0 的校验收紧外,按标准流程升级即可。
回滚
配置回滚:restore 会先把当前状态自动备份一份再覆盖写回,只恢复备份中包含的内容,不删除 root 里的其他文件:
thurgio config list
thurgio config restore <备份名>
二进制回滚:用升级前记录的旧版本 tag 重新走一遍下载、校验、替换流程(见标准升级步骤),然后重启:
sudo systemctl restart thurgio
curl -fsS http://127.0.0.1:10100/ready
数据库与输出文件位于数据根目录,替换二进制不影响其中数据(见部署指南 · 升级);若回滚同时涉及配置,先执行上面的 config restore 再重启。
升级后验证清单
-
thurgio version输出为目标版本 -
curl -fsS http://127.0.0.1:10100/ready返回 200 与{"status":"ready",...} -
GET /health显示新版本号且"database": "connected" -
journalctl -u thurgio -n 200无错误日志 - 触发一次构建或刷新,确认输出文件正常生成
- 本次版本涉及迁移项的(见版本间迁移说明),逐项确认已应用:端点路径、
hp绑定、source写法、provider 合并等 - 已接入 Prometheus 的实例确认
GET /metrics抓取正常
Dashboard 使用指南
Dashboard 是 thurgio serve 内置的 Web 控制台,以单文件模板 ui.tpl 的形式提供状态监控、渲染结果查看、管理操作与临时资源创建能力,无需额外前端构建或 CDN。
模板来源:
fixtures/examples/tpls/ui.tpl为唯一来源(thurgio init经include_str!分发写入用户tpls/ui.tpl),CSS/JS 内联,纯原生 JS + canvas 实现。
概述
是什么
默认 ui.tpl 仅展示 Server/Version/Last Build 与 SSE 自动刷新,Dashboard 在此基础上提供全功能控制台:
- 只读视图:系统健康、性能指标、熔断器状态、各 provider/snippet 渲染结果、构建事件流。
- 管理能力(仅
admin作用域可见):渲染参数编辑、构建/刷新/重载/保存、开发模式切换、临时 provider/snippet 创建。
如何访问
-
启动服务:
thurgio serve # 默认监听 0.0.0.0:10100,可用 --address / --port 覆盖,详见 [Serve 命令](./cli/serve.md) -
打开登录页:
http://<address>:<port>/app/login输入令牌完成校验后跳转至 Dashboard。或直接携带令牌访问:
http://<address>:<port>/app/read/ui?token=<your-token>令牌亦可通过
Authorization: Bearer <token>请求头传递(查询参数优先),与 API 参考 认证方式一致。
提示:
GET /仅返回Hello world!健康问候,不承载 Dashboard;Dashboard 固定路由为GET /app/read/ui,需认证。
登录与令牌
作用域
在 thurgio.yaml 中按作用域分组配置(至少配置一个):
# thurgio.yaml
setting:
tokens:
admin:
- "admin-token-xxx"
read:
- "read-token-yyy"
- Read — 仅可访问只读渲染端点。
- Admin — 可访问全部端点(含管理端点),同时满足 Read 要求。
未认证或作用域不足的请求返回 401 Unauthorized。详见 API 参考 与 配置指南。
登录流程
-
GET /app/login— 返回登录页 HTML(公开端点,无需认证)。 -
POST /app/login— 接收 JSON{"token": "..."},校验作用域:curl -X POST http://localhost:10100/app/login \ -H "Content-Type: application/json" \ -d '{"token":"admin-token-xxx"}'成功响应:
{ "ok": true, "scope": "admin" }scope为"admin"或"read";缺失token字段返回400,无效令牌返回401。
登录页校验通过后,前端以 ?token= 形式跳转至 /app/read/ui。Dashboard 内 token 仅从 URL 读取,不写入 localStorage/sessionStorage;刷新或分享链接时需保留 URL 中的 token 参数。
权限差异
| 能力 | Read token | Admin token |
|---|---|---|
| 查看 Dashboard、系统状态、指标、渲染项、事件日志 | 有 | 有 |
| 参数编辑、操作区(Build/Refresh/Reload/Save/Dev)、临时资源创建 | 无(前端不展示,接口返回 401) | 有 |
UI 按 GET /app/read/ui 注入的 Tera 上下文变量 scope("admin" / "read")显隐管理模块,实际权限由服务端中间件强制。
页面功能走查
下表按 design/specs/spec-dashboard.md 的功能列表逐项说明,并标注对应的 HTTP API 端点。
顶栏
- 展示:版本、当前
scope徽章、连接状态、手动刷新按钮。 - 数据源:
GET /v(版本字符串)与GET /app/read/ui注入的scope/ Teraversion上下文。
系统状态卡
- 展示:DB 连接、内存占用、CPU 核心数、状态、时间戳。
- 数据源:
GET /health(公开端点,返回status/version/database/memory_usage_mb/cpu_cores/timestamp,详见 API 参考)。
指标卡 + sparkline
- 展示:请求数、错误数、平均延迟、代理数、渲染线程、缓存命中/未命中、命中率;请求/延迟/线程三条 canvas 趋势线。
- 数据源:
GET /metrics(Prometheus 暴露格式,含请求指标、熔断器快照、渲染线程池指标renderer_active_threads等)。 - 刷新:10s 静默轮询,sparkline 采样上限 40 点。
断路器
- 展示:每 breaker 状态(
Closed/Open/Half-Open)与失败计数。 - 数据源:
GET /metrics(熔断器快照段)。 - 交互:默认折叠,仅显示标题栏 + 总数摘要,点击展开完整内容;刷新页面恢复默认折叠。
渲染项
- 展示:两类 tab(provider / snippet),点击展开
<pre>预览,支持复制、复制链接、新标签打开;超长内容自动折叠(见下文)。 - 数据源:
GET /app/read/c— 渲染完整 Tera 上下文为 JSON(含scope,config内proxies/content已剔除),用于获取资源键名与参数p。GET /app/read/l?name={name}— 按名称渲染 provider(支持?d=下载头与请求级规则覆盖pre_rename/filter/rename/ap_*/emoji/udp/tpl,详见 API 参考)。GET /app/read/r?name={name}— 按名称渲染 snippet。
- 交互:点击 tab 按需拉取对应
l/r内容并注入<pre>;超长时仅显示前 100 行 +… 共 M 行提示与展开/收起按钮;复制按钮始终复制完整内容,不受折叠影响;复制链接与新标签打开均复制/打开带token的完整 URL。
事件日志
- 展示:
build_complete时间戳流,上限 50 条。 - 数据源:
GET /app/read/events(SSE,text/event-stream,每次构建完成推送build_complete,每 15s 发送keep-alive心跳)。
参数编辑(仅 admin)
-
展示:扁平 KV 表格(点分路径如
dns.enabled),提交后与内存中配置参数深度合并。 -
数据源:读取
GET /app/read/c中的p字段,写入GET /app/admin/p(查询字符串即参数,点分键写入嵌套结构)。 -
交互:默认折叠,仅显示标题栏 + 参数数量摘要,点击展开;提示“值只能覆盖不能删除“(深合并语义,无清除 API)。
-
示例:
curl "http://localhost:10100/app/admin/p?dns.enabled=true&port=9090&token=admin-token-xxx"
操作区(仅 admin)
| 按钮 | 端点 | 行为 |
|---|---|---|
| Build | GET /app/admin/write | 完整构建:更新所有 provider/snippet、渲染并写盘;请求同步阻塞至构建完成,fetch 生命周期即状态条,失败显示响应 message |
| Refresh | GET /app/admin/refresh | 强制更新所有 provider/snippet(不写盘) |
| Reload | GET /app/admin/reload | 重载 Tera 模板引擎,拾取磁盘变更 |
| Save | GET /app/admin/save | 将当前已 fetch 的 provider/snippet 响应持久化到 DB(未 fetch 项跳过,避免覆盖 last-known-good) |
| Dev | GET /app/admin/dev?value={value} | 启用/禁用开发模式(value 必填 true / false,响应返回当前值字符串);开发模式下每次渲染前重载模板 |
临时资源(仅 admin)
-
展示:表单 + 结果内嵌预览(超长折叠逻辑同渲染项)。
-
端点:
GET /app/admin/adhoc(创建一次性 provider/snippet,不写入配置文件,缓存持久化到 DB,重启后命中缓存即恢复;按adhoc.max_items/adhoc.ttl_seconds自动清理)。 -
查询参数:
参数 必填 说明 type是 provider/snippeturl是 数据源 URL name否 名称,缺省自动生成 adhoc-<8 位 hex>format否 clash/meta/v2rayn/sip002/surge/auto(仅 provider 生效),默认autotpl否 模板名,缺省回退 setting.adhoc.tpl.provider/tpl.snippetpre_rename/filter/rename否 逗号分隔的规则名,按序应用(未知规则名静默忽略) force否 true时忽略内存 + DB 缓存强制重抓curl "http://localhost:10100/app/admin/adhoc?type=provider&url=https://example.com/clash.yaml&token=admin-token-xxx"响应为
text/plain渲染内容,并携带subscription-userinfo头;同名临时资源可经GET /app/read/l/GET /app/read/r后续引用,对配置中同名正式项呈遮蔽效果。
刷新与折叠行为
- SSE 驱动:收到
build_complete后触发 toast,并重拉GET /health、GET /app/read/c与GET /metrics。 - 指标轮询:10s 静默刷新
GET /metrics。 - 超长面板折叠:渲染项展开区与临时资源预览,内容行数 > 100 行或高度 > 60vh 时折叠;判定为内容注入
<pre>后一次性基于行数/scrollHeight判定;长内容以textContent一次性注入。 - 段级默认折叠:断路器与参数编辑模块默认折叠,点击标题栏展开。
权限矩阵(接口层)
| 端点 | Read token | Admin token |
|---|---|---|
GET /app/read/ui、GET /app/read/c、GET /app/read/events、GET /app/read/l?name=、GET /app/read/r?name= | 允许 | 允许 |
GET /app/admin/p | 401 | 允许 |
GET /app/admin/write、GET /app/admin/refresh、GET /app/admin/reload、GET /app/admin/save、GET /app/admin/dev?value=、GET /app/admin/adhoc | 401 | 允许 |
自定义界面
覆盖方式
Dashboard 页面由模板 tpls/ui.tpl 渲染(端点 GET /app/read/ui)。thurgio init 会在目标目录生成:
<dir>/
├── thurgio.yaml
├── provider.yaml
├── filter.yaml
├── rename.yaml
├── params.yaml
├── snippet.yaml
└── tpls/
├── base.tpl
└── ui.tpl # Dashboard 页面模板
来源为 fixtures/examples/tpls/ui.tpl,详见 Init 命令 与 模板示例。
可直接编辑 tpls/ui.tpl 自定义 Dashboard:
# 编辑后重载
curl "http://localhost:10100/app/admin/reload?token=admin-token-xxx"
# 或以开发模式自动重载
thurgio serve --dev
# 或运行时切换
curl "http://localhost:10100/app/admin/dev?value=true&token=admin-token-xxx"
模板上下文可用变量与普通模板一致(config / p / hp / headers / version),另注入 scope("admin" / "read")用于按权限显隐模块,详见 API 参考 中“渲染 UI“与“渲染上下文“小节。
注意事项
- 勿删除
tpls/ui.tpl— 服务器自带 UI 依赖它,删除后GET /app/read/ui将渲染失败。 - 单文件约束 — CSS/JS 需内联,无外部依赖/CDN,保持单文件可分发。
- 热更新 —
thurgio serve默认仅监听主配置thurgio.yaml变更,不监听模板目录;模板变更需--dev或手动调GET /app/admin/reload,详见 Serve 命令 与 常见问题。 - 模板语法 — 使用 Tera(类 Jinja2),内置 30+ 过滤器,详见 模板示例 与 配置指南。
常见问题
Read 令牌看不到操作区是正常现象吗?
是。参数编辑、操作区、临时资源仅对 admin 作用域可见,Read 令牌访问对应管理端点会返回 401。如需操作能力,请使用 setting.tokens.admin 中配置的令牌登录。
URL 中的 token 会留在浏览器历史中吗?
会。Dashboard 与登录页均以 ?token= 传递令牌,浏览器历史与“复制链接“/“新标签打开“生成的 URL 均会包含明文 token,属已知取舍。避免在公共设备上保留历史,或使用 Authorization: Bearer 方式通过 API 访问。
构建一直转圈或返回错误如何排查?
- 操作区 Build 为同步阻塞请求,
fetch结束即构建结束;失败时状态条会显示响应的message。 - 可结合
GET /health、GET /metrics、GET /app/read/events(SSE)与GET /app/read/c查看上下文,确认参数与熔断器状态。 - 模板相关问题可用
RUST_LOG=debug thurgio serve查看日志,或切至开发模式GET /app/admin/dev?value=true后重试。
常见问题
如何开始?
运行 thurgio init 生成最小配置(含 provider.yaml、filter.yaml 等拆分文件与 tpls/ 模板目录),然后执行 thurgio build。用 thurgio check 验证配置。
如何添加代理提供者?
编辑 provider.yaml,填入你的提供者 URL 和类型(clash/meta/v2rayn/sip002/surge)。本地文件直接用路径字符串 source: "path",远程 URL 用 source: "https://..."(http(s) 开头自动识别为 URL)。
如何过滤代理?
在 filter.yaml 中定义过滤规则。示例:
# 简写:包含任一关键字的代理(忽略大小写)
jp: "日本|Japan"
# 排除(negate)
rm_jp:
- contain: [日本, Japan]
negate: true
contain/start/end/type/regex 五种匹配器,详见配置参考。
如何重命名代理?
在 rename.yaml 中定义正则替换规则([正则, 替换串],支持 $1 反向引用):
us: ["(.*)", "🇺🇸 $1"]
如何启用 TLS?
使用 thurgio serve --tls-cert cert.pem --tls-cert-key key.pem(可加 --tls-port、--tls-only),或用环境变量 TLS_CERT / TLS_CERT_KEY。
如何监控配置变更?
使用 thurgio build --watch 在当前目录文件变更时自动重建;thurgio serve 也会监听配置文件变更并自动重建。模板目录(tpls/)变更会自动重载渲染器。
如何获取实时构建通知?
通过 Server-Sent Events 连接到 /app/read/events(需令牌认证,?token= 或 Authorization: Bearer)。每次构建完成会推送 build_complete 事件。也可设置 THURGIO_WEBHOOK_URL 在构建完成时收到 HTTP 通知。
如何查看渲染后的配置?
启动 thurgio serve 后请求 /app/read/l?name={provider} 或 /app/read/r?name={snippet},例如:
curl "http://localhost:10100/app/read/l?name=my_provider&token=my-token"
输出文件在哪里?
在 thurgio.yaml 的 setting.output 中配置(按渲染目标 + 模板名分组):
setting:
output:
provider:
"clash.tpl":
- "output/proxies.yaml"
snippet:
"rule.tpl":
- "output/rules.yaml"
条目键为模板名,值是要写入该模板默认输出的目录列表。
如何升级?
运行 thurgio upgrade 检查 GitHub Releases 是否有新版本;或 cargo install --path crates/thurgio-cli(开发构建)/ 下载最新发布版本。
故障排查
系统性的排查步骤(日志、启动失败、HTTP 错误码速查、熔断器、数据库问题)见故障排查。
配置改坏了,起不来怎么办?
用 thurgio check 定位解析错误;改坏前做过备份的话直接 thurgio config restore <备份名> 回滚——config 命令不加载配置,坏配置下也能运行。
订阅源挂了构建会失败吗?
不会立即失败。抓取失败时回退数据库中的 last-known-good 缓存;连续失败触发熔断器(open 后跳过该源一段时间),恢复情况看 GET /app/read/health 聚合端点。
过滤/改名规则好像没生效?
规则名拼错会被静默忽略(解析期丢弃未知名字)。跑 thurgio check,悬空引用会以 WARNING 列出。
模板报 rule set 'xxx' not found?
ruleset 过滤器的名字在 ruleset.yaml 与内置资产中都找不到。注意它与 filter/rename 不同:未定义直接报错而非静默忽略。
输出文件没有生成?
检查两点:provider 的 filename 是否写了;若期望默认输出,setting.output 的键是模板名且目录条目要匹配 item 自己的模板。thurgio check 会报告输出父目录缺失。
改了模板但不生效?
thurgio build --watch下模板变更直接触发重建。serve默认只监听主配置文件,模板变更不监听——开发时加--dev(每次渲染前重载模板)或调GET /app/admin/reload。
渲染结果不对怎么调试?
RUST_LOG=debug,thurgio=trace thurgio build # 详细日志
RUST_LOG_FORMAT=json thurgio serve # 结构化日志
另可用 GET /app/read/c 直接查看渲染上下文 JSON,确认变量取值。
401 但 token 明明对?
令牌分 Read/Admin 作用域:Read 令牌访问 /app/admin/* 会被拒。检查用的是哪组令牌、请求的是哪个命名空间。
故障排查
按「先看日志、再定位子系统」的顺序排查问题。常见问题的快速解答见常见问题。
第一步:看日志
如何调整日志详细程度?
日志级别与过滤由 RUST_LOG 环境变量控制(默认 info)。常用取值:
# 完整调试输出(更细可用 trace)
RUST_LOG=debug thurgio serve
# 只看订阅抓取流程(更新 + HTTP 客户端)
RUST_LOG="thurgio_app_update=debug,thurgio_request=debug" thurgio serve
# 只看数据库操作 / 模板渲染 / 配置热重载
RUST_LOG=thurgio_db=debug thurgio serve
RUST_LOG=thurgio_renderer=debug thurgio serve
RUST_LOG=thurgio_infra=debug thurgio serve
# 生产排障:安静模式
RUST_LOG="thurgio=info,warn" thurgio serve
日志在哪里看?
thurgio 把日志输出到 stdout/stderr:
- systemd 部署:由 journald 收集,journald 自带容量与轮转管理,无需额外配置:
journalctl -u thurgio -f # 跟随最新日志 journalctl -u thurgio -n 200 # 最近 200 行 journalctl -u thurgio --since "1 hour ago" - 仅当你自行把日志重定向到文件时才需要配置 logrotate,示例见部署指南。
需要结构化日志时设置 RUST_LOG_FORMAT=json,便于用 jq 过滤:
RUST_LOG_FORMAT=json thurgio serve | jq .
如何用 trace_id 追踪一次请求?
HTTP 错误响应的 JSON 体中带有 trace_id 字段。文本日志中直接搜索 trace_id= 即可定位同一次更新流程的全部日志;JSON 日志用 jq 过滤:
RUST_LOG_FORMAT=json thurgio serve | jq 'select(.fields.trace_id == 42)'
服务无法启动
报端口被占用怎么办?
默认监听 0.0.0.0:10100(setting.address / setting.port)。找出占用端口的进程:
ss -ltnp 'sport = :10100'
然后停掉占用进程,或换端口启动(命令行参数优先于配置):
thurgio serve --address 127.0.0.1 --port 10200
# 等价环境变量:ADDRESS=127.0.0.1 PORT=10200 thurgio serve
配置解析错误起不来怎么办?
thurgio check
check 会解析完整配置并打印逐项检查结果,不访问网络、不写任何文件。如果是改配置改坏的,直接回滚:
thurgio config list
thurgio config restore <备份名>
config 命令不加载配置,坏配置下也能运行。更多配置陷阱(规则名拼错被静默忽略等)见常见问题。
TLS 启动失败怎么办?
TLS 通过 --tls-cert / --tls-cert-key(或环境变量 TLS_CERT / TLS_CERT_KEY)启用。逐一排查:
# 1. 证书与私钥文件存在且进程用户可读
ls -l cert.pem key.pem
sudo -u thurgio head -c 64 /etc/thurgio/cert.pem
# 2. 同时提供明文与 HTTPS 时,--tls-port 必须与 --port 不同
thurgio serve --tls-cert cert.pem --tls-cert-key key.pem --tls-port 10443
# 3. --tls-only 要求证书与私钥同时提供,缺一会报错
thurgio serve --tls-only --tls-cert cert.pem --tls-cert-key key.pem
生产环境更常见的做法是让反向代理(nginx/Caddy)终止 TLS,thurgio 只监听 127.0.0.1,参见部署指南。
提示数据库打不开或权限不足怎么办?
数据库位置由 DATABASE_URL 指定(默认相对数据根目录 -r/--root 的 thurgio.db),所在目录必须对运行用户可写:
ls -ld /var/lib/thurgio
sudo -u thurgio test -w /var/lib/thurgio && echo writable
可用公开端点确认数据库状态:GET /ready 在数据库可读时返回 200 {"status":"ready",...},不可读时返回 503 {"status":"not_ready","database":"disconnected"}。
HTTP 错误码速查表
所有 API 错误以 JSON 返回,形如 {"error":"circuit_open","message":"...","kind":"CircuitOpen","status":503,"trace_id":42}。完整端点语义见 API 参考。
| 状态码 | 原因 | 排查动作 |
|---|---|---|
401 | 令牌无效/缺失,或作用域不足(Read 令牌访问 /app/admin/*);上游订阅源返回 401/403 时也会归入此类 | 核对用的是 Read 还是 Admin 令牌组、请求的是哪个命名空间;用 POST /app/login 校验令牌作用域 |
403 | 通常并非 thurgio 返回——多来自反向代理的访问控制(如限制 /metrics 的内网白名单) | 检查 nginx 的 allow/deny 或 Caddy 的 respond 403 规则 |
404 | 资源不存在:provider/snippet 名字拼错,或该 provider 响应中没有可解析的流量元数据 | 用 GET /app/read/subs 列出全部订阅名核对拼写 |
429 | 超过速率限制(默认每 IP 每 60 秒 600 次)或并发限制,响应带 Retry-After 头 | 按 Retry-After 等待后重试;降低客户端轮询频率;必要时用 THURGIO_RATE_LIMIT_REQUESTS / THURGIO_RATE_LIMIT_WINDOW_SECONDS 调整限额 |
500 | 服务器内部错误(数据库/存储、模板渲染、内部异常) | 取响应体中的 trace_id 到日志中定位;检查模板语法与磁盘空间 |
502 | 从上游订阅源抓取失败或超时(上游请求超时默认 9 秒,REQUEST_TIMEOUT 可调)且无缓存可回退 | 直接 curl 上游 URL 验证可达性(DNS、网络、上游凭证) |
503 | 两种情况:渲染时熔断器已打开且无缓存可回退(kind: "CircuitOpen");或 GET /ready 报告数据库不可读 | 前者见下节「订阅抓取失败」;后者检查数据库路径与磁盘 |
订阅抓取失败
上游挂了构建会失败吗?
不会立即失败。抓取失败时回退到数据库中的 last-known-good 缓存继续渲染;只有连缓存都没有时才会向上传播错误。
熔断器 open 是什么意思?
连续失败达到阈值(默认 5 次)后熔断器进入 open:一段时间内(基础 30 秒,带随机抖动)跳过该源不再发起请求,避免无效重试风暴。超时后进入 half-open 放行探测请求,探测成功即回到 closed。熔断器状态持久化到数据库,重启后恢复。
日志中搜索 circuit breaker 可以看到状态切换(如 circuit breaker opened after N failures、错误 circuit breaker open for <name>)。
如何观察各订阅源的健康状况?
curl "http://localhost:10100/app/read/health?token=my-token"
为每个 provider/snippet 报告熔断器状态(closed/open/half-open)、失败计数、最后失败时间与最后成功更新时间,并汇总为总体状态:healthy(无 open/half-open)、degraded(部分熔断)、unhealthy(全部 open)。
上游恢复了,如何让它立刻重试?
- 等待半开探测自动恢复(无需干预);或
- Admin 令牌强制刷新一次:
GET /app/admin/refresh;或 - 经
GET /app/admin/s(prov/url请求头)更新源 URL——会同时重置该 provider 的熔断器并触发强制更新。
输出不更新
为什么改了上游但输出没变?
更新按 TTL 判断:缓存未过期时直接复用,不重新抓取。TTL 在 setting.ttl 中配置,默认 provider 600 秒(最小 60 秒)、snippet 86400 秒(最小 300 秒):
setting:
ttl:
provider: 600
snippet: 86400
想立即刷新:
thurgio build --force # 忽略缓存状态强制重新获取所有订阅源
服务运行时等价的 Admin 端点:GET /app/admin/refresh(仅更新)或 GET /app/admin/write(更新 + 渲染 + 写盘)。
为什么 --watch 下改动没有立即重建?
监听模式的变更事件经 500ms 防抖合并后才触发重建,属正常设计。另外它忽略构建自身的写入(输出目录与 SQLite 数据库文件),避免自触发循环。
为什么 serve 下改模板没生效?
serve 只监听主配置文件本身,模板变更不在监听范围内。两种解决方式:
# 方式一:开发模式启动(每次渲染前重新加载模板)
thurgio serve --dev
# 方式二:不重启,手动触发模板引擎重载
curl "http://localhost:10100/app/admin/reload?token=my-admin-token"
输出文件缺失、规则静默失效等其他「不生效」场景见常见问题。
数据库问题
目录下的 thurgio.db-wal / thurgio.db-shm 是什么?
thurgio 以 SQLite WAL 模式运行数据库(支持多进程共享同一文件)。-wal 与 -shm 是 WAL 模式的伴生文件,属于正常现象。不要在服务运行期间手动删除或移动它们——连同主库文件一起由 SQLite 管理,删除可能导致数据丢失。
怀疑磁盘空间不足怎么排查?
磁盘写满时数据库写入与输出文件发布都会失败,典型表现是请求返回 500(Persistence 类错误)或 GET /ready 变为 503。排查:
df -h /var/lib/thurgio # 检查数据库与输出所在分区
du -sh /var/lib/thurgio/* # 定位大文件
清理出空间后,确认 /ready 恢复 200 即可;无需迁移数据。
可观测性指南
本章介绍 Thurgio 的三类可观测入口如何配合使用:Prometheus 指标(GET /metrics)、订阅健康度聚合(GET /app/read/health)与结构化日志。
概述
| 入口 | 路径 | 认证 | 用途 | 适合告警 |
|---|---|---|---|---|
| 指标 | GET /metrics | 无需令牌(公开端点,见 API 参考) | 机器可读的时序数据:请求、构建管线、缓存、熔断器、渲染线程池、内存 | 是 |
| 健康度 | GET /app/read/health | 需 Read/Admin 令牌 | 面向订阅的业务健康度:按 provider/snippet 汇总熔断器状态与最后成功时间 | 否(适合 Dashboard/人工巡检) |
| 日志 | stdout/stderr(journald) | — | 请求追踪、错误链、事件总线与熔断器状态切换 | 否(适合排障) |
三者在 部署指南 的监控检查清单中均有对应项。
Prometheus 接入
认证说明
GET /metrics 归类为公开端点,与 GET /health、GET /ready 一致,无需 ?token= 或 Authorization: Bearer 头。反向代理层应对其做网络级访问控制,禁止公网直接暴露。详见 部署指南 的 Nginx/Caddy 示例——allow 10.0.0.0/8 / deny all 或 remote_ip 匹配。
scrape_config 示例
以下配置可直接放入 prometheus.yml 的 scrape_configs:
scrape_configs:
- job_name: "thurgio"
metrics_path: /metrics
scrape_interval: 15s
scrape_timeout: 10s
static_configs:
- targets: ["127.0.0.1:10100"]
labels:
instance: "thurgio-prod-01"
若 Prometheus 与 Thurgio 不在同一内网,经反向代理抓取时改用域名并按需添加 TLS 配置:
scrape_configs:
- job_name: "thurgio"
metrics_path: /metrics
scheme: https
static_configs:
- targets: ["thurgio.example.com"]
# 若反向代理对 /metrics 做了 IP 白名单,Prometheus 需部署在白名单网段内
验证抓取是否生效:
curl -fsS http://127.0.0.1:10100/metrics | head -n 40
# 应返回 Prometheus 文本暴露格式,首行为 # HELP http_requests_total
GET /metrics 的响应为纯文本 Prometheus 暴露格式,非 JSON;每次请求实时计算 process_memory_usage_mb(VmRSS)与熔断器快照并追加渲染线程池块(见 crates/thurgio-server/src/routes/public.rs:metrics 与 crates/thurgio-server/src/metrics.rs:render/render_renderer_metrics)。
OTLP 可选链路
thurgio-infra/src/metrics.rs 定义的 MetricsRecorder 在启用 otlp feature 时经 OtelMetrics 写入全局 OpenTelemetry Meter Provider(OTEL_EXPORTER_OTLP_ENDPOINT,默认 http://localhost:4317,gRPC)。MetricsCollector 对未映射的指标名会转发至 fallback recorder(set_fallback),未启用 otlp 时直接丢弃。因此下表标注为 OTLP 的指标仅在 cargo run -p thurgio-cli --features otlp 或对应 release 构建下可见;Prometheus 标注的指标无论是否启用 OTLP 均出现在 /metrics。
指标参考
名称与类型以源码为准。来源文件:
crates/thurgio-infra/src/metrics.rs(names模块,规范名称单一来源)crates/thurgio-server/src/metrics.rs(MetricsCollector::render与render_renderer_metrics的 Prometheus 渲染)
1. HTTP 请求(MetricsCollector 直接渲染)
| 名称 | 类型 | 含义 |
|---|---|---|
http_requests_total | counter | 累计 HTTP 请求数(record_request 每次 +1) |
http_errors_total | counter | 累计 status >= 400 的请求数 |
active_connections | gauge | 当前在途请求数(RequestTimer::new +1 / finish -1) |
http_request_duration_ms | gauge | 平均请求耗时(毫秒,request_duration_sum / count) |
http_request_duration_histogram | histogram | 请求耗时直方图;桶边界 [1, 5, 10, 50, 100, 500, 1000, 5000, 10000] 毫秒,另含 +Inf 桶、_sum 与 _count |
| `http_status_codes{category=“1xx | 2xx | 3xx |
http_rate_limited_total | counter | 因并发或速率限制被拒绝并返回 429 的请求数(RATELIMIT = http_rate_limited_total) |
2. 构建与更新管线
| 名称 | 类型 | 含义 | 暴露 |
|---|---|---|---|
config_updates_total | counter | provider/snippet 更新总次数(record_config_update) | Prometheus |
config_updates_failed_total | counter | 更新失败次数(CONFIG_UPDATES_FAILED,经 MetricsRecorder 映射) | Prometheus |
config_renders_total | counter | 模板渲染总次数(record_config_render,含 provider 主输出、具名输出与 snippet) | Prometheus |
config_builds_total | counter | 完整构建(build)总次数 | Prometheus |
build.completed | counter | build 管线成功完成计数(BUILD_COMPLETED) | Prometheus |
build.update.failure | counter | build 更新阶段失败计数(BUILD_UPDATE_FAILURE) | Prometheus |
build.provider.success{name="<provider>"} | counter | 单个 provider 拉取成功计数(ITEM_PROVIDER.success,label name) | Prometheus |
build.provider.failure{name="<provider>"} | counter | 单个 provider 拉取失败计数(ITEM_PROVIDER.failure) | Prometheus |
build.snippet.success{name="<snippet>"} | counter | 单个 snippet 拉取成功计数(ITEM_SNIPPET.success) | Prometheus |
build.snippet.failure{name="<snippet>"} | counter | 单个 snippet 拉取失败计数(ITEM_SNIPPET.failure) | Prometheus |
renderer.reload | counter | 渲染器重载次数(RENDERER_RELOAD,dev 模式或 GET /app/admin/reload 触发) | Prometheus |
config_reload_failed_total | counter | 配置热重载重解析失败次数(CONFIG_RELOAD_FAILED,失败时保留旧配置,不发事件) | Prometheus |
proxy_count | gauge | 当前代理总数(PROXY_COUNT,gauge_set,可增可减) | Prometheus |
build.duration | histogram | 完整 build 耗时(秒,BUILD_DURATION) | OTLP |
| `build.update.duration{status=“success | failure“}` | histogram | build 更新阶段耗时(秒,label status) |
build.render.duration | histogram | build 渲染阶段耗时(秒,BUILD_RENDER_DURATION) | OTLP |
build.update.success | counter | build 更新阶段成功计数(BUILD_UPDATE_SUCCESS) | OTLP |
autobuild.success | counter | AutoBuildHandler 自动重建成功计数 | OTLP |
autobuild.failure | counter | AutoBuildHandler 自动重建失败计数 | OTLP |
proxy_parse_failed_total{name="<provider>"} | counter | 单 provider 解析阶段丢弃的坏条目数(label name) | OTLP |
ruleset_fetch_failed_total{name="<ruleset>"} | counter | 远程规则集拉取失败次数(回退旧文本,不传播错误;label name) | OTLP |
3. 缓存
| 名称 | 类型 | 含义 | 暴露 |
|---|---|---|---|
cache_hits_total | counter | TTL 缓存命中次数(CACHE_HIT,label `kind=provider | snippet |
cache_misses_total | counter | TTL 缓存未命中次数(CACHE_MISS,实际发起抓取时 +1) | Prometheus |
4. 熔断器(Circuit Breaker)
| 名称 | 类型 | 含义 |
|---|---|---|
circuit_breaker_state{name="<provider|snippet>"} | gauge | 熔断器状态:0 = Closed(正常)、1 = Open(拒绝抓取)、2 = HalfOpen(试探) |
circuit_breaker_fail_count{name="<provider|snippet>"} | counter | 熔断器累计失败次数(CircuitBreakerSnapshot::failure_count) |
快照来源为 CircuitBreakerPool::snapshots(),与 GET /app/read/health 的 failure_count 同源;键格式 circuit_breaker:{identity} 由 thurgio-infra::circuit_breaker::SNAPSHOT_KEY_PREFIX 单一拥有。
5. 资源与内存
| 名称 | 类型 | 含义 | 暴露 |
|---|---|---|---|
process_memory_usage_mb | gauge | 当前 RSS 内存(MB,get_memory_usage 读取 /proc/self/status 的 VmRSS;非 Linux 置 0) | Prometheus |
process_memory_baseline_mb | gauge | 空闲驱逐后的引擎基线 RSS(MB,PROCESS_MEMORY_BASELINE) | Prometheus |
data_cache_memory_mb | gauge | 空闲驱逐释放的数据缓存内存(MB,DATA_CACHE_MEMORY,rss_before - rss_after) | Prometheus |
6. 渲染线程池(render_renderer_metrics)
由 AppState::renderer().pool_metrics() 在 /metrics 末尾追加,HELP/TYPE/value 三行一组:
| 名称 | 类型 | 含义 |
|---|---|---|
renderer_active_threads | gauge | 当前活跃渲染线程数 |
renderer_backlog_smoothed | gauge | EMA 平滑后的待渲染积压深度(浮点,保留两位小数) |
renderer_threads_spawned_total | counter | 累计创建的渲染线程数 |
renderer_threads_exited_total | counter | 累计退出的渲染线程数 |
renderer_slots_total | gauge | 渲染线程池总槽位数 |
池参数由环境变量 THURGIO_RENDERER_MIN_THREADS / THURGIO_RENDERER_MAX_THREADS 调整(默认 min 1 / max 64),见 部署指南。
告警建议
下列规则为 Prometheus groups YAML 片段,可直接加入 prometheus.yml 的 rule_files 或 Alertmanager 规则文件。阈值按需调整。
groups:
- name: thurgio
interval: 30s
rules:
# 1. 熔断器 Open 超过 10 分钟
- alert: ThurgioBreakerOpen
expr: circuit_breaker_state == 1
for: 10m
labels:
severity: warning
annotations:
summary: "订阅 {{ $labels.name }} 熔断器持续 Open"
description: "该 provider/snippet 已连续 10 分钟拒绝抓取,检查上游可用性与网络。"
# 2. 任意订阅拉取失败率升高(5 分钟内失败 > 0)
- alert: ThurgioProviderFailure
expr: increase(build.provider.failure[5m]) > 0
labels:
severity: warning
annotations:
summary: "provider {{ $labels.name }} 拉取失败"
description: "5 分钟内出现拉取失败,对照日志 trace_id 排查上游与解析错误。"
# 3. 渲染线程池积压过高
- alert: ThurgioRendererBacklogHigh
expr: renderer_backlog_smoothed > 20
for: 5m
labels:
severity: warning
annotations:
summary: "渲染线程池积压过高"
description: "平滑积压 {{ $value }} 超过 20,考虑调大 THURGIO_RENDERER_MAX_THREADS 或降低并发渲染压力。"
# 4. 限流误伤(正常流量下不应出现 429)
- alert: ThurgioRateLimited
expr: increase(http_rate_limited_total[5m]) > 0
labels:
severity: warning
annotations:
summary: "请求被限流"
description: "5 分钟内出现 429,检查是否误伤正常客户端,必要时调整 THURGIO_RATE_LIMIT_* 或反向代理限流配置。"
# 5. 配置热重载持续失败
- alert: ThurgioConfigReloadFailed
expr: increase(config_reload_failed_total[10m]) > 0
labels:
severity: critical
annotations:
summary: "配置热重载失败"
description: "10 分钟内出现重解析失败,旧配置仍生效但新配置未生效,检查 YAML 语法与模板存在性。"
可选补充(按需启用):
# 6. 内存基线异常增长(按实例规格调整阈值)
- alert: ThurgioMemoryHigh
expr: process_memory_usage_mb > 800
for: 10m
labels:
severity: warning
annotations:
summary: "内存占用过高"
description: "RSS {{ $value }} MB 超过 800 MB,检查是否存在订阅膨胀或渲染泄漏。"
与 GET /app/read/health 的分工
| 维度 | GET /metrics | GET /app/read/health |
|---|---|---|
| 定位 | 面向监控系统的时序指标 | 面向人的订阅健康度聚合 |
| 数据 | 计数器/直方图/gauge,带时间序列 | 当前快照:overall(healthy/degraded/unhealthy)、summary{total,closed,open,half_open} 与 items[] 明细 |
| 熔断器 | circuit_breaker_state(数值 0/1/2)与 circuit_breaker_fail_count | breaker(closed/open/half-open)、failure_count、last_failure_at(Unix 秒,null 表示从未失败) |
| 时间 | 仅计数,不含时间戳 | last_update_at(RFC 3339,取 DB time 列;未抓取过为 null;epoch 哨兵已过滤) |
| 排序 | 按 Prometheus 文本顺序 | provider 在前(配置声明顺序)、snippet 在后 |
| 触发 | 不触发更新 | 不触发更新(仅读熔断器池与 DB) |
| 认证 | 公开 | 需 Read/Admin 令牌(见 API 参考) |
使用建议:
- 告警走
/metrics(Prometheus 规则见上节)。 - 巡检/排障走
/app/read/health:overall == degraded时检查items中breaker == open的条目,结合failure_count与last_failure_at判断故障时长;last_update_at == null表示该订阅从未成功抓取。 - Dashboard 可同时消费两者:时序图用
/metrics,订阅列表用/app/read/health(与内置 UI 的数据源一致,见docs/src/dashboard.md)。
日志
Thurgio 经 tracing + tracing-subscriber 输出日志到 stdout/stderr,由 systemd 的 journald 或容器日志驱动收集。日志级别由 RUST_LOG 控制,格式由 RUST_LOG_FORMAT 控制(见 部署指南 与 thurgio-infra::init_logger)。
| 环境变量 | 默认 | 说明 |
|---|---|---|
RUST_LOG | info | EnvFilter 语法,如 info,thurgio_app_update=debug;just 默认 info,opentelemetry=off |
RUST_LOG_FORMAT | 文本 | 设为 json 输出结构化日志,便于 jq 过滤 |
RUST_LOG_ANSI | 自动检测(TTY 时启用) | 是否输出 ANSI 颜色,同时影响 CLI 彩色输出 |
常用排障命令:
# 跟随日志
journalctl -u thurgio -f
# 仅看熔断器状态切换
journalctl -u thurgio | grep "circuit breaker"
# 仅看失败请求(结构化日志)
RUST_LOG_FORMAT=json thurgio serve | jq 'select(.fields.trace_id != null)'
# 提升更新管线的日志级别
RUST_LOG="thurgio_app_update=debug,thurgio_request=debug" thurgio serve
关键日志信号:
- 熔断器:
circuit breaker opened after N failures、circuit breaker reopened after half-open failure、circuit breaker open for <name>。 - 配置热重载失败:
config_reload_failed_total递增时伴随error!日志,旧配置保持生效。 - 事件总线:
domain event: provider '<name>' update failed等(thurgio-app-build/src/event_handlers.rs)。
延伸阅读
- 部署指南 — 生产检查清单、反向代理与 systemd 配置
- API 参考 —
GET /metrics与GET /app/read/health的完整响应契约 - 架构 — 统一错误类型(
ErrorKind)与熔断器设计 design/debugging.md—RUST_LOG过滤速查表design/specs/spec-circuit-breaker.md— 熔断器键格式与持久化契约
API 参考
概述
基础 URL:http://<address>:<port>/(默认 0.0.0.0:10100)。
认证方式:令牌通过 ?token= 查询参数或 Authorization: Bearer <token> 请求头传递(查询参数优先)。令牌按作用域分组:
- Read — 可访问只读渲染端点。
- Admin — 可访问全部端点(Admin 令牌同时满足 Read 要求)。
所有受保护的端点都位于 /app/ 命名空间下。未认证或作用域不足的请求返回 401 Unauthorized。
中间件
每个请求都会经过以下中间件栈(自外向内):
| 中间件 | 行为 |
|---|---|
| 超时 | 请求超过 9 秒返回 504 Gateway Timeout |
| 追踪 | HTTP 请求追踪(TraceLayer) |
| 压缩 | 透明负载压缩(CompressionLayer) |
| 请求体限制 | 超过 10 MB 返回 413 Payload Too Large |
| 请求追踪器 | 为每个请求添加 x-request-id 响应头并记录日志 |
| 指标 | 为每个请求记录 Prometheus 指标 |
| CORS | 可配置的来源(setting.cors_origins,默认 ["*"]);所有响应附带 Access-Control-Allow-* 与 HSTS 头。来源列表每请求从 live config 读取,修改 cors_origins 后热重载立即生效 |
| 速率限制 | 按 IP 限流(默认每 60 秒 600 次)+ 并发限制(默认 600),超限返回 429 Too Many Requests(带 Retry-After 头) |
| 内容类型校验 | POST/PUT/PATCH 必须携带 Content-Type: application/json 或 application/x-www-form-urlencoded,缺失返回 400,类型不符返回 415 |
| 认证 | 按路由作用域校验令牌(Read/Admin) |
公开端点
以下端点无需认证。
GET /
健康检查。返回纯文本问候信息。
响应:200 OK
Hello world!
GET /v
版本信息。
响应:200 OK
thurgio 0.18.0 (git-describe 快照)
GET /health
以 JSON 格式返回详细的系统健康状态。
响应:200 OK
{
"status": "healthy",
"version": "thurgio 0.18.0 (...)",
"database": "connected",
"memory_usage_mb": 42,
"cpu_cores": 8,
"timestamp": "2025-01-01T00:00:00Z"
}
字段说明:
| 字段 | 类型 | 描述 |
|---|---|---|
status | string | 始终为 "healthy" |
version | string | 名称 + 版本号 + git 快照 |
database | string | 始终为 "connected" |
memory_usage_mb | integer | RSS 内存(MB,来自 /proc/self/status 的 VmRSS) |
cpu_cores | integer | 可用并行度 |
timestamp | string | RFC 3339 时间戳(UTC) |
GET /ready
就绪探针——检查 SQLite 数据库是否可读。
响应 200 OK(数据库可读):
{
"status": "ready",
"database": "connected"
}
响应 503 Service Unavailable(数据库不可读):
{
"status": "not_ready",
"database": "disconnected"
}
GET /metrics
Prometheus 格式的指标数据,包含请求指标、熔断器快照、渲染线程池指标(renderer_active_threads、renderer_backlog_smoothed、renderer_threads_spawned_total、renderer_threads_exited_total、renderer_slots_total)与内存占用。
响应:200 OK
纯文本 Prometheus 暴露格式。
GET /favicon.svg / GET /favicon-dark.svg
编译期嵌入的站点图标(crates/thurgio-server/assets/favicon.svg 与 favicon-dark.svg)。登录页与 Dashboard 通过 <link rel="icon" media="(prefers-color-scheme: ...)"> 按系统配色自动切换亮/暗图标。
响应:200 OK,内容类型 image/svg+xml。
GET /icon-app.svg
编译期嵌入的渐变 App 图标(crates/thurgio-server/assets/icon-app.svg),Dashboard 头部 brand 标记使用。
响应:200 OK,内容类型 image/svg+xml。
GET /app/login / POST /app/login
令牌登录页与校验接口(无需认证)。
GET返回登录页 HTML。POST接收 JSON{"token": "..."},校验令牌作用域:
响应 200 OK(有效令牌):
{ "ok": true, "scope": "admin" }
scope 为 "admin" 或 "read"。
响应 400 Bad Request(缺少 token 字段):
{ "ok": false, "error": "missing token" }
响应 401 Unauthorized(令牌无效):
{ "ok": false, "error": "invalid token" }
只读端点(Read/Admin 令牌)
以下端点接受 Read 或 Admin 作用域令牌。
渲染 Provider
GET /app/read/l?name={name}
按名称渲染 provider(name 为必填查询参数)。渲染前会按 TTL 检查并更新数据。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 必填——要渲染的 provider 名称(缺失或为空 → 400) |
d | string | 可选——设置 Content-Disposition: attachment; filename="..."(为空时使用 name) |
token | string | 认证令牌(也可通过 Authorization: Bearer 头传递) |
| (任意) | string | 其他查询参数将作为渲染参数(hp)传入 |
请求级规则覆盖(ADR-061,仅 provider 端点,不写入配置):
| 参数 | 语义 |
|---|---|
pre_rename / filter / rename | 覆盖配置中对应阶段(逗号分隔的规则名);空值 = 清空该阶段 |
ap_pre_rename / ap_filter / ap_rename | 追加到配置对应阶段规则之后 |
emoji | 覆盖 emoji 策略(enable/disable/hide;非法值 → 400) |
udp | 覆盖 udp 策略(none/false/try/true;非法值 → 400) |
tpl | 覆盖本次渲染模板(空值回退 provider template) |
同一阶段同时传普通与 ap_ 参数(如 rename= + ap_rename=)→ 400;未知规则名静默忽略;带规则参数时从原始响应内容重新解析(仅 tpl 时复用缓存)。snippet(/app/read/r)与 adhoc 遮蔽命中时不支持规则覆盖。
# 覆盖 filter:本次请求只保留 us + vmess 规则命中的节点
curl ".../app/read/l?name=my-provider&token=...&filter=us,vmess"
# 追加规则 / 清空过滤
curl ".../app/read/l?name=my-provider&token=...&ap_filter=vmess"
curl ".../app/read/l?name=my-provider&token=...&filter="
# 切换模板
curl ".../app/read/l?name=my-provider&token=...&tpl=clash.tpl"
响应:渲染后的 provider 内容,并转发提供者响应的 subscription-userinfo 头。
渲染 Snippet
GET /app/read/r?name={name}
按名称渲染 snippet(name 为必填查询参数)。
查询参数:与 /app/read/l?name= 相同(但不支持请求级规则覆盖)。
响应:渲染后的 snippet 内容,并转发请求携带的 subscription-userinfo 头。
渲染 UI
GET /app/read/ui
后台触发一次更新,然后以 HTML 形式渲染 ui.tpl 模板。上下文注入当前令牌的 scope 变量("admin"/"read")。
响应:200 OK,内容类型为 text/html。
渲染上下文
GET /app/read/c
以 JSON 格式渲染完整的 Tera 上下文(config、p、hp、headers、version 等),同样注入 scope 变量;config 内的 proxies/content 字段已剔除(dashboard 只消费资源键名)。
响应:200 OK,内容类型为 application/json。
构建通知(SSE)
GET /app/read/events
Server-Sent Events 流。每次构建完成推送 build_complete 事件,每 15 秒发送一次 keep-alive 心跳。
响应:200 OK,text/event-stream。
订阅健康度
GET /app/read/health
订阅健康度聚合:为每个 provider/snippet 报告熔断器状态(closed/open/half-open)、失败计数、最后失败时间(Unix 秒)与最后成功更新时间(RFC 3339),并汇总为总体状态。数据源为熔断器池快照与 DB 中持久化的抓取时间戳,不触发更新。
响应:200 OK
{
"overall": "degraded",
"summary": { "total": 3, "closed": 2, "open": 1, "half_open": 0 },
"items": [
{
"key": "provider:example",
"name": "example",
"type": "provider",
"breaker": "open",
"failure_count": 5,
"last_failure_at": 1755160000,
"last_update_at": "2026-08-14T12:00:00Z"
}
]
}
overall 取值:healthy(无 open/half-open)、degraded(存在但未全部 open)、unhealthy(全部 open);空配置视为 healthy。未抓取过的 item 两个时间字段均为 null。
订阅商店
GET /app/read/subs
聚合列出全部配置订阅。provider 在前(声明顺序),带解析后节点数(proxies)与流量元数据(来自 subscription-userinfo 头,无该头或不可解析时各字段为 null);snippet 在后(无节点数与元数据字段)。adhoc 临时资源不列出。
响应:200 OK
{
"count": 2,
"items": [
{
"name": "example",
"kind": "provider",
"proxies": 42,
"upload": 1073741824,
"download": 10737418240,
"total": 107374182400,
"used": 11811160064,
"expire": 1893456000,
"expire_iso": "2030-01-01T00:00:00Z"
},
{ "name": "ruleset", "kind": "snippet", "proxies": null, "upload": null, "download": null, "total": null, "used": null, "expire": null, "expire_iso": null }
]
}
除数据就绪门卫外不触发抓取,纯只读聚合。
订阅元数据
GET /app/read/meta?name={name}
报告单个 provider 的流量/配额元数据,解析其缓存响应中的 subscription-userinfo 头。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 必填——provider 名 |
响应:200 OK
{
"name": "example",
"upload": 1073741824,
"download": 10737418240,
"total": 107374182400,
"used": 11811160064,
"expire": 1893456000,
"expire_iso": "2030-01-01T00:00:00Z"
}
| 字段 | 类型 | 描述 |
|---|---|---|
used | integer | null | 已用流量(upload + download),两者均未知时为 null |
expire_iso | string | null | 到期时间的 UTC RFC 3339 形式 |
| 其余 | integer | null | 上传/下载/总量/到期 Unix 秒,未知时为 null |
仅支持 provider 名:snippet 或 adhoc 同名 → 400;未知 → 404;响应无该头或无可解析字段 → 404。
Provider 历史序列
GET /app/read/history?name={name}&limit={n}
报告单个 provider 持久化的快照时间序列——每次成功同步上游记录一个点(时间戳、节点数、当时的流量元数据),按时间升序。只读 KV 持久层:无就绪门卫、不触发网络;从未同步过的 provider 返回空列表。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 必填——provider 名 |
limit | integer | 可选——只保留最近 N 个快照(结果仍升序) |
响应:200 OK
{
"name": "example",
"snapshots": [
{
"ts": 1755153600,
"proxies": 40,
"upload": null, "download": null, "total": null, "expire": null
},
{
"ts": 1755160000,
"proxies": 42,
"upload": 1073741824, "download": 10737418240, "total": 107374182400, "expire": 1893456000
}
]
}
provider-only 策略与 /app/read/meta 一致:snippet/adhoc 同名 → 400;未知 → 404。
历史快照对比
GET /app/read/history/diff?name={name}&from={a}&to={b}
对比同一 provider 时间序列中的两个快照。from/to 先按快照时间戳精确匹配,匹配不到再作为 0 基序列索引解析;非数字、无法定位、或 from 新于 to 均 → 400。
响应:200 OK
{
"name": "example",
"from": { "ts": 1755153600, "proxies": 40, "...": "..." },
"to": { "ts": 1755160000, "proxies": 42, "...": "..." },
"proxies_delta": 2
}
快照只持久化聚合约(时间戳/节点数/流量元数据),不含逐节点明细,因此没有节点级 added/removed 分解——需要时用 CLI 的 thurgio diff nodes 对比原始订阅源。
延迟测试
GET /app/read/latency?name={name}&timeout_ms={ms}&retry={n}
对单个 provider 的全部已解析代理做 TCP 连接延迟测试。结果按次计算、永不缓存(延迟是时效性数据)。代理列表来自已解析的代理库(与渲染缓存同源),不重新解析原始内容。
查询参数:
| 参数 | 类型 | 缺省 | 描述 |
|---|---|---|---|
name | string | — | 必填——provider 名 |
timeout_ms | integer | 3000 | 单次探测超时(钳制到 [100, 10000]) |
retry | integer | 1 | 每个代理重试次数(钳制到 [1, 3]) |
并发度取 setting.concurrency_limit。
响应:200 OK(结果按延迟升序,不可达代理排在最后)
{
"name": "example",
"count": 2,
"results": [
{ "proxy_name": "HK-01", "alive": true, "latency_ms": 45 },
{ "proxy_name": "US-01", "alive": false, "latency_ms": null }
]
}
UDP 能力
GET /app/read/udp?name={name}
逐代理报告 UDP 转发生效标志。这是订阅元数据的静态能力报告(非实时探测):标志在解析期应用全局 setting.udp 策略后得出——源条目省略 udp 字段但策略为 Try 时可能报 true。
查询参数:name(必填,provider 名)。
响应:200 OK(按 provider 内顺序)
{
"name": "example",
"count": 2,
"results": [
{ "proxy_name": "HK-01", "udp": true },
{ "proxy_name": "US-01", "udp": false }
]
}
入口落地检测
GET /app/read/geo?name={name}&timeout_ms={ms}
把每个已解析代理的 server 主机做 DNS 解析,并经 ip-api 批量 GeoIP 查询国家/地区。
查询参数:
| 参数 | 类型 | 缺省 | 描述 |
|---|---|---|---|
name | string | — | 必填——provider 名 |
timeout_ms | integer | 5000 | 单主机解析超时(钳制到 [100, 10000]) |
并发解析上限 8。单主机失败不传播错误——对应条目字段置 null。
响应:200 OK(按 provider 内顺序,重复 server 保留)
{
"name": "example",
"count": 2,
"results": [
{ "server": "hk.example.com", "ip": "103.1.2.3", "country": "Hong Kong", "country_code": "HK" },
{ "server": "us.example.com", "ip": null, "country": null, "country_code": null }
]
}
流媒体解锁探测
GET /app/read/unlock?services={csv}&timeout_ms={ms}&proxy={url}
探测流媒体服务在当前出口网络的可用性与地区。
查询参数:
| 参数 | 类型 | 缺省 | 描述 |
|---|---|---|---|
services | csv | 全部三项 | 要探测的服务:netflix、youtube、disney(别名 disneyplus);未知值 → 400 |
timeout_ms | integer | 5000 | 单服务探测超时(钳制到 [100, 10000]) |
proxy | URL | 无 | 本次请求使用的一次性上游代理(http(s):// 或 socks5://),非法格式 → 400 |
出口语义:探测的是 thurgio 进程自身的出口网络(或
proxy=指定的上游)——不是逐节点的出口。逐节点测试需要内嵌代理核心,超出 v1 范围。并发探测上限 3。
响应:200 OK(按请求顺序)
{
"results": [
{ "service": "netflix", "available": true, "region": "JP", "latency_ms": 230 },
{ "service": "youtube", "available": false, "region": null, "latency_ms": 180 },
{ "service": "disney", "available": false, "region": null, "latency_ms": null, "error": "timeout" }
]
}
被拦截的 HTTP 状态归类为“不可用“而非错误;只有网络错误/超时才填 error 字段。
管理端点(仅 Admin 令牌)
以下端点仅接受 Admin 作用域令牌。
设置参数
GET /app/admin/p
通过查询字符串设置渲染参数,与内存中的配置参数深度合并(点分键如 dns.enabled 会写入嵌套结构)。
查询参数:任意键值对——每个都成为一个渲染参数。
响应:空内容,200 OK。
设置提供者源 URL
GET /app/admin/s
动态更新提供者的源 URL。不使用查询参数——依赖于请求头。
必需请求头:
| 请求头 | 值 |
|---|---|
prov | 要更新的提供者名称 |
url | 提供者的新源 URL |
更新后会自动重置该提供者的熔断器并触发一次强制更新。
响应:200 OK
{ "status": "ok", "provider": "<name>", "source": "<url>" }
响应 400 Bad Request(缺少 prov 或 url 头);404 Not Found(提供者不存在)。
构建并写入输出
GET /app/admin/write
完整构建流程:更新所有提供者,渲染所有 provider/snippet,并将输出文件写入磁盘。
响应:空内容,200 OK(失败时返回错误 JSON)。
强制刷新
GET /app/admin/refresh
强制更新所有提供者和 snippet。与 /app/admin/write 不同,此端点仅执行更新步骤,不写入输出。
响应:空内容,200 OK(失败时返回错误 JSON)。
重新加载模板引擎
GET /app/admin/reload
重新加载 Tera 模板引擎,拾取磁盘上模板文件的所有变更。
响应:空内容,200 OK。
持久化到数据库
GET /app/admin/save
将所有当前 provider/snippet 响应持久化到数据库(未 fetch 过的 item 跳过,避免覆盖 last-known-good 缓存行)。
响应:空内容,200 OK。
设置开发模式
GET /app/admin/dev?value={value}
启用或禁用开发模式(每次渲染前重新加载模板)。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
value | boolean | 必填——true 或 false(缺失或非法值 → 400) |
响应:设置后的当前开发模式值的字符串——"true" 或 "false"。
创建临时 Provider/Snippet
GET /app/admin/adhoc
创建一次性 provider/snippet(不写入配置文件),立即渲染返回;同名临时资源可经 /app/read/l、/app/read/r 后续引用,且对配置中的同名正式项呈遮蔽效果。缓存持久化到 DB,重启后命中缓存即恢复;按 adhoc.max_items/adhoc.ttl_seconds 自动清理。
查询参数:
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
type | string | 是 | provider / snippet |
url | string | 是 | 数据源 URL |
name | string | 否 | 名称,缺省自动生成 adhoc-<8 位 hex> |
format | string | 否 | clash/meta/v2rayn/sip002/surge/auto,默认 auto(仅 provider 生效) |
tpl | string | 否 | 模板名;缺省回退 setting.adhoc.tpl.provider/tpl.snippet |
pre_rename / filter / rename | string | 否 | 逗号分隔的规则名,按顺序应用(未知规则名静默忽略) |
force | boolean | 否 | true 忽略内存 + DB 缓存强制重新获取 |
响应:渲染后的内容,内容类型 text/plain,并携带 Content-Disposition(d 参数)与抓取响应的 subscription-userinfo 头。
备份配置到 GitHub Gist
GET /app/admin/gist?token={gh_pat}&description={text}
把进程工作目录(即 -r/--root 切换后的目录)下的声明配置 YAML 备份为 GitHub 私密 Gist。
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
token | string | 可选——GitHub PAT;缺省时依次回退 config params 的 gist.token、环境变量 GITHUB_TOKEN |
description | string | 可选——Gist 描述,默认 thurgio config backup |
备份文件清单:thurgio.yaml / provider.yaml / params.yaml / snippet.yaml / filter.yaml / rename.yaml / ruleset.yaml 中实际存在的文件;一个都不存在 → 400。token 三个来源全空 → 400。v1 无状态,不持久化任何备份记录。
响应:200 OK
{
"id": "abc123...",
"html_url": "https://gist.github.com/xxx/abc123",
"files": ["thurgio.yaml", "provider.yaml"]
}
上游失败 / 非 2xx / 错误信封 → 502 并携带上游消息。
短链接生成
GET /app/admin/shorten?url={long}&service={name}
经配置的短链接服务缩短 URL。服务模板取自 params:?service= 命中 shortlink.<service> 键时用之,否则用 shortlink.default;模板必须包含 {url} 占位符(点路径深合并产生的嵌套 mapping 形态同样接受)。内置 TinyURL 预设。
# params.yaml 示例
shortlink:
default: "https://tinyurl.com/api-create.php?url={url}"
查询参数:
| 参数 | 类型 | 描述 |
|---|---|---|
url | string | 必填——长链接,必须以 http:// / https:// 开头(否则 → 400) |
service | string | 可选——params 中的服务模板名 |
响应:200 OK
{ "short": "https://tinyurl.com/2p2xxxxx", "long": "https://example.com/very-long-url" }
未配置服务模板 → 400;上游请求失败或响应中提取不到短链 → 502。
查询参数语义
渲染端点(l/r/p/ui/c)的查询参数:
token用于认证,不会进入渲染参数。- 其余参数的值按 YAML 解析(
true→ 布尔、9090→ 数字、其他 → 字符串),作为hp注入模板(请求参数面,与配置参数面p分离)。 - 键中包含
.的参数(如dns.enabled=true)会深度合并进嵌套结构(hp.dns.enabled)。 - 单次请求参数超过 100 个会被拒绝(参数整体忽略并告警)。
d参数额外触发Content-Disposition: attachment; filename="<d>"响应头(用于订阅下载场景)。
响应状态码
| 状态码 | 含义 |
|---|---|
200 OK | 成功 |
400 Bad Request | 缺少必需请求头 / POST 缺少 Content-Type / 登录缺少 token |
401 Unauthorized | 令牌无效或缺失(或作用域不足) |
404 Not Found | 资源不存在(provider、snippet 等) |
413 Payload Too Large | 请求体超过 10 MB 限制 |
415 Unsupported Media Type | POST/PUT/PATCH 的 Content-Type 不受支持 |
429 Too Many Requests | 超过速率限制或并发限制(带 Retry-After 头) |
500 Internal Server Error | 服务器内部错误 |
503 Service Unavailable | 数据库未就绪(仅就绪探针) |
504 Gateway Timeout | 请求处理超过 9 秒 |
CLI 参考
thurgio [OPTIONS] [COMMAND]
全局参数
| 参数 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
-c, --config <FILE> | — | thurgio.yaml | 配置文件路径。sibling YAML 与模板目录相对其所在目录解析 |
-r, --root <DIR> | ROOT | 当前目录 | 工作根目录(进程 chdir 到该目录;数据库与输出相对路径均按它解析) |
-h, --help | — | — | 帮助 |
-V, --version | — | — | 版本 |
全局参数必须放在子命令之前:thurgio -r . build(thurgio build -r . 会报错)。
子命令总览
| 命令 | 别名 | 说明 | 文档 |
|---|---|---|---|
build | b | 更新 + 渲染 + 写盘;--watch 监听重建 | build |
serve | s | 启动 HTTP/TLS API 服务器 | serve |
init | i | 生成最小可用配置脚手架 | init |
check | c | 校验配置并打印摘要与逐项检查结果 | check |
diff | d | 输出文件 diff 或订阅节点级 diff | diff |
config | cfg | 配置声明文件备份 / 回滚 / 列表 | config |
upgrade | u | 检查 GitHub Releases 新版本 | upgrade |
generate | g | 生成 shell 补全脚本 | generate |
version | V | 打印版本信息 | version |
不带子命令直接运行 thurgio 只加载配置后退出(可用于验证解析是否成功)。
命令的初始化需求
各命令对环境的依赖不同(排错时有用):
| 阶段 | 命令 |
|---|---|
| 不需要 logger/配置 | generate, version |
| 只需要 logger | init |
| 只需要 root 目录 | config(备份/回滚在坏配置下也能运行) |
| 完整初始化(配置 + 数据库 + 渲染器 + 熔断器恢复) | build, check, diff, serve, upgrade |
启动流程细节见 app。
环境变量
所有带环境变量的参数遵循同一优先级:命令行 > 环境变量 > 默认值/配置值。完整清单见配置指南。
App
启动流程(thurgio_cli::cli::init):
- 若子命令为
generate或version,直接执行并返回(不需要 logger)。 - 初始化 logger:
- 读取
RUST_LOG过滤级别,默认info(如info,thurgio=debug表示所有 crate 启用 info 级,thurgio 启用 debug 级;也可用于关闭某些 crate 的日志)。 - 读取
RUST_LOG_ANSI,为true时启用颜色;未设置时自动检测(stdout 为 TTY 才启用)。同时控制 logger 与 CLI 彩色输出(owo-colors,如build的✔/✘)。 - 读取
RUST_LOG_FORMAT,为json时输出 JSON 日志。
- 读取
- 若子命令为
init,生成配置并返回(需要 logger,不需要配置)。 - 若设置了
-r/--root(或ROOT环境变量),set_root_dir创建并切换到该目录。 - 初始化 Config 和 Renderer(
thurgio_app::initialize:Source → AppState → 熔断器状态恢复)。 - 执行子命令(
build/check/diff/upgrade/serve)。
不带子命令直接运行 thurgio 只执行初始化(加载配置)后退出。
build
alias: b
执行完整的 更新 → 渲染 → 写盘 管线,生成所有 provider(主输出 + 具名输出)与 snippet 的输出文件。
执行流程
- 更新订阅源 — 按 TTL 判断每个 provider/snippet 是否过期:未过期直接复用内存缓存;过期或
--force时重新获取;获取失败时回退到数据库中的 last-known-good 缓存(配合熔断器,见常见问题)。 - 解析与规则管线 — 解析代理列表并应用
pre_rename → filter → rename(详见配置指南)。 - 渲染 — 构建渲染上下文(
config/p/headers/version),provider 阶段渲染主输出与具名输出,与 snippet 阶段并发执行。 - 写盘 — 渲染结果经暂存目录原子发布到各输出路径。
- 发事件 — 构建完成发出
ProviderUpdated/SnippetUpdated/BuildCompleted事件:触发 SSE 通知(/app/read/events)与 webhook(设置THURGIO_WEBHOOK_URL时 POST JSON)。
成功输出 ✔ Build complete,失败输出 ✘ Build failed(颜色遵循 RUST_LOG_ANSI / TTY 检测)。
参数
| 参数 | 别名 | 环境变量 | 默认 | 说明 |
|---|---|---|---|---|
--force | -f | FORCE | false | 忽略缓存状态,强制重新获取所有订阅源 |
--watch | -w | WATCH | false | 构建一次后进入监听模式,文件变更自动重建 |
--notify | -N | NOTIFY | false | 构建完成时通过 notify-send 发送系统通知(Linux 桌面) |
监听模式(--watch)
thurgio build --watch
- 监听当前工作目录(递归)。
- 变更事件经 500ms 防抖合并后触发重建。
- 忽略构建自身的写入(输出目录与 SQLite 数据库文件),避免自触发循环。
- 命中配置声明文件(
thurgio.yaml、provider.yaml、params.yaml、rename.yaml、filter.yaml、snippet.yaml)时先全量重载配置再重建;重载失败保留旧配置仅记日志。其余路径(模板等)直接重跑更新 + 渲染管线。
按 Ctrl+C 停止。
示例
# 常规构建(TTL 内复用缓存)
thurgio build
# 强制刷新所有订阅源
thurgio build --force
# 构建 + 监听变更 + 完成时系统通知
thurgio build --watch --notify
Serve
alias: s
运行 HTTP 服务器。
参数
| 参数 | 别名 | 环境变量 | 说明 |
|---|---|---|---|
--address | -a | ADDRESS | 监听地址;未指定时使用配置 setting.address(默认 0.0.0.0) |
--port | -p | PORT | 监听端口;未指定时使用配置 setting.port(默认 10100)。端口为 0 时使用随机端口 |
--dev | -d | DEV | 开发模式:每次渲染前重新加载模板 |
--idle | -i | IDLE | 空闲模式:IDLE_TIMEOUT(默认 30s)内无请求则退出程序(配合 systemd Restart=always 按需拉起) |
--evict | -e | EVICT | 空闲时清空数据缓存(默认开启;--evict=false 关闭):EVICT_TIMEOUT(默认 10s)无请求先清缓存,进程继续存活 |
--tls-only | -s | TLS_ONLY | 仅提供 HTTPS(必须同时提供证书与私钥,否则报错) |
--tls-port | -t | TLS_PORT | TLS 端口,默认等于 --port |
--tls-cert | -c | TLS_CERT | TLS 证书文件 |
--tls-cert-key | -k | TLS_CERT_KEY | TLS 私钥文件 |
--open | -O | OPEN | 启动时用 xdg-open 打开浏览器 |
提供证书与私钥时,TLS 服务器与明文服务器可并行启动(--tls-only 则只启动 TLS)。同时提供明文与 HTTPS 时必须用 --tls-port 指定与 --port 不同的端口。
若配置文件来自本地文件,serve 会监听该配置文件本身(无防抖):thurgio.yaml 变更时全量重载配置并链式触发重建(成功才重建,失败保留旧配置仅记日志)。模板与 sibling YAML 的变更不在 serve 的监听范围内——需要模板热更新请用 --dev 模式(每次渲染前重载模板)或 thurgio build --watch。
app(公开端点)
/:Hello world!/v: 版本字符串(get_version():NAME VERSION (GIT_BUILD))/health: JSON 健康状态/ready: 就绪探针/metrics: Prometheus 指标/favicon.svg: 编译期嵌入的站点图标,亮色方案(assets/favicon.svg,image/svg+xml)/favicon-dark.svg: 站点图标,暗色方案(image/svg+xml)/icon-app.svg: 渐变 App 图标(Dashboard 头部 brand 标记,image/svg+xml)/app/login: 登录页(GET)与令牌校验(POST JSON{"token": ...})
app_th(受保护端点)
令牌通过 ?token= 查询参数或 Authorization: Bearer 请求头传递,按作用域分组(Read/Admin)。
Read 作用域:
/app/read/l?name=: 更新数据后渲染 provider(name为必填查询参数;支持请求级规则覆盖与tpl=autoUA 自适应,见 API 参考)。/app/read/r?name=: 更新数据后渲染 snippet(name为必填查询参数)。/app/read/ui: 后台更新,渲染ui.tpl(上下文含当前令牌scope)。/app/read/c: 渲染完整 Tera 上下文为 JSON(含scope,剔除config内proxies/content)。/app/read/events: SSE 构建通知(build_complete,15s keep-alive)。/app/read/health: 订阅健康度聚合(熔断器状态/失败计数/最后更新时间,只读)。/app/read/subs: 订阅商店聚合清单(provider 带解析后节点数与流量元数据,snippet 在后)。/app/read/meta?name=: 单个 provider 的流量元数据(解析subscription-userinfo头)。/app/read/history?name=&limit=: provider 快照时间序列(节点数 + 流量元数据)。/app/read/history/diff?name=&from=&to=: 对比时间序列中两个快照。/app/read/latency?name=: 逐代理 TCP 连接延迟测试(按需、不缓存)。/app/read/udp?name=: 逐代理 UDP 生效标志(静态能力报告)。/app/read/geo?name=: 入口落地检测(DNS 解析 + GeoIP 查询)。/app/read/unlock?services=: 流媒体解锁探测(netflix/youtube/disney)。
Admin 作用域:
/app/admin/s: 通过prov/url请求头更新提供者来源并强制刷新。/app/admin/p: params,请求携带的 queries 会深度合并覆盖当前内存中的 params。/app/admin/adhoc: 创建临时 provider/snippet(不写入配置,缓存持久化到 DB)。/app/admin/write: 执行完整构建(更新 + 渲染 + 写盘)。/app/admin/refresh: 无视缓存强制更新 providers/snippets(不写盘)。/app/admin/reload: 重载所有 templates。/app/admin/save: 持久化所有响应到数据库。/app/admin/dev?value=: 设置 dev 状态(true/false)。/app/admin/gist: 把配置声明文件备份为 GitHub 私密 Gist。/app/admin/shorten?url=: 经配置的短链接服务缩短 URL。
query
当携带参数请求接口时,比如 example.com/app/read/l?name=example&udp=true&port=9090,除 token 与 name 外的 queries 会被转换为 YAML 值(true → 布尔、9090 → 数字),作为 hp 注入模板——请求参数与配置参数分离:p 仅来自配置(params.yaml + /app/admin/p 热更新),hp 携带本次请求的查询参数。点分键(如 dns.enabled)在 hp 中写入嵌套结构(hp.dns.enabled)。单次请求参数超过 100 个会被拒绝。配置中的 params 可在模板中用 config.params 访问。
headers
访问 l / r 时:
- 转发提供者响应中的
subscription-userinfo头(snippet 转发请求头中的同名头)。 - 携带
d=config.yaml查询参数时,返回Content-Disposition: attachment; filename="config.yaml"下载头。
init
alias: i
在目标目录生成一份最小可用的配置脚手架。init 只需要 logger,不需要已有配置,因此可以在任意空目录中直接运行。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
-d, --dir <DIR> | 当前目录 | 目标目录(不存在时自动创建,支持嵌套路径) |
-f, --force | false | 覆盖已存在的文件 |
生成的文件
<dir>/
├── thurgio.yaml # 主配置(setting 节)
├── provider.yaml # provider 定义
├── filter.yaml # 过滤规则
├── rename.yaml # 重命名规则
├── params.yaml # 模板参数
├── snippet.yaml # snippet 定义
└── tpls/
├── base.tpl # 最小示例模板(provider 渲染入口)
└── ui.tpl # Dashboard 页面模板(serve 自带 UI 依赖它,勿删除)
注意:
- 不会生成
ruleset.yaml——它是唯一“缺失合法“的 sibling 配置文件,需要时手动创建即可(见配置参考)。 - 任一目标文件已存在且未指定
--force时报错退出;使用--force会用内置模板覆盖所有文件。 - 生成的
provider.yaml中带有一个指向本地示例的 provider 条目,可直接作为起点修改。
示例
# 在当前目录初始化
thurgio init
# 在指定目录初始化(目录不存在时自动创建)
thurgio init --dir ~/thurgio-config
# 已有文件时强制重新生成(覆盖为全新脚手架)
thurgio init --force
check
alias: c
校验配置而不构建:解析完整配置(thurgio.yaml + 全部 sibling YAML)后打印摘要与逐项检查结果。不访问网络、不写任何文件,适合放在 CI 或 cron 中做配置回归检查。
参数
无专属参数。支持全局参数 -c/--config 与 -r/--root。
输出内容
✅ Configuration is valid
Summary:
Providers: 2
Snippets: 1
Outputs: 3
Tokens: 2 configured
Address: 0.0.0.0
Port: 10100
Ttl: provider=600s, snippet=86400s
Provider 'example': OK (https://example.com/sub)
Provider 'local': OK (file: prov/example.yaml)
| 检查项 | 说明 |
|---|---|
| Summary | provider / snippet / 具名输出数量、令牌总数、监听地址/端口、TTL |
| Provider 来源 | URL 源检查 http(s):// 前缀格式;本地文件源检查文件是否存在 |
| 规则引用 | provider 的 pre_rename / filter / rename 规则名是否在 filter.yaml / rename.yaml 中定义 |
| 输出目录 | 所有配置输出路径的父目录是否存在 |
WARNING 类型
解析期未知规则名会被静默忽略(不参与管线),check 把它们以 WARNING 形式报告出来:
Provider 'example' filter rule 'us': WARNING (not defined in filter.yaml)
Provider 'example' pre_rename rule 'typo_name': WARNING (not defined in rename.yaml)
Provider 'local': WARNING (file not found: prov/example.yaml)
Output directory 'output/proxies.yaml': WARNING (parent directory does not exist)
- URL 格式异常 — 来源不是
http:///https://开头却被当作 URL 解析。 - 文件不存在 — 本地文件源指向的路径不存在。
- 规则未定义 — 引用的规则名在对应表中不存在(最常见的静默失效来源)。
- 输出父目录不存在 — 构建时写入会失败。
WARNING 不影响退出码(配置仍视为有效),但通常意味着运行时行为与预期不符。
示例
# 校验当前目录配置
thurgio check
# 校验指定配置
thurgio -c /etc/thurgio/thurgio.yaml check
diff
alias: d
两种对比模式:对当前配置的输出文件做文本级 diff(file,默认),或对两个订阅源做节点级 diff(nodes)。
diff file(默认模式)
thurgio diff [file] [NAME]
bare thurgio diff 等价于 thurgio diff file。
执行过程:
- 读取
setting.output中配置的所有输出文件的当前内容(不存在的文件视为空内容)。 - 执行一次强制构建(等价
build --force,会真实访问订阅源)。 - 逐文件打印新旧内容的行级差异:
-删除行、+新增行、空格前缀为未变更行。
| 参数 | 说明 |
|---|---|
NAME | 可选——只对比路径中包含该子串的输出文件 |
# 对比所有输出
thurgio diff
# 只对比路径含 example 的输出
thurgio diff file example
NAME匹配的是输出路径子串,不是 provider 名;但输出路径通常包含 item 名,按名过滤在多数场景下等效。
diff nodes
thurgio diff nodes <from> <to>
节点级对比两个订阅源:
http(s)://开头视为 URL,其余视为本地文件路径。- 格式自动检测(Clash / V2RayN / SIP002 等),不套用规则管线——对比的是原始节点。
- 不可解析的条目被丢弃并记 debug 日志。
输出分组
1 added, 0 removed, 1 changed, 1 renamed, 1 unchanged
+ HK-01 (trojan 9.9.9.9:443)
~ US-01: ss 1.2.3.4:8388 → ss 1.2.3.4:9000
↻ TW-01 → TW-02 (ss 6.6.6.6:8388)
| 分组 | 标记 | 颜色 | 判定 |
|---|---|---|---|
| added | + | 绿 | 仅新源中存在 |
| removed | - | 红 | 仅旧源中存在 |
| changed | ~ | 黄 | 同名但指纹不同 |
| renamed | ↻ | 黄 | 指纹相同但名称不同 |
| unchanged | — | — | 完全一致 |
指纹 = 去掉 name 字段后的全部序列化字段(type/server/port/密码等),任一变化即视为 changed。全部无变化时整行绿色输出 N unchanged。颜色遵循 RUST_LOG_ANSI / NO_COLOR。
示例
# 对比升级订阅前后的节点变化
thurgio diff nodes prov/old.yaml https://example.com/sub
# 对比两个本地文件
thurgio diff nodes prov/a.yaml prov/b.yaml
config
alias: cfg
备份 / 回滚配置声明文件。不加载配置、不访问网络、不碰数据库——即使当前配置已经改坏也能运行,是“改配置前留后路“的安全网。
备份范围
| 内容 | 说明 |
|---|---|
thurgio.yaml | 主配置 |
provider.yaml / params.yaml / rename.yaml / filter.yaml / snippet.yaml | 5 个 sibling 声明文件 |
tpls/ | 模板目录(递归复制;符号链接与特殊文件跳过) |
只复制实际存在的文件(缺失的声明文件跳过,部分配置按现状备份);ruleset.yaml 不在备份清单中。
backup
thurgio config backup [-d, --dir <DIR>]
| 参数 | 默认值 | 说明 |
|---|---|---|
-d, --dir | <root>/backups/<YYYYMMDD-HHMMSS> | 备份目标目录 |
- 目录名为本地时间戳;同一秒内多次备份自动追加
-1、-2后缀。 - root 中找不到任何声明文件时报错并清理空目录。
- 成功输出
✔ Backup created: <路径>。
restore
thurgio config restore <target>
| 参数 | 说明 |
|---|---|
target | 备份名(backups/ 下的子目录名)、绝对路径或相对 root 的路径(含路径分隔符时按路径解析) |
执行过程:
- 校验目标存在且包含至少一个声明文件(否则报错,不触碰当前状态)。
- 先把当前状态自动备份到
backups/<时间戳>(保险)——当前状态为空则跳过。 - 覆盖写回声明文件与
tpls/:只恢复备份中包含的内容,不删除 root 中的其他文件。
安全护栏:拒绝把 root 目录自身当作备份源(防止自我复制循环)。
成功输出 ✔ Restored from '<target>' (current state backed up at <路径>)。
list
thurgio config list
列出 backups/ 下所有备份目录(名称 + 本地时间修改时间),最新在前:
Available backups in /root/path/backups:
20260814-153000 2026-08-14 15:30:00
20260814-120000 2026-08-14 12:00:00
示例
# 改配置前备份
thurgio config backup
# 改坏了,回滚
thurgio config list
thurgio config restore 20260814-153000
upgrade
alias: u
检查 GitHub Releases 是否有新版本。只检查并提示,不会自动下载或安装。
参数
| 参数 | 别名 | 默认 | 说明 |
|---|---|---|---|
--yes | -y | false | 有新版本时额外打印发布页下载链接 |
行为
- 查询
https://api.github.com/repos/xylylab/thurgio/releases/latest(10 秒超时)。 - 以 semver 比较当前版本与最新 tag(忽略
v前缀)。
有新版本时:
Update available: v0.18.0 → v0.19.0
Release: https://github.com/xylylab/thurgio/releases/tag/v0.19.0
加 --yes 再打印一次 Download the latest release from: 与链接(便于脚本抓取)。已是最新时输出:
Already up to date (v0.18.0)
升级安装方式见部署指南。
Generate
alias: g
生成 shell 补全脚本。
参数
-s, --shell <shell>: 目标 shell,默认bash。支持 bash/zsh/fish 等(clap_complete::Shell)。环境变量SHELL。-o, --output <output>: 输出文件路径;不指定时输出到 stdout。环境变量OUTPUT。
示例
# Bash
thurgio generate --shell bash > /etc/bash_completion.d/thurgio
# Zsh
thurgio generate --shell zsh > /usr/local/share/zsh/site-functions/_thurgio
# Fish
thurgio generate --shell fish > ~/.config/fish/completions/thurgio.fish
# 直接写入文件
thurgio generate -s fish -o ~/.config/fish/completions/thurgio.fish
version
alias: V
打印版本信息。与 generate 一样不需要 logger 与配置,可在任何目录直接运行。
参数
| 参数 | 别名 | 说明 |
|---|---|---|
--json | -j | 以 JSON 输出 |
示例
$ thurgio version
thurgio 0.18.0
$ thurgio version --json
{"name":"thurgio","version":"0.18.0"}
服务器端对应 GET /v 端点(含 git 快照后缀时两者一致),见 API 参考。
Config
首先, 我们需要一个配置文件 thurgio.yaml。setting 顶级节包含服务器设置。
tokens
API 令牌,按作用域分组。至少配置一个令牌(否则校验失败)。
setting:
tokens:
admin:
- "admin-token" # Admin 作用域:可访问全部 /app/ 端点
- "${THURGIO_ADMIN_TOKEN}" # 环境变量引用(可选)
read:
- "read-token" # Read 作用域:仅可访问只读渲染端点
令牌通过 ?token= 查询参数或 Authorization: Bearer 请求头传递。
环境变量引用
token 值可写成 ${NAME} 形式,解析期从进程环境读取并替换(自托管免明文存 token)。仅当整个值恰好为 ${NAME} 时展开(如 prefix-${NAME}、${NAME}suffix 均保留字面);引用的环境变量未定义时配置解析报错。仅 token 值支持展开,其余配置字段不展开。
address
服务监听地址,默认 0.0.0.0。serve 启动时可用 -a/--address 或 ADDRESS 环境变量覆盖。
port
服务监听端口,默认 10100。serve 启动时可用 -p/--port 或 PORT 环境变量覆盖。端口为 0 时使用随机端口。
udp
对于节点的 udp 字段处理逻辑。
None: 不处理False: 设为 falseTry: 如果代理类型支持 udp 则启用(默认)True: 设为 true
mode
emoji 模式(全局)。
Disable: 不添加 emoji 前缀Enable: 检测到国家/地区时添加旗帜 emoji 前缀(默认)Hide: 去除代理名称中已有的 emoji 前缀
output
渲染结果的默认输出路径,按渲染目标 + 模板名分组(键为 provider/snippet,每个桶内再按模板名映射到目录列表):
setting:
output:
provider:
"clash/base.tpl":
- "prod/list/"
snippet:
"rule.tpl":
- "prod/rules/"
item 默认输出写入 {dir}/{key}.{ext}(key = item 名,ext 来自 ext 字段或参数 p.ext),目录取 item 自己 target 桶 + 自己模板名对应条目;未配置则无默认输出。
旧版的扁平目录列表写法(如
provider: ["prod/"])已不再支持,解析会直接报错(ADR-080)。
ttl
缓存有效期(秒)。
provider: 最小60,默认600(10 分钟)snippet: 最小300,默认86400(24 小时)
缓存未过期时不会重新获取。
concurrency_limit
并发任务限制(渲染/持久化等任务的并发数),最小 1,默认 10。可用环境变量 THURGIO_CONCURRENCY_LIMIT 覆盖。更新管道的抓取阶段为无界并发(每个 item 同时启动,ADR-073),不受此限制约束。
cors_origins
允许的 CORS 来源列表,默认 ["*"]。生产环境建议限制为具体域名;来源列表每请求从 live config 读取,配置热重载后立即生效。
adhoc
临时 provider/snippet(/app/admin/adhoc 端点)的默认行为:
setting:
adhoc:
tpl:
provider: "tpls/adhoc_nodelist.tpl" # 临时 provider 缺省模板
snippet: "tpls/adhoc_snippet.tpl" # 临时 snippet 缺省模板
max_items: 100 # 数量上限,超出淘汰最旧创建者
ttl_seconds: 86400 # 清理 TTL:自最后访问起算(秒)
| 字段 | 默认值 | 说明 |
|---|---|---|
tpl.provider / tpl.snippet | 无 | 请求未传 tpl 参数时的回退模板;两者都缺且请求也未指定时 → 400 |
max_items | 100 | 临时资源数量上限(内存与 DB 一致清理) |
ttl_seconds | 86400 | 滑动 TTL——自最后访问时间起算 |
example
thurgio.yaml
setting:
tokens:
admin:
- token
# address: 0.0.0.0 # $ADDRESS
# port: 10100 # $PORT
# udp: Try
# mode: Enable
# output:
# provider:
# "base.tpl":
# - output/example.yaml
# ttl:
# provider: 600
# snippet: 86400
# concurrency_limit: 10
# cors_origins:
# - "*"
Provider
provider 是代理订阅实体:从 source 获取 → 解析 → pre_rename → filter → rename → 按 template 渲染 → 写入 filename。
字段总览
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 显示名,未指定时用键名 |
type | string | 是 | 输入配置格式,见下表 |
source | string | 是 | URL(http(s):// 开头)或本地文件路径 |
template | string | 是 | 主输出模板(相对模板目录) |
filename | string 列表 | 否 | 渲染结果写入的文件列表 |
pre_rename | 规则名列表 | 否 | 解析后、过滤前应用(引用 rename.yaml) |
filter | 规则名列表 | 否 | 过滤阶段应用(引用 filter.yaml) |
rename | 规则名列表 | 否 | 过滤后应用(引用 rename.yaml),应用前后均检查重名 |
emoji | bool | true | 是否执行 emoji 逻辑 |
default_output | bool | 回退 p.default_output | 是否同时写默认路径文件 |
ext | string | 回退 p.ext | 默认输出文件的扩展名 |
outputs | mapping | 否 | 具名输出,见下文 |
p | 任意 YAML | {} | 自定义参数,模板中以 p.* 访问 |
type
输入配置格式:
clash— Clash YAMLmeta— Clash.Meta YAMLv2rayn— V2RayN 分享链接(Base64 JSON)sip002— SIP002 URI(逐行)surge— Surge 配置[Proxy]段qx/loon/singbox/sip008/ssdtelegram— TG 类 HTTP/SOCKS5 代理链接(tg://http/tg://socks/t.me/*)
auto不是合法的 providertype——它只用于渲染侧tpl=auto的 UA 自适应模板选择;输入格式必须显式指定。
source
untagged 枚举,纯字符串:
"https://..."或"http://..."— 远程 URL- 其余 — 本地文件路径(相对配置文件所在目录解析)
outputs(具名输出)
同一份代理列表经不同模板投影为多种格式。每个输出可独立指定:
example:
type: clash
source: "prov/example.yaml"
template: clash_list.tpl # 主输出
filename:
- prod/list/example.yaml
outputs:
base64_sub: # 具名输出 1
template: base64_sub.tpl
filename:
- prod/example_base64.txt
plain_list: # 具名输出 2
template: plain_list.tpl
filename:
- prod/example_plain.txt
p:
note: "human-readable" # 每个输出有自己的 p
- 输出渲染上下文与主输出一致:
l绑定所属 provider。 - 输出名跨所有 provider 全局唯一——重名在构建期报配置错误。
- 每个输出的
default_output/ext未设置时回退到自己的p同名键。
default_output 与 ext
默认输出路径 = <目录>/<item 名>.<ext>,其中目录取 setting.output 中“该 item 自己的 target 桶 + 自己模板名“对应条目(详见 setting.output)。未配置对应目录时不写默认输出。
example
provider.yaml
example:
name: example
type: clash
pre_rename:
- us
filter:
- us
rename:
- rm_brackets
source: "prov/example.yaml"
emoji: true
template: clash_list.tpl
filename:
- prod/list/example.yaml
p:
one: one
two: two
Filter
过滤规则(声明式语法)。在 filter.yaml 中定义,被 provider 的 filter 列表、请求级规则覆盖(/app/read/l)与模板 filter filter(rules= kwarg)按键名引用,依次应用。
每条规则值为三种形状之一:
- 裸字符串简写:
contain匹配任一|分隔关键字,忽略大小写(最常用) - 单个匹配器 mapping
- 匹配器 mapping 列表:顺序应用,代理必须命中全部(AND)
匹配器
| 键 | 值 | 说明 |
|---|---|---|
contain | 字符串或字符串数组 | 名称包含任一关键字(数组 = OR,等价 ` |
start | 字符串 | 名称以关键字开头 |
end | 字符串 | 名称以关键字结尾 |
type | 字符串 | 按代理协议类型过滤(ss, vmess, trojan 等) |
regex | 字符串 | 按正则匹配代理名 |
共享选项
| 键 | 缺省 | 说明 |
|---|---|---|
ignore_case | true | 忽略大小写 |
negate | false | 反转:命中则剔除 |
限制:列表(AND)仅支持 contain/start/end 匹配器;type/regex 只能单条使用。每个 mapping 必须恰好一个匹配器键。
example
filter.yaml
# 简写:保留名称包含任一关键字的代理(忽略大小写)
us: "美国|United States|American"
# 排除(negate)
rm_netease:
- contain: [netease, unblock, music]
negate: true
# 名称开头
hk_prefix:
- start: HK
# 类型过滤
vmess_only:
- type: vmess
# 正则
custom:
- regex: '\W+(US|us|United States)\W+'
# 多条规则顺序应用(AND)
strict:
- contain: [美国]
- start: HK
Rename
重命名规则,基于正则替换(regex::replace_all,支持 $1 反向引用)。在 rename.yaml 中定义,被 provider 的 pre_rename / rename 列表与模板 rename 过滤器(rules= kwarg)按键名引用。
规则形状
每条规则是一个二元素列表 [正则, 替换串],对代理名称执行全局替换:
us:
- "(.*)"
- "🇺🇸 $1"
- 正则语法为 Rust
regexcrate:支持\d、(?:...)、命名组(?<name>...)等,不支持回溯(lookahead/lookbehind 不可用)。 - 替换串中
$1/${name}引用捕获组;字面$写$$。
应用位置
| 阶段 | 配置键 | 时机 |
|---|---|---|
| 解析后 | pre_rename | 过滤前改名——先规范名称再筛选 |
| 过滤后 | rename | 过滤后改名——只对保留节点生效 |
两个阶段各自按列表顺序依次应用;应用后检查重复名称,重名报错。
示例
# rename.yaml
# 全体加国旗前缀
us:
- "(.*)"
- "🇺🇸 $1"
# 去掉括号前的 0:"香港 01(2)" → "香港 01 2"
rm_brackets:
- "0(\\d)"
- " $1"
# 房号规范化:"108-204" → "108/204"
room_number:
- "(\\d+)-(\\d+)"
- "$1-$2"
# 删掉倍率后缀
strip_rate:
- "\\s*[xX]\\d+(\\.\\d+)?$"
- ""
# 多条规则组合:先删信息素后加地区前缀
clean_and_tag:
- "剩余.*|到期.*|官网.*"
- ""
注意
- 未定义的规则名被静默忽略(不参与管线);用
thurgio check可发现这类悬空引用。 - 模板内可动态追加应用:
{{ l.proxies | rename(rules="rm_brackets") }}。 - YAML 中写正则时注意转义:
\d在双引号字符串中要写成"\\d"。
Snippet
snippet 是不依赖 provider 的独立渲染单元:从 source 获取内容 → 用 template 渲染(上下文绑定 s.response.content)→ 写入 filename。典型用途:规则集聚合、静态片段、模板化配置文件。
字段总览
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 显示名,未指定时用键名 |
template | string | 是 | 渲染模板(相对模板目录 THURGIO_TPL_DIR,默认 tpls/) |
source | string | 否* | URL(http(s):// 开头)或本地文件路径;内容进入 s.response.content |
filename | string 列表 | 否 | 渲染结果写入的文件列表 |
default_output | bool | 回退 p.default_output | 是否同时写默认路径文件 |
ext | string | 回退 p.ext | 默认输出文件的扩展名 |
p | 任意 YAML | {} | 自定义参数,模板中以 s.p.* 或 config.snippet.<key>.p 访问 |
* 无 source 的 snippet 也可用——模板仅依赖 config/p 等全局上下文时可以省略。
source
untagged 枚举,纯字符串:
"https://..."或"http://..."— 远程 URL- 其余 — 本地文件路径(相对配置文件所在目录解析)
与 provider 的区别
- 无解析与规则管线:内容原样作为文本进入模板,不做代理解析、不过滤/改名。
- 上下文不同:provider 绑定
l.proxies,snippet 绑定s.response.content。 - TTL 更长:默认缓存 24 小时(provider 默认 10 分钟),见 setting.ttl。
- snippet 的
p在其他 provider 模板中可经config.snippet.<key>.p读取——这是给其他模板传参数的常用手法(如clash.tpl遍历 snippet 读p.clash_url)。
example
snippet.yaml
example:
name: example
template: snippet.tpl
source: "rules/China.list"
filename:
- prod/list/example.yaml
p:
one: one
two: two
Ruleset
可选 sibling 文件 ruleset.yaml(唯一缺失合法的 sibling——其余 5 个缺失即报错),按名称定义命名规则集,供模板 ruleset 过滤器经 _rules.ruleset 上下文面消费(ADR-086/088)。
条目形状
每条目值为两种形状之一:
- 行列表(本地形态)——规则行数组
- URL 字符串(远程形态)——
http(s)://开头;其他字符串在解析期报错
# ruleset.yaml
ads:
- DOMAIN-SUFFIX,ads.example.com
- DOMAIN-KEYWORD,tracker
cnlist: https://example.com/cn.list
远程条目语义
- 由更新管线定时拉取并物化进
_rules.ruleset面。 - TTL 24 小时内命中缓存不重拉;URL 变更或强制刷新时重拉。
- 拉取失败回退上次成功文本并递增
ruleset_fetch_failed_total计数器。 - 同名冲突时本地声明优先。
模板消费:ruleset 过滤器
{{ l.proxies | ruleset(name="ads") }} {# → Clash 规则行 #}
{{ l.proxies | ruleset(name="ads", format="surge") }} {# → Surge 规则行 #}
{{ l.proxies | ruleset(name="ads", format="clash-classical") }} {# → Clash classical rule-provider 文档(payload: 序列)#}
| 参数 | 缺省 | 说明 |
|---|---|---|
name | 必填 | 规则集名(过滤器输入值被忽略) |
format | clash | clash / surge / clash-classical |
policy | 🚀 Node Select | Surge 格式的路由策略后缀 |
text | 无 | 内联规则行文本,覆盖其他一切来源 |
来源优先级:内联 text > _rules.ruleset 命名表(本文件)> 内置 assets/rulesets/*.list 资产。未定义的规则名会报渲染错误(与 filter/rename 的静默忽略不同)。
Params
全局自定义参数,供模板通过 p 面访问。在 params.yaml 中定义,也可放在 thurgio.yaml 的 params 顶级节(两者合并)。
值可以是任意 YAML 结构(标量、映射、列表),模板中按路径访问:
# params.yaml
mixed-port: true
dns:
enable: true
mode: fake-ip
format: json
test:
interval: 60
url: https://cp.cloudflare.com/generate_204
mixed-port: {{ p["mixed-port"] }}
dns-mode: {{ p.dns.mode }}
interval: {{ p.test.interval }}
参数面:p 与 hp 的分离
模板中有两个独立的参数面(ADR-081):
| 面 | 来源 | 可变性 |
|---|---|---|
p.* | 配置:params.yaml + /app/admin/p 端点热更新 | 进程生命周期内持久 |
hp.* | 本次 HTTP 请求的查询参数(YAML 解析、点分键嵌套) | 单次请求 |
HTTP 请求的查询参数不会覆盖 p 中的同名值——订阅方无法篡改你的配置参数;需要请求级差异时在模板中显式消费 hp。
热更新
Admin 令牌请求 /app/admin/p?key=value 会把查询参数深度合并进内存中的 params(点分键写入嵌套结构),立即对后续渲染生效,不写回配置文件:
curl "http://localhost:10100/app/admin/p?test.interval=120&token=ADMIN_TOKEN"
example
见上方 YAML 示例;完整参数面语义(解析规则、100 个上限等)见 API 参考。