Files
token-list-share/deploy.sh
Charles7c 2419141284 接入百炼额度探针、重置卡管理与周期/自动刷新升级
- 百炼 Token Plan:探针(cookie 网关)每 5 分钟写快照,卡片展示 7 天窗口、订阅到期与重置卡
- 重置卡使用:reset-card-server 本机服务经 Admin Key+卡归属校验后代发 use,前端二次确认;入口默认 CSS 隐藏(localStorage bailian-reset-card=1 开启),探针本体只读不碰 use
- 时间周期:新增近 7 天/近 30 天预设,筛选行独立成一行,分组页统计随区间变化
- list/groups 每 5 分钟自动刷新,后台标签页暂停、回窗补拉
- deploy.sh 一键 web/cookie/probe/nginx/status/all;脱敏 env.example,补文档
2026-09-14 15:40:35 +08:00

308 lines
10 KiB
Bash
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
# 一键部署/运维脚本(在本机 Git Bash 里跑,依赖 pnpm、ssh、scp已配置免密登录
#
# 用法:
# bash deploy.sh # = web构建并发布前端
# bash deploy.sh web # pnpm build → 上传 dist → 切换站点目录(保留 data/)→ 同步 .env
# bash deploy.sh cookie # 交互式粘贴新的百炼 Cookie写入服务器并立即试跑探针
# bash deploy.sh cookie 'cna=…' # 直接以参数提供 Cookie会出现在 shell 历史,优先用交互方式)
# bash deploy.sh probe # 更新探针脚本与 systemd 单元,立即试跑
# bash deploy.sh nginx # 安装/刷新 nginx 站点配置并 reload
# bash deploy.sh status # 查看定时器、最新快照与 HTTP 自检
# bash deploy.sh all # nginx + web + probe不动 Cookie
#
# 可用环境变量覆盖默认值:
# DEPLOY_HOST=root@10.1.192.225 REMOTE_DIR=/usr/dcits/token-list-share SITE_PORT=18081
set -euo pipefail
REMOTE_HOST="${DEPLOY_HOST:-root@10.1.192.225}"
REMOTE_DIR="${REMOTE_DIR:-/usr/dcits/token-list-share}"
SITE_PORT="${SITE_PORT:-18081}"
SUB2API_UPSTREAM="${SUB2API_UPSTREAM:-http://127.0.0.1:18080}"
PROBE_ENV=/etc/bailian-probe.env
PROBE_DIR="$REMOTE_DIR/probe"
PROBE_BIN="$PROBE_DIR/bailian-probe.py"
RESET_CARD_BIN="$PROBE_DIR/reset-card-server.py"
RESET_CARD_PORT="${RESET_CARD_PORT:-18082}"
SNAPSHOT="$REMOTE_DIR/www/data/bailian.json"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
log() { printf '\033[1;36m▶ %s\033[0m\n' "$*"; }
ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
die() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; }
ssh_r() { ssh -o BatchMode=yes "$REMOTE_HOST" "$@"; }
scp_r() { scp -o BatchMode=yes "$@"; }
# ---------- web ----------
cmd_web() {
command -v pnpm >/dev/null || die "找不到 pnpm"
log "本地构建"
(cd "$SCRIPT_DIR" && pnpm build)
local tmp_tgz; tmp_tgz="$(mktemp -t token-dist-XXXXXX.tar.gz)"
tar -czf "$tmp_tgz" -C "$SCRIPT_DIR/dist" .
log "上传并发布到 $REMOTE_HOST:$REMOTE_DIR"
scp_r -q "$tmp_tgz" "$REMOTE_HOST:/tmp/token-list-dist.tar.gz"
# .env 仅作服务器侧备份key 已构建进 JS不入 Web 根目录
scp_r -q "$SCRIPT_DIR/.env" "$REMOTE_HOST:$REMOTE_DIR/.env.new"
ssh_r 'bash -s' \
"$REMOTE_DIR" "$SNAPSHOT" <<'REMOTE'
set -euo pipefail
DIR="$1"
mkdir -p "$DIR"
chmod 600 "$DIR/.env.new"
mv "$DIR/.env.new" "$DIR/.env"
mkdir -p "$DIR/www.new"
tar -xzf /tmp/token-list-dist.tar.gz -C "$DIR/www.new"
rm -f /tmp/token-list-dist.tar.gz
# 探针快照目录在站点根内,发版必须保留
if [ -d "$DIR/www/data" ]; then
mkdir -p "$DIR/www.new/data"
cp -a "$DIR/www/data/." "$DIR/www.new/data/"
fi
rm -rf "$DIR/www.old"
mv "$DIR/www" "$DIR/www.old"
mv "$DIR/www.new" "$DIR/www"
rm -rf "$DIR/www.old"
REMOTE
rm -f "$tmp_tgz"
ok "前端已发布data/ 快照目录已保留)"
}
# ---------- cookie ----------
read_cookie() {
# 显式传参(含空字符串,用于清除)优先;其次 BAILIAN_COOKIE 环境变量;最后交互输入
if [ "$#" -ge 2 ]; then
printf '%s' "$2"
return
fi
if [ -n "${BAILIAN_COOKIE:-}" ]; then
printf '%s' "$BAILIAN_COOKIE"
return
fi
printf '粘贴百炼控制台 Cookie一整行粘贴后回车输入留空则清除\n' >&2
local line
IFS= read -r line </dev/tty || true
printf '%s' "$line"
}
cmd_cookie() {
local cookie; cookie="$(read_cookie "$@")"
# Cookie 经临时文件透传,绝不出现在远程命令行/进程列表里
local tmp; tmp="$(mktemp -t bailian-cookie-XXXXXX)"
chmod 600 "$tmp"
printf '%s' "$cookie" >"$tmp"
scp_r -q "$tmp" "$REMOTE_HOST:/run/.bailian-cookie.in"
rm -f "$tmp"
log "更新 $PROBE_ENV 并试跑探针"
ssh_r 'bash -s' "$PROBE_ENV" "$SNAPSHOT" <<'REMOTE'
set -euo pipefail
ENV_FILE="$1"; SNAPSHOT="$2"
ENV_FILE="$ENV_FILE" python3 - <<'PY'
import os, pathlib
p = pathlib.Path(os.environ["ENV_FILE"])
p.parent.mkdir(parents=True, exist_ok=True)
cookie = pathlib.Path("/run/.bailian-cookie.in").read_text(encoding="utf-8").strip()
lines = p.read_text().splitlines() if p.exists() else []
out, found = [], False
for ln in lines:
if ln.startswith("BAILIAN_COOKIE="):
out.append("BAILIAN_COOKIE=" + cookie); found = True
else:
out.append(ln)
if not found:
out.append("BAILIAN_COOKIE=" + cookie)
p.write_text("\n".join(out) + "\n", encoding="utf-8")
PY
chmod 600 "$ENV_FILE"
rm -f /run/.bailian-cookie.in
systemctl reset-failed bailian-probe.service >/dev/null 2>&1 || true
# 凭证失效时 oneshot 退出码为 1快照仍会写出属预期
systemctl start bailian-probe.service >/dev/null 2>&1 || true
SNAPSHOT="$SNAPSHOT" python3 - <<'PY'
import json, os, pathlib
snap = pathlib.Path(os.environ["SNAPSHOT"])
s = json.loads(snap.read_text(encoding="utf-8"))
if s.get("ok"):
print("✓ 探针成功:%s 个用量窗口probed_at=%s" % (len(s.get("windows", [])), s.get("probed_at")))
else:
print("✗ 探针未取到数据(%s%s" % (s.get("error_kind"), s.get("last_error")))
PY
REMOTE
}
# ---------- probe ----------
cmd_probe() {
log "上传探针脚本与 systemd 单元"
scp_r -q "$SCRIPT_DIR/deploy/bailian-probe.py" "$REMOTE_HOST:/tmp/bailian-probe.py"
scp_r -q "$SCRIPT_DIR/deploy/reset-card-server.py" "$REMOTE_HOST:/tmp/reset-card-server.py"
scp_r -q "$SCRIPT_DIR/deploy/bailian-probe.timer" "$REMOTE_HOST:/tmp/bailian-probe.timer"
ssh_r 'bash -s' "$PROBE_ENV" "$SNAPSHOT" "$PROBE_DIR" "$RESET_CARD_PORT" "$REMOTE_DIR" <<'REMOTE'
set -euo pipefail
ENV_FILE="$1"; SNAPSHOT="$2"; PROBE_DIR="$3"; CARD_PORT="$4"; APP_DIR="$5"
mkdir -p "$PROBE_DIR" "$(dirname "$SNAPSHOT")"
install -m 755 /tmp/bailian-probe.py "$PROBE_DIR/bailian-probe.py"
install -m 755 /tmp/reset-card-server.py "$PROBE_DIR/reset-card-server.py"
install -m 644 /tmp/bailian-probe.timer /etc/systemd/system/bailian-probe.timer
# 用卡助手:常驻本地服务(只绑 127.0.0.1),用卡前后由 nginx /bailian-admin/ 鉴权转发
cat > /etc/systemd/system/reset-card-server.service <<EOF
[Unit]
Description=百炼重置卡使用助手(本地服务,只读用卡,含 Admin Key 校验)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=$ENV_FILE
Environment=BAILIAN_PROBE_PATH=$PROBE_DIR/bailian-probe.py
Environment=APP_ENV_PATH=$APP_DIR/.env
Environment=RESET_CARD_BIND=127.0.0.1
Environment=RESET_CARD_PORT=$CARD_PORT
ExecStart=/usr/bin/python3 $PROBE_DIR/reset-card-server.py
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
# service 的 ExecStart 按实际安装路径渲染(仓库模板里写的是 /opt 路径)
cat > /etc/systemd/system/bailian-probe.service <<EOF
[Unit]
Description=阿里云百炼 Token Plan 额度探针
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=$ENV_FILE
ExecStart=/usr/bin/python3 $PROBE_DIR/bailian-probe.py
EOF
rm -f /tmp/bailian-probe.py /tmp/bailian-probe.timer
# 旧版装在 /opt迁移后清掉
rm -rf /opt/bailian-probe
if [ ! -f "$ENV_FILE" ]; then
cat > "$ENV_FILE" <<EOF
BAILIAN_MODE=cookie
BAILIAN_VARIANT=cn-personal
BAILIAN_COOKIE=
BAILIAN_API_KEY=
BAILIAN_BASE_URL=https://token-plan.cn-beijing.maas.aliyuncs.com
BAILIAN_OUT=$SNAPSHOT
EOF
chmod 600 "$ENV_FILE"
fi
systemctl daemon-reload
systemctl enable --now bailian-probe.timer >/dev/null
systemctl enable --now reset-card-server.service >/dev/null 2>&1 || true
systemctl reset-failed bailian-probe.service >/dev/null 2>&1 || true
systemctl start bailian-probe.service >/dev/null 2>&1 || true
SNAPSHOT="$SNAPSHOT" python3 - <<'PY'
import json, os, pathlib
s = json.loads(pathlib.Path(os.environ["SNAPSHOT"]).read_text(encoding="utf-8"))
print("快照ok=%s stale=%s windows=%d err=%s" %
(s.get("ok"), s.get("stale"), len(s.get("windows", [])), s.get("last_error")))
PY
REMOTE
ok "探针已更新,定时器每 5 分钟跑一次"
}
# ---------- nginx ----------
cmd_nginx() {
log "安装 nginx 站点(端口 $SITE_PORT,反代 $SUB2API_UPSTREAM"
local tmp; tmp="$(mktemp -t token-nginx-XXXXXX.conf)"
cat >"$tmp" <<EOF
server {
listen $SITE_PORT;
listen [::]:$SITE_PORT;
server_name _;
location /api/ {
proxy_pass $SUB2API_UPSTREAM;
proxy_http_version 1.1;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
# 百炼重置卡用卡助手:只转本地 reset-card-server其内部再校验 x-api-key
location /bailian-admin/ {
proxy_pass http://127.0.0.1:$RESET_CARD_PORT/;
proxy_http_version 1.1;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 90s;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
location / {
root $REMOTE_DIR/www;
try_files \$uri \$uri/ /index.html;
}
}
EOF
scp_r -q "$tmp" "$REMOTE_HOST:/etc/nginx/sites-available/token-list"
ssh_r 'bash -s' <<'REMOTE'
set -euo pipefail
ln -sf /etc/nginx/sites-available/token-list /etc/nginx/sites-enabled/token-list
nginx -t
systemctl reload nginx
REMOTE
ok "nginx 配置已生效"
}
# ---------- status ----------
cmd_status() {
ssh_r 'bash -s' "$SITE_PORT" "$SNAPSHOT" <<'REMOTE'
set -euo pipefail
PORT="$1"; SNAPSHOT="$2"
echo "== timer =="
systemctl list-timers bailian-probe.timer --no-pager | head -3
echo "== snapshot =="
if [ -f "$SNAPSHOT" ]; then
python3 -c '
import json, sys
s = json.load(open(sys.argv[1], encoding="utf-8"))
print("ok=%s stale=%s windows=%d probed_at=%s" % (s.get("ok"), s.get("stale"), len(s.get("windows", [])), s.get("probed_at")))
if s.get("last_error"): print("last_error:", s.get("last_error"))
' "$SNAPSHOT"
else
echo "(快照不存在)"
fi
echo "== http =="
for path in / /list.html /groups.html /data/bailian.json /api/v1/admin/groups/all; do
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT$path")
printf ' %s %s\n' "$code" "$path"
done
REMOTE
}
# ---------- main ----------
case "${1:-web}" in
web) cmd_web ;;
cookie) cmd_cookie "$@" ;;
probe) cmd_probe ;;
nginx) cmd_nginx ;;
status) cmd_status ;;
all) cmd_nginx; cmd_web; cmd_probe ;;
*) die "未知子命令:$1(支持 web | cookie | probe | nginx | status | all" ;;
esac