nexus-dashboard/lib/ssh.ts
2026-02-01 18:42:22 +00:00

127 lines
4.1 KiB
TypeScript

import { execSync } from 'child_process';
const SSH_CONFIG: Record<string, { host: string; user: string }> = {
'proxmox': { host: '192.168.178.32', user: 'root' },
'n8n': { host: '192.168.178.129', user: 'basti' },
'webserver': { host: '192.168.178.130', user: 'basti' }
};
const SSH_KEY_PATH = '/home/basti/.ssh/id_ed25519';
function executeSSHCommand(host: string, command: string): string {
try {
const config = SSH_CONFIG[host];
const sshHost = config ? `${config.user}@${config.host}` : host;
const fullCommand = `ssh -o StrictHostKeyChecking=no -i ${SSH_KEY_PATH} ${sshHost} "${command.replace(/"/g, '\\"')}"`;
const stdout = execSync(fullCommand, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
env: { ...process.env, HOME: '/home/basti' }
});
return stdout.trim();
} catch (error: any) {
console.error('SSH Command Error:', error.message);
throw new Error(`SSH command failed: ${error.message}`);
}
}
export async function getVMList() {
const output = executeSSHCommand('proxmox', 'qm list');
const lines = output.split('\n').slice(1); // Skip header
return lines
.map(line => {
const parts = line.trim().split(/\s+/);
if (!parts[0] || parts[0] === '') return null;
return {
vmid: parts[0],
name: parts[1] || 'unknown',
status: parts[2] || 'unknown',
mem: parts[3] || '0',
bootdisk: parts[4] || '0',
pid: parts[5] || null
};
})
.filter((vm): vm is NonNullable<typeof vm> => vm !== null);
}
export async function getVMConfig(vmid: string) {
const output = executeSSHCommand('proxmox', `qm config ${vmid}`);
const config: Record<string, string> = {};
output.split('\n').forEach(line => {
const [key, ...valueParts] = line.split(':');
if (key && valueParts.length) {
config[key.trim()] = valueParts.join(':').trim();
}
});
return config;
}
export async function getVMStatus(vmid: string) {
const output = executeSSHCommand('proxmox', `qm status ${vmid}`);
const match = output.match(/status:\s*(\w+)/);
return match ? match[1] : 'unknown';
}
export async function controlVM(vmid: string, action: 'start' | 'stop' | 'restart') {
return executeSSHCommand('proxmox', `qm ${action} ${vmid}`);
}
// Get VM stats via SSH
export async function getVMStats(vmid: string, vmName: string) {
try {
// Use the webserver directly since we're running on VM 200
if (vmName === 'webserver') {
const cpuCmd = execSync("top -bn1 | grep 'Cpu(s)' | awk '{print 100 - $8}'", { encoding: 'utf-8' });
const memCmd = execSync("free -m | awk 'NR==2{printf \"%d/%d\", $3, $2}'", { encoding: 'utf-8' });
const diskCmd = execSync("df -h / | awk 'NR==2{print $5}'", { encoding: 'utf-8' });
const [memUsed, memTotal] = memCmd.trim().split('/').map(Number);
return {
cpu: Math.round(parseFloat(cpuCmd.trim()) || 0),
mem: {
used: memUsed,
total: memTotal,
percent: Math.round((memUsed / memTotal) * 100)
},
disk: {
percent: parseInt(diskCmd.replace('%', '').trim()) || 0
}
};
}
// For other VMs, use SSH
const cpuCmd = executeSSHCommand(vmName, "top -bn1 | grep 'Cpu(s)' | awk '{print 100 - \\$8}'");
const memCmd = executeSSHCommand(vmName, "free -m | awk 'NR==2{printf \"%d/%d\", \\$3, \\$2}'");
const diskCmd = executeSSHCommand(vmName, "df -h / | awk 'NR==2{print \\$5}'");
const [memUsed, memTotal] = memCmd.split('/').map(Number);
return {
cpu: Math.round(parseFloat(cpuCmd) || 0),
mem: {
used: memUsed,
total: memTotal,
percent: Math.round((memUsed / memTotal) * 100)
},
disk: {
percent: parseInt(diskCmd.replace('%', '')) || 0
}
};
} catch (error) {
console.error('Error fetching VM stats:', error);
// If VM is not running or SSH fails, return zeros
return {
cpu: 0,
mem: { used: 0, total: 0, percent: 0 },
disk: { percent: 0 }
};
}
}