/** * Server Metrics Collector Service * Recolhe métricas de todos os servidores via SSH (ssh2 library) * @author Descomplicar® | @link descomplicar.pt | @copyright 2026 */ import db from '../db.js' import { Client } from 'ssh2' interface SSHServer { name: string monitorName: string host: string port: number user: string pass: string } // EasyPanel metrics: collected via API in monitoring-collector.ts // Gateway metrics: not needed (just Nginx proxy, covered by HTTP health check) // Only CWP Server remains on SSH (password auth) const SSH_SERVERS: SSHServer[] = [ { name: 'server', monitorName: 'CWP Server', host: process.env.SERVER_HOST || '5.9.90.105', port: parseInt(process.env.SERVER_PORT || '9443'), user: process.env.SERVER_USER || 'root', pass: process.env.SERVER_PASS || '' } ] interface ServerMetrics { cpu: number ram: number disk: number load: number containers?: number } function parseSSHMetrics(output: string): ServerMetrics { const lines = output.split('\n') const metrics: ServerMetrics = { cpu: 0, ram: 0, disk: 0, load: 0 } for (const line of lines) { if (line.startsWith('CPU:')) metrics.cpu = parseFloat(line.split(':')[1]) || 0 if (line.startsWith('MEM:')) metrics.ram = parseFloat(line.split(':')[1]) || 0 if (line.startsWith('DISK:')) metrics.disk = parseFloat(line.split(':')[1]) || 0 if (line.startsWith('LOAD:')) metrics.load = parseFloat(line.split(':')[1]) || 0 if (line.startsWith('CONTAINERS:')) metrics.containers = parseInt(line.split(':')[1]) || 0 } return metrics } /** * Execute SSH command via ssh2 library */ function executeSSH(server: SSHServer, command: string): Promise { return new Promise((resolve, reject) => { const conn = new Client() let output = '' const timeout = setTimeout(() => { conn.end() reject(new Error(`SSH timeout for ${server.name}`)) }, 20000) conn.on('ready', () => { conn.exec(command, (err, stream) => { if (err) { clearTimeout(timeout) conn.end() reject(err) return } stream.on('data', (data: Buffer) => { output += data.toString() }) stream.on('close', () => { clearTimeout(timeout) conn.end() resolve(output) }) stream.stderr.on('data', () => { // ignore stderr }) }) }) conn.on('error', (err) => { clearTimeout(timeout) reject(err) }) conn.connect({ host: server.host, port: server.port, username: server.user, password: server.pass, readyTimeout: 15000, algorithms: { kex: [ // Algoritmos modernos (Vulnerabilidade 3.6) 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group14-sha256' // REMOVIDOS (inseguros): diffie-hellman-group14-sha1, diffie-hellman-group1-sha1 ] } }) }) } /** * Collect metrics from all SSH servers */ export async function collectSSHMetrics(): Promise<{ success: number; failed: number }> { let success = 0 let failed = 0 const metricsCommand = `echo "CPU:$(top -bn1 | grep 'Cpu(s)' | awk '{print $2}')"; echo "MEM:$(free -m | awk '/Mem:/ {printf "%.1f", $3/$2*100}')"; echo "DISK:$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')"; echo "LOAD:$(cat /proc/loadavg | awk '{print $1}')"; echo "CONTAINERS:$(docker ps -q 2>/dev/null | wc -l || echo 0)"` for (const server of SSH_SERVERS) { if (!server.pass) { console.log(`[SSH] Skipping ${server.name}: no password configured`) failed++ continue } try { const output = await executeSSH(server, metricsCommand) if (!output) { failed++ continue } const metrics = parseSSHMetrics(output) // Update monitoring table await db.query( `UPDATE tbl_eal_monitoring SET details = ?, status = 'up', last_check = NOW() WHERE category = 'server' AND name = ?`, [ JSON.stringify({ cpu: metrics.cpu, ram: metrics.ram, disk: metrics.disk, load: metrics.load }), server.monitorName ] ) success++ console.log(`[SSH] ${server.monitorName}: CPU=${metrics.cpu}%, RAM=${metrics.ram}%, Disk=${metrics.disk}%`) } catch (error: unknown) { console.error(`[SSH] Failed ${server.name}:`, error instanceof Error ? error.message : 'Unknown error') failed++ } } return { success, failed } } /** * Collect all server metrics (SSH only - replaces Hetzner sync) */ export async function collectAllServerMetrics(): Promise<{ ssh: { success: number; failed: number } }> { console.log('[METRICS] Collecting server metrics via SSH...') const ssh = await collectSSHMetrics() console.log(`[METRICS] Done: ${ssh.success} OK, ${ssh.failed} failed`) return { ssh } } // Keep for backward compatibility with routes export async function syncHetznerToMonitoring(): Promise { // Now handled by SSH collection const result = await collectSSHMetrics() return result.success }