'use strict'; const express = require('express'); const { execFile } = require('child_process'); const path = require('path'); const { pool } = require('../lib/db'); const router = express.Router(); /** Server start time — captured once at module load */ const SERVER_START = Date.now(); // ─── Helpers ───────────────────────────────────────────────────────────────── /** * Run a shell command and resolve with its stdout, or reject on error. * @param {string} cmd * @param {string[]} args * @param {number} timeoutMs * @returns {Promise} */ function runCommand(cmd, args, timeoutMs = 3000) { return new Promise((resolve, reject) => { execFile(cmd, args, { timeout: timeoutMs }, (err, stdout) => { if (err) return reject(err); resolve(stdout.trim()); }); }); } /** * Parse `df -h` output for a given mount point. * Returns { used, total, percent } or null. * @param {string} mountPoint * @returns {Promise<{ used: string, total: string, percent: string } | null>} */ async function checkDisk(mountPoint) { try { const out = await runCommand('df', ['-h', mountPoint]); // Header line + data line const lines = out.split('\n').filter(Boolean); const dataLine = lines[lines.length - 1]; // Columns: Filesystem Size Used Avail Use% Mounted const parts = dataLine.split(/\s+/); if (parts.length < 6) return null; return { used: parts[2], total: parts[1], percent: parts[4], // e.g. "72%" }; } catch { return null; } } /** * Determine overall status from individual checks. * Any "outage" → overall "outage"; any "degraded" → "degraded"; else "operational". * @param {{ status: string }[]} checks * @returns {'operational' | 'degraded' | 'outage'} */ function computeOverall(checks) { if (checks.some(c => c.status === 'outage')) return 'outage'; if (checks.some(c => c.status === 'degraded')) return 'degraded'; return 'operational'; } // ─── Route: GET /status (serve HTML page) ──────────────────────────────────── router.get('/', (req, res) => { res.sendFile(path.resolve(__dirname, '../public/status.html')); }); // ─── Route: GET /status/api ─────────────────────────────────────────────────── router.get('/api', async (req, res) => { const checks = []; // 1. Web server — always operational if we reached this handler checks.push({ name: 'Веб-сервер', status: 'operational', latency: 0 }); // 2. Database — SELECT 1 { const t0 = Date.now(); try { await pool.execute('SELECT 1'); checks.push({ name: 'База данных', status: 'operational', latency: Date.now() - t0 }); } catch { checks.push({ name: 'База данных', status: 'outage', latency: Date.now() - t0 }); } } // 3. FFmpeg { const t0 = Date.now(); try { await runCommand('which', ['ffmpeg']); checks.push({ name: 'FFmpeg', status: 'operational', latency: Date.now() - t0 }); } catch { checks.push({ name: 'FFmpeg', status: 'degraded', latency: Date.now() - t0 }); } } // 4. Disk / { const disk = await checkDisk('/'); if (disk) { const pct = parseInt(disk.percent, 10); const status = pct >= 95 ? 'outage' : pct >= 85 ? 'degraded' : 'operational'; checks.push({ name: 'Диск / (NVMe)', status, detail: `${disk.percent} использовано (${disk.used} / ${disk.total})`, }); } else { checks.push({ name: 'Диск / (NVMe)', status: 'degraded', detail: 'Не удалось проверить' }); } } // 5. Disk /mnt/webdata { const disk = await checkDisk('/mnt/webdata'); if (disk) { const pct = parseInt(disk.percent, 10); const status = pct >= 95 ? 'outage' : pct >= 85 ? 'degraded' : 'operational'; checks.push({ name: 'Диск /mnt/webdata', status, detail: `${disk.percent} использовано (${disk.used} / ${disk.total})`, }); } else { // Mount point may not exist on dev — treat as degraded, not outage checks.push({ name: 'Диск /mnt/webdata', status: 'degraded', detail: 'Не смонтирован' }); } } const mem = process.memoryUsage(); res.json({ overall: computeOverall(checks), uptime: Math.floor((Date.now() - SERVER_START) / 1000), checks, server: { node: process.version, memory: { rss: Math.round(mem.rss / 1024 / 1024) + 'MB', heap: Math.round(mem.heapUsed / 1024 / 1024) + '/' + Math.round(mem.heapTotal / 1024 / 1024) + 'MB', }, pid: process.pid, }, timestamp: new Date().toISOString(), }); }); module.exports = router;