Files
lobe-sandbox-backend/.memory/project.md
kang 7df50e0dc3 phase 5 prep: backfill-users.ts + deployment runbook in .memory
- scripts/backfill-users.ts: 扫 PG users 表,对每人幂等调 orchestrator POST /users
- orchestrator/package.json: 加 postgres 依赖
- .memory/project.md: Phase 3/4 完成状态,Phase 5 上线 checklist

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:17:01 +08:00

140 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lobe Sandbox Backend — 自托管沙箱后端
## What
替换 LobeChat 对 `market.lobehub.com` 云沙箱的依赖,改为公司 VPS(2.24.28.41)本地自托管的 **per-user Incus LXC** 工作区。
- **服务对象**:`ai.milejoy.com`(公司主)+ `lobehub.kang-kang.com`(个人备份,同 backend)
- **隔离粒度**:每个 LobeChat 用户一个 Incus 容器,跨对话共享文件系统(概念对标 ChatGPT Code Interpreter)
- **挂钩点**:better-auth `databaseHooks.user.create.after`(在 LobeChat 项目里改)
## Why 这个设计
- 三条路(a 自建 / b 走 LobeHub 白名单付费 / c 让员工个人账号 OAuth 授权),**a 省钱 + 数据不出公司 + 掌控力**
- 沙箱技术选 Incus LXC(不是 Docker / Firecracker / Daytona): 长命工作区 = 系统容器,公司已有 Incus 栈(hermes-box 同宿主),无新技术栈,CoW 省磁盘
- btrfs 存储驱动(不是 ZFS):Debian 13 内核原生支持,`apt install btrfs-progs` 零重启,CoW 效果等价
## 架构
```
LobeChat (Coolify)
↓ HTTP 内网(X-Sandbox-Secret HMAC)
Sandbox Orchestrator(Bun 写的 HTTP 服务,systemd 跑在 VPS 宿主机)
↓ incus CLI(unix socket)
Incus 宿主(VPS 2.24.28.41)
├── lobe-sandbox project
├── lobe-sandbox-pool(btrfs 200GiB,CoW)
├── sandbox-default profile(2GB RAM / 2 CPU / 10GiB disk)
└── sb-<userId> 容器 × N(默认 stopped,调用时起)
↓ exportFile
MinIO(192.168.2.221:9000)
```
## 生命周期
| 阶段 | 触发 | 动作 |
|---|---|---|
| 创建 | better-auth user.create | `incus copy lobe-sandbox-base → sb-<userId>`,不启动 |
| 激活 | 首次 tool 调用 | `incus start` 2-3 秒内可用 |
| 空闲 | 30min 无调用 | `incus stop`,rootfs 保留 |
| 删除 | 手动 / admin | 备份到 MinIO + `incus delete` |
## 部署目标
- VPS: `root@2.24.28.41`
- Orchestrator 端口: `127.0.0.1:8700`(内网,不对外暴露)
- Base 镜像: `lobe-sandbox-base`(Debian 13 + Python 3.13 + Node 20 + Bun + uv + 中文字体)
- Orchestrator 部署路径: `/opt/lobe-sandbox/orchestrator`(TBD)
- systemd unit: `sandbox-orchestrator.service`
## 关键决策 & 坑点
### 1. 存储池用 btrfs 不是 ZFS
原方案 ZFS,实发现 ZFS 在这台 Ubuntu 要 DKMS 编译,可能触发重启(用户规则:不频繁重启服务)。改用 btrfs,内核原生,一键 `apt install btrfs-progs` 零重启,CoW 效果一样。
### 2. ⚠️ UFW 挡了 incusbr0 的 DHCP/DNS(大坑)
**现象**:新容器用 `DHCP=true`,`DHCPDISCOVER` 发出去但 `No DHCPOFFERS received`,只拿到 IPv6 SLAAC。
**原因**:这台 VPS `iptables INPUT 策略=DROP`(UFW 默认)+ Docker 的 FORWARD chain。默认没放行 incusbr0 的 udp/67、udp/53、tcp/53,也没放 FORWARD。nftables 里 Incus 自己加的规则不生效(iptables 和 nft 都跑,都要通过)。
**绕开**:hermes-box 用**静态 IP**(`Address=10.146.223.10/24`),所以没触发这问题。
**修复**(已落盘到 `/etc/iptables/rules.v4`,通过 netfilter-persistent 持久):
```
iptables -I INPUT -i incusbr0 -p udp --dport 67 -j ACCEPT
iptables -I INPUT -i incusbr0 -p udp --dport 53 -j ACCEPT
iptables -I INPUT -i incusbr0 -p tcp --dport 53 -j ACCEPT
iptables -I FORWARD -i incusbr0 -j ACCEPT
iptables -I FORWARD -o incusbr0 -j ACCEPT
```
修复后 DHCP 立即可用(容器拿到 `10.146.223.248`)。本地 `scripts/host-init.sh` 里写了这段幂等实现,新环境部署自动修。
### 3. Python 版本
Debian 13 默认 Python 3.13(不是计划的 3.12)。不影响功能,更新。
### 4. 沙箱是 per-user 不是 per-topic
对话跨 topic 共享工作区,对齐 ChatGPT Code Interpreter 的心智模型。
### 5. 基座"什么都不装"原则
- 只装必需的运行时(Python/Node/Bun/uv)+ 系统工具(git/curl/build-essential/中文字体)
- **不预装** pandas/numpy/torch/playwright/ffmpeg/libreoffice
- LLM 按需 `uv pip install xxx` — 后续加本地 PyPI 缓存镜像让它快到秒级(尚未做,列在 TODO)
## 进度(2026-04-19 更新)
- ✅ Phase 0 Pre-flight(VPS 摸底:31GB RAM / 387GB 磁盘 / Incus 6.0.0 / hermes-box 已跑)
- ✅ Phase 1 宿主初始化(btrfs pool + project + profile + **UFW iptables 修复并持久化**)
- ✅ Phase 2 base 镜像 `lobe-sandbox-base` 已发布(500MiB,fingerprint `de8d9b6b73c4`)
- Debian 13 trixie + Python 3.13.5 + Node 20.20.2 + Bun 1.3.12 + uv 0.11.7 + git/ripgrep/fd/bat + 中文字体 + zh_CN locale
- **两个坑踩过并写进脚本**:
1. `INSTALL_DIR` 装 uv 无效,要用 `UV_INSTALL_DIR` 环境变量
2. `nohup bash -s <<EOF &` 脱不开 SSH,后续全改 `systemd-run --unit=xxx --slice=system.slice`
- ✅ Phase 3 Orchestrator(Bun + Hono,已部署 VPS systemd)
- 服务:`sandbox-orchestrator.service` 监听 `127.0.0.1:8700`
- 配置:`/etc/lobe-sandbox/orchestrator.env`(含 SANDBOX_ORCH_SECRET)
- 端到端验证:POST /users → executeCode → runCommand + uv pip install numpy → writeLocalFile → listLocalFiles 全 OK
- 已实现 13 工具(executeCode/runCommand/readLocalFile/writeLocalFile/editLocalFile/listLocalFiles/moveLocalFiles/renameLocalFile/searchLocalFiles/grepContent/globLocalFiles + ensureRunning 逻辑 + 30min idle reaper + HMAC auth + S3 export 占位)
- **长驻命令(getCommandOutput/killCommand)留到 v2**,当前 runCommand 是同步 2min 超时
- ✅ Phase 4 LobeChat 侧接入代码已提交(分支 `feat/self-hosted-sandbox`,commit `12b53e90b1`)
- `src/envs/app.ts` 新增 `SANDBOX_BACKEND_URL` + `SANDBOX_BACKEND_SECRET`
- `src/server/services/sandbox/localSandbox.ts` 新 LocalSandboxService
- `src/server/services/toolExecution/serverRuntimes/cloudSandbox.ts` env 分叉
- `src/libs/better-auth/define-config.ts` user.create.after 追加 provision(fire-and-forget)
- **注意**: main 分支 pre-commit 会跑全量 tsc 撑爆内存 + 碰到别人的 pre-existing TS 错误,所以必须在 feature branch 上 commit
- ⏸ Phase 5 待做(上线):
- [ ] 确定 LobeChat docker 容器访问 orchestrator 的网络路径(host.docker.internal / docker0 网关 172.17.0.1 / 绑 0.0.0.0 + UFW 限制)
- [ ] merge `feat/self-hosted-sandbox` 到 main(需先解决 main 上别人的 pre-existing TS 错误,或单独 deploy 这个 branch)
- [ ] Coolify 里 LobeChat 容器加 `SANDBOX_BACKEND_URL` + `SANDBOX_BACKEND_SECRET` 环境变量
- [ ] 重启 LobeChat(一次性,不要频繁重启)
- [ ]`scripts/backfill-users.ts` 给存量用户补建沙箱
- [ ] exportFile 接入:当前 `S3_ENDPOINT=192.168.2.221:9000` 是内网,VPS 够不着 → 改用 LobeChat 同机上的 RustFS(`lobemile2026/RustFS@2026MileSecure`)
- [ ] 端到端 smoke:用户 ai.milejoy.com 发"写个 Python 打印 hello" → 查 orchestrator 日志命中 → 不是 market.lobehub.com
## 关键路径速查
### 本地
- 项目根:`/Users/kangwan/Projects/business/20260418-lobe-sandbox-backend/`
- 宿主初始化脚本:`scripts/host-init.sh`
- base 镜像构建脚本:`images/base/build.sh`
### VPS(2.24.28.41)
- 构建日志:`/var/log/build-sandbox-base.log`(第一次,卡 UFW)
- 续跑日志:`/var/log/build-sandbox-base.log.2`(第二次,SSH 断开死)
- 构建脚本副本:`/root/build-sandbox-base.sh`
- Incus 数据:`/var/lib/incus/`
- btrfs pool 文件:`/var/lib/incus/disks/lobe-sandbox-pool.img`(200GiB 稀疏)
### Incus 操作(切到 lobe-sandbox project)
```bash
incus list --project lobe-sandbox
incus profile show sandbox-default --project lobe-sandbox
incus image list --project lobe-sandbox
```
## 规划中的 / 待决
- PyPI 本地缓存镜像(nginx 反代清华源)—— 让 `uv pip install` 秒级
- npm 本地缓存(Verdaccio)
- exportFile 对接 MinIO 的 presigned URL 上传流
- 用户删除的手动清理脚本 + 定时 GC(better-auth 没有 user.delete hook)