/** * SSH Discovery API Endpoint * * POST /api/discover - Discover all hosts via SSH and return their status */ import { Router } from 'express'; import { execSync } from 'child_process'; import { homedir } from 'os'; import { getHostConfigs } from '../config'; import { DiscoveryResponse } from '../types'; const router = Router(); interface HostDiscoveryResult { name: string; ip: string; online: boolean; containers?: string[]; services?: string[]; vms?: Array<{ id: string; name: string; status: string; type: 'lxc' | 'qemu' }>; error?: string; } async function discoverHost( name: string, ip: string, sshUser: string, sshKeyPath?: string, sshPort?: number, hostType?: string ): Promise { try { const keyPath = (sshKeyPath || `${homedir()}/.ssh/id_ed25519`).replace(/^~/, homedir()); console.error(`DEBUG: ${name} keyPath=${keyPath}, user=${sshUser}`); const keyArg = `-i ${keyPath}`; const portArg = sshPort && sshPort !== 22 ? `-p ${sshPort}` : ''; const dockerCmd = `ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ${keyArg} ${portArg} ${sshUser}@${ip} "docker ps --format '{{.Names}}'" 2>/dev/null`; const dockerOutput = execSync(dockerCmd, { encoding: 'utf-8', timeout: 15000 }); const containers = dockerOutput.trim().split('\n').filter(c => c.trim()); let services: string[] = []; if (hostType !== 'proxmox') { try { const systemdCmd = `ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ${keyArg} ${portArg} ${sshUser}@${ip} "systemctl list-units --type=service --state=running --no-pager --no-legend --quiet | awk '{print \\$1}'" 2>/dev/null`; const systemdOutput = execSync(systemdCmd, { encoding: 'utf-8', timeout: 10000 }); services = systemdOutput.trim().split('\n').filter(s => s.trim()); } catch { console.error(`DEBUG: ${name} systemd discovery failed`); } } let vms: Array<{ id: string; name: string; status: string; type: 'lxc' | 'qemu' }> = []; if (hostType === 'proxmox' || name === 'proxmox') { try { const lxcCmd = `ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ${keyArg} ${portArg} ${sshUser}@${ip} "pct list" 2>/dev/null`; const lxcOutput = execSync(lxcCmd, { encoding: 'utf-8', timeout: 10000 }); const lxcLines = lxcOutput.trim().split('\n').slice(1); for (const line of lxcLines) { const parts = line.trim().split(/\s+/); if (parts.length >= 2) { vms.push({ id: parts[0], name: parts[1], status: parts[2] || 'unknown', type: 'lxc' }); } } const vmCmd = `ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ${keyArg} ${portArg} ${sshUser}@${ip} "qm list" 2>/dev/null`; const vmOutput = execSync(vmCmd, { encoding: 'utf-8', timeout: 10000 }); const vmLines = vmOutput.trim().split('\n').slice(1); for (const line of vmLines) { const parts = line.trim().split(/\s+/); if (parts.length >= 2) { vms.push({ id: parts[0], name: parts[1], status: parts[2] || 'unknown', type: 'qemu' }); } } } catch { console.error(`DEBUG: ${name} Proxmox discovery failed`); } } return { name, ip, online: true, containers, services: services.length > 0 ? services : undefined, vms: vms.length > 0 ? vms : undefined, }; } catch (error: any) { return { name, ip, online: false, containers: [], error: error.message || 'Discovery failed', }; } } // POST /api/discover - Discover all hosts via SSH router.post('/discover', async (req, res) => { try { const hosts = getHostConfigs(); if (hosts.length === 0) { const response: DiscoveryResponse = { hosts: [], timestamp: new Date().toISOString(), errors: ['No hosts configured'], }; return res.json(response); } const results: HostDiscoveryResult[] = []; for (const host of hosts) { try { const keyPath = host.sshKeyPath?.replace(/^~/, homedir()); const result = await discoverHost( host.name, host.ip, host.sshUser, keyPath, host.sshPort, host.hostType ); results.push(result); } catch (error: any) { results.push({ name: host.name, ip: host.ip, online: false, containers: [], error: error.message || 'Discovery failed' }); } } const errors: string[] = []; results.forEach((result: HostDiscoveryResult) => { if (!result.online && result.error) { errors.push(`${result.name}: ${result.error}`); } }); const response: DiscoveryResponse = { hosts: results.map(r => ({ name: r.name, ip: r.ip, online: r.online, containers: r.containers, services: r.services, vms: r.vms, })), timestamp: new Date().toISOString(), errors, }; res.json(response); } catch (error: any) { const response: DiscoveryResponse = { hosts: [], timestamp: new Date().toISOString(), errors: [error.message || 'Discovery failed'], }; res.status(500).json(response); } }); export default router;