computerd 注入式服务Injected Service完全指南沙箱内工作区守护进程的架构、部署与运维【免费下载链接】computerGive your agent a computer 项目地址: https://gitcode.com/GitHub_Trending/computer1/computer本文基于仓库文档 docs/07_injected_service.md 展开并辅以 packages/computerd 与 packages/computer/src/backends/container 中的源码、配置与测试作为实现佐证。injected service注入式服务是本项目沙箱体系中的工作区守护进程它以单一 Node SEA 二进制computerd的形式注入到沙箱容器内部统一承载 FUSE 挂载、容器内 VFS、exec 运行器以及 Durable ObjectDO通过 capnweb 与之通信的 RPC 端点。读完本文你将掌握computerd的构建与分发方式、容器镜像内的安装配方、完整的 HTTP/WS 路由表面、Cloudflare Containers 下的启动时序与环境变量语义以及它的故障处理与生命周期边界。注入式服务是什么computerd在沙箱中的位置从架构上看一个工作区Workspace同时存在于两侧DO 侧Durable Object 持有 SQLite 支撑的 VFS是跨重启的权威数据源容器侧沙箱容器内跑着computerd它挂载 FUSE 文件系统、维护一份内存 VFS并向外暴露 RPC 供 DO 通信。computerd正是容器侧的核心进程。原文档明确列出它的五项职责FUSE 挂载在MOUNT_POINT默认/workspace挂载容器内 VFS使得容器里任何工具node、shell、编译器看到的目录树与 DO 看到的是同一棵、路径完全一致。后端由FUSE_MOUNT环境变量选择默认auto。脏写跟踪Dirty tracking经 FUSE 写入的数据落入容器内 VFS 数据库宿主通过 capnweb 会话把修订拉回具体同步协议见 02. Sync Protocol。Exec运行 shell 命令并通过 capnweb 流式回传 stdout/stderr接口见 05. Runtime Interface。Apply接收 DO 推送的变更并写入 VFS同时抑制自身脏写跟踪避免删除操作反弹回去。健康检查暴露GET /health让宿主侧在建立 RPC 连接前先探测就绪状态。从代码看computerd的入口在 packages/computerd/src/cli/computerd.ts它解析环境变量、解析 FUSE 后端、创建 Node VFScreateNodeVirtualFileSystem、构造 Runner 与WorkspaceRPC服务最后启动 HTTP WebSocket 服务器。构建与分发Node SEA 单二进制computerd以 npm 包cloudflare/computerdbin 名computerd发布源码位于 packages/computerd其 package.json 中bin指向./dist/cli/computerd.cjs要求 Node.js 22。关键特性是它被打包成 Node Single Executable ApplicationSEA自包含二进制内嵌 Node 运行时、fuse-native预编译产物和libfuse作为 SEA 资产因此宿主镜像不需要再装 Node、npm install或package.json。构建命令npm run build:bin --workspace cloudflare/computerd # → artifacts/computerd/computerd-linux-x64 # → artifacts/computerd/computerd-macos-x64从 packages/computerd/README.md 可以看到 SEA 的生产流程scripts/build-bin.mjs先用 esbuild 打包 CLI再用node --experimental-sea-config生成 SEA blob下载目标平台的 Node 二进制并用postject注入macOS 目标还会剥离符号并做 ad-hoc 重签名。另外注意编译期可通过COMPUTERD_DEFAULT_PORT在 SEA 打包时替换内嵌的默认端口packages/computerd/src/cli/computerd.ts 中__COMPUTERD_BUILD_DEFAULT_PORT__的define替换tsc 纯编译时保留字面量45678运行时的PORT环境变量优先级仍高于编译期默认值。文档指出把端口做成构建期变量目前仍在路线图planned上。把computerd装进沙箱镜像Dockerfile 配方原文档给出的规范配方是examples/container/DockerfileFROM --platformlinux/amd64 debian:stable-slim RUN apt-get update \ apt-get install -y --no-install-recommends \ fuse3 libfuse2t64 ca-certificates \ rm -rf /var/lib/apt/lists/* COPY build/computerd-linux-x64 /usr/local/bin/computerd RUN chmod x /usr/local/bin/computerd ENV PORT8080 ENV MOUNT_POINT/workspace ENV FUSE_MOUNTauto EXPOSE 8080 ENTRYPOINT [/usr/local/bin/computerd]要点说明零 Node 依赖不需要 Node、npm install、package.jsonSEA 二进制已内嵌一切。libfuse本身已打进二进制作为 SEA 资产apt 安装的fuse3 libfuse2t64提供的是用户态工具和供宿主内核使用的/dev/fuse管道设施。EXPOSE 8080与 Cloudflare 后端固定的端口一致若在 Cloudflare Containers 之外运行可让PORT保持未设置默认45678或自选端口。补充仓库中实际的 examples/container/Dockerfile 在文档所述配方基础上进一步演进——它先从scratch单层的 GHCR 镜像ghcr.io/cloudflare/computer-computerd-linux-x64:0.2.1COPY出二进制并在 debian 运行层额外安装了curl gnupg git与 Node.js供容器内 workload 使用同时保持PORT8080、MOUNT_POINT/workspace、FUSE_MOUNTauto三组默认环境变量与ENTRYPOINT [/usr/local/bin/computerd]不变。这验证了同一个镜像两种用法的设计Cloudflare Containers 暴露/dev/fuse时走真实 FUSE 后端wrangler dev没有/dev/fuse时自动回退到用户态 shim。HTTP / WS 路由表面computerd监听单一端口默认45678Cloudflare 后端固定8080提供如下路由路由方法用途/healthGET、HEAD存活探针HTTP 服务器一绑定端口即返回200 ok\n。是唯一不需要共享密钥即可访问的路由。/__computerd/infoGET运行时信息FUSE 后端、挂载点、端口。/apiGET升级WebSocket capnweb 传输bootstrap stub 为WorkspaceRPC。只有精确路径才会升级无Upgrade头的请求返回400不支持的Sec-WebSocket-Version返回426并附上服务器支持的版本。/api/watermarksGET、HEAD同步修订号currentRev、pushRev、fetchCursor。与sync.watermarks()返回相同供不想持有会话、只想拿几个数字的调用方使用。/connectPOST让computerd主动外呼到调用方指定的端点并在该出站 WebSocket 上服务一个WorkspaceRPC会话。供 Cloudflare 后端使用。/GETBanner / 信息页。路由分层有明确语义/api是工作区表面会话本身 经它读取的一切/__computerd是守护进程内省所以运行时信息在/__computerd/info而修订号不在。capnweb bootstrap 接口是WorkspaceRPC定义于 packages/rpc拆分为sync与shell两个子 stub。源码级细节见 packages/computerd/src/cli/computerd.tsUNAUTHENTICATED_PATHS new Set([/health])之外的每个路由在路由分派之前就做鉴权检查未授权调用方连存在哪些路由都无法探测无密钥时整个表面开放harness 与本地运行依赖此行为。/api的 WebSocket 服务器启用了perMessageDeflateNode 侧客户端可协商压缩帧不宣告该扩展的客户端workerd、浏览器自动降级为普通帧。握手版本检查只对单个、服务器不认识的整数字面版本回答426附带Sec-WebSocket-Version: 13, 8缺失、重复node 会把13, 13合并或非整数都属于畸形握手交给ws回答400。额外的内省路由GET /__computerd/stats返回 DOFS 各表行数vfs_nodes、vfs_dirents、vfs_chunks、vfs_blobs、内联/blob 字节数、孤儿 blob 子集以及进程 RSS/heap/external 内存用于诊断长时间 install 时的存储增长GET /__computerd/stubs则在CAPNWEB_TRACK_STUBS1时返回 stub 快照。/api/watermarks位于方法守卫仅允许GET/HEAD之下因此不会被 POST/DELETE 打到其数据直接读自rpc.sync.watermarks()与会话内读数不会漂移。/connect被反转的 WebSocket 载体/connect是 Cloudflare Containers 拓扑的关键。POST /connect的 JSON body 形如{ base, health, api, healthTimeoutMs }宿主把端点每一部分都命名清楚base必须是http://或https://health/api必须是/开头的绝对路径且不能解析为独立地址computerd只负责拼装base health并轮询默认 30s、每 250ms 一次随后按base的 scheme 以ws:///wss://外呼base api并服务会话。守护进程不自行拼装任何路径宿主重命名自己的路由时无需同步发布新二进制。两个值得注意的实现细节isEndpointPath会拒绝以//开头的协议相对路径防止调用方把外呼重定向到 base 之外的地址。computerd在出站握手时也会带上Authorization: Bearer secret若设置了密钥因为任何本守护进程运行的命令都能触达宿主端点没有令牌就可能顶替工作区成为对端。重复POST /connect例如 DO 休眠后新化身启动会先关闭旧的出站会话upstreamSlot避免旧会话在容器生命周期内泄漏。启动序列Boot sequence与提供方无关的通用启动形状是三步按序执行启动二进制宿主侧工作区请沙箱提供方把computerd作为容器 entrypoint 拉起。轮询健康端点宿主持续HEAD /health直到返回200。注意/health由 HTTP 服务器接线socket 一绑定就回答200在启用 FUSE 的路径上挂载在listen之前完成因此/health应答时 FUSE 已就绪。FUSE_MOUNTnone时则完全没有 FUSE 步骤。打开 capnweb 会话宿主直接升级到/api或通过POST /connect让computerd外呼到自己控制的端点并在出站 socket 上服务会话。无论哪种方式bootstrap stub 都是WorkspaceRPC。Cloudflare Containers 的具体接线CloudflareContainerBackendpackages/computer/src/backends/container/cloudflare-container.ts按如下方式接线StartWorkspaceContainerAPI.start({ env, enableInternet })直达 Cloudflare Containers API——不是cloudflare/sandboxSDK没有进程名注册表没有startProcess/getProcess也没有node /app/...命令容器ENTRYPOINT直接跑computerd。containerEnv固定PORT8080让镜像自带的FUSE_MOUNT通常为auto生效API 还会追加RPC_CLIENT_SECRET。运行中容器的环境与联网标志都不可改因此启动记录这两项已运行的容器只有在匹配时才被采纳adoption否则重新拉起——这正是热池不会把为别的用途配置的容器交给工作区的原因在 API 之外启动的容器没有记录也会被重新拉起。接线 egresscontainer.interceptOutboundHttp(egressHost, egress)把容器在egressHost上的出站 HTTP 路由回 DO 控制的 WorkerFetcher。探测container.getTcpPort(containerPort).fetch(/health, { method: HEAD })反复执行直至200。健康探测的共享实现见 packages/computer/src/backends/container/health-probe.ts——单次 HEAD、带AbortSignal.timeout的每探针超时、不做重试让启动与租约告警各自组合自己的退避预算。反转 WebSocketDO 先武装升级槽#armUpgrade再对容器POST /connect#postConnect。请求指明 egress base 与两个路径computerd轮询base health后外呼base api由于 egress 被拦截那次出站拨号会绕回 DO 的handleFetch()DO 接受升级并 resolve 在途的#pendingUpgradecapnweb 会话随之建立。WebSocket 载体被反转了——不是朴素的宿主拨入容器而是容器拨出到宿主。cloudflare-container.ts中真实存在的几处锐利边缘#armUpgrade必须在#postConnect之前设置因为computerd可能在POST /connect响应返回前就拨回。容器宿主记录每个被监控世代的退出原因死掉的容器会关闭其 WebSocketfetchPort()也会用传输错误短路后续请求两条路径都会使对应的 Workspace 句柄失效。重连会整体替换会话WebSocket 断掉后Workspace使缓存句柄失效并关闭对应后端句柄随后再次调用CloudflareContainerBackend.connect()替代过程会完整重跑 start、egress 拦截、健康、/connect、反向 WebSocket 全套序列后端从不把新载体拼接进已死的 capnweb 会话。可重放安全的同步与进程生命周期操作会获得一次重试命令 spawn 仅在未发出请求时重试。后端还带有一系列可调参数构造函数 options均含默认值containerPort8080、connectTimeoutMs30s、heartbeatIntervalMs20s应用层sync.watermarks()心跳用于快速发现静默死掉的对端并保持中间盒空闲计时器活跃、restartAttempts1即初始 start 后再重启一次、healthProbeTimeoutMs2s、healthRetryInitialDelayMs250ms失败后翻倍上限healthRetryMaxDelayMs2s、egressHost默认computer.internal。此外#requireAuthEnforced会在授权前先确认容器确实拒绝未授权请求——防止旧镜像不理解RPC_CLIENT_SECRET而假装安全。环境变量全表computerd实际消费的环境变量如下实现见 packages/computerd/src/cli/computerd.ts 与 packages/computerd/src/fuse/backend.ts变量默认值含义PORT45678HTTP 服务器监听端口。CF 后端固定为8080。MOUNT_POINT/workspace容器内挂载 FUSE 文件系统的绝对路径。FUSE_MOUNTnone时忽略。FUSE_MOUNTauto后端选择器auto探测/dev/fuselinux或 macFUSEdarwin失败回退到用户态 shimfuse/macfuse要求对应真实后端shim强制用户态 shimnone完全跳过挂载。EXEC_LOG_MAX_BYTESrunner 默认值限制每个 exec 的 stdout/stderr 在内存中保留的日志字节上限。RPC_CLIENT_SECRET未设置设置后除/health外的每个路由含/api升级都需要Authorization: Bearer secret未设置则表面开放。Cloudflare 后端为每个工作区生成一个并在启动时设置。COMPUTER_VAR_*未设置以去掉前缀的方式转发进每个shell.exec命令例如COMPUTER_VAR_NODE_ENV到达命令时是NODE_ENV。LOG_FILE未设置设置后每行console.log/console.error以及任何uncaughtException/unhandledRejection也会追加写入该文件stdout/stderr 行为不变。源码级补充PORT解析必须是 0–65535 的整数否则启动报错MOUNT_POINT必须是绝对路径。RPC_CLIENT_SECRET的读取在main()一开始读取一次并从环境中删除delete process.env.RPC_CLIENT_SECRET——exec 白名单已保证不泄漏给子进程这里再删一次杜绝未来任何spread process.env的代码路径重新引入泄漏。鉴权比较使用timingSafeEqual且先比长度再比较避免通过长度差异泄露密钥信息scheme token 大小写不敏感且允许多个空格手写客户端发bearer secret同样有效。FUSE_MOUNT的解析packages/computerd/src/fuse/backend.ts合法值为auto、fuse、macfuse、shim、none非法值直接抛错linux 探测/dev/fusedarwin 探测/Library/Filesystems/macfuse.fs。FUSE_MOUNTfuse在非 linux 平台抛错macfuse在非 darwin 抛错。旧变量迁移历史上的DISABLE_FUSE、FUSE_SHIM、WSD_FUSE_BACKEND三个变量已合并进FUSE_MOUNTcomputerd检测到任一旧变量被设置就会拒绝启动并提示新名字而不是静默翻译——项目处于 pre-1.0 alpha没有生产消费者因此宁可让操作者一次性学会新名字。EXEC_LOG_MAX_BYTES必须是正整数用于在不重编二进制的情况下让 harness 强制大小上限淘汰默认值在 Runner 内部。FUSE_MOUNTshim的用户态 shimpackages/computerd/src/shim/shim.ts启动时把MOUNT_POINT下的 VFS 子树物化到宿主文件系统同路径vfs.watchAsync(MOUNT_POINT, { recursive: true })驱动 VFS→磁盘周期性轮询约 250ms走目录并对照内容哈希 shadow 推送磁盘→VFSshadow 同时充当循环抑制器。exec的cwdMOUNT_POINT与真实 FUSE 一致。Shim 仅限开发跨缝的并发写由下一次 reconcile tick 决出VFS 赢平局symlink/xattr/chmod/chown/watch 扇出均不建模大文件每次变更都要整读。故障处理Failure handling当前行为见 packages/computerd/src/cli/logger.tscomputerd通过installLogging()安装uncaughtException与unhandledRejection处理器。每个处理器把格式化条目写入同一 logger——console.error若设置LOG_FILE也写文件——然后process.exit(1)。日志默认走 stdout/stderr。设置LOG_FILE后每行console.log/console.error也追加写入该文件以O_APPEND打开、ISO 时间戳、[info]/[error]前缀。无轮转由操作者管理文件。FUSE_MOUNTfuse或macfuse在对应内核设施不可用时启动即报错FUSE_MOUNTauto则静默回退到用户态 shim。唯一完全跳过挂载的路径是显式的FUSE_MOUNTnone选择。实现细节formatLogEntry把 Error 渲染为 stack、对象用util.inspectdepth 4createFileLogger用openSync(path, a)保持 fd 跨行复用避免每行一次 open/closeteardown 闭包恢复原始 console 方法并关闭文件句柄。优雅退出路径SIGINT/SIGTERM会先关闭 HTTP/WS 服务器、disposeAll()所有 runner、卸载 FUSE再以 130/143 退出。PlannedFUSE 探测失败的软失败模式服务器仍然启动、仍暴露 RPC并通过/__computerd/info报告fuseActivefalse是否包含宿主-FS 镜像以承接容器内写操作仍悬而未决。生命周期与持久性边界computerd进程是长命的比 Durable Object 重启活得久沙箱容器只在其生命周期策略允许时才被回收新的 DO 化身通过新的 WebSocket 重连到同一个仍在运行的守护进程。容器监视器与传输错误分类器会丢弃陈旧句柄使操作能穿过就绪闸门重连。关键注意事项目前没有磁盘持久化见 packages/computerd/README.md。跨 DO 重启保持同一内存 VFS 的前提是容器进程一直存活容器重启会丢失 VFS 状态。水位线对账与下一次 push 会从 DO 存储重建镜像但重连无法恢复容器进程死掉前从未被拉走的容器本地文件。开放问题Open questions以下行为尚未完全定型如果你的用例依赖某种特定解法请提交 issue连接认证。当前 WebSocket 端点信任任何能触达端口的东西。在 Cloudflare Containers 上这是安全的只有持有者 DO 能触达容器的 TCP 端口但一旦支持网络暴露面更广的提供方服务器就需要在 RPC 握手上自带认证。候选方案工作区铸造的短生命周期共享密钥经环境变量传递、每连接挑战或启动时预置的 mTLS 客户端证书。线上表面08. Capnweb Interface在暴露 bootstrap stub 之前需要一个 hello/auth 阶段。进程用户与文件所有权。computerd当前以沙箱镜像ENTRYPOINT的用户运行通常是root这对一个挂载 FUSE 并 spawn 任意 shell 命令的进程来说是个糟糕的默认值。意图是以非特权用户运行computerd但 exec 出的命令又要能读写 FUSE 挂载树。待定挂载归属哪个用户、exec 以什么用户运行workspace按 exec 动态、allow_other/ setuid / 共享组所有权如何接线才能让两侧看到同一批文件又不把挂载开放给容器内所有进程。FUSE 软失败行为。即上文Failure handling——降级的fuseActivefalse模式是包含宿主-FS 镜像还是干脆拒绝容器侧写操作尚未有结论。延伸阅读同步协议与水位线docs/02_sync_protocol.mdShell / exec 运行时接口docs/05_runtime_interface.mdcapnweb 线上接口与 auth 讨论docs/08_capnweb_interface.mdcomputerd包与 FUSE 写模型、测试指引packages/computerd/README.mdCloudflare 容器后端实现packages/computer/src/backends/container/cloudflare-container.ts规范镜像配方examples/container/Dockerfile本地快速体验需要 Node 22先在工作区构建依赖npm run build --workspacecloudflare/computerd PORT45678 MOUNT_POINT/tmp/workspace FUSE_MOUNTshim npx -p cloudflare/computerd computerdFUSE_MOUNTshim让没有/dev/fuse的本地环境多数 CI、未装 macFUSE 的 macOS、无/dev/fuse的 Linux 容器也能完整跑通 VFS HTTP/RPC 表面curl localhost:45678/health返回ok\n即说明守护进程已就绪。【免费下载链接】computerGive your agent a computer 项目地址: https://gitcode.com/GitHub_Trending/computer1/computer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考