48 lines
2.4 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const fs = require("node:fs");
const path = require("node:path");
const { transition } = require("./state");
const { sendSmtpMail } = require("../lake-lamp-authz/smtp-mailer");
async function main() {
const config = JSON.parse(fs.readFileSync(process.env.SENTINEL_CONFIG || "/etc/guanghu/state-change-sentinel.json", "utf8"));
const stateFile = process.env.SENTINEL_STATE || "/var/lib/guanghu/state-change-sentinel/state.json";
const previous = fs.existsSync(stateFile) ? JSON.parse(fs.readFileSync(stateFile, "utf8")) : {};
const next = {};
const notices = [];
for (const target of config.targets) {
const current = await check(target);
next[target.id] = current;
const changed = transition(previous[target.id], current);
if (changed.notify) notices.push({ id: target.id, kind: changed.kind, detail: current.detail });
}
fs.mkdirSync(path.dirname(stateFile), { recursive: true, mode: 0o700 });
fs.writeFileSync(stateFile, JSON.stringify(next), { mode: 0o600 });
if (!notices.length) return;
const lines = notices.map(item => `${item.kind === "anomaly" ? "异常" : "恢复"}: ${item.id} · ${item.detail}`);
await sendSmtpMail({
to: process.env.AUTH_RECIPIENT,
subject: `光湖节点状态变化 · ${notices.map(item => item.id).join(", ")}`,
text: ["光湖节点状态发生变化。静止在线期间不会发送邮件。", "", ...lines, "", new Date().toISOString()].join("\n"),
smtpHost: process.env.SMTP_HOST || "smtp.qq.com",
smtpPort: Number(process.env.SMTP_PORT || 465),
smtpUser: process.env.SMTP_USER,
smtpPass: process.env.SMTP_PASS,
});
}
async function check(target) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), target.timeout_ms || 8000);
try {
const response = await fetch(target.url, { method: "GET", redirect: "manual", signal: controller.signal, headers: { "user-agent": "Guanghu-State-Change-Sentinel/1.0" } });
const ok = (target.accept || [200]).includes(response.status);
return { ok, detail: `HTTP ${response.status}`, observed_at: new Date().toISOString() };
} catch (error) {
return { ok: false, detail: error.name === "AbortError" ? "timeout" : "connection-failed", observed_at: new Date().toISOString() };
} finally { clearTimeout(timer); }
}
main().catch(error => { process.stderr.write(`state sentinel failed: ${error.message}\n`); process.exit(1); });