@glassmkr/crucible 0.6.0 → 0.6.1

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.
@@ -2,12 +2,11 @@ import { run } from "../lib/exec.js";
2
2
  export async function collectNtp() {
3
3
  // Try timedatectl first (systemd-timesyncd)
4
4
  const tdctl = await run("timedatectl", ["show", "--property=NTPSynchronized", "--value"]);
5
- if (tdctl !== null) {
5
+ // Only trust timedatectl if it returns a clear "yes" or "no"
6
+ if (tdctl !== null && (tdctl.trim() === "yes" || tdctl.trim() === "no")) {
6
7
  const synced = tdctl.trim() === "yes";
7
- // Get the source daemon name
8
8
  const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
9
9
  const ntpEnabled = statusOut?.trim() === "yes";
10
- // Try to get offset from timedatectl timesync-status
11
10
  let offset = 0;
12
11
  try {
13
12
  const tsStatus = await run("timedatectl", ["timesync-status"]);
@@ -33,47 +32,55 @@ export async function collectNtp() {
33
32
  daemon_running: ntpEnabled || synced,
34
33
  };
35
34
  }
36
- // Try chrony
35
+ // Try chrony - validate output contains expected "Leap status" field
37
36
  const chronyOut = await run("chronyc", ["tracking"]);
38
37
  if (chronyOut) {
39
38
  const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
40
- const synced = leapMatch ? leapMatch[1].trim() === "Normal" : false;
41
- let offset = 0;
42
- const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
43
- if (offsetMatch)
44
- offset = parseFloat(offsetMatch[1]);
45
- return {
46
- synced,
47
- offset_seconds: Math.abs(offset),
48
- source: "chrony",
49
- daemon_running: true,
50
- };
39
+ if (leapMatch) {
40
+ // Output looks like real chrony tracking data
41
+ const synced = leapMatch[1].trim() === "Normal";
42
+ let offset = 0;
43
+ const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
44
+ if (offsetMatch)
45
+ offset = parseFloat(offsetMatch[1]);
46
+ return {
47
+ synced,
48
+ offset_seconds: Math.abs(offset),
49
+ source: "chrony",
50
+ daemon_running: true,
51
+ };
52
+ }
53
+ // chronyc returned output but not tracking data (error message, daemon not running)
54
+ // Fall through to ntpq
51
55
  }
52
- // Try ntpq
56
+ // Try ntpq - validate output contains the header line with "remote"
53
57
  const ntpqOut = await run("ntpq", ["-pn"]);
54
58
  if (ntpqOut) {
55
- // A line starting with * means a selected peer (synced)
56
- const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
57
- let offset = 0;
58
- // Parse offset from the selected peer line
59
- const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
60
- if (selectedLine) {
61
- const fields = selectedLine.trim().split(/\s+/);
62
- // offset is typically field 8 (in ms)
63
- if (fields.length >= 9) {
64
- const rawOffset = parseFloat(fields[8]);
65
- if (!isNaN(rawOffset))
66
- offset = Math.abs(rawOffset) / 1000;
59
+ const hasHeader = ntpqOut.split("\n").some((line) => line.includes("remote"));
60
+ if (hasHeader) {
61
+ // Output looks like real ntpq peer table
62
+ const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
63
+ let offset = 0;
64
+ const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
65
+ if (selectedLine) {
66
+ const fields = selectedLine.trim().split(/\s+/);
67
+ if (fields.length >= 9) {
68
+ const rawOffset = parseFloat(fields[8]);
69
+ if (!isNaN(rawOffset))
70
+ offset = Math.abs(rawOffset) / 1000;
71
+ }
67
72
  }
73
+ return {
74
+ synced,
75
+ offset_seconds: offset,
76
+ source: "ntpd",
77
+ daemon_running: true,
78
+ };
68
79
  }
69
- return {
70
- synced,
71
- offset_seconds: offset,
72
- source: "ntpd",
73
- daemon_running: true,
74
- };
80
+ // ntpq returned output but not peer table (error message, daemon not running)
81
+ // Fall through to "none"
75
82
  }
76
- // No time sync daemon found
83
+ // No time sync daemon found (or all probes returned invalid output)
77
84
  return {
78
85
  synced: false,
79
86
  offset_seconds: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"ntp.js","sourceRoot":"","sources":["../../src/collect/ntp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ntp.js","sourceRoot":"","sources":["../../src/collect/ntp.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassmkr/crucible",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Lightweight bare metal server monitoring. IPMI, SMART, OS, network. Opinionated alerts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,7 +12,14 @@
12
12
  "start": "node dist/index.js",
13
13
  "clean": "rm -rf dist"
14
14
  },
15
- "keywords": ["monitoring", "bare-metal", "ipmi", "smart", "server", "alerts"],
15
+ "keywords": [
16
+ "monitoring",
17
+ "bare-metal",
18
+ "ipmi",
19
+ "smart",
20
+ "server",
21
+ "alerts"
22
+ ],
16
23
  "license": "MIT",
17
24
  "repository": {
18
25
  "type": "git",
@@ -10,13 +10,12 @@ export interface NtpData {
10
10
  export async function collectNtp(): Promise<NtpData> {
11
11
  // Try timedatectl first (systemd-timesyncd)
12
12
  const tdctl = await run("timedatectl", ["show", "--property=NTPSynchronized", "--value"]);
13
- if (tdctl !== null) {
13
+ // Only trust timedatectl if it returns a clear "yes" or "no"
14
+ if (tdctl !== null && (tdctl.trim() === "yes" || tdctl.trim() === "no")) {
14
15
  const synced = tdctl.trim() === "yes";
15
- // Get the source daemon name
16
16
  const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
17
17
  const ntpEnabled = statusOut?.trim() === "yes";
18
18
 
19
- // Try to get offset from timedatectl timesync-status
20
19
  let offset = 0;
21
20
  try {
22
21
  const tsStatus = await run("timedatectl", ["timesync-status"]);
@@ -40,47 +39,55 @@ export async function collectNtp(): Promise<NtpData> {
40
39
  };
41
40
  }
42
41
 
43
- // Try chrony
42
+ // Try chrony - validate output contains expected "Leap status" field
44
43
  const chronyOut = await run("chronyc", ["tracking"]);
45
44
  if (chronyOut) {
46
45
  const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
47
- const synced = leapMatch ? leapMatch[1].trim() === "Normal" : false;
48
- let offset = 0;
49
- const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
50
- if (offsetMatch) offset = parseFloat(offsetMatch[1]);
51
- return {
52
- synced,
53
- offset_seconds: Math.abs(offset),
54
- source: "chrony",
55
- daemon_running: true,
56
- };
46
+ if (leapMatch) {
47
+ // Output looks like real chrony tracking data
48
+ const synced = leapMatch[1].trim() === "Normal";
49
+ let offset = 0;
50
+ const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
51
+ if (offsetMatch) offset = parseFloat(offsetMatch[1]);
52
+ return {
53
+ synced,
54
+ offset_seconds: Math.abs(offset),
55
+ source: "chrony",
56
+ daemon_running: true,
57
+ };
58
+ }
59
+ // chronyc returned output but not tracking data (error message, daemon not running)
60
+ // Fall through to ntpq
57
61
  }
58
62
 
59
- // Try ntpq
63
+ // Try ntpq - validate output contains the header line with "remote"
60
64
  const ntpqOut = await run("ntpq", ["-pn"]);
61
65
  if (ntpqOut) {
62
- // A line starting with * means a selected peer (synced)
63
- const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
64
- let offset = 0;
65
- // Parse offset from the selected peer line
66
- const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
67
- if (selectedLine) {
68
- const fields = selectedLine.trim().split(/\s+/);
69
- // offset is typically field 8 (in ms)
70
- if (fields.length >= 9) {
71
- const rawOffset = parseFloat(fields[8]);
72
- if (!isNaN(rawOffset)) offset = Math.abs(rawOffset) / 1000;
66
+ const hasHeader = ntpqOut.split("\n").some((line) => line.includes("remote"));
67
+ if (hasHeader) {
68
+ // Output looks like real ntpq peer table
69
+ const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
70
+ let offset = 0;
71
+ const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
72
+ if (selectedLine) {
73
+ const fields = selectedLine.trim().split(/\s+/);
74
+ if (fields.length >= 9) {
75
+ const rawOffset = parseFloat(fields[8]);
76
+ if (!isNaN(rawOffset)) offset = Math.abs(rawOffset) / 1000;
77
+ }
73
78
  }
79
+ return {
80
+ synced,
81
+ offset_seconds: offset,
82
+ source: "ntpd",
83
+ daemon_running: true,
84
+ };
74
85
  }
75
- return {
76
- synced,
77
- offset_seconds: offset,
78
- source: "ntpd",
79
- daemon_running: true,
80
- };
86
+ // ntpq returned output but not peer table (error message, daemon not running)
87
+ // Fall through to "none"
81
88
  }
82
89
 
83
- // No time sync daemon found
90
+ // No time sync daemon found (or all probes returned invalid output)
84
91
  return {
85
92
  synced: false,
86
93
  offset_seconds: 0,