Imported from brightmeows/dotfiles (
dot_config/clash-meta/AGENTS.md). Install upstream withnpx skills add brightmeows/dotfiles --skill clash-meta. Copyright stays with the author.
clash-meta 配置(面向代理)
本目录管理 mihomo (clash-meta) tun 全局透明代理。部署步骤、订阅管理、已知系统约束(systemd-resolved / firewalld / SELinux)见 README.md(面向人)。本文件聚焦代理维护时的高信号约定与踩坑点。
仓库根 AGENTS.md 的通用约定(chezmoi 默认源目录、提交规范、中文引号)此处不重复。
分层架构
| 层 | 载体 | 职责 |
|---|---|---|
| 配置源 | config.yaml.tmpl |
chezmoi 模板 → 渲染到 ~/.config/clash-meta/config.yaml |
| 部署桥梁 | deploy.sh |
手动 sudo:渲染产物 → 系统配置目录 + 重载服务(自动探测发行版布局) |
| 运行 | mihomo.service(Arch)/ clash-meta.service(Fedora) |
root + CAP_NET_ADMIN,读系统配置目录 |
| 分发壳 | party-override.yaml.tmpl → gen-party-override.sh → party-override.yaml(进仓库) |
Clash Party / Verge Rev 覆写包,GitHub raw URL 分发,不部署到 $HOME |
订阅明文仅落在 $HOME(渲染产物),不进 git。
发行版布局(deploy.sh 自动探测)
| Arch(mihomo-bin AUR) | Fedora(clash-meta COPR) | |
|---|---|---|
| 系统配置 | /etc/mihomo/config.yaml |
/etc/clash-meta/config.yaml |
数据目录(-d) |
/etc/mihomo |
/var/lib/clash-meta |
| 服务名 | mihomo.service |
clash-meta.service |
下文凡提 /var/lib/clash-meta(如 PUT /configs path 模式安全限制),Arch 上对应 /etc/mihomo。
订阅注入:消费点必须同步
订阅来自 ~/.env_self 的 *_SUBSCRIPTION 变量。config.yaml.tmpl 开头用 output 提取为 $subs,并物化 $subNames(合法条目的变量名,供策略组生成消费)。下列位置共同消费订阅列表,改提取/过滤逻辑必须同步:
| 位置 | 消费 | 作用 | 缺失的后果 |
|---|---|---|---|
proxy-providers |
$subs |
生成 provider 条目 | 无节点来源 |
proxy-groups |
$subNames |
生成订阅级自动/手动组与“节点选择”成员 | 订阅组缺失,入口无订阅可选 |
rules 头部 DOMAIN,host,DIRECT |
$subs |
订阅域名直连 | 连接死锁:下载订阅走 MATCH→节点→无节点 |
dns.nameserver-policy |
$subs |
订阅域名用国内 DoH | DNS 死锁:解析境外 IP 触发 fallback→走节点→无节点 |
域名提取统一用 urlParse $url | .host。nameserver-policy 是 mapping,须用 dict / hasKey 去重
(同一域名多个订阅会生成重复 key 导致 mihomo 报错)。rules 头部 DOMAIN,host,DIRECT 现在同样使用
dict / hasKey 去重(与 nameserver-policy 一致的 $seen dict 模式,但 rules 是序列而非 mapping,
去重目的是消除冗余规则而非避免报错)。
策略组结构
命名约定“emoji + 空格 + 对象 - 模式”:⚡ <订阅名> - 自动选择、🇯🇵 日本 - 手动选择;顶层为 🚀 节点选择,分流组为 🤖 AI / ✈️ Telegram / 🎬 流媒体与下载 / 🎵 BOF。
🚀 节点选择(select,全部规则的默认出口)= 订阅组 ×4 + 地区组 ×14 + DIRECT(19 项)- 域名分流组(AI / Telegram / 流媒体与下载)共用
&DomainGroupMembers锚(🚀 节点选择 + 全部订阅组 + 全部地区组 + DIRECT,20 项), 默认随“🚀 节点选择”,可按网站指定订阅或地区出口;分别承接ai_non_ip、telegram_*、stream_*+download_*规则 🎵 BOF组单独定义成员,首项 DIRECT 为默认出口,承接bmsoffighters.net与manbow.nothing.sh; BOF 投稿来源必须是住宅 IP,故默认直连,需要时可从组内切到订阅或地区节点- 组名出现在
rules出口与DomainGroupMembers锚(BOF 组不在锚内),改名须全量同步(grep 无 emoji 的旧名确认零残留)
维护约定:
- 地区列表在
.chezmoidata/clash-meta.yaml(仓库根,模板变量$.regions)一处定义,主配置与覆写壳的双模板循环共享;新增地区只改数据文件,正则锚在.fragments/filters.part.yaml同步 - 面板组顺序 = 配置里
proxy-groups的定义顺序(metacubexd 以GLOBAL.all为索引排序):保持“订阅源、地区、领域”三段,新增组按归属插到对应段内 - 订阅级组用
use: [<订阅名>]精确引用各自 provider;地区组用include-all + filter跨订阅汇入 - 自动组
url必须与 provider 健康检查 URL 一致(gstatic):相同 URL 共享测速、不注册额外检查任务;改成其它 URL 会让每轮健康检查对同批节点多测一遍 - 信息类假节点在 provider 层
exclude-filter剔除(剩余|到期|过期|重置|官网|订阅):改词表前先拉/providers/proxies的真实节点名做命中验证,确认零误伤(真实节点名含“下载”“流媒体”等字样,勿进词表) - “韩国”“其它”两个池在当前订阅里无匹配节点,组回退为 COMPATIBLE 占位(直连语义),属预期行为
共享事实源与覆写壳
规则主体与地区数据由主配置和覆写壳两个消费点共享,改动必须保双端同步:
| 事实源 | 消费点 |
|---|---|
.chezmoidata/clash-meta.yaml(仓库根,$.regions) |
双模板的地区组循环与成员列表 |
.fragments/filters.part.yaml(Filter* 锚定义) |
双模板 include |
.fragments/rule-providers.part.yaml(RuleSet* 锚 + providers) |
双模板 include |
.fragments/rules.part.yaml(静态规则行,两空格缩进文本碎片) |
双模板 include 拼进 rules: |
party-override.yaml.tmpl(壳版组结构) |
gen-party-override.sh 渲染 → party-override.yaml(生成物进仓库) |
- 壳的必要差异仅一处:组结构无订阅级组(party 侧订阅 URL 未知,include-all 地区组覆盖选节点);组名、地区组字段、规则行与主配置逐字段同源
- 壳不含 dns / tun / sniffer / 订阅注入:本机强耦合段,且客户端接管配置优先级高于覆写
- 改片段或地区数据后跑
bash dot_config/clash-meta/gen-party-override.sh重新生成;忘跑会被 pre-commit 的--check拦下 .fragments/与生成物在.oxfmtrc.jsonc排除格式化(碎片非完整 yaml,格式化会破坏缩进);模板与生成物在.chezmoiignore排除部署(两源条目映射同一目标,chezmoi 报 inconsistent state)- raw 分发地址、导入姿势与验证命令见 docs/clash-meta-party-override.md
踩坑点
自动组固定节点(fixed)与其解除
url-test / fallback 组支持 fixed(固定优先节点):PUT /proxies/{组名} body {"name":"节点名"} 固定;
DELETE /proxies/{组名} 清除;对组做延迟测试(GET /group/{组名}/delay)也会清除 fixed。
fixed 节点存活时一直使用(不比较延迟),不存活时回退正常逻辑,恢复后自动切回;随 profile.store-selected 持久化在 cache.db,重启保留。
fixed 是组级共享状态:对“🇯🇵 日本 - 自动选择”固定会同时影响所有引用它的组(“🚀 节点选择”与各分类组)。
测速机制:url-test 为何不增加探测流量
mihomo 无 ICMP 测速,组延迟与 provider 健康检查都是 HTTP GET 到 url(gstatic),数值含 TCP/TLS 握手。
use: [provider] 的组不自行调度测速:创建时把组 url 注册给 provider 健康检查(与 provider URL 相同则忽略,不同则每轮多测一套并按组 filter 限定成员)。
provider 每 interval 全量测速,lazy: true(默认)时一个周期内无流量经过则跳过;tolerance 只影响切换频率、不减少探测。
抓取 yaml 参考必须 curl,禁用 web_fetch
exa_web_fetch / anysearch_extract 会把多空格缩进压平到 1 层,导致 mihomo 报大量 mapping key already defined。抓远程 yaml 用:
curl -sL <raw URL> -o /tmp/ref.yaml
DNS 双死锁(respect-rules + fallback)
respect-rules: true 让 DNS 按域名匹配的规则选 nameserver;fallback(境外 DoH)需走节点。节点未就绪时(启动期、订阅失效),解析到境外 IP 的域名全部超时——包括订阅域名本身,形成“下不到订阅→无节点→解析更下不到”的死循环。
死锁面控制:nameserver 仅含国内 DoH(alidns / doh.pub),不放境外 DoH(如 cloudflare)——境外 DNS 全放 fallback。避免 nameserver 中的境外 DoH 服务端域名在 respect-rules 下同样需走节点才能解析,把死锁面从订阅域名扩到所有经该 DoH 解析的域名。
订阅域名靠 nameserver-policy 绕过 fallback 打破。改 dns 全局策略前务必理解此耦合。
新增:fallback-filter.domain 显式列出了已知易被污染的境外域名(+.google.com、+.github.com 等),让这些域名直接走 fallback(境外 DNS)解析,跳过 nameserver 污染→fallback-filter 判定的环节,更快拿到正确 IP。
route-exclude-address 须含本地网段
tun.route-exclude-address 必须排除全部本地网段,否则 mDNS / SSDP / 局域网设备发现会走代理而异常。须包含:私有段(10/8、172.16/12、192.168/16)、127.0.0.0/8(loopback)、
169.254.0.0/16(link-local)、224.0.0.0/4(组播)及 IPv6 对应段(fc00::/7、fe80::/10、ff00::/8)。验证:ip route get 169.254.1.1 应走物理网卡而非 mihomo。
auto-redirect 与 Fedora firewalld 冲突(TUN 静默失败)
tun.auto-redirect: true 用 nftables 在 output 链重定向本机出站流量到 tun(mihomo 1.19.5+,文档标“比 auto-route 快”)。
Fedora firewalld 默认 nftables backend,auto-redirect 创建规则时与之冲突,netlink 返回 EEXIST(file exists),
mihomo 报 Start TUN listening error: auto redirect: ...,tun 接口从未创建、流量全走直连——
但 mihomo 进程仍 active,极具迷惑性。
桌面单机透明代理不需要 auto-redirect:auto-route(路由表 2022 + ip rule fwmark)已足够把本机流量导入 tun,
纯路由表方式不碰 nftables。auto-redirect 为网关/转发场景设计。本配置 auto-redirect: false,勿改回 true。
诊断 TUN 未生效:ip -br link | grep mihomo(无)+ ip route show table 2022(空)+ journalctl -u clash-meta | grep "auto redirect"(file exists)。
system/mixed 栈 NAT 回注与 ufw INPUT(放行由 deploy.sh 保证)
sing-tun stack: system/mixed 的 TCP 是 NAT 模式:tun 读到 SYN 后改写目的为 tun 接口地址 + 内核监听
端口(processIPv4TCP → writeBack),交本机内核 TCP 栈完成握手。回注包以 iif=mihomo、
conntrack NEW 过 netfilter INPUT 链;ufw(本机 Arch 是 ufw/iptables-nft,非 Fedora firewalld)
INPUT 默认拒绝,目的为 LOCAL 的包经 ufw-after-input → ufw-skip-to-policy-input 丢弃:SYN 到不了内核
监听 → 无 SYN-ACK → 全部 TCP 静默死亡(境内直连也死),/connections 无连接、无日志。UDP 不过此
路径(system 栈 UDP 直通写回、mixed 栈 UDP 走 gvisor),ICMP 同样用户态应答——故障面精确限定在 TCP。
单变量实验闭环(内核 7.1.9):mixed 栈下 ufw allow in on mihomo → TCP 立即复活;
ufw delete → 复死。官方 wiki 亦明示“开启防火墙则 system/mixed 不可用需放行”。
本机决策:stack: system + deploy.sh 自动放行(ufw 分支 allow in on mihomo,幂等、持久化于
/etc/ufw/user.rules;firewalld 机器走 trusted zone 分支),部署后自动 curl 验证数据路径。
勿绕过 deploy.sh 改防火墙或换防火墙——放行缺失即静默复发。诊断:TCP 全死(境内直连也死)+
pkexec nft list chain ip filter ufw-skip-to-policy-input 计数随探测增长即中招。
payload 热加载重建 tun 有竞态(device or resource busy)
PUT /configs?force=true 会重建 tun 设备,实测偶发 Start TUN listening error: configure tun interface: device or resource busy——tun 消失、流量走直连,且用直连探测会得到“正常”假象(境内直连
本就通)。需要验证 tun 行为时先 PUT 一份 tun.enable: false 的配置卸载,再 PUT 目标配置;
或直接 systemctl restart。部署一律走 deploy.sh(systemctl restart),不受此影响。
DoT 直连 IP 与接口监控空窗(TUN 探测警告风暴)
fallback 中的 tls://8.8.4.4:853(IP 直连 DoT)在 suspend/resume、换网、挂起恢复的接口监控空窗期
高频重试并刷 Auto detect interface ... get empty name:monitor 拿不到默认接口名时,每次 DoT 重连
都记一条 warning。实测:单次挂起恢复风暴 2.1 万条,风暴日 mihomo 日志全天可达数十
万行(journal 历史累计 562 万行/2.8GB)。已移除该项,保留 dns.google 与 dns.twnic.tw
两个域名形式 DoH(解析走 bootstrap,重试节奏不同,双源冗余不变)。若日后要恢复第三境外源,
优先 DoH 域名形式,勿用 IP 直连形式。
开机竞态(本机 drop-in 已修)
Arch 上 mihomo 早于 NM 的 DHCP 约 3s,开机刷 default interface lost + no such device(monitor 自愈)。
Omarchy mask 了 wait-online,After=network-online.target 无效。本机在
/etc/systemd/system/mihomo.service.d/override.conf 用 ExecStartPre 等默认路由(最长 30s 超时放行);
机器本地配置不入仓库,换机部署需重建(内容见 README 开机竞态节)。
external-controller 端口与 external-ui serve 启动时绑定
external-controller 监听端口与 external-ui 的 HTTP serve 在 mihomo 启动时绑定。payload 热加载(PUT /configs)不重新绑定端口、不重新注册 ui serve——改这两项后必须 deploy.sh 重启验证。
本地面板访问(clash.localhost)
面板用 http://clash.localhost:9090/ui/metacubexd/(external-ui-name 决定子路径)。
external-controller 监听 9090(与 metacubexd 默认后端一致,免手动填后端)。
不要用 mihomo hosts 做本地域名——实测其对 DNS 查询的拦截不可靠(自定义 TLD 查询返回 NXDOMAIN),.localhost 走浏览器原生解析绕过此问题。
配置校验与热加载(无需 sudo 的验证闭环)
# 1. 语法校验(oxfmt 不管 .tmpl,这是唯一强校验)
mihomo -t -d /tmp -f ~/.config/clash-meta/config.yaml
# 2. payload 模式热加载验证运行效果(无需 sudo)
CFG=$(python3 -c 'import json;print(json.dumps(open("/var/home/brightmeows/.config/clash-meta/config.yaml").read()))')
curl -X PUT 'http://127.0.0.1:9090/configs?force=true' -d "{\"payload\":$CFG}"
# 3. 验证地区组节点就位
curl -s http://127.0.0.1:9090/proxies | python3 -c "import json,sys;d=json.load(sys.stdin)['proxies'];print('🇭🇰',len(d['香港 - 手动选择']['all']),'🇯🇵',len(d['日本 - 手动选择']['all']))"
PUT /configs 的 path 模式只接受 /var/lib/clash-meta 下路径(mihomo 安全限制),验证时必须用 payload 传内容。
工作流
改配置的标准流程:
- 编辑
config.yaml.tmpl chezmoi apply ~/.config/clash-metamihomo -t -d /tmp -f ~/.config/clash-meta/config.yaml(退出码须为 0)- payload 热加载 +
/proxies验证节点 sudo bash ~/.config/clash-meta/deploy.sh(持久化到/etc)
完成标准:mihomo -t 退出码 0 + 地区策略组有节点 + 外部连接(curl https://www.google.com)HTTP 200。
边界
Always
- 改订阅注入逻辑时,同步
$subs/$subNames的全部消费点(proxy-providers / proxy-groups / rules DIRECT / nameserver-policy) - 静态主体改动后跑
mihomo -t - 抓远程 yaml 用
curl,不用 web_fetch
Ask First
- 改
dns全局策略(respect-rules / fallback / enhanced-mode)——死锁敏感 - 改
tun段(auto-redirect / dns-hijack)——影响全局路由与 DNS 劫持
Never
- 把订阅明文 URL 写入本目录文件(仅允许
~/.env_self与渲染产物) - 用 web_fetch 抓 yaml 参考(压平缩进)
