A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz

Enable SSH and Git operations in Cloudflare sandbox

Install openssh-client in the sandbox image and add consola for logging.
Read SSH_PRIVATE_KEY/SSH_PUBLIC_KEY env vars to write SSH keys, set
known_hosts and git config, then clone the repository in the /run
handler.
Expose SSH env types in worker typings and update ProcessEnv mapping.

+49 -7
+3
.sandbox/cloudflare/Dockerfile
··· 1 FROM docker.io/cloudflare/sandbox:0.7.0 2 3 # Required during local development to access exposed ports 4 EXPOSE 8080
··· 1 FROM docker.io/cloudflare/sandbox:0.7.0 2 3 + RUN apt-get update && apt-get install -y --no-install-recommends \ 4 + openssh-client 5 + 6 # Required during local development to access exposed ports 7 EXPOSE 8080
+5
.sandbox/cloudflare/bun.lock
··· 4 "workspaces": { 5 "": { 6 "name": "@cloudflare/sandbox-minimal-example", 7 "devDependencies": { 8 "@cloudflare/sandbox": "*", 9 "@types/node": "^24.10.11", ··· 156 "@types/node": ["@types/node@24.10.11", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ=="], 157 158 "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 159 160 "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], 161
··· 4 "workspaces": { 5 "": { 6 "name": "@cloudflare/sandbox-minimal-example", 7 + "dependencies": { 8 + "consola": "^3.4.2", 9 + }, 10 "devDependencies": { 11 "@cloudflare/sandbox": "*", 12 "@types/node": "^24.10.11", ··· 159 "@types/node": ["@types/node@24.10.11", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ=="], 160 161 "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 162 + 163 + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 164 165 "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], 166
+4 -1
.sandbox/cloudflare/package.json
··· 18 "wrangler": "^4.63.0" 19 }, 20 "author": "", 21 - "license": "MIT" 22 }
··· 18 "wrangler": "^4.63.0" 19 }, 20 "author": "", 21 + "license": "MIT", 22 + "dependencies": { 23 + "consola": "^3.4.2" 24 + } 25 }
+28 -5
.sandbox/cloudflare/src/index.ts
··· 1 import { getSandbox } from "@cloudflare/sandbox"; 2 3 export { Sandbox } from "@cloudflare/sandbox"; 4 ··· 11 12 // Execute a shell command 13 if (url.pathname === "/run") { 14 - const result = await sandbox.exec('echo "2 + 3 = $((2 + 3))"'); 15 return Response.json({ 16 - output: result.stdout, 17 - error: result.stderr, 18 - exitCode: result.exitCode, 19 - success: result.success, 20 }); 21 } 22 ··· 28 content: file.content, 29 }); 30 } 31 32 return new Response("Try /run or /file"); 33 },
··· 1 import { getSandbox } from "@cloudflare/sandbox"; 2 + import consola from "consola"; 3 4 export { Sandbox } from "@cloudflare/sandbox"; 5 ··· 12 13 // Execute a shell command 14 if (url.pathname === "/run") { 15 + const HOME = "/root"; 16 + await sandbox.exec("mkdir -p $HOME/.ssh"); 17 + await sandbox.writeFile(`${HOME}/.ssh/id_rsa`, env.SSH_PRIVATE_KEY); 18 + await sandbox.writeFile(`${HOME}/.ssh/id_rsa.pub`, env.SSH_PUBLIC_KEY); 19 + await sandbox.exec("chmod 600 $HOME/.ssh/id_rsa"); 20 + await sandbox.exec( 21 + "ssh-keyscan -t rsa tangled.org >> $HOME/.ssh/known_hosts", 22 + ); 23 + await sandbox.exec("git config --global user.name 'Cloudflare Sandbox'"); 24 + await sandbox.exec( 25 + "git config --global user.email 'tsiry.sndr@rocksky.app'", 26 + ); 27 + consola.info("SSH keys uploaded to sandbox."); 28 + 29 + consola.info("Sandbox environment configured for Git operations."); 30 + consola.info("Cloning repository..."); 31 + const clone = await sandbox.exec( 32 + "git clone git@tangled.org:rocksky.app/rocksky rocksky -b main", 33 + ); 34 + consola.log(clone.stdout); 35 + const ls = await sandbox.exec("ls -la rocksky"); 36 return Response.json({ 37 + output: ls.stdout, 38 + error: ls.stderr, 39 + exitCode: ls.exitCode, 40 + success: ls.success, 41 }); 42 } 43 ··· 49 content: file.content, 50 }); 51 } 52 + 53 + sandbox.destroy(); 54 55 return new Response("Try /run or /file"); 56 },
+9 -1
.sandbox/cloudflare/worker-configuration.d.ts
··· 1 /* eslint-disable */ 2 - // Generated by Wrangler by running `wrangler types` (hash: 6ac4664e0b184e123d5c5ac50212c5b8) 3 // Runtime types generated with workerd@1.20260205.0 2025-05-06 nodejs_compat 4 declare namespace Cloudflare { 5 interface GlobalProps { ··· 7 durableNamespaces: "Sandbox"; 8 } 9 interface Env { 10 Sandbox: DurableObjectNamespace<import("./src/index").Sandbox>; 11 } 12 } 13 interface Env extends Cloudflare.Env {} 14 15 // Begin runtime types 16 /*! *****************************************************************************
··· 1 /* eslint-disable */ 2 + // Generated by Wrangler by running `wrangler types` (hash: fc7de8dc66857a5a298a86791b7745d0) 3 // Runtime types generated with workerd@1.20260205.0 2025-05-06 nodejs_compat 4 declare namespace Cloudflare { 5 interface GlobalProps { ··· 7 durableNamespaces: "Sandbox"; 8 } 9 interface Env { 10 + SSH_PRIVATE_KEY: string; 11 + SSH_PUBLIC_KEY: string; 12 Sandbox: DurableObjectNamespace<import("./src/index").Sandbox>; 13 } 14 } 15 interface Env extends Cloudflare.Env {} 16 + type StringifyValues<EnvType extends Record<string, unknown>> = { 17 + [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; 18 + }; 19 + declare namespace NodeJS { 20 + interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SSH_PRIVATE_KEY" | "SSH_PUBLIC_KEY">> {} 21 + } 22 23 // Begin runtime types 24 /*! *****************************************************************************