@glassmkr/crucible 0.7.1 → 0.8.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 (103) hide show
  1. package/dist/alerts/__tests__/rules.test.d.ts +1 -0
  2. package/dist/alerts/__tests__/rules.test.js +325 -0
  3. package/dist/alerts/__tests__/rules.test.js.map +1 -0
  4. package/dist/alerts/rules.d.ts +8 -0
  5. package/dist/alerts/rules.js +139 -32
  6. package/dist/alerts/rules.js.map +1 -1
  7. package/dist/api.d.ts +2 -0
  8. package/dist/api.js +7 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/collect/__tests__/dmi.test.d.ts +1 -0
  11. package/dist/collect/__tests__/dmi.test.js +114 -0
  12. package/dist/collect/__tests__/dmi.test.js.map +1 -0
  13. package/dist/collect/__tests__/ipmi.test.js +47 -1
  14. package/dist/collect/__tests__/ipmi.test.js.map +1 -1
  15. package/dist/collect/__tests__/thermal.test.d.ts +1 -0
  16. package/dist/collect/__tests__/thermal.test.js +164 -0
  17. package/dist/collect/__tests__/thermal.test.js.map +1 -0
  18. package/dist/collect/dmi.d.ts +19 -0
  19. package/dist/collect/dmi.js +109 -0
  20. package/dist/collect/dmi.js.map +1 -0
  21. package/dist/collect/ipmi.d.ts +27 -2
  22. package/dist/collect/ipmi.js +90 -2
  23. package/dist/collect/ipmi.js.map +1 -1
  24. package/dist/collect/thermal.d.ts +10 -0
  25. package/dist/collect/thermal.js +187 -0
  26. package/dist/collect/thermal.js.map +1 -0
  27. package/dist/config.d.ts +10 -0
  28. package/dist/config.js +2 -0
  29. package/dist/config.js.map +1 -1
  30. package/dist/index.js +51 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/lib/__tests__/capability.test.d.ts +1 -0
  33. package/dist/lib/__tests__/capability.test.js +87 -0
  34. package/dist/lib/__tests__/capability.test.js.map +1 -0
  35. package/dist/lib/__tests__/vendor-sensors.test.d.ts +1 -0
  36. package/dist/lib/__tests__/vendor-sensors.test.js +49 -0
  37. package/dist/lib/__tests__/vendor-sensors.test.js.map +1 -0
  38. package/dist/lib/capability.d.ts +21 -0
  39. package/dist/lib/capability.js +110 -0
  40. package/dist/lib/capability.js.map +1 -0
  41. package/dist/lib/cpu-thermal-chips.d.ts +2 -0
  42. package/dist/lib/cpu-thermal-chips.js +28 -0
  43. package/dist/lib/cpu-thermal-chips.js.map +1 -0
  44. package/dist/lib/types.d.ts +58 -0
  45. package/dist/lib/vendor-sensors.d.ts +27 -0
  46. package/dist/lib/vendor-sensors.js +63 -0
  47. package/dist/lib/vendor-sensors.js.map +1 -0
  48. package/dist/notify/telegram.js +1 -1
  49. package/dist/notify/telegram.js.map +1 -1
  50. package/package.json +16 -1
  51. package/rule-ids.json +29 -0
  52. package/.dockerignore +0 -13
  53. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
  54. package/.github/ISSUE_TEMPLATE/no_data.md +0 -26
  55. package/.github/workflows/docker.yml +0 -53
  56. package/.github/workflows/publish.yml +0 -25
  57. package/Dockerfile +0 -59
  58. package/config/collector.example.yaml +0 -43
  59. package/docker-compose.yml +0 -26
  60. package/scripts/sign-release.sh +0 -29
  61. package/src/__tests__/cli.test.ts +0 -74
  62. package/src/__tests__/reboot-marker.test.ts +0 -122
  63. package/src/alerts/evaluator.ts +0 -15
  64. package/src/alerts/rules.ts +0 -283
  65. package/src/alerts/state.ts +0 -92
  66. package/src/cli.ts +0 -112
  67. package/src/collect/__tests__/ipmi.test.ts +0 -96
  68. package/src/collect/__tests__/smart.test.ts +0 -68
  69. package/src/collect/__tests__/system.test.ts +0 -29
  70. package/src/collect/__tests__/zfs.test.ts +0 -72
  71. package/src/collect/conntrack.ts +0 -27
  72. package/src/collect/cpu.ts +0 -92
  73. package/src/collect/disks.ts +0 -91
  74. package/src/collect/fd.ts +0 -31
  75. package/src/collect/io-errors.ts +0 -23
  76. package/src/collect/io-latency.ts +0 -103
  77. package/src/collect/ipmi.ts +0 -207
  78. package/src/collect/memory.ts +0 -30
  79. package/src/collect/network.ts +0 -193
  80. package/src/collect/ntp.ts +0 -114
  81. package/src/collect/os-alerts.ts +0 -43
  82. package/src/collect/raid.ts +0 -40
  83. package/src/collect/security.ts +0 -268
  84. package/src/collect/smart.ts +0 -72
  85. package/src/collect/system.ts +0 -32
  86. package/src/collect/systemd.ts +0 -33
  87. package/src/collect/zfs.ts +0 -66
  88. package/src/config.ts +0 -65
  89. package/src/index.ts +0 -221
  90. package/src/lib/__tests__/parse.test.ts +0 -28
  91. package/src/lib/exec.ts +0 -16
  92. package/src/lib/parse.ts +0 -29
  93. package/src/lib/reboot-marker.ts +0 -88
  94. package/src/lib/types.ts +0 -226
  95. package/src/lib/version-check.ts +0 -39
  96. package/src/lib/version.ts +0 -33
  97. package/src/metrics-server.ts +0 -123
  98. package/src/notify/email.ts +0 -69
  99. package/src/notify/slack.ts +0 -47
  100. package/src/notify/telegram.ts +0 -65
  101. package/src/push/forge.ts +0 -109
  102. package/tsconfig.json +0 -15
  103. package/vitest.config.ts +0 -12
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { detectIpmiCapability, formatCapabilityLine } from "../capability.js";
3
+ describe("detectIpmiCapability", () => {
4
+ it("returns no_ipmitool_binary when neither device nor binary exist (Pi)", async () => {
5
+ const cap = await detectIpmiCapability({
6
+ statDevice: async () => "enoent",
7
+ runIpmitool: async () => { const e = new Error("not found"); e.code = "ENOENT"; throw e; },
8
+ });
9
+ expect(cap).toEqual({ available: false, reason: "no_ipmitool_binary" });
10
+ });
11
+ it("returns available when /dev/ipmi0 exists and ipmitool runs", async () => {
12
+ const cap = await detectIpmiCapability({
13
+ statDevice: async (p) => p === "/dev/ipmi0" ? "ok" : "enoent",
14
+ runIpmitool: async () => ({ stdout: "ipmitool version 1.8.18\n", stderr: "" }),
15
+ });
16
+ expect(cap).toEqual({ available: true, method: "ipmitool_in_band", ipmitool_version: "1.8.18" });
17
+ });
18
+ it("returns no_bmc_device when ipmitool exists but device is missing AND sensor probe fails", async () => {
19
+ const cap = await detectIpmiCapability({
20
+ statDevice: async () => "enoent",
21
+ runIpmitool: async (args) => {
22
+ if (args[0] === "-V")
23
+ return { stdout: "ipmitool version 1.8.18", stderr: "" };
24
+ const e = new Error("Could not open device");
25
+ e.stderr = "Could not open device at /dev/ipmi0: No such file or directory";
26
+ throw e;
27
+ },
28
+ });
29
+ expect(cap.available).toBe(false);
30
+ if (!cap.available)
31
+ expect(cap.reason).toBe("no_bmc_device");
32
+ });
33
+ it("returns permission_denied on EACCES", async () => {
34
+ const cap = await detectIpmiCapability({
35
+ statDevice: async () => "eacces",
36
+ runIpmitool: async () => ({ stdout: "ipmitool version 1.8.18", stderr: "" }),
37
+ });
38
+ expect(cap.available).toBe(false);
39
+ if (!cap.available) {
40
+ expect(cap.reason).toBe("permission_denied");
41
+ expect(cap.detail).toContain("ipmi");
42
+ }
43
+ });
44
+ it("VM with ipmitool installed but no virtual BMC: no_bmc_device", async () => {
45
+ const cap = await detectIpmiCapability({
46
+ statDevice: async () => "enoent",
47
+ runIpmitool: async (args) => {
48
+ if (args[0] === "-V")
49
+ return { stdout: "ipmitool version 1.8.19", stderr: "" };
50
+ const e = new Error("could not open");
51
+ e.stderr = "Could not open device";
52
+ throw e;
53
+ },
54
+ });
55
+ expect(cap.available).toBe(false);
56
+ if (!cap.available)
57
+ expect(cap.reason).toBe("no_bmc_device");
58
+ });
59
+ it("captures ipmitool version when present", async () => {
60
+ const cap = await detectIpmiCapability({
61
+ statDevice: async (p) => p === "/dev/ipmi0" ? "ok" : "enoent",
62
+ runIpmitool: async () => ({ stdout: "ipmitool version 1.8.21-rc1\n", stderr: "" }),
63
+ });
64
+ expect(cap.available).toBe(true);
65
+ if (cap.available)
66
+ expect(cap.ipmitool_version).toBe("1.8.21-rc1");
67
+ });
68
+ });
69
+ describe("formatCapabilityLine", () => {
70
+ it("formats available capability", () => {
71
+ expect(formatCapabilityLine({ available: true, method: "ipmitool_in_band", ipmitool_version: "1.8.18" }))
72
+ .toBe("IPMI: available (ipmitool 1.8.18, ipmitool in band)");
73
+ });
74
+ it("formats no ipmitool", () => {
75
+ expect(formatCapabilityLine({ available: false, reason: "no_ipmitool_binary" }))
76
+ .toBe("IPMI: not available (ipmitool not installed)");
77
+ });
78
+ it("formats no BMC", () => {
79
+ expect(formatCapabilityLine({ available: false, reason: "no_bmc_device" }))
80
+ .toBe("IPMI: not available (no /dev/ipmi*, BMC not detected)");
81
+ });
82
+ it("formats permission denied", () => {
83
+ expect(formatCapabilityLine({ available: false, reason: "permission_denied", detail: "/dev/ipmi0 not readable" }))
84
+ .toBe("IPMI: not available (/dev/ipmi0 not readable)");
85
+ });
86
+ });
87
+ //# sourceMappingURL=capability.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/capability.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isPsuSensor, isPsuRedundancySensor, classifyPsuRedundancyState } from "../vendor-sensors.js";
3
+ describe("isPsuSensor", () => {
4
+ it("matches generic Supermicro/HPE patterns regardless of vendor", () => {
5
+ expect(isPsuSensor("PSU1 Status", "supermicro")).toBe(true);
6
+ expect(isPsuSensor("PSU1 Status", "dell")).toBe(true);
7
+ expect(isPsuSensor("Power Supply 1", "hpe")).toBe(true);
8
+ expect(isPsuSensor("Power Supply 1", "generic")).toBe(true);
9
+ });
10
+ it("matches Dell PS<N> pattern only when vendor is dell", () => {
11
+ expect(isPsuSensor("PS1 Status", "dell")).toBe(true);
12
+ expect(isPsuSensor("PS2 Status", "dell")).toBe(true);
13
+ expect(isPsuSensor("PS1 Status", "supermicro")).toBe(false);
14
+ expect(isPsuSensor("PS1 Status", "generic")).toBe(false);
15
+ });
16
+ it("does not match PS Redundancy as an individual PSU sensor", () => {
17
+ expect(isPsuSensor("PS Redundancy", "dell")).toBe(false);
18
+ });
19
+ it("rejects unrelated sensors", () => {
20
+ expect(isPsuSensor("CPU1 Temp", "dell")).toBe(false);
21
+ expect(isPsuSensor("Some Random Sensor", "supermicro")).toBe(false);
22
+ });
23
+ });
24
+ describe("isPsuRedundancySensor", () => {
25
+ it("matches Dell PS Redundancy", () => {
26
+ expect(isPsuRedundancySensor("PS Redundancy", "dell")).toBe(true);
27
+ });
28
+ it("does not match on other vendors", () => {
29
+ expect(isPsuRedundancySensor("PS Redundancy", "supermicro")).toBe(false);
30
+ expect(isPsuRedundancySensor("PS Redundancy", "generic")).toBe(false);
31
+ });
32
+ it("does not match individual PSU sensors", () => {
33
+ expect(isPsuRedundancySensor("PS1 Status", "dell")).toBe(false);
34
+ });
35
+ });
36
+ describe("classifyPsuRedundancyState", () => {
37
+ it("recognises fully redundant", () => {
38
+ expect(classifyPsuRedundancyState("Fully Redundant")).toBe("fully_redundant");
39
+ expect(classifyPsuRedundancyState("ok")).toBe("fully_redundant");
40
+ });
41
+ it("recognises lost / degraded", () => {
42
+ expect(classifyPsuRedundancyState("Redundancy Lost")).toBe("redundancy_lost");
43
+ expect(classifyPsuRedundancyState("Redundancy Degraded")).toBe("redundancy_degraded");
44
+ });
45
+ it("returns unknown for unrecognised text", () => {
46
+ expect(classifyPsuRedundancyState("0x42")).toBe("unknown");
47
+ });
48
+ });
49
+ //# sourceMappingURL=vendor-sensors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vendor-sensors.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/vendor-sensors.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEtG,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,WAAW,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,CAAC,WAAW,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,WAAW,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,WAAW,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,WAAW,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,CAAC,WAAW,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,qBAAqB,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,qBAAqB,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzE,MAAM,CAAC,qBAAqB,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,qBAAqB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,0BAA0B,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC9E,MAAM,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,0BAA0B,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC9E,MAAM,CAAC,0BAA0B,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ export type IpmiCapability = {
2
+ available: true;
3
+ method: "ipmitool_in_band";
4
+ ipmitool_version: string | null;
5
+ } | {
6
+ available: false;
7
+ reason: "no_ipmitool_binary" | "no_bmc_device" | "execution_failed" | "permission_denied";
8
+ detail?: string;
9
+ };
10
+ interface DetectDeps {
11
+ /** Override for tests. Returns "ok" | "enoent" | "eacces" per path. */
12
+ statDevice?: (path: string) => Promise<"ok" | "enoent" | "eacces">;
13
+ /** Override for tests. Returns stdout or throws with err.code. */
14
+ runIpmitool?: (args: string[]) => Promise<{
15
+ stdout: string;
16
+ stderr: string;
17
+ }>;
18
+ }
19
+ export declare function detectIpmiCapability(deps?: DetectDeps): Promise<IpmiCapability>;
20
+ export declare function formatCapabilityLine(cap: IpmiCapability): string;
21
+ export {};
@@ -0,0 +1,110 @@
1
+ // One-shot IPMI capability detection at process startup.
2
+ //
3
+ // The agent runs collectIpmi() every cycle. Without this layer, hosts
4
+ // without a BMC (Pi, laptop, VM, container without /dev mapped) hit four
5
+ // ipmitool ENOENT or "Could not open device" execs every interval forever.
6
+ // They're silent (lib/exec.ts swallows ENOENT) but still wasted process
7
+ // spawns, and there's no log telling the user IPMI is unavailable here.
8
+ //
9
+ // This module probes once at startup and caches the result. collectIpmi()
10
+ // reads the cached capability and short-circuits when unavailable.
11
+ import { promises as fs } from "node:fs";
12
+ import { execFile } from "node:child_process";
13
+ import { promisify } from "node:util";
14
+ const execFileAsync = promisify(execFile);
15
+ const DEVICE_CANDIDATES = ["/dev/ipmi0", "/dev/ipmi/0", "/dev/ipmidev/0"];
16
+ async function defaultStatDevice(path) {
17
+ try {
18
+ await fs.access(path, fs.constants.R_OK);
19
+ return "ok";
20
+ }
21
+ catch (err) {
22
+ if (err.code === "EACCES" || err.code === "EPERM")
23
+ return "eacces";
24
+ return "enoent";
25
+ }
26
+ }
27
+ async function defaultRunIpmitool(args) {
28
+ const { stdout, stderr } = await execFileAsync("ipmitool", args, { timeout: 2000 });
29
+ return { stdout, stderr };
30
+ }
31
+ export async function detectIpmiCapability(deps = {}) {
32
+ const statDevice = deps.statDevice ?? defaultStatDevice;
33
+ const runIpmitool = deps.runIpmitool ?? defaultRunIpmitool;
34
+ // Step 1: probe /dev/ipmi* device nodes.
35
+ let deviceFound = false;
36
+ let permissionDenied = false;
37
+ for (const path of DEVICE_CANDIDATES) {
38
+ const result = await statDevice(path);
39
+ if (result === "ok") {
40
+ deviceFound = true;
41
+ break;
42
+ }
43
+ if (result === "eacces") {
44
+ permissionDenied = true;
45
+ }
46
+ }
47
+ if (permissionDenied && !deviceFound) {
48
+ return {
49
+ available: false,
50
+ reason: "permission_denied",
51
+ detail: "/dev/ipmi0 exists but is not readable; run as root or add user to ipmi group",
52
+ };
53
+ }
54
+ // Step 2: probe ipmitool binary.
55
+ let ipmitoolVersion = null;
56
+ try {
57
+ const { stdout } = await runIpmitool(["-V"]);
58
+ const m = stdout.match(/ipmitool version (\S+)/);
59
+ ipmitoolVersion = m ? m[1] : null;
60
+ }
61
+ catch (err) {
62
+ if (err?.code === "ENOENT") {
63
+ return { available: false, reason: "no_ipmitool_binary" };
64
+ }
65
+ return {
66
+ available: false,
67
+ reason: "execution_failed",
68
+ detail: String(err?.stderr ?? err?.message ?? err).split("\n")[0]?.slice(0, 200),
69
+ };
70
+ }
71
+ // Step 3: device + binary present → assume capable.
72
+ if (deviceFound) {
73
+ return { available: true, method: "ipmitool_in_band", ipmitool_version: ipmitoolVersion };
74
+ }
75
+ // Step 4: binary present but no device node — try one sensor probe to
76
+ // disambiguate (some kernels expose IPMI through unconventional paths).
77
+ try {
78
+ const { stdout, stderr } = await runIpmitool(["sensor"]);
79
+ const out = stdout || "";
80
+ const errOut = (stderr || "").toLowerCase();
81
+ if (out.trim().length > 0 && !errOut.includes("could not open")) {
82
+ return { available: true, method: "ipmitool_in_band", ipmitool_version: ipmitoolVersion };
83
+ }
84
+ return { available: false, reason: "no_bmc_device" };
85
+ }
86
+ catch (err) {
87
+ const stderr = String(err?.stderr ?? "").toLowerCase();
88
+ if (stderr.includes("could not open")) {
89
+ return { available: false, reason: "no_bmc_device" };
90
+ }
91
+ return {
92
+ available: false,
93
+ reason: "execution_failed",
94
+ detail: String(err?.stderr ?? err?.message ?? err).split("\n")[0]?.slice(0, 200),
95
+ };
96
+ }
97
+ }
98
+ export function formatCapabilityLine(cap) {
99
+ if (cap.available) {
100
+ const v = cap.ipmitool_version ? `ipmitool ${cap.ipmitool_version}, ` : "";
101
+ return `IPMI: available (${v}${cap.method.replace(/_/g, " ")})`;
102
+ }
103
+ switch (cap.reason) {
104
+ case "no_ipmitool_binary": return "IPMI: not available (ipmitool not installed)";
105
+ case "no_bmc_device": return "IPMI: not available (no /dev/ipmi*, BMC not detected)";
106
+ case "permission_denied": return `IPMI: not available (${cap.detail ?? "permission denied"})`;
107
+ case "execution_failed": return `IPMI: not available (execution failed${cap.detail ? `: ${cap.detail}` : ""})`;
108
+ }
109
+ }
110
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../../src/lib/capability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const CPU_THERMAL_CHIPS: ReadonlySet<string>;
2
+ export declare function isCpuChip(name: string): boolean;
@@ -0,0 +1,28 @@
1
+ // Allowlist of hwmon chip `name` values that report CPU die / package
2
+ // temperatures. Adding a new platform here is the supported way to teach
3
+ // the thermal collector that a chip's readings represent CPU temperature.
4
+ //
5
+ // Drivers explicitly excluded:
6
+ // - "nvme" already covered by SMART; double counts otherwise
7
+ // - "acpitz" often reads ambient or chassis air, not CPU die. Goes to
8
+ // other_readings if found, never to cpu_readings.
9
+ export const CPU_THERMAL_CHIPS = new Set([
10
+ // Intel x86
11
+ "coretemp",
12
+ // AMD x86 (modern)
13
+ "k10temp",
14
+ "zenpower",
15
+ // ARM / SoC
16
+ "cpu_thermal", // Raspberry Pi 4/5, many ARM SBCs
17
+ "armada_thermal", // Marvell Armada SoCs
18
+ "tegra_thermal", // NVIDIA Tegra
19
+ "qcom_tsens", // Qualcomm
20
+ "imx_thermal", // NXP i.MX
21
+ "sun4i_ts", // Allwinner sunxi
22
+ "rockchip_thermal", // Rockchip RK3399 etc.
23
+ "exynos_thermal", // Samsung Exynos
24
+ ]);
25
+ export function isCpuChip(name) {
26
+ return CPU_THERMAL_CHIPS.has(name);
27
+ }
28
+ //# sourceMappingURL=cpu-thermal-chips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpu-thermal-chips.js","sourceRoot":"","sources":["../../src/lib/cpu-thermal-chips.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,yEAAyE;AACzE,0EAA0E;AAC1E,EAAE;AACF,+BAA+B;AAC/B,iEAAiE;AACjE,wEAAwE;AACxE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC;IAC5D,YAAY;IACZ,UAAU;IACV,mBAAmB;IACnB,SAAS;IACT,UAAU;IACV,YAAY;IACZ,aAAa,EAAO,kCAAkC;IACtD,gBAAgB,EAAI,sBAAsB;IAC1C,eAAe,EAAK,eAAe;IACnC,YAAY,EAAQ,WAAW;IAC/B,aAAa,EAAO,WAAW;IAC/B,UAAU,EAAU,kBAAkB;IACtC,kBAAkB,EAAE,uBAAuB;IAC3C,gBAAgB,EAAI,iBAAiB;CACtC,CAAC,CAAC;AAEH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC"}
@@ -9,6 +9,8 @@ export interface Snapshot {
9
9
  network: NetworkInfo[];
10
10
  raid: RaidInfo[];
11
11
  ipmi: IpmiInfo;
12
+ dmi?: DmiInfo;
13
+ thermal?: ThermalInfo;
12
14
  os_alerts: OsAlerts;
13
15
  security?: SecurityData;
14
16
  zfs?: ZfsData;
@@ -206,8 +208,33 @@ export interface FanStatus {
206
208
  rpm: number;
207
209
  status: string;
208
210
  }
211
+ export type Vendor = "dell" | "hpe" | "supermicro" | "asrockrack" | "lenovo" | "inspur" | "cisco" | "generic" | "virtual";
212
+ export interface DmiInfo {
213
+ available: boolean;
214
+ vendor: Vendor;
215
+ /** Exact /sys/class/dmi/id/sys_vendor contents, trimmed. */
216
+ raw_vendor: string | null;
217
+ product_name: string | null;
218
+ bios_version: string | null;
219
+ bios_date: string | null;
220
+ is_virtual: boolean;
221
+ }
222
+ export type PsuRedundancyState = "fully_redundant" | "redundancy_lost" | "redundancy_degraded" | "unknown";
223
+ export type IpmiCapability = {
224
+ available: true;
225
+ method: "ipmitool_in_band";
226
+ ipmitool_version: string | null;
227
+ } | {
228
+ available: false;
229
+ reason: "no_ipmitool_binary" | "no_bmc_device" | "execution_failed" | "permission_denied";
230
+ detail?: string;
231
+ };
209
232
  export interface IpmiInfo {
210
233
  available: boolean;
234
+ /** One-shot startup detection result; helps Forge surface "IPMI not
235
+ * available on this host" with a precise reason. Not present on
236
+ * pre-detection snapshots (older agent versions). */
237
+ detection?: IpmiCapability;
211
238
  sensors: Array<{
212
239
  name: string;
213
240
  value: number | string;
@@ -219,10 +246,41 @@ export interface IpmiInfo {
219
246
  correctable: number;
220
247
  uncorrectable: number;
221
248
  };
249
+ /**
250
+ * ECC error counts derived from SEL events instead of named sensors.
251
+ * Dell iDRAC reports memory ECC only via SEL on the Memory entity, so
252
+ * the named-sensor counter (`ecc_errors`) stays at zero on Dell. The
253
+ * `ecc_errors` rule reads max(named, sel) to cover both vendors.
254
+ * Cumulative since last SEL clear, not rate over interval.
255
+ */
256
+ ecc_errors_from_sel?: {
257
+ correctable: number;
258
+ uncorrectable: number;
259
+ newest_event_timestamp: string | null;
260
+ };
261
+ /**
262
+ * Aggregate PSU redundancy state from a vendor sensor (currently Dell
263
+ * `PS Redundancy` only). Undefined on hosts where no aggregate sensor
264
+ * exists; the rule then falls back to per-PSU status checks.
265
+ */
266
+ psu_redundancy_state?: PsuRedundancyState;
222
267
  sel_entries_count: number;
223
268
  sel_events_recent: SelEvent[];
224
269
  fans: FanStatus[];
225
270
  }
271
+ export interface ThermalReading {
272
+ label: string;
273
+ value_celsius: number;
274
+ source_chip: string;
275
+ source: "hwmon" | "thermal_zone";
276
+ }
277
+ export interface ThermalInfo {
278
+ available: boolean;
279
+ source: "hwmon" | "thermal_zone" | "none";
280
+ cpu_readings: ThermalReading[];
281
+ other_readings: ThermalReading[];
282
+ max_cpu_celsius: number | null;
283
+ }
226
284
  export interface OsAlerts {
227
285
  oom_kills_recent: number;
228
286
  zombie_processes: number;
@@ -0,0 +1,27 @@
1
+ import type { Vendor } from "./types.js";
2
+ /**
3
+ * True if `name` looks like an individual PSU sensor (status / wattage / etc.)
4
+ * for the given vendor.
5
+ *
6
+ * Generic patterns cover Supermicro, HPE iLO, ASRockRack:
7
+ * "PSU1 Status", "Power Supply 1", "PSU2 Power Out"
8
+ *
9
+ * Dell iDRAC adds:
10
+ * "PS1 Status", "PS2 Status", "PS3 Status"
11
+ *
12
+ * Note that "PS Redundancy" is NOT an individual PSU sensor; see
13
+ * `isPsuRedundancySensor`.
14
+ */
15
+ export declare function isPsuSensor(name: string, vendor: Vendor): boolean;
16
+ /**
17
+ * True if `name` is the aggregate PSU redundancy state sensor.
18
+ * Currently only Dell exposes this as a discrete sensor reading;
19
+ * HPE/Supermicro report it via SEL events instead.
20
+ */
21
+ export declare function isPsuRedundancySensor(name: string, vendor: Vendor): boolean;
22
+ /**
23
+ * Map the value/status text of a Dell `PS Redundancy` sensor to a canonical
24
+ * state. Dell reports strings like "Fully Redundant", "Redundancy Lost",
25
+ * "Redundancy Degraded", or "0x01"-style raw codes depending on iDRAC firmware.
26
+ */
27
+ export declare function classifyPsuRedundancyState(valueOrStatus: string): "fully_redundant" | "redundancy_lost" | "redundancy_degraded" | "unknown";
@@ -0,0 +1,63 @@
1
+ // Vendor-aware classifiers for IPMI sensor names.
2
+ //
3
+ // The substring filters in alert rules historically assumed Supermicro /
4
+ // ASRockRack naming conventions (`PSU1 Status`, `CPU1 Temp`, etc.). Dell
5
+ // iDRAC names sensors very differently: `PS1 Status`, `PS Redundancy`,
6
+ // bare `Temp` per processor entity. HPE iLO is closer to Supermicro's
7
+ // shape but has its own quirks. Adding a new vendor means adding a case
8
+ // here, not editing every rule.
9
+ /**
10
+ * True if `name` looks like an individual PSU sensor (status / wattage / etc.)
11
+ * for the given vendor.
12
+ *
13
+ * Generic patterns cover Supermicro, HPE iLO, ASRockRack:
14
+ * "PSU1 Status", "Power Supply 1", "PSU2 Power Out"
15
+ *
16
+ * Dell iDRAC adds:
17
+ * "PS1 Status", "PS2 Status", "PS3 Status"
18
+ *
19
+ * Note that "PS Redundancy" is NOT an individual PSU sensor; see
20
+ * `isPsuRedundancySensor`.
21
+ */
22
+ export function isPsuSensor(name, vendor) {
23
+ const lower = name.toLowerCase();
24
+ if (lower.includes("psu") || lower.includes("power supply"))
25
+ return true;
26
+ if (vendor === "dell") {
27
+ // PS1, PS2 ... optionally followed by " Status" or other suffix.
28
+ // Excludes "PS Redundancy" and similar non-numeric.
29
+ if (/^ps\d+\b/i.test(name))
30
+ return true;
31
+ }
32
+ return false;
33
+ }
34
+ /**
35
+ * True if `name` is the aggregate PSU redundancy state sensor.
36
+ * Currently only Dell exposes this as a discrete sensor reading;
37
+ * HPE/Supermicro report it via SEL events instead.
38
+ */
39
+ export function isPsuRedundancySensor(name, vendor) {
40
+ if (vendor === "dell") {
41
+ return /^ps\s+redundancy$/i.test(name);
42
+ }
43
+ return false;
44
+ }
45
+ /**
46
+ * Map the value/status text of a Dell `PS Redundancy` sensor to a canonical
47
+ * state. Dell reports strings like "Fully Redundant", "Redundancy Lost",
48
+ * "Redundancy Degraded", or "0x01"-style raw codes depending on iDRAC firmware.
49
+ */
50
+ export function classifyPsuRedundancyState(valueOrStatus) {
51
+ const lower = valueOrStatus.toLowerCase();
52
+ if (lower.includes("fully redundant") || lower.includes("fully-redundant"))
53
+ return "fully_redundant";
54
+ if (lower.includes("lost"))
55
+ return "redundancy_lost";
56
+ if (lower.includes("degraded"))
57
+ return "redundancy_degraded";
58
+ // Some iDRAC firmwares report "ok" + numeric value 1 = fully redundant
59
+ if (lower === "ok")
60
+ return "fully_redundant";
61
+ return "unknown";
62
+ }
63
+ //# sourceMappingURL=vendor-sensors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vendor-sensors.js","sourceRoot":"","sources":["../../src/lib/vendor-sensors.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,uEAAuE;AACvE,sEAAsE;AACtE,wEAAwE;AACxE,gCAAgC;AAIhC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,iEAAiE;QACjE,oDAAoD;QACpD,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,MAAc;IAChE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,aAAqB;IAC9D,MAAM,KAAK,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACrG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACrD,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,qBAAqB,CAAC;IAC7D,uEAAuE;IACvE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,iBAAiB,CAAC;IAC7C,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -3,7 +3,7 @@ const PRIORITY_MAP = {
3
3
  oom_kills: "P2", ram_high: "P2", disk_space_high: "P2", ipmi_sel_critical: "P2", disk_io_errors: "P2", zfs_pool_unhealthy: "P2",
4
4
  cpu_iowait_high: "P3", nvme_wear_high: "P3", disk_latency_high: "P3", cpu_temperature_high: "P3",
5
5
  ssh_root_password: "P3", pending_security_updates: "P3", kernel_vulnerabilities: "P3", zfs_scrub_errors: "P3",
6
- swap_active: "P4", no_firewall: "P4", kernel_needs_reboot: "P4", unattended_upgrades_disabled: "P4",
6
+ swap_high: "P4", no_firewall: "P4", kernel_needs_reboot: "P4", unattended_upgrades_disabled: "P4",
7
7
  interface_errors: "P4", link_speed_mismatch: "P4", interface_saturation: "P4",
8
8
  };
9
9
  const PRIORITY_LABELS = {
@@ -1 +1 @@
1
- {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../src/notify/telegram.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../src/notify/telegram.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "@glassmkr/crucible",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Lightweight bare metal server monitoring. IPMI, SMART, OS, network. Opinionated alerts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./api": {
10
+ "types": "./dist/api.d.ts",
11
+ "default": "./dist/api.js"
12
+ },
13
+ "./rule-ids.json": "./rule-ids.json",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "rule-ids.json",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
7
22
  "bin": {
8
23
  "glassmkr-crucible": "./dist/index.js"
9
24
  },
package/rule-ids.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "$comment": "Static, hand-maintained list of every rule ID this collector ships. Kept in sync with src/alerts/rules.ts (ALL_RULE_IDS). Published in the npm package so tooling can read it without dynamic-importing the CLI module. CI checks the two stay in sync.",
3
+ "version": 1,
4
+ "rule_ids": [
5
+ "ram_high",
6
+ "swap_high",
7
+ "disk_space_high",
8
+ "cpu_iowait_high",
9
+ "oom_kills",
10
+ "smart_failing",
11
+ "nvme_wear_high",
12
+ "raid_degraded",
13
+ "disk_latency_high",
14
+ "interface_errors",
15
+ "link_speed_mismatch",
16
+ "interface_saturation",
17
+ "cpu_temperature_high",
18
+ "ecc_errors",
19
+ "psu_redundancy_loss",
20
+ "ipmi_sel_critical",
21
+ "ipmi_fan_failure",
22
+ "ssh_root_password",
23
+ "no_firewall",
24
+ "pending_security_updates",
25
+ "kernel_vulnerabilities",
26
+ "kernel_needs_reboot",
27
+ "unattended_upgrades_disabled"
28
+ ]
29
+ }
package/.dockerignore DELETED
@@ -1,13 +0,0 @@
1
- node_modules
2
- .git
3
- .github
4
- *.md
5
- !README.md
6
- .svelte-kit
7
- dist
8
- coverage
9
- .env*
10
- .vscode
11
- .idea
12
- *.log
13
- .DS_Store
@@ -1,24 +0,0 @@
1
- ---
2
- name: Bug Report
3
- about: Something is not working as expected
4
- ---
5
-
6
- **Crucible version:** (check package.json or service logs)
7
- **OS and version:** (e.g., Ubuntu 24.04)
8
- **Kernel:** (output of `uname -r`)
9
- **Node.js version:** (output of `node --version`)
10
- **Running as root:** yes/no
11
-
12
- **What happened:**
13
-
14
- **What you expected:**
15
-
16
- **Service status:**
17
- ```
18
- systemctl status glassmkr-crucible
19
- ```
20
-
21
- **Last 50 log lines:**
22
- ```
23
- journalctl -u glassmkr-crucible -n 50 --no-pager
24
- ```