Spaces:
Build error
Build error
subham
Sync local n8n deployment: refactor to single Dockerfile, add runners, sandbox, cleanup legacy compose/nginx
4c98a98 Download scripts/sandbox-runner.js from Subham9126/n8n: direct link, hf CLI and curl.
- Browser
- Download file 4.67 kB
-
https://huggingface.co/spaces/Subham9126/n8n/resolve/main/scripts/sandbox-runner.js
- Command line
-
hf download hf://spaces/Subham9126/n8n/scripts/sandbox-runner.js
-
curl -L -o sandbox-runner.js https://huggingface.co/spaces/Subham9126/n8n/resolve/main/scripts/sandbox-runner.js
4.67 kB
| // sandbox-runner.js — rootless isolated execution (bwrap if available, else direct) | |
| // Listens 127.0.0.1:9091 POST /run {code, language, timeoutMs} | |
| const http = require('http'); | |
| const fs = require('fs'); | |
| const os = require('os'); | |
| const path = require('path'); | |
| const { spawn } = require('child_process'); | |
| const PORT = parseInt(process.env.SANDBOX_RUNNER_PORT || '9091', 10); | |
| const TMP_BASE = process.env.SANDBOX_TMP || '/tmp/sandbox'; | |
| const PYTHON_BIN = process.env.PYTHON_BIN || '/opt/runners/task-runner-python/.venv/bin/python'; | |
| const NODE_BIN = process.env.NODE_BIN || '/usr/local/bin/node'; | |
| function hasBwrap(){ try{ fs.accessSync('/usr/bin/bwrap', fs.constants.X_OK); return true;}catch{ return false; } } | |
| function canUseBwrap(){ | |
| if (!hasBwrap()) return false; | |
| // test if unprivileged namespaces allowed (kernel) | |
| try{ | |
| const { spawnSync } = require('child_process'); | |
| const r = spawnSync('/usr/bin/bwrap', ['--unshare-all','--die-with-parent','--ro-bind','/','/','--tmpfs','/tmp','--','/bin/true'], {timeout:2000}); | |
| return r.status===0; | |
| }catch{ return false; } | |
| } | |
| const USE_BWRAP = canUseBwrap(); | |
| console.log(`[sandbox-runner] bwrap=${USE_BWRAP} python=${PYTHON_BIN}`); | |
| function runInSandbox(code, language, timeoutMs=30000){ | |
| return new Promise((resolve)=>{ | |
| const id = Math.random().toString(36).slice(2,9); | |
| const dir = path.join(TMP_BASE, id); | |
| fs.mkdirSync(dir, {recursive:true}); | |
| const timeout = Math.min(Math.max(timeoutMs, 1000), 120000); | |
| let cmd, args, opts={}; | |
| // Write code file | |
| if (language==='python' || language==='py'){ | |
| const f = path.join(dir,'script.py'); | |
| fs.writeFileSync(f, code); | |
| if (USE_BWRAP){ | |
| cmd='/usr/bin/bwrap'; | |
| args=['--unshare-all','--die-with-parent','--ro-bind','/','/','--tmpfs','/tmp','--proc','/proc','--dev','/dev','--bind',dir,dir,'--chdir',dir,'--', PYTHON_BIN, '-I','-B', f]; | |
| } else { | |
| cmd=PYTHON_BIN; args=['-I','-B', f]; opts.cwd=dir; | |
| } | |
| } else { | |
| // javascript / typescript | |
| const f = path.join(dir,'script.js'); | |
| fs.writeFileSync(f, code); | |
| if (USE_BWRAP){ | |
| cmd='/usr/bin/bwrap'; | |
| args=['--unshare-all','--die-with-parent','--ro-bind','/','/','--tmpfs','/tmp','--proc','/proc','--dev','/dev','--bind',dir,dir,'--chdir',dir,'--', NODE_BIN, '--disallow-code-generation-from-strings','--disable-proto=delete', f]; | |
| } else { | |
| cmd=NODE_BIN; args=['--disallow-code-generation-from-strings','--disable-proto=delete', f]; opts.cwd=dir; | |
| } | |
| } | |
| let stdout='', stderr='', killed=false; | |
| const child = spawn(cmd, args, {...opts, timeout}); | |
| const timer = setTimeout(()=>{ killed=true; try{child.kill('SIGKILL');}catch{} }, timeout+500); | |
| child.stdout.on('data',d=>{ stdout += d.toString().slice(0,200000); }); | |
| child.stderr.on('data',d=>{ stderr += d.toString().slice(0,200000); }); | |
| child.on('close',(code)=>{ | |
| clearTimeout(timer); | |
| // cleanup | |
| try{ fs.rmSync(dir,{recursive:true,force:true});}catch{} | |
| resolve({ | |
| exitCode: killed? 124 : (code===null?1:code), | |
| stdout: stdout.slice(0,100000), | |
| stderr: stderr.slice(0,100000) + (killed? `\n[timeout ${timeout}ms]`:'' ), | |
| executionId: id, | |
| bwrap: USE_BWRAP | |
| }); | |
| }); | |
| child.on('error',(e)=>{ | |
| clearTimeout(timer); | |
| try{ fs.rmSync(dir,{recursive:true,force:true});}catch{} | |
| resolve({exitCode:1, stdout:'', stderr: e.message, executionId:id}); | |
| }); | |
| }); | |
| } | |
| const server=http.createServer((req,res)=>{ | |
| const url=new URL(req.url,`http://localhost:${PORT}`); | |
| if ((url.pathname==='/'||url.pathname==='/healthz'||url.pathname==='/health') && req.method==='GET'){ | |
| res.writeHead(200,{'Content-Type':'application/json'}); res.end(JSON.stringify({status:'ok', bwrap:USE_BWRAP, pid:process.pid})); return; | |
| } | |
| if (url.pathname==='/run' && req.method==='POST'){ | |
| let body=''; req.on('data',c=>body+=c); req.on('end', async()=>{ | |
| try{ | |
| const {code, language, timeoutMs}=JSON.parse(body||'{}'); | |
| if(!code){ res.writeHead(400); res.end(JSON.stringify({error:'missing code'})); return;} | |
| const r=await runInSandbox(code, language||'python', timeoutMs||30000); | |
| res.writeHead(200,{'Content-Type':'application/json'}); res.end(JSON.stringify(r)); | |
| }catch(e){ res.writeHead(500); res.end(JSON.stringify({error:e.message}));} | |
| }); return; | |
| } | |
| res.writeHead(404); res.end(JSON.stringify({error:'not found'})); | |
| }); | |
| server.listen(PORT,'127.0.0.1',()=>console.log(`[sandbox-runner] listening 127.0.0.1:${PORT}`)); | |
| process.on('SIGTERM',()=>server.close(()=>process.exit(0))); | |