import { readFile } from 'fs/promises' import { exec } from './exec.js' import type { SystemStats, ServiceStatus } from '../types/index.js' /** * Read CPU usage (0-100%) from /proc/stat. */ async function getCpuUsage(): Promise { try { const stat1 = await readFile('/proc/stat', 'utf8') await new Promise(r => setTimeout(r, 200)) const stat2 = await readFile('/proc/stat', 'utf8') const parse = (s: string) => { const line = s.split('\n')[0].split(' ').filter(Boolean).slice(1).map(Number) const idle = line[3] + line[4] const total = line.reduce((a, b) => a + b, 0) return { idle, total } } const s1 = parse(stat1) const s2 = parse(stat2) const idleDiff = s2.idle - s1.idle const totalDiff = s2.total - s1.total return Math.round((1 - idleDiff / totalDiff) * 100) } catch { return 0 } } /** * Get memory stats from /proc/meminfo (bytes). */ async function getMemoryStats(): Promise { try { const content = await readFile('/proc/meminfo', 'utf8') const get = (key: string) => { const match = content.match(new RegExp(`${key}:\\s+(\\d+)\\s+kB`)) return match ? parseInt(match[1], 10) * 1024 : 0 } const total = get('MemTotal') const free = get('MemFree') const buffers = get('Buffers') const cached = get('Cached') const used = total - free - buffers - cached return { total, used, free: total - used, percent: Math.round((used / total) * 100), } } catch { return { total: 0, used: 0, free: 0, percent: 0 } } } /** * Get disk usage for / (bytes). */ async function getDiskStats(): Promise { try { const result = await exec('df', ['-B1', '/']) const lines = result.stdout.split('\n') const parts = lines[1]?.split(/\s+/) if (!parts) return { total: 0, used: 0, free: 0, percent: 0 } const total = parseInt(parts[1], 10) const used = parseInt(parts[2], 10) const free = parseInt(parts[3], 10) return { total, used, free, percent: Math.round((used / total) * 100) } } catch { return { total: 0, used: 0, free: 0, percent: 0 } } } /** * Get system load averages from /proc/loadavg. */ async function getLoadAvg(): Promise<[number, number, number]> { try { const content = await readFile('/proc/loadavg', 'utf8') const parts = content.split(' ') return [parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])] } catch { return [0, 0, 0] } } /** * Gather all system stats in parallel. */ export async function getSystemStats(): Promise { const [cpu, memory, disk, load] = await Promise.all([ getCpuUsage(), getMemoryStats(), getDiskStats(), getLoadAvg(), ]) return { cpu, memory, disk, load, uptime: Math.floor(process.uptime()), } } /** * Check the status of a systemd service. */ export async function getServiceStatus(name: string): Promise { const [activeResult, enabledResult] = await Promise.all([ exec('systemctl', ['is-active', name]), exec('systemctl', ['is-enabled', name]), ]) const active = activeResult.stdout.trim() const enabled = enabledResult.stdout.trim() === 'enabled' let status: ServiceStatus['status'] = 'unknown' if (active === 'active') status = 'running' else if (active === 'inactive') status = 'stopped' else if (active === 'failed') status = 'failed' return { name, status, enabled } } export const MANAGED_SERVICES = [ 'apache2', 'php8.2-fpm', 'php8.3-fpm', 'exim4', 'dovecot', 'pdns', 'mariadb', 'fail2ban', 'ssh', ]