@glassmkr/crucible 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/config/collector.example.yaml +43 -0
- package/dist/alerts/evaluator.d.ts +3 -0
- package/dist/alerts/evaluator.js +14 -0
- package/dist/alerts/evaluator.js.map +1 -0
- package/dist/alerts/rules.d.ts +7 -0
- package/dist/alerts/rules.js +203 -0
- package/dist/alerts/rules.js.map +1 -0
- package/dist/alerts/state.d.ts +6 -0
- package/dist/alerts/state.js +77 -0
- package/dist/alerts/state.js.map +1 -0
- package/dist/collect/cpu.d.ts +2 -0
- package/dist/collect/cpu.js +35 -0
- package/dist/collect/cpu.js.map +1 -0
- package/dist/collect/disks.d.ts +2 -0
- package/dist/collect/disks.js +33 -0
- package/dist/collect/disks.js.map +1 -0
- package/dist/collect/ipmi.d.ts +2 -0
- package/dist/collect/ipmi.js +55 -0
- package/dist/collect/ipmi.js.map +1 -0
- package/dist/collect/memory.d.ts +2 -0
- package/dist/collect/memory.js +27 -0
- package/dist/collect/memory.js.map +1 -0
- package/dist/collect/network.d.ts +2 -0
- package/dist/collect/network.js +54 -0
- package/dist/collect/network.js.map +1 -0
- package/dist/collect/os-alerts.d.ts +2 -0
- package/dist/collect/os-alerts.js +41 -0
- package/dist/collect/os-alerts.js.map +1 -0
- package/dist/collect/raid.d.ts +2 -0
- package/dist/collect/raid.js +34 -0
- package/dist/collect/raid.js.map +1 -0
- package/dist/collect/smart.d.ts +2 -0
- package/dist/collect/smart.js +56 -0
- package/dist/collect/smart.js.map +1 -0
- package/dist/collect/system.d.ts +2 -0
- package/dist/collect/system.js +19 -0
- package/dist/collect/system.js.map +1 -0
- package/dist/config.d.ts +208 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/exec.d.ts +1 -0
- package/dist/lib/exec.js +19 -0
- package/dist/lib/exec.js.map +1 -0
- package/dist/lib/parse.d.ts +4 -0
- package/dist/lib/parse.js +29 -0
- package/dist/lib/parse.js.map +1 -0
- package/dist/lib/types.d.ts +103 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/notify/email.d.ts +4 -0
- package/dist/notify/email.js +55 -0
- package/dist/notify/email.js.map +1 -0
- package/dist/notify/slack.d.ts +2 -0
- package/dist/notify/slack.js +38 -0
- package/dist/notify/slack.js.map +1 -0
- package/dist/notify/telegram.d.ts +2 -0
- package/dist/notify/telegram.js +38 -0
- package/dist/notify/telegram.js.map +1 -0
- package/dist/push/forge.d.ts +2 -0
- package/dist/push/forge.js +26 -0
- package/dist/push/forge.js.map +1 -0
- package/package.json +29 -0
- package/src/alerts/evaluator.ts +15 -0
- package/src/alerts/rules.ts +184 -0
- package/src/alerts/state.ts +92 -0
- package/src/collect/cpu.ts +44 -0
- package/src/collect/disks.ts +36 -0
- package/src/collect/ipmi.ts +60 -0
- package/src/collect/memory.ts +30 -0
- package/src/collect/network.ts +61 -0
- package/src/collect/os-alerts.ts +43 -0
- package/src/collect/raid.ts +40 -0
- package/src/collect/smart.ts +60 -0
- package/src/collect/system.ts +21 -0
- package/src/config.ts +60 -0
- package/src/index.ts +112 -0
- package/src/lib/exec.ts +16 -0
- package/src/lib/parse.ts +29 -0
- package/src/lib/types.ts +110 -0
- package/src/notify/email.ts +68 -0
- package/src/notify/slack.ts +46 -0
- package/src/notify/telegram.ts +45 -0
- package/src/push/forge.ts +25 -0
- package/tsconfig.json +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { collectSystem } from "./collect/system.js";
|
|
4
|
+
import { collectCpu } from "./collect/cpu.js";
|
|
5
|
+
import { collectMemory } from "./collect/memory.js";
|
|
6
|
+
import { collectDisks } from "./collect/disks.js";
|
|
7
|
+
import { collectSmart } from "./collect/smart.js";
|
|
8
|
+
import { collectNetwork } from "./collect/network.js";
|
|
9
|
+
import { collectRaid } from "./collect/raid.js";
|
|
10
|
+
import { collectIpmi } from "./collect/ipmi.js";
|
|
11
|
+
import { collectOsAlerts } from "./collect/os-alerts.js";
|
|
12
|
+
import { evaluateAlerts } from "./alerts/evaluator.js";
|
|
13
|
+
import { updateAlertState } from "./alerts/state.js";
|
|
14
|
+
import { sendTelegram } from "./notify/telegram.js";
|
|
15
|
+
import { sendSlack } from "./notify/slack.js";
|
|
16
|
+
import { sendEmail } from "./notify/email.js";
|
|
17
|
+
import { pushToForge } from "./push/forge.js";
|
|
18
|
+
const configPath = process.argv[2] || "/etc/glassmkr/collector.yaml";
|
|
19
|
+
const config = loadConfig(configPath);
|
|
20
|
+
console.log(`[collector] Starting. Server: ${config.server_name}. Interval: ${config.collection.interval_seconds}s`);
|
|
21
|
+
console.log(`[collector] IPMI: ${config.collection.ipmi ? "enabled" : "disabled"}, SMART: ${config.collection.smart ? "enabled" : "disabled"}`);
|
|
22
|
+
console.log(`[collector] Forge: ${config.forge.enabled ? config.forge.url : "disabled"}`);
|
|
23
|
+
const emptyIpmi = { available: false, sensors: [], ecc_errors: { correctable: 0, uncorrectable: 0 }, sel_entries_count: 0 };
|
|
24
|
+
async function collect() {
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
console.log(`[collector] Collecting...`);
|
|
27
|
+
const [system, cpu, memory, disks, smart, network, raid, ipmi, osAlerts] = await Promise.all([
|
|
28
|
+
collectSystem(),
|
|
29
|
+
collectCpu(),
|
|
30
|
+
collectMemory(),
|
|
31
|
+
collectDisks(),
|
|
32
|
+
config.collection.smart ? collectSmart() : Promise.resolve([]),
|
|
33
|
+
collectNetwork(),
|
|
34
|
+
collectRaid(),
|
|
35
|
+
config.collection.ipmi ? collectIpmi() : Promise.resolve(emptyIpmi),
|
|
36
|
+
collectOsAlerts(),
|
|
37
|
+
]);
|
|
38
|
+
const snapshot = {
|
|
39
|
+
collector_version: "0.1.0",
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
system, cpu, memory, disks, smart, network, raid, ipmi, os_alerts: osAlerts,
|
|
42
|
+
};
|
|
43
|
+
// Evaluate alerts
|
|
44
|
+
const alertResults = evaluateAlerts(snapshot, config.thresholds);
|
|
45
|
+
const { newAlerts, resolvedAlerts } = updateAlertState(alertResults);
|
|
46
|
+
const elapsed = Date.now() - startTime;
|
|
47
|
+
console.log(`[collector] Collected in ${elapsed}ms. Alerts: ${alertResults.length} active, ${newAlerts.length} new, ${resolvedAlerts.length} resolved`);
|
|
48
|
+
// Send notifications for new/resolved alerts
|
|
49
|
+
if (newAlerts.length > 0 || resolvedAlerts.length > 0) {
|
|
50
|
+
if (config.channels.telegram.enabled && config.channels.telegram.bot_token && config.channels.telegram.chat_id) {
|
|
51
|
+
await sendTelegram(config.channels.telegram.bot_token, config.channels.telegram.chat_id, newAlerts, resolvedAlerts, config.server_name);
|
|
52
|
+
}
|
|
53
|
+
if (config.channels.slack.enabled && config.channels.slack.webhook_url) {
|
|
54
|
+
await sendSlack(config.channels.slack.webhook_url, newAlerts, resolvedAlerts, config.server_name);
|
|
55
|
+
}
|
|
56
|
+
if (config.channels.email.enabled && config.channels.email.to) {
|
|
57
|
+
await sendEmail(config.channels.email, newAlerts, resolvedAlerts, config.server_name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Push to Forge (non-blocking)
|
|
61
|
+
if (config.forge.enabled && config.forge.api_key) {
|
|
62
|
+
pushToForge(config.forge.url, config.forge.api_key, snapshot);
|
|
63
|
+
}
|
|
64
|
+
// Print summary on first run
|
|
65
|
+
if (firstRun) {
|
|
66
|
+
firstRun = false;
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log("=== First collection complete ===");
|
|
69
|
+
console.log(`Server: ${system.hostname} (${system.os})`);
|
|
70
|
+
console.log(`CPU: ${cpu.user_percent.toFixed(1)}% (load: ${cpu.load_1m})`);
|
|
71
|
+
const ramPct = memory.total_mb > 0 ? ((memory.used_mb / memory.total_mb) * 100).toFixed(1) : "0";
|
|
72
|
+
console.log(`RAM: ${ramPct}% (${memory.used_mb} / ${memory.total_mb} MB)`);
|
|
73
|
+
if (disks.length > 0)
|
|
74
|
+
console.log(`Disk: ${disks[0].percent_used}% (${disks[0].mount})`);
|
|
75
|
+
console.log(`SMART: ${smart.length > 0 ? `${smart.length} drive(s) checked` : "not available"}`);
|
|
76
|
+
console.log(`Network: ${network.map((n) => n.interface).join(", ") || "none detected"}`);
|
|
77
|
+
console.log(`IPMI: ${ipmi.available ? "available" : "not available"}`);
|
|
78
|
+
console.log(`Active alerts: ${alertResults.length}`);
|
|
79
|
+
console.log(`Forge: ${config.forge.enabled ? "enabled" : "disabled"}`);
|
|
80
|
+
console.log("");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
let firstRun = true;
|
|
84
|
+
// Run immediately
|
|
85
|
+
collect();
|
|
86
|
+
// Then on interval
|
|
87
|
+
setInterval(collect, config.collection.interval_seconds * 1000);
|
|
88
|
+
process.on("SIGTERM", () => {
|
|
89
|
+
console.log("[collector] Received SIGTERM, shutting down");
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
process.on("SIGINT", () => {
|
|
93
|
+
console.log("[collector] Received SIGINT, shutting down");
|
|
94
|
+
process.exit(0);
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(cmd: string, args: string[], timeoutMs?: number): Promise<string | null>;
|
package/dist/lib/exec.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export async function run(cmd, args, timeoutMs = 10000) {
|
|
5
|
+
try {
|
|
6
|
+
const { stdout } = await execFileAsync(cmd, args, { timeout: timeoutMs });
|
|
7
|
+
return stdout;
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (err.code === "ENOENT")
|
|
11
|
+
return null; // command not installed
|
|
12
|
+
if (err.killed)
|
|
13
|
+
return null; // timeout
|
|
14
|
+
if (err.stdout)
|
|
15
|
+
return err.stdout; // non-zero exit but has output
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/lib/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,GAAW,EAAE,IAAc,EAAE,SAAS,GAAG,KAAK;IACtE,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1E,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;QAChE,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,CAAC,UAAU;QACvC,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,+BAA+B;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
export function readProcFile(path) {
|
|
3
|
+
try {
|
|
4
|
+
return readFileSync(path, "utf-8");
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function parseKeyValue(raw) {
|
|
11
|
+
const result = {};
|
|
12
|
+
for (const line of raw.split("\n")) {
|
|
13
|
+
const idx = line.indexOf(":");
|
|
14
|
+
if (idx === -1)
|
|
15
|
+
continue;
|
|
16
|
+
result[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
export function parseKb(val) {
|
|
21
|
+
if (!val)
|
|
22
|
+
return 0;
|
|
23
|
+
const num = parseInt(val.replace(/\s*kB$/i, ""), 10);
|
|
24
|
+
return isNaN(num) ? 0 : num;
|
|
25
|
+
}
|
|
26
|
+
export function sleep(ms) {
|
|
27
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/lib/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAElC,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,SAAS;QACzB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAuB;IAC7C,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,CAAC;IACnB,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface Snapshot {
|
|
2
|
+
collector_version: string;
|
|
3
|
+
timestamp: string;
|
|
4
|
+
system: SystemInfo;
|
|
5
|
+
cpu: CpuInfo;
|
|
6
|
+
memory: MemoryInfo;
|
|
7
|
+
disks: DiskInfo[];
|
|
8
|
+
smart: SmartInfo[];
|
|
9
|
+
network: NetworkInfo[];
|
|
10
|
+
raid: RaidInfo[];
|
|
11
|
+
ipmi: IpmiInfo;
|
|
12
|
+
os_alerts: OsAlerts;
|
|
13
|
+
}
|
|
14
|
+
export interface SystemInfo {
|
|
15
|
+
hostname: string;
|
|
16
|
+
ip: string;
|
|
17
|
+
os: string;
|
|
18
|
+
kernel: string;
|
|
19
|
+
uptime_seconds: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CpuInfo {
|
|
22
|
+
user_percent: number;
|
|
23
|
+
system_percent: number;
|
|
24
|
+
iowait_percent: number;
|
|
25
|
+
idle_percent: number;
|
|
26
|
+
load_1m: number;
|
|
27
|
+
load_5m: number;
|
|
28
|
+
load_15m: number;
|
|
29
|
+
}
|
|
30
|
+
export interface MemoryInfo {
|
|
31
|
+
total_mb: number;
|
|
32
|
+
used_mb: number;
|
|
33
|
+
available_mb: number;
|
|
34
|
+
swap_total_mb: number;
|
|
35
|
+
swap_used_mb: number;
|
|
36
|
+
}
|
|
37
|
+
export interface DiskInfo {
|
|
38
|
+
device: string;
|
|
39
|
+
mount: string;
|
|
40
|
+
total_gb: number;
|
|
41
|
+
used_gb: number;
|
|
42
|
+
available_gb: number;
|
|
43
|
+
percent_used: number;
|
|
44
|
+
io_read_mb_s?: number;
|
|
45
|
+
io_write_mb_s?: number;
|
|
46
|
+
latency_p99_ms?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface SmartInfo {
|
|
49
|
+
device: string;
|
|
50
|
+
model: string;
|
|
51
|
+
health: string;
|
|
52
|
+
temperature_c?: number;
|
|
53
|
+
percentage_used?: number;
|
|
54
|
+
reallocated_sectors?: number;
|
|
55
|
+
pending_sectors?: number;
|
|
56
|
+
power_on_hours?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface NetworkInfo {
|
|
59
|
+
interface: string;
|
|
60
|
+
speed_mbps: number;
|
|
61
|
+
rx_bytes_sec: number;
|
|
62
|
+
tx_bytes_sec: number;
|
|
63
|
+
rx_errors: number;
|
|
64
|
+
tx_errors: number;
|
|
65
|
+
rx_drops: number;
|
|
66
|
+
tx_drops: number;
|
|
67
|
+
}
|
|
68
|
+
export interface RaidInfo {
|
|
69
|
+
device: string;
|
|
70
|
+
level: string;
|
|
71
|
+
status: string;
|
|
72
|
+
degraded: boolean;
|
|
73
|
+
disks: string[];
|
|
74
|
+
failed_disks: string[];
|
|
75
|
+
}
|
|
76
|
+
export interface IpmiInfo {
|
|
77
|
+
available: boolean;
|
|
78
|
+
sensors: Array<{
|
|
79
|
+
name: string;
|
|
80
|
+
value: number | string;
|
|
81
|
+
unit: string;
|
|
82
|
+
status: string;
|
|
83
|
+
upper_critical?: number;
|
|
84
|
+
}>;
|
|
85
|
+
ecc_errors: {
|
|
86
|
+
correctable: number;
|
|
87
|
+
uncorrectable: number;
|
|
88
|
+
};
|
|
89
|
+
sel_entries_count: number;
|
|
90
|
+
}
|
|
91
|
+
export interface OsAlerts {
|
|
92
|
+
oom_kills_recent: number;
|
|
93
|
+
zombie_processes: number;
|
|
94
|
+
time_drift_ms: number;
|
|
95
|
+
}
|
|
96
|
+
export interface AlertResult {
|
|
97
|
+
type: string;
|
|
98
|
+
severity: "critical" | "warning";
|
|
99
|
+
title: string;
|
|
100
|
+
message: string;
|
|
101
|
+
evidence: Record<string, unknown>;
|
|
102
|
+
recommendation: string;
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export async function sendEmail(config, newAlerts, resolvedAlerts, serverName) {
|
|
5
|
+
if (!config.to)
|
|
6
|
+
return false;
|
|
7
|
+
const subject = buildSubject(newAlerts, resolvedAlerts, serverName);
|
|
8
|
+
const body = buildBody(newAlerts, resolvedAlerts, serverName);
|
|
9
|
+
const email = [
|
|
10
|
+
`To: ${config.to}`,
|
|
11
|
+
`From: glassmkr-collector@${serverName}`,
|
|
12
|
+
`Subject: ${subject}`,
|
|
13
|
+
`Content-Type: text/plain; charset=utf-8`,
|
|
14
|
+
"",
|
|
15
|
+
body,
|
|
16
|
+
].join("\n");
|
|
17
|
+
try {
|
|
18
|
+
const child = execFileAsync("/usr/sbin/sendmail", ["-t"], { timeout: 10000 });
|
|
19
|
+
child.child.stdin?.write(email);
|
|
20
|
+
child.child.stdin?.end();
|
|
21
|
+
await child;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
console.error("[email] Failed to send. Is sendmail/postfix/msmtp installed?");
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function buildSubject(newAlerts, resolvedAlerts, serverName) {
|
|
30
|
+
if (newAlerts.length > 0) {
|
|
31
|
+
const worst = newAlerts.find((a) => a.severity === "critical") ? "CRITICAL" : "WARNING";
|
|
32
|
+
return `[${worst}] ${serverName}: ${newAlerts.length} alert(s)`;
|
|
33
|
+
}
|
|
34
|
+
return `[RESOLVED] ${serverName}: ${resolvedAlerts.length} alert(s) cleared`;
|
|
35
|
+
}
|
|
36
|
+
function buildBody(newAlerts, resolvedAlerts, serverName) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
lines.push(`Server: ${serverName}`);
|
|
39
|
+
lines.push(`Time: ${new Date().toISOString()}`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
for (const a of newAlerts) {
|
|
42
|
+
lines.push(`[${a.severity.toUpperCase()}] ${a.title}`);
|
|
43
|
+
lines.push(a.message);
|
|
44
|
+
lines.push(`Action: ${a.recommendation}`);
|
|
45
|
+
lines.push("");
|
|
46
|
+
}
|
|
47
|
+
for (const a of resolvedAlerts) {
|
|
48
|
+
lines.push(`[RESOLVED] ${a.title}`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
}
|
|
51
|
+
lines.push("---");
|
|
52
|
+
lines.push("Glassmkr Collector v0.1.0");
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/notify/email.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function sendSlack(webhookUrl, newAlerts, resolvedAlerts, serverName) {
|
|
2
|
+
const blocks = [];
|
|
3
|
+
if (newAlerts.length > 0) {
|
|
4
|
+
const criticals = newAlerts.filter((a) => a.severity === "critical");
|
|
5
|
+
const warnings = newAlerts.filter((a) => a.severity === "warning");
|
|
6
|
+
if (criticals.length > 0) {
|
|
7
|
+
blocks.push({ type: "section", text: { type: "mrkdwn", text: `\u{1F534} *${criticals.length} CRITICAL* on *${serverName}*` } });
|
|
8
|
+
for (const a of criticals)
|
|
9
|
+
blocks.push({ type: "section", text: { type: "mrkdwn", text: `*${a.title}*\n${a.recommendation}` } });
|
|
10
|
+
}
|
|
11
|
+
if (warnings.length > 0) {
|
|
12
|
+
blocks.push({ type: "section", text: { type: "mrkdwn", text: `\u{1F7E1} *${warnings.length} WARNING* on *${serverName}*` } });
|
|
13
|
+
for (const a of warnings)
|
|
14
|
+
blocks.push({ type: "section", text: { type: "mrkdwn", text: `*${a.title}*\n${a.recommendation}` } });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (resolvedAlerts.length > 0) {
|
|
18
|
+
blocks.push({ type: "section", text: { type: "mrkdwn", text: `\u2705 *${resolvedAlerts.length} resolved* on *${serverName}*` } });
|
|
19
|
+
}
|
|
20
|
+
if (blocks.length === 0)
|
|
21
|
+
return true;
|
|
22
|
+
blocks.push({ type: "divider" });
|
|
23
|
+
blocks.push({ type: "context", elements: [{ type: "mrkdwn", text: "Glassmkr Collector v0.1.0" }] });
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(webhookUrl, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify({ blocks }),
|
|
29
|
+
signal: AbortSignal.timeout(10000),
|
|
30
|
+
});
|
|
31
|
+
return res.ok;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
console.error("[slack] Failed to send notification");
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=slack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/notify/slack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function sendTelegram(botToken, chatId, newAlerts, resolvedAlerts, serverName) {
|
|
2
|
+
const parts = [];
|
|
3
|
+
if (newAlerts.length > 0) {
|
|
4
|
+
const criticals = newAlerts.filter((a) => a.severity === "critical");
|
|
5
|
+
const warnings = newAlerts.filter((a) => a.severity === "warning");
|
|
6
|
+
if (criticals.length > 0) {
|
|
7
|
+
parts.push(`\u{1F534} <b>${criticals.length} CRITICAL</b> on <b>${serverName}</b>:\n`);
|
|
8
|
+
for (const a of criticals)
|
|
9
|
+
parts.push(` \u2022 <b>${a.title}</b>\n ${a.recommendation}\n`);
|
|
10
|
+
}
|
|
11
|
+
if (warnings.length > 0) {
|
|
12
|
+
parts.push(`\u{1F7E1} <b>${warnings.length} WARNING</b> on <b>${serverName}</b>:\n`);
|
|
13
|
+
for (const a of warnings)
|
|
14
|
+
parts.push(` \u2022 <b>${a.title}</b>\n ${a.recommendation}\n`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (resolvedAlerts.length > 0) {
|
|
18
|
+
parts.push(`\u2705 <b>${resolvedAlerts.length} resolved</b> on <b>${serverName}</b>:\n`);
|
|
19
|
+
for (const a of resolvedAlerts)
|
|
20
|
+
parts.push(` \u2022 ${a.title}\n`);
|
|
21
|
+
}
|
|
22
|
+
if (parts.length === 0)
|
|
23
|
+
return true;
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify({ chat_id: chatId, text: parts.join("\n"), parse_mode: "HTML", disable_web_page_preview: true }),
|
|
29
|
+
signal: AbortSignal.timeout(10000),
|
|
30
|
+
});
|
|
31
|
+
return res.ok;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
console.error("[telegram] Failed to send notification");
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=telegram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../src/notify/telegram.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAgB,EAChB,MAAc,EACd,SAAwB,EACxB,cAA6B,EAC7B,UAAkB;IAElB,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;QACrE,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC;QAEnE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,gBAAgB,SAAS,CAAC,MAAM,uBAAuB,UAAU,SAAS,CAAC,CAAC;YACvF,KAAK,MAAM,CAAC,IAAI,SAAS;gBAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,gBAAgB,QAAQ,CAAC,MAAM,sBAAsB,UAAU,SAAS,CAAC,CAAC;YACrF,KAAK,MAAM,CAAC,IAAI,QAAQ;gBAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,aAAa,cAAc,CAAC,MAAM,uBAAuB,UAAU,SAAS,CAAC,CAAC;QACzF,KAAK,MAAM,CAAC,IAAI,cAAc;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,+BAA+B,QAAQ,cAAc,EAAE;YAC7E,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,wBAAwB,EAAE,IAAI,EAAE,CAAC;YACrH,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export async function pushToForge(url, apiKey, snapshot) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await fetch(`${url}/api/v1/ingest`, {
|
|
4
|
+
method: "POST",
|
|
5
|
+
headers: {
|
|
6
|
+
Authorization: `Bearer ${apiKey}`,
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
},
|
|
9
|
+
body: JSON.stringify(snapshot),
|
|
10
|
+
signal: AbortSignal.timeout(10000),
|
|
11
|
+
});
|
|
12
|
+
if (response.ok) {
|
|
13
|
+
const data = await response.json();
|
|
14
|
+
console.log(`[forge] Push successful. Active alerts: ${data.active_alerts ?? 0}`);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.error(`[forge] Push failed: ${response.status} ${response.statusText}`);
|
|
18
|
+
}
|
|
19
|
+
return response.ok;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.error("[forge] Push failed, will retry next cycle");
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=forge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forge.js","sourceRoot":"","sources":["../../src/push/forge.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,MAAc,EAAE,QAAkB;IAC/E,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,gBAAgB,EAAE;YACnD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,EAAE;gBACjC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAqD,CAAC;YACtF,OAAO,CAAC,GAAG,CAAC,2CAA2C,IAAI,CAAC,aAAa,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,QAAQ,CAAC,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glassmkr/crucible",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight bare metal server monitoring. IPMI, SMART, OS, network. Opinionated alerts.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"glassmkr-crucible": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && chmod 755 dist/index.js",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"clean": "rm -rf dist"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["monitoring", "bare-metal", "ipmi", "smart", "server", "alerts"],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/glassmkr/crucible.git"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"yaml": "^2.4.0",
|
|
23
|
+
"zod": "^3.23.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"typescript": "^5.5.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { allRules } from "./rules.js";
|
|
2
|
+
import type { Snapshot, AlertResult } from "../lib/types.js";
|
|
3
|
+
import type { Config } from "../config.js";
|
|
4
|
+
|
|
5
|
+
export function evaluateAlerts(snapshot: Snapshot, thresholds: Config["thresholds"]): AlertResult[] {
|
|
6
|
+
const results: AlertResult[] = [];
|
|
7
|
+
for (const rule of allRules) {
|
|
8
|
+
try {
|
|
9
|
+
results.push(...rule.evaluate(snapshot, thresholds));
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.error(`[alerts] Rule ${rule.type} error:`, err);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return results;
|
|
15
|
+
}
|