@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.
- package/dist/alerts/__tests__/rules.test.d.ts +1 -0
- package/dist/alerts/__tests__/rules.test.js +325 -0
- package/dist/alerts/__tests__/rules.test.js.map +1 -0
- package/dist/alerts/rules.d.ts +8 -0
- package/dist/alerts/rules.js +139 -32
- package/dist/alerts/rules.js.map +1 -1
- package/dist/api.d.ts +2 -0
- package/dist/api.js +7 -0
- package/dist/api.js.map +1 -0
- package/dist/collect/__tests__/dmi.test.d.ts +1 -0
- package/dist/collect/__tests__/dmi.test.js +114 -0
- package/dist/collect/__tests__/dmi.test.js.map +1 -0
- package/dist/collect/__tests__/ipmi.test.js +47 -1
- package/dist/collect/__tests__/ipmi.test.js.map +1 -1
- package/dist/collect/__tests__/thermal.test.d.ts +1 -0
- package/dist/collect/__tests__/thermal.test.js +164 -0
- package/dist/collect/__tests__/thermal.test.js.map +1 -0
- package/dist/collect/dmi.d.ts +19 -0
- package/dist/collect/dmi.js +109 -0
- package/dist/collect/dmi.js.map +1 -0
- package/dist/collect/ipmi.d.ts +27 -2
- package/dist/collect/ipmi.js +90 -2
- package/dist/collect/ipmi.js.map +1 -1
- package/dist/collect/thermal.d.ts +10 -0
- package/dist/collect/thermal.js +187 -0
- package/dist/collect/thermal.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +51 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/__tests__/capability.test.d.ts +1 -0
- package/dist/lib/__tests__/capability.test.js +87 -0
- package/dist/lib/__tests__/capability.test.js.map +1 -0
- package/dist/lib/__tests__/vendor-sensors.test.d.ts +1 -0
- package/dist/lib/__tests__/vendor-sensors.test.js +49 -0
- package/dist/lib/__tests__/vendor-sensors.test.js.map +1 -0
- package/dist/lib/capability.d.ts +21 -0
- package/dist/lib/capability.js +110 -0
- package/dist/lib/capability.js.map +1 -0
- package/dist/lib/cpu-thermal-chips.d.ts +2 -0
- package/dist/lib/cpu-thermal-chips.js +28 -0
- package/dist/lib/cpu-thermal-chips.js.map +1 -0
- package/dist/lib/types.d.ts +58 -0
- package/dist/lib/vendor-sensors.d.ts +27 -0
- package/dist/lib/vendor-sensors.js +63 -0
- package/dist/lib/vendor-sensors.js.map +1 -0
- package/dist/notify/telegram.js +1 -1
- package/dist/notify/telegram.js.map +1 -1
- package/package.json +16 -1
- package/rule-ids.json +29 -0
- package/.dockerignore +0 -13
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- package/.github/ISSUE_TEMPLATE/no_data.md +0 -26
- package/.github/workflows/docker.yml +0 -53
- package/.github/workflows/publish.yml +0 -25
- package/Dockerfile +0 -59
- package/config/collector.example.yaml +0 -43
- package/docker-compose.yml +0 -26
- package/scripts/sign-release.sh +0 -29
- package/src/__tests__/cli.test.ts +0 -74
- package/src/__tests__/reboot-marker.test.ts +0 -122
- package/src/alerts/evaluator.ts +0 -15
- package/src/alerts/rules.ts +0 -283
- package/src/alerts/state.ts +0 -92
- package/src/cli.ts +0 -112
- package/src/collect/__tests__/ipmi.test.ts +0 -96
- package/src/collect/__tests__/smart.test.ts +0 -68
- package/src/collect/__tests__/system.test.ts +0 -29
- package/src/collect/__tests__/zfs.test.ts +0 -72
- package/src/collect/conntrack.ts +0 -27
- package/src/collect/cpu.ts +0 -92
- package/src/collect/disks.ts +0 -91
- package/src/collect/fd.ts +0 -31
- package/src/collect/io-errors.ts +0 -23
- package/src/collect/io-latency.ts +0 -103
- package/src/collect/ipmi.ts +0 -207
- package/src/collect/memory.ts +0 -30
- package/src/collect/network.ts +0 -193
- package/src/collect/ntp.ts +0 -114
- package/src/collect/os-alerts.ts +0 -43
- package/src/collect/raid.ts +0 -40
- package/src/collect/security.ts +0 -268
- package/src/collect/smart.ts +0 -72
- package/src/collect/system.ts +0 -32
- package/src/collect/systemd.ts +0 -33
- package/src/collect/zfs.ts +0 -66
- package/src/config.ts +0 -65
- package/src/index.ts +0 -221
- package/src/lib/__tests__/parse.test.ts +0 -28
- package/src/lib/exec.ts +0 -16
- package/src/lib/parse.ts +0 -29
- package/src/lib/reboot-marker.ts +0 -88
- package/src/lib/types.ts +0 -226
- package/src/lib/version-check.ts +0 -39
- package/src/lib/version.ts +0 -33
- package/src/metrics-server.ts +0 -123
- package/src/notify/email.ts +0 -69
- package/src/notify/slack.ts +0 -47
- package/src/notify/telegram.ts +0 -65
- package/src/push/forge.ts +0 -109
- package/tsconfig.json +0 -15
- 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":"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"}
|
|
@@ -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,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"}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -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"}
|
package/dist/notify/telegram.js
CHANGED
|
@@ -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
|
-
|
|
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":"AAEA,MAAM,YAAY,GAA2B;IAC3C,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI;IAC7G,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI;IAC/H,eAAe,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI;IAChG,iBAAiB,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI;IAC7G,
|
|
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.
|
|
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,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
|
-
```
|