vps-health-api/index.js
le king fu d6eb06302c feat: initial vps-health-api service
Zero-dependency Node.js health endpoint exposing CPU, RAM, disk and
uptime metrics. Bearer token auth, Docker-ready (node:22-alpine).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:48:09 -05:00

103 lines
2.8 KiB
JavaScript

const http = require("node:http");
const os = require("node:os");
const { execSync } = require("node:child_process");
const PORT = parseInt(process.env.PORT || "3001", 10);
const TOKEN = process.env.HEALTH_TOKEN;
function readProcStat() {
try {
const line = execSync("head -1 /proc/stat", { encoding: "utf-8" }).trim();
const parts = line.split(/\s+/).slice(1).map(Number);
const idle = parts[3] + parts[4];
const total = parts.reduce((a, b) => a + b, 0);
return { idle, total };
} catch {
return null;
}
}
function getCpuPercent() {
const t1 = readProcStat();
if (!t1) return 0;
const { idle: idle1, total: total1 } = t1;
// Sample over 500ms
execSync("sleep 0.5");
const t2 = readProcStat();
if (!t2) return 0;
const dIdle = t2.idle - idle1;
const dTotal = t2.total - total1;
if (dTotal === 0) return 0;
return Math.round((1 - dIdle / dTotal) * 100);
}
function getDisk() {
try {
const out = execSync("df / --output=size,used,avail -B1", {
encoding: "utf-8",
});
const parts = out.trim().split("\n")[1].trim().split(/\s+/).map(Number);
const totalGB = +(parts[0] / 1e9).toFixed(1);
const usedGB = +(parts[1] / 1e9).toFixed(1);
const freeGB = +(parts[2] / 1e9).toFixed(1);
const usagePercent = totalGB > 0 ? Math.round((usedGB / totalGB) * 100) : 0;
return { totalGB, usedGB, freeGB, usagePercent };
} catch {
return { totalGB: 0, usedGB: 0, freeGB: 0, usagePercent: 0 };
}
}
function getHealth() {
const cpus = os.cpus();
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
return {
timestamp: new Date().toISOString(),
hostname: os.hostname(),
uptime: Math.floor(os.uptime()),
cpu: {
model: cpus[0]?.model?.trim() || "unknown",
cores: cpus.length,
loadAvg: os.loadavg().map((l) => +l.toFixed(2)),
usagePercent: getCpuPercent(),
},
memory: {
totalGB: +(totalMem / 1e9).toFixed(1),
usedGB: +(usedMem / 1e9).toFixed(1),
freeGB: +(freeMem / 1e9).toFixed(1),
usagePercent: Math.round((usedMem / totalMem) * 100),
},
disk: getDisk(),
};
}
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.url !== "/health" || req.method !== "GET") {
res.writeHead(404);
res.end(JSON.stringify({ error: "Not found" }));
return;
}
if (TOKEN) {
const auth = req.headers["authorization"];
if (auth !== `Bearer ${TOKEN}`) {
res.writeHead(401);
res.end(JSON.stringify({ error: "Unauthorized" }));
return;
}
}
const data = getHealth();
res.writeHead(200);
res.end(JSON.stringify(data));
});
server.listen(PORT, () => {
console.log(`vps-health-api listening on :${PORT}`);
});