README_zh.md
1 # Alibaba Sandbox JavaScript/TypeScript SDK 2 3 中文 | [English](README.md) 4 5 用于与 OpenSandbox 进行底层交互的 TypeScript/JavaScript SDK。它提供了创建、管理和与安全沙箱环境交互的能力,包括执行 Shell 命令、管理文件以及读取资源指标等。 6 7 ## 安装指南 8 9 ### npm 10 11 ```bash 12 npm install @alibaba-group/opensandbox 13 ``` 14 15 ### pnpm 16 17 ```bash 18 pnpm add @alibaba-group/opensandbox 19 ``` 20 21 ### yarn 22 23 ```bash 24 yarn add @alibaba-group/opensandbox 25 ``` 26 27 ## 快速开始 28 29 以下示例展示了如何创建一个沙箱并执行 Shell 命令。 30 31 > **注意**: 在运行此示例之前,请确保 OpenSandbox 服务已启动。服务启动请参考根目录的 [README_zh.md](../../../docs/README_zh.md)。 32 33 ```ts 34 import { ConnectionConfig, Sandbox, SandboxException } from "@alibaba-group/opensandbox"; 35 36 const config = new ConnectionConfig({ 37 domain: "api.opensandbox.io", 38 apiKey: "your-api-key", 39 // protocol: "https", 40 // requestTimeoutSeconds: 60, 41 }); 42 43 try { 44 const sandbox = await Sandbox.create({ 45 connectionConfig: config, 46 image: "ubuntu", 47 timeoutSeconds: 10 * 60, 48 }); 49 50 const execution = await sandbox.commands.run("echo 'Hello Sandbox!'"); 51 console.log(execution.logs.stdout[0]?.text); 52 53 await sandbox.kill(); 54 await sandbox.close(); 55 } catch (err) { 56 if (err instanceof SandboxException) { 57 console.error(`沙箱错误: [${err.error.code}] ${err.error.message ?? ""}`); 58 console.error(`Request ID: ${err.requestId ?? "N/A"}`); 59 } else { 60 console.error(err); 61 } 62 } 63 ``` 64 65 ## 核心功能示例 66 67 ### 1. 生命周期管理 68 69 管理沙箱的生命周期,包括续期、暂停、恢复和状态查询。 70 71 ```ts 72 const info = await sandbox.getInfo(); 73 console.log("状态:", info.status.state); 74 console.log("创建时间:", info.createdAt); 75 console.log("过期时间:", info.expiresAt); 76 77 await sandbox.pause(); 78 79 // resume 会返回新的、已连接的 Sandbox 实例 80 const resumed = await sandbox.resume(); 81 82 // renew:expiresAt = now + timeoutSeconds 83 await resumed.renew(30 * 60); 84 85 // 获取当前状态 86 const info = await resumed.getInfo(); 87 console.log("状态:", info.status.state); 88 console.log("过期时间:", info.expiresAt); // 使用手动清理模式时为 null 89 ``` 90 91 通过传入 `timeoutSeconds: null` 创建一个不会自动过期的沙箱: 92 93 ```ts 94 const manual = await Sandbox.create({ 95 connectionConfig: config, 96 image: "ubuntu", 97 timeoutSeconds: null, 98 }); 99 ``` 100 101 ### 2. 自定义健康检查 102 103 定义自定义逻辑来判断沙箱是否就绪/健康。这会覆盖“就绪检测”默认使用的 ping 检查逻辑。 104 105 ```ts 106 const sandbox = await Sandbox.create({ 107 connectionConfig: config, 108 image: "nginx:latest", 109 healthCheck: async (sbx) => { 110 // 示例:当 80 端口 endpoint 可获取时认为沙箱可用 111 const ep = await sbx.getEndpoint(80); 112 return !!ep.endpoint; 113 }, 114 }); 115 ``` 116 117 ### 3. 命令执行与流式响应 118 119 执行命令并实时处理输出流。 120 121 ```ts 122 import type { ExecutionHandlers } from "@alibaba-group/opensandbox"; 123 124 const handlers: ExecutionHandlers = { 125 onStdout: (m) => console.log("STDOUT:", m.text), 126 onStderr: (m) => console.error("STDERR:", m.text), 127 onExecutionComplete: (c) => console.log("耗时(ms):", c.executionTimeMs), 128 }; 129 130 await sandbox.commands.run( 131 'for i in 1 2 3; do echo "Count $i"; sleep 0.2; done', 132 undefined, 133 handlers, 134 ); 135 ``` 136 137 ### 4. 全面的文件操作 138 139 管理文件和目录,包括读写、列表/搜索与删除。 140 141 ```ts 142 await sandbox.files.createDirectories([{ path: "/tmp/demo", mode: 755 }]); 143 144 await sandbox.files.writeFiles([ 145 { path: "/tmp/demo/hello.txt", data: "Hello World", mode: 644 }, 146 ]); 147 148 const content = await sandbox.files.readFile("/tmp/demo/hello.txt"); 149 console.log("文件内容:", content); 150 151 const files = await sandbox.files.search({ 152 path: "/tmp/demo", 153 pattern: "*.txt", 154 }); 155 console.log(files.map((f) => f.path)); 156 157 await sandbox.files.deleteDirectories(["/tmp/demo"]); 158 ``` 159 160 ### 5. Endpoint 161 162 `getEndpoint()` 返回 **不带 scheme** 的 endpoint(例如 `"localhost:44772"`)。如果你希望直接得到可用的绝对 URL(例如 `"http://localhost:44772"`),请使用 `getEndpointUrl()`。 163 164 ```ts 165 const { endpoint } = await sandbox.getEndpoint(44772); 166 const url = await sandbox.getEndpointUrl(44772); 167 ``` 168 169 ### 6. Volume 挂载 170 171 `volumes` 现在支持 `host`、`pvc` 和 `ossfs` 三种 backend。每个 volume 必须且只能指定其中一种。 172 173 ```ts 174 const sandbox = await Sandbox.create({ 175 connectionConfig: config, 176 image: "ubuntu", 177 volumes: [ 178 { 179 name: "oss-data", 180 ossfs: { 181 bucket: "bucket-a", 182 endpoint: "oss-cn-hangzhou.aliyuncs.com", 183 accessKeyId: process.env.OSS_ACCESS_KEY_ID!, 184 accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!, 185 version: "2.0", 186 }, 187 mountPath: "/mnt/oss", 188 subPath: "prefix", 189 }, 190 ], 191 }); 192 ``` 193 194 ### 7. 沙箱管理(Admin) 195 196 使用 `SandboxManager` 进行管理操作,如查询现有沙箱列表。 197 198 ```ts 199 import { SandboxManager } from "@alibaba-group/opensandbox"; 200 201 const manager = SandboxManager.create({ connectionConfig: config }); 202 const list = await manager.listSandboxInfos({ 203 states: ["Running"], 204 pageSize: 10, 205 }); 206 console.log(list.items.map((s) => s.id)); 207 ``` 208 209 ## 配置说明 210 211 ### 1. 连接配置 (Connection Configuration) 212 213 `ConnectionConfig` 类管理与 API 服务器的连接设置。 214 215 运行环境说明: 216 217 - 浏览器环境下,SDK 使用全局 `fetch`。 218 - Node.js 环境下,每个 `Sandbox` 和 `SandboxManager` 都会通过 `ConnectionConfig.withTransportIfMissing()` 创建独立的 keep-alive 池(基于 `undici`)。完成交互后请调用 `sandbox.close()` 或 `manager.close()` 来释放对应的 agent,以避免遗留连接,这与 Python SDK 的 transport 生命周期一致。 219 220 | 参数 | 描述 | 默认值 | 环境变量 | 221 | ----------------------- | ------------------------------------------------------------------------- | ---------------- | ---------------------- | 222 | `apiKey` | 用于认证的 API Key | 可选 | `OPEN_SANDBOX_API_KEY` | 223 | `domain` | 沙箱服务域名(`host[:port]`) | `localhost:8080` | `OPEN_SANDBOX_DOMAIN` | 224 | `protocol` | HTTP 协议(`http`/`https`) | `http` | - | 225 | `requestTimeoutSeconds` | SDK HTTP 请求超时(秒) | `30` | - | 226 | `debug` | 是否开启基础 HTTP 调试日志 | `false` | - | 227 | `headers` | 每次请求附加的 Header | `{}` | - | 228 | `useServerProxy` | 是否通过沙箱服务代理访问 execd/endpoint(适用于客户端无法直连沙箱的场景) | `false` | - | 229 230 ```ts 231 import { ConnectionConfig } from "@alibaba-group/opensandbox"; 232 233 // 1. 基础配置 234 const config = new ConnectionConfig({ 235 domain: "api.opensandbox.io", 236 apiKey: "your-key", 237 requestTimeoutSeconds: 60, 238 }); 239 240 // 2. 进阶配置:自定义 headers 241 const config2 = new ConnectionConfig({ 242 domain: "api.opensandbox.io", 243 apiKey: "your-key", 244 headers: { "X-Custom-Header": "value" }, 245 }); 246 ``` 247 248 ### 2. 沙箱创建配置 (Sandbox Creation Configuration) 249 250 `Sandbox.create()` 用于配置沙箱环境。 251 252 | 参数 | 描述 | 默认值 | 253 | ---------------------------- | ------------------------------------ | ---------------------------- | 254 | `image` | 使用的 Docker 镜像 | 必填 | 255 | `timeoutSeconds` | 自动终止超时时间(服务端 TTL) | 10 分钟 | 256 | `entrypoint` | 容器启动入口命令 | `["tail","-f","/dev/null"]` | 257 | `resource` | CPU/内存限制(字符串 map) | `{"cpu":"1","memory":"2Gi"}` | 258 | `env` | 环境变量 | `{}` | 259 | `metadata` | 自定义元数据标签 | `{}` | 260 | `networkPolicy` | 可选的出站网络策略(egress) | - | 261 | `extensions` | 额外的服务端扩展字段 | `{}` | 262 | `skipHealthCheck` | 跳过就绪检测(`Running` + 健康检查) | `false` | 263 | `healthCheck` | 自定义就绪检查 | - | 264 | `readyTimeoutSeconds` | 等待就绪最大时间 | 30 秒 | 265 | `healthCheckPollingInterval` | 就绪轮询间隔(毫秒) | 200 ms | 266 267 注意:`opensandbox.io/` 前缀下的 metadata key 属于系统保留标签,服务端会拒绝用户传入。 268 269 ```ts 270 const sandbox = await Sandbox.create({ 271 connectionConfig: config, 272 image: "python:3.11", 273 networkPolicy: { 274 defaultAction: "deny", 275 egress: [{ action: "allow", target: "pypi.org" }], 276 }, 277 }); 278 ``` 279 280 ### 3. 运行时 Egress 策略更新 281 282 运行时的 egress 查询和 patch 会直接访问沙箱内的 egress sidecar。 283 SDK 会先解析 `18080` 端口对应的 sandbox endpoint,再调用 sidecar 的 `/policy` API。 284 285 ```ts 286 const policy = await sandbox.getEgressPolicy(); 287 288 await sandbox.patchEgressRules([ 289 { action: "allow", target: "www.github.com" }, 290 { action: "deny", target: "pypi.org" }, 291 ]); 292 ``` 293 294 ### 4. 资源清理 295 296 在 Node.js 环境下,`Sandbox` 和 `SandboxManager` 会拥有各自的 HTTP agent,因此即使多个实例共享同一个 `ConnectionConfig` 也不会互相影响。SDK 会借助 `ConnectionConfig.withTransportIfMissing()` 复刻每个实例的 transport。完成使用后调用 `sandbox.close()` / `manager.close()` 来释放底层连接池; 297 298 ## 浏览器注意事项 299 300 - SDK 可在浏览器运行,但**流式文件上传仅支持 Node**。 301 - 如果 `writeFiles` 传入 `ReadableStream` 或 `AsyncIterable`,浏览器会回退为**先缓存在内存,再上传**。 302 - 原因:浏览器不支持以自定义 boundary 的 `multipart/form-data` 流式请求体(execd 上传接口需要此能力)。