BitMiracle-AI/Dormice

GitHub: BitMiracle-AI/Dormice

Dormice 是一个自托管的、E2B 兼容的 AI agent 沙箱平台,让沙箱在单台机器上永久存活且空闲零成本。

Stars: 279 | Forks: 26

# 睡鼠 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/BitMiracle-AI/Dormice/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) **AI agent 沙箱的 SQLite** —— 一个自托管的 AI agent 沙箱平台。一台机器,永久存活的沙箱,空闲成本为零。 ## 核心理念 云端沙箱平台会对沙箱存在的每一秒收费,因此它们的沙箱都是一次性的。Dormice 颠覆了这一点:你在自己已经付费的机器上运行它,沙箱就是**永久的** —— 它们闲置的时间越长,保持运行的成本就越低。 - **`acquireSandbox(userKey)` 就是全部的心智模型。** 幂等操作:相同的 key 总是会回到相同的沙箱,无论它之前处于什么状态。没有沙箱 → 创建;已冻结 → 唤醒;已停止 → 启动;已归档 → 恢复。 - **空闲即是免费。** 沙箱会自动冷却 —— `active → frozen → stopped → archived` —— 逐级降级,而任何 acquire 操作都能将它们恢复。在真实硬件上测量:冻结操作可将持有 1 GiB 内存的空闲沙箱降至约 5 MiB 的常驻内存,而唤醒它只需约 50 毫秒。 - **像单一二进制文件一样部署。** 一个守护进程,一个 SQLite 账本,一个端口。不需要 Kubernetes,也不需要外部数据库。 - **[兼容 E2B](#e2b-compatibility)**:只需更改两个 URL,官方 `e2b` SDK 即可针对 Dormice 工作。 ## 安装 在纯净的 Ubuntu/Debian x86_64 主机上(以 root 身份)运行一条命令: ``` curl -fsSL https://raw.githubusercontent.com/BitMiracle-AI/Dormice/main/deploy/install.sh | bash ``` 如果到常规软件源的网络连接较慢,可以添加 `-s -- --mirror cn`。 安装程序是幂等的 —— 重新运行它会升级代码并修复偏差,而且永远不会轮换你的 API token。它最后会运行 `dor doctor`,这是一系列只读检查 —— 其中三个会启动一个真实的 gVisor 容器 —— 以此决定安装是否真正成功;`dor doctor` 也可以随时单独重新运行。 ## 快速开始 `@dormice/sdk` 是原生 TypeScript 客户端。(尚未发布到 npm —— 首个版本正在排队发布;在本仓库中,`pnpm build` 可以生成它。) ``` import { Dormice } from '@dormice/sdk'; const client = new Dormice({ endpoint: 'http://127.0.0.1:3676', token: process.env.DORMICE_API_TOKEN!, }); // One key, one sandbox — created, woken or restarted as needed. // stopAfterSeconds: null makes it a resident agent: it may freeze when // idle (~50 ms to wake) but never cold-starts. await client.acquireSandbox('my-agent', { policy: { stopAfterSeconds: null } }); const result = await client.execCommand('my-agent', 'python3 -c "print(6 * 7)"'); console.log(result.exitCode, result.stdout); // 0 42 await client.writeFiles('my-agent', [ { path: 'notes.txt', content: 'survives freeze and stop' }, ]); await client.destroySandbox('my-agent'); // destroy — the only verb that loses data ``` 通信使用的是纯粹的 HTTP RPC(`POST /acquireSandbox`、`POST /execCommand` 等),因此在 SDK 无法触达的地方 `curl` 同样有效,而 `dor` CLI 负责运维端的操作:`dor sandbox ls / exec / push / pull / rebuild / destroy`,以及 `dor doctor`。完整的原生接口文档位于 [`packages/sdk`](packages/sdk/README.md),这些流程的可运行版本位于 [`examples/`](examples/)。 ## Agent 技能 Dormice 的用户是 agent,因此手册以一种 agent 可以安装的形式提供 —— 即开放 [Agent Skills](https://skills.sh) 格式中的技能: ``` npx skills add BitMiracle-AI/Dormice ``` 只需一个文件 [`skills/dormice/SKILL.md`](skills/dormice/SKILL.md),就能教编程 agent 掌握全部接口:如何连接,如何在 E2B SDK / 原生 API / CLI 之间进行选择,如何运行命令、移动文件以及设置生命周期策略。文档站点同样对 AI 友好 —— 其构建过程会生成 `/llms.txt`、`/llms-full.txt`,以及每个文档页面对应的纯净 markdown `.md` 副本。 ## E2B 兼容性 守护进程在两个前缀(`/e2b/api`、`/e2b/envd`)上使用 E2B 协议进行通信。**官方 `e2b` 包** —— 未经修改,直接来自 npm —— 只需更改两个 URL 和一个 API-key 前缀即可针对 Dormice 运行;迁移应用程序仅仅是更改配置,而不需要修改代码: ``` import { Sandbox } from 'e2b'; const sbx = await Sandbox.create({ apiKey: `e2b_${process.env.DORMICE_API_TOKEN}`, apiUrl: 'http://127.0.0.1:3676/e2b/api', sandboxUrl: 'http://127.0.0.1:3676/e2b/envd', }); ``` 下方的所有内容均由黑盒 e2e 测试套件*通过官方包*进行验证,并且已经在真实的 Docker + gVisor 守护进程上测试通过: | 接口 | 说明 | | --- | --- | | `Sandbox.create` / `connect` / `kill` / `list` | `list` 按状态和元数据进行过滤,并支持分页 | | 超时 | `timeoutMs` 和 `setTimeout()` 是真实的绝对截止时间;`lifecycle: { onTimeout: 'pause' }` 会挂起沙箱而不是销毁它 | | `pause()` / 恢复 | 显式暂停;`connect` 会将其恢复,文件保持完好 | | `commands.run` | 实时流式传输(`onStdout` / `onStderr`),后台运行 + `connect` / `disconnect`、`sendStdin`、`kill`、`list`,支持每个命令独立的 `envs`、`user: 'root'`;非零退出码会抛出 SDK 自身的 `CommandExitError` | | `pty.*` | `create` / `sendInput` / `resize` / `kill` —— 真正的交互式 bash | | `files` 读取 / 写入 | 文本或字节流式传输,没有人为的大小限制 —— 磁盘配额即是上限 | | `uploadUrl()` / `downloadUrl()` | 签名 URL:强制校验过期时间,被篡改的签名会返回 401 | | `files.list` / `exists` / `makeDir` / `rename` / `remove` | 类型化的错误符合 SDK 的预期 | | `files.watchDir` | 流式事件,以及 Python 同步 SDK 使用的轮询监视器 API | | `getHost(port)` | 带有流量唤醒的端口代理(设置 `DORMICE_SANDBOX_DOMAIN`) | | `getMetrics()` | 一次实时采样;观察永远不会唤醒已冻结的沙箱 | 与托管产品相比有意的差异: - **没有模板构建。** 模板是命名的镜像:使用 docker 构建一个镜像,通过 `dor template add` 注册它,`Sandbox.create('name')` 就会解析它(未注册的名称会如实返回 404)。托管产品的构建流水线(`e2b template build`)未被实现。 - **冻结会保留进程。** 已冻结沙箱的进程会在执行过程中暂停并恢复 —— 任何触碰都会在约 50 毫秒内唤醒沙箱。 - **永久性仍然是默认设置。** 通过 E2B 接口创建的沙箱会像 E2B 语义要求的那样获得真实的截止时间;截止时间永远不会强加给原生创建的沙箱。 - **`metadata.externalId`** 将 `Sandbox.create` 变成了 Dormice 的幂等 acquire:相同的 key 会返回相同的沙箱,而不是创建一个新的。 - **未实现**,并在网络通信中如实返回 `unimplemented` 的功能包括:`Process/StreamInput`(JS SDK 从不调用它)和基于 xattr 的文件元数据。守护进程报告的 envd 版本为 `0.6.1`,因此 SDK 本身会在客户端侧禁用这些功能。 ## Web 控制台 守护进程在 `http://127.0.0.1:3676/console` 提供了一个小型的 Web 控制台 —— 只需使用 API token 登录一次,它就会变成一个 httpOnly 会话 cookie;token 本身永远不会存储在页面可以读取的任何地方。控制台显示每个沙箱及其生命周期实时状态(与 SDK 看到的 `/listSandboxes` 相同),打开每个沙箱的详情视图,创建沙箱(同样的幂等 `acquire`,带有生命周期控制选项),释放它们,并且包含一个连接页面,其中提供了指向你自身端点的每个客户端(E2B SDK、原生 SDK、CLI)的复制粘贴代码片段。 守护进程仅监听 127.0.0.1,因此要从另一台机器访问它是你显式做出的选择,有两种方式: - **SSH 隧道**(私有,零设置): `ssh -L 3676:127.0.0.1:3676 root@host`,然后打开 `http://127.0.0.1:3676/console`。 - **反向代理**,同时代理控制台、API 和 E2B 接口 —— 例如 Caddy,一旦你给它分配了域名,它就会自动处理 TLS 证书: your-domain.example { reverse_proxy 127.0.0.1:3676 { flush_interval -1 } } `flush_interval -1` 很关键:流式命令输出是逐帧写入的,缓冲代理会在最后将其变成一大块数据输出。 任何暴露在 localhost 之外的内容都应该使用 HTTPS —— API token 和会话 cookie 会包含在每一个请求中。 ## 冷归档(S3,可选) 设置四个 `DORMICE_S3_*` 变量,空闲的沙箱就会迈出降级的最后一步:在停止一周后(可通过 `archiveAfterSeconds` 针对单个沙箱进行调节),磁盘数据将使用 `tar` + `zstd` 打包,发送到任何兼容 S3 的存储桶(AWS、Cloudflare R2、MinIO、处于 S3 兼容模式的阿里云 OSS),并在本地释放空间。下一次 `acquireSandbox` 会立即返回 `{ status: 'restoring', progress }`,并在磁盘数据恢复后切换为 `ready` —— 缓慢的唤醒是如实反馈的,绝不会是无声的卡死。 ``` DORMICE_S3_ENDPOINT=https://s3.example.com # full URL; MinIO speaks http DORMICE_S3_BUCKET=dormice-archive DORMICE_S3_ACCESS_KEY_ID=... DORMICE_S3_SECRET_ACCESS_KEY=... # DORMICE_S3_REGION=us-east-1 # DORMICE_S3_FORCE_PATH_STYLE=true # MinIO 需要此项 ``` 如果未设置,该功能将切实不存在:沙箱会永远停留在 `stopped` 状态,而要求归档的策略会被拒绝,而不是被静默存储。存储桶必须存在;主机需要安装 `zstd`(`install.sh` 会安装它,`dor doctor` 会检查它)。 ## 主机先决条件(docker 执行器) 守护进程本身可以在任何运行 Node 22+ 的地方运行,并为开发提供了一个内存中的模拟执行器。运行**真实的**沙箱需要一台如上所述准备好环境的 Linux 主机 —— `install.sh` 会自动完成所有工作,并且 `dor doctor` 会对其进行验证,但以下是背后的实际情况: - **Docker + gVisor (`runsc`)**,以及 root 权限(循环挂载,cgroup 写入)。 - **Swap,且 `vm.swappiness=100`。** 冻结过程会将空闲沙箱的内存挤入 swap;gVisor 将沙箱内存作为共享内存持有,在默认的 swappiness 下内核会拒绝将其交换出去 —— 在真实硬件上测量的结果是:在默认值下回收了 0 字节,在 100 时回收了 99.5%。请注意,一些云镜像(例如阿里云)默认配置了 `vm.swappiness=0`;请使用 `sysctl vm.swappiness` 检查*有效*值,而不是查看配置文件。 - **网络强化不是可选的。** 沙箱会运行不受信任的代码:阻止容器访问云元数据服务(`iptables -I DOCKER-USER -d 169.254.0.0/16 -j DROP`,并持久化保存)并禁用容器间通信(在 `daemon.json` 中设置 `"icc": false`)。守护进程被设计为仅绑定到 127.0.0.1,故意不提供控制开关;将其暴露出去是反向代理的工作。 - **一台机器,一个守护进程。** 守护进程通过其账本旁边的锁来强制执行这一点,如果账本与机器的实际情况不匹配,则拒绝启动。 ## 什么时候 Dormice 不是合适的工具 如果遇到以下情况,请选择其他工具: - **你需要集群。** 一台机器,一个守护进程,这是设计使然 —— 这正是其简单性的来源。多机器分片是未来的方向(schema 中已经包含了相关字段),而不是当前的功能。 - **你想要托管服务。** 没有任何托管内容,也没有 SLA。那是 E2B 的产品,并且它在这方面做得很好。 - **你的威胁模型要求硬件虚拟化。** 沙箱使用的是 Docker + gVisor —— 一个用户态内核,这是刻意为之的选择:冻结要求沙箱必须是进程,而“随处安装”排除了对 KVM 的要求。如果只有 Firecracker 级别的 VM 隔离才能满足你,那么这种权衡不适合你。 - **你的工作负载不是 Linux。** 沙箱是 Linux 容器。 ## 仓库布局 pnpm monorepo: | 路径 | 它是什么 | | --- | --- | | `packages/shared` | 协议 schema (zod) —— 网络类型的唯一事实来源 | | `packages/server` | 守护进程:Fastify + SQLite 账本 + 生命周期引擎 | | `packages/sdk` | `@dormice/sdk` —— 针对原生 API 的 TypeScript 客户端 | | `packages/cli` | `dormice` 命令行工具(简称为 `dor`) | | `packages/console` | Web控制台:React SPA,由守护进程在 `/console` 路径提供服务 | | `e2e` | 黑盒测试套件:启动构建好的守护进程,通过网络对其进行驱动测试 | | `examples` | 可运行的演示:原生 SDK、官方 `e2b` 包、常驻 agent | ## 开发 ``` pnpm install pnpm build # the e2e suite boots the built daemon, so build comes first pnpm typecheck pnpm lint pnpm test ``` ## 许可证 [Apache-2.0](LICENSE) ## 商标声明 E2B 是其各自所有者的商标。Dormice 是一个独立项目,与 E2B 没有关联、未经其认可或赞助。对 `e2b` 包的引用仅描述了与其已发布 API 的互操作性。
标签:AI基础设施, MITM代理, 容器技术, 沙箱环境, 自动化攻击, 自托管, 请求拦截, 资源调度