Files
proxy-station/AGENTS.md
YANG JIANKUAN a5d980a5e9 feat: 添加 WireGuard 设备管理并完善客户端分流
实现独立加密设备身份、网关 agent、持久生命周期任务和 Surge/FlClash 专属订阅凭证。

完善 Shadowrocket 规则转换与 Clash AI DNS,增加 Surge FFSB 适配、规则快照、运维工具及验收文档。
2026-11-02 22:44:23 +08:00

241 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AGENTS.md
本文件是 Codex 与 Claude Code 共用的项目开发约定。`CLAUDE.md` 用 `@AGENTS.md` 导入本文件;项目规则只在这里维护。双工具入口、技能发现和使用方法见 `docs/AI-COLLABORATION.md`。
## 协作方式
- 默认用中文沟通,代码标识符沿用项目现有英文命名。
- 按任务读取相关代码与文档;以代码、测试及用户最新要求为准,发现说明过时就同步修正。
- 本地阅读、编辑、类型检查、单测和构建按任务自主完成。部署或生产数据变更须属于用户授权的任务;已有明确授权不重复确认。普通代码修改不隐含上线或修改生产数据。
- 保留用户已有改动。提交、推送与部署分别按用户要求执行。
- 项目技能唯一源文件在 `.claude/skills/`;`.agents/skills/` 通过相对符号链接供 Codex 发现。修改技能时编辑源文件,避免维护两份副本。
## 这是什么
本仓库统一维护边缘 VPS 节点的部署管理与 Proxy Station 代理节点、分流规则管理平台。Web 界面管理多协议节点(VMess/Trojan/Shadowsocks/Hysteria2)与策略组、规则,向 macOS Surge、iOS ShadowRocket、Android ClashMeta 下发托管订阅。规则模板来自 [ClashConnectRules/Surge](https://github.com/ClashConnectRules/Surge),边缘舰队的工具与私有交付记录已从原 `../edge-nodes/` 迁入本项目。
### 当前生产约定(2026-10-04)
最新盘点、完整组顺序与成员、节点启用范围和 Session 变更索引见 [当前生产状态](docs/CURRENT-STATE.md)。日期化体检/变更文档保留当时的数量与验证结果;维护前经管理 API 重读,不能用历史快照或初始化模板覆盖最新生产数据。
- 当前登记 30 个节点,12 个启用、18 个停用;12 个启用组、21 个显式成员、130 条规则(128 启用)。停用节点保留凭据,不因文档整理、排序、部署或导入清单核对而自动启用。应用的节点启用状态不等于边缘 VPS 的运行状态。
- 普通代理流量交给 `🚀 Proxy`,只有它引用 `⚡️ Automatic`。Automatic 为 url-test,成员依次为新加坡、美国、日本;规则不直接指向 Automatic。`🤖 AI` 为 select,成员依次为美国、新加坡、日本、`🧠 AI Nodes`,不引用 Automatic。
- `📋 All Nodes` 与 `🧠 AI Nodes` 都是独立 select 组,开启 includeAllNodes、没有筛选正则或显式成员;AI Nodes 紧接 AI 显示。两组收纳相同的订阅范围内可用节点,各自保存客户端选择,不能用引用 All Nodes 代替独立的 AI Nodes。
- 两组按完整节点名称字符串升序输出,保留 Direct/Warp 出口词。现有节点 sortOrder 全为默认 0,IR 按 sortOrder、name 读取;新增/导入节点保持默认值。非零权重会先于名称排序,调整它影响节点声明及其他动态列表,不只是这两个组。Web 节点页仍按站点、协议与出口展示。
- Streaming 当前承接 Disney、Netflix、TikTok、YouTube、GoogleSearch 及欧洲/韩国/香港/台湾流媒体集合;美国/日本流媒体集合直接交给对应地区组,Emby 交给 Proxy。组显示顺序不决定规则匹配顺序。
- 规则优先级保持:STUN 与 googlevideo UDP 防护 → Unbreak DIRECT → 启用的 AI 规则 → 业务例外/广告与其余分流 → 唯一 FINAL。FINAL 仍为 `🎯 NoAuto,dns-failed`,NoAuto → Mainland → DIRECT。保留 Google 更新/翻译直连例外;全局 PROTOCOL DOH 与 Dia 关键词规则继续停用。
- Apple/Global Services/Speedtest/Other 及无节点的香港/台湾/韩国/英国组已删除,不因模板仍存在而恢复。App Store DIRECT、Apple News 美国、Apple Relay AI 例外继续保留。
### 节点部署管理
- `edge-nodes/README.md` 是节点工作区入口;`edge-nodes/build-delivery-doc.py` 从 Git 忽略的 `edge-nodes/private/fleet.json` 读取清单并离线生成汇总交付页,不在源码里保存真实站点信息。
- 通用部署脚本、参考资料与占位交付模板唯一源码在 `.claude/skills/edge-node-multi-protocol/`,Codex 从 `.agents/skills/edge-node-multi-protocol` 相对链接加载。部署/重建/维护 VPS 节点使用此技能;发布 Proxy Station 应用用 `deploy`,生产订阅数据查询与修改用 `update-db`。
- 真实配置、面板入口、分享链接、二维码、交付文档和各站点脚本工作副本在 `edge-nodes/private/<id>-edge-node-docs/`,全部禁止入 Git 或公开静态托管,也不发送到在线二维码服务。新增配置/产物用 `umask 077`,私有目录用受控备份转交,新克隆不会携带这些文件。
- 当前舰队为 sg(AWS 新加坡)、la(DMIT 洛杉矶,同时承载 Proxy Station)、rn(RackNerd 洛杉矶,当前工作副本为 `rn2-edge-node-docs/`)、jp(VMISS 东京,裸 nginx)。`rn-edge-node-docs/` 与 `private/archive/` 是历史资料,不作为当前配置来源;原兄弟目录仅保留为迁移前备份,后续在本仓库维护。
- 舰队自 2026-10-03 统一使用 IPv4:系统接口禁用 IPv6,Xray/sing-box 仅解析 IPv4 并拒绝 IPv6 目标(含 WARP),节点域名不保留 AAAA。重建或扩展线路须保留这些约束,详见 `docs/EDGE-IPV4-ONLY.md`;仅禁用网卡或删除 AAAA 不能阻止 WARP 隧道内的 IPv6 转发。
- TCP 基础线路由 nginx+3x-ui/Xray 承载 VMess/Trojan/SS × direct/WARP,QUIC(HY2/TUIC)由 sing-box 独立承载。3x-ui 客户端不能只写投影表,必须保留 `settings.clients`,否则升级迁移会丢失认证。改远端前读取节点交付记录与技能参考,备份受影响配置和数据库,并保留已有证书续期动作。
- 迁移工程、修改脚本或复制文件不会授权远端部署;节点与应用的部署各自按任务执行。应用运行时仍通过管理 API/Web 导入节点,不自动读取私有目录。TUIC/SOCKS5 交付线路不属于本系统当前支持的导入类型。
## 命令
```bash
CI=true pnpm install --frozen-lockfile # 按锁文件安装依赖
pnpm dev # server :3000 + web :5173(都监听 0.0.0.0,局域网可访问)
pnpm -r typecheck # 三个包全量类型检查
pnpm test # 服务端单测(vitest)
pnpm --filter @proxy-station/web build
# macOS Surge:先取实际 HTTPS 订阅到私有临时文件,再用官方 CLI 与规则集快照验证
python3 scripts/check-surge.py /private/tmp/<profile>.conf --fetch-rulesets
# 单测过滤(在仓库根目录下)
pnpm --filter @proxy-station/server exec vitest run parse
pnpm --filter @proxy-station/server exec vitest run -t "round-trip"
pnpm --filter @proxy-station/server exec vitest # watch 模式
# 部署到 LA 生产机(web|server|all),详见 .claude/skills/deploy/SKILL.md 与 docs/DEPLOY.md
bash .claude/skills/deploy/scripts/deploy.sh all
# 本机离线生成边缘舰队交付页(清单与产物均在 Git 忽略的私有目录)
python3 edge-nodes/build-delivery-doc.py
# 校验实际 Clash 配置;生产验收改用实际 HTTPS 入口,真实 URL 仅在本机使用
umask 077
check_dir="$(mktemp -d /private/tmp/proxy-station-check.XXXXXX)"
curl -fsSL --max-time 45 "http://localhost:3000/sub/<token>/clash" -o "$check_dir/clash.yaml" && \
test -s "$check_dir/clash.yaml" && mihomo -t -d "$check_dir" -f "$check_dir/clash.yaml"
```
本机开发及当前生产部署使用 Node 22。依赖使用 pnpm 与已提交的 `pnpm-lock.yaml`;不要混用 npm/yarn 或无故更新锁文件。pnpm 在无 TTY 时安装需要 `CI=true`,原生模块(better-sqlite3、esbuild、vue-demi)的构建许可写在 `pnpm-workspace.yaml` 的 `allowBuilds`。
本机会话注入的代理曾造成 git/安装/网络检查挂起;遇到此问题,在单条命令作用域内用 `env -u HTTPS_PROXY -u https_proxy -u HTTP_PROXY -u http_proxy -u ALL_PROXY -u all_proxy <命令>` 直连。现有运维脚本已自行处理代理。不依赖其他工具的全局指令文件,也不修改用户全局代理配置。
## 架构
应用部分为 pnpm monorepo:`shared/`(zod schema 与 DTO,前后端共用源码,非构建产物)+ `server/`(Hono + better-sqlite3 + Drizzle)+ `web/`(Vue 3 + Naive UI + Pinia)。生产模式由 server 静态托管 web 产物,单端口。`edge-nodes/` 与节点部署技能是 Bash/Python 运维工具,不加入 pnpm 包,也不随应用部署上传;Docker 构建上下文排除它们。
### 配置生成流水线(核心)
```
SQLite → buildProfileIR({nodeIds}) → ProfileIR → 四个序列化器
├─ generateSurge(ir, {selfUrl})
├─ generateShadowrocket(ir) 节点订阅
├─ generateShadowrocketConf(ir) 策略组+规则
└─ generateClash(ir, {convertBaseUrl})
```
`server/src/services/ir.ts` 是配置生成流水线的统一读库入口,把节点、策略组、规则、Surge 原文段落收成与客户端无关的 `ProfileIR`;四个生成器只读 IR,互不依赖。**新增客户端就是加一个序列化器**,不要在生成器里再查数据库。
IR 层承担三件防护,改动时不要绕过:
- 停用或已删除的策略组会被剔除,引用它的成员与规则一并跳过
- `FINAL` 规则永远保留,其策略组失效时降级为 `DIRECT`
- `nodeIds` 为空数组表示「不限制」,非空则按订阅分配的节点范围过滤
### 客户端能力矩阵
`shared/src/protocols.ts` 的 `clientSupports()` 决定某节点能否下发给某客户端,生成器据此排除并回报 `excluded`。**Surge 不支持 Shadowsocks over WebSocket**;本项目对 Surge 的 VMess 仅下发 `alterId=0`,加密方式只支持 auto/aes-128-gcm/chacha20-poly1305(转换为 IETF 名称)。其他客户端的能力独立判断。不要把无法表达的输入静默改成另一个协议组合。
### 协议字段差异
同一协议在三端写法不同,映射集中在 `shared/src/protocols.ts` 与各生成器:
- SS cipher:分享链接用 Xray 命名 `chacha20-poly1305`,Surge/Clash 要 `chacha20-ietf-poly1305`(`normalizeSsCipher()`)
- VMess `alterId=0` 必须输出 Surge 的 `vmess-aead=true`,否则连不上且症状隐晦
- Hysteria2 的 salamander 混淆:Surge 用 `salamander-password=`,Clash 用 `obfs`/`obfs-password`
- SS over WS 在 Clash 侧唯一可行方案是 `plugin: v2ray-plugin`
这些写法参考了 `../sub-router` 项目(用户已在生产使用的同类系统),遇到新的客户端适配问题优先查它。
### 策略组的两种取节点方式
`includeAllNodes` 与 `filterRegex` 可以共存,生成器里 **filterRegex 分支优先**:
- 有 `filterRegex` → 动态筛选:Surge 保留显式节点/内置成员,全部成员组作为 `include-other-group` 来源,并可同时启用 `include-all-proxies=true`;正则只筛动态来源,不筛显式成员。Clash 用 `include-all-proxies: true` + `filter` 并保留 `proxies` 显式成员;ShadowRocket 保留显式成员并用 `policy-regex-filter` 动态收节点。新增同地区节点会自动进组
- 仅 `includeAllNodes` → Surge 静态展开全部节点名,Clash/ShadowRocket 仍用动态引用
**地区组靠节点名称正则匹配,不看 `region` 字段**。节点命名规范与新增地区的完整步骤见 `docs/ADDING-REGIONS.md`——命名不对就进不了地区组。
节点与组共用客户端的策略名称空间:禁止名称中的配置分隔符、引号、反斜线、控制字符和首尾空格;不能与内置策略或另一类实体重名。导入预览与实际写入都走同一 schema。组成员必须存在且类型正确,创建/更新(包括改名)校验完整引用图无环,并在事务内写组、替换成员与级联引用。
Surge 不支持组级 `url=`;组的 `testUrl` 仅用于 Clash/ShadowRocket。Surge 使用 General 的 `proxy-test-url` 或节点 `test-url`,fallback 不输出 tolerance。协议、规则和参数规范以 [Surge 官方手册](https://manual.nssurge.com/) 与本机 `surge-cli --check` 为依据,不能凭其他客户端语法推断。
### 规则集:映射与兜底转换
模板里的 `RULE-SET`/`DOMAIN-SET` 指向外部 URL。Surge/ShadowRocket 直接透传;Clash 需要 rule-providers,`services/rulesets/mapping.ts` 按序尝试映射到 skk.moe 的 Clash 版与 blackmatrix7 的 Clash 目录,映射不到的标记 `needsConvert`,provider URL 指向自托管端点 `/sub/:token/ruleset/:id.yaml`——该端点实时拉取 Surge 版并用 `convert.ts` 转成 classical provider。规则集拉取带 24h 缓存 + etag,失败回落陈旧缓存。
### 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。完整体检需分别核对客户端能力与生成器范围,原生解析成功不等于设备连接或隐私验收。
### 鉴权与订阅
除公开的 `/api/health` 外,`/api/*` 走 `adminAuth`:设置了 `ADMIN_TOKEN` 则要求 Bearer,未设置时仅放行 localhost 与 RFC1918 私有网段(公网 IP 一律拒绝)。`/sub/:token/*` 公开,token 用常量时间比对。**订阅 URL 完全由请求来源决定**——后端只返回路径,前端用 `window.location.origin` 拼接,`#!MANAGED-CONFIG` 与 Clash provider URL 取本次请求的 origin,所以没有也不需要「订阅基址」配置项。
未匹配的 `/api/*` 显式返回 404,不落到 SPA 回退(否则会拿到一页 HTML,调接口时极难排查)。
### Surge Mac 与 Tailscale 共存
修改增强模式、DNS、STUN 或 VPN 路由前,先读 [docs/SURGE-TAILSCALE.md](docs/SURGE-TAILSCALE.md)。2026-10-06 已实测:独立 Tailscale 客户端设置 `--accept-dns=false`,Surge 仅将具体 tailnet 域名交给 `100.100.100.100`,配合 always-real-ip 和实际 Tailscale 网络扩展/CLI 的优先 DIRECT 规则。保留原有公网代理、AI DNS 和其他应用的 STUN 阻断;不使用 Exit Node,不需要向项目保存 Tailscale auth key。
**不能机械套用 `tun-excluded-routes = 100.64.0.0/10`**:本机实测该项创建指向物理网关的路由,覆盖 Tailscale 路由。最终配置不添加这些排除项。skip-proxy、DIRECT 和 VIF 路由排除是不同层次,必须检查实际路由表及普通应用连接。
tailscale ping 可通不代表 curl/SSH 可通;客户端显示 Running 也不代表公网未受影响。测试必须同时覆盖原代理、AI DNS、MagicDNS、普通 tailnet 服务和外网入站 SSH。使用 Tailscale CLI 做 netcheck 时也需进程例外,避免把自身 STUN 被阻断误判为网络不支持 UDP。测试 VPN 重连前设置失败自动 tailscale down 的恢复措施,并确认用户未同时手动切换。
现有订阅的持久来源是生产 settings/rules,模板仅供首次初始化;通过 update-db 技能先备份再走 API,不直接改托管文件或重置规则。结论仅限实测环境与场景:两种启动顺序、IPv4/IPv6 服务和蜂窝网络入站 SSH 已通过;睡眠唤醒、长期运行及网络切换尚未验证,不宣称“完美兼容”。
同账号/同 Surge 订阅不会同步 Tailscale 的本机 `accept-dns` 与 Exit Node 设置。新 Mac 必须逐台检查;MacBook Air 使用相同配置时仍曾保留 accept-dns=true,需在该机单独关闭。不要仅凭 Surge 自带探测成功认定普通应用正常,须另外测不使用显式代理的 curl。
### Telegram 跨平台分流(2026-10-11)
最新修复记录见 [docs/TELEGRAM-RULES.md](docs/TELEGRAM-RULES.md),规则快照见 [rules/telegram.list](rules/telegram.list)。生产已通过管理 API 添加 37 条高优先级自定义规则,Telegram 域名及 IPv4/IPv6 地址统一交给 `🚀 Proxy`;本机更新订阅后登录二维码恢复显示。仅配置 `osx.telegram.org` 不足以覆盖数据中心连接。此变更晚于上面的 2026-10-04 生产盘点,历史数量与规则顺序不能视为当前值;维护前仍须经 API 重读。规则快照不会自动跟随上游更新。
## 前端约定
UI 走「站牌导视」设计方向(轨道交通导视系统语言):白底 `#f7f7f4`、深藏青重点色 `#2b3f5c`、**琥珀 `#b26a00` = 直连出口、青 `#007a72` = WARP 净出口**,协议各有线路色圆标。颜色只在承载信息时出现——避免发光、亮边条、装饰性渐变。四个方向的设计对比留在 `docs/design-demos.html`。
两个共用工具:
- `utils/nodeName.ts` 的 `nodeDisplayName()`:与出口标签并排显示时剥掉名字末尾的出口词。**下发给客户端的名字必须保留出口词**,客户端里没有标签,用户只能靠名字区分同协议的两条线路
- `utils/clipboard.ts` 的 `copyText()`:`navigator.clipboard` 只在 HTTPS/localhost 可用,局域网 HTTP 访问时是 `undefined`,直接调用会静默失败。必须走这个带 `execCommand` 降级的封装
## 数据与安全
SQLite 落在 `server/data/`,存有节点真实凭据。`data/`、`*.db`、`.env`、`edge-nodes/private/`、`config.env` 及其备份、`node-links/` 已在 `.gitignore`——**绝不入 git**。可入库的示例、测试、模板一律用占位凭据;填写后的交付副本只放私有目录。节点通过 Web 界面粘贴分享链接导入,应用不直接读 `edge-nodes/private/`(见 `docs/SEEDING.md`)。
schema 变更同时更新 `shared/src/schemas.ts`/DTO(涉及对外字段时)、`server/src/db/schema.ts` 的 Drizzle 定义和 `server/src/db/index.ts` 的启动 SQL。新表在 `BOOTSTRAP_SQL` 中加 `CREATE TABLE IF NOT EXISTS`,旧库补列用同文件下方的轻量 `ALTER TABLE` 循环(吞掉「列已存在」错误)——没有引入 migration 工具。
公网部署必须设 `ADMIN_TOKEN` 并用反代提供 HTTPS:订阅 token 就在 URL 里,明文 HTTP 会泄露。
## 初始化与已有数据
`server/src/db/seed.ts` 只在策略组为空时从 `server/assets/surge-template.conf` 初始化策略组、规则和 Surge 段落。修改模板不会更新已有数据库;既有订阅仍以数据库为准。生产数据变更使用 `update-db` 技能,经管理 API 修改并先做 SQLite 在线备份,确认备份 root 属主、600 权限,避免绕过校验和级联更新。
组的 members 是整体替换;排序请求始终先读完整列表并提交全量、无重复 ids。规则 reorder API 强制校验完整性与末尾 FINAL;当前组 reorder API 只逐条写顺序,调用方须自行核对完整性。组 PUT 不更新 sortOrder,显示排序使用独立 `/api/groups/reorder`。节点 PUT 使用私有文件提交完整原字段,排序调整后重读确认除 sortOrder/updatedAt 外没有变化。
`server/src/env.ts` 直接读 `process.env`,没有 dotenv 加载;仅复制 `.env.example` 成 `.env` 并不会让 `pnpm dev` 自动读取它。变量须由启动环境提供(Docker Compose 与 systemd 有各自注入方式)。相对 `DATA_DIR` 始终基于 `server/` 解析。
## Surge AI DNS 与协议规则
改 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 域名规则仍须审阅覆盖范围。
Net.Coffee 的 Claude 页通过随机 `*.d.ip.net.coffee` 探测递归 DNS 出口,不直接查询 Claude 域名;使用 `DOMAIN-SUFFIX,ip.net.coffee,🤖 AI` 同时覆盖页面和探测子域。Surge 派生根域/通配 Host 到 Cloudflare DoH,并随 AI 出口。本机 Chrome+Surge 已复测为美国 Cloudflare「未检测到泄露」,仅代表该探测路径的实际结果,不能推广为所有客户端/域名/远端解析器已验收,见 `docs/NET-COFFEE-DNS-2026-10-04.md`。
规则变更先读 `docs/SURGE-CHANGES.md`。API 必须保住一条启用且末尾的 FINAL;禁止停用、删除、改类型或创建重复 FINAL,排序必须提交完整且无重复的 ids。IR 对遗留坏数据防御性补齐 FINAL,其失效策略降级 DIRECT。重置规则在事务内保留自定义规则,不重复插入原主键,也不能用 reset 代替定向调整生产规则。
规则按首条匹配执行(另留意 pre-matching)。业务例外须在覆盖它的广告/通用规则集之前,特定 UDP 拦截须在对应服务域名规则之前。靠前的 IP 规则集是否加 `no-resolve` 必须按意图决定:它能避免主动解析,但会改变未解析域名的 IP 拦截覆盖。普通规则值与策略引用用 `surge-format.ts` 编码,逻辑表达式保留结构;不要手拼含逗号的正则或含换行的字段。
共享订阅不附公开默认控制口令;默认关闭 Remote Controller/HTTP API,确需使用时按设备配置独立凭据与访问限制。MITM 不关闭上游服务器证书验证;需要 MITM 时另做设备 CA、开关与 HTTPS 行为验证。
## 验证与完成标准
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/分享链接用例 |
| shared schema/DTO/协议能力 | `pnpm -r typecheck` + `pnpm test`,兼顾前后端消费者与各客户端产物 |
| Vue 页面/样式/状态 | `pnpm --filter @proxy-station/web typecheck` + `pnpm --filter @proxy-station/web build`;页面交互与局域网 HTTP 复制按需验证 |
| 数据库结构/启动行为 | 临时 `DATA_DIR` 或 `createDb(':memory:')` 验证新库与旧库升级;注意下述导入副作用 |
| 文档/协作入口/技能 | 检查路径、导入、符号链接、技能 frontmatter 与脚本语法;不为纯文档修改触发部署 |
| 节点部署工具/交付页生成器 | Bash 语法与 Python 编译检查、占位配置离线验证;迁移核对原文件、私有路径 Git 忽略及技能链接,不为迁移执行远端脚本 |
涉及节点、组、规则、DNS 或 Surge 段落时,在上述检查之外对实际订阅运行 `scripts/check-surge.py`;涉及规则集或顺序用 `--fetch-rulesets`,审阅域名遮蔽提示。脚本调用官方 Surge CLI,不更换活动配置;输出 OK 不表示测速、业务连接或设备隐私已验收。敏感配置用权限 600 的临时文件,不进入仓库或对话。生产写入先在线备份,再 API 更新、重读、HTTPS 拉订阅与 CLI 验证;必要时同时用 mihomo 校验受影响的 Clash 产物。
取订阅后先核对下载成功、非空及预期结构,再交给原生解析器。mihomo 在目标配置不存在时可能创建默认配置并报告成功,不能用这个结果验收订阅;所有检查遇到失败即停止,避免前一步失败而最后一条命令成功掩盖错误。
区分数据库、实际 HTTPS 产物、客户端活动配置与设备网络结果。用户授权的同机复测需刷新既有托管配置时,先核对托管 URL、备份并读取最新组选择,用 `surge-cli managed-profile update` 后检查 effective 配置与实际选项。组改名会改变客户端保存选择的身份;必要时仅将原选定节点及父组路径迁移到新名称,保留 All Nodes/AI Nodes 的独立选择,不擅自切换出口或活动配置。客户端模块可覆盖生成的组类型与参数,不能把本机模块效果写回生产数据库。
`db/index.ts` 在模块加载时就执行 `export const db = createDb()`,包括只导入 `createDb` 的情形;临时数据库测试/启动验证应在加载模块前设置绝对路径的临时 `DATA_DIR`,不要拿本地真实 `server/data/` 做试验。现有生成器单测使用内存中的 IR;运行服务还会执行初始化。
改生成器时检查节点过滤、组成员引用、规则顺序和 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 会隐藏认证字段,不能将其输出直接复制成可用的完整配置;需保留真实凭据时在本机私有文件中使用磁盘原件并校验,输出与备份仍须脱敏、受限权限。