Docker sandbox real test + cloud merge status endpoint
- DockerExecutor: extra mounts (host.py work/state, repo, codex-home
at same paths), worker --docker-extra-mounts, container user
configurable; Dockerfile copies codex-resources beside the codex
binary and puts bwrap on PATH (previous /usr/local/lib layout
made codex report bubblewrap unavailable inside the container).
- Real Docker task on external host: triple added in ~18s, merged
inside a container, main repo now add+mul2+triple, --rm cleanup.
Honest boundary: this host/kernel forbids unprivileged user
namespaces, so bwrap fails on host and in containers; production
mode here is Docker isolation + inner danger-full-access, not
double sandboxing.
- Cloud merge status: POST /tasks/{id}/merge syncs status only
(merge itself stays with host.py merge); completed -> merged,
maintainer+ (approve_merge), other states and repeat calls 409,
audit merge_task. Tests: cloud 223, host 27, all passing.
This commit is contained in:
1 parent
631580beab
commit
07a8eaaa73
9 files changed
+1082
-37
No files matched your search
+54
-6
@@ -42,9 +42,11 @@ queued → running → completed / failed / interrupted / merged / merge-failed
|
||||
```
|
||||
|
||||
本版 server 只流转 queued → running(心跳)→ interrupted(取消 /
|
||||
sweep 回收)。`completed` / `merged` 由 worker / 控制面后续调用方在
|
||||
跑完模型回合、合完分支后回写,本 server 不提供“跑模型”端点,也不
|
||||
要假装一次 POST 就能让任务变 completed。
|
||||
sweep 回收),以及 completed → merged(见下表 `POST /tasks/{id}/merge`)。
|
||||
`completed` 由 worker 在跑完模型回合后回写;`merged` 只由合并
|
||||
状态接口同步,不执行合并本身——合并仍由 host.py merge(本地
|
||||
或 Docker 容器内)执行,成功后再调本接口,云端不该在任务
|
||||
completed 时自动改主分支。
|
||||
|
||||
## 启动方式
|
||||
|
||||
@@ -166,6 +168,7 @@ psycopg,写明“需安装 psycopg”),绝不静默回落到 SQLite,
|
||||
| `GET /tasks?project_id=...` | 该项目 `read` | 返回该项目任务列表:`{"tasks": [...]}`;无权限 `403` |
|
||||
| `GET /tasks/{id}` | 该任务所属项目 `read` | 无权限 `403`;不存在 `404` |
|
||||
| `POST /tasks/{id}/cancel` | 该项目 `create_task` | `queued` / `running` 改为 `interrupted` 并记审计 `cancel_task`;已是终态(completed / failed / interrupted / merged / merge-failed)返回 `400`;不存在 `404` |
|
||||
| `POST /tasks/{id}/merge` | 该项目 `approve_merge`(maintainer 以上) | 只同步状态,不执行合并:仅 `completed` 可改为 `merged` 并记审计 `merge_task`,其它状态(含已 merged 的重复调用)返回 `409`;不存在 `404`,越权 `403`,无 key `401` |
|
||||
| `POST /tasks/{id}/heartbeat` | 该项目 `create_task` | 更新 `last_heartbeat = now`;状态为 `queued` 则改为 `running`;返回任务 |
|
||||
| `POST /admin/sweep` | 认证即可 | 调 `sweep_stale_tasks(db_path, threshold_seconds=300)`(body 可传 `threshold_seconds` 覆盖默认值);把 `running` 且 `last_heartbeat`(无心跳则用 `updated_at`)早于 `now - threshold` 的任务改为 `interrupted` 并记审计 `sweep_interrupted`;返回 `{"swept": [ids]}` |
|
||||
| `POST /tasks/{id}/cost` | 该任务所属项目 `read` | 成本聚合:`{task_id, project_id, total_tokens, usd_micros, turns, fallback_count, actual_tokens, estimated_tokens}`;真数与估算分开;不存在 `404`,越权 `403` |
|
||||
@@ -258,9 +261,11 @@ interrupted 且终态任务 cancel 返回 400、心跳后 sweep 不扫刚心跳
|
||||
- **Slack App**:需要真实 Slack App(签名密钥、斜杠命令、事件订阅)
|
||||
才能接通 `SLACK_SIGNING_SECRET` 背后的入站;目前只有验签与解析,
|
||||
且 `NotificationService` 的 Slack 发送端仍是占位注入。
|
||||
- **云容器账号**:`CloudExecutor` 仍是占位(无账号即抛错),Docker
|
||||
执行也未接通容器挂载 / 网络隔离 / 资源限额;接通前不要宣称任务
|
||||
已在云端或容器里跑。
|
||||
- **云容器账号**:`CloudExecutor` 仍是占位(无账号即抛错)。
|
||||
Docker 执行已接通(见下文“Docker 沙箱执行器”),但真实
|
||||
docker run、镜像构建、资源限额实测仍需在外部主机上做:
|
||||
本 VM 没有 Docker,本轮未连外部主机、未向真实外部服务发
|
||||
请求、未跑真实模型回合,不要宣称 Docker 沙箱已在真实环境验证。
|
||||
- **模型 key 供给方式**:这是当前最实在的卡点。上次
|
||||
real-nightly-tests automation 就卡在这里:定时进程手里没有中转站
|
||||
key,key 从不存文件、只在进程环境里 transient 供给。控制面要跑
|
||||
@@ -308,3 +313,46 @@ interrupted 且终态任务 cancel 返回 400、心跳后 sweep 不扫刚心跳
|
||||
的 Store 连接已统一走 cloud.db 的 WAL + busy_timeout 连接
|
||||
(Postgres 后端仍是骨架,驱动缺失时启动即失败、不先建 SQLite
|
||||
文件,这一点迁移后未退化)。
|
||||
|
||||
## Docker 沙箱执行器(本轮新增)
|
||||
|
||||
- 用法:worker 选 docker 执行器——
|
||||
`python3 -m cloud.worker --db <库> --executor docker
|
||||
[--docker-image codex-app-server:latest] [--docker-cpus 2]
|
||||
[--docker-memory 2g] [--docker-user 1000:1000]
|
||||
[--docker-worktrees-root <目录>] [--docker-timeout <秒>]`;
|
||||
或在代码里直接传 `DockerExecutor(...)` 给 `Worker(executor=...)`。
|
||||
选 docker 但本机无 docker 时任务明确失败,error_text 含
|
||||
docker 缺失,绝不静默降级为本地执行;选 local 时行为不变。
|
||||
- 命令构造(示例,任务 id 为 t1、worktree 为 /tmp/wt-t1 时):
|
||||
`docker run --rm --name codex-t1 --user 1000:1000 --cpus 2
|
||||
--memory 2g -v /tmp/wt-t1:/work -v <代码目录>:/app:ro -w /work
|
||||
-e XCM_API_KEY codex-app-server:latest python3 /app/host.py
|
||||
dispatch --name t1 --task <提示>`。要点:只挂任务自己的
|
||||
worktree(可写)和代码目录(只读),绝不挂宿主根目录、绝不
|
||||
挂任何含密钥的目录;容器名由任务 id 派生(仅安全字符);
|
||||
`-e` 只传 env_key 名,不带值,key 缺失时容器根本不启动;
|
||||
资源限额默认 2 核 / 2G,可配置;容器内进程以非 root 用户跑。
|
||||
- 镜像:`cloud/docker/Dockerfile`,基于 python:3.12-slim,装
|
||||
git,复制调用方提供的配对 codex 二进制(codex +
|
||||
codex-code-mode-host + codex-resources)。构建命令示例(在
|
||||
仓库根目录,构建上下文就是仓库根):
|
||||
`docker build -f cloud/docker/Dockerfile
|
||||
-t codex-app-server:latest --build-arg CODEX_BIN_DIR=bin .`
|
||||
bin/ 不进仓库,构建主机上没有配对二进制就构建失败;代码
|
||||
不在镜像里,运行时只读挂载到 /app,代码改了不用重新构建,
|
||||
codex 版本变了才需要重新构建;镜像里不含任何 key。
|
||||
- 清理与取消:`docker run --rm`,正常失败也自动删容器;超时
|
||||
时先 docker kill 再 docker rm -f;worker 取消时按任务派生的
|
||||
容器名 docker kill,不只改数据库状态。
|
||||
- 网络白名单的诚实边界:docker run 默认 bridge 网络,本执行
|
||||
器不做 egress 白名单——真正的“只放行模型 API”需要宿主防火
|
||||
墙或代理规则,那是部署项,不是本轮代码假装做到的,不要宣称
|
||||
已实现网络白名单。另有一项待实测:codex 自带的 bubblewrap
|
||||
沙箱在容器内是否可用,取决于宿主的容器权限配置,本镜像不
|
||||
假装解决;若 bwrap 不可用,host.py 的 sandbox 参数仍可退到
|
||||
read-only / workspace-write 的控制面约束。
|
||||
- 仍需在外部主机上实测(本 VM 没有 Docker,本轮未连外部主机):
|
||||
真实 docker build、真实 docker run、资源限额在真实负载下的
|
||||
表现、bwrap 在容器内的可用性、以及取消时容器确实消失。
|
||||
|
||||
Reference in new issue
Block a user