# 在 Docker 沙盒中运行 NanoClaw(手动设置) 本指南介绍如何从头开始在 [Docker 沙盒](https://docs.docker.com/ai/sandboxes/)中设置 NanoClaw——无需安装脚本,无需预构建的 fork。你将克隆上游仓库,应用必要的补丁,并在完全的 hypervisor 级别隔离下运行 agent(智能体)。 ## 架构 ``` 宿主机 (macOS / Windows WSL) └── Docker 沙盒 (带隔离内核的微 VM) ├── NanoClaw 进程 (Node.js) │ ├── 频道适配器 (WhatsApp, Telegram 等) │ └── 容器启动器 → 嵌套 Docker 守护进程 └── Docker-in-Docker └── nanoclaw-agent 容器 └── Claude Agent SDK ``` 每个 agent 在自己的容器中运行,位于一个与你的宿主机完全隔离的微 VM 内。两层隔离:每个 agent 的容器 + VM 边界。 沙盒在 `host.docker.internal:3128` 提供一个 MITM 代理,处理网络访问并自动注入你的 Anthropic API 密钥。 > **注意:** 本指南基于在 macOS(Apple Silicon)上使用 WhatsApp 验证过的设置。其他频道(Telegram、Slack 等)和环境(Windows WSL)可能需要针对其特定 HTTP/WebSocket 客户端添加额外的代理补丁。核心补丁(容器运行器、凭据代理、Dockerfile)普遍适用——频道特定的代理配置各有不同。 ## 前提条件 - **Docker Desktop v4.40+** 支持沙盒 - **Anthropic API 密钥**(沙盒代理管理注入) - 对于 **Telegram**:来自 [@BotFather](https://t.me/BotFather) 的 bot token 和你的 chat ID - 对于 **WhatsApp**:一部安装了 WhatsApp 的手机 验证沙盒支持: ```bash docker sandbox version ``` ## 步骤 1:创建沙盒 在你的宿主机上: ```bash # 创建工作区目录 mkdir -p ~/nanoclaw-workspace # 创建带工作区挂载的 shell 沙盒 docker sandbox create shell ~/nanoclaw-workspace ``` 如果你使用 WhatsApp,配置代理绕过,使 WhatsApp 的 Noise 协议不被 MITM 检查: ```bash docker sandbox network proxy shell-nanoclaw-workspace \ --bypass-host web.whatsapp.com \ --bypass-host "*.whatsapp.com" \ --bypass-host "*.whatsapp.net" ``` Telegram 不需要代理绕过。 进入沙盒: ```bash docker sandbox run shell-nanoclaw-workspace ``` ## 步骤 2:安装前提依赖 在沙盒内: ```bash sudo apt-get update && sudo apt-get install -y build-essential python3 npm config set strict-ssl false ``` ## 步骤 3:克隆并安装 NanoClaw NanoClaw 必须位于工作区目录内——Docker-in-Docker 只能从共享的工作区路径进行 bind-mount。 ```bash # 先克隆到家目录(virtiofs 可能在克隆期间损坏 git pack 文件) cd ~ git clone https://github.com/nanocoai/nanoclaw.git # 替换为你的工作区路径(你传递给 `docker sandbox create` 的宿主机路径) WORKSPACE=/Users/you/nanoclaw-workspace # 移入工作区,以便 DinD 挂载生效 mv nanoclaw "$WORKSPACE/nanoclaw" cd "$WORKSPACE/nanoclaw" # 安装依赖 pnpm install pnpm install https-proxy-agent ``` ## 步骤 4:应用代理和沙盒补丁 NanoClaw 需要多个补丁才能在 Docker 沙盒内工作。这些补丁处理代理路由、CA 证书和 Docker-in-Docker 挂载限制。 ### 4a. Dockerfile——用于容器镜像构建的代理参数 `sandbox 内的 docker build` 会因为沙盒的 MITM 代理提供自己的证书而出现 `SELF_SIGNED_CERT_IN_CHAIN` 失败。向 `container/Dockerfile` 添加代理构建参数: 在 `FROM` 行之后添加这些行: ```dockerfile # 接受代理构建参数 ARG http_proxy ARG https_proxy ARG no_proxy ARG NODE_EXTRA_CA_CERTS ARG npm_config_strict_ssl=true RUN npm config set strict-ssl ${npm_config_strict_ssl} ``` 并在 `RUN pnpm install` 行之后: ```dockerfile RUN npm config set strict-ssl true ``` ### 4b. 构建脚本——转发代理参数 补丁 `container/build.sh`,将代理环境变量传递给 `docker build`: 在 `docker build` 命令中添加这些 `--build-arg` 标志: ```bash --build-arg http_proxy="${http_proxy:-$HTTP_PROXY}" \ --build-arg https_proxy="${https_proxy:-$HTTPS_PROXY}" \ --build-arg no_proxy="${no_proxy:-$NO_PROXY}" \ --build-arg npm_config_strict_ssl=false \ ``` ### 4c. 容器运行器——代理转发、CA 证书挂载、/dev/null 修复 对 `src/container-runner.ts` 的三处更改: **替换 `/dev/null` 影子挂载。** 沙盒拒绝 `/dev/null` bind mount。找到 `.env` 被影子挂载到 `/dev/null` 的位置,将其替换为一个空文件: ```typescript // 创建一个空文件来影子 .env(Docker 沙盒拒绝 /dev/null 挂载) const emptyEnvPath = path.join(DATA_DIR, 'empty-env'); if (!fs.existsSync(emptyEnvPath)) fs.writeFileSync(emptyEnvPath, ''); // 在挂载中使用 emptyEnvPath 代替 '/dev/null' ``` **将代理环境变量转发**到生成的 agent 容器。为 `HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY` 及其小写变体添加 `-e` 标志。 **挂载 CA 证书。** 如果设置了 `NODE_EXTRA_CA_CERTS` 或 `SSL_CERT_FILE`,将证书复制到项目目录并挂载到 agent 容器中: ```typescript const caCertSrc = process.env.NODE_EXTRA_CA_CERTS || process.env.SSL_CERT_FILE; if (caCertSrc) { const certDir = path.join(DATA_DIR, 'ca-cert'); fs.mkdirSync(certDir, { recursive: true }); fs.copyFileSync(caCertSrc, path.join(certDir, 'proxy-ca.crt')); // 挂载:certDir -> /workspace/ca-cert(只读) // 在容器中设置 NODE_EXTRA_CA_CERTS=/workspace/ca-cert/proxy-ca.crt } ``` ### 4d. 容器运行时——防止自我终止 在 `src/container-runtime.ts` 中,`cleanupOrphans()` 函数通过 `nanoclaw-` 前缀匹配容器。在沙盒内,沙盒容器本身可能匹配(例如 `nanoclaw-docker-sandbox`)。过滤掉当前主机名: ```typescript // 在 cleanupOrphans() 中,从要停止的容器列表中过滤掉 os.hostname() ``` ### 4e. 凭据代理——通过 MITM 代理路由 在 `src/credential-proxy.ts` 中,上游 API 请求需要通过沙盒代理。向出站请求添加 `HttpsProxyAgent`: ```typescript import { HttpsProxyAgent } from 'https-proxy-agent'; const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy; const upstreamAgent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined; // 将 upstreamAgent 传递给 https.request() 选项 ``` ### 4f. 安装脚本——代理构建参数 补丁 `setup/container.ts`,传递与 `build.sh`(步骤 4b)相同的代理 `--build-arg` 标志。 ## 步骤 5:构建 ```bash pnpm run build bash container/build.sh ``` ## 步骤 6:添加频道 ### Telegram ```bash # 应用 Telegram 技能 pnpm exec tsx scripts/apply-skill.ts .claude/skills/add-telegram # 应用技能后重建 pnpm run build # 配置 .env cat > .env << EOF TELEGRAM_BOT_TOKEN= ASSISTANT_NAME=nanoclaw ANTHROPIC_API_KEY=proxy-managed EOF mkdir -p data/env && cp .env data/env/env # 注册你的聊天 pnpm exec tsx setup/index.ts --step register \ --jid "tg:" \ --name "My Chat" \ --trigger "@nanoclaw" \ --folder "telegram_main" \ --channel telegram \ --assistant-name "nanoclaw" \ --is-main \ --no-trigger-required ``` **查找你的 chat ID:** 向你的 bot 发送任意消息,然后: ```bash curl -s --proxy $HTTPS_PROXY "https://api.telegram.org/bot/getUpdates" | python3 -m json.tool ``` **群组中的 Telegram:** 在 @BotFather 中禁用群组隐私(`/mybots` > Bot Settings > Group Privacy > Turn off),然后移除并重新添加 bot。 **重要提示:** 如果 Telegram 技能创建了 `src/channels/telegram.ts`,你需要为代理支持打补丁。添加一个 `HttpsProxyAgent` 并通过 `baseFetchConfig.agent` 将其传递给 grammy 的 `Bot` 构造函数。然后重建。 ### WhatsApp 确保你已先在[步骤 1](#步骤-1创建沙盒)中配置了代理绕过。 ```bash # 应用 WhatsApp 技能 pnpm exec tsx scripts/apply-skill.ts .claude/skills/add-whatsapp # 重建 pnpm run build # 配置 .env cat > .env << EOF ASSISTANT_NAME=nanoclaw ANTHROPIC_API_KEY=proxy-managed EOF mkdir -p data/env && cp .env data/env/env # 认证(选择一种): # QR 码——用 WhatsApp 相机扫描: pnpm exec tsx src/whatsapp-auth.ts # 或配对码——在 WhatsApp > 已关联设备 > 通过电话号码关联中输入代码: pnpm exec tsx src/whatsapp-auth.ts --pairing-code --phone # 注册你的聊天(JID = 你的电话号码 + @s.whatsapp.net) pnpm exec tsx setup/index.ts --step register \ --jid "@s.whatsapp.net" \ --name "My Chat" \ --trigger "@nanoclaw" \ --folder "whatsapp_main" \ --channel whatsapp \ --assistant-name "nanoclaw" \ --is-main \ --no-trigger-required ``` **重要提示:** WhatsApp 技能文件(`src/channels/whatsapp.ts` 和 `src/whatsapp-auth.ts`)也需要代理补丁——为 WebSocket 连接添加 `HttpsProxyAgent` 以及一个支持代理的版本获取。然后重建。 ### 两个频道 应用两个技能,为两者打代理补丁,合并 `.env` 变量,并分别注册每个聊天。 ## 步骤 7:运行 ```bash pnpm start ``` 你不需要手动设置 `ANTHROPIC_API_KEY`。沙盒代理拦截请求,并自动将 `proxy-managed` 替换为你的真实密钥。 ## 网络细节 ### 代理如何工作 沙盒的所有流量通过宿主机代理路由,地址为 `host.docker.internal:3128`: ``` Agent 容器 → DinD 网桥 → 沙盒 VM → host.docker.internal:3128 → 宿主机代理 → api.anthropic.com ``` **"绕过"并不意味着流量跳过代理。** 它意味着代理在不做 MITM 检查的情况下传递流量。Node.js 不会自动使用 `HTTP_PROXY` 环境变量——你需要在每个 HTTP/WebSocket 客户端中显式配置 `HttpsProxyAgent`。 ### DinD 挂载的共享路径 只有工作区目录可用于 Docker-in-Docker bind mount。工作区之外的路径会失败并显示"path not shared": - `/dev/null` → 改为项目目录中的空文件 - `/usr/local/share/ca-certificates/` → 将证书复制到项目目录 - `/home/agent/` → 改为克隆到工作区 ### Git clone 和 virtiofs 工作区通过 virtiofs 挂载。Git 的 pack 文件处理在 virtiofs 上可能在 clone 期间损坏。解决方法:先 clone 到 `/home/agent`,然后 `mv` 到工作区。 ## 故障排查 ### pnpm install 失败并出现 SELF_SIGNED_CERT_IN_CHAIN ```bash npm config set strict-ssl false ``` ### 容器构建失败并出现代理错误 ```bash docker build \ --build-arg http_proxy=$http_proxy \ --build-arg https_proxy=$https_proxy \ -t nanoclaw-agent:latest container/ ``` ### Agent 容器失败并出现 "path not shared" 所有 bind-mounted 路径必须在工作区目录下。检查: - NanoClaw 是否克隆到了工作区?(不是 `/home/agent/`) - CA 证书是否复制到了项目根目录? - 空的 `.env` 影子文件是否已创建? ### Agent 容器无法访问 Anthropic API 验证代理环境变量是否转发到了 agent 容器。检查容器日志中的 `HTTP_PROXY=http://host.docker.internal:3128`。 ### WhatsApp 错误 405 版本获取返回了过时的版本。确保已应用支持代理的 `fetchWaVersionViaProxy` 补丁——它通过 `HttpsProxyAgent` 获取 `sw.js` 并解析 `client_revision`。 ### WhatsApp 立即显示 "Connection failed" 代理绕过未配置。从**宿主机**运行: ```bash docker sandbox network proxy \ --bypass-host web.whatsapp.com \ --bypass-host "*.whatsapp.com" \ --bypass-host "*.whatsapp.net" ``` ### Telegram bot 不接收消息 1. 检查 grammy 代理补丁已应用(在 `src/channels/telegram.ts` 中查找 `HttpsProxyAgent`) 2. 如果在群组中使用,检查 @BotFather 中群组隐私已禁用 ### Git clone 失败并出现 "inflate: data stream error" 先 clone 到非工作区路径,然后移动: ```bash cd ~ && git clone https://github.com/nanocoai/nanoclaw.git && mv nanoclaw /path/to/workspace/nanoclaw ``` ### WhatsApp QR 码不显示 在沙盒内以交互方式运行认证命令(不通过 `docker sandbox exec` 管道): ```bash docker sandbox run shell-nanoclaw-workspace # 然后在沙盒内: pnpm exec tsx src/whatsapp-auth.ts ```