From 4eacfe1bd90de83f17250fef6537322c02da3201 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 1 Jun 2026 15:54:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20update.sh=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=BA=90=E6=94=B9=E8=85=BE=E8=AE=AF=E9=81=BF=E5=BC=80=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E6=BB=9E=E5=90=8E(=E5=8D=A1=20litellm>=3D1.83);build?= =?UTF-8?q?=20=E8=B7=B3=E8=BF=87=E6=94=B9=20--skip-build;=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E5=8F=AF=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 阿里 PyPI 镜像同步滞后(只到 litellm 1.82.6),撞 requirements 的 >=1.83.0。 - 默认 APT/PIP/NPM 源阿里 -> 腾讯(已到 1.88);step 2 host pip 显式 --index-url(sudo -u 会洗掉 PIP_INDEX_URL env) - ZCBOT_SKIP_SANDBOX_BUILD=1 env -> --skip-build CLI flag(开发期不留兼容) - step 2 pip 去 -q + step 4 docker build --progress=plain,部署逐行流式可见 Co-Authored-By: Claude Opus 4.8 (1M context) --- PROGRESS.md | 3 ++- RUN.md | 8 +++++--- deploy/update.sh | 44 +++++++++++++++++++++++++++++++++----------- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index 565f405..2ab7505 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-06-01(加一键部署脚本 deploy/update.sh:pull/pip/migration/build/restart/healthz,build 必先于 restart) +最后更新:2026-06-01(update.sh 默认源改腾讯/避开阿里滞后卡 litellm;build 跳过改 --skip-build;pip 不静默 + build --progress=plain 进度可见) --- @@ -23,6 +23,7 @@ ### 2026-06-01 +- **`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。 - **sandbox 镜像加中文字体,修 matplotlib / mermaid 出图中文方块**:用户报绘图(mermaid + matplotlib)出的 PNG 里中文全是豆腐块 □。**根因 = `deploy/sandbox/Dockerfile` 从 `python:3.12-slim` 起一个 CJK 字体都没装**:matplotlib `skills/plot_pub/style.py::_find_chinese_font()` 扫候选无果退回 Arial/DejaVu;mermaid 经 mmdc→chromium 渲染,chromium 经 fontconfig 也找不到中文字形;`skills/ppt/scripts/render_icon.py` 引用的 `wqy-microhei.ttc` / `NotoSansCJK-Regular.ttc` 路径根本不存在。三处同一病根。**改法**:① Dockerfile chromium 块后加一层 `apt-get install fonts-noto-cjk fonts-wqy-microhei fontconfig && fc-cache -f`(Noto 出版级 +~330MB,wqy 兜底 +~5MB 且匹配 style.py 现有候选 / render_icon 引用路径);② `style.py` 候选清单首位加 `"Noto Sans CJK SC"` 让 matplotlib 优先用 Noto。fontconfig 刷缓存供 chromium 选字;matplotlib 走自家 font_manager 扫 `/usr/share/fonts` 运行时首用自动建缓存,无需额外处理。**否决**仅装 wqy(体积小但黑体不如 Noto 精致,出版图略糙)/ 仅装 Noto(render_icon 第一候选 wqy.ttc 落空走 fallback OK 但不够干净)。**纯镜像 + 配置改,DESIGN 不动**(无架构 / 取舍 / schema 变化);`RUN.md` 故障表加一行(中文方块 → 重 build 镜像 + 清旧容器 + `fc-list :lang=zh` 验证)。**生效**:改了 Dockerfile 必须 `docker build` 重建 + `docker rm -f` 清旧容器 + restart web,旧容器仍跑老镜像不会自动更新。 diff --git a/RUN.md b/RUN.md index 76a377c..ef7c53b 100644 --- a/RUN.md +++ b/RUN.md @@ -2,7 +2,7 @@ > 怎么把 zcbot 跑起来。env / 常用命令 / 故障兜底。设计看 `DESIGN.md`,进度看 `PROGRESS.md`。 -最后更新:2026-06-01(加一键部署脚本 deploy/update.sh;§部署 SOP 重写为指向脚本 + 手动 fallback) +最后更新:2026-06-01(update.sh 默认源改腾讯/阿里滞后卡 litellm;build 跳过改 --skip-build;pip 不静默 + build --progress=plain) --- @@ -262,8 +262,9 @@ sudo bash /opt/zcbot/deploy/update.sh 脚本顺序写死:`git pull --ff-only` → `pip install -r` → `db upgrade head` → **`docker build` sandbox 镜像** → **`systemctl restart zcbot`** → `curl /healthz` 验活。要点: - **build 必须在 restart 之前**:sandbox 容器 per-user 长驻 + 复用,`tools/` 是 build 进镜像的(非 mount)。restart 时 `shutdown_all` 清旧容器,下次 `ensure()` 才用新 `zcbot-sandbox:latest` 重建 —— 顺序反了新 tools/ 要等下次重启才生效。 -- **sandbox build 每次都跑没关系**:layer cache 让重活(pip ~1G / chromium / 字体 / mermaid,都在 `COPY tools/` 之上)在改代码部署时秒过;只有 `requirements.txt` 变了才整体重建(~5-10min,正好也是该重建的时候)。host backend 机器不想动 docker:`ZCBOT_SKIP_SANDBOX_BUILD=1 sudo -E bash deploy/update.sh`。 -- **镜像源默认阿里**(`APT_MIRROR=mirrors.aliyun.com` / `PIP_INDEX_URL=mirrors.aliyun.com/pypi/simple/` / `NPM_REGISTRY=registry.npmmirror.com`,境内快)。要命中 docker cache 就别两组源来回换(换源从 pip 层炸开全重跑)。想用官方源:`PIP_INDEX_URL= sudo -E bash deploy/update.sh`(置空即回落 Dockerfile 官方默认)。 +- **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 会折叠刷新、看着像非串行)。 - **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` 并以非零退出。 @@ -598,6 +599,7 @@ sudo xfs_quota -x -c "limit -p bhard=10g zcbot_" /opt | `[startup] [warn] fs quota: on ...` | workspace 所在 fs 没启 OS 层 quota。dogfood 阶段忽略;外部用户开放前必须升级 xfs prjquota / ext4 project / zfs(详 RUN.md「配额硬化」段) | | `docker run zcbot-sandbox:latest` 报 `Unable to find image` | 镜像没 build。`sudo -u zcbot docker build -f deploy/sandbox/Dockerfile --build-arg HOST_UID=$(id -u zcbot) --build-arg HOST_GID=$(id -g zcbot) -t zcbot-sandbox:latest .` | | 镜像 build pip 报 `ReadTimeoutError: HTTPSConnectionPool(host='files.pythonhosted.org', ...)` | 境内访问 PyPI 抖动。加 `--build-arg PIP_INDEX_URL=https://mirrors.cloud.tencent.com/pypi/simple/`(腾讯云内网)或阿里云 / 清华源,详 RUN.md「镜像构建」段。Dockerfile 已把 pip timeout 拉到 60s,主因仍是源不通而非超时 | +| pip 报 `Could not find a version that satisfies the requirement litellm>=1.83.0`(伴随一串 `Ignored ... yanked versions: 0.1.xxxx`) | 用的镜像源同步滞后,没有该新版本。**阿里 PyPI 一度只到 litellm 1.82.6** —— update.sh 默认已改腾讯源(到 1.88)。若手动 build 撞到:换 `PIP_INDEX_URL=https://mirrors.cloud.tencent.com/pypi/simple/` 或清华源。那串 `0.1.xxxx` 是 litellm 远古版本,纯干扰信息 | | 镜像 build npm 装 mermaid-cli 慢 / fail | npm 源境内慢。加 `--build-arg NPM_REGISTRY=https://mirrors.cloud.tencent.com/npm/`(腾讯云)或 `https://registry.npmmirror.com/`(阿里) | | 镜像 build apt 报 `OpenSSL error: ... unexpected eof while reading` | 腾讯云 / 阿里 mirror HTTPS 端偶发 close_notify 缺失,OpenSSL 3 严格 fail。改用 http 形式:`--build-arg APT_MIRROR=http://mirrors.cloud.tencent.com`(apt 包 GPG 签名校验,无 HTTPS 安全收益)。Dockerfile 已配 apt retry=5 + 关 pipeline,重 build 一般直接过 | | 容器内 shell 写工作目录报 `Permission denied`(but `sandbox check` ⑤ HOST_UID aligned ok) | DockerExecutor 写死了 `--user 1000:1000` 不会自动跟 build 的 HOST_UID 同步(改 `--user zcbot` 后已修)。仍报错检查镜像内 `docker run --rm --entrypoint id zcbot-sandbox:latest zcbot` 输出 uid 是否 = `id -u $(whoami)` | diff --git a/deploy/update.sh b/deploy/update.sh index 46914db..86207c7 100755 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -12,17 +12,29 @@ # 6) curl /healthz 验活 # # 怎么跑:在部署机 `sudo deploy/update.sh`(需 root 跑 systemctl;文件操作降到 $APP_USER)。 +# --skip-build 跳过第 4 步 sandbox 镜像重建(host backend 机器 / 临时不想动 docker) # 一次性 bootstrap(useradd / 写 systemd unit / enable)不在这里 —— 见 RUN.md §部署 一次性。 # # 可调 env(不传则用默认 / Dockerfile 默认): # APP_DIR(默认=脚本上级目录,即仓库根) APP_USER(默认=部署目录属主) SERVICE(zcbot) PORT(8765) # APT_MIRROR / PIP_INDEX_URL / PIP_TRUSTED_HOST / NPM_REGISTRY 镜像源, -# 默认阿里源(境内快);要稳定命中 docker cache 就别在两组源之间来回换 -# (换源会从 pip 层炸开全重跑)。想用官方源:PIP_INDEX_URL= sudo -E bash ...(置空) -# ZCBOT_SKIP_SANDBOX_BUILD=1 跳过第 4 步(host backend 机器 / 临时不想动 docker) +# 默认腾讯源(境内快,litellm 等新包同步及时;阿里源滞后过——曾卡死在没有的版本上)。 +# 要稳定命中 docker cache 就别在两组源之间来回换(换源会从 pip 层炸开全重跑)。 +# 想用官方源:PIP_INDEX_URL= sudo -E bash ...(置空) set -euo pipefail +# ── 参数 ────────────────────────────────────────────────────── +# 默认 build;--skip-build 显式跳过(不为旧用法留 env 别名,开发期不留兼容)。 +SKIP_BUILD=0 +while [ $# -gt 0 ]; do + case "$1" in + --skip-build) SKIP_BUILD=1; shift ;; + -h|--help) echo "用法:sudo $0 [--skip-build]"; exit 0 ;; + *) echo "[deploy] ERROR: 未知参数:$1(只支持 --skip-build)" >&2; exit 1 ;; + esac +done + # 部署目录 = 本脚本所在目录(deploy/)的上一级,不写死路径;仍可 env 覆盖。 # readlink -f 解析软链 + 相对路径,兼容 `sudo ./update.sh` / `sudo bash /abs/deploy/update.sh` # / 经软链调用(Ubuntu coreutils 有 readlink -f)。 @@ -37,11 +49,12 @@ IMAGE="zcbot-sandbox:latest" PY="$APP_DIR/.venv/bin/python" HEALTH_URL="http://127.0.0.1:${PORT}/healthz" -# 镜像源默认阿里(境内快);Dockerfile sed 只替 host 前缀,mirror 站目录结构与官方一致。 +# 镜像源默认腾讯(境内快 + 新包同步及时;Dockerfile sed 只替 host 前缀,目录结构同官方)。 +# 阿里 PyPI 镜像同步滞后(litellm 曾只到 1.82.6,卡死 requirements 的 >=1.83.0),故默认改腾讯。 # 置空(如 PIP_INDEX_URL= sudo -E ...)则该项不传 build-arg,回落 Dockerfile 官方源默认。 -APT_MIRROR="${APT_MIRROR-https://mirrors.aliyun.com}" -PIP_INDEX_URL="${PIP_INDEX_URL-https://mirrors.aliyun.com/pypi/simple/}" -NPM_REGISTRY="${NPM_REGISTRY-https://registry.npmmirror.com/}" +APT_MIRROR="${APT_MIRROR-https://mirrors.cloud.tencent.com}" +PIP_INDEX_URL="${PIP_INDEX_URL-https://mirrors.cloud.tencent.com/pypi/simple/}" +NPM_REGISTRY="${NPM_REGISTRY-https://mirrors.cloud.tencent.com/npm/}" log() { echo "[deploy] $*"; } fail() { echo "[deploy] ERROR: $*" >&2; exit 1; } @@ -80,8 +93,14 @@ else fi # ── 2) 依赖 ─────────────────────────────────────────────────── -log "pip install -r requirements.txt ..." -asuser "$PY" -m pip install -q -r "$APP_DIR/requirements.txt" +# host venv 的 pip 也走同一镜像源 —— 经 sudo -u 后 PIP_INDEX_URL env 会被洗掉, +# 不显式 --index-url 的话 host 用自己的 pip.conf(可能仍指阿里,撞 litellm>=1.83 缺版本)。 +# 不带 -q:依赖装包是耗时步骤,留输出让部署时能看到进度(下载哪个包 / 卡在哪)。 +pip_args=(-r "$APP_DIR/requirements.txt" --timeout 60) +[ -n "${PIP_INDEX_URL:-}" ] && pip_args+=(--index-url "${PIP_INDEX_URL}") +[ -n "${PIP_TRUSTED_HOST:-}" ] && pip_args+=(--trusted-host "${PIP_TRUSTED_HOST}") +log "pip install -r requirements.txt(源:${PIP_INDEX_URL:-官方默认})..." +asuser "$PY" -m pip install "${pip_args[@]}" # ── 3) DB migration ────────────────────────────────────────── # env.py 直接读 os.environ['ZCBOT_DB_URL'](不读 .env),这里从 .env 抠出来显式喂进去。 @@ -92,8 +111,8 @@ log "db upgrade head ..." asuser env ZCBOT_DB_URL="$DB_URL" "$PY" "$APP_DIR/main.py" db upgrade head # ── 4) 重建 sandbox 镜像 ────────────────────────────────────── -if [ "${ZCBOT_SKIP_SANDBOX_BUILD:-0}" = "1" ]; then - log "ZCBOT_SKIP_SANDBOX_BUILD=1,跳过 sandbox 镜像重建" +if [ "$SKIP_BUILD" = "1" ]; then + log "--skip-build,跳过 sandbox 镜像重建" else # build-arg 稳定才命中 cache:HOST_UID/GID 跟 host $APP_USER 对齐(bind mount owner), # 镜像源仅在对应 env 非空时才传(传空会把 Dockerfile 默认覆盖成空串,pip 直接挂)。 @@ -106,8 +125,11 @@ else [ -n "${PIP_TRUSTED_HOST:-}" ] && build_args+=(--build-arg "PIP_TRUSTED_HOST=${PIP_TRUSTED_HOST}") [ -n "${NPM_REGISTRY:-}" ] && build_args+=(--build-arg "NPM_REGISTRY=${NPM_REGISTRY}") + # --progress=plain:逐行流式输出(默认 TTY 进度 UI 多层并行折叠刷新,看着像非串行 + # 且非交互管道下会缓冲);部署时要能持续看到每层在干啥,故强制 plain。 log "docker build $IMAGE(改代码秒过 / requirements 变了 ~5-10min)..." asuser docker build \ + --progress=plain \ -f "$APP_DIR/deploy/sandbox/Dockerfile" \ "${build_args[@]}" \ -t "$IMAGE" \