const Docker = require('dockerode'); const fs = require('fs'); let docker; try { docker = new Docker(); } catch (e) { console.warn('Docker not available — container features disabled'); } async function getOrCreateContainer(projectId, userId, workDir) { if (!docker) throw new Error('Docker not available'); const name = `indus-${projectId}`; // Check if already running try { const container = docker.getContainer(name); const info = await container.inspect(); if (info.State.Running) return container; await container.start(); return container; } catch (e) { // Container doesn't exist yet } // Create workspace directory on host fs.mkdirSync(workDir, { recursive: true }); // Create fresh container const container = await docker.createContainer({ Image: 'indus-base:latest', name, Tty: true, OpenStdin: true, WorkingDir: '/workspace', HostConfig: { Memory: 512 * 1024 * 1024, // 512MB RAM MemorySwap: 512 * 1024 * 1024, // No swap CpuQuota: 50000, // 50% of one CPU NetworkMode: 'bridge', Binds: [`${workDir}:/workspace`], // Mount user files AutoRemove: false, }, }); await container.start(); console.log(`Container created: ${name}`); return container; } async function stopContainer(projectId) { if (!docker) return; try { const container = docker.getContainer(`indus-${projectId}`); await container.stop({ t: 5 }); console.log(`Container stopped: indus-${projectId}`); } catch (e) { } } async function removeContainer(projectId) { if (!docker) return; try { const container = docker.getContainer(`indus-${projectId}`); await container.remove({ force: true }); console.log(`Container removed: indus-${projectId}`); } catch (e) { } } async function execInContainer(projectId, command) { if (!docker) throw new Error('Docker not available'); const container = docker.getContainer(`indus-${projectId}`); const exec = await container.exec({ Cmd: ['bash', '-c', command], AttachStdout: true, AttachStderr: true, }); const stream = await exec.start({ Detach: false }); return new Promise((resolve, reject) => { let output = ''; stream.on('data', (chunk) => { output += chunk.toString(); }); stream.on('end', () => resolve(output)); stream.on('error', reject); }); } module.exports = { getOrCreateContainer, stopContainer, removeContainer, execInContainer, };