Files
YANG JIANKUAN 5326c572a2 feat: 合并边缘节点运维并完善 Surge 配置安全
迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。

完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。

验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
2026-10-03 23:19:59 +08:00

103 lines
9.6 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.

---
name: update-db
description: 经管理 API 查询或维护 LA 生产环境的节点、策略组、规则、订阅 token 与 Surge 设置,写入前在线备份。用于线上数据盘点、导入预览与数据变更;代码部署使用 deploy。
---
# 变更 proxy-station 生产数据
线上数据(节点、策略组、规则、token、设置)住在 la 机的 SQLite(`/opt/proxy-station/data/proxy-station.db`),由常驻的 `proxy-station` 服务直连。本 Skill 的做法是:**走服务自己的管理 API 做变更,变更前用 sqlite3 在线备份做快照**,不直接写库、不重启服务。
本技能由 Codex 与 Claude Code 共用,命令在仓库根目录运行;`.agents/skills/update-db` 链接到本目录。先区分查询、导入预览与实际变更;纯查询/`commit:false` 预览无需备份,也不能借此执行写入。用户已明确授权的具体变更不重复确认,范围不清时先查询现状并明确目标。
## 为什么必须走 API,不能直写 SQLite
API 不是简单转发,直写会绕过它的全部防护:
- zod 校验拦住坏数据(VMess 必有 UUID、WS 必有 path、SS 必有 method)
- 节点名唯一冲突返回 409;规则的 policy 必须指向存在的组或内置策略,否则规则整条失效
- 策略组改名会级联更新引用它的组成员与规则;members 是「整组替换」语义,API 帮你做对
- 订阅 token 由 crypto 随机生成、常量时间比对——手工 INSERT 塞进 null 或坏格式,`/sub` 全部当场 500
生成器每次请求实时读库,**API 改完订阅即刻生效**,不需要重启服务。直接动 SQLite 文件仅限两种只读场景:`api.sh counts` 的行数盘点,以及从备份快照里查旧值。
## 用法
```bash
bash .claude/skills/update-db/scripts/api.sh api GET /api/nodes
bash .claude/skills/update-db/scripts/api.sh api POST /api/nodes '{"name":"…","protocol":"…",…}'
bash .claude/skills/update-db/scripts/api.sh api PUT /api/settings --body-file /private/tmp/<private-body>.json
bash .claude/skills/update-db/scripts/api.sh api GET /sub/<token>/clash # 拉订阅做校验(公开端点)
bash .claude/skills/update-db/scripts/api.sh backup <context> # context 用小写连字符 slug,如 add-jp-hy2
bash .claude/skills/update-db/scripts/api.sh counts
bash .claude/skills/update-db/scripts/api.sh health
```
- `api` 经 ssh 在**远端本机回环**调接口:`ADMIN_TOKEN` 只在远端 `/etc/proxy-station/env` 里展开,不经本地 shell、不落对话记录;JSON 走 stdin,无需转义
- 含凭据、私有 Host 或复杂 JSON 时使用权限 600 的临时文件及 `--body-file`,不把正文放到 shell 字符串或进程参数;响应也保存到私有文件并只汇报脱敏摘要
- 响应末尾带 `__HTTP_STATUS:200__` 一行。**非 2xx 时先读响应体里的错误信息再决定下一步**,不要盲目重试;要管道给 jq 时先剥掉这行:`| sed '$d' | jq …`
- 连不上 la(解析出 198.18.x.x)是本地 fake-IP 劫持了 ssh,与远端无关,等网络恢复再试
- 环境变量可覆盖 `DEPLOY_HOST`/`PS_DATA_DIR`/`PS_ENV_FILE`,默认即生产值
## 标准流程
1. **摸现状**(只读):`api GET /api/groups`、`/api/rules`、`/api/nodes`、`/api/tokens`,把要动的对象当前值拿到手。改组成员、调规则顺序这类「全量替换」操作,不先拿全量必出错。
涉及下发行为先读 [Surge 变更验证](../../../docs/SURGE-CHANGES.md)。检查业务例外与广告/服务集合的顺序、Host 的 DNS 例外、FINAL 和客户端节点范围;新防护依赖代码修复时先完成并验证代码部署。
2. **备份**:`api.sh backup <context>`。一个变更主题一个备份,同主题内的多条变更共用一份。备份落在远端 `data/backups/`(root 属主),不要清理。
3. **执行**:逐条调 API,每条核对 HTTP_STATUS 与返回体。某条失败就停下排查,不要跳过继续。
4. **校验**:
- `counts` 与变更前对比,行数增减要能对上意图
- `api GET` 变更对象,确认落库的是新值
- 涉及节点/组/规则/token 范围/Surge 设置:从实际 HTTPS 入口拉 Surge 订阅到私有临时文件,核对 self URL,运行 `python3 scripts/check-surge.py <文件> --fetch-rulesets`;审阅遮蔽提示。回环 API 拉取的内网 self URL 不能替代 HTTPS 验收,CLI 不等于节点连接或泄漏测试
- 影响 Clash 时拉其产物并在独立临时目录运行 `mihomo -t -d <临时目录> -f <文件>`;不启动代理或覆盖活动配置
- `excluded`/`skippedRules` 只存在于生成器内部、不在 HTTP 响应里;要核对静默排除时,用 `/api/nodes` 列表对比 yaml 里的 `proxies`
5. **汇报**:向用户复述三件事——变更清单、备份文件名、校验结果。三者齐了才算完成。
## 各实体的操作要点
### 节点 `/api/nodes`
- 新节点来自分享链接:先 `POST /api/nodes/import` 带 `{"text":"…","commit":false}` 预览(返回每条的解析结果与自动去重后的名字);用户授权导入且结果符合目标时,备份后再 `commit:true` 入库。只要求预览时到预览为止
- 节点名是分流的关键:必须保留出口词尾缀(客户端里没有标签,用户靠名字区分直连/WARP 线路);地区组按名称正则收编,涉及新地区先读 `docs/ADDING-REGIONS.md`
- 名称不能含配置分隔符、引号、反斜线、控制字符或首尾空格,不能与组或内置策略重名;导入预览与 commit 使用同一字段验证。Surge 当前排除 SS over WS、非零 alterId 及不支持加密方式的 VMess,核对实际输出而非只看节点总数
- 临时下线用 `PATCH /:id/enabled {"enabled":false}`,不要删——IR 层会自动剔除停用节点,删了凭据就没了
- 真要删除:先 GET 全量字段核对对象,向用户展示名称及影响(不展示密码/UUID);删除范围已有明确授权时按流程 DELETE,否则先确认
### 策略组 `/api/groups`
- `members` 是**整体替换**:GET 全量 → 改数组 → PUT 回去,别只发改动项
- 成员三态:`{kind:"node", nodeId}`/`{kind:"group", refName}`/`{kind:"builtin", refName}`
- `filterRegex` 与 `includeAllNodes` 可共存;Surge 保留显式节点/内置成员,全部成员组成为动态来源,正则只筛动态来源。新增同地区节点按名称进入组
- 成员必须存在且类型正确,图无环;禁用组在 IR 中被剔除,不要新写指向停用组的规则
- 改名后端会级联引用,但已导入客户端的旧组名会失配——改名前先向用户点明
- `testUrl` 用于 Clash/ShadowRocket;Surge 用 General 的 `proxy-test-url`,组级 `url=` 无效。fallback 不使用 tolerance,url-test 可显式设 0
### 规则 `/api/rules`
- 顺序由 `PUT /api/rules/reorder` 的**全量、无重复 ids 数组**决定,启用的唯一 FINAL 必须末尾。自定义创建置顶,但有明确顺序意图时按完整列表调整;业务例外在覆盖它的集合之前,特定 UDP 拦截在服务域名规则之前
- FINAL 不可停用、删除、改类型或重复创建;失效策略由 IR 降级 DIRECT。不要借防御性兜底制造无效数据库状态
- 新规则的 policy 必须已存在(内置 DIRECT/REJECT… 或现有组名),后端会校验,报错就先建组
- `POST /api/rules/reset` 事务内恢复模板并**保留自定义规则**;不是定向更新生产规则的方法,除非用户明确要求重置,否则不调用
- 靠前 IP 集是否加 `no-resolve` 按意图决定;主动解析可能绕过后续业务规则,no-resolve 又会改变对未解析域名的覆盖。URL-REGEX 值只检查 HTTP URL,不能拿 SMTP 文字当邮件协议匹配
### 规则集 `/api/rulesets`(只读)
- `GET /api/rulesets` 看清单,`GET /:id/content` 看自托管转换结果
- `ruleset_cache` 是 24h 缓存(etag + 陈旧回落)自动管理,不要手动碰;「改规则集」实际是改引用它的规则
### 订阅 token `/api/tokens`
- 输入 `{name, nodeIds}`:`nodeIds` 空数组 = 下发全部启用节点;非空 = 只下发这些节点(给不同设备发不同范围的入口就在这里配)
- token 值由后端随机生成,创建响应里拿一次;`lastAccessAt`/`accessCount` 可判断是否仍在被使用
- **删除 token 会即刻断供所有用它的订阅 URL**——删除前必须向用户点明后果
### 设置 `/api/settings`
- `surgeSections` 是 Surge 原文段落透传,改错直接毁掉 Surge 配置;动它之前先 GET 存档 + backup,改完拉 `/sub/<token>/surge` 全文核对
- 保留手写 Host 的精确 DNS 例外,不将全局 DNS 或全部端口 53 送到 AI;设置变化也须实际 HTTPS 订阅与官方 CLI 验证
- 共享订阅默认不启用控制接口,不使用公开模板口令;确需远程控制时使用设备独立凭据与访问限制。MITM 不关闭上游证书验证,启用效果另核对设备 CA 与开关
## 回滚
- 单条改错:用 API 反向改回。旧值可以从备份快照只读查出:远端 `sqlite3 data/backups/<file>.db 'SELECT …'`
- 整体回滚:`api.sh restore data/backups/<file>.db --confirm`——停服换库再拉起,订阅中断约 10–20 秒,只在用户明确要求时执行
## 边界
- 不改代码、不做部署(那是 deploy Skill 的事);不动 nginx/证书/`ADMIN_TOKEN`/systemd 单元
- 删除类操作与 reset:先核对并说明对象清单与影响;未明确授权时先确认,已有明确授权且对象一致时直接按流程执行
- 节点凭据是真凭据:汇报里不要粘贴密码/UUID 全文,用节点名指代