import express from 'express'; import http from 'node:http'; import { requireAuth } from '../middleware/auth.js'; const router = express.Router(); router.use(requireAuth); const DOCKER_SOCKET = '/var/run/docker.sock'; const COMPOSE_PROJECT = (process.env.DOCKER_NETWORK || 'wild-dragon_wild-dragon').split('_')[0]; function dockerGet(path) { return new Promise((resolve, reject) => { const req = http.request( { socketPath: DOCKER_SOCKET, path, method: 'GET' }, (res) => { let data = ''; res.on('data', chunk => { data += chunk; }); res.on('end', () => { try { resolve({ status: res.statusCode, body: JSON.parse(data) }); } catch { resolve({ status: res.statusCode, body: data }); } }); } ); req.on('error', reject); req.end(); }); } function dockerPost(path) { return new Promise((resolve, reject) => { const req = http.request( { socketPath: DOCKER_SOCKET, path, method: 'POST', headers: { 'Content-Length': '0' } }, (res) => { res.resume(); res.on('end', () => resolve({ status: res.statusCode })); } ); req.on('error', reject); req.end(); }); } // GET /containers – list containers for this compose project router.get('/containers', async (req, res, next) => { try { const r = await dockerGet('/containers/json?all=1'); if (r.status !== 200) return res.status(502).json({ error: 'Docker API error', code: r.status }); const containers = r.body .filter(c => { const labels = c.Labels || {}; // Match by compose project label; fall back to network name if (labels['com.docker.compose.project'] === COMPOSE_PROJECT) return true; return Object.keys(c.NetworkSettings?.Networks || {}).some(n => n.includes(COMPOSE_PROJECT)); }) .map(c => ({ id: c.Id.slice(0, 12), name: (c.Names[0] || '').replace(/^\//, ''), image: c.Image, state: c.State, status: c.Status, created: c.Created, service: (c.Labels || {})['com.docker.compose.service'] || '', ports: (c.Ports || []) .filter(p => p.PublicPort) .map(p => `${p.PublicPort}:${p.PrivatePort}/${p.Type}`) .join(', '), })); res.json(containers); } catch (err) { next(err); } }); // POST /containers/:id/restart router.post('/containers/:id/restart', async (req, res, next) => { try { const r = await dockerPost(`/containers/${req.params.id}/restart`); if (r.status !== 204) return res.status(502).json({ error: 'Failed to restart', code: r.status }); res.json({ ok: true }); } catch (err) { next(err); } }); // POST /containers/:id/stop router.post('/containers/:id/stop', async (req, res, next) => { try { const r = await dockerPost(`/containers/${req.params.id}/stop`); if (r.status !== 204 && r.status !== 304) return res.status(502).json({ error: 'Failed to stop', code: r.status }); res.json({ ok: true }); } catch (err) { next(err); } }); // POST /containers/:id/start router.post('/containers/:id/start', async (req, res, next) => { try { const r = await dockerPost(`/containers/${req.params.id}/start`); if (r.status !== 204 && r.status !== 304) return res.status(502).json({ error: 'Failed to start', code: r.status }); res.json({ ok: true }); } catch (err) { next(err); } }); export default router;