@glassmkr/crucible 0.7.0 → 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 +52 -14
- 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/lib/version-check.js +1 -1
- package/dist/lib/version-check.js.map +1 -1
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +32 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/notify/email.js +2 -1
- package/dist/notify/email.js.map +1 -1
- package/dist/notify/slack.js +2 -1
- package/dist/notify/slack.js.map +1 -1
- 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 -233
- 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 -38
- package/src/metrics-server.ts +0 -123
- package/src/notify/email.ts +0 -68
- package/src/notify/slack.ts +0 -46
- 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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/lib/version-check.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/lib/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAEnE,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB,IAAI,UAAU,GAA2E,IAAI,CAAC;AAC9F,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,sBAAsB;AAEjE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAiB;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,GAAG,GAAG,aAAa,GAAG,cAAc;QAAE,OAAO;IACjD,aAAa,GAAG,GAAG,CAAC;IAEpB,MAAM,GAAG,GAAG,QAAQ,IAAI,4BAA4B,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,iBAAiB,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO;QACpB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAwF,CAAC;QACpH,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;QACrC,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,IAAI,MAAM,KAAK,eAAe,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,4CAA4C,MAAM,cAAc,eAAe,GAAG,CAAC,CAAC;YAChG,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,QAAQ,EAAE,aAAa,IAAI,+CAA+C,EAAE,CAAC,CAAC;YACtH,OAAO,CAAC,GAAG,CAAC,4FAA4F,CAAC,CAAC;YAC1G,UAAU,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,IAAI,EAAE,EAAE,CAAC;QAChG,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QACjE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CRUCIBLE_VERSION: string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Single source of truth for the Crucible version string at runtime.
|
|
2
|
+
// Read from package.json so a release bump propagates everywhere
|
|
3
|
+
// (notification footers, version-check log lines, --version flag, the
|
|
4
|
+
// `collector_version` field on every snapshot) without anyone having to
|
|
5
|
+
// remember to update a hardcoded constant.
|
|
6
|
+
//
|
|
7
|
+
// Returns "0.0.0" on read failure rather than throwing; the agent has to
|
|
8
|
+
// keep running even if its package.json is somehow missing.
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
function readPkgVersion() {
|
|
14
|
+
// src/lib/version.ts -> ../../package.json under src layout, but at
|
|
15
|
+
// runtime (compiled to dist/lib/version.js) it's still ../../package.json.
|
|
16
|
+
// Both paths resolve correctly because package.json sits one level above
|
|
17
|
+
// dist/ AND one level above src/.
|
|
18
|
+
for (const candidate of [
|
|
19
|
+
join(__dirname, "..", "..", "package.json"),
|
|
20
|
+
join(__dirname, "..", "package.json"),
|
|
21
|
+
]) {
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
24
|
+
if (pkg && typeof pkg.version === "string")
|
|
25
|
+
return pkg.version;
|
|
26
|
+
}
|
|
27
|
+
catch { /* try next */ }
|
|
28
|
+
}
|
|
29
|
+
return "0.0.0";
|
|
30
|
+
}
|
|
31
|
+
export const CRUCIBLE_VERSION = readPkgVersion();
|
|
32
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/lib/version.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,iEAAiE;AACjE,sEAAsE;AACtE,wEAAwE;AACxE,2CAA2C;AAC3C,EAAE;AACF,yEAAyE;AACzE,4DAA4D;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,SAAS,cAAc;IACrB,oEAAoE;IACpE,2EAA2E;IAC3E,yEAAyE;IACzE,kCAAkC;IAClC,KAAK,MAAM,SAAS,IAAI;QACtB,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;QAC3C,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC;KACtC,EAAE,CAAC;QACF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;YACxD,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;gBAAE,OAAO,GAAG,CAAC,OAAO,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,EAAE,CAAC"}
|
package/dist/notify/email.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
|
+
import { CRUCIBLE_VERSION } from "../lib/version.js";
|
|
3
4
|
const execFileAsync = promisify(execFile);
|
|
4
5
|
export async function sendEmail(config, newAlerts, resolvedAlerts, serverName) {
|
|
5
6
|
if (!config.to)
|
|
@@ -49,7 +50,7 @@ function buildBody(newAlerts, resolvedAlerts, serverName) {
|
|
|
49
50
|
lines.push("");
|
|
50
51
|
}
|
|
51
52
|
lines.push("---");
|
|
52
|
-
lines.push(
|
|
53
|
+
lines.push(`Glassmkr Crucible v${CRUCIBLE_VERSION}`);
|
|
53
54
|
return lines.join("\n");
|
|
54
55
|
}
|
|
55
56
|
//# sourceMappingURL=email.js.map
|
package/dist/notify/email.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/notify/email.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/notify/email.ts"],"names":[],"mappings":"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"}
|
package/dist/notify/slack.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CRUCIBLE_VERSION } from "../lib/version.js";
|
|
1
2
|
export async function sendSlack(webhookUrl, newAlerts, resolvedAlerts, serverName) {
|
|
2
3
|
const blocks = [];
|
|
3
4
|
if (newAlerts.length > 0) {
|
|
@@ -20,7 +21,7 @@ export async function sendSlack(webhookUrl, newAlerts, resolvedAlerts, serverNam
|
|
|
20
21
|
if (blocks.length === 0)
|
|
21
22
|
return true;
|
|
22
23
|
blocks.push({ type: "divider" });
|
|
23
|
-
blocks.push({ type: "context", elements: [{ type: "mrkdwn", text:
|
|
24
|
+
blocks.push({ type: "context", elements: [{ type: "mrkdwn", text: `Glassmkr Crucible v${CRUCIBLE_VERSION}` }] });
|
|
24
25
|
try {
|
|
25
26
|
const res = await fetch(webhookUrl, {
|
|
26
27
|
method: "POST",
|