迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。 完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。 验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
90 lines
4.9 KiB
Bash
Executable File
90 lines
4.9 KiB
Bash
Executable File
#!/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
|