Container-side isolation was project-blind: every task mounted
the shared /app/work (all worktrees + host-state), the shared
codex-home (all thread history), the shared repos dir and the
shared test-repo, and all containers shared the default bridge
network. With --tenant-isolation (Docker only; Local runs on
the host under one user and has no container boundary to
enforce), each project gets its own host dirs under
tenants_root/<project>/{work,codex-home,test-repo,<project>}:
- mounts: container paths stay identical (/app/work,
codex-home, repos/<pid>, test-repo) so host.py config needs
no change; only the host mount sources are per-project.
Static --docker-extra-mounts are replaced per task (not
appended), so a shared mount cannot leak back in.
- codex-home seeds ONLY the template's config.toml (relay
endpoint/provider names, no secret values); thread_history
is never copied (it holds other tasks' prompts/results).
test-repo is a per-project copy of the fixture (worktree
creation needs a writable repo; a shared writable fixture
would let one tenant poison the next task's base code).
- repos: repo_url checkouts go to the tenant dir and task
repo is set to the container path; no-repo_url tasks get
the tenant test-repo copy instead of the shared fixture.
- network: one Docker network per project (codex-<pid>,
created idempotently, already-exists is OK); network
creation failure fails the task instead of silently
falling back to the default network (a silent downgrade
would make isolation fail quietly).
- hardening for all Docker runs: --cap-drop=ALL and
--security-opt=no-new-privileges (non-root user was already
the case); verified live by a good-model task, not assumed.
Honest limit, kept in README: the inner codex sandbox is
still danger-full-access (host kernel blocks user namespaces,
bwrap unusable), so the boundary is container + mounts +
network, not the inner sandbox.
Tests: 7 tenant tests (seeding incl. no-history, per-project
mounts, network sanitization, per-call override, ensure
network ok/fail). Full suites pass.
26 lines
1.9 KiB
Desktop File
26 lines
1.9 KiB
Desktop File
[Unit]
|
||
Description=Codex cloud autoscaler (scales worker processes by queue depth)
|
||
After=codex-cloud-server.service
|
||
Wants=codex-cloud-server.service
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=codexsvc
|
||
Group=codexsvc
|
||
WorkingDirectory=/opt/codex-app-server
|
||
EnvironmentFile=/etc/codex-app-server/env
|
||
# scaler 自身参数在前,`--` 之后是传给 cloud.worker 的参数。
|
||
# 不要在这里写 --worker-id / --worker-capacity / --loop,
|
||
# scaler 会按实例强制追加;--db 只给 scaler,scaler 会补给 worker。
|
||
# 注意:启用 autoscaler 后必须停掉静态 codex-cloud-worker,
|
||
# 否则两边同时派任务(scaler 只管理 autoscale-* 实例)。
|
||
ExecStart=/usr/bin/python3 -m cloud.autoscaler --db /var/lib/codex-app-server/control.db --interval 30 --min-workers 1 --max-workers 2 --per-worker-capacity 2 --scale-down-after 3 --drain-timeout 60 --state-file /var/lib/codex-app-server/autoscaler-state.json --log-dir /var/lib/codex-app-server/autoscaler-logs -- --interval 10 --config /opt/codex-app-server/host-config-docker.json --executor docker --docker-image codex-app-server:latest --docker-user 1003:1004 --docker-cpus 2 --docker-memory 2g --docker-extra-mounts /opt/codex-app-server/work:/app/work,/var/lib/codex-app-server/codex-home:/var/lib/codex-app-server/codex-home,/var/lib/codex-app-server/repos:/var/lib/codex-app-server/repos,/var/lib/codex-app-server/test-repo:/var/lib/codex-app-server/test-repo --repos-dir /var/lib/codex-app-server/repos --tenant-isolation --tenants-root /var/lib/codex-app-server/tenants --tenant-template-home /var/lib/codex-app-server/codex-home --tenant-template-repo /var/lib/codex-app-server/test-repo
|
||
Restart=on-failure
|
||
RestartSec=10
|
||
# 停 scaler 时要等它把 worker 优雅排空(drain 60 秒 + 缓冲),
|
||
# 默认 90 秒不够,放到 180 秒;超时后 systemd 再 SIGKILL。
|
||
TimeoutStopSec=180
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|