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

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

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

90 lines
4.9 KiB
Bash
Executable File
Raw 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.

#!/usr/bin/env bash
# update-db 的唯一执行通道:对生产库的一切操作经它走 ssh(在远端本机回环上调 API / 做快照)。
#
# 用法:
# 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 # 各表行数,变更前后对比用
# api.sh health # systemd 状态 + /api/health
# api.sh restore <file> --confirm # 整体回滚:停服换库再拉起(仅在用户明确要求时执行)
#
# 可用环境变量覆盖:DEPLOY_HOST(默认 la)、DEPLOY_SERVICE(默认 proxy-station)、
# PS_DATA_DIR(默认 /opt/proxy-station/data)、PS_ENV_FILE(默认 /etc/proxy-station/env)
set -euo pipefail
# 会话注入的 HTTP 代理只供模型 API 使用,ssh 一律直连(同 deploy.sh)
unset HTTPS_PROXY HTTP_PROXY https_proxy http_proxy ALL_PROXY all_proxy 2>/dev/null || true
HOST="${DEPLOY_HOST:-la}"
SERVICE="${DEPLOY_SERVICE:-proxy-station}"
DATA_DIR="${PS_DATA_DIR:-/opt/proxy-station/data}"
DB_FILE="$DATA_DIR/proxy-station.db"
BACKUP_DIR="$DATA_DIR/backups"
ENV_FILE="${PS_ENV_FILE:-/etc/proxy-station/env}"
SSH=(ssh -o BatchMode=yes -o ConnectTimeout=15 "$HOST")
die() { echo "错误:$*" >&2; exit 1; }
[ $# -ge 1 ] || die "缺少子命令(api/backup/backups/counts/health/restore)"
cmd="$1"; shift
case "$cmd" in
api)
[ $# -ge 2 ] || die "用法:api.sh api METHOD PATH [JSON]"
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_FILE" ]; then
"${SSH[@]}" "$RCMD --data-binary @-" < "$BODY_FILE"
elif [ -n "$BODY" ]; then
printf '%s' "$BODY" | "${SSH[@]}" "$RCMD --data-binary @-"
else
"${SSH[@]}" "$RCMD"
fi
;;
backup)
CTX="${1:-}"; [ -n "$CTX" ] || die "用法:api.sh backup <context>(小写字母/数字/连字符,如 add-jp-hy2)"
case "$CTX" in *[!a-z0-9-]*) die "context 只允许小写字母、数字与连字符";; esac
# .backup 是 SQLite 在线备份,WAL 下安全,产出独立完整快照(无需 wal/shm)
"${SSH[@]}" 'set -e; ts=$(date +%Y%m%d-%H%M%S); f='"$BACKUP_DIR"'/before-'"$CTX"'-$ts.db; sqlite3 '"$DB_FILE"' ".backup $f"; if [ -s "$f" ]; then ls -la "$f"; else echo "备份为空" >&2; exit 1; fi'
;;
backups)
"${SSH[@]}" "ls -la '$BACKUP_DIR' | tail -20"
;;
counts)
# 表清单来自 BOOTSTRAP_SQL;新增表时应同步更新这里
SQL="SELECT 'nodes='||COUNT(*) FROM nodes"
for t in policy_groups group_members rules rulesets ruleset_cache sub_tokens settings; do
SQL="$SQL UNION ALL SELECT '$t='||COUNT(*) FROM $t"
done
"${SSH[@]}" "sqlite3 '$DB_FILE' \"$SQL\""
;;
health)
"${SSH[@]}" "systemctl is-active $SERVICE; curl -sS -m 5 http://127.0.0.1:3000/api/health; echo"
;;
restore)
FILE="${1:-}"; [ -n "$FILE" ] || die "用法:api.sh restore <备份文件> --confirm"
[ "${2:-}" = "--confirm" ] || die "restore 会停服换库(订阅中断约 10–20 秒),必须由用户明确要求后追加 --confirm 执行"
case "$FILE" in "$BACKUP_DIR"/*.db) ;; *) die "备份文件必须是 $BACKUP_DIR 下的 .db 快照";; esac
"${SSH[@]}" 'set -e; f='"$FILE"'; [ -f "$f" ] || { echo "备份不存在:$f" >&2; exit 1; }
systemctl stop '"$SERVICE"'
cp "$f" '"$DB_FILE"'.new && mv '"$DB_FILE"'.new '"$DB_FILE"'
rm -f '"$DB_FILE"'-wal '"$DB_FILE"'-shm
chown '"$SERVICE"':'"$SERVICE"' '"$DB_FILE"'
systemctl start '"$SERVICE"'
for i in $(seq 1 20); do curl -sf -m 2 http://127.0.0.1:3000/api/health >/dev/null && break; sleep 1; done
systemctl is-active '"$SERVICE"' && curl -s http://127.0.0.1:3000/api/health && echo && echo "已回滚自 $(basename "$f")"'
;;
*) die "未知子命令:$cmd(可用:api/backup/backups/counts/health/restore)" ;;
esac