revert(workspace): 撤 ZCBOT_WORKSPACE_DIR env 覆盖(架构 bug),数据盘改用 bind mount
2026-06-02 给 resolve_workspace 加的 env 覆盖与路径存储层冲突:core/paths.py 把 DB working_dir 锚定 ROOT(代码仓库目录)存相对串,to_db_path 对 ROOT 外路径 直接 relative_to raise。env 一旦指向 ROOT 外的 /data/...,三家分叉: - 文件面板 /v1/files 走 resolve_workspace(吃到 env)看数据盘 - agent resume 走 from_db_path(锚 ROOT)看 ROOT/workspace - 新建 task to_db_path 直接 500 现场症状:文件面板"目录尚未创建",但 agent 文件其实写在老 ROOT/workspace。 改法:resolve_workspace 回退成 显式 arg > cfg workspace_dir > 默 workspace (均 ROOT/<值>)。import os 保留(别处仍用)。要把重写入区落独立数据盘改用 bind mount 把 /data/zcbot/workspace 接到 ROOT/workspace —— .resolve() 不展开 bind,内核路径保持 ROOT 内,relative_to(ROOT) 照常过,DB 不用改,dev 不受影响。 RUN.md:删 .env 的 ZCBOT_WORKSPACE_DIR 段;「workspace 落独立数据盘」段改 bind mount(+ RequiresMountsFor 开机顺序硬化);故障表替换旧 env 行 + 加 "目录尚未创建"诊断行。PROGRESS.md:2026-06-03 加回退记录。DESIGN 不动。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
382a85e88e
commit
9cd33cf4d6
|
|
@ -21,9 +21,13 @@
|
|||
|
||||
## 已完成关键能力
|
||||
|
||||
### 2026-06-03
|
||||
|
||||
- **回退 `ZCBOT_WORKSPACE_DIR` env 覆盖(架构有 bug),workspace 落数据盘改用 bind mount**:2026-06-02 加的 env 覆盖与路径存储层冲突 —— `core/paths.py` 把 DB 的 `working_dir` 锚定 **ROOT**(代码仓库目录)存相对串,`to_db_path` 对 ROOT 外路径直接 `relative_to` raise。env 一旦指向 ROOT 外的 `/data/...`,三家分叉:文件面板 `/v1/files` 走 `resolve_workspace`(吃到 env)看数据盘、agent resume 走 `from_db_path`(锚 ROOT)看 `ROOT/workspace`、新建 task `to_db_path` 直接 500。现场症状:文件面板"目录尚未创建"但 agent 文件其实写在老 `ROOT/workspace`。改法:`resolve_workspace` 回退成 `显式 arg > cfg workspace_dir > 默 workspace`(均 `ROOT/<值>`),删掉 env 分支与 `import os` 无关(os 别处仍用,保留)。要落数据盘改用 **bind mount** 把 `/data/zcbot/workspace` 接到 `ROOT/workspace`(`.resolve()` 不展开 bind,内核路径保持 ROOT 内,DB 不用改,dev 不受影响)。**对外行为变化 → 更 RUN.md**:删 `.env` 的 `ZCBOT_WORKSPACE_DIR` 段、「workspace 落独立数据盘」段改 bind mount(+ `RequiresMountsFor` 开机顺序硬化)、故障表两行(替换旧 env 行 + 加"目录尚未创建"诊断行)。DESIGN 不动。
|
||||
|
||||
### 2026-06-02
|
||||
|
||||
- **`resolve_workspace` 加 env 覆盖 `ZCBOT_WORKSPACE_DIR`(per-host 部署,不碰共用 yaml)**:prod 想把重写入的 `workspace/users/` 落到独立数据盘(1T xfs prjquota,空间 + OS 层配额一步到位),但 `config/agent.yaml` 的 `workspace_dir` 是 dev/prod 共用提交的,改成绝对路径会带歪 dev。改法:`core/agent_builder.py:resolve_workspace` 优先级改为 **显式 arg > env `ZCBOT_WORKSPACE_DIR` > cfg `workspace_dir` > 默 `workspace`**,env/cfg 值都 `ROOT / ws`(POSIX 上绝对右操作数覆盖左 → 绝对路径直接生效,相对挂 repo 根)。prod systemd 设 `Environment=ZCBOT_WORKSPACE_DIR=/data/zcbot-workspace`,dev 不设照旧。PG 暂不迁(元数据库小,留默认 `/var/lib/postgresql` 少坑,等真涨到 30–40G 再说)。**对外行为(env 变量)变化 → 更 RUN.md**:env 段加 `ZCBOT_WORKSPACE_DIR`、新增「workspace 落独立数据盘」段(整盘 mkfs.xfs + fstab prjquota + rsync 迁移 + systemd env)、故障表加一行。DESIGN 不动(无架构/schema 变化)。
|
||||
- **【已于 2026-06-03 回退,见上】`resolve_workspace` 加 env 覆盖 `ZCBOT_WORKSPACE_DIR`(per-host 部署,不碰共用 yaml)**:prod 想把重写入的 `workspace/users/` 落到独立数据盘(1T xfs prjquota,空间 + OS 层配额一步到位),但 `config/agent.yaml` 的 `workspace_dir` 是 dev/prod 共用提交的,改成绝对路径会带歪 dev。改法:`core/agent_builder.py:resolve_workspace` 优先级改为 **显式 arg > env `ZCBOT_WORKSPACE_DIR` > cfg `workspace_dir` > 默 `workspace`**,env/cfg 值都 `ROOT / ws`(POSIX 上绝对右操作数覆盖左 → 绝对路径直接生效,相对挂 repo 根)。prod systemd 设 `Environment=ZCBOT_WORKSPACE_DIR=/data/zcbot-workspace`,dev 不设照旧。PG 暂不迁(元数据库小,留默认 `/var/lib/postgresql` 少坑,等真涨到 30–40G 再说)。**对外行为(env 变量)变化 → 更 RUN.md**:env 段加 `ZCBOT_WORKSPACE_DIR`、新增「workspace 落独立数据盘」段(整盘 mkfs.xfs + fstab prjquota + rsync 迁移 + systemd env)、故障表加一行。DESIGN 不动(无架构/schema 变化)。
|
||||
|
||||
- **修 embed 模式"登录页一闪而过"(绘制时机,非鉴权)**:`web/static/dev.html` 的 `#login` 默认 `display:flex` 且带 `login-in .35s` 动画,而加 `body.embed-mode`(→ CSS 隐藏 `#login`)的 `embedInit()` 在 body 末尾才跑;单文件 3800+ 行,浏览器常在解析到底部脚本前就先把登录卡画出来 → 闪一下。改法:在 `<body>` 第一行加一段同步内联脚本,`?embed=1` 时立即 `document.body.classList.add("embed-mode")`,赶在 `#login` 解析/绘制之前隐藏它 → 根本不绘制。只是"绘制闸门",底部 `embedInit()`(postMessage 握手 / `embed-waiting` 覆盖层 / token 分支)完全不动,`embed-mode` 幂等。未提前加 `embed-waiting`(有 stored token 时 `embedInit` 走 `enterApp` 不移除等待层会卡死,故等待层决定仍留底部按 token 判)。**bug 修复,DESIGN 不动;URL 参数/命令/env 无变化,RUN 不动**。
|
||||
|
||||
|
|
|
|||
55
RUN.md
55
RUN.md
|
|
@ -372,16 +372,13 @@ sudo -u zcbot docker network create --internal zcbot-sandbox-net
|
|||
# PG 实际 IP,逗号分隔。defense-in-depth ── 即便落内网三段(§7.5 #1),
|
||||
# init.sh 再加一遍 DROP 规则。生产部署必填。
|
||||
ZCBOT_PG_IPS=10.1.2.3,10.1.2.4
|
||||
|
||||
# workspace 根目录(per-host 覆盖)。来源只有两个:这条 env,或 yaml `workspace_dir`
|
||||
# (默值 = `workspace`);env 设了就用 env,没设就用 yaml 那条。两者都按 `ROOT/<值>` 解析
|
||||
# (绝对路径直接生效,相对路径挂 repo 根下)。所以**不设这条 env → 走 yaml 的
|
||||
# workspace_dir=workspace → ROOT/workspace(即 zcbot 下的 workspace)**,dev 维持原样。
|
||||
# prod 设成数据盘绝对路径,把重写入的 user 子树落过去(下方「workspace 落独立数据盘」段),
|
||||
# 不碰提交进仓库的 agent.yaml,两边不抢同一份配置。
|
||||
# ZCBOT_WORKSPACE_DIR=/data/zcbot-workspace
|
||||
```
|
||||
|
||||
> workspace 根目录**没有 env 覆盖**:固定 `ROOT/workspace`(由 yaml `workspace_dir` 决定,默 `workspace`)。
|
||||
> 要把重写入区落独立数据盘,用 **bind mount** 把数据盘目录接到 `ROOT/workspace`(逻辑路径不变),
|
||||
> 不要指向 ROOT 外的绝对路径 —— DB 的 `working_dir` 锚定 ROOT 存相对串,ROOT 外路径会 raise。
|
||||
> 详「workspace 落独立数据盘」段。
|
||||
|
||||
### 验证
|
||||
|
||||
Step 3 之后,推荐用集成验证(web 起 docker backend + dev SPA 发 `shell` / `run_python` 消息):
|
||||
|
|
@ -555,9 +552,15 @@ lifespan 启动时同样会打第 ⑤ 项的 WARN 到 stdout(`[startup] [warn] f
|
|||
### workspace 落独立数据盘(prod,大空间 + quota fs)
|
||||
|
||||
prod 的 `workspace/users/<uuid>/` 是重写入区(报告 / 图 / pptx 等大件落这,DB 只存元数据)。
|
||||
推荐挂一块独立数据盘(xfs prjquota),空间和 OS 层配额一步到位。**dev 不动** —— 走提交进
|
||||
仓库的相对 `workspace_dir`(= `ROOT/workspace`);prod 在 systemd unit 里设 env
|
||||
`ZCBOT_WORKSPACE_DIR` 指到数据盘,两边不抢同一份 `agent.yaml`。
|
||||
推荐挂一块独立数据盘(xfs prjquota),空间和 OS 层配额一步到位。
|
||||
|
||||
**手法是 bind mount,不是 env 覆盖。** 代码把 workspace 固定解析成 `ROOT/workspace`,且 DB 的
|
||||
`working_dir` 经 `core/paths.py` 锚定 ROOT 存**相对串**(`to_db_path` 对 ROOT 外的绝对路径直接
|
||||
raise)。所以**不能**让 workspace 指向 ROOT 外的 `/data/...`(三处会各算各的:文件面板走
|
||||
`resolve_workspace` 看数据盘、agent 走 `from_db_path` 看 `ROOT/workspace`、新建 task 直接 500)。
|
||||
正解:让物理数据落 `/data`,但逻辑路径仍是 `ROOT/workspace` —— 用 bind mount 把数据盘目录接上去。
|
||||
`bind` 不像 symlink 会被 `.resolve()` 展开,内核路径保持 `ROOT/workspace`,`relative_to(ROOT)` 照常过,
|
||||
**DB 一个字不用改,dev 不受影响(dev 不 bind,直接用本地 `ROOT/workspace`)**。
|
||||
|
||||
PG 不必跟着搬:它是元数据库,长期个位数~几十 G,根盘够用;留默认 `/var/lib/postgresql/<ver>/main`
|
||||
更省坑(`pg_ctlcluster` / AppArmor 按标准路径来)。等 `pg_database_size` 真奔 30–40G、根盘紧了
|
||||
|
|
@ -578,18 +581,27 @@ echo "UUID=$UUID /data xfs defaults,prjquota 0 0" | sudo tee -a /etc/fstab
|
|||
sudo mount -a
|
||||
findmnt -no FSTYPE,OPTIONS /data # 期望:xfs ... prjquota
|
||||
|
||||
# 3) 迁现有 workspace 数据(prod 若 workspace 还空则跳过 rsync),owner 对齐跑服务的账号
|
||||
sudo rsync -aXS /home/ubuntu/zcbot/workspace/ /data/zcbot-workspace/
|
||||
sudo chown -R ubuntu:ubuntu /data/zcbot-workspace # uid 必须 == 容器内 zcbot(HOST_UID)
|
||||
# 3) 准备数据盘上的物理目录 + 迁现有 workspace 数据(prod 若还空则跳过 rsync)
|
||||
sudo mkdir -p /data/zcbot/workspace
|
||||
sudo rsync -aXS /home/ubuntu/zcbot/workspace/ /data/zcbot/workspace/
|
||||
sudo chown -R ubuntu:ubuntu /data/zcbot/workspace # uid 必须 == 容器内 zcbot(HOST_UID)
|
||||
|
||||
# 4) systemd unit 加 env 指过去(Environment= 或 EnvironmentFile 的 .env),重启
|
||||
# Environment=ZCBOT_WORKSPACE_DIR=/data/zcbot-workspace
|
||||
sudo systemctl daemon-reload && sudo systemctl start zcbot
|
||||
python3 main.py sandbox check # fs quota 那项应变 [ok]
|
||||
# 4) bind mount:物理 /data/zcbot/workspace,逻辑仍是 ROOT/workspace(写 fstab 持久化)
|
||||
sudo mkdir -p /home/ubuntu/zcbot/workspace
|
||||
echo "/data/zcbot/workspace /home/ubuntu/zcbot/workspace none bind 0 0" | sudo tee -a /etc/fstab
|
||||
sudo mount -a
|
||||
findmnt /home/ubuntu/zcbot/workspace # 确认 bind 上了,FS 显示落 /data 那块盘
|
||||
|
||||
sudo systemctl start zcbot
|
||||
python3 main.py sandbox check # fs quota 那项应变 [ok](探测的是 bind 后的真实 fs)
|
||||
```
|
||||
|
||||
确认 `sandbox check` 通过、新 task 文件确实落 `/data/zcbot-workspace/users/...` 后,再删旧
|
||||
`/home/ubuntu/zcbot/workspace`。
|
||||
确认 `sandbox check` 通过、新 task 文件确实落到 `/data` 那块盘后即可。`/data/zcbot/workspace` 下叫什么、
|
||||
嵌几层随意,关键是 bind 到 `ROOT/workspace`。
|
||||
|
||||
> **开机顺序硬化(强烈建议)**:给 systemd unit `[Service]` 加 `RequiresMountsFor=/home/ubuntu/zcbot/workspace`,
|
||||
> 否则若开机时 bind mount 还没就绪,service 抢先启动会把文件写进根盘那个空壳 `ROOT/workspace`(数据分叉)。
|
||||
> 加完 `sudo systemctl daemon-reload`。
|
||||
|
||||
### 配额硬化(§7.5 #4,外部开放前必做)
|
||||
|
||||
|
|
@ -645,7 +657,8 @@ sudo xfs_quota -x -c "limit -p bhard=10g zcbot_<user_uuid>" /opt
|
|||
| Sandbox 容器 build 完起不来,`docker logs` 显示 iptables 报错 | 缺 NET_ADMIN cap(`--cap-add=NET_ADMIN` 漏了)或 kernel 不支持(WSL2 / OpenVZ 环境不能跑)。Ubuntu 物理 / KVM 正常。验:`docker exec ... iptables -V` |
|
||||
| 启动报 `ZCBOT_SANDBOX_BACKEND=docker but sandbox init failed: ...` | docker daemon 没起 / 用户不在 docker group / network create 失败。先跑 `main.py sandbox check` 看哪一项 err |
|
||||
| `[startup] [warn] fs quota: <fstype> on ...` | workspace 所在 fs 没启 OS 层 quota。dogfood 阶段忽略;外部用户开放前必须升级 xfs prjquota / ext4 project / zfs(详 RUN.md「配额硬化」段) |
|
||||
| prod 想把 workspace 落独立数据盘,但 `agent.yaml` 是 dev/prod 共用提交的 | 别改 `workspace_dir`(会带歪 dev)。prod systemd 设 env `ZCBOT_WORKSPACE_DIR=/data/zcbot-workspace`(优先级 env > yaml > 默)。详「workspace 落独立数据盘」段 |
|
||||
| prod 想把 workspace 落独立数据盘 | **别用 env / 别指 ROOT 外绝对路径**(workspace 锚定 ROOT,ROOT 外会让文件面板 / agent / 新建 task 三家分叉)。用 **bind mount** 把 `/data/...` 接到 `ROOT/workspace`,逻辑路径不变,DB 不用改。详「workspace 落独立数据盘」段 |
|
||||
| 文件面板"目录尚未创建"但文件确实在 / agent 写的文件面板看不到 | workspace 被指到了 ROOT 外(旧 `ZCBOT_WORKSPACE_DIR` 绝对路径残留)→ 文件面板走 `resolve_workspace` 看一处、agent 走 DB `from_db_path`(锚 ROOT)看另一处。删掉 env、改用 bind mount(见上段),三家归一 |
|
||||
| `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 远古版本,纯干扰信息 |
|
||||
|
|
|
|||
|
|
@ -95,19 +95,15 @@ def _resolve_executor(
|
|||
|
||||
|
||||
def resolve_workspace(workspace: Optional[str], cfg: Optional[dict] = None) -> Path:
|
||||
"""workspace 根解析,优先级:显式 arg > env `ZCBOT_WORKSPACE_DIR` > cfg `workspace_dir` > "workspace"。
|
||||
"""workspace 根解析,优先级:显式 arg > cfg `workspace_dir` > 默 "workspace"(均 `ROOT/<值>`)。
|
||||
|
||||
env 覆盖给 per-host 部署用 —— prod 在 systemd 里设 `ZCBOT_WORKSPACE_DIR=/data/zcbot-workspace`
|
||||
把重写入的 user 子树落到独立数据盘(xfs prjquota),dev 不设就吃提交进仓库的相对
|
||||
`workspace_dir`(= ROOT/workspace),两边不抢同一份 yaml。env / cfg 值绝对相对都行:
|
||||
`ROOT / "/abs"` 在 POSIX 上即 "/abs"(绝对右操作数覆盖左),相对则挂在 repo 根下。
|
||||
workspace 必须落在 ROOT 子树内 —— DB 的 `working_dir` 经 `core/paths.py` 锚定 ROOT 存
|
||||
相对串(`to_db_path` 对 ROOT 外路径直接 raise)。要把重写入区落独立数据盘,用 **bind mount**
|
||||
把 `/data/...` 接到 `ROOT/workspace`(逻辑路径不变,`.resolve()` 不展开 bind),
|
||||
不要指向 ROOT 外的绝对路径(详 RUN.md「workspace 落独立数据盘」段)。
|
||||
"""
|
||||
cfg = cfg or load_config()
|
||||
if workspace:
|
||||
p = Path(workspace)
|
||||
else:
|
||||
ws = os.getenv("ZCBOT_WORKSPACE_DIR") or cfg.get("workspace_dir", "workspace")
|
||||
p = ROOT / ws
|
||||
p = Path(workspace) if workspace else ROOT / cfg.get("workspace_dir", "workspace")
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue