fix(deploy): update.sh 加自更新重跑守卫,修"改了源仍报旧错"

脚本 git pull 会改自己:变量默认值在 pull 前已求值、bash 又按字节偏移边读边跑,
首次拉到"改 update.sh"的提交那一轮跑的仍是旧脚本(默认源还是阿里 -> litellm 报缺版本)。
pull 后检出本脚本有变更即 exec 新版本从头重跑(ORIG_ARGS 原样回传,ZCBOT_UPDATE_REEXEC 防死循环)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-06-01 16:04:25 +08:00
parent 4eacfe1bd9
commit 74580e951e
3 changed files with 16 additions and 2 deletions

View File

@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`
最后更新:2026-06-01(update.sh 默认源改腾讯/避开阿里滞后卡 litellm;build 跳过改 --skip-build;pip 不静默 + build --progress=plain 进度可见)
最后更新:2026-06-01(update.sh 加自更新重跑守卫修"改源仍报旧错";默认源改腾讯/避开阿里滞后卡 litellm;build 跳过改 --skip-build;进度可见)
---
@ -23,6 +23,7 @@
### 2026-06-01
- **`deploy/update.sh` 加自更新重跑守卫(修"改了源仍报旧错"根因)**:脚本 `git pull` 会改自己 —— 变量默认值在 pull 前已求值、bash 又按字节偏移边读边跑,所以**首次拉到"改 update.sh"的提交那一轮,跑的还是旧脚本的过期行为**(默认源还是阿里 → litellm 仍报缺版本)。改法:pull 后 `git diff --quiet OLD NEW -- deploy/update.sh` 检出本脚本有变更,就 `exec env ZCBOT_UPDATE_REEXEC=1 bash $0 "${ORIG_ARGS[@]}"` 用新版本从头重跑(原始参数原样回传,标记防死循环;pull 幂等,重跑里 pull 变 no-op)。**纯运维脚本,DESIGN 不动**;`RUN.md` §部署 SOP 加一条要点。
- **`deploy/update.sh` 默认源改腾讯 + build 跳过改 `--skip-build` + 进度可见**:部署 build 报 `Could not find a version that satisfies the requirement litellm>=1.83.0`。**根因 = 阿里 PyPI 镜像同步滞后(只到 litellm 1.82.6),而 `requirements.txt``>=1.83.0`(zai/GLM provider 要)**;腾讯 / 清华源已到 1.88。三处改:① 默认镜像源(APT/PIP/NPM)由阿里改腾讯(`mirrors.cloud.tencent.com`),并修 step 2 host venv pip —— 经 `sudo -u``PIP_INDEX_URL` env 被洗掉,改为脚本显式拼 `--index-url`,不再靠 host pip.conf(否则 host 仍撞阿里缺版本);② 跳过 sandbox build 从 env `ZCBOT_SKIP_SANDBOX_BUILD=1` 改为 CLI flag `--skip-build`(开发期不留 env 别名兼容),顶部 `while/case` 解析参数;③ 进度可见 —— step 2 pip 去掉 `-q`、step 4 `docker build --progress=plain`(默认 BuildKit TTY UI 多层并行折叠刷新,看着像非串行且管道下缓冲),部署时能逐行看实时输出。**纯运维脚本,DESIGN 不动**;`RUN.md` §部署 SOP 三条要点改写 + 故障表加 litellm 版本找不到一行 + 最后更新日期刷新。
- **修 MP host 工具的全量下载(IP 被封根因)**:`mp_search_summary` 之前不给 `summary.search` 传分页参数 → mp-api 默认 `chunk_size=1000``list(docs)` 自动翻完所有页,`limit` 只在客户端切片,等于每次搜索都整库级下载 → MP 判 abusive traffic 封 host IP/ASN(403 "blocked")。改为 `search(num_chunks=1, chunk_size=limit, ...)`,服务端单页限量。`mp_get_entries` 的 `limit` 同样是"只裁剪保存、不减网络流量"的假参数,但 `get_entries_in_chemsys` 天然全量(相图用途),改不了,只在 description 里点明"拉整个 chemsys、元素越多越重、别反复调"。测试加断言锁定 `num_chunks/chunk_size` 已传。**注:宿主 IP `49.232.14.174` 当前仍被 MP 临时封(无公开时限、不确认自动解除),需发邮件 support@materialsproject.org 人工解封后才能联网复测。**
- **加一键部署脚本 `deploy/update.sh`(Ubuntu / systemd)**:把日常部署固化成一把梭 —— `git pull --ff-only``pip install -r``db upgrade head``docker build` sandbox 镜像 → `systemctl restart zcbot``curl /healthz` 验活。**两处必须钉死的顺序 / 步骤**:① migration 不能漏(`db/migrations/env.py` 直读 `os.environ['ZCBOT_DB_URL']` 不读 .env,脚本从 .env 抠出来 `env ZCBOT_DB_URL=...` 喂进去);② **build 必须在 restart 之前** —— sandbox 容器 per-user 长驻复用、`tools/` 是 build 进镜像(非 mount),restart 时 `pool.shutdown_all` 清旧容器、下次 `ensure()` 才用新 `zcbot-sandbox:latest` 重建,顺序反了新 tools/ 要等下次重启才生效。**sandbox 每次都 build 无所谓**:重活(pip ~1G / chromium / 字体 / mermaid)都在 Dockerfile `COPY tools/` 之上,layer cache 让改代码部署秒过、只有 `requirements.txt` 变了才整体重建(~5-10min)。镜像源默认阿里(`${VAR-default}` 不带冒号,显式置空可回落官方源)。前置守卫:非 root / 非 git 仓库 / 工作区脏(已跟踪文件)/ 缺 .env 中止;healthz 15s 不 ok → dump journalctl 非零退出。`ZCBOT_SKIP_SANDBOX_BUILD=1` 跳过 build(host backend 机)。一次性 bootstrap(useradd / 写 unit / enable)不进脚本,留 RUN.md。git 可执行位已置(100755)。**纯运维脚本,DESIGN 不动**;`RUN.md` §部署 SOP 重写为指向脚本 + 手动逐条 fallback。

3
RUN.md
View File

@ -2,7 +2,7 @@
> 怎么把 zcbot 跑起来。env / 常用命令 / 故障兜底。设计看 `DESIGN.md`,进度看 `PROGRESS.md`
最后更新:2026-06-01(update.sh 默认源改腾讯/阿里滞后卡 litellm;build 跳过改 --skip-build;pip 不静默 + build --progress=plain)
最后更新:2026-06-01(update.sh 加自更新重跑守卫;默认源改腾讯/阿里滞后卡 litellm;build 跳过改 --skip-build;pip 不静默 + build --progress=plain)
---
@ -265,6 +265,7 @@ sudo bash /opt/zcbot/deploy/update.sh
- **sandbox build 每次都跑没关系**:layer cache 让重活(pip ~1G / chromium / 字体 / mermaid,都在 `COPY tools/` 之上)在改代码部署时秒过;只有 `requirements.txt` 变了才整体重建(~5-10min,正好也是该重建的时候)。host backend 机器 / 临时不想动 docker:`sudo bash deploy/update.sh --skip-build`。
- **镜像源默认腾讯**(`APT_MIRROR=mirrors.cloud.tencent.com` / `PIP_INDEX_URL=mirrors.cloud.tencent.com/pypi/simple/` / `NPM_REGISTRY=mirrors.cloud.tencent.com/npm/`,境内快且新包同步及时)。**阿里 PyPI 镜像同步滞后**(litellm 一度只到 1.82.6,卡死 `requirements.txt``>=1.83.0`),故默认避开阿里。要命中 docker cache 就别两组源来回换(换源从 pip 层炸开全重跑)。想用官方源:`PIP_INDEX_URL= sudo -E bash deploy/update.sh`(置空即回落 Dockerfile 官方默认)。host venv 的 step 2 pip 也吃这个源(脚本显式 `--index-url`,不靠 host pip.conf)。
- **进度可见**:step 2 pip 不带 `-q`、step 4 `docker build --progress=plain`,部署时能逐行看到装包 / 各 build 层的实时输出(默认 BuildKit TTY UI 会折叠刷新、看着像非串行)。
- **脚本会自更新重跑**:`git pull` 若动了 `deploy/update.sh` 本身,脚本会 `exec` 新版本从头重跑(旧脚本的变量默认值在 pull 前就求值了、bash 又按字节偏移边读边跑,不重跑会跑出过期行为 —— 这是首次拉到改 update.sh 的提交时"改了源还报旧错"的根因)。日志出现「update.sh 自身有更新 —— 用新版本重跑」即正常;`ZCBOT_UPDATE_REEXEC=1` 防死循环。
- **migration 取 DB URL**:`db/migrations/env.py` 直接读 `os.environ['ZCBOT_DB_URL']`(不读 .env),脚本从 `.env` 抠出来显式 `env ZCBOT_DB_URL=... ` 喂进 `main.py db upgrade`
- **前置守卫**:非 root / 不是 git 仓库 / 工作区脏(已跟踪文件有未提交改动)/ 缺 .env → 直接中止。`/healthz` 15s 内不返回 `ok` → dump `journalctl -n 50` 并以非零退出。

View File

@ -26,6 +26,8 @@ set -euo pipefail
# ── 参数 ──────────────────────────────────────────────────────
# 默认 build;--skip-build 显式跳过(不为旧用法留 env 别名,开发期不留兼容)。
# 原始参数先存一份 —— pull 后若脚本自身有更新要 exec 新版本重跑,得把参数原样传回。
ORIG_ARGS=("$@")
SKIP_BUILD=0
while [ $# -gt 0 ]; do
case "$1" in
@ -85,6 +87,16 @@ log "git pull --ff-only ..."
asuser git -C "$APP_DIR" pull --ff-only
NEW_HEAD="$(asuser git -C "$APP_DIR" rev-parse HEAD)"
# 脚本改自己的坑:正在跑的是 pull 前的旧脚本(变量默认值早在顶部求值,bash 又按字节
# 偏移边读边跑),pull 进来的新 update.sh 这一轮不生效。若这次 pull 动了本脚本,exec
# 新版本从头重跑(原始参数原样传回),ZCBOT_UPDATE_REEXEC 标记防死循环。pull 幂等,
# 重跑里它变 no-op,只多一次 git 往返,代价可忽略。
if [ "$OLD_HEAD" != "$NEW_HEAD" ] && [ "${ZCBOT_UPDATE_REEXEC:-0}" != "1" ] \
&& ! asuser git -C "$APP_DIR" diff --quiet "$OLD_HEAD" "$NEW_HEAD" -- deploy/update.sh; then
log "update.sh 自身有更新 —— 用新版本重跑(避免旧脚本跑出过期行为)..."
exec env ZCBOT_UPDATE_REEXEC=1 bash "$APP_DIR/deploy/update.sh" "${ORIG_ARGS[@]}"
fi
if [ "$OLD_HEAD" = "$NEW_HEAD" ]; then
log "代码无更新($NEW_HEAD),仍继续 pip/migration/build 兜底幂等"
else