@axiom-lattice/opensandbox-gateway 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +12 -0
- package/Dockerfile +14 -0
- package/README.md +80 -0
- package/deploy/.env.example +9 -0
- package/deploy/OFFLINE_DEPLOYMENT.md +126 -0
- package/deploy/README.md +95 -0
- package/deploy/docker-compose.yml +35 -0
- package/deploy/sandbox.toml +46 -0
- package/docker-compose.yml +35 -0
- package/package.json +1 -1
- package/sandbox.toml +46 -0
package/.env.example
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy to .env and fill in. docker compose auto-reads .env in the packages/opensandbox-gateway directory.
|
|
2
|
+
# Shared auth key for the whole sandbox system (server + gateway; Core must set MICROSANDBOX_API_KEY to the same value).
|
|
3
|
+
SANDBOX_API_KEY=change-me-sandbox-key
|
|
4
|
+
# The published gateway image (see README "Docker Compose Deployment").
|
|
5
|
+
GATEWAY_IMAGE=kioko12520/opensandbox-gateway:0.1.5
|
|
6
|
+
# Host directory for named-volume data. Must match allowed_host_paths in sandbox.toml.
|
|
7
|
+
VOLUME_DIR=/data/opensandbox/volumes
|
|
8
|
+
# Optional: separate key for the gateway's own auth; defaults to SANDBOX_API_KEY.
|
|
9
|
+
# GATEWAY_API_KEY=change-me-gateway-key
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @axiom-lattice/opensandbox-gateway@0.2.
|
|
2
|
+
> @axiom-lattice/opensandbox-gateway@0.2.2 build /home/runner/work/agentic/agentic/packages/opensandbox-gateway
|
|
3
3
|
> tsup src/index.ts src/cli.ts --format esm --dts --clean --sourcemap
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
[32mESM[39m [1mdist/cli.mjs.map [22m[32m387.00 B[39m
|
|
15
15
|
[32mESM[39m [1mdist/index.mjs.map [22m[32m71.00 B[39m
|
|
16
16
|
[32mESM[39m [1mdist/chunk-DXSDNFP5.mjs.map [22m[32m78.82 KB[39m
|
|
17
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m ⚡️ Build success in 178ms
|
|
18
18
|
[34mDTS[39m Build start
|
|
19
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
+
[32mDTS[39m ⚡️ Build success in 29393ms
|
|
20
20
|
[32mDTS[39m [1mdist/cli.d.mts [22m[32m20.00 B[39m
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m10.55 KB[39m
|
package/CHANGELOG.md
CHANGED
package/Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
FROM node:20-slim AS builder
|
|
2
|
+
RUN corepack enable && corepack prepare pnpm@10.4.0 --activate
|
|
3
|
+
WORKDIR /repo
|
|
4
|
+
COPY . .
|
|
5
|
+
RUN pnpm install --frozen-lockfile \
|
|
6
|
+
&& pnpm --filter @axiom-lattice/opensandbox-gateway build \
|
|
7
|
+
&& pnpm --filter @axiom-lattice/opensandbox-gateway deploy --legacy --prod /out
|
|
8
|
+
|
|
9
|
+
FROM node:20-slim
|
|
10
|
+
ENV NODE_ENV=production
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
COPY --from=builder /out .
|
|
13
|
+
EXPOSE 4002
|
|
14
|
+
CMD ["node", "dist/cli.mjs"]
|
package/README.md
CHANGED
|
@@ -241,6 +241,85 @@ CMD ["lattice-opensandbox-gateway", "--host", "0.0.0.0", "--port", "4002"]
|
|
|
241
241
|
pnpm --filter @axiom-lattice/opensandbox-gateway dev
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
+
## Docker Compose Deployment
|
|
245
|
+
|
|
246
|
+
Deploys the full sandbox stack (`opensandbox-server` + `opensandbox-gateway`) with a single command. Files live in this package:
|
|
247
|
+
|
|
248
|
+
- `docker-compose.yml` — both services, network, and volume wiring
|
|
249
|
+
- `sandbox.toml` — OpenSandbox Server config (mounted into the server container)
|
|
250
|
+
- `Dockerfile` — gateway image build (pnpm install → build → `pnpm deploy --prod`)
|
|
251
|
+
- `.env.example` — secret template; copy to `.env` and fill in
|
|
252
|
+
|
|
253
|
+
### Architecture
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
Core (agent) Docker host
|
|
257
|
+
│ MICROSANDBOX_SERVICE_URL │
|
|
258
|
+
▼ │
|
|
259
|
+
opensandbox-gateway ──HTTP──▶ opensandbox-server ──docker.sock──▶ sandbox containers
|
|
260
|
+
(Node Fastify, :4002) (Python FastAPI, :8080)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Prerequisites
|
|
264
|
+
|
|
265
|
+
- Docker Engine with compose support (Docker Desktop on macOS)
|
|
266
|
+
- On macOS, `VOLUME_DIR` must be under a Docker Desktop shared path (e.g. a dir under `/Users`)
|
|
267
|
+
|
|
268
|
+
### Configure
|
|
269
|
+
|
|
270
|
+
1. Publish the gateway image once (it has no official image), then set `.env`:
|
|
271
|
+
```bash
|
|
272
|
+
cp .env.example .env
|
|
273
|
+
# edit .env → SANDBOX_API_KEY, GATEWAY_IMAGE, VOLUME_DIR
|
|
274
|
+
```
|
|
275
|
+
The `GATEWAY_IMAGE` must point to an image built from this package's `Dockerfile` (see the deploy package README for the build/push commands).
|
|
276
|
+
2. (Optional) Override defaults via the compose `environment` or `sandbox.toml`:
|
|
277
|
+
- Business sandbox image — set on the **Core** side via `MICROSANDBOX_IMAGE` (default `kioko12520/sandbox:0.1.0`). The gateway fallback is `OPEN_SANDBOX_DEFAULT_IMAGE` (default `ubuntu:22.04`).
|
|
278
|
+
- `execd_image` / `egress.image` in `sandbox.toml` must be pullable on the host.
|
|
279
|
+
|
|
280
|
+
> **Linux hosts:** `sandbox.toml` ships with `[proxy] resolve_internal = false` + `[docker] host_ip = "172.17.0.1"` (host-mapped routing). Keep them — with the default `resolve_internal = true`, the Linux server container cannot route to sandbox bridge IPs and sandbox operations hang (OrbStack/macOS tolerates it; Linux does not). See `deploy/OFFLINE_DEPLOYMENT.md`.
|
|
281
|
+
|
|
282
|
+
### Start
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
cd packages/opensandbox-gateway
|
|
286
|
+
docker compose up -d
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Verify:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
curl http://localhost:8080/health # OpenSandbox Server → {"status":"healthy"}
|
|
293
|
+
curl http://localhost:4002/health # gateway
|
|
294
|
+
curl -H "Authorization: Bearer $SANDBOX_API_KEY" http://localhost:4002/api/sandboxes
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Connect from Core
|
|
298
|
+
|
|
299
|
+
On the agent process, set:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
MICROSANDBOX_SERVICE_URL=http://localhost:4002
|
|
303
|
+
MICROSANDBOX_API_KEY=<same as SANDBOX_API_KEY>
|
|
304
|
+
MICROSANDBOX_IMAGE=kioko12520/sandbox:0.1.0 # optional, dynamic sandbox image
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Path alignment (important)
|
|
308
|
+
|
|
309
|
+
`VOLUME_DIR` (host) is bind-mounted into the gateway at `/data/opensandbox/volumes`, and the server only allows bind mounts under `allowed_host_paths` in `sandbox.toml`. All three must agree:
|
|
310
|
+
|
|
311
|
+
| Place | Value |
|
|
312
|
+
|-------|-------|
|
|
313
|
+
| `.env` → `VOLUME_DIR` (host, Docker daemon view) | `/data/opensandbox/volumes` (default) |
|
|
314
|
+
| Gateway `VOLUME_BASE_PATH` (inside container, fixed) | `/data/opensandbox/volumes` |
|
|
315
|
+
| `sandbox.toml` → `allowed_host_paths` | `/data/opensandbox/volumes` (default) |
|
|
316
|
+
|
|
317
|
+
If you change `VOLUME_DIR`, update `allowed_host_paths` in `sandbox.toml` to the **same** value. A mismatch produces `Host path is not under any allowed prefix` on volume mounts.
|
|
318
|
+
|
|
319
|
+
### Auth
|
|
320
|
+
|
|
321
|
+
`SANDBOX_API_KEY` is shared by both services (server `api_key` + gateway's server-auth key). The gateway's own auth defaults to the same key and can be overridden with `GATEWAY_API_KEY`. The Core side must set `MICROSANDBOX_API_KEY` to the gateway's auth key.
|
|
322
|
+
|
|
244
323
|
## Operational Notes
|
|
245
324
|
|
|
246
325
|
- Requires a running OpenSandbox Server. Start it before starting the gateway.
|
|
@@ -249,6 +328,7 @@ pnpm --filter @axiom-lattice/opensandbox-gateway dev
|
|
|
249
328
|
- Image management endpoints (`GET /api/images`, `POST /api/images/pull`) are stubs and return empty results.
|
|
250
329
|
- Sandbox logs and runtime metrics are not yet implemented (stub returns).
|
|
251
330
|
- Volume FS operations (`/api/volumes/:name/fs/*`) read/write files on the host at `$VOLUME_BASE_PATH`. Works with Docker runtime where the gateway has filesystem access to the host.
|
|
331
|
+
- **Sandbox TTL:** sandboxes expire after `OPEN_SANDBOX_DEFAULT_TIMEOUT` seconds (default `600`) of inactivity. The server destroys the container on expiry. `PUT /api/sandboxes/:name` (ensure) recreates an expired sandbox, but file operations do **not** auto-recreate — they fail against a dead container. This is transparent to the Core agent (it ensures before every operation), but note that **container filesystem state is lost on rebuild** unless data lives in named volumes. For long-running workloads, raise `OPEN_SANDBOX_DEFAULT_TIMEOUT` or use named volumes for persistence.
|
|
252
332
|
|
|
253
333
|
## API Compatibility
|
|
254
334
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy to .env and fill in. docker compose auto-reads .env in the deploy directory.
|
|
2
|
+
# Shared auth key for the whole sandbox system (server + gateway; Core must set MICROSANDBOX_API_KEY to the same value).
|
|
3
|
+
SANDBOX_API_KEY=change-me-sandbox-key
|
|
4
|
+
# The published gateway image (see README "Packaging for distribution").
|
|
5
|
+
GATEWAY_IMAGE=kioko12520/opensandbox-gateway:0.1.5
|
|
6
|
+
# Host directory for named-volume data. Must match allowed_host_paths in sandbox.toml.
|
|
7
|
+
VOLUME_DIR=/data/opensandbox/volumes
|
|
8
|
+
# Optional: separate key for the gateway's own auth; defaults to SANDBOX_API_KEY.
|
|
9
|
+
# GATEWAY_API_KEY=change-me-gateway-key
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# OpenSandbox Offline Deployment Guide
|
|
2
|
+
|
|
3
|
+
Deploy the OpenSandbox sandbox stack on an air-gapped / offline machine using a pre-packaged image bundle. **This procedure has been verified end-to-end on a Linux x86_64 server** (2 CPUs, Docker 29).
|
|
4
|
+
|
|
5
|
+
## What you need to carry over
|
|
6
|
+
|
|
7
|
+
| Item | Source | Content |
|
|
8
|
+
|------|--------|---------|
|
|
9
|
+
| `sandbox-stack-amd64.tar.gz` (1.5 GB) | offline packager | 5 Docker images: `opensandbox/server`, `kioko12520/opensandbox-gateway:0.1.5`, `opensandbox/execd:v1.1.0`, `opensandbox/egress:v1.1.7`, `kioko12520/sandbox:0.1.0` |
|
|
10
|
+
| `deploy/` folder | this repo | `docker-compose.yml`, `sandbox.toml`, `.env.example`, `README.md` |
|
|
11
|
+
|
|
12
|
+
> Only the gateway image is self-built. The rest are official images and the business sandbox image. All are amd64 in this bundle; for arm64 targets build an arm64 bundle (see "Preparing the bundle").
|
|
13
|
+
|
|
14
|
+
## Target machine prerequisites
|
|
15
|
+
|
|
16
|
+
- Docker Engine with Compose
|
|
17
|
+
- ~5 GB free disk (bundle extraction + runtime overhead)
|
|
18
|
+
- `docker0` bridge gateway reachable from containers (default `172.17.0.1`; verify below)
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 1. Load all images (no network needed)
|
|
24
|
+
gunzip -c sandbox-stack-amd64.tar.gz | docker load
|
|
25
|
+
docker images | grep -E "opensandbox|kioko12520/sandbox" # confirm 5 images
|
|
26
|
+
|
|
27
|
+
# 2. Prepare the deploy folder
|
|
28
|
+
mkdir -p /opt/opensandbox && cp -r deploy/ /opt/opensandbox/
|
|
29
|
+
cd /opt/opensandbox
|
|
30
|
+
cp .env.example .env
|
|
31
|
+
# edit .env: SANDBOX_API_KEY=<your-secret>, GATEWAY_IMAGE=kioko12520/opensandbox-gateway:0.1.5
|
|
32
|
+
|
|
33
|
+
# 3. (Only if needed) verify/adjust [docker] host_ip in sandbox.toml
|
|
34
|
+
docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'
|
|
35
|
+
# If the gateway is NOT 172.17.0.1, update host_ip in sandbox.toml to match.
|
|
36
|
+
|
|
37
|
+
# 4. Start
|
|
38
|
+
docker compose up -d
|
|
39
|
+
|
|
40
|
+
# 5. Verify
|
|
41
|
+
curl http://localhost:8080/health # {"status":"healthy"}
|
|
42
|
+
curl http://localhost:4002/health # {"success":true,"data":{"status":"ok"}}
|
|
43
|
+
curl -H "Authorization: Bearer $SANDBOX_API_KEY" http://localhost:4002/api/sandboxes
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Smoke test (sandbox lifecycle)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
KEY=<your SANDBOX_API_KEY>
|
|
50
|
+
# Create (first create pulls nothing - images already loaded; large images take 1-3 min, keep client timeout ≥ 300s)
|
|
51
|
+
curl -m 300 -X PUT "http://localhost:4002/api/sandboxes/test1" \
|
|
52
|
+
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
|
|
53
|
+
-d '{"image":"kioko12520/sandbox:0.1.0","cpus":1,"memoryMib":512}'
|
|
54
|
+
|
|
55
|
+
# Exec a command
|
|
56
|
+
curl -X POST "http://localhost:4002/api/shell/exec" \
|
|
57
|
+
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
|
|
58
|
+
-d '{"sandboxName":"test1","command":"uname -m && python3 --version"}'
|
|
59
|
+
|
|
60
|
+
# File write/read
|
|
61
|
+
curl -X POST "http://localhost:4002/api/files/write" \
|
|
62
|
+
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
|
|
63
|
+
-d '{"sandboxName":"test1","path":"/tmp/offline.txt","content":"ok"}'
|
|
64
|
+
curl -X POST "http://localhost:4002/api/files/read" \
|
|
65
|
+
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
|
|
66
|
+
-d '{"sandboxName":"test1","path":"/tmp/offline.txt"}'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Connect the agent (Core)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
MICROSANDBOX_SERVICE_URL=http://localhost:4002
|
|
73
|
+
MICROSANDBOX_API_KEY=<same as SANDBOX_API_KEY>
|
|
74
|
+
MICROSANDBOX_IMAGE=kioko12520/sandbox:0.1.0
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Critical configuration (verified)
|
|
78
|
+
|
|
79
|
+
`sandbox.toml` must use **host-mapped port routing**, not internal bridge IPs:
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
[proxy]
|
|
83
|
+
resolve_internal = false
|
|
84
|
+
|
|
85
|
+
[docker]
|
|
86
|
+
network_mode = "bridge"
|
|
87
|
+
host_ip = "172.17.0.1" # docker0 gateway; must match `docker network inspect bridge`
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Why:** on a Linux host, the server container runs on the Compose bridge (e.g. `172.21.0.x`) and **cannot route to sandbox containers on the default bridge** (`172.17.0.x`). With the default `resolve_internal = true`, the server's execd proxy silently hangs (sandbox creation appears stuck, and commands/file ops fail with `SANDBOX_NOT_FOUND`). `resolve_internal = false` + `host_ip` targets the sandbox's host-mapped port via the docker0 gateway, which works from any container. (OrbStack on macOS happens to allow cross-bridge routing, so the default also worked there — Linux servers will not.)
|
|
91
|
+
|
|
92
|
+
## Known behaviors / caveats
|
|
93
|
+
|
|
94
|
+
- **Large-image first provisioning is slow** (1-3 min for `kioko12520/sandbox:0.1.0`). If the client disconnects mid-create, the gateway can leave the sandbox in a stuck in-memory state; `docker compose restart opensandbox-gateway` clears it, and a re-`PUT` recovers the existing container.
|
|
95
|
+
- **TTL**: sandboxes expire after `OPEN_SANDBOX_DEFAULT_TIMEOUT` (default 600 s) and the server destroys the container. File ops do not auto-recreate; the agent re-`ensure`s before each op. Rebuilds lose container filesystem state unless data lives in named volumes.
|
|
96
|
+
- **Architecture matters**: an amd64 bundle only runs on x86_64 targets. Build a separate arm64 bundle for arm64 machines, or use `skopeo` to package a multi-arch bundle (`:all`).
|
|
97
|
+
- `VOLUME_DIR` (host) must match `allowed_host_paths` in `sandbox.toml`, else volume mounts fail with `Host path is not under any allowed prefix`.
|
|
98
|
+
|
|
99
|
+
## Preparing the bundle (on a networked machine)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Same-arch source machine
|
|
103
|
+
docker pull opensandbox/server:latest
|
|
104
|
+
docker pull kioko12520/opensandbox-gateway:0.1.5
|
|
105
|
+
docker pull opensandbox/execd:v1.1.0
|
|
106
|
+
docker pull opensandbox/egress:v1.1.7
|
|
107
|
+
docker pull kioko12520/sandbox:0.1.0
|
|
108
|
+
docker save opensandbox/server:latest kioko12520/opensandbox-gateway:0.1.5 \
|
|
109
|
+
opensandbox/execd:v1.1.0 opensandbox/egress:v1.1.7 kioko12520/sandbox:0.1.0 \
|
|
110
|
+
| gzip > sandbox-stack-amd64.tar.gz
|
|
111
|
+
|
|
112
|
+
# Pin the architecture on a cross-arch source (e.g. amd64 bundle from an arm64 machine)
|
|
113
|
+
docker pull --platform linux/amd64 <each image above>
|
|
114
|
+
# The --platform flag is required here too; otherwise an ARM host can export its ARM64 variant.
|
|
115
|
+
docker image save --platform linux/amd64 \
|
|
116
|
+
opensandbox/server:latest kioko12520/opensandbox-gateway:0.1.5 \
|
|
117
|
+
opensandbox/execd:v1.1.0 opensandbox/egress:v1.1.7 kioko12520/sandbox:0.1.0 \
|
|
118
|
+
| gzip > sandbox-stack-amd64.tar.gz
|
|
119
|
+
|
|
120
|
+
# Multi-arch bundle (all platforms, needs skopeo)
|
|
121
|
+
skopeo copy docker://docker.io/kioko12520/opensandbox-gateway:0.1.5 \
|
|
122
|
+
docker-archive:gateway-multiarch.tar:kioko12520/opensandbox-gateway:0.1.5:all
|
|
123
|
+
# repeat for each image
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If Docker Hub is unreachable from the packager, pull via a mirror first (e.g. `hub.rat.dev/<image>`) then `docker tag` back to the canonical name before saving.
|
package/deploy/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# OpenSandbox Deploy Package
|
|
2
|
+
|
|
3
|
+
Self-contained Docker Compose deployment for the Axiom Lattice OpenSandbox sandbox stack. Hand this folder to anyone who needs the service; they do **not** need the Axiom Lattice monorepo.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
| File | Purpose |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| `docker-compose.yml` | Two services: `opensandbox-server` (Python, creates/runs sandbox containers via the host Docker daemon) + `opensandbox-gateway` (Node HTTP gateway, the API your agent connects to) |
|
|
10
|
+
| `sandbox.toml` | OpenSandbox Server runtime config (mounted into the server container) |
|
|
11
|
+
| `.env.example` | Secret/image/path template — copy to `.env` |
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Core (agent) Docker host
|
|
17
|
+
│ MICROSANDBOX_SERVICE_URL │
|
|
18
|
+
▼ ▼
|
|
19
|
+
opensandbox-gateway ──HTTP──▶ opensandbox-server ──docker.sock──▶ sandbox containers
|
|
20
|
+
(Node Fastify, :4002) (Python FastAPI, :8080)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Requirements (on the target host)
|
|
24
|
+
|
|
25
|
+
- Docker Engine with Compose
|
|
26
|
+
- Ability to pull: `opensandbox/server`, `opensandbox/execd:v1.1.0`, `opensandbox/egress:v1.1.7`, the gateway image, and the business sandbox images
|
|
27
|
+
- For offline / air-gapped targets, see **[`OFFLINE_DEPLOYMENT.md`](OFFLINE_DEPLOYMENT.md)** (verified end-to-end on Linux x86_64)
|
|
28
|
+
|
|
29
|
+
> **Important (Linux hosts):** `sandbox.toml` ships with `[proxy] resolve_internal = false` and `[docker] host_ip = "172.17.0.1"`. Do not revert `resolve_internal` to `true` — on Linux the server container cannot route to sandbox bridge IPs, and sandbox operations hang (see OFFLINE_DEPLOYMENT.md for details).
|
|
30
|
+
|
|
31
|
+
## Quick Start (consumer)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cp .env.example .env # fill SANDBOX_API_KEY, GATEWAY_IMAGE, VOLUME_DIR
|
|
35
|
+
docker compose up -d
|
|
36
|
+
curl http://localhost:8080/health # OpenSandbox Server
|
|
37
|
+
curl http://localhost:4002/health # gateway
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then on the agent process:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
MICROSANDBOX_SERVICE_URL=http://localhost:4002
|
|
44
|
+
MICROSANDBOX_API_KEY=<same as SANDBOX_API_KEY>
|
|
45
|
+
MICROSANDBOX_IMAGE=kioko12520/sandbox:0.1.0 # optional; dynamic business sandbox image
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Packaging for distribution (publisher)
|
|
49
|
+
|
|
50
|
+
The gateway has **no official image**, so publish it once, then ship this folder:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. Build + push the gateway image (from the monorepo root)
|
|
54
|
+
docker build -t <registry>/opensandbox-gateway:<tag> -f packages/opensandbox-gateway/Dockerfile .
|
|
55
|
+
docker push <registry>/opensandbox-gateway:<tag>
|
|
56
|
+
|
|
57
|
+
# 2. Set GATEWAY_IMAGE in the deploy/.env.example to that image
|
|
58
|
+
# 3. Ship the deploy/ folder
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Offline / air-gapped alternative — hand over the image as a tar instead of a registry:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
docker save <registry>/opensandbox-gateway:<tag> | gzip > opensandbox-gateway.tar.gz
|
|
65
|
+
# recipient:
|
|
66
|
+
gunzip -c opensandbox-gateway.tar.gz | docker load
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The `opensandbox/server` image is pulled automatically from Docker Hub.
|
|
70
|
+
|
|
71
|
+
## Key configuration
|
|
72
|
+
|
|
73
|
+
| Variable | Meaning |
|
|
74
|
+
|----------|---------|
|
|
75
|
+
| `SANDBOX_API_KEY` | Shared auth key (server + gateway). Core must set `MICROSANDBOX_API_KEY` to the same value |
|
|
76
|
+
| `GATEWAY_IMAGE` | The published gateway image |
|
|
77
|
+
| `VOLUME_DIR` | Host directory for named-volume data. **Must match `allowed_host_paths` in `sandbox.toml`** |
|
|
78
|
+
|
|
79
|
+
### Path alignment (important)
|
|
80
|
+
|
|
81
|
+
`VOLUME_DIR` (host) is bind-mounted into the gateway at `/data/opensandbox/volumes`, and the server only allows bind mounts under `allowed_host_paths` in `sandbox.toml`. All three must agree:
|
|
82
|
+
|
|
83
|
+
| Place | Value |
|
|
84
|
+
|-------|-------|
|
|
85
|
+
| `.env` → `VOLUME_DIR` (host, Docker daemon view) | `/data/opensandbox/volumes` (default) |
|
|
86
|
+
| Gateway `VOLUME_BASE_PATH` (inside container, fixed) | `/data/opensandbox/volumes` |
|
|
87
|
+
| `sandbox.toml` → `allowed_host_paths` | `/data/opensandbox/volumes` (default) |
|
|
88
|
+
|
|
89
|
+
If you change `VOLUME_DIR`, update `allowed_host_paths` in `sandbox.toml` to the **same** value. A mismatch produces `Host path is not under any allowed prefix` on volume mounts.
|
|
90
|
+
|
|
91
|
+
### Sandbox behavior notes
|
|
92
|
+
|
|
93
|
+
- Default image: `OPEN_SANDBOX_DEFAULT_IMAGE` fallback `ubuntu:22.04`; the effective image comes from the client (`MICROSANDBOX_IMAGE`).
|
|
94
|
+
- **TTL:** sandboxes expire after 600s idle (`OPEN_SANDBOX_DEFAULT_TIMEOUT`) and the server destroys the container. `PUT /api/sandboxes/:name` (ensure) recreates them; file operations do not. Container filesystem state is lost on rebuild unless using named volumes. Raise the TTL for long-running workloads.
|
|
95
|
+
- Image management endpoints are stubs (return empty / 501).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
services:
|
|
2
|
+
opensandbox-server:
|
|
3
|
+
image: opensandbox/server:latest
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
environment:
|
|
6
|
+
SANDBOX_CONFIG_PATH: /etc/opensandbox/config.toml
|
|
7
|
+
OPENSANDBOX_SERVER_API_KEY: ${SANDBOX_API_KEY:?set SANDBOX_API_KEY in .env}
|
|
8
|
+
volumes:
|
|
9
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
10
|
+
- ./sandbox.toml:/etc/opensandbox/config.toml:ro
|
|
11
|
+
- opensandbox-store:/data/opensandbox
|
|
12
|
+
ports:
|
|
13
|
+
- "8080:8080"
|
|
14
|
+
|
|
15
|
+
opensandbox-gateway:
|
|
16
|
+
image: ${GATEWAY_IMAGE:?set GATEWAY_IMAGE to the published gateway image}
|
|
17
|
+
restart: unless-stopped
|
|
18
|
+
environment:
|
|
19
|
+
HOST: 0.0.0.0
|
|
20
|
+
PORT: 4002
|
|
21
|
+
OPEN_SANDBOX_DOMAIN: opensandbox-server:8080
|
|
22
|
+
OPEN_SANDBOX_PROTOCOL: http
|
|
23
|
+
OPEN_SANDBOX_API_KEY: ${SANDBOX_API_KEY:?set SANDBOX_API_KEY in .env}
|
|
24
|
+
OPEN_SANDBOX_USE_SERVER_PROXY: "true"
|
|
25
|
+
GATEWAY_API_KEY: ${GATEWAY_API_KEY:-${SANDBOX_API_KEY}}
|
|
26
|
+
VOLUME_BASE_PATH: /data/opensandbox/volumes
|
|
27
|
+
volumes:
|
|
28
|
+
- ${VOLUME_DIR:-/data/opensandbox/volumes}:/data/opensandbox/volumes
|
|
29
|
+
ports:
|
|
30
|
+
- "4002:4002"
|
|
31
|
+
depends_on:
|
|
32
|
+
- opensandbox-server
|
|
33
|
+
|
|
34
|
+
volumes:
|
|
35
|
+
opensandbox-store:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[server]
|
|
2
|
+
host = "0.0.0.0"
|
|
3
|
+
port = 8080
|
|
4
|
+
# api_key is injected via the OPENSANDBOX_SERVER_API_KEY env var (see docker-compose.yml), not hardcoded here
|
|
5
|
+
|
|
6
|
+
[proxy]
|
|
7
|
+
# Docker Compose: the server container cannot route to sandbox bridge IPs on Linux hosts,
|
|
8
|
+
# so target host-mapped ports instead of internal bridge IPs (see [docker] host_ip).
|
|
9
|
+
resolve_internal = false
|
|
10
|
+
|
|
11
|
+
[log]
|
|
12
|
+
level = "INFO"
|
|
13
|
+
|
|
14
|
+
[runtime]
|
|
15
|
+
type = "docker"
|
|
16
|
+
execd_image = "opensandbox/execd:v1.1.0"
|
|
17
|
+
|
|
18
|
+
[storage]
|
|
19
|
+
# Must equal VOLUME_DIR on the host (Docker daemon view). Default: /data/opensandbox/volumes
|
|
20
|
+
# If you change VOLUME_DIR in .env, update this to match.
|
|
21
|
+
allowed_host_paths = ["/data/opensandbox/volumes"]
|
|
22
|
+
|
|
23
|
+
[store]
|
|
24
|
+
type = "sqlite"
|
|
25
|
+
path = "/data/opensandbox/opensandbox.db"
|
|
26
|
+
|
|
27
|
+
[docker]
|
|
28
|
+
network_mode = "bridge"
|
|
29
|
+
# Host address the server uses to reach sandbox host-mapped ports (resolve_internal=false).
|
|
30
|
+
# docker0 gateway is reachable from any container; change it if the host bridge subnet differs.
|
|
31
|
+
host_ip = "172.17.0.1"
|
|
32
|
+
port_range_min = 40000
|
|
33
|
+
port_range_max = 60000
|
|
34
|
+
drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"]
|
|
35
|
+
no_new_privileges = true
|
|
36
|
+
pids_limit = 4096
|
|
37
|
+
apparmor_profile = ""
|
|
38
|
+
seccomp_profile = ""
|
|
39
|
+
|
|
40
|
+
[ingress]
|
|
41
|
+
mode = "direct"
|
|
42
|
+
|
|
43
|
+
[egress]
|
|
44
|
+
image = "opensandbox/egress:v1.1.7"
|
|
45
|
+
mode = "dns"
|
|
46
|
+
readiness_timeout_seconds = 30.0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
services:
|
|
2
|
+
opensandbox-server:
|
|
3
|
+
image: opensandbox/server:latest
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
environment:
|
|
6
|
+
SANDBOX_CONFIG_PATH: /etc/opensandbox/config.toml
|
|
7
|
+
OPENSANDBOX_SERVER_API_KEY: ${SANDBOX_API_KEY:?set SANDBOX_API_KEY in .env}
|
|
8
|
+
volumes:
|
|
9
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
10
|
+
- ./sandbox.toml:/etc/opensandbox/config.toml:ro
|
|
11
|
+
- opensandbox-store:/data/opensandbox
|
|
12
|
+
ports:
|
|
13
|
+
- "8080:8080"
|
|
14
|
+
|
|
15
|
+
opensandbox-gateway:
|
|
16
|
+
image: ${GATEWAY_IMAGE:?set GATEWAY_IMAGE to the published gateway image}
|
|
17
|
+
restart: unless-stopped
|
|
18
|
+
environment:
|
|
19
|
+
HOST: 0.0.0.0
|
|
20
|
+
PORT: 4002
|
|
21
|
+
OPEN_SANDBOX_DOMAIN: opensandbox-server:8080
|
|
22
|
+
OPEN_SANDBOX_PROTOCOL: http
|
|
23
|
+
OPEN_SANDBOX_API_KEY: ${SANDBOX_API_KEY:?set SANDBOX_API_KEY in .env}
|
|
24
|
+
OPEN_SANDBOX_USE_SERVER_PROXY: "true"
|
|
25
|
+
GATEWAY_API_KEY: ${GATEWAY_API_KEY:-${SANDBOX_API_KEY}}
|
|
26
|
+
VOLUME_BASE_PATH: /data/opensandbox/volumes
|
|
27
|
+
volumes:
|
|
28
|
+
- ${VOLUME_DIR:-/data/opensandbox/volumes}:/data/opensandbox/volumes
|
|
29
|
+
ports:
|
|
30
|
+
- "4002:4002"
|
|
31
|
+
depends_on:
|
|
32
|
+
- opensandbox-server
|
|
33
|
+
|
|
34
|
+
volumes:
|
|
35
|
+
opensandbox-store:
|
package/package.json
CHANGED
package/sandbox.toml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[server]
|
|
2
|
+
host = "0.0.0.0"
|
|
3
|
+
port = 8080
|
|
4
|
+
# api_key is injected via the OPENSANDBOX_SERVER_API_KEY env var (see docker-compose.yml), not hardcoded here
|
|
5
|
+
|
|
6
|
+
[proxy]
|
|
7
|
+
# Docker Compose: the server container cannot route to sandbox bridge IPs on Linux hosts,
|
|
8
|
+
# so target host-mapped ports instead of internal bridge IPs (see [docker] host_ip).
|
|
9
|
+
resolve_internal = false
|
|
10
|
+
|
|
11
|
+
[log]
|
|
12
|
+
level = "INFO"
|
|
13
|
+
|
|
14
|
+
[runtime]
|
|
15
|
+
type = "docker"
|
|
16
|
+
execd_image = "opensandbox/execd:v1.1.0"
|
|
17
|
+
|
|
18
|
+
[storage]
|
|
19
|
+
# Must equal VOLUME_DIR on the host (Docker daemon view). Default: /data/opensandbox/volumes
|
|
20
|
+
# If you change VOLUME_DIR in .env, update this to match.
|
|
21
|
+
allowed_host_paths = ["/data/opensandbox/volumes"]
|
|
22
|
+
|
|
23
|
+
[store]
|
|
24
|
+
type = "sqlite"
|
|
25
|
+
path = "/data/opensandbox/opensandbox.db"
|
|
26
|
+
|
|
27
|
+
[docker]
|
|
28
|
+
network_mode = "bridge"
|
|
29
|
+
# Host address the server uses to reach sandbox host-mapped ports (resolve_internal=false).
|
|
30
|
+
# docker0 gateway is reachable from any container; change it if the host bridge subnet differs.
|
|
31
|
+
host_ip = "172.17.0.1"
|
|
32
|
+
port_range_min = 40000
|
|
33
|
+
port_range_max = 60000
|
|
34
|
+
drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"]
|
|
35
|
+
no_new_privileges = true
|
|
36
|
+
pids_limit = 4096
|
|
37
|
+
apparmor_profile = ""
|
|
38
|
+
seccomp_profile = ""
|
|
39
|
+
|
|
40
|
+
[ingress]
|
|
41
|
+
mode = "direct"
|
|
42
|
+
|
|
43
|
+
[egress]
|
|
44
|
+
image = "opensandbox/egress:v1.1.7"
|
|
45
|
+
mode = "dns"
|
|
46
|
+
readiness_timeout_seconds = 30.0
|