@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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +92 -0
  3. package/config/collector.example.yaml +43 -0
  4. package/dist/alerts/evaluator.d.ts +3 -0
  5. package/dist/alerts/evaluator.js +14 -0
  6. package/dist/alerts/evaluator.js.map +1 -0
  7. package/dist/alerts/rules.d.ts +7 -0
  8. package/dist/alerts/rules.js +203 -0
  9. package/dist/alerts/rules.js.map +1 -0
  10. package/dist/alerts/state.d.ts +6 -0
  11. package/dist/alerts/state.js +77 -0
  12. package/dist/alerts/state.js.map +1 -0
  13. package/dist/collect/cpu.d.ts +2 -0
  14. package/dist/collect/cpu.js +35 -0
  15. package/dist/collect/cpu.js.map +1 -0
  16. package/dist/collect/disks.d.ts +2 -0
  17. package/dist/collect/disks.js +33 -0
  18. package/dist/collect/disks.js.map +1 -0
  19. package/dist/collect/ipmi.d.ts +2 -0
  20. package/dist/collect/ipmi.js +55 -0
  21. package/dist/collect/ipmi.js.map +1 -0
  22. package/dist/collect/memory.d.ts +2 -0
  23. package/dist/collect/memory.js +27 -0
  24. package/dist/collect/memory.js.map +1 -0
  25. package/dist/collect/network.d.ts +2 -0
  26. package/dist/collect/network.js +54 -0
  27. package/dist/collect/network.js.map +1 -0
  28. package/dist/collect/os-alerts.d.ts +2 -0
  29. package/dist/collect/os-alerts.js +41 -0
  30. package/dist/collect/os-alerts.js.map +1 -0
  31. package/dist/collect/raid.d.ts +2 -0
  32. package/dist/collect/raid.js +34 -0
  33. package/dist/collect/raid.js.map +1 -0
  34. package/dist/collect/smart.d.ts +2 -0
  35. package/dist/collect/smart.js +56 -0
  36. package/dist/collect/smart.js.map +1 -0
  37. package/dist/collect/system.d.ts +2 -0
  38. package/dist/collect/system.js +19 -0
  39. package/dist/collect/system.js.map +1 -0
  40. package/dist/config.d.ts +208 -0
  41. package/dist/config.js +58 -0
  42. package/dist/config.js.map +1 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.js +96 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/lib/exec.d.ts +1 -0
  47. package/dist/lib/exec.js +19 -0
  48. package/dist/lib/exec.js.map +1 -0
  49. package/dist/lib/parse.d.ts +4 -0
  50. package/dist/lib/parse.js +29 -0
  51. package/dist/lib/parse.js.map +1 -0
  52. package/dist/lib/types.d.ts +103 -0
  53. package/dist/lib/types.js +2 -0
  54. package/dist/lib/types.js.map +1 -0
  55. package/dist/notify/email.d.ts +4 -0
  56. package/dist/notify/email.js +55 -0
  57. package/dist/notify/email.js.map +1 -0
  58. package/dist/notify/slack.d.ts +2 -0
  59. package/dist/notify/slack.js +38 -0
  60. package/dist/notify/slack.js.map +1 -0
  61. package/dist/notify/telegram.d.ts +2 -0
  62. package/dist/notify/telegram.js +38 -0
  63. package/dist/notify/telegram.js.map +1 -0
  64. package/dist/push/forge.d.ts +2 -0
  65. package/dist/push/forge.js +26 -0
  66. package/dist/push/forge.js.map +1 -0
  67. package/package.json +29 -0
  68. package/src/alerts/evaluator.ts +15 -0
  69. package/src/alerts/rules.ts +184 -0
  70. package/src/alerts/state.ts +92 -0
  71. package/src/collect/cpu.ts +44 -0
  72. package/src/collect/disks.ts +36 -0
  73. package/src/collect/ipmi.ts +60 -0
  74. package/src/collect/memory.ts +30 -0
  75. package/src/collect/network.ts +61 -0
  76. package/src/collect/os-alerts.ts +43 -0
  77. package/src/collect/raid.ts +40 -0
  78. package/src/collect/smart.ts +60 -0
  79. package/src/collect/system.ts +21 -0
  80. package/src/config.ts +60 -0
  81. package/src/index.ts +112 -0
  82. package/src/lib/exec.ts +16 -0
  83. package/src/lib/parse.ts +29 -0
  84. package/src/lib/types.ts +110 -0
  85. package/src/notify/email.ts +68 -0
  86. package/src/notify/slack.ts +46 -0
  87. package/src/notify/telegram.ts +45 -0
  88. package/src/push/forge.ts +25 -0
  89. 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>;
@@ -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,4 @@
1
+ export declare function readProcFile(path: string): string | null;
2
+ export declare function parseKeyValue(raw: string): Record<string, string>;
3
+ export declare function parseKb(val: string | undefined): number;
4
+ export declare function sleep(ms: number): Promise<void>;
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { AlertResult } from "../lib/types.js";
2
+ export declare function sendEmail(config: {
3
+ to: string;
4
+ }, newAlerts: AlertResult[], resolvedAlerts: AlertResult[], serverName: string): Promise<boolean>;
@@ -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,2 @@
1
+ import type { AlertResult } from "../lib/types.js";
2
+ export declare function sendSlack(webhookUrl: string, newAlerts: AlertResult[], resolvedAlerts: AlertResult[], serverName: string): Promise<boolean>;
@@ -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,2 @@
1
+ import type { AlertResult } from "../lib/types.js";
2
+ export declare function sendTelegram(botToken: string, chatId: string, newAlerts: AlertResult[], resolvedAlerts: AlertResult[], serverName: string): Promise<boolean>;
@@ -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,2 @@
1
+ import type { Snapshot } from "../lib/types.js";
2
+ export declare function pushToForge(url: string, apiKey: string, snapshot: Snapshot): Promise<boolean>;
@@ -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
+ }