diff --git a/AGENTS.md b/AGENTS.md index bce7090..a986d79 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ pnpm build # vue-tsc 类型检查 + vite build,产出 dist/ - 界面文案统一 `Token`(首字母大写),表头样式禁用 `text-transform: uppercase`。 - 页面名分保持中立(「用量排行」「用量榜」),不加评价性徽章/高亮等引导元素。 - 面向用户的品牌名是「行业应用」(页眉 eyebrow);代码注释里指代后端系统仍可写 Sub2API,不要全局替换。 +- 线上整站有 nginx HTTP Basic Auth(含 `/api/`、`/data/`、`/bailian-admin/`),账号密码只在服务器 htpasswd(apr1),不进 git;对线上做 curl/探测要么预期 401,要么带 `-u`。开关与换人用 `deploy.sh auth`,不要手改 nginx 配置里的 auth_basic。 - 颜色走 `src/style.css` 的 CSS 变量:成本 `--accent` 橙、Token `--token` 钢蓝、金 `--gold`;图表内因 ECharts 读不到 CSS 变量而硬编码同值,改色时两处同步。 - 百炼 Token Plan 额度:数据来自服务器探针写出的静态快照 `/data/bailian.json`(契约见 `types.ts` 的 `BailianSnapshot`,`schema: 1`,含只读的 `reset_cards` 重置卡列表)。**Cookie/Key 只留在服务器 `/etc/bailian-probe.env`,绝不进前端产物、快照文件或 git;探针(bailian-probe.py)只调 reset-card/list 等只读接口,绝不接 reset-card/use**。用卡写操作只有 `deploy/reset-card-server.py`(本机 127.0.0.1:18082,nginx `/bailian-admin/`)在 Admin Key + 卡归属校验后才允许代发;分组页入口默认 CSS 隐藏,需 `localStorage.bailian-reset-card='1'` 开启。Sub2API 的智谱额度通道(账号 `extra.zhipu_*`)复用不到百炼:平台是 openai/anthropic、`account_mode` 为空、域名不在识别表内,别指望接进 `cn_provider_quota_service`。 @@ -28,4 +29,4 @@ pnpm build # vue-tsc 类型检查 + vite build,产出 dist/ - `src/App.vue` 排行主页;`src/groups/GroupsApp.vue` 分组页(分组配 `src/groups.config.ts`,含百炼额度卡)。 - `src/api.ts` 数据层(Sub2API 接口 + 百炼快照读取,快照失败返回 null 不阻断页面);`src/format.ts` 格式化与时区(Asia/Shanghai)。 - `deploy/bailian-probe.py` 百炼额度探针(Python 3 标准库,cookie 主路 / apikey 验证路);部署见 `deploy/DEPLOY.md` 第 5 节。 -- 生产部署为同域反代,见 `deploy/DEPLOY.md`;现行环境(10.1.192.225:18081 → `/usr/dcits/token-list-share`)的发布/换 Cookie 用根目录 `deploy.sh`(web / cookie / probe / nginx / status / all)。 +- 生产部署为同域反代,见 `deploy/DEPLOY.md`;现行环境(10.1.192.225:18081 → `/usr/dcits/token-list-share`)的发布/换 Cookie 用根目录 `deploy.sh`(web / cookie / probe / nginx / auth / status / all)。 diff --git a/deploy.sh b/deploy.sh index 6394f24..b0054e8 100644 --- a/deploy.sh +++ b/deploy.sh @@ -8,6 +8,9 @@ # bash deploy.sh cookie 'cna=…' # 直接以参数提供 Cookie(会出现在 shell 历史,优先用交互方式) # bash deploy.sh probe # 更新探针脚本与 systemd 单元,立即试跑 # bash deploy.sh nginx # 安装/刷新 nginx 站点配置并 reload +# bash deploy.sh auth # 交互式设置 Basic Auth 用户名/密码(不回显)并开启整站认证 +# bash deploy.sh auth user pwd # 非交互设置(密码会进 shell 历史,优先用交互方式) +# bash deploy.sh auth off # 关闭 Basic Auth(删除 htpasswd 并 reload) # bash deploy.sh status # 查看定时器、最新快照与 HTTP 自检 # bash deploy.sh all # nginx + web + probe(不动 Cookie) # @@ -27,6 +30,7 @@ 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" +HTPASSWD=/etc/nginx/token-list.htpasswd SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -216,17 +220,87 @@ REMOTE ok "探针已更新,定时器每 5 分钟跑一次" } +# ---------- auth ---------- + +# 在远端用 openssl apr1 生成 htpasswd 行(Debian/Ubumtu 的 openssl 带 -apr1) +cmd_auth() { + if [ "${2:-}" = "off" ]; then + log "关闭 Basic Auth" + ssh_r "rm -f '$HTPASSWD'" + cmd_nginx + ok "Basic Auth 已关闭" + return + fi + + local user password + if [ "$#" -ge 3 ]; then + user="$2"; password="$3" + else + printf 'Basic Auth 用户名:' >&2 + IFS= read -r user &2 + IFS= read -r -s password &2 + fi + [ -n "$user" ] || die "用户名不能为空" + [ -n "$password" ] || die "密码不能为空" + + # 密码经 600 临时文件透传,不出现在远端命令行/进程列表 + local tmp; tmp="$(mktemp -t token-htpasswd-XXXXXX)" + chmod 600 "$tmp" + printf '%s' "$password" >"$tmp" + scp_r -q "$tmp" "$REMOTE_HOST:/run/.token-htpasswd.in" + rm -f "$tmp" + + log "写入 $HTPASSWD 并刷新 nginx" + # stdout 输出除本次用户外仍保留的账号,用于提示旧账号仍可登录 + local others + others="$(ssh_r 'bash -s' "$HTPASSWD" "$user" <<'REMOTE' +set -euo pipefail +HTPASSWD="$1"; USER_NAME="$2" +command -v openssl >/dev/null || { echo "✗ 远端没有 openssl,无法生成 apr1 哈希" >&2; exit 1; } +[ -s /run/.token-htpasswd.in ] || { echo "✗ 密码临时文件缺失" >&2; exit 1; } +hash=$(openssl passwd -apr1 -in /run/.token-htpasswd.in) +rm -f /run/.token-htpasswd.in +mkdir -p "$(dirname "$HTPASSWD")" +# 同名用户覆盖更新,其余用户保留 +if [ -f "$HTPASSWD" ]; then + grep -v "^${USER_NAME}:" "$HTPASSWD" > "$HTPASSWD.new" || true +else + : > "$HTPASSWD.new" +fi +printf '%s:%s\n' "$USER_NAME" "$hash" >> "$HTPASSWD.new" +install -m 640 "$HTPASSWD.new" "$HTPASSWD" +rm -f "$HTPASSWD.new" +chown root:www-data "$HTPASSWD" 2>/dev/null || chown root:nginx "$HTPASSWD" 2>/dev/null || true +cut -d: -f1 "$HTPASSWD" | grep -v "^${USER_NAME}$" || true +REMOTE +)" + cmd_nginx >/dev/null + ok "Basic Auth 已开启:用户 $user(浏览器访问整站时输入)" + if [ -n "$others" ]; then + printf '\033[1;33m! htpasswd 里还有其他账号仍可登录:%s\n' "$(printf '%s' "$others" | tr '\n' ' ')" >&2 + printf ' 如要停用旧账号:ssh %s "sed -i \x27/^<用户名>:/d\x27 %s"\033[0m\n' "$REMOTE_HOST" "$HTPASSWD" >&2 + fi +} + # ---------- nginx ---------- cmd_nginx() { log "安装 nginx 站点(端口 $SITE_PORT,反代 $SUB2API_UPSTREAM)" + # 先看远端 htpasswd 是否存在:存在就在整站 server 块开 Basic Auth + local auth_block="" + if ssh_r "test -s '$HTPASSWD'"; then + auth_block="$(printf ' auth_basic "token-list-share";\n auth_basic_user_file %s;\n' "$HTPASSWD")" + log "检测到 $HTPASSWD,开启 Basic Auth" + fi local tmp; tmp="$(mktemp -t token-nginx-XXXXXX.conf)" cat >"$tmp" <