Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

详细说明请参阅快速开始指南。


链接


致谢

快速开始

从零开始运行 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/。

从预编译二进制安装

  1. 从 Releases 页面下载适用于你操作系统/架构的压缩包。
  2. 解压并将 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)、AppHttpClient trait。
  • thurgio-event — 进程内部事件总线(EventBus、DomainEvent、EventHandler、Envelope)。使用 tokio::sync::broadcast,慢订阅者会丢失事件。
  • thurgio-test-utils — 集成测试工具(测试应用工厂、测试配置构建器)。

领域层

  • thurgio-domain — 核心业务类型:Provider/RawProvider、Snippet、Output/RawOutput(provider 具名输出),以及更新生命周期(UpdateState、Cache/Lifecycle/Parsable trait,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(marker V2rayN/Sip002/Surge/Qx/Loon)提供格式解析分发,formats.rs 能力表为单一事实源。全部协议无条件编译。
  • thurgio-formats — 共享行解析辅助(Surge/QX/Loon 行解析抽离,供 proxy 与 renderer 复用)。
  • thurgio-proxy-singbox — Sing-box 特定的代理类型转换(CoreSingBox、ToSingBox trait,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/Render trait + 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 在 server ua.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=auto UA 自适应,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_URL POST 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

TraitCrate用途
Stored, EntityStore, SqliteEntityStorethurgio-db存储模型关联 / 类型化实体存取
KvStorethurgio-db键值持久化(SQLite)
Proxy, ConfType, ProxyFormatthurgio-proxy代理类型定义与格式解析分发(marker V2rayN/Sip002/Surge)
CoreSingBox, ToSingBoxthurgio-proxy-singboxSing-box 转换
EventHandler, EventBusthurgio-event事件订阅与分发
Render, RenderTargetthurgio-renderer-api渲染数据合约(无 Tera 依赖)
Renderable, Render, AppRenderEnginethurgio-renderer模板渲染执行

设计原则

  1. 单一职责 — 每个 crate 有一个清晰的用途。
  2. 依赖反转 — 上层依赖于抽象。
  3. 关注点分离 — 各层干净分离。
  4. 外观模式 — 接口层 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):

类型描述
clashClash YAML 格式
metaClash.Meta YAML 格式
v2raynV2RayN 分享链接格式(Base64 JSON 数组)
sip002SIP002 URI 格式(逐行 ss:// 等)
sip008SIP008 JSON 订阅格式(Shadowsocks Android,version+servers)
surgeSurge 配置格式(解析 [Proxy] 段)
qxQuantumult X 节点行(shadowsocks=/vmess= 等 7 种)
loonLoon 节点行(10 种)
ssdSSD 订阅格式(ssd:// Base64 JSON 文档)
singboxsing-box 配置 JSON(解析 outbounds 数组,基础字段,含 wireguard/naive/ssh)
telegramTG 类 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.proxiesprovider、具名输出代理对象列表
l.nameprovider、具名输出当前 provider 的名称
l.renameprovider、具名输出当前 provider 的重命名规则(键名)
l.filterprovider、具名输出当前 provider 的过滤规则(键名)
s.response.contentsnippet当前 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.yamlprovider合并空
params.yamlparams合并空
rename.yamlrename报错(必须存在空或有内容)
filter.yamlfilter报错
snippet.yamlsnippet合并空
ruleset.yamlruleset合并空(唯一缺失合法的 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)当前目录
FORCEbuild 强制更新(-f/--force)false
WATCHbuild 监听模式(-w/--watch)false
NOTIFYbuild 完成时发送系统通知(-N/--notify)false
SHELLgenerate 补全目标 shell(-s/--shell)bash
OUTPUTgenerate 输出文件(-o/--output)stdout
ADDRESSserve 监听地址(-a/--address)配置 setting.address(默认 0.0.0.0)
PORTserve 监听端口(-p/--port)配置 setting.port(默认 10100)
DEVserve 开发模式(-d/--dev)false
IDLEserve 空闲模式(-i/--idle):无请求时自动退出false
EVICTserve 空闲时清空数据缓存(-e/--evict)true
TLS_ONLYserve 仅提供 HTTPS(-s/--tls-only)false
TLS_PORTserve TLS 端口(-t/--tls-port)同 PORT
TLS_CERTTLS 证书路径(-c/--tls-cert)无
TLS_CERT_KEYTLS 私钥路径(-k/--tls-cert-key)无
OPENserve 启动时打开浏览器(-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_ALPHAbacklog EMA 平滑系数300
THURGIO_WEBHOOK_URL构建完成通知 webhook(POST JSON)无(禁用)
EVICT_TIMEOUT空闲后清空数据缓存的等待时间(秒,--evict 开启时)10
IDLE_TIMEOUT空闲模式退出等待时间(秒,--idle 开启时)30
DATABASE_URL数据库文件路径thurgio.db
REQUEST_TIMEOUTHTTP 客户端请求超时(秒)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_ENDPOINTOTLP 导出端点(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.tplproviderClash YAML最小模板(thurgio init 生成),json_str 逐节点输出
clash.tplproviderClash YAML紧凑 JSON 节点 + test 块 + rule-providers(遍历 config.snippet 的 p.clash_url)
clash_groups.tplproviderClash YAML按 p.groups 规则名动态生成 proxy-groups(filter(rules=...) 实时筛选成员)
clash_list.tplproviderClash YAMLclash.tpl 变体:头部注释打印 l.rename / l.filter
surge.tplproviderSurge 配置手写 [Proxy] 段(for 循环 + 字段展开,教学向)
surge_proxy.tplproviderSurge 配置surge_proxy 过滤器版 [Proxy] 段({{ l.proxies | surge_proxy() }})
surge_full.tplproviderSurge 完整配置[General] / [Proxy] / [Proxy Group] / [Rule] 四段齐全,可直接订阅导入(surge_proxy 节点行 + select/url-test 组 + p.rules 规则聚合,首行 #!MANAGED-CONFIG)
v2rayn.tplprovider纯文本每行一个 vmess:// 分享链接(v2rayn 过滤器)
sip002.tplprovider纯文本每行一个 ss:// 分享链接(sip002 过滤器)
sip008.tplproviderJSONSS Android 官方订阅:version + servers 数组(sip008 过滤器,仅 ss 节点)
ssd.tplprovider纯文本ssd:// 订阅:UrlSafe Base64 JSON(ssd 过滤器,仅 ss 节点)
shadowrocket.tplprovider纯文本每行一个分享链接(shadowrocket 过滤器,复用 sip002/v2rayn 转换,wg 跳过)
base64_sub.tplproviderBase64机场式订阅:v2rayn + sip002 链接整体 base64(mode="encode") 编码
plain_list.tplprovider纯文本人类可读节点清单:名称 | 类型 | 服务器:端口(for 循环 + 字段访问)
singbox.tplprovidersing-box JSON完整 sing-box 配置:sibox 转 outbounds + direct/block
singbox_rules.tplsnippetsing-box JSONclash 规则文本 → sing-box rule 数组(clashbox 过滤器),与 singbox.tpl 组合成完整配置
qx.tplproviderQX 节点行每行一个 QX 节点(qx 过滤器,ss/ssr/vmess/vless/trojan/http/socks5)
qx_config.tplproviderQX 完整配置完整 Quantumult X 配置:[general]/[dns]/[server_local]/[policy](qx_config 过滤器)
loon.tplproviderLoon 节点行每行一个 Loon 节点(loon 过滤器,10 种类型)
loon_config.tplproviderLoon 完整配置完整 Loon 配置:[General]/[Proxy]/[Proxy Group]/[Rule](loon_config 过滤器)
mellow.tplproviderMellow JSONMellow/V2Ray-core JSON:socks inbound + per-proxy outbounds(mellow 过滤器,ss/vmess/vless/trojan/http/socks5 + TCP/WS/TLS)
mixed.tplprovider混合分享链接混合单链接列表:逐节点 V2RayN 优先、SIP002 回退,mixed 过滤器
rules_aggregate.tplsnippetQuantumult X 规则集多规则集聚合:拼接 config.snippet 中各规则集内容 → uniq 去重 → sort_domain_ip 排序 → quantumult 输出(s.p.rules 指定要聚合的 snippet 名,s.p.policy 指定策略);换 clash / surge 过滤器即得对应格式规则列表
snippet.tplsnippet原样原样输出 snippet 内容(s.response.content)
rules_pipeline.tplprovider教学演示模板内 filter → rename → v2rayn/sip002/cla 过滤器链
date.tpl片段文本{% include %} 引入的生成时间注释
ui.tpl—HTMLDashboard 页面(服务器自带,勿删除)
auto/*.tplproviderUA 自适应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.yamlclash_list.tplClash
prod/example.yamlclash.tplClash
prod/example_groups.yamlclash_groups.tplClash(动态 proxy-groups)
prod/example_surge.txtsurge.tplSurge(手写 [Proxy] 段)
prod/example_surge_proxy.txtsurge_proxy.tplSurge(过滤器版 [Proxy] 段)
prod/list/example_v2rayn.txt / prod/example_v2rayn.txtv2rayn.tpl分享链接
prod/list/example_sip002.txt / prod/example_sip002.txtsip002.tpl分享链接
prod/example_sip008.jsonsip008.tplSS Android JSON 订阅
prod/example_ssd.txtssd.tplssd:// 订阅
prod/example_shadowrocket.txtshadowrocket.tplShadowrocket 分享链接
prod/example_base64.txtbase64_sub.tplBase64 订阅
prod/example_plain.txtplain_list.tpl纯文本节点清单
prod/example_singbox.jsonsingbox.tplsing-box 完整配置
prod/example_singbox_rules.jsonsingbox_rules.tplsing-box 规则数组
prod/example_china.listsnippet.tpl原样规则集
prod/example_quantumult.txtrules_aggregate.tplQuantumult 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 片段(小写)候选键 → 模板文件说明
surgesurge → auto/surge.tpl,未命中则按默认顺序继续 cla / qx / loon / sibox / shadowrocket / v2raynSurge(含 Mac / iOS)
quantumultqx → auto/qx.tplQuantumult X
loonloon → auto/loon.tplLoon
clashcla → auto/cla.tplclash / clash-verge / clashmeta / Clash for Windows 等
mihomocla → auto/cla.tplmihomo
stashcla → auto/cla.tplStash
sing-boxsibox → auto/sibox.tplsing-box
singboxsibox → auto/sibox.tplsingbox(无连字符)
sfasibox → auto/sibox.tplsing-box Android 前端
sfisibox → auto/sibox.tplsing-box iOS 前端
sfmsibox → auto/sibox.tplsing-box macOS 前端
shadowrocketshadowrocket → auto/shadowrocket.tplShadowrocket
v2rayv2rayn → auto/v2rayn.tplv2rayN / 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_configcheck_url=, dns=, group={{ l.proxies | qx_config(group="Proxy") }} — 完整 QX 配置四段
loon—{{ l.proxies | loon() }} — Loon 节点行(10 种类型)
loon_configdns=, group={{ l.proxies | loon_config(group="Proxy") }} — 完整 Loon 配置四段
mellowport=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)
clameta=true{{ l.proxies | cla(meta=true) | json_str() }} — 过滤出 clash 兼容节点
proxy_suffixsuffix="..."{{ l.proxies | proxy_suffix(suffix="HK") }} — 给节点名加后缀
jssrc="..."{{ l.proxies | js(src="return $input.map(p=>...)") }} — 内联 JS 变换(boa_engine)
sort_nodesby=name/region/latency, order=asc/desc, latencies={{ l.proxies | sort_nodes(by="latency", latencies=map) }}
clash_groupsgroups="a,b"{{ l.proxies | clash_groups() }} — Clash/Mihomo 策略组
surge_groups—{{ l.proxies | surge_groups() }} — Surge 策略组

规则过滤器(模板内动态应用 filter.yaml / rename.yaml)

过滤器参数示例
filterrules="a,b"(或数组){{ l.proxies | filter(rules="hk") | v2rayn() }}
renamerules="a,b"(或数组){{ l.proxies | rename(rules="rm_brackets") | v2rayn() }}

未定义的规则名静默忽略;过滤器可链式组合,顺序即应用顺序。

规则文本过滤器(snippet 内容 / 规则集)

过滤器参数示例
clashpolicy=..., symbol=...{{ s.response.content | clash(policy="Proxy") }} — 规则文本 → clash 规则列表
surgepolicy=..., symbol=...{{ s.response.content | surge(policy="Proxy") }}
quantumultpolicy=..., symbol=...{{ s.response.content | quantumult(policy="Proxy") }} — 规则文本 → Quantumult X 规则集(host/host-suffix/host-keyword/ip-cidr/ip6-cidr/geoip/user-agent/final;无对应类型的规则丢弃)
clashboxpolicy=..., premium=..., meta=..., pretty=true{{ s.response.content | clashbox(policy="proxy") }} — clash 规则文本 → sing-box rule 对象
no_resolvevalue=true/false{{ s.response.content | no_resolve(value=true) }}
sort_domain_ip—域名/IP 规则排序

通用过滤器

过滤器参数示例
base64mode="encode"/"decode"{{ lines | base64(mode="encode") }}
json_strpretty=false{{ i | json_str(pretty=false) }}
yaml_str—{{ l.proxies | yaml_str() }}
indentwidth=2{{ body | indent(width=2) }}
uniq—按行去重
clearnum=3, n=2压缩连续空行
slugify / urlencodeseparator="-"字符串处理

函数

函数参数示例
nowformat="[year]-[month]-[day]...", tz="+08:00", utc=true, timestamp=true{{ now() }}、{{ now(timestamp=true) }}
get_envname="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 等按需出现)
udpUDP 覆盖标志(可选)

字段集合随代理类型不同而异,以实际序列化结果为准;模板中 {{ 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.proxiesprovider代理对象数组(每个元素有 name / type / server / port 等字段)
l.name / l.rename / l.filterprovider名称 / 配置层规则名
s.response.contentsnippetsnippet 源内容(原样文本)
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-androidAndroid 设备(不是服务器)thurgio-aarch64-linux-android
aarch64-unknown-linux-muslARM64 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 秒用 busybox wget 探测公开端点 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)
Windowsjust build-win(x86_64-pc-windows-gnu)
Androidjust build-android(aarch64-linux-android)
Linux ARMjust 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 部署细节见部署指南。


升级前准备

  1. 备份配置声明文件:

    thurgio config backup
    

    该命令把 thurgio.yaml、5 个 sibling YAML 与 tpls/ 模板目录复制到 <root>/backups/<YYYYMMDD-HHMMSS>;它不加载配置、不访问网络、不碰数据库——即使当前配置已经改坏也能运行。详见 config backup/restore/list 与备份与回滚。

  2. 检查 CHANGELOG:对照下方版本间迁移说明,确认目标版本是否有需要手动处理的破坏性变更。没有对应条目时,按标准流程直接升级即可。

  3. 记录当前版本,便于回滚时选择旧 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 原文,只列真实存在的破坏性变更或需要用户动作的条目;无对应条目的版本按标准流程升级即可。

版本变更用户需要做什么
UnreleasedHTTP 参数绑定拆分(ADR-081):p 只承载配置参数,请求级覆盖移入新的 hp 绑定模板与调用方改用 hp 传请求级参数
0.18.0Token 作用域路由前缀(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.0Source 改为 untagged(ADR-065):反序列化为纯字符串,http(s) 前缀即 URL 源(breaking:!Url/!File 配置语法不再解析)删除 source: 字段的 YAML 标签写法
0.18.0Provider/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 创建。

如何访问

  1. 启动服务:

    thurgio serve
    # 默认监听 0.0.0.0:10100,可用 --address / --port 覆盖,详见 [Serve 命令](./cli/serve.md)
    
  2. 打开登录页:

    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 tokenAdmin 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 / Tera version 上下文。

系统状态卡

  • 展示: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)

按钮端点行为
BuildGET /app/admin/write完整构建:更新所有 provider/snippet、渲染并写盘;请求同步阻塞至构建完成,fetch 生命周期即状态条,失败显示响应 message
RefreshGET /app/admin/refresh强制更新所有 provider/snippet(不写盘)
ReloadGET /app/admin/reload重载 Tera 模板引擎,拾取磁盘变更
SaveGET /app/admin/save将当前已 fetch 的 provider/snippet 响应持久化到 DB(未 fetch 项跳过,避免覆盖 last-known-good)
DevGET /app/admin/dev?value={value}启用/禁用开发模式(value 必填 true / false,响应返回当前值字符串);开发模式下每次渲染前重载模板

临时资源(仅 admin)

  • 展示:表单 + 结果内嵌预览(超长折叠逻辑同渲染项)。

  • 端点:GET /app/admin/adhoc(创建一次性 provider/snippet,不写入配置文件,缓存持久化到 DB,重启后命中缓存即恢复;按 adhoc.max_items / adhoc.ttl_seconds 自动清理)。

  • 查询参数:

    参数必填说明
    type是provider / snippet
    url是数据源 URL
    name否名称,缺省自动生成 adhoc-<8 位 hex>
    format否clash / meta / v2rayn / sip002 / surge / auto(仅 provider 生效),默认 auto
    tpl否模板名,缺省回退 setting.adhoc.tpl.provider / tpl.snippet
    pre_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 tokenAdmin 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/p401允许
GET /app/admin/write、GET /app/admin/refresh、GET /app/admin/reload、GET /app/admin/save、GET /app/admin/dev?value=、GET /app/admin/adhoc401允许

自定义界面

覆盖方式

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_totalcounter累计 HTTP 请求数(record_request 每次 +1)
http_errors_totalcounter累计 status >= 400 的请求数
active_connectionsgauge当前在途请求数(RequestTimer::new +1 / finish -1)
http_request_duration_msgauge平均请求耗时(毫秒,request_duration_sum / count)
http_request_duration_histogramhistogram请求耗时直方图;桶边界 [1, 5, 10, 50, 100, 500, 1000, 5000, 10000] 毫秒,另含 +Inf 桶、_sum 与 _count
`http_status_codes{category=“1xx2xx3xx
http_rate_limited_totalcounter因并发或速率限制被拒绝并返回 429 的请求数(RATELIMIT = http_rate_limited_total)

2. 构建与更新管线

名称类型含义暴露
config_updates_totalcounterprovider/snippet 更新总次数(record_config_update)Prometheus
config_updates_failed_totalcounter更新失败次数(CONFIG_UPDATES_FAILED,经 MetricsRecorder 映射)Prometheus
config_renders_totalcounter模板渲染总次数(record_config_render,含 provider 主输出、具名输出与 snippet)Prometheus
config_builds_totalcounter完整构建(build)总次数Prometheus
build.completedcounterbuild 管线成功完成计数(BUILD_COMPLETED)Prometheus
build.update.failurecounterbuild 更新阶段失败计数(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.reloadcounter渲染器重载次数(RENDERER_RELOAD,dev 模式或 GET /app/admin/reload 触发)Prometheus
config_reload_failed_totalcounter配置热重载重解析失败次数(CONFIG_RELOAD_FAILED,失败时保留旧配置,不发事件)Prometheus
proxy_countgauge当前代理总数(PROXY_COUNT,gauge_set,可增可减)Prometheus
build.durationhistogram完整 build 耗时(秒,BUILD_DURATION)OTLP
`build.update.duration{status=“successfailure“}`histogrambuild 更新阶段耗时(秒,label status)
build.render.durationhistogrambuild 渲染阶段耗时(秒,BUILD_RENDER_DURATION)OTLP
build.update.successcounterbuild 更新阶段成功计数(BUILD_UPDATE_SUCCESS)OTLP
autobuild.successcounterAutoBuildHandler 自动重建成功计数OTLP
autobuild.failurecounterAutoBuildHandler 自动重建失败计数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_totalcounterTTL 缓存命中次数(CACHE_HIT,label `kind=providersnippet
cache_misses_totalcounterTTL 缓存未命中次数(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_mbgauge当前 RSS 内存(MB,get_memory_usage 读取 /proc/self/status 的 VmRSS;非 Linux 置 0)Prometheus
process_memory_baseline_mbgauge空闲驱逐后的引擎基线 RSS(MB,PROCESS_MEMORY_BASELINE)Prometheus
data_cache_memory_mbgauge空闲驱逐释放的数据缓存内存(MB,DATA_CACHE_MEMORY,rss_before - rss_after)Prometheus

6. 渲染线程池(render_renderer_metrics)

由 AppState::renderer().pool_metrics() 在 /metrics 末尾追加,HELP/TYPE/value 三行一组:

名称类型含义
renderer_active_threadsgauge当前活跃渲染线程数
renderer_backlog_smoothedgaugeEMA 平滑后的待渲染积压深度(浮点,保留两位小数)
renderer_threads_spawned_totalcounter累计创建的渲染线程数
renderer_threads_exited_totalcounter累计退出的渲染线程数
renderer_slots_totalgauge渲染线程池总槽位数

池参数由环境变量 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 /metricsGET /app/read/health
定位面向监控系统的时序指标面向人的订阅健康度聚合
数据计数器/直方图/gauge,带时间序列当前快照:overall(healthy/degraded/unhealthy)、summary{total,closed,open,half_open} 与 items[] 明细
熔断器circuit_breaker_state(数值 0/1/2)与 circuit_breaker_fail_countbreaker(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_LOGinfoEnvFilter 语法,如 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"
}

字段说明:

字段类型描述
statusstring始终为 "healthy"
versionstring名称 + 版本号 + git 快照
databasestring始终为 "connected"
memory_usage_mbintegerRSS 内存(MB,来自 /proc/self/status 的 VmRSS)
cpu_coresinteger可用并行度
timestampstringRFC 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 检查并更新数据。

查询参数:

参数类型描述
namestring必填——要渲染的 provider 名称(缺失或为空 → 400)
dstring可选——设置 Content-Disposition: attachment; filename="..."(为空时使用 name)
tokenstring认证令牌(也可通过 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 头。

查询参数:

参数类型描述
namestring必填——provider 名

响应:200 OK

{
  "name": "example",
  "upload": 1073741824,
  "download": 10737418240,
  "total": 107374182400,
  "used": 11811160064,
  "expire": 1893456000,
  "expire_iso": "2030-01-01T00:00:00Z"
}
字段类型描述
usedinteger | null已用流量(upload + download),两者均未知时为 null
expire_isostring | 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 返回空列表。

查询参数:

参数类型描述
namestring必填——provider 名
limitinteger可选——只保留最近 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 连接延迟测试。结果按次计算、永不缓存(延迟是时效性数据)。代理列表来自已解析的代理库(与渲染缓存同源),不重新解析原始内容。

查询参数:

参数类型缺省描述
namestring—必填——provider 名
timeout_msinteger3000单次探测超时(钳制到 [100, 10000])
retryinteger1每个代理重试次数(钳制到 [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 查询国家/地区。

查询参数:

参数类型缺省描述
namestring—必填——provider 名
timeout_msinteger5000单主机解析超时(钳制到 [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}

探测流媒体服务在当前出口网络的可用性与地区。

查询参数:

参数类型缺省描述
servicescsv全部三项要探测的服务:netflix、youtube、disney(别名 disneyplus);未知值 → 400
timeout_msinteger5000单服务探测超时(钳制到 [100, 10000])
proxyURL无本次请求使用的一次性上游代理(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}

启用或禁用开发模式(每次渲染前重新加载模板)。

查询参数:

参数类型描述
valueboolean必填——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 自动清理。

查询参数:

参数类型必填描述
typestring是provider / snippet
urlstring是数据源 URL
namestring否名称,缺省自动生成 adhoc-<8 位 hex>
formatstring否clash/meta/v2rayn/sip002/surge/auto,默认 auto(仅 provider 生效)
tplstring否模板名;缺省回退 setting.adhoc.tpl.provider/tpl.snippet
pre_rename / filter / renamestring否逗号分隔的规则名,按顺序应用(未知规则名静默忽略)
forceboolean否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。

查询参数:

参数类型描述
tokenstring可选——GitHub PAT;缺省时依次回退 config params 的 gist.token、环境变量 GITHUB_TOKEN
descriptionstring可选——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}"

查询参数:

参数类型描述
urlstring必填——长链接,必须以 http:// / https:// 开头(否则 → 400)
servicestring可选——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 TypePOST/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 . 会报错)。

子命令总览

命令别名说明文档
buildb更新 + 渲染 + 写盘;--watch 监听重建build
serves启动 HTTP/TLS API 服务器serve
initi生成最小可用配置脚手架init
checkc校验配置并打印摘要与逐项检查结果check
diffd输出文件 diff 或订阅节点级 diffdiff
configcfg配置声明文件备份 / 回滚 / 列表config
upgradeu检查 GitHub Releases 新版本upgrade
generateg生成 shell 补全脚本generate
versionV打印版本信息version

不带子命令直接运行 thurgio 只加载配置后退出(可用于验证解析是否成功)。

命令的初始化需求

各命令对环境的依赖不同(排错时有用):

阶段命令
不需要 logger/配置generate, version
只需要 loggerinit
只需要 root 目录config(备份/回滚在坏配置下也能运行)
完整初始化(配置 + 数据库 + 渲染器 + 熔断器恢复)build, check, diff, serve, upgrade

启动流程细节见 app。

环境变量

所有带环境变量的参数遵循同一优先级:命令行 > 环境变量 > 默认值/配置值。完整清单见配置指南。

App

启动流程(thurgio_cli::cli::init):

  1. 若子命令为 generate 或 version,直接执行并返回(不需要 logger)。
  2. 初始化 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 日志。
  3. 若子命令为 init,生成配置并返回(需要 logger,不需要配置)。
  4. 若设置了 -r/--root(或 ROOT 环境变量),set_root_dir 创建并切换到该目录。
  5. 初始化 Config 和 Renderer(thurgio_app::initialize:Source → AppState → 熔断器状态恢复)。
  6. 执行子命令(build/check/diff/upgrade/serve)。

不带子命令直接运行 thurgio 只执行初始化(加载配置)后退出。

build

alias: b

执行完整的 更新 → 渲染 → 写盘 管线,生成所有 provider(主输出 + 具名输出)与 snippet 的输出文件。

执行流程

  1. 更新订阅源 — 按 TTL 判断每个 provider/snippet 是否过期:未过期直接复用内存缓存;过期或 --force 时重新获取;获取失败时回退到数据库中的 last-known-good 缓存(配合熔断器,见常见问题)。
  2. 解析与规则管线 — 解析代理列表并应用 pre_rename → filter → rename(详见配置指南)。
  3. 渲染 — 构建渲染上下文(config / p / headers / version),provider 阶段渲染主输出与具名输出,与 snippet 阶段并发执行。
  4. 写盘 — 渲染结果经暂存目录原子发布到各输出路径。
  5. 发事件 — 构建完成发出 ProviderUpdated / SnippetUpdated / BuildCompleted 事件:触发 SSE 通知(/app/read/events)与 webhook(设置 THURGIO_WEBHOOK_URL 时 POST JSON)。

成功输出 ✔ Build complete,失败输出 ✘ Build failed(颜色遵循 RUST_LOG_ANSI / TTY 检测)。

参数

参数别名环境变量默认说明
--force-fFORCEfalse忽略缓存状态,强制重新获取所有订阅源
--watch-wWATCHfalse构建一次后进入监听模式,文件变更自动重建
--notify-NNOTIFYfalse构建完成时通过 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-aADDRESS监听地址;未指定时使用配置 setting.address(默认 0.0.0.0)
--port-pPORT监听端口;未指定时使用配置 setting.port(默认 10100)。端口为 0 时使用随机端口
--dev-dDEV开发模式:每次渲染前重新加载模板
--idle-iIDLE空闲模式:IDLE_TIMEOUT(默认 30s)内无请求则退出程序(配合 systemd Restart=always 按需拉起)
--evict-eEVICT空闲时清空数据缓存(默认开启;--evict=false 关闭):EVICT_TIMEOUT(默认 10s)无请求先清缓存,进程继续存活
--tls-only-sTLS_ONLY仅提供 HTTPS(必须同时提供证书与私钥,否则报错)
--tls-port-tTLS_PORTTLS 端口,默认等于 --port
--tls-cert-cTLS_CERTTLS 证书文件
--tls-cert-key-kTLS_CERT_KEYTLS 私钥文件
--open-OOPEN启动时用 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=auto UA 自适应,见 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, --forcefalse覆盖已存在的文件

生成的文件

<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)
检查项说明
Summaryprovider / 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。

执行过程:

  1. 读取 setting.output 中配置的所有输出文件的当前内容(不存在的文件视为空内容)。
  2. 执行一次强制构建(等价 build --force,会真实访问订阅源)。
  3. 逐文件打印新旧内容的行级差异:- 删除行、+ 新增行、空格前缀为未变更行。
参数说明
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.yaml5 个 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 的路径(含路径分隔符时按路径解析)

执行过程:

  1. 校验目标存在且包含至少一个声明文件(否则报错,不触碰当前状态)。
  2. 先把当前状态自动备份到 backups/<时间戳>(保险)——当前状态为空则跳过。
  3. 覆盖写回声明文件与 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-yfalse有新版本时额外打印发布页下载链接

行为

  • 查询 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: 设为 false
  • Try: 如果代理类型支持 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_items100临时资源数量上限(内存与 DB 一致清理)
ttl_seconds86400滑动 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。

字段总览

字段类型必填说明
namestring否显示名,未指定时用键名
typestring是输入配置格式,见下表
sourcestring是URL(http(s):// 开头)或本地文件路径
templatestring是主输出模板(相对模板目录)
filenamestring 列表否渲染结果写入的文件列表
pre_rename规则名列表否解析后、过滤前应用(引用 rename.yaml)
filter规则名列表否过滤阶段应用(引用 filter.yaml)
rename规则名列表否过滤后应用(引用 rename.yaml),应用前后均检查重名
emojibooltrue是否执行 emoji 逻辑
default_outputbool回退 p.default_output是否同时写默认路径文件
extstring回退 p.ext默认输出文件的扩展名
outputsmapping否具名输出,见下文
p任意 YAML{}自定义参数,模板中以 p.* 访问

type

输入配置格式:

  • clash — Clash YAML
  • meta — Clash.Meta YAML
  • v2rayn — V2RayN 分享链接(Base64 JSON)
  • sip002 — SIP002 URI(逐行)
  • surge — Surge 配置 [Proxy] 段
  • qx / loon / singbox / sip008 / ssd
  • telegram — TG 类 HTTP/SOCKS5 代理链接(tg://http / tg://socks / t.me/*)

auto 不是合法的 provider type——它只用于渲染侧 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)按键名引用,依次应用。

每条规则值为三种形状之一:

  1. 裸字符串简写:contain 匹配任一 | 分隔关键字,忽略大小写(最常用)
  2. 单个匹配器 mapping
  3. 匹配器 mapping 列表:顺序应用,代理必须命中全部(AND)

匹配器

键值说明
contain字符串或字符串数组名称包含任一关键字(数组 = OR,等价 `
start字符串名称以关键字开头
end字符串名称以关键字结尾
type字符串按代理协议类型过滤(ss, vmess, trojan 等)
regex字符串按正则匹配代理名

共享选项

键缺省说明
ignore_casetrue忽略大小写
negatefalse反转:命中则剔除

限制:列表(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 regex crate:支持 \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。典型用途:规则集聚合、静态片段、模板化配置文件。

字段总览

字段类型必填说明
namestring否显示名,未指定时用键名
templatestring是渲染模板(相对模板目录 THURGIO_TPL_DIR,默认 tpls/)
sourcestring否*URL(http(s):// 开头)或本地文件路径;内容进入 s.response.content
filenamestring 列表否渲染结果写入的文件列表
default_outputbool回退 p.default_output是否同时写默认路径文件
extstring回退 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)。

条目形状

每条目值为两种形状之一:

  1. 行列表(本地形态)——规则行数组
  2. 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必填规则集名(过滤器输入值被忽略)
formatclashclash / surge / clash-classical
policy🚀 Node SelectSurge 格式的路由策略后缀
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 参考。