A decentralized music tracking and discovery platform built on AT Protocol 🎵

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.

+221 -21
+2 -1
.sandbox/cloudflare/Dockerfile
··· 10 10 11 11 RUN curl -fsSL https://openclaw.ai/install.sh | bash || true 12 12 13 - RUN npm install -g @anthropic-ai/claude-code 13 + RUN npm install -g @anthropic-ai/claude-code pm2 14 14 15 15 WORKDIR /workspace 16 16 17 17 # Required during local development to access exposed ports 18 18 EXPOSE 8080 19 19 EXPOSE 3000 20 + EXPOSE 18789 20 21 21 22 ENTRYPOINT ["/sandbox"]
+32 -2
.sandbox/cloudflare/src/index.ts
··· 1 - import { getSandbox } from "@cloudflare/sandbox"; 1 + import { getSandbox, Sandbox } from "@cloudflare/sandbox"; 2 2 import consola from "consola"; 3 3 4 4 export { Sandbox } from "@cloudflare/sandbox"; 5 + 6 + const HOME = "/root"; 5 7 6 8 export default { 7 9 async fetch(request: Request, env: Env): Promise<Response> { ··· 12 14 13 15 // Execute a shell command 14 16 if (url.pathname === "/run") { 15 - const HOME = "/root"; 17 + await setupOpenClaw(sandbox); 16 18 await sandbox.exec("mkdir -p $HOME/.ssh"); 17 19 await sandbox.writeFile(`${HOME}/.ssh/id_rsa`, env.SSH_PRIVATE_KEY); 18 20 await sandbox.writeFile(`${HOME}/.ssh/id_rsa.pub`, env.SSH_PUBLIC_KEY); ··· 66 68 return new Response("Try /run or /file"); 67 69 }, 68 70 }; 71 + 72 + async function setupOpenClaw(sandbox: Sandbox) { 73 + consola.info(`Setting up OpenClaw in sandbox...`); 74 + await sandbox.exec("mkdir -p $HOME/.openclaw/agents/main/agent"); 75 + await sandbox.writeFile( 76 + `${HOME}/.openclaw/openclaw.json`, 77 + process.env.OPENCLAW_CONFIG!.replace("OPENCLAW_WORKSPACE", `${HOME}/clawd`), 78 + ); 79 + await sandbox.writeFile( 80 + `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, 81 + process.env.OPENCLAW_AUTH_PROFILES!, 82 + ); 83 + 84 + consola.info("OpenClaw configuration files written."); 85 + 86 + const start = await sandbox.exec( 87 + 'pm2 start "openclaw gateway" --name "openclaw-gateway"', 88 + ); 89 + await new Promise((resolve) => setTimeout(resolve, 3000)); 90 + const status = await sandbox.exec("openclaw gateway status"); 91 + 92 + consola.info(start.stdout); 93 + consola.info(start.stderr); 94 + consola.info(status.stdout); 95 + consola.info(status.stderr); 96 + 97 + consola.info(`Sandbox setup complete.`); 98 + }
+5 -2
.sandbox/cloudflare/worker-configuration.d.ts
··· 1 1 /* eslint-disable */ 2 - // Generated by Wrangler by running `wrangler types` (hash: fc7de8dc66857a5a298a86791b7745d0) 2 + // Generated by Wrangler by running `wrangler types` (hash: e787ff6e8693c31a0011ce031e7f5718) 3 3 // Runtime types generated with workerd@1.20260205.0 2025-05-06 nodejs_compat 4 4 declare namespace Cloudflare { 5 5 interface GlobalProps { ··· 9 9 interface Env { 10 10 SSH_PRIVATE_KEY: string; 11 11 SSH_PUBLIC_KEY: string; 12 + SANDBOX_ROOT: string; 13 + OPENCLAW_CONFIG: string; 14 + OPENCLAW_AUTH_PROFILES: string; 12 15 Sandbox: DurableObjectNamespace<import("./src/index").Sandbox>; 13 16 } 14 17 } ··· 17 20 [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; 18 21 }; 19 22 declare namespace NodeJS { 20 - interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SSH_PRIVATE_KEY" | "SSH_PUBLIC_KEY">> {} 23 + interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SSH_PRIVATE_KEY" | "SSH_PUBLIC_KEY" | "SANDBOX_ROOT" | "OPENCLAW_CONFIG" | "OPENCLAW_AUTH_PROFILES">> {} 21 24 } 22 25 23 26 // Begin runtime types
+3
.sandbox/daytona/bun.lock
··· 8 8 "@daytonaio/sdk": "^0.139.0", 9 9 "chalk": "^5.6.2", 10 10 "consola": "^3.4.2", 11 + "envalid": "^8.1.1", 11 12 }, 12 13 "devDependencies": { 13 14 "@types/bun": "latest", ··· 255 256 "dotenv": ["dotenv@17.2.4", "", {}, "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw=="], 256 257 257 258 "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=="], 259 + 260 + "envalid": ["envalid@8.1.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w=="], 258 261 259 262 "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 260 263
+62 -8
.sandbox/daytona/index.ts
··· 1 - import { Daytona } from "@daytonaio/sdk"; 1 + import { Daytona, Sandbox } from "@daytonaio/sdk"; 2 2 import chalk from "chalk"; 3 3 import consola from "consola"; 4 + 5 + const HOME = "/home/daytona"; 6 + const OPENCLAW_GATEWAY_PORT = 18789; 4 7 5 8 async function main() { 6 9 // Initialize the SDK (uses environment variables by default) ··· 11 14 // Create a new sandbox 12 15 const sandbox = await daytona.create({ 13 16 language: "typescript", 14 - envVars: { NODE_ENV: "development" }, 15 - snapshot: "daytona-openclaw-small", 17 + snapshot: "daytona-openclaw-medium", 16 18 }); 19 + consola.info("Sandbox created with ID:", chalk.greenBright(sandbox.id)); 17 20 18 - const HOME = "/home/daytona"; 21 + consola.info("Installing PM2 in sandbox..."); 22 + const pm2 = await sandbox.process.executeCommand("npm install -g pm2"); 23 + consola.log(pm2.result); 24 + await setupOpenClaw(sandbox); 19 25 20 - consola.info("Sandbox created with ID:", chalk.greenBright(sandbox.id)); 26 + process.on("SIGINT", async () => { 27 + consola.info("Received SIGINT, shutting down sandbox..."); 28 + await sandbox.stop(); 29 + await sandbox.delete(); 30 + process.exit(0); 31 + }); 21 32 22 33 await Promise.all([ 23 34 sandbox.fs.uploadFile( ··· 56 67 57 68 const response = await sandbox.process.executeCommand("ls -la rocksky"); 58 69 consola.log(response.result); 70 + } 59 71 60 - await sandbox.stop(); 61 - await sandbox.delete(); 72 + async function setupOpenClaw(sandbox: Sandbox) { 73 + consola.info(`Setting up OpenClaw in sandbox...`); 74 + await sandbox.process.executeCommand( 75 + "mkdir -p $HOME/.openclaw/agents/main/agent", 76 + ); 77 + await sandbox.fs.uploadFile( 78 + Buffer.from( 79 + process.env.OPENCLAW_CONFIG!.replace( 80 + "OPENCLAW_WORKSPACE", 81 + `${HOME}/clawd`, 82 + ), 83 + "utf-8", 84 + ), 85 + `${HOME}/.openclaw/openclaw.json`, 86 + ); 87 + await sandbox.fs.uploadFile( 88 + Buffer.from(process.env.OPENCLAW_AUTH_PROFILES!, "utf-8"), 89 + `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, 90 + ); 91 + 92 + consola.info("OpenClaw configuration files written."); 93 + 94 + const start = await sandbox.process.executeCommand( 95 + 'pm2 start "openclaw gateway" --name openclaw-gateway', 96 + ); 97 + consola.info(start.result); 98 + 99 + await new Promise((resolve) => setTimeout(resolve, 3000)); 100 + 101 + const status = await sandbox.process.executeCommand( 102 + "openclaw gateway status", 103 + ); 104 + consola.info(status.result); 105 + 106 + const signedUrl = await sandbox.getSignedPreviewUrl( 107 + OPENCLAW_GATEWAY_PORT, 108 + 3600, 109 + ); 110 + 111 + consola.info( 112 + `OpenClaw Gateway is available at: ${chalk.greenBright(signedUrl.url)}`, 113 + ); 62 114 } 63 115 64 - main().catch(console.error); 116 + main().catch(consola.error); 117 + 118 + await new Promise(() => {});
+2 -1
.sandbox/daytona/package.json
··· 12 12 "dependencies": { 13 13 "@daytonaio/sdk": "^0.139.0", 14 14 "chalk": "^5.6.2", 15 - "consola": "^3.4.2" 15 + "consola": "^3.4.2", 16 + "envalid": "^8.1.1" 16 17 } 17 18 }
+40 -1
.sandbox/deno/main.ts
··· 5 5 export async function main() { 6 6 await using sandbox = await Sandbox.create({ 7 7 root: Deno.env.get("SANDBOX_ROOT"), 8 + port: 18789, 9 + memory: "4GiB", 8 10 }); 9 11 10 12 const HOME = await sandbox.env.get("HOME"); ··· 12 14 await sandbox.env.set("PATH", `${HOME}/.npm-global/bin:/usr/bin:${PATH}`); 13 15 14 16 consola.info("Sandbox created with ID:", chalk.greenBright(sandbox.id)); 17 + 18 + Deno.addSignalListener("SIGINT", async () => { 19 + consola.warn("SIGINT received. Cleaning up sandbox..."); 20 + await sandbox.close(); 21 + Deno.exit(0); 22 + }); 23 + 24 + await setupOpenClaw(sandbox); 15 25 16 26 await sandbox.sh`mkdir -p $HOME/.ssh`; 17 27 ··· 40 50 await sandbox.sh`which claude`; 41 51 await sandbox.sh`which openclaw`; 42 52 43 - await sandbox.close(); 53 + // await sandbox.close(); 54 + 55 + await new Promise(() => {}); 56 + } 57 + 58 + async function setupOpenClaw(sandbox: Sandbox) { 59 + consola.info("Setting up OpenClaw in sandbox..."); 60 + await sandbox.sh`mkdir -p $HOME/.openclaw/agents/main/agent`; 61 + 62 + const HOME = await sandbox.env.get("HOME"); 63 + await sandbox.fs.writeTextFile( 64 + `${HOME}/.openclaw/openclaw.json`, 65 + Deno.env 66 + .get("OPENCLAW_CONFIG")! 67 + .replace("OPENCLAW_WORKSPACE", `${HOME}/clawd`), 68 + ); 69 + await sandbox.fs.writeTextFile( 70 + `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, 71 + Deno.env.get("OPENCLAW_AUTH_PROFILES")!, 72 + ); 73 + 74 + consola.info("OpenClaw configuration files uploaded."); 75 + 76 + await sandbox.sh`pm2 start "openclaw gateway" --name "openclaw-gateway"`; 77 + await new Promise((resolve) => setTimeout(resolve, 3000)); 78 + await sandbox.sh`openclaw gateway status`; 79 + 80 + consola.info( 81 + `Sandbox setup complete. You can now interact with it using the OpenClaw gateway: ${chalk.greenBright(sandbox.url)}`, 82 + ); 44 83 } 45 84 46 85 // Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
+5
.sandbox/vercel/bun.lock
··· 8 8 "@vercel/sandbox": "^1.4.1", 9 9 "chalk": "^5.6.2", 10 10 "consola": "^3.4.2", 11 + "envalid": "^8.1.1", 11 12 }, 12 13 "devDependencies": { 13 14 "@types/bun": "latest", ··· 38 39 39 40 "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 40 41 42 + "envalid": ["envalid@8.1.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w=="], 43 + 41 44 "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], 42 45 43 46 "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], ··· 57 60 "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], 58 61 59 62 "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], 63 + 64 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 60 65 61 66 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 62 67
+68 -5
.sandbox/vercel/index.ts
··· 2 2 import consola from "consola"; 3 3 import chalk from "chalk"; 4 4 5 + const OPENCLAW_GATEWAY_PORT = 18789; 6 + 7 + const ports = { 8 + ports: [OPENCLAW_GATEWAY_PORT], 9 + }; 10 + 5 11 const sandbox = await Sandbox.create( 6 12 process.env.SNAPSHOT_ID 7 13 ? { ··· 9 15 type: "snapshot", 10 16 snapshotId: process.env.SNAPSHOT_ID, 11 17 }, 18 + ...ports, 12 19 } 13 - : undefined, 20 + : ports, 14 21 ); 15 22 23 + process.on("SIGINT", async () => { 24 + consola.info("Received SIGINT, shutting down sandbox..."); 25 + try { 26 + await sandbox.stop(); 27 + } catch (e) { 28 + consola.error("Error stopping Vercel Sandbox:", e); 29 + } 30 + process.exit(0); 31 + }); 32 + 16 33 consola.info( 17 34 "Vercel Sandbox created with ID:", 18 35 chalk.greenBright(sandbox.sandboxId), 19 36 ); 20 37 21 38 const HOME = "/home/vercel-sandbox"; 39 + 40 + await setupOpenClaw(sandbox); 22 41 23 42 await sandbox.mkDir(`${HOME}/.ssh`); 24 43 ··· 102 121 stderr: process.stderr, 103 122 }); 104 123 105 - try { 106 - await sandbox.stop(); 107 - } catch (e) { 108 - consola.error("Error stopping Vercel Sandbox:", e); 124 + async function setupOpenClaw(sandbox: Sandbox) { 125 + consola.info("Setting up OpenClaw in sandbox..."); 126 + await sandbox.runCommand({ 127 + cmd: "mkdir", 128 + args: ["-p", `${HOME}/.openclaw/agents/main/agent`], 129 + stdout: process.stdout, 130 + stderr: process.stderr, 131 + }); 132 + 133 + await sandbox.writeFiles([ 134 + { 135 + path: `${HOME}/.openclaw/openclaw.json`, 136 + content: Buffer.from( 137 + process.env.OPENCLAW_CONFIG!.replace( 138 + "OPENCLAW_WORKSPACE", 139 + `${HOME}/clawd`, 140 + ), 141 + "utf-8", 142 + ), 143 + }, 144 + { 145 + path: `${HOME}/.openclaw/agents/main/agent/auth-profiles.json`, 146 + content: Buffer.from(process.env.OPENCLAW_AUTH_PROFILES!, "utf-8"), 147 + }, 148 + ]); 149 + 150 + consola.info("OpenClaw configuration files written."); 151 + 152 + await sandbox.runCommand({ 153 + cmd: "bash", 154 + args: ["-c", "openclaw gateway"], 155 + detached: true, 156 + stdout: process.stdout, 157 + stderr: process.stderr, 158 + }); 159 + 160 + await sandbox.runCommand({ 161 + cmd: "openclaw", 162 + args: ["gateway", "status"], 163 + stdout: process.stdout, 164 + stderr: process.stderr, 165 + }); 166 + 167 + consola.info( 168 + `Sandbox setup complete. You can now interact with it using the OpenClaw gateway: ${chalk.greenBright(sandbox.domain(OPENCLAW_GATEWAY_PORT))}`, 169 + ); 109 170 } 171 + 172 + await new Promise(() => {});
+2 -1
.sandbox/vercel/package.json
··· 12 12 "dependencies": { 13 13 "@vercel/sandbox": "^1.4.1", 14 14 "chalk": "^5.6.2", 15 - "consola": "^3.4.2" 15 + "consola": "^3.4.2", 16 + "envalid": "^8.1.1" 16 17 } 17 18 }