From e70f996f57fde2536940c3fcc2726ecc3eb4832f Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sun, 8 Feb 2026 22:00:14 +0300 Subject: [PATCH] chore: Set up OpenClaw gateway in sandboxes Install pm2 and expose gateway port 18789 in Dockerfile. Add setupOpenClaw helpers for Cloudflare, Daytona, Deno and Vercel that write OpenClaw config/auth files, start the gateway (pm2 or background command), and log status. Add SANDBOX_ROOT, OPENCLAW_CONFIG and OPENCLAW_AUTH_PROFILES to runtime types and ProcessEnv. Add envalid to dependencies, update snapshots, keep sandboxes running, and add SIGINT handlers for graceful shutdown. --- .sandbox/cloudflare/Dockerfile | 3 +- .sandbox/cloudflare/src/index.ts | 34 ++++++++- .sandbox/cloudflare/worker-configuration.d.ts | 7 +- .sandbox/daytona/bun.lock | 3 + .sandbox/daytona/index.ts | 70 ++++++++++++++++-- .sandbox/daytona/package.json | 3 +- .sandbox/deno/main.ts | 41 ++++++++++- .sandbox/vercel/bun.lock | 5 ++ .sandbox/vercel/index.ts | 73 +++++++++++++++++-- .sandbox/vercel/package.json | 3 +- 10 files changed, 221 insertions(+), 21 deletions(-) diff --git a/.sandbox/cloudflare/Dockerfile b/.sandbox/cloudflare/Dockerfile index 337a498b..d83f8771 100644 --- a/.sandbox/cloudflare/Dockerfile +++ b/.sandbox/cloudflare/Dockerfile @@ -10,12 +10,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN curl -fsSL https://openclaw.ai/install.sh | bash || true -RUN npm install -g @anthropic-ai/claude-code +RUN npm install -g @anthropic-ai/claude-code pm2 WORKDIR /workspace # Required during local development to access exposed ports EXPOSE 8080 EXPOSE 3000 +EXPOSE 18789 ENTRYPOINT ["/sandbox"] diff --git a/.sandbox/cloudflare/src/index.ts b/.sandbox/cloudflare/src/index.ts index f68dabd8..f616c0c6 100644 --- a/.sandbox/cloudflare/src/index.ts +++ b/.sandbox/cloudflare/src/index.ts @@ -1,8 +1,10 @@ -import { getSandbox } from "@cloudflare/sandbox"; +import { getSandbox, Sandbox } from "@cloudflare/sandbox"; import consola from "consola"; export { Sandbox } from "@cloudflare/sandbox"; +const HOME = "/root"; + export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); @@ -12,7 +14,7 @@ export default { // Execute a shell command if (url.pathname === "/run") { - const HOME = "/root"; + await setupOpenClaw(sandbox); await sandbox.exec("mkdir -p $HOME/.ssh"); await sandbox.writeFile(`${HOME}/.ssh/id_rsa`, env.SSH_PRIVATE_KEY); await sandbox.writeFile(`${HOME}/.ssh/id_rsa.pub`, env.SSH_PUBLIC_KEY); @@ -66,3 +68,31 @@ export default { return new Response("Try /run or /file"); }, }; + +async function setupOpenClaw(sandbox: Sandbox) { + consola.info(`Setting up OpenClaw in sandbox...`); + await sandbox.exec("mkdir -p $HOME/.openclaw/agents/main/agent"); + await sandbox.writeFile( + `${HOME}/.openclaw/openclaw.json`, + process.env.OPENCLAW_CONFIG!.replace("OPENCLAW_WORKSPACE", `${HOME}/clawd`), + ); + await sandbox.writeFile( + `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, + process.env.OPENCLAW_AUTH_PROFILES!, + ); + + consola.info("OpenClaw configuration files written."); + + const start = await sandbox.exec( + 'pm2 start "openclaw gateway" --name "openclaw-gateway"', + ); + await new Promise((resolve) => setTimeout(resolve, 3000)); + const status = await sandbox.exec("openclaw gateway status"); + + consola.info(start.stdout); + consola.info(start.stderr); + consola.info(status.stdout); + consola.info(status.stderr); + + consola.info(`Sandbox setup complete.`); +} diff --git a/.sandbox/cloudflare/worker-configuration.d.ts b/.sandbox/cloudflare/worker-configuration.d.ts index 6a089886..a4f588a6 100644 --- a/.sandbox/cloudflare/worker-configuration.d.ts +++ b/.sandbox/cloudflare/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: fc7de8dc66857a5a298a86791b7745d0) +// Generated by Wrangler by running `wrangler types` (hash: e787ff6e8693c31a0011ce031e7f5718) // Runtime types generated with workerd@1.20260205.0 2025-05-06 nodejs_compat declare namespace Cloudflare { interface GlobalProps { @@ -9,6 +9,9 @@ declare namespace Cloudflare { interface Env { SSH_PRIVATE_KEY: string; SSH_PUBLIC_KEY: string; + SANDBOX_ROOT: string; + OPENCLAW_CONFIG: string; + OPENCLAW_AUTH_PROFILES: string; Sandbox: DurableObjectNamespace; } } @@ -17,7 +20,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/.sandbox/daytona/bun.lock b/.sandbox/daytona/bun.lock index e3d12620..dfe849d7 100644 --- a/.sandbox/daytona/bun.lock +++ b/.sandbox/daytona/bun.lock @@ -8,6 +8,7 @@ "@daytonaio/sdk": "^0.139.0", "chalk": "^5.6.2", "consola": "^3.4.2", + "envalid": "^8.1.1", }, "devDependencies": { "@types/bun": "latest", @@ -256,6 +257,8 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "envalid": ["envalid@8.1.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], diff --git a/.sandbox/daytona/index.ts b/.sandbox/daytona/index.ts index de8c3552..e7e639c4 100644 --- a/.sandbox/daytona/index.ts +++ b/.sandbox/daytona/index.ts @@ -1,7 +1,10 @@ -import { Daytona } from "@daytonaio/sdk"; +import { Daytona, Sandbox } from "@daytonaio/sdk"; import chalk from "chalk"; import consola from "consola"; +const HOME = "/home/daytona"; +const OPENCLAW_GATEWAY_PORT = 18789; + async function main() { // Initialize the SDK (uses environment variables by default) const daytona = new Daytona({ @@ -11,13 +14,21 @@ async function main() { // Create a new sandbox const sandbox = await daytona.create({ language: "typescript", - envVars: { NODE_ENV: "development" }, - snapshot: "daytona-openclaw-small", + snapshot: "daytona-openclaw-medium", }); + consola.info("Sandbox created with ID:", chalk.greenBright(sandbox.id)); - const HOME = "/home/daytona"; + consola.info("Installing PM2 in sandbox..."); + const pm2 = await sandbox.process.executeCommand("npm install -g pm2"); + consola.log(pm2.result); + await setupOpenClaw(sandbox); - consola.info("Sandbox created with ID:", chalk.greenBright(sandbox.id)); + process.on("SIGINT", async () => { + consola.info("Received SIGINT, shutting down sandbox..."); + await sandbox.stop(); + await sandbox.delete(); + process.exit(0); + }); await Promise.all([ sandbox.fs.uploadFile( @@ -56,9 +67,52 @@ async function main() { const response = await sandbox.process.executeCommand("ls -la rocksky"); consola.log(response.result); +} + +async function setupOpenClaw(sandbox: Sandbox) { + consola.info(`Setting up OpenClaw in sandbox...`); + await sandbox.process.executeCommand( + "mkdir -p $HOME/.openclaw/agents/main/agent", + ); + await sandbox.fs.uploadFile( + Buffer.from( + process.env.OPENCLAW_CONFIG!.replace( + "OPENCLAW_WORKSPACE", + `${HOME}/clawd`, + ), + "utf-8", + ), + `${HOME}/.openclaw/openclaw.json`, + ); + await sandbox.fs.uploadFile( + Buffer.from(process.env.OPENCLAW_AUTH_PROFILES!, "utf-8"), + `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, + ); - await sandbox.stop(); - await sandbox.delete(); + consola.info("OpenClaw configuration files written."); + + const start = await sandbox.process.executeCommand( + 'pm2 start "openclaw gateway" --name openclaw-gateway', + ); + consola.info(start.result); + + await new Promise((resolve) => setTimeout(resolve, 3000)); + + const status = await sandbox.process.executeCommand( + "openclaw gateway status", + ); + consola.info(status.result); + + const signedUrl = await sandbox.getSignedPreviewUrl( + OPENCLAW_GATEWAY_PORT, + 3600, + ); + + consola.info( + `OpenClaw Gateway is available at: ${chalk.greenBright(signedUrl.url)}`, + ); } -main().catch(console.error); +main().catch(consola.error); + +await new Promise(() => {}); diff --git a/.sandbox/daytona/package.json b/.sandbox/daytona/package.json index b86c1d37..a8af6d91 100644 --- a/.sandbox/daytona/package.json +++ b/.sandbox/daytona/package.json @@ -12,6 +12,7 @@ "dependencies": { "@daytonaio/sdk": "^0.139.0", "chalk": "^5.6.2", - "consola": "^3.4.2" + "consola": "^3.4.2", + "envalid": "^8.1.1" } } diff --git a/.sandbox/deno/main.ts b/.sandbox/deno/main.ts index 4f253bbe..a88e8766 100644 --- a/.sandbox/deno/main.ts +++ b/.sandbox/deno/main.ts @@ -5,6 +5,8 @@ import chalk from "chalk"; export async function main() { await using sandbox = await Sandbox.create({ root: Deno.env.get("SANDBOX_ROOT"), + port: 18789, + memory: "4GiB", }); const HOME = await sandbox.env.get("HOME"); @@ -13,6 +15,14 @@ export async function main() { consola.info("Sandbox created with ID:", chalk.greenBright(sandbox.id)); + Deno.addSignalListener("SIGINT", async () => { + consola.warn("SIGINT received. Cleaning up sandbox..."); + await sandbox.close(); + Deno.exit(0); + }); + + await setupOpenClaw(sandbox); + await sandbox.sh`mkdir -p $HOME/.ssh`; await sandbox.fs.writeTextFile( @@ -40,7 +50,36 @@ export async function main() { await sandbox.sh`which claude`; await sandbox.sh`which openclaw`; - await sandbox.close(); + // await sandbox.close(); + + await new Promise(() => {}); +} + +async function setupOpenClaw(sandbox: Sandbox) { + consola.info("Setting up OpenClaw in sandbox..."); + await sandbox.sh`mkdir -p $HOME/.openclaw/agents/main/agent`; + + const HOME = await sandbox.env.get("HOME"); + await sandbox.fs.writeTextFile( + `${HOME}/.openclaw/openclaw.json`, + Deno.env + .get("OPENCLAW_CONFIG")! + .replace("OPENCLAW_WORKSPACE", `${HOME}/clawd`), + ); + await sandbox.fs.writeTextFile( + `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, + Deno.env.get("OPENCLAW_AUTH_PROFILES")!, + ); + + consola.info("OpenClaw configuration files uploaded."); + + await sandbox.sh`pm2 start "openclaw gateway" --name "openclaw-gateway"`; + await new Promise((resolve) => setTimeout(resolve, 3000)); + await sandbox.sh`openclaw gateway status`; + + consola.info( + `Sandbox setup complete. You can now interact with it using the OpenClaw gateway: ${chalk.greenBright(sandbox.url)}`, + ); } // Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts diff --git a/.sandbox/vercel/bun.lock b/.sandbox/vercel/bun.lock index a8945229..f13275d8 100644 --- a/.sandbox/vercel/bun.lock +++ b/.sandbox/vercel/bun.lock @@ -8,6 +8,7 @@ "@vercel/sandbox": "^1.4.1", "chalk": "^5.6.2", "consola": "^3.4.2", + "envalid": "^8.1.1", }, "devDependencies": { "@types/bun": "latest", @@ -38,6 +39,8 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "envalid": ["envalid@8.1.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w=="], + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -58,6 +61,8 @@ "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="], diff --git a/.sandbox/vercel/index.ts b/.sandbox/vercel/index.ts index d1731591..fae8a1ed 100644 --- a/.sandbox/vercel/index.ts +++ b/.sandbox/vercel/index.ts @@ -2,6 +2,12 @@ import { Sandbox } from "@vercel/sandbox"; import consola from "consola"; import chalk from "chalk"; +const OPENCLAW_GATEWAY_PORT = 18789; + +const ports = { + ports: [OPENCLAW_GATEWAY_PORT], +}; + const sandbox = await Sandbox.create( process.env.SNAPSHOT_ID ? { @@ -9,10 +15,21 @@ const sandbox = await Sandbox.create( type: "snapshot", snapshotId: process.env.SNAPSHOT_ID, }, + ...ports, } - : undefined, + : ports, ); +process.on("SIGINT", async () => { + consola.info("Received SIGINT, shutting down sandbox..."); + try { + await sandbox.stop(); + } catch (e) { + consola.error("Error stopping Vercel Sandbox:", e); + } + process.exit(0); +}); + consola.info( "Vercel Sandbox created with ID:", chalk.greenBright(sandbox.sandboxId), @@ -20,6 +37,8 @@ consola.info( const HOME = "/home/vercel-sandbox"; +await setupOpenClaw(sandbox); + await sandbox.mkDir(`${HOME}/.ssh`); await sandbox.writeFiles([ @@ -102,8 +121,52 @@ await sandbox.runCommand({ stderr: process.stderr, }); -try { - await sandbox.stop(); -} catch (e) { - consola.error("Error stopping Vercel Sandbox:", e); +async function setupOpenClaw(sandbox: Sandbox) { + consola.info("Setting up OpenClaw in sandbox..."); + await sandbox.runCommand({ + cmd: "mkdir", + args: ["-p", `${HOME}/.openclaw/agents/main/agent`], + stdout: process.stdout, + stderr: process.stderr, + }); + + await sandbox.writeFiles([ + { + path: `${HOME}/.openclaw/openclaw.json`, + content: Buffer.from( + process.env.OPENCLAW_CONFIG!.replace( + "OPENCLAW_WORKSPACE", + `${HOME}/clawd`, + ), + "utf-8", + ), + }, + { + path: `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, + content: Buffer.from(process.env.OPENCLAW_AUTH_PROFILES!, "utf-8"), + }, + ]); + + consola.info("OpenClaw configuration files written."); + + await sandbox.runCommand({ + cmd: "bash", + args: ["-c", "openclaw gateway"], + detached: true, + stdout: process.stdout, + stderr: process.stderr, + }); + + await sandbox.runCommand({ + cmd: "openclaw", + args: ["gateway", "status"], + stdout: process.stdout, + stderr: process.stderr, + }); + + consola.info( + `Sandbox setup complete. You can now interact with it using the OpenClaw gateway: ${chalk.greenBright(sandbox.domain(OPENCLAW_GATEWAY_PORT))}`, + ); } + +await new Promise(() => {}); diff --git a/.sandbox/vercel/package.json b/.sandbox/vercel/package.json index d0852fc2..48ee5cc1 100644 --- a/.sandbox/vercel/package.json +++ b/.sandbox/vercel/package.json @@ -12,6 +12,7 @@ "dependencies": { "@vercel/sandbox": "^1.4.1", "chalk": "^5.6.2", - "consola": "^3.4.2" + "consola": "^3.4.2", + "envalid": "^8.1.1" } } -- 2.51.2