/** * Pi/host fleet heartbeat: self-report local pm2 process health to Postgres so a * central detector on Erik can catch crash-loops (restart_time velocity + near-0 * uptime) and stale/dead hosts — which a naive "pm2 status == online" check misses. * Runs on each Pi via cron (every 5 min). DB via the local tunnel (127.0.0.1:5433). */ import { execFileSync } from "node:child_process"; import os from "node:os"; import { config } from "dotenv"; import pg from "pg"; config({ path: new URL("../.env", import.meta.url) }); // ~/tip/.env const host = os.hostname(); let procs = []; try { procs = JSON.parse(execFileSync("pm2", ["jlist"], { encoding: "utf8", maxBuffer: 8 * 1024 * 1024 })); } catch { process.exit(0); } const pool = new pg.Pool({ host: "127.0.0.1", port: 5433, user: process.env.POSTGRES_USER || "tip", database: process.env.POSTGRES_DB || "transceiver_db", password: process.env.POSTGRES_PASSWORD, max: 1, connectionTimeoutMillis: 8000, statement_timeout: 8000, }); try { for (const p of procs) { const e = p.pm2_env || {}; const uptimeS = Math.round((Date.now() - (e.pm_uptime || Date.now())) / 1000); await pool.query( "insert into fleet_health(host,pm_name,status,restart_time,uptime_s) values($1,$2,$3,$4,$5)", [host, p.name, e.status || null, e.restart_time ?? 0, uptimeS], ); } } catch { /* heartbeat is best-effort; never crash the host */ } finally { await pool.end().catch(() => {}); }