import { Router } from 'express'; import { loadDeployConfig, saveDeployConfig, getServerSshConfig } from '../lib/config.js'; import { SSHService } from '../lib/ssh.js'; const router = Router(); function wrap(fn) { return async (req, res) => { try { const result = await fn(req, res); if (!res.headersSent) res.json(result); } catch (err) { res.status(err.status || 500).json({ error: err.message }); } }; } // GET /api/servers — list all configured deployment servers router.get('/', wrap(async () => { const config = loadDeployConfig(); return config.servers || []; })); // POST /api/servers — add or update a server router.post('/', wrap(async (req) => { const server = req.body; const config = loadDeployConfig(); if (!config.servers) config.servers = []; const index = config.servers.findIndex(s => s.id === server.id); if (index >= 0) { config.servers[index] = server; } else { server.id = Date.now().toString(); config.servers.push(server); } saveDeployConfig(config); return { success: true, server }; })); // DELETE /api/servers/:id — delete a server router.delete('/:id', wrap(async (req) => { const config = loadDeployConfig(); config.servers = (config.servers || []).filter(s => s.id !== req.params.id); saveDeployConfig(config); return { success: true }; })); // GET /api/servers/:id/scan — scan remote server for deployed containers router.get('/:id/scan', wrap(async (req) => { const config = loadDeployConfig(); const server = config.servers.find(s => s.id === req.params.id); if (!server) throw Object.assign(new Error('Server not found'), { status: 404 }); const sshConfig = getServerSshConfig(server); const ssh = new SSHService(sshConfig); try { await ssh.connect(); const result = await ssh.exec('ls -1 ~/containers 2>/dev/null || echo ""'); const projectDirs = result.split('\n').filter(Boolean); const deployed = []; for (const dir of projectDirs) { const remotePath = `~/containers/${dir}`; try { const filesResult = await ssh.exec(`ls -la ${remotePath} 2>/dev/null || echo ""`); const hasDockerCompose = filesResult.includes('docker-compose.yml'); const hasEnv = filesResult.includes('.env'); const hasData = filesResult.includes('data'); const tarMatch = filesResult.match(/(\S+\.tar)/); let dockerComposeContent = null; if (hasDockerCompose) { try { dockerComposeContent = await ssh.exec(`cat ${remotePath}/docker-compose.yml 2>/dev/null`); } catch { /* ignore */ } } deployed.push({ name: dir, remotePath, hasDockerCompose, hasEnv, hasData, tarFile: tarMatch ? tarMatch[1] : null, dockerComposeContent, }); } catch { /* skip */ } } ssh.disconnect(); return { success: true, deployed }; } catch (err) { throw new Error(`Scan failed: ${err.message}`); } })); // GET /api/servers/:id/containers — list running docker containers router.get('/:id/containers', wrap(async (req) => { const config = loadDeployConfig(); const server = config.servers.find(s => s.id === req.params.id); if (!server) throw Object.assign(new Error('Server not found'), { status: 404 }); const sshConfig = getServerSshConfig(server); const ssh = new SSHService(sshConfig); try { await ssh.connect(); const result = await ssh.exec('docker ps --format "{{.Names}}|{{.Status}}|{{.Ports}}"'); ssh.disconnect(); const containers = result.split('\n').filter(Boolean).map(line => { const [name, status, ports] = line.split('|'); return { name, status, ports }; }); return { success: true, containers }; } catch (err) { throw new Error(`Failed to get containers: ${err.message}`); } })); // GET /api/servers/:id/logs — get container logs router.get('/:id/logs', wrap(async (req) => { const config = loadDeployConfig(); const server = config.servers.find(s => s.id === req.params.id); if (!server) throw Object.assign(new Error('Server not found'), { status: 404 }); const { containerName, remotePath, lines = 100 } = req.query; const sshConfig = getServerSshConfig(server); const ssh = new SSHService(sshConfig); const password = sshConfig.password; const sudoPrefix = server.useSudo ? `echo '${password}' | sudo -S ` : ''; try { await ssh.connect(); let logs; if (remotePath) { logs = await ssh.exec(`cd ${remotePath} && ${sudoPrefix}docker compose logs --tail ${lines} 2>&1`); } else if (containerName) { logs = await ssh.exec(`${sudoPrefix}docker logs ${containerName} --tail ${lines} 2>&1`); } else { throw Object.assign(new Error('containerName or remotePath required'), { status: 400 }); } ssh.disconnect(); return { success: true, logs }; } catch (err) { throw new Error(`Failed to get logs: ${err.message}`); } })); export default router;