import http from "node:http"; import * as k8s from "@kubernetes/client-node"; const PORT = Number(process.env.PORT || 8787); const HOST = "0.0.0.0"; /** * @example const imgObj = { image: "gcc:14", installcommands: [aptUpdateAndInstall("gpp")], filename: "main.cpp", run: ... }, */ const aptUpdateAndInstall = (pkgs: string[] | string) => { const istr = typeof pkgs === 'string' ? pkgs : pkgs.join(" "); return `apt-get update \ && apt-get install -y --no-install-recommends ${istr} \ && rm -rf /var/lib/apt/lists/*;` } // allow-listed images and run commands per language const LANGS = { python: { image: "python:3.12-alpine", filename: "main.py", run: ["python", "main.py"] }, node: { image: "node:22-alpine", filename: "main.mjs", run: ["node", "main.mjs"] }, bun: { image: "oven/bun:1.2.2-alpine", filename: "main.ts", run: ["bun", "run", "main.ts"] }, bash: { image: "alpine:3.20", filename: "main.sh", run: ["sh", "main.sh"] }, ruby: { image: "ruby:3.3-alpine", filename: "main.rb", run: ["ruby", "main.rb"] }, go: { image: "golang:1.22-alpine", filename: "main.go", run: ["go run main.go"] }, rust: { image: "rust:1-alpine", filename: "main.rs", run: ["rustc -O main.rs -o main && ./main"] }, java: { image: "eclipse-temurin:21-jdk", filename: "Main.java", run: ["javac Main.java && java Main"] }, c: { image: "gcc:14", filename: "main.c", run: ["gcc -O2 main.c -o main.out && ./main.out"] }, cpp: { image: "gcc:14", filename: "main.cpp", run: ["g++ -O2 main.cpp -o main.out && ./main.out"] }, }; type langObj = { image: string, filename: string, run: string[], installcommands?: string[] } type fileType = { path: string, content: string } const NS = process.env.NAMESPACE || "ai"; const kc = new k8s.KubeConfig(); kc.loadFromDefault(); // in-cluster uses serviceaccount const batch = kc.makeApiClient(k8s.BatchV1Api), core = kc.makeApiClient(k8s.CoreV1Api); // basic openapi for open webui const OPENAPI = { openapi: "3.1.0", info: { title: "Container Code Runner", version: "1.0.0", description: "run source code inside a sandboxed container. important: provide pure source code only; do not wrap code in shell commands or pipelines." }, paths: { "/execute": { post: { operationId: "execute", summary: "Run code in a sandboxed container", // the model sees this text description: "use the language directly, not bash + the language. e.g., `#include...` (good) vs `echo '#include...' && gcc` (bad). pass only pure source text in `code`.", requestBody: { required: true, content: { "application/json": { schema: { type: "object", properties: { language: { type: "string", enum: Object.keys(LANGS), description: "the programming language to run. do not use 'bash' to wrap or invoke compilers/interpreters; select the actual language (e.g., 'c', 'cpp', 'python')." }, code: { type: "string", description: "pure source code only. do not include shell commands, redirections, pipes, or `echo`/`printf` wrappers. examples: good: `print('hi')`; bad: `echo \"print('hi')\" | python`." }, args: { type: "array", items: { type: "string" } }, files: { type: "array", items: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"], description: "optional supporting files. contents must be pure file text, not shell commands." } } }, required: ["language", "code"] } } } }, responses: { "200": { description: "Execution result", content: { "application/json": { schema: { type: "object", properties: { stdout: { type: "string" }, stderr: { type: "string" }, exitCode: { type: "integer" }, timedOut: { type: "boolean" } } } } } } } } } } }; function sendJson(res: any, status: number, obj: any) { const body = JSON.stringify(obj); res.writeHead(status, { "content-type": "application/json; charset=utf-8" }); res.end(body); } async function ensureImage(spec: langObj) { // note: 'docker run' has a --pull policy (missing|always|never), const { spawn } = await import("node:child_process"), DOCKER_BIN = process.env.DOCKER_BIN || "docker"; // check if image exists locally const inspect = spawn(DOCKER_BIN, ["image", "inspect", spec.image], { stdio: "ignore" }), ok = await new Promise((r) => inspect.on("close", (c) => r(c === 0))); if (ok) return; // pull with a bigger timeout the first time (4 minutes) await new Promise((resolve) => { const child = spawn(DOCKER_BIN, ["image", "pull", "--quiet", spec.image]); let timer = setTimeout(() => { child.kill("SIGKILL"); resolve(new Error("pull timeout")); }, 240_000); child.on("close", (code) => { clearTimeout(timer); resolve(code === 0 ? null : new Error(`pull failed: ${code}`)); }); }).then((err) => { if (err) throw err; }); // // dependancies // if (spec.installcommands) { // const commandFull = spec.installcommands.join(" && "); // await new Promise((resolve) => { // const child = spawn(DOCKER_BIN, ["image", "pull", "--quiet", spec.image]); // let timer = setTimeout(() => { // child.kill("SIGKILL"); // resolve(new Error("pull timeout")); // }, 240_000); // child.on("close", (code) => { // clearTimeout(timer); // resolve(code === 0 ? null : new Error(`pull failed: ${code}`)); // }); // }).then((err) => { // if (err) throw err; // }) // } } async function waitForJobPod(core: k8s.CoreV1Api, jobName: string): Promise { const labelSelector = `job-name=${jobName}`; for (; ;) { const pods = await core.listNamespacedPod({ namespace: NS, labelSelector }); const pod = pods.items.find((p) => p.status?.phase === "Running" || p.status?.phase === "Succeeded" || p.status?.phase === "Failed"); if (pod?.metadata?.name) return pod.metadata.name; await new Promise((r) => setTimeout(r, 400)); } }; async function waitForCompletionAndLogs(core: k8s.CoreV1Api, podName: string): Promise<{ status: string; stdout: string; stderr: string; }> { for (; ;) { const readReq = { name: podName, namespace: NS }, p = await core.readNamespacedPod(readReq), phase = p.status?.phase ?? "Pending"; if (phase === "Succeeded" || phase === "Failed") { const logs = await core.readNamespacedPodLog(readReq); // stderr is not separated by the api; you can split by stream if needed return { status: phase, stdout: logs, stderr: "" }; }; await new Promise((r) => setTimeout(r, 500)); } }; async function runInContainer({ language, code, args = [], files = [] }: { language: string, code: string, args: string[], files: fileType[] }) { if (!(language in LANGS)) throw new Error(`language not allowed: ${language}`); const spec = LANGS[language as keyof typeof LANGS]; // build the same shell script you already use const script = [ `echo ${JSON.stringify(Buffer.from(code, "utf8").toString("base64"))} | base64 -d > ${spec.filename}`, ...files.flatMap((f) => [ `mkdir -p "$(dirname "${f.path}")"`, `echo ${JSON.stringify(Buffer.from(f.content, "utf8").toString("base64"))} | base64 -d > "${f.path}"`, ]), `${spec.run.join(' ')} ${args.map((a) => JSON.stringify(a)).join(' ')}` ].join('\n'); const uid = crypto.randomUUID().slice(0, 8); const jobName = `coderun-${uid}`; // create a short-lived Job with tight security and resource caps const job: k8s.V1Job = { apiVersion: "batch/v1", kind: "Job", metadata: { name: jobName, namespace: NS }, spec: { ttlSecondsAfterFinished: 300, // auto-clean once done backoffLimit: 0, // no retries activeDeadlineSeconds: 25, // mirrors your 25s timeout template: { metadata: { labels: { app: "coderunner-task" } }, spec: { restartPolicy: "Never", securityContext: { runAsNonRoot: true, seccompProfile: { type: "RuntimeDefault" } }, containers: [{ name: "task", image: spec.image, command: ["sh", "-lc", script], resources: { requests: { cpu: "1", memory: "512Mi" }, limits: { cpu: "1", memory: "512Mi" } }, securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: ["ALL"] } } }] } } } }; await batch.createNamespacedJob({ namespace: NS, body: job }); // wait for pod to complete, then get logs const podName = await waitForJobPod(core, jobName); const { status, stdout, stderr } = await waitForCompletionAndLogs(core, podName); // delete job for hygiene (ttl also cleans it eventually) try { await batch.deleteNamespacedJob({ namespace: NS, propagationPolicy: "Background", name: jobName }); } catch (err) { console.error(err); }; return { stdout, stderr, exitCode: status === "Succeeded" ? 0 : 1, timedOut: status === "Failed" }; } const server = http.createServer(async (req, res) => { try { console.debug(`recieved ${req.method} request on ${req.url}`); if (req.method === "GET" && req.url === "/openapi.json") { sendJson(res, 200, OPENAPI); return; } if (req.method === "GET" && req.url === "/") { res.writeHead(200); res.end("Ok"); return; } if (req.method === "POST" && req.url === "/execute") { let body = ""; req.on("data", (c) => { body += c.toString(); }); req.on("end", async () => { try { const payload = JSON.parse(body || "{}"); const out = await runInContainer(payload); sendJson(res, 200, out); } catch (e: any) { sendJson(res, 400, { error: String(e?.message || e) }); } }); return; } res.writeHead(404); res.end("not found"); } catch (e: any) { sendJson(res, 500, { error: String(e?.message || e) }); } }); server.listen(PORT, HOST, () => { console.log(`[runner] listening on http://${HOST}:${PORT}`); });