feat: 添加 WireGuard 设备管理并完善客户端分流

实现独立加密设备身份、网关 agent、持久生命周期任务和 Surge/FlClash 专属订阅凭证。

完善 Shadowrocket 规则转换与 Clash AI DNS,增加 Surge FFSB 适配、规则快照、运维工具及验收文档。
This commit is contained in:
YANG JIANKUAN
2026-11-02 22:44:23 +08:00
parent f741aeeccd
commit a5d980a5e9
80 changed files with 4777 additions and 51 deletions

View File

@@ -126,7 +126,7 @@ Surge 不支持组级 `url=`;组的 `testUrl` 仅用于 Clash/ShadowRocket
### ShadowRocket 分两步导入
ShadowRocket 的原生订阅格式只承载节点,因此拆成两个端点:`/shadowrocket`(节点 base64 URI 列表)与 `/shadowrocket-conf`(策略组与规则的 .conf)。节点订阅按完整名称字符串升序排列,保留出口词;该序列化器对副本排序,不修改 IR/数据库,跨客户端名称排序由上述节点权重约定保证。**ShadowRocket 没有 Surge 的 `use=` 订阅引用语法**(真机验证过:`use=true` 会被当成解析不了的成员残留),策略组动态收节点靠 `policy-regex-filter` 从 App 内全部节点里筛(「收纳全部节点」输出 `policy-regex-filter=.*`),显式成员也要保留。因此订阅名随意,但用户在 App 里挂的其他订阅的节点也会被筛进来;这些节点及 App 自定义排序不由本服务控制。本生成器当前保守跳过 `DOMAIN-SET`/`AND`/`OR`/`NOT`/`URL-REGEX`/`PROTOCOL`,并回报 `skippedRules`;这不是客户端不支持这些类型的断言。官方更新记录已确认存在逻辑规则与 QUIC 匹配,具体配置语法仍须以 App 内帮助和真机编译验证,详见 `docs/MOBILE-CLIENT-AUDIT-2026-10-04.md`。**ShadowRocket 的 conf 策略名不可用 Surge 引号机制**:策略名(含空格/emoji)一律裸写,加引号会被当作名字的一部分,导致规则指向不存在的策略组而整条失效、流量落到默认行为走代理——Surge 生成器可以加引号,ShadowRocket 生成器绝不可以。
ShadowRocket 的原生订阅格式只承载节点,因此拆成两个端点:`/shadowrocket`(节点 base64 URI 列表)与 `/shadowrocket-conf`(策略组与规则的 .conf)。节点订阅按完整名称字符串升序排列,保留出口词;该序列化器对副本排序,不修改 IR/数据库,跨客户端名称排序由上述节点权重约定保证。**ShadowRocket 没有 Surge 的 `use=` 订阅引用语法**(真机验证过:`use=true` 会被当成解析不了的成员残留),策略组动态收节点靠 `policy-regex-filter` 从 App 内全部节点里筛(「收纳全部节点」输出 `policy-regex-filter=.*`),显式成员也要保留。因此订阅名随意,但用户在 App 里挂的其他订阅的节点也会被筛进来;这些节点及 App 自定义排序不由本服务控制。Shadowrocket conf 在提供转换基址时把 DOMAIN-SET 转成本服务鉴权 `.list` 端点的普通 RULE-SET(精确主机→DOMAIN,前导点→DOMAIN-SUFFIX);未提供转换基址的离线调用仍跳过。LAN 按 Surge 官方文档列出的 14 条域名/CIDR 规则展开,保留原参数;明确的 macOS `/Applications/*.app/` 进程路径不下发 iOS。AND/OR/NOT/URL-REGEX/PROTOCOL 仍保守跳过并回报 `skippedRules`;这不是客户端不支持这些类型的断言。2026-10-13 已部署并完成实际 HTTPS 订阅/转换端点验收,依据与验证见 `docs/MOBILE-CLIENT-FIX-2026-10-13.md`。官方更新记录已确认存在逻辑规则与 QUIC 匹配,具体配置语法仍须以 App 内帮助和真机编译验证,详见 `docs/MOBILE-CLIENT-AUDIT-2026-10-04.md`。**ShadowRocket 的 conf 策略名不可用 Surge 引号机制**:策略名(含空格/emoji)一律裸写,加引号会被当作名字的一部分,导致规则指向不存在的策略组而整条失效、流量落到默认行为走代理——Surge 生成器可以加引号,ShadowRocket 生成器绝不可以。
Clash 节点必须明确下发 UDP 能力:VMess/Trojan/纯 TCP SS/HY2 输出 `udp: true`,SS WS 插件仅转发 TCP,输出 false,不能假设 nginx 的同端口提供 SS UDP。VMess cipher 按 mihomo 命名映射、未知 cipher 明确排除;SS plugin 的 TLS 参数在 `plugin-opts` 内。RULE-SET 保留 no-resolve;逻辑规则逐个转换子类型,仅把 PROTOCOL TCP/UDP 映射 NETWORK,任一子条件无法表达时整条跳过。两端 url-test 保留零 tolerance,fallback 不输出 tolerance。完整体检需分别核对客户端能力与生成器范围,原生解析成功不等于设备连接或隐私验收。
@@ -179,7 +179,7 @@ schema 变更同时更新 `shared/src/schemas.ts`/DTO(涉及对外字段时
## Surge AI DNS 与协议规则
改 DNS/隐私行为先读 `docs/SURGE-PRIVACY.md`、`server/src/services/generators/surge-ai-dns.ts` 及对应测试。AI 域名 DNS 映射来自 IR 内启用的 AI 路由规则;不要把全局 DNS 或所有端口 53 请求改送 AI。Surge 的 `PROTOCOL` 应用协议规则不能直接复制到其他客户端:Clash 只把 TCP/UDP 转成 `NETWORK`,其他应用协议跳过;ShadowRocket 跳过 `PROTOCOL`。生成配置校验不能替代设备 DNS/WebRTC 真机验证。
改 DNS/隐私行为先读 `docs/SURGE-PRIVACY.md`、`server/src/services/generators/surge-ai-dns.ts` 及对应测试。 Clash 的本地候选从同一 IR 域名规则按顺序派生 nameserver-policy,使用 inline classical matcher 保留早期非 AI 例外,并复用远程 provider;AI DoH 通过官方 `#策略组名` 参数绑定 AI,节点域名用独立普通 DNS 引导。该改动不作用于 Surge,未开启全局 respect-rules。既有 DoH 会话可能复用旧出口,新建连接才随 AI 选择;不要声称切组立即迁移全部 DNS 会话。Shadowrocket 的 AI DNS 写法尚缺可靠语法/真机依据,仍未实现。AI 域名 DNS 映射来自 IR 内启用的 AI 路由规则;不要把全局 DNS 或所有端口 53 请求改送 AI。Surge 的 `PROTOCOL` 应用协议规则不能直接复制到其他客户端:Clash 只把 TCP/UDP 转成 `NETWORK`,其他应用协议跳过;ShadowRocket 跳过 `PROTOCOL`。生成配置校验不能替代设备 DNS/WebRTC 真机验证。
手写 Host 项优先;生成的域名 DNS 映射按连接规则顺序保留 AI 之前的非 AI 域名/规则集例外,不把 Host 当成能表达进程、协议或 IP 条件的路由系统。宽泛 AI 域名规则仍须审阅覆盖范围。
@@ -193,6 +193,16 @@ Net.Coffee 的 Claude 页通过随机 `*.d.ip.net.coffee` 探测递归 DNS 出
## 验证与完成标准
WireGuard 已有实现见 `docs/WIREGUARD-LAN.md`:历史身份曾放在 `sub_tokens.wireguard`(已迁移为独立加密设备,见下段),设备使用独立 token/地址/密钥,不加入共享公网代理池。`irFor()` 只附加当前 token 身份,原共享订阅保持无 WG;历史 token PUT 省略 wireguard 时保留身份;当前通过独立设备映射兼容,token API 不再接受身份写入。标准 WG 配置保留独立授权导出;Shadowrocket WG YAML 只属于历史实现,本轮已移除生成器与 DTO 链接,旧专用路径明确返回 404。Mac Surge 入站要求 6.11+,旧版本 CLI 解析通过不等于入站验收。撤销订阅 token 不等于撤销已导入的 WireGuard peer。2026-11-01 实测 Apple VNC 成功组合为 iPad 官方 WireGuard → njcq → Mac Surge 6.11.0 build 12660,MTU 1280;同机 Shadowrocket 路径出现媒体缺包/黑屏,具体实现原因尚未确认。
**WireGuard 最新产品约定(2026-11-02,已部署)**:开发先读 [实现交接](docs/design/wireguard-devices/HANDOFF.md)、[完整设计](docs/design/wireguard-devices/DESIGN.md) 与同目录可点击原型。用户个人 macOS/iPadOS/iOS 已统一 Surge。设备页保留固定 IP、状态、最后活动、流量、管理操作及独立 WG 导出,删除“使用客户端”。Surge 订阅增加可选设备下拉:不选为原默认纯代理 URL,选中生成带设备分发凭证的派生 URL,只附加此设备身份;清除选择恢复默认 URL,不改变其他已生成链接。设备身份迁到独立实体,旧 token 兼容保留;不实现身份模块方案,也不把 UI 选择写成共享 token 的全局设备状态。凭证不可枚举,`#!MANAGED-CONFIG` 必须保留完整设备参数;暂停需真正撤销 peer。原型附加设备均为演示,不能拿去初始化生产。实现状态、API、网关安装与验收边界见 [WIREGUARD-DEVICES](docs/WIREGUARD-DEVICES.md)。已将实际旧 token 身份迁移到加密 `wg_devices`,旧 token 用 `wg_device_id` 兼容映射;`/api/tokens` 不再接受 wireguard 写入,普通列表不返回密钥。`wg-master.key`(或启动环境 `WG_MASTER_KEY`)必须与 SQLite 一同受控备份,丢失不得重新生成覆盖;暂停/恢复/重置/移除由持久任务等待运行与持久配置双确认,LA 与 njcq agent 已部署,原有两台设备只读核对并 confirmed,网关持久/运行 peer 身份不变、普通代理正文不变。用户随后已在 macOS/iPadOS/iOS 全面验证 Surge + WireGuard 可工作;真实新增/暂停/恢复/重置等未由本轮助手执行,不把组网验证扩写为所有生命周期、长期/睡眠/换网均已验收;FFSB 原生 WG 试验属于不同身份与网络,不合并进 njcq。
**同日后续:单一主订阅**:用户已明确只保留一个订阅,生产通过 API 删除两条 `NJCQ WireGuard - iPad Pro/Mac Studio` 旧订阅,仅保留原 `Proxy Station` 主订阅及原默认 URL。两台设备身份、IP、peer 和独立导出仍保留,统一从主订阅选择设备生成带凭证的派生 URL;旧设备 token URL 已 404,不再按早期兼容约定恢复这两条卡片。该要求是当前生产收拢,不是将某台设备写成主订阅的全局身份,也不限制其他部署的通用多订阅能力。详情与备份见 [单一主订阅记录](docs/WIREGUARD-DEVICES.md#2026-11-02-单一主订阅收拢)。
**同日后续:停止 Shadowrocket WG 兼容**:用户确认 macOS/iPadOS/iOS 的 Surge + WireGuard 已实测可工作,Shadowrocket 的 Apple VNC 失败排查仅作历史记录,不再推进该路径。Shadowrocket 节点与 conf 仅下发普通代理数据:路由不附加/读取设备 WG 身份,序列化器忽略 IR.wireguard,不生成 WG 私网策略规则或额外节点导入提示,也不再按 WG 身份改写动态组筛选。已删除 Shadowrocket 专用 WG YAML 生成器与 DTO 导出链接,`/sub/:token/wireguard-shadowrocket` 显式返回 404,防止落入 SPA。保留 Surge 可选设备、标准 WG 独立导出/二维码、设备与网关管理,普通 Shadowrocket 代理兼容仍保留。该清理当前仅完成本地代码验证,尚未重新部署。
**FlClash Android(2026-11-02)**:已核对官方 v0.9.2 及其固定内核,WireGuard 出站支持;当前标准 Android 实现未提供 Surge 式 `allow-peer-access` 本机服务入站桥接。不能用 allow-lan、TUN、内部栈 Listen 方法或 WG 握手成功代替双向设备验收。FlClash 可选设备身份已部署,旧 `/clash` 默认仍为纯代理,设备凭证按用途隔离;尚未进行 Android 真机测试,不恢复 Shadowrocket WG 兼容。完整依据及兼容性审查见 [FlClash 核对](docs/FLCLASH-WIREGUARD-AUDIT-2026-11-02.md) 与 [配置兼容性](docs/FLCLASH-COMPATIBILITY-2026-11-02.md)。
| 改动 | 建议验证 |
|---|---|
| 服务端逻辑/解析器/生成器 | `pnpm --filter @proxy-station/server typecheck` + 相关 Vitest;新增行为用占位凭据构造 IR/分享链接用例 |
@@ -213,3 +223,18 @@ Net.Coffee 的 Claude 页通过随机 `*.d.ip.net.coffee` 探测递归 DNS 出
改生成器时检查节点过滤、组成员引用、规则顺序和 FINAL 兜底;Clash 可用 mihomo 校验,Surge/ShadowRocket 的客户端语法与实际连接仍需真机验收。`excluded`/`skippedRules` 是生成器返回字段,订阅 HTTP 响应只返回配置正文。
完成后说明改动、已通过的验证与尚未完成的实际验证。新增业务约束或修正旧认识时同步更新这里或相应 `docs/`,让后续 Codex/Claude Code 会话能接续。
### FFSB / Claude 手动订阅分流(2026-10-19)
最新方案见 `docs/FFSB-MANUAL-SUBSCRIPTION-2026-10-19.md`,已替代早先本机协调层。当前仅用正式托管 `Proxy Station`;不恢复 `Proxy Station Claude` 或 LaunchAgent。可见 `🦀 Claude` 为 select,新增 `🦁 FFSB`;仅源清单 25 项受它选择,合并的集合外地址始终经隐藏 `Claude-Surge-Internal` → `🧠 AI Nodes`。Surge-only 生成器适配读取生产 settings 的 FFSB JSON,其他客户端保持原行为。25 项唯一源 `server/assets/ffsb-domains.json`,`rules/ffsb-domains.json` 为符号链接。
必要设备前提:FFSB `disableDns=true` 并重连生效,本机实测仍保留内部 DNS;Surge 增强模式用 fake-IP 接住普通域名流量。不要给这 25 项添加 always-real-ip,否则真实地址可被 FFSB 系统路由提前接走,绕过手动选择。选择 FFSB 时绑定 utun100 并向 100.70.255.254 查询;选择代理时远端解析;FFSB 不运行时选它允许失败,不自动回退。其他 Mac 需一次性确认接口、DNS 和客户端设置,不能声称仅导入就通用;iOS 未验收。用户已授权本次一次性设置调整和有恢复保证的 FFSB 断开/重连测试,仍未授权重启 Surge 引擎。真实模型流、长期/睡眠/换网及完整启动顺序仍未验收。
### 本机 FFSB 原生 WireGuard 独立验证(2026-11-02)
探索记录、管理连接边界与接续步骤见 `docs/FFSB-WIREGUARD-SURGE-2026-11-02.md`。用户授权导出既有 WG 身份并独立验证:测试期间 FFSB GUI/daemon 停止,27 项原生规则、隧道内 DNS、35/35 多轮请求、错误端点反向对照及 39 秒公开响应传输通过。原生方案只使用已下发配置快照,没有替代 NetBird 管理连接;长期/睡眠/换网/重新认证、真实模型业务与完整在线规则集快照未验收。真实 WG 身份和证据仅在本机权限 700/600 的私有归档中,绝不入 Git;不写入共享订阅。
**当前状态(同日 11:05 回退验收)**:按用户要求恢复正式托管 `Proxy Station`+FFSB,Claude → `🦁 FFSB`,FFSB GUI/daemon 和 utun100 已恢复,disableDns=true;3 轮 24/24 请求、原清单 25/25 规则、FFSB 内层 utun100/外层 DIRECT en0、集合外美国 AI 与普通日本 Proxy 分流通过。试验 `Proxy Station WG Validation` 已移出 Surge 配置列表并私有归档,原托管文件、生产数据、生成器及其他手动组选择未改,未重启引擎。FFSB 下发新增 hCaptcha 两项尚未同步生产 Surge 清单,保留原方案范围。后续若再试原生 WG,须先断开并停止 FFSB;两者复用相同身份,不可并行连接。不要依据旧试验快照擅自重新激活原生 WG。
Surge CLI 的 profile dump 会隐藏认证字段,不能将其输出直接复制成可用的完整配置;需保留真实凭据时在本机私有文件中使用磁盘原件并校验,输出与备份仍须脱敏、受限权限。