@glassmkr/crucible 0.6.2 → 0.6.3
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/collect/ntp.d.ts +1 -0
- package/dist/collect/ntp.js +39 -25
- package/dist/collect/ntp.js.map +1 -1
- package/package.json +1 -1
- package/src/collect/ntp.ts +42 -25
package/dist/collect/ntp.d.ts
CHANGED
package/dist/collect/ntp.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import { run } from "../lib/exec.js";
|
|
2
|
+
// Check whether a systemd unit is active. Returns false for missing units, not-found, etc.
|
|
3
|
+
async function isUnitActive(unit) {
|
|
4
|
+
const out = await run("systemctl", ["is-active", unit], 3000);
|
|
5
|
+
return out?.trim() === "active";
|
|
6
|
+
}
|
|
7
|
+
// Detect which time-sync daemon unit is currently active on the host, if any.
|
|
8
|
+
// Returns "" when none are. We check the common names in order of preference.
|
|
9
|
+
async function detectActiveDaemon() {
|
|
10
|
+
const candidates = ["chrony", "chronyd", "systemd-timesyncd", "ntp", "ntpsec", "ntpd"];
|
|
11
|
+
for (const unit of candidates) {
|
|
12
|
+
if (await isUnitActive(unit))
|
|
13
|
+
return unit;
|
|
14
|
+
}
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
2
17
|
export async function collectNtp() {
|
|
3
|
-
//
|
|
18
|
+
// The authoritative "is the daemon running" check is systemctl is-active,
|
|
19
|
+
// not any derived flag from timedatectl. This catches daemon crashes and
|
|
20
|
+
// manual stops where the kernel clock is still synced.
|
|
21
|
+
const daemonName = await detectActiveDaemon();
|
|
22
|
+
const daemonRunning = daemonName !== "";
|
|
23
|
+
// Try timedatectl first (works for systemd-timesyncd and records the kernel
|
|
24
|
+
// NTPSynchronized flag regardless of which daemon set it).
|
|
4
25
|
const tdctl = await run("timedatectl", ["show", "--property=NTPSynchronized", "--value"]);
|
|
5
|
-
// Only trust timedatectl if it returns a clear "yes" or "no"
|
|
6
26
|
if (tdctl !== null && (tdctl.trim() === "yes" || tdctl.trim() === "no")) {
|
|
7
27
|
const synced = tdctl.trim() === "yes";
|
|
8
|
-
const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
|
|
9
|
-
const ntpEnabled = statusOut?.trim() === "yes";
|
|
10
28
|
let offset = 0;
|
|
11
29
|
try {
|
|
12
30
|
const tsStatus = await run("timedatectl", ["timesync-status"]);
|
|
@@ -25,19 +43,16 @@ export async function collectNtp() {
|
|
|
25
43
|
}
|
|
26
44
|
}
|
|
27
45
|
catch { /* offset stays 0 */ }
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
daemon_running: ntpEnabled || synced,
|
|
33
|
-
};
|
|
46
|
+
// Prefer an explicitly detected daemon name; fall back to systemd-timesyncd
|
|
47
|
+
// since timedatectl is most commonly the timesyncd frontend.
|
|
48
|
+
const source = daemonName || "systemd-timesyncd";
|
|
49
|
+
return { synced, offset_seconds: offset, source, daemon_running: daemonRunning, daemon_name: daemonName };
|
|
34
50
|
}
|
|
35
|
-
//
|
|
51
|
+
// Chrony tracking. Validate Leap status so we do not misread error text.
|
|
36
52
|
const chronyOut = await run("chronyc", ["tracking"]);
|
|
37
53
|
if (chronyOut) {
|
|
38
54
|
const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
|
|
39
55
|
if (leapMatch) {
|
|
40
|
-
// Output looks like real chrony tracking data
|
|
41
56
|
const synced = leapMatch[1].trim() === "Normal";
|
|
42
57
|
let offset = 0;
|
|
43
58
|
const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
|
|
@@ -46,19 +61,17 @@ export async function collectNtp() {
|
|
|
46
61
|
return {
|
|
47
62
|
synced,
|
|
48
63
|
offset_seconds: Math.abs(offset),
|
|
49
|
-
source: "chrony",
|
|
50
|
-
daemon_running:
|
|
64
|
+
source: daemonName || "chrony",
|
|
65
|
+
daemon_running: daemonRunning,
|
|
66
|
+
daemon_name: daemonName,
|
|
51
67
|
};
|
|
52
68
|
}
|
|
53
|
-
// chronyc returned output but not tracking data (error message, daemon not running)
|
|
54
|
-
// Fall through to ntpq
|
|
55
69
|
}
|
|
56
|
-
//
|
|
70
|
+
// ntpq peer table. Header check avoids false positives on error messages.
|
|
57
71
|
const ntpqOut = await run("ntpq", ["-pn"]);
|
|
58
72
|
if (ntpqOut) {
|
|
59
73
|
const hasHeader = ntpqOut.split("\n").some((line) => line.includes("remote"));
|
|
60
74
|
if (hasHeader) {
|
|
61
|
-
// Output looks like real ntpq peer table
|
|
62
75
|
const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
|
|
63
76
|
let offset = 0;
|
|
64
77
|
const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
|
|
@@ -73,19 +86,20 @@ export async function collectNtp() {
|
|
|
73
86
|
return {
|
|
74
87
|
synced,
|
|
75
88
|
offset_seconds: offset,
|
|
76
|
-
source: "ntpd",
|
|
77
|
-
daemon_running:
|
|
89
|
+
source: daemonName || "ntpd",
|
|
90
|
+
daemon_running: daemonRunning,
|
|
91
|
+
daemon_name: daemonName,
|
|
78
92
|
};
|
|
79
93
|
}
|
|
80
|
-
// ntpq returned output but not peer table (error message, daemon not running)
|
|
81
|
-
// Fall through to "none"
|
|
82
94
|
}
|
|
83
|
-
// No
|
|
95
|
+
// No usable probe output. If systemd still reports a daemon as active, trust that;
|
|
96
|
+
// otherwise report fully down.
|
|
84
97
|
return {
|
|
85
98
|
synced: false,
|
|
86
99
|
offset_seconds: 0,
|
|
87
|
-
source: "none",
|
|
88
|
-
daemon_running:
|
|
100
|
+
source: daemonName || "none",
|
|
101
|
+
daemon_running: daemonRunning,
|
|
102
|
+
daemon_name: daemonName,
|
|
89
103
|
};
|
|
90
104
|
}
|
|
91
105
|
//# sourceMappingURL=ntp.js.map
|
package/dist/collect/ntp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ntp.js","sourceRoot":"","sources":["../../src/collect/ntp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ntp.js","sourceRoot":"","sources":["../../src/collect/ntp.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/collect/ntp.ts
CHANGED
|
@@ -5,16 +5,37 @@ export interface NtpData {
|
|
|
5
5
|
offset_seconds: number;
|
|
6
6
|
source: string;
|
|
7
7
|
daemon_running: boolean;
|
|
8
|
+
daemon_name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Check whether a systemd unit is active. Returns false for missing units, not-found, etc.
|
|
12
|
+
async function isUnitActive(unit: string): Promise<boolean> {
|
|
13
|
+
const out = await run("systemctl", ["is-active", unit], 3000);
|
|
14
|
+
return out?.trim() === "active";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Detect which time-sync daemon unit is currently active on the host, if any.
|
|
18
|
+
// Returns "" when none are. We check the common names in order of preference.
|
|
19
|
+
async function detectActiveDaemon(): Promise<string> {
|
|
20
|
+
const candidates = ["chrony", "chronyd", "systemd-timesyncd", "ntp", "ntpsec", "ntpd"];
|
|
21
|
+
for (const unit of candidates) {
|
|
22
|
+
if (await isUnitActive(unit)) return unit;
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
8
25
|
}
|
|
9
26
|
|
|
10
27
|
export async function collectNtp(): Promise<NtpData> {
|
|
11
|
-
//
|
|
28
|
+
// The authoritative "is the daemon running" check is systemctl is-active,
|
|
29
|
+
// not any derived flag from timedatectl. This catches daemon crashes and
|
|
30
|
+
// manual stops where the kernel clock is still synced.
|
|
31
|
+
const daemonName = await detectActiveDaemon();
|
|
32
|
+
const daemonRunning = daemonName !== "";
|
|
33
|
+
|
|
34
|
+
// Try timedatectl first (works for systemd-timesyncd and records the kernel
|
|
35
|
+
// NTPSynchronized flag regardless of which daemon set it).
|
|
12
36
|
const tdctl = await run("timedatectl", ["show", "--property=NTPSynchronized", "--value"]);
|
|
13
|
-
// Only trust timedatectl if it returns a clear "yes" or "no"
|
|
14
37
|
if (tdctl !== null && (tdctl.trim() === "yes" || tdctl.trim() === "no")) {
|
|
15
38
|
const synced = tdctl.trim() === "yes";
|
|
16
|
-
const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
|
|
17
|
-
const ntpEnabled = statusOut?.trim() === "yes";
|
|
18
39
|
|
|
19
40
|
let offset = 0;
|
|
20
41
|
try {
|
|
@@ -31,20 +52,17 @@ export async function collectNtp(): Promise<NtpData> {
|
|
|
31
52
|
}
|
|
32
53
|
} catch { /* offset stays 0 */ }
|
|
33
54
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
daemon_running: ntpEnabled || synced,
|
|
39
|
-
};
|
|
55
|
+
// Prefer an explicitly detected daemon name; fall back to systemd-timesyncd
|
|
56
|
+
// since timedatectl is most commonly the timesyncd frontend.
|
|
57
|
+
const source = daemonName || "systemd-timesyncd";
|
|
58
|
+
return { synced, offset_seconds: offset, source, daemon_running: daemonRunning, daemon_name: daemonName };
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
//
|
|
61
|
+
// Chrony tracking. Validate Leap status so we do not misread error text.
|
|
43
62
|
const chronyOut = await run("chronyc", ["tracking"]);
|
|
44
63
|
if (chronyOut) {
|
|
45
64
|
const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
|
|
46
65
|
if (leapMatch) {
|
|
47
|
-
// Output looks like real chrony tracking data
|
|
48
66
|
const synced = leapMatch[1].trim() === "Normal";
|
|
49
67
|
let offset = 0;
|
|
50
68
|
const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
|
|
@@ -52,20 +70,18 @@ export async function collectNtp(): Promise<NtpData> {
|
|
|
52
70
|
return {
|
|
53
71
|
synced,
|
|
54
72
|
offset_seconds: Math.abs(offset),
|
|
55
|
-
source: "chrony",
|
|
56
|
-
daemon_running:
|
|
73
|
+
source: daemonName || "chrony",
|
|
74
|
+
daemon_running: daemonRunning,
|
|
75
|
+
daemon_name: daemonName,
|
|
57
76
|
};
|
|
58
77
|
}
|
|
59
|
-
// chronyc returned output but not tracking data (error message, daemon not running)
|
|
60
|
-
// Fall through to ntpq
|
|
61
78
|
}
|
|
62
79
|
|
|
63
|
-
//
|
|
80
|
+
// ntpq peer table. Header check avoids false positives on error messages.
|
|
64
81
|
const ntpqOut = await run("ntpq", ["-pn"]);
|
|
65
82
|
if (ntpqOut) {
|
|
66
83
|
const hasHeader = ntpqOut.split("\n").some((line) => line.includes("remote"));
|
|
67
84
|
if (hasHeader) {
|
|
68
|
-
// Output looks like real ntpq peer table
|
|
69
85
|
const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
|
|
70
86
|
let offset = 0;
|
|
71
87
|
const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
|
|
@@ -79,19 +95,20 @@ export async function collectNtp(): Promise<NtpData> {
|
|
|
79
95
|
return {
|
|
80
96
|
synced,
|
|
81
97
|
offset_seconds: offset,
|
|
82
|
-
source: "ntpd",
|
|
83
|
-
daemon_running:
|
|
98
|
+
source: daemonName || "ntpd",
|
|
99
|
+
daemon_running: daemonRunning,
|
|
100
|
+
daemon_name: daemonName,
|
|
84
101
|
};
|
|
85
102
|
}
|
|
86
|
-
// ntpq returned output but not peer table (error message, daemon not running)
|
|
87
|
-
// Fall through to "none"
|
|
88
103
|
}
|
|
89
104
|
|
|
90
|
-
// No
|
|
105
|
+
// No usable probe output. If systemd still reports a daemon as active, trust that;
|
|
106
|
+
// otherwise report fully down.
|
|
91
107
|
return {
|
|
92
108
|
synced: false,
|
|
93
109
|
offset_seconds: 0,
|
|
94
|
-
source: "none",
|
|
95
|
-
daemon_running:
|
|
110
|
+
source: daemonName || "none",
|
|
111
|
+
daemon_running: daemonRunning,
|
|
112
|
+
daemon_name: daemonName,
|
|
96
113
|
};
|
|
97
114
|
}
|