2025-09-10 16:26:35 -04:00
import http from "node:http" ;
2025-09-12 11:20:18 -04:00
import * as k8s from "@kubernetes/client-node" ;
2025-09-10 16:26:35 -04:00
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
}
2025-09-12 11:20:18 -04:00
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 ) ;
2025-09-10 16:26:35 -04:00
// 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" }
}
}
}
}
}
}
}
}
}
} ;
2025-09-12 11:20:18 -04:00
function sendJson ( res : any , status : number , obj : any ) {
2025-09-10 16:26:35 -04:00
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;
// })
// }
}
2025-09-12 11:20:18 -04:00
async function waitForJobPod ( core : k8s.CoreV1Api , jobName : string ) : Promise < string > {
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 ) ) ;
}
} ;
2025-09-10 16:26:35 -04:00
async function runInContainer ( { language , code , args = [ ] , files = [ ] } : {
2025-09-12 11:20:18 -04:00
language : string , code : string , args : string [ ] , files : fileType [ ]
2025-09-10 16:26:35 -04:00
} ) {
2025-09-12 11:20:18 -04:00
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
2025-09-10 16:26:35 -04:00
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' ) ;
2025-09-12 11:20:18 -04:00
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" ] }
}
} ]
}
}
}
} ;
2025-09-10 16:26:35 -04:00
2025-09-12 11:20:18 -04:00
await batch . createNamespacedJob ( {
namespace : NS ,
body : job
2025-09-10 16:26:35 -04:00
} ) ;
2025-09-12 11:20:18 -04:00
// 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"
} ;
2025-09-10 16:26:35 -04:00
}
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 ) ;
2025-09-12 11:20:18 -04:00
} catch ( e : any ) {
2025-09-10 16:26:35 -04:00
sendJson ( res , 400 , { error : String ( e ? . message || e ) } ) ;
}
} ) ;
return ;
}
res . writeHead ( 404 ) ;
res . end ( "not found" ) ;
2025-09-12 11:20:18 -04:00
} catch ( e : any ) {
2025-09-10 16:26:35 -04:00
sendJson ( res , 500 , { error : String ( e ? . message || e ) } ) ;
}
} ) ;
server . listen ( PORT , HOST , ( ) = > {
console . log ( ` [runner] listening on http:// ${ HOST } : ${ PORT } ` ) ;
} ) ;