feat: 合并边缘节点运维并完善 Surge 配置安全
迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。 完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。 验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
---
|
||||
name: update-db
|
||||
description: 对 LA 生产机上的 proxy-station 数据做规范化变更——增删节点、导入分享链接、调整策略组与成员、规则、规则集引用、订阅 token、Surge 设置段落。凡是用户要动线上数据的(「线上加/删个节点」「把这个组改成 url-test」「调一下规则」「建个订阅 token」「删掉那个 token」「改下生产上的设置」),都用这个 Skill,即使没提到「数据库」三个字。它只改数据不碰代码:代码更新与部署归 deploy Skill。
|
||||
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 不是简单转发,直写会绕过它的全部防护:
|
||||
@@ -23,6 +25,7 @@ API 不是简单转发,直写会绕过它的全部防护:
|
||||
```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
|
||||
@@ -30,6 +33,7 @@ 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`,默认即生产值
|
||||
@@ -37,35 +41,40 @@ bash .claude/skills/update-db/scripts/api.sh health
|
||||
## 标准流程
|
||||
|
||||
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 范围):拉一次 `api GET /sub/<token>/clash` 确认 200;本地装了 mihomo 的话,`mihomo -t -f <文件>` 是最有说服力的端到端验证
|
||||
- 涉及节点/组/规则/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` 真正入库
|
||||
- 新节点来自分享链接:先 `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 全量字段向用户展示将消失的内容,确认后 DELETE
|
||||
- 真要删除:先 GET 全量字段核对对象,向用户展示名称及影响(不展示密码/UUID);删除范围已有明确授权时按流程 DELETE,否则先确认
|
||||
|
||||
### 策略组 `/api/groups`
|
||||
- `members` 是**整体替换**:GET 全量 → 改数组 → PUT 回去,别只发改动项
|
||||
- 成员三态:`{kind:"node", nodeId}`/`{kind:"group", refName}`/`{kind:"builtin", refName}`
|
||||
- `filterRegex` 与 `includeAllNodes` 可共存,**filterRegex 优先**(动态筛选用它,新增同地区节点自动进组)
|
||||
- `filterRegex` 与 `includeAllNodes` 可共存;Surge 保留显式节点/内置成员,全部成员组成为动态来源,正则只筛动态来源。新增同地区节点按名称进入组
|
||||
- 成员必须存在且类型正确,图无环;禁用组在 IR 中被剔除,不要新写指向停用组的规则
|
||||
- 改名后端会级联引用,但已导入客户端的旧组名会失配——改名前先向用户点明
|
||||
- url-test/fallback 组别忘了 `testUrl`/`interval`/`tolerance`
|
||||
- `testUrl` 用于 Clash/ShadowRocket;Surge 用 General 的 `proxy-test-url`,组级 `url=` 无效。fallback 不使用 tolerance,url-test 可显式设 0
|
||||
|
||||
### 规则 `/api/rules`
|
||||
- 顺序由 `PUT /api/rules/reorder` 的**全量 ids 数组**决定(GET 拿现有顺序 → 调整 → 全量提交);自定义规则置顶是既有约定
|
||||
- FINAL 规则永远保留;其策略组失效时 IR 层降级 DIRECT——兜底存在,但别依赖
|
||||
- 顺序由 `PUT /api/rules/reorder` 的**全量、无重复 ids 数组**决定,启用的唯一 FINAL 必须末尾。自定义创建置顶,但有明确顺序意图时按完整列表调整;业务例外在覆盖它的集合之前,特定 UDP 拦截在服务域名规则之前
|
||||
- FINAL 不可停用、删除、改类型或重复创建;失效策略由 IR 降级 DIRECT。不要借防御性兜底制造无效数据库状态
|
||||
- 新规则的 policy 必须已存在(内置 DIRECT/REJECT… 或现有组名),后端会校验,报错就先建组
|
||||
- `POST /api/rules/reset` 会重置回模板初版并**清掉全部自定义规则**——除非用户明说「重置」,否则永远不调用
|
||||
- `POST /api/rules/reset` 事务内恢复模板并**保留自定义规则**;不是定向更新生产规则的方法,除非用户明确要求重置,否则不调用
|
||||
- 靠前 IP 集是否加 `no-resolve` 按意图决定;主动解析可能绕过后续业务规则,no-resolve 又会改变对未解析域名的覆盖。URL-REGEX 值只检查 HTTP URL,不能拿 SMTP 文字当邮件协议匹配
|
||||
|
||||
### 规则集 `/api/rulesets`(只读)
|
||||
- `GET /api/rulesets` 看清单,`GET /:id/content` 看自托管转换结果
|
||||
@@ -78,6 +87,8 @@ bash .claude/skills/update-db/scripts/api.sh health
|
||||
|
||||
### 设置 `/api/settings`
|
||||
- `surgeSections` 是 Surge 原文段落透传,改错直接毁掉 Surge 配置;动它之前先 GET 存档 + backup,改完拉 `/sub/<token>/surge` 全文核对
|
||||
- 保留手写 Host 的精确 DNS 例外,不将全局 DNS 或全部端口 53 送到 AI;设置变化也须实际 HTTPS 订阅与官方 CLI 验证
|
||||
- 共享订阅默认不启用控制接口,不使用公开模板口令;确需远程控制时使用设备独立凭据与访问限制。MITM 不关闭上游证书验证,启用效果另核对设备 CA 与开关
|
||||
|
||||
## 回滚
|
||||
|
||||
@@ -87,5 +98,5 @@ bash .claude/skills/update-db/scripts/api.sh health
|
||||
## 边界
|
||||
|
||||
- 不改代码、不做部署(那是 deploy Skill 的事);不动 nginx/证书/`ADMIN_TOKEN`/systemd 单元
|
||||
- 删除类操作与 reset:先列出将删除/重置的完整清单,用户确认后再执行
|
||||
- 删除类操作与 reset:先核对并说明对象清单与影响;未明确授权时先确认,已有明确授权且对象一致时直接按流程执行
|
||||
- 节点凭据是真凭据:汇报里不要粘贴密码/UUID 全文,用节点名指代
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# 用法:
|
||||
# api.sh api METHOD PATH [JSON] # 调管理接口;PATH 以 /api/ 或 /sub/ 开头;JSON 作第三参
|
||||
# api.sh api METHOD PATH --body-file FILE # 含凭据或复杂 JSON 优先用权限 600 的文件,避免放入 argv
|
||||
# api.sh backup <context> # 变更前快照 → data/backups/before-<context>-<时间戳>.db
|
||||
# api.sh backups # 列出最近 20 个备份
|
||||
# api.sh counts # 各表行数,变更前后对比用
|
||||
@@ -33,12 +34,19 @@ cmd="$1"; shift
|
||||
case "$cmd" in
|
||||
api)
|
||||
[ $# -ge 2 ] || die "用法:api.sh api METHOD PATH [JSON]"
|
||||
METHOD="$1"; APIPATH="$2"; BODY="${3:-}"
|
||||
METHOD="$1"; APIPATH="$2"; BODY="${3:-}"; BODY_FILE=""
|
||||
if [ "$BODY" = "--body-file" ]; then
|
||||
BODY_FILE="${4:-}"; BODY=""
|
||||
[ -n "$BODY_FILE" ] && [ -f "$BODY_FILE" ] || die "--body-file 需要可读的 JSON 文件"
|
||||
fi
|
||||
case "$METHOD" in GET|POST|PUT|PATCH|DELETE) ;; *) die "METHOD 只能是 GET/POST/PUT/PATCH/DELETE";; esac
|
||||
case "$APIPATH" in /api/*|/sub/*) ;; *) die "PATH 必须以 /api/ 或 /sub/ 开头";; esac
|
||||
case "$APIPATH" in *[!a-zA-Z0-9_./?=%\&~:+-]*) die "PATH 含不安全字符,请先做 URL 编码";; esac
|
||||
# 单引号段内的 $ADMIN_TOKEN 与 ${PORT:-3000} 留给远端 shell 展开:token 不经本地 shell、不落本地磁盘
|
||||
RCMD='. '"$ENV_FILE"' 2>/dev/null; curl -sS -m 60 -X '"$METHOD"' -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" -w "\n__HTTP_STATUS:%{http_code}__" http://127.0.0.1:${PORT:-3000}'"$APIPATH"
|
||||
if [ -n "$BODY" ]; then
|
||||
RCMD='. '"$ENV_FILE"' 2>/dev/null; curl -sS -m 60 -X '"$METHOD"' -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" -w "\n__HTTP_STATUS:%{http_code}__" "http://127.0.0.1:${PORT:-3000}'"$APIPATH"'"'
|
||||
if [ -n "$BODY_FILE" ]; then
|
||||
"${SSH[@]}" "$RCMD --data-binary @-" < "$BODY_FILE"
|
||||
elif [ -n "$BODY" ]; then
|
||||
printf '%s' "$BODY" | "${SSH[@]}" "$RCMD --data-binary @-"
|
||||
else
|
||||
"${SSH[@]}" "$RCMD"
|
||||
|
||||
Reference in New Issue
Block a user