rigscope 0.1.8 → 0.2.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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +3 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/administration/page_client-reference-manifest.js +1 -1
- package/.next/server/app/administration.html +1 -1
- package/.next/server/app/administration.rsc +3 -3
- package/.next/server/app/administration.segments/_full.segment.rsc +3 -3
- package/.next/server/app/administration.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/alerts/page_client-reference-manifest.js +1 -1
- package/.next/server/app/alerts.html +1 -1
- package/.next/server/app/alerts.rsc +3 -3
- package/.next/server/app/alerts.segments/_full.segment.rsc +3 -3
- package/.next/server/app/alerts.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/connections/page.js +1 -1
- package/.next/server/app/connections/page_client-reference-manifest.js +1 -1
- package/.next/server/app/connections.html +1 -1
- package/.next/server/app/connections.rsc +4 -4
- package/.next/server/app/connections.segments/_full.segment.rsc +4 -4
- package/.next/server/app/connections.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/history/page.js +2 -2
- package/.next/server/app/history/page_client-reference-manifest.js +1 -1
- package/.next/server/app/history.html +1 -1
- package/.next/server/app/history.rsc +6 -6
- package/.next/server/app/history.segments/_full.segment.rsc +6 -6
- package/.next/server/app/history.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes.html +1 -1
- package/.next/server/app/processes.rsc +3 -3
- package/.next/server/app/processes.segments/_full.segment.rsc +3 -3
- package/.next/server/app/processes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +1 -1
- package/.next/server/chunks/161.js +1 -1
- package/.next/server/chunks/168.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/408-351fe4a1e9a2d556.js +1 -0
- package/.next/static/chunks/939-92930fde48061e20.js +1 -0
- package/.next/static/chunks/app/connections/page-7d18b901f2338f20.js +1 -0
- package/.next/static/chunks/app/history/page-adc25194b30c689d.js +3 -0
- package/.next/static/css/{f5d3162842dcac31.css → 2abef6d49c1c2970.css} +1 -1
- package/ARCHITECTURE.md +43 -15
- package/CHANGELOG.md +62 -0
- package/README.md +10 -3
- package/dist/cli.cjs +868 -511
- package/dist/collector.cjs +603 -120
- package/dist/hub.cjs +3429 -1451
- package/dist/ops.cjs +1178 -888
- package/dist/system.cjs +1322 -363
- package/docs/configuration.md +34 -1
- package/docs/installation.md +12 -6
- package/docs/monitoring.md +60 -17
- package/docs/operations.md +78 -22
- package/docs/system-installation.md +11 -4
- package/docs/telemetry-storage.md +258 -0
- package/package.json +3 -1
- package/packaging/collector.env.example +6 -0
- package/packaging/system/rigscope-collector.service.example +3 -0
- package/.next/static/chunks/408-9fdba9e4e956ef08.js +0 -1
- package/.next/static/chunks/939-514e554087419c71.js +0 -1
- package/.next/static/chunks/app/connections/page-75abd16fc495fcbd.js +0 -1
- package/.next/static/chunks/app/history/page-a25ba8fab7bd7cb9.js +0 -3
- /package/.next/static/{ip43TIgmaByEBn2GFtvfO → JbaLjudQSxrMUD_VHPrnD}/_buildManifest.js +0 -0
- /package/.next/static/{ip43TIgmaByEBn2GFtvfO → JbaLjudQSxrMUD_VHPrnD}/_ssgManifest.js +0 -0
package/dist/system.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/cli/system.ts
|
|
@@ -86,23 +96,23 @@ __export(system_exports, {
|
|
|
86
96
|
verifySystemHealth: () => verifySystemHealth
|
|
87
97
|
});
|
|
88
98
|
module.exports = __toCommonJS(system_exports);
|
|
89
|
-
var
|
|
99
|
+
var import_node_fs6 = require("node:fs");
|
|
90
100
|
var import_node_child_process = require("node:child_process");
|
|
91
|
-
var
|
|
92
|
-
var
|
|
93
|
-
var
|
|
101
|
+
var import_node_crypto7 = require("node:crypto");
|
|
102
|
+
var import_node_path6 = require("node:path");
|
|
103
|
+
var import_node_sqlite3 = require("node:sqlite");
|
|
94
104
|
var import_node_util = require("node:util");
|
|
95
105
|
|
|
96
106
|
// src/cli/health.ts
|
|
97
107
|
var import_node_http = require("node:http");
|
|
98
108
|
var MAX_HEALTH_BODY_BYTES = 64 * 1024;
|
|
99
109
|
function probeHealth(target, timeoutMs = 3e3) {
|
|
100
|
-
return new Promise((
|
|
110
|
+
return new Promise((resolve5) => {
|
|
101
111
|
let settled = false;
|
|
102
112
|
const finish = (result) => {
|
|
103
113
|
if (settled) return;
|
|
104
114
|
settled = true;
|
|
105
|
-
|
|
115
|
+
resolve5(result);
|
|
106
116
|
};
|
|
107
117
|
const options = target.kind === "unix" ? { socketPath: target.socketPath, path: target.path, method: "GET", headers: { Host: target.hostHeader } } : { host: target.host, port: target.port, path: target.path, method: "GET" };
|
|
108
118
|
let req;
|
|
@@ -185,7 +195,7 @@ function bounded(value, min, max) {
|
|
|
185
195
|
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
|
|
186
196
|
}
|
|
187
197
|
function delay(ms) {
|
|
188
|
-
return new Promise((
|
|
198
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
189
199
|
}
|
|
190
200
|
function errorText(error) {
|
|
191
201
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -377,6 +387,9 @@ var SHARING_KEYS = [
|
|
|
377
387
|
"processArguments"
|
|
378
388
|
];
|
|
379
389
|
|
|
390
|
+
// collector/destinations/queue.ts
|
|
391
|
+
var DEFAULT_COLLECTOR_QUEUE_MODE = "memory";
|
|
392
|
+
|
|
380
393
|
// collector/config.ts
|
|
381
394
|
var ConfigError = class extends Error {
|
|
382
395
|
issues;
|
|
@@ -582,6 +595,18 @@ function parseDestinations(env, issues, destinations) {
|
|
|
582
595
|
}
|
|
583
596
|
}
|
|
584
597
|
}
|
|
598
|
+
var QUEUE_MODES = ["memory", "durable"];
|
|
599
|
+
function parseQueueMode(env, issues) {
|
|
600
|
+
const raw = env["COLLECTOR_QUEUE_MODE"]?.trim();
|
|
601
|
+
if (raw === void 0 || raw === "") {
|
|
602
|
+
return DEFAULT_COLLECTOR_QUEUE_MODE;
|
|
603
|
+
}
|
|
604
|
+
if (!QUEUE_MODES.includes(raw)) {
|
|
605
|
+
issues.push(`COLLECTOR_QUEUE_MODE must be one of ${QUEUE_MODES.join(" or ")}`);
|
|
606
|
+
return DEFAULT_COLLECTOR_QUEUE_MODE;
|
|
607
|
+
}
|
|
608
|
+
return raw;
|
|
609
|
+
}
|
|
585
610
|
function parseIntervalMs(env, issues) {
|
|
586
611
|
const raw = env["COLLECTOR_INTERVAL_MS"]?.trim();
|
|
587
612
|
if (raw === void 0 || raw === "") {
|
|
@@ -612,6 +637,7 @@ function parseCollectorConfig(env) {
|
|
|
612
637
|
}
|
|
613
638
|
const destinations = [];
|
|
614
639
|
const intervalMs = parseIntervalMs(env, issues);
|
|
640
|
+
const queueMode = parseQueueMode(env, issues);
|
|
615
641
|
parseDestinations(env, issues, destinations);
|
|
616
642
|
if (issues.length > 0) {
|
|
617
643
|
throw new ConfigError(issues);
|
|
@@ -621,7 +647,8 @@ function parseCollectorConfig(env) {
|
|
|
621
647
|
hostname,
|
|
622
648
|
hostnameIsMockDefault: rawHostname === "",
|
|
623
649
|
intervalMs,
|
|
624
|
-
destinations
|
|
650
|
+
destinations,
|
|
651
|
+
queueMode
|
|
625
652
|
};
|
|
626
653
|
}
|
|
627
654
|
|
|
@@ -789,6 +816,13 @@ async function enrollCollector(path, connectUrl, enrollmentToken, sharing, repla
|
|
|
789
816
|
// src/hub/config.ts
|
|
790
817
|
var import_node_net = require("node:net");
|
|
791
818
|
var import_node_path3 = require("node:path");
|
|
819
|
+
|
|
820
|
+
// src/contracts/telemetry.ts
|
|
821
|
+
var MAX_PERSISTENT_FLUSH_MS = 5e3;
|
|
822
|
+
var MIN_EPOCH = Date.parse("2000-01-01T00:00:00Z");
|
|
823
|
+
var MAX_EPOCH = Date.parse("2200-01-01T00:00:00Z");
|
|
824
|
+
|
|
825
|
+
// src/hub/config.ts
|
|
792
826
|
var MAX_HUB_COLLECTORS_BYTES = 1024 * 1024;
|
|
793
827
|
var MAX_COLLECTOR_ID_LENGTH = 128;
|
|
794
828
|
var MAX_MACHINE_ID_LENGTH = 128;
|
|
@@ -816,6 +850,7 @@ function parseHubConfig(env) {
|
|
|
816
850
|
maxCollectorConnections = parsed;
|
|
817
851
|
}
|
|
818
852
|
const connectionBudget = maxCollectorConnections === void 0 ? {} : { maxCollectorConnections };
|
|
853
|
+
const history = parseHistoryConfig(env);
|
|
819
854
|
if (publicOrigin === void 0 !== (socketPath === void 0)) {
|
|
820
855
|
throw new HubConfigError(
|
|
821
856
|
"HUB_PUBLIC_ORIGIN and HUB_SOCKET_PATH must be set together: remote mode needs both the exact HTTPS origin and the Unix socket path"
|
|
@@ -835,6 +870,7 @@ function parseHubConfig(env) {
|
|
|
835
870
|
collectors,
|
|
836
871
|
development,
|
|
837
872
|
remote: parseRemoteConfig(publicOrigin, socketPath),
|
|
873
|
+
history,
|
|
838
874
|
...connectionBudget
|
|
839
875
|
};
|
|
840
876
|
}
|
|
@@ -844,9 +880,65 @@ function parseHubConfig(env) {
|
|
|
844
880
|
collectors,
|
|
845
881
|
development,
|
|
846
882
|
remote: null,
|
|
883
|
+
history,
|
|
847
884
|
...connectionBudget
|
|
848
885
|
};
|
|
849
886
|
}
|
|
887
|
+
var DEFAULT_MEMORY_BUDGET_BYTES = 128 * 1024 * 1024;
|
|
888
|
+
var DEFAULT_PERSISTENT_FLUSH_MS = MAX_PERSISTENT_FLUSH_MS;
|
|
889
|
+
var DEFAULT_RECORDING_MAX_DURATION_MS = 60 * 6e4;
|
|
890
|
+
var DEFAULT_RECORDING_MAX_BYTES = 64 * 1024 * 1024;
|
|
891
|
+
var DEFAULT_RECORDING_BACKLOG_MS = 10 * 6e4;
|
|
892
|
+
var DEFAULT_MAX_ACTIVE_RECORDINGS = 4;
|
|
893
|
+
var HOUR_MS = 36e5;
|
|
894
|
+
var DAY_MS = 864e5;
|
|
895
|
+
function parseHistoryConfig(env) {
|
|
896
|
+
const modeValue = env.HUB_TELEMETRY_MODE ?? "memory";
|
|
897
|
+
if (modeValue !== "memory" && modeValue !== "persistent") {
|
|
898
|
+
throw new HubConfigError("HUB_TELEMETRY_MODE must be memory or persistent");
|
|
899
|
+
}
|
|
900
|
+
const memoryBudgetBytes = envInteger(
|
|
901
|
+
env.HUB_HISTORY_MEMORY_BYTES,
|
|
902
|
+
DEFAULT_MEMORY_BUDGET_BYTES,
|
|
903
|
+
1 * 1024 * 1024,
|
|
904
|
+
16 * 1024 * 1024 * 1024,
|
|
905
|
+
"HUB_HISTORY_MEMORY_BYTES"
|
|
906
|
+
);
|
|
907
|
+
const flushIntervalMs = envInteger(
|
|
908
|
+
env.HUB_TELEMETRY_FLUSH_MS,
|
|
909
|
+
DEFAULT_PERSISTENT_FLUSH_MS,
|
|
910
|
+
1e3,
|
|
911
|
+
MAX_PERSISTENT_FLUSH_MS,
|
|
912
|
+
"HUB_TELEMETRY_FLUSH_MS"
|
|
913
|
+
);
|
|
914
|
+
const retention = {
|
|
915
|
+
rawMs: envInteger(env.HUB_RETENTION_RAW_MS, 2 * DAY_MS, 0, 400 * DAY_MS, "HUB_RETENTION_RAW_MS"),
|
|
916
|
+
tenSecondMs: envInteger(env.HUB_RETENTION_10S_MS, 2 * HOUR_MS, 0, 30 * DAY_MS, "HUB_RETENTION_10S_MS"),
|
|
917
|
+
oneMinuteMs: envInteger(env.HUB_RETENTION_1M_MS, 30 * DAY_MS, 0, 400 * DAY_MS, "HUB_RETENTION_1M_MS"),
|
|
918
|
+
oneHourMs: envInteger(env.HUB_RETENTION_1H_MS, 400 * DAY_MS, 0, 800 * DAY_MS, "HUB_RETENTION_1H_MS"),
|
|
919
|
+
receiptsMs: envInteger(env.HUB_RECEIPT_RETENTION_MS, 7 * DAY_MS, HOUR_MS, 400 * DAY_MS, "HUB_RECEIPT_RETENTION_MS"),
|
|
920
|
+
gapsMs: envInteger(env.HUB_GAP_RETENTION_MS, 400 * DAY_MS, 0, 800 * DAY_MS, "HUB_GAP_RETENTION_MS")
|
|
921
|
+
};
|
|
922
|
+
const recordings = {
|
|
923
|
+
directory: nonEmpty(env.HUB_RECORDINGS_DIR) ?? null,
|
|
924
|
+
maxDurationMs: envInteger(env.HUB_RECORDING_MAX_DURATION_MS, DEFAULT_RECORDING_MAX_DURATION_MS, 1e3, 24 * 60 * 6e4, "HUB_RECORDING_MAX_DURATION_MS"),
|
|
925
|
+
maxBytes: envInteger(env.HUB_RECORDING_MAX_BYTES, DEFAULT_RECORDING_MAX_BYTES, 64 * 1024, 1024 * 1024 * 1024, "HUB_RECORDING_MAX_BYTES"),
|
|
926
|
+
backlogMs: envInteger(env.HUB_RECORDING_BACKLOG_MS, DEFAULT_RECORDING_BACKLOG_MS, 0, 24 * 60 * 6e4, "HUB_RECORDING_BACKLOG_MS"),
|
|
927
|
+
maxActive: envInteger(env.HUB_RECORDING_MAX_ACTIVE, DEFAULT_MAX_ACTIVE_RECORDINGS, 1, 32, "HUB_RECORDING_MAX_ACTIVE")
|
|
928
|
+
};
|
|
929
|
+
return { mode: modeValue, memoryBudgetBytes, flushIntervalMs, retention, recordings };
|
|
930
|
+
}
|
|
931
|
+
function envInteger(raw, fallback, min, max, name) {
|
|
932
|
+
if (raw === void 0 || raw === "") return fallback;
|
|
933
|
+
if (!/^\d+$/.test(raw)) {
|
|
934
|
+
throw new HubConfigError(`${name} must be a nonnegative integer`);
|
|
935
|
+
}
|
|
936
|
+
const value = Number(raw);
|
|
937
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
938
|
+
throw new HubConfigError(`${name} must be between ${min} and ${max}`);
|
|
939
|
+
}
|
|
940
|
+
return value;
|
|
941
|
+
}
|
|
850
942
|
function nonEmpty(value) {
|
|
851
943
|
return value === void 0 || value === "" ? void 0 : value;
|
|
852
944
|
}
|
|
@@ -1020,6 +1112,851 @@ function boundedString(value, field, message, maxLength) {
|
|
|
1020
1112
|
return value;
|
|
1021
1113
|
}
|
|
1022
1114
|
|
|
1115
|
+
// src/cli/machine-transition.ts
|
|
1116
|
+
var import_node_fs5 = require("node:fs");
|
|
1117
|
+
var import_node_path5 = require("node:path");
|
|
1118
|
+
var import_node_crypto6 = require("node:crypto");
|
|
1119
|
+
|
|
1120
|
+
// src/operations/database.ts
|
|
1121
|
+
var import_node_fs4 = require("node:fs");
|
|
1122
|
+
var import_node_sqlite2 = require("node:sqlite");
|
|
1123
|
+
var sqlite2 = __toESM(require("node:sqlite"));
|
|
1124
|
+
|
|
1125
|
+
// src/hub/storage/db.ts
|
|
1126
|
+
var import_node_sqlite = require("node:sqlite");
|
|
1127
|
+
var sqlite = __toESM(require("node:sqlite"));
|
|
1128
|
+
|
|
1129
|
+
// src/hub/auth/sessions.ts
|
|
1130
|
+
var DEFAULT_SESSION_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
1131
|
+
|
|
1132
|
+
// src/hub/admin/store.ts
|
|
1133
|
+
var MAX_SCOPE_BYTES = 16 * 1024;
|
|
1134
|
+
function adminSchema(db) {
|
|
1135
|
+
db.exec(`
|
|
1136
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
1137
|
+
username TEXT PRIMARY KEY,
|
|
1138
|
+
password_hash TEXT NOT NULL,
|
|
1139
|
+
scope_json TEXT NOT NULL,
|
|
1140
|
+
is_admin INTEGER NOT NULL DEFAULT 0,
|
|
1141
|
+
disabled INTEGER NOT NULL DEFAULT 0
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
CREATE TABLE IF NOT EXISTS admin_audit (
|
|
1145
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1146
|
+
actor TEXT NOT NULL,
|
|
1147
|
+
action TEXT NOT NULL,
|
|
1148
|
+
target TEXT NOT NULL,
|
|
1149
|
+
at_ms INTEGER NOT NULL
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
CREATE TABLE IF NOT EXISTS admin_meta (
|
|
1153
|
+
key TEXT PRIMARY KEY,
|
|
1154
|
+
value TEXT NOT NULL
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
CREATE INDEX IF NOT EXISTS admin_audit_at ON admin_audit (at_ms DESC);
|
|
1158
|
+
`);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/hub/alerts/schema.ts
|
|
1162
|
+
function installAlertSchema(db) {
|
|
1163
|
+
db.exec(`
|
|
1164
|
+
-- Rules are durable. Per-rule pending/incident state lives here so a
|
|
1165
|
+
-- restart can continue a duration or avoid opening a duplicate incident.
|
|
1166
|
+
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
1167
|
+
id TEXT PRIMARY KEY,
|
|
1168
|
+
name TEXT NOT NULL,
|
|
1169
|
+
machine_id TEXT NOT NULL,
|
|
1170
|
+
metric TEXT NOT NULL,
|
|
1171
|
+
resource_id TEXT NOT NULL DEFAULT '',
|
|
1172
|
+
condition TEXT NOT NULL,
|
|
1173
|
+
threshold REAL,
|
|
1174
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
1175
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1176
|
+
maintenance_until_ms INTEGER,
|
|
1177
|
+
state TEXT NOT NULL DEFAULT 'ok',
|
|
1178
|
+
pending_since_ms INTEGER,
|
|
1179
|
+
active_incident_id TEXT,
|
|
1180
|
+
last_value REAL,
|
|
1181
|
+
last_observed_at_ms INTEGER,
|
|
1182
|
+
created_at_ms INTEGER NOT NULL,
|
|
1183
|
+
updated_at_ms INTEGER NOT NULL
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
CREATE INDEX IF NOT EXISTS alert_rules_machine
|
|
1187
|
+
ON alert_rules (machine_id);
|
|
1188
|
+
|
|
1189
|
+
-- Incident history is bounded by the engine, but never loses an open row.
|
|
1190
|
+
CREATE TABLE IF NOT EXISTS alert_incidents (
|
|
1191
|
+
id TEXT PRIMARY KEY,
|
|
1192
|
+
rule_id TEXT NOT NULL,
|
|
1193
|
+
rule_name TEXT NOT NULL,
|
|
1194
|
+
machine_id TEXT NOT NULL,
|
|
1195
|
+
metric TEXT NOT NULL,
|
|
1196
|
+
resource_id TEXT NOT NULL DEFAULT '',
|
|
1197
|
+
condition TEXT NOT NULL,
|
|
1198
|
+
status TEXT NOT NULL,
|
|
1199
|
+
started_at_ms INTEGER NOT NULL,
|
|
1200
|
+
resolved_at_ms INTEGER,
|
|
1201
|
+
started_value REAL,
|
|
1202
|
+
resolved_value REAL,
|
|
1203
|
+
last_observed_at_ms INTEGER NOT NULL,
|
|
1204
|
+
message TEXT NOT NULL
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
CREATE INDEX IF NOT EXISTS alert_incidents_started
|
|
1208
|
+
ON alert_incidents (started_at_ms DESC);
|
|
1209
|
+
CREATE INDEX IF NOT EXISTS alert_incidents_machine_started
|
|
1210
|
+
ON alert_incidents (machine_id, started_at_ms DESC);
|
|
1211
|
+
CREATE INDEX IF NOT EXISTS alert_incidents_rule_status
|
|
1212
|
+
ON alert_incidents (rule_id, status);
|
|
1213
|
+
CREATE UNIQUE INDEX IF NOT EXISTS alert_incidents_one_open
|
|
1214
|
+
ON alert_incidents (rule_id) WHERE status = 'open';
|
|
1215
|
+
|
|
1216
|
+
-- One durable job per logical incident event. The primary key is the
|
|
1217
|
+
-- deterministic idempotency key sent to the webhook receiver.
|
|
1218
|
+
CREATE TABLE IF NOT EXISTS alert_jobs (
|
|
1219
|
+
id TEXT PRIMARY KEY,
|
|
1220
|
+
rule_id TEXT NOT NULL,
|
|
1221
|
+
incident_id TEXT NOT NULL,
|
|
1222
|
+
machine_id TEXT NOT NULL,
|
|
1223
|
+
event TEXT NOT NULL,
|
|
1224
|
+
status TEXT NOT NULL,
|
|
1225
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1226
|
+
max_attempts INTEGER NOT NULL DEFAULT 6,
|
|
1227
|
+
next_attempt_ms INTEGER,
|
|
1228
|
+
last_attempt_ms INTEGER,
|
|
1229
|
+
last_error TEXT,
|
|
1230
|
+
created_at_ms INTEGER NOT NULL,
|
|
1231
|
+
delivered_at_ms INTEGER,
|
|
1232
|
+
payload_json TEXT NOT NULL,
|
|
1233
|
+
UNIQUE (incident_id, event)
|
|
1234
|
+
);
|
|
1235
|
+
|
|
1236
|
+
CREATE INDEX IF NOT EXISTS alert_jobs_due
|
|
1237
|
+
ON alert_jobs (status, next_attempt_ms);
|
|
1238
|
+
CREATE INDEX IF NOT EXISTS alert_jobs_created
|
|
1239
|
+
ON alert_jobs (created_at_ms DESC);
|
|
1240
|
+
|
|
1241
|
+
-- Exactly one webhook channel. URL, headers and optional HMAC secret are
|
|
1242
|
+
-- private database state and are never returned by the browser API.
|
|
1243
|
+
CREATE TABLE IF NOT EXISTS alert_webhook (
|
|
1244
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
1245
|
+
url TEXT NOT NULL,
|
|
1246
|
+
headers_json TEXT NOT NULL DEFAULT '{}',
|
|
1247
|
+
secret TEXT,
|
|
1248
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1249
|
+
updated_at_ms INTEGER NOT NULL,
|
|
1250
|
+
last_test_at_ms INTEGER,
|
|
1251
|
+
last_test_ok INTEGER,
|
|
1252
|
+
last_error TEXT
|
|
1253
|
+
);
|
|
1254
|
+
|
|
1255
|
+
-- Last LIVE record accepted by the engine. Used to ignore duplicate or
|
|
1256
|
+
-- older records after restart; replay never calls observe.
|
|
1257
|
+
CREATE TABLE IF NOT EXISTS alert_machine_progress (
|
|
1258
|
+
machine_id TEXT PRIMARY KEY,
|
|
1259
|
+
collector_id TEXT NOT NULL,
|
|
1260
|
+
run_id TEXT NOT NULL,
|
|
1261
|
+
seq INTEGER NOT NULL,
|
|
1262
|
+
received_at_ms INTEGER NOT NULL,
|
|
1263
|
+
updated_at_ms INTEGER NOT NULL
|
|
1264
|
+
);
|
|
1265
|
+
|
|
1266
|
+
-- Resources this rule has actually observed. A resource missing from an
|
|
1267
|
+
-- otherwise-ok family after being seen is unavailable, not a silent
|
|
1268
|
+
-- recovery to "normal".
|
|
1269
|
+
CREATE TABLE IF NOT EXISTS alert_observed_resources (
|
|
1270
|
+
rule_id TEXT NOT NULL,
|
|
1271
|
+
metric TEXT NOT NULL,
|
|
1272
|
+
resource_id TEXT NOT NULL,
|
|
1273
|
+
first_seen_ms INTEGER NOT NULL,
|
|
1274
|
+
last_seen_ms INTEGER NOT NULL,
|
|
1275
|
+
PRIMARY KEY (rule_id, metric, resource_id)
|
|
1276
|
+
);
|
|
1277
|
+
`);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/hub/enrollment/registry.ts
|
|
1281
|
+
function installRegistrySchema(db) {
|
|
1282
|
+
db.exec(`
|
|
1283
|
+
CREATE TABLE IF NOT EXISTS collector_registry (
|
|
1284
|
+
collector_id TEXT PRIMARY KEY, machine_id TEXT NOT NULL UNIQUE, token TEXT NOT NULL UNIQUE,
|
|
1285
|
+
revoked INTEGER NOT NULL DEFAULT 0, created_at_ms INTEGER NOT NULL, updated_at_ms INTEGER NOT NULL
|
|
1286
|
+
);
|
|
1287
|
+
CREATE TABLE IF NOT EXISTS collector_registry_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
1288
|
+
CREATE TABLE IF NOT EXISTS collector_grants (
|
|
1289
|
+
token_hash TEXT PRIMARY KEY, expires_at_ms INTEGER NOT NULL, replace_machine_id TEXT,
|
|
1290
|
+
consumed_by TEXT, request_id TEXT, recovery_hash TEXT, recovery_until_ms INTEGER, actor TEXT NOT NULL
|
|
1291
|
+
);
|
|
1292
|
+
CREATE TABLE IF NOT EXISTS collector_audit (
|
|
1293
|
+
id INTEGER PRIMARY KEY, at_ms INTEGER NOT NULL, actor TEXT NOT NULL, action TEXT NOT NULL, target TEXT NOT NULL
|
|
1294
|
+
);
|
|
1295
|
+
`);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/hub/storage/migrations.ts
|
|
1299
|
+
var MIGRATIONS = [
|
|
1300
|
+
{
|
|
1301
|
+
version: 1,
|
|
1302
|
+
name: "telemetry",
|
|
1303
|
+
up(db) {
|
|
1304
|
+
db.exec(`
|
|
1305
|
+
-- Hub-local machine presentation metadata; never required for ingest.
|
|
1306
|
+
CREATE TABLE machine_meta (
|
|
1307
|
+
machine_id TEXT PRIMARY KEY,
|
|
1308
|
+
display_name TEXT,
|
|
1309
|
+
tags_json TEXT NOT NULL DEFAULT '[]',
|
|
1310
|
+
updated_at_ms INTEGER NOT NULL
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
-- Integer series catalog. resource_id is '' for single-resource
|
|
1314
|
+
-- families and e.g. "core:3", a GPU/filesystem/disk/interface id for
|
|
1315
|
+
-- per-resource families.
|
|
1316
|
+
CREATE TABLE series (
|
|
1317
|
+
series_id INTEGER PRIMARY KEY,
|
|
1318
|
+
machine_id TEXT NOT NULL,
|
|
1319
|
+
metric TEXT NOT NULL,
|
|
1320
|
+
resource_id TEXT NOT NULL DEFAULT '',
|
|
1321
|
+
UNIQUE (machine_id, metric, resource_id)
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
-- Raw samples. WITHOUT ROWID makes (series_id, ts_ms) the clustered
|
|
1325
|
+
-- primary key, so a history range read is one index range scan.
|
|
1326
|
+
-- A NULL value is an explicit unavailable reading, never zero.
|
|
1327
|
+
CREATE TABLE points_raw (
|
|
1328
|
+
series_id INTEGER NOT NULL,
|
|
1329
|
+
ts_ms INTEGER NOT NULL,
|
|
1330
|
+
value REAL,
|
|
1331
|
+
PRIMARY KEY (series_id, ts_ms)
|
|
1332
|
+
) WITHOUT ROWID;
|
|
1333
|
+
|
|
1334
|
+
-- One-minute rollups, maintained on insert (including late/replayed
|
|
1335
|
+
-- data): min/max preserved, count of non-NULL values, null_count of
|
|
1336
|
+
-- explicit unavailable readings. sum is 0 for a null-only bucket and
|
|
1337
|
+
-- is only interpreted together with count.
|
|
1338
|
+
CREATE TABLE points_1m (
|
|
1339
|
+
series_id INTEGER NOT NULL,
|
|
1340
|
+
bucket_ms INTEGER NOT NULL,
|
|
1341
|
+
min REAL,
|
|
1342
|
+
max REAL,
|
|
1343
|
+
sum REAL NOT NULL DEFAULT 0,
|
|
1344
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
1345
|
+
null_count INTEGER NOT NULL DEFAULT 0,
|
|
1346
|
+
PRIMARY KEY (series_id, bucket_ms)
|
|
1347
|
+
) WITHOUT ROWID;
|
|
1348
|
+
|
|
1349
|
+
CREATE TABLE points_1h (
|
|
1350
|
+
series_id INTEGER NOT NULL,
|
|
1351
|
+
bucket_ms INTEGER NOT NULL,
|
|
1352
|
+
min REAL,
|
|
1353
|
+
max REAL,
|
|
1354
|
+
sum REAL NOT NULL DEFAULT 0,
|
|
1355
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
1356
|
+
null_count INTEGER NOT NULL DEFAULT 0,
|
|
1357
|
+
PRIMARY KEY (series_id, bucket_ms)
|
|
1358
|
+
) WITHOUT ROWID;
|
|
1359
|
+
|
|
1360
|
+
-- Durable receipt / dedup key. Every accepted record kind has a row
|
|
1361
|
+
-- here before its side effects are committed.
|
|
1362
|
+
CREATE TABLE sample_receipts (
|
|
1363
|
+
collector_id TEXT NOT NULL,
|
|
1364
|
+
run_id TEXT NOT NULL,
|
|
1365
|
+
seq INTEGER NOT NULL,
|
|
1366
|
+
machine_id TEXT NOT NULL,
|
|
1367
|
+
kind TEXT NOT NULL,
|
|
1368
|
+
mode TEXT NOT NULL,
|
|
1369
|
+
captured_at_ms INTEGER NOT NULL,
|
|
1370
|
+
received_at_ms INTEGER NOT NULL,
|
|
1371
|
+
expected_interval_ms INTEGER,
|
|
1372
|
+
PRIMARY KEY (collector_id, run_id, seq)
|
|
1373
|
+
) WITHOUT ROWID;
|
|
1374
|
+
|
|
1375
|
+
-- Freshness and gap detection scan a machine's receipt timeline.
|
|
1376
|
+
CREATE INDEX sample_receipts_machine_captured
|
|
1377
|
+
ON sample_receipts (machine_id, captured_at_ms);
|
|
1378
|
+
-- Retention deletes oldest receipts without an unbounded scan.
|
|
1379
|
+
CREATE INDEX sample_receipts_captured
|
|
1380
|
+
ON sample_receipts (captured_at_ms);
|
|
1381
|
+
|
|
1382
|
+
-- Collector-reported dropped intervals.
|
|
1383
|
+
CREATE TABLE gaps (
|
|
1384
|
+
collector_id TEXT NOT NULL,
|
|
1385
|
+
run_id TEXT NOT NULL,
|
|
1386
|
+
seq INTEGER NOT NULL,
|
|
1387
|
+
machine_id TEXT NOT NULL,
|
|
1388
|
+
from_ms INTEGER NOT NULL,
|
|
1389
|
+
to_ms INTEGER NOT NULL,
|
|
1390
|
+
dropped INTEGER NOT NULL,
|
|
1391
|
+
reason TEXT NOT NULL,
|
|
1392
|
+
received_at_ms INTEGER NOT NULL,
|
|
1393
|
+
PRIMARY KEY (collector_id, run_id, seq)
|
|
1394
|
+
) WITHOUT ROWID;
|
|
1395
|
+
|
|
1396
|
+
CREATE INDEX gaps_machine_to ON gaps (machine_id, to_ms);
|
|
1397
|
+
-- Retention deletes oldest gaps without a full scan.
|
|
1398
|
+
CREATE INDEX gaps_to ON gaps (to_ms);
|
|
1399
|
+
|
|
1400
|
+
-- Latest LIVE metrics per machine, persisted across restart. A replay
|
|
1401
|
+
-- batch never touches this table.
|
|
1402
|
+
CREATE TABLE machine_latest (
|
|
1403
|
+
machine_id TEXT PRIMARY KEY,
|
|
1404
|
+
collector_id TEXT NOT NULL,
|
|
1405
|
+
run_id TEXT NOT NULL,
|
|
1406
|
+
seq INTEGER NOT NULL,
|
|
1407
|
+
captured_at_ms INTEGER NOT NULL,
|
|
1408
|
+
received_at_ms INTEGER NOT NULL,
|
|
1409
|
+
payload_json TEXT NOT NULL
|
|
1410
|
+
);
|
|
1411
|
+
|
|
1412
|
+
-- Latest inventory per machine. Inventory is not time-series data.
|
|
1413
|
+
CREATE TABLE inventory (
|
|
1414
|
+
machine_id TEXT PRIMARY KEY,
|
|
1415
|
+
captured_at_ms INTEGER NOT NULL,
|
|
1416
|
+
received_at_ms INTEGER NOT NULL,
|
|
1417
|
+
inventory_json TEXT NOT NULL
|
|
1418
|
+
);
|
|
1419
|
+
`);
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
version: 2,
|
|
1424
|
+
name: "latest_session_identity",
|
|
1425
|
+
up(db) {
|
|
1426
|
+
const columns = new Set(
|
|
1427
|
+
db.prepare("PRAGMA table_info(machine_latest)").all().map(
|
|
1428
|
+
(column) => column.name
|
|
1429
|
+
)
|
|
1430
|
+
);
|
|
1431
|
+
if (!columns.has("boot_id")) {
|
|
1432
|
+
db.exec("ALTER TABLE machine_latest ADD COLUMN boot_id TEXT NOT NULL DEFAULT ''");
|
|
1433
|
+
}
|
|
1434
|
+
if (!columns.has("expected_interval_ms")) {
|
|
1435
|
+
db.exec("ALTER TABLE machine_latest ADD COLUMN expected_interval_ms INTEGER");
|
|
1436
|
+
}
|
|
1437
|
+
if (!columns.has("hostname")) {
|
|
1438
|
+
db.exec("ALTER TABLE machine_latest ADD COLUMN hostname TEXT");
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1442
|
+
{ version: 3, name: "admin", up: adminSchema },
|
|
1443
|
+
{ version: 4, name: "alerts", up: installAlertSchema },
|
|
1444
|
+
{ version: 5, name: "collector_registry", up: installRegistrySchema },
|
|
1445
|
+
{ version: 6, name: "telemetry_modes_and_recordings", up: telemetryModesAndRecordings }
|
|
1446
|
+
];
|
|
1447
|
+
function telemetryModesAndRecordings(db) {
|
|
1448
|
+
db.exec(`
|
|
1449
|
+
CREATE TABLE IF NOT EXISTS points_10s (
|
|
1450
|
+
series_id INTEGER NOT NULL,
|
|
1451
|
+
bucket_ms INTEGER NOT NULL,
|
|
1452
|
+
min REAL,
|
|
1453
|
+
max REAL,
|
|
1454
|
+
sum REAL NOT NULL DEFAULT 0,
|
|
1455
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
1456
|
+
null_count INTEGER NOT NULL DEFAULT 0,
|
|
1457
|
+
PRIMARY KEY (series_id, bucket_ms)
|
|
1458
|
+
) WITHOUT ROWID;
|
|
1459
|
+
|
|
1460
|
+
CREATE TABLE IF NOT EXISTS recordings (
|
|
1461
|
+
id TEXT PRIMARY KEY,
|
|
1462
|
+
created_by TEXT NOT NULL,
|
|
1463
|
+
status TEXT NOT NULL,
|
|
1464
|
+
machines_json TEXT NOT NULL,
|
|
1465
|
+
metrics_json TEXT NOT NULL,
|
|
1466
|
+
resolution_ms INTEGER NOT NULL,
|
|
1467
|
+
created_at_ms INTEGER NOT NULL,
|
|
1468
|
+
started_at_ms INTEGER NOT NULL,
|
|
1469
|
+
stopped_at_ms INTEGER,
|
|
1470
|
+
deadline_ms INTEGER NOT NULL,
|
|
1471
|
+
max_bytes INTEGER NOT NULL,
|
|
1472
|
+
bytes_written INTEGER NOT NULL DEFAULT 0,
|
|
1473
|
+
file_name TEXT NOT NULL,
|
|
1474
|
+
include_backlog INTEGER NOT NULL DEFAULT 0,
|
|
1475
|
+
reason TEXT,
|
|
1476
|
+
error TEXT
|
|
1477
|
+
);
|
|
1478
|
+
|
|
1479
|
+
CREATE INDEX IF NOT EXISTS recordings_created
|
|
1480
|
+
ON recordings (created_at_ms DESC);
|
|
1481
|
+
`);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/hub/storage/db.ts
|
|
1485
|
+
var DEFAULT_MAX_DATABASE_DISK_BYTES = 2 * 1024 * 1024 * 1024;
|
|
1486
|
+
var HUB_WAL_RESERVE_BYTES = 128 * 1024 * 1024;
|
|
1487
|
+
|
|
1488
|
+
// src/operations/fs.ts
|
|
1489
|
+
var import_node_fs3 = require("node:fs");
|
|
1490
|
+
var import_node_crypto5 = require("node:crypto");
|
|
1491
|
+
var import_node_path4 = require("node:path");
|
|
1492
|
+
var PRIVATE_DIR_MODE = 448;
|
|
1493
|
+
var PRIVATE_FILE_MODE = 384;
|
|
1494
|
+
var OperationsError = class extends Error {
|
|
1495
|
+
code;
|
|
1496
|
+
constructor(code, message, options) {
|
|
1497
|
+
super(message, options);
|
|
1498
|
+
this.name = "OperationsError";
|
|
1499
|
+
this.code = code;
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
function errorMessage(error) {
|
|
1503
|
+
return error instanceof Error ? error.message : String(error);
|
|
1504
|
+
}
|
|
1505
|
+
function timestampSlug(ms = Date.now()) {
|
|
1506
|
+
return new Date(ms).toISOString().replace(/[:.]/g, "-");
|
|
1507
|
+
}
|
|
1508
|
+
function isPrivateMode(mode) {
|
|
1509
|
+
return (mode & 63) === 0;
|
|
1510
|
+
}
|
|
1511
|
+
function ensurePrivateDir(path) {
|
|
1512
|
+
const absolute = (0, import_node_path4.resolve)(path);
|
|
1513
|
+
(0, import_node_fs3.mkdirSync)(absolute, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
1514
|
+
const stat = (0, import_node_fs3.lstatSync)(absolute);
|
|
1515
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
1516
|
+
throw new OperationsError("unsafe-path", `${absolute} must be a real directory`);
|
|
1517
|
+
}
|
|
1518
|
+
if (!isPrivateMode(stat.mode)) {
|
|
1519
|
+
throw new OperationsError(
|
|
1520
|
+
"insecure-permissions",
|
|
1521
|
+
`${absolute} must be private (0700 or stricter); found ${formatMode(stat.mode)}`
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
function assertPrivateRegularFile(path, label) {
|
|
1526
|
+
const absolute = (0, import_node_path4.resolve)(path);
|
|
1527
|
+
let stat;
|
|
1528
|
+
try {
|
|
1529
|
+
stat = (0, import_node_fs3.lstatSync)(absolute);
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
throw new OperationsError("missing-file", `${label} does not exist: ${absolute}`, { cause: error });
|
|
1532
|
+
}
|
|
1533
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1534
|
+
throw new OperationsError("unsafe-path", `${label} must be a regular file, not a symlink: ${absolute}`);
|
|
1535
|
+
}
|
|
1536
|
+
if (!isPrivateMode(stat.mode)) {
|
|
1537
|
+
throw new OperationsError(
|
|
1538
|
+
"insecure-permissions",
|
|
1539
|
+
`${label} must be private (0600/0400); found ${formatMode(stat.mode)}: ${absolute}`
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
return stat;
|
|
1543
|
+
}
|
|
1544
|
+
function formatMode(mode) {
|
|
1545
|
+
return `0${(mode & 511).toString(8).padStart(3, "0")}`;
|
|
1546
|
+
}
|
|
1547
|
+
function writeFileAtomic(path, content, mode) {
|
|
1548
|
+
const absolute = (0, import_node_path4.resolve)(path);
|
|
1549
|
+
const parent = (0, import_node_path4.dirname)(absolute);
|
|
1550
|
+
const temporary = (0, import_node_path4.join)(parent, `.${(0, import_node_path4.basename)(absolute)}.tmp-${(0, import_node_crypto5.randomUUID)()}`);
|
|
1551
|
+
let fd = null;
|
|
1552
|
+
try {
|
|
1553
|
+
fd = (0, import_node_fs3.openSync)(temporary, "wx", mode);
|
|
1554
|
+
(0, import_node_fs3.writeFileSync)(fd, content);
|
|
1555
|
+
(0, import_node_fs3.fsyncSync)(fd);
|
|
1556
|
+
(0, import_node_fs3.closeSync)(fd);
|
|
1557
|
+
fd = null;
|
|
1558
|
+
(0, import_node_fs3.chmodSync)(temporary, mode);
|
|
1559
|
+
(0, import_node_fs3.renameSync)(temporary, absolute);
|
|
1560
|
+
fsyncDir(parent);
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
if (fd !== null) {
|
|
1563
|
+
try {
|
|
1564
|
+
(0, import_node_fs3.closeSync)(fd);
|
|
1565
|
+
} catch {
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
removeIfExists(temporary);
|
|
1569
|
+
throw error;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function writePrivateFile(path, content) {
|
|
1573
|
+
ensurePrivateDir((0, import_node_path4.dirname)((0, import_node_path4.resolve)(path)));
|
|
1574
|
+
writeFileAtomic(path, content, PRIVATE_FILE_MODE);
|
|
1575
|
+
}
|
|
1576
|
+
function fsyncDir(path) {
|
|
1577
|
+
let fd = null;
|
|
1578
|
+
try {
|
|
1579
|
+
fd = (0, import_node_fs3.openSync)(path, "r");
|
|
1580
|
+
(0, import_node_fs3.fsyncSync)(fd);
|
|
1581
|
+
} catch {
|
|
1582
|
+
} finally {
|
|
1583
|
+
if (fd !== null) {
|
|
1584
|
+
try {
|
|
1585
|
+
(0, import_node_fs3.closeSync)(fd);
|
|
1586
|
+
} catch {
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
function removeIfExists(path) {
|
|
1592
|
+
try {
|
|
1593
|
+
(0, import_node_fs3.unlinkSync)(path);
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
if (error.code !== "ENOENT") throw error;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
function fileSize(path) {
|
|
1599
|
+
try {
|
|
1600
|
+
const stat = (0, import_node_fs3.statSync)(path);
|
|
1601
|
+
return stat.isFile() ? stat.size : 0;
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
if (error.code === "ENOENT") return 0;
|
|
1604
|
+
throw error;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
function resolvePath(value, label) {
|
|
1608
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1609
|
+
throw new OperationsError("invalid-argument", `${label} must be a nonempty path`);
|
|
1610
|
+
}
|
|
1611
|
+
return (0, import_node_path4.resolve)(value);
|
|
1612
|
+
}
|
|
1613
|
+
function findHubLock(databasePath) {
|
|
1614
|
+
const absolute = (0, import_node_path4.resolve)(databasePath);
|
|
1615
|
+
const parent = (0, import_node_path4.dirname)(absolute);
|
|
1616
|
+
const envLock = process.env.RIGSCOPE_HUB_LOCK_FILE;
|
|
1617
|
+
const candidates = [
|
|
1618
|
+
...envLock ? [(0, import_node_path4.resolve)(envLock)] : [],
|
|
1619
|
+
`${absolute}.lock`,
|
|
1620
|
+
`${absolute}-lock`,
|
|
1621
|
+
`${absolute}.hub.lock`,
|
|
1622
|
+
(0, import_node_path4.join)(parent, "hub.lock"),
|
|
1623
|
+
(0, import_node_path4.join)(parent, "hub.pid")
|
|
1624
|
+
// `run/hub.lock` is the component supervisor lease, not a database
|
|
1625
|
+
// lease. Treating it as a hub lock would make an upgrade's own held lease
|
|
1626
|
+
// look like a live hub during database backup/restore.
|
|
1627
|
+
];
|
|
1628
|
+
for (const candidate of candidates) {
|
|
1629
|
+
try {
|
|
1630
|
+
(0, import_node_fs3.lstatSync)(candidate);
|
|
1631
|
+
return candidate;
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
if (error.code !== "ENOENT") throw error;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return null;
|
|
1637
|
+
}
|
|
1638
|
+
function assertNoActiveHubLock(databasePath) {
|
|
1639
|
+
const lock = findHubLock(databasePath);
|
|
1640
|
+
if (lock !== null) {
|
|
1641
|
+
throw new OperationsError(
|
|
1642
|
+
"hub-active",
|
|
1643
|
+
`an active hub lock exists at ${lock}; stop the hub, verify no process owns it, and remove the lock only after explicit inspection`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// src/operations/database.ts
|
|
1649
|
+
var SUPPORTED_SCHEMA_VERSION = MIGRATIONS.length === 0 ? 0 : MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
1650
|
+
var BUSY_TIMEOUT_MS = 5e3;
|
|
1651
|
+
var PRIVATE_FILE_MODE2 = 384;
|
|
1652
|
+
var COUNTED_TABLES = /* @__PURE__ */ new Set([
|
|
1653
|
+
"schema_migrations",
|
|
1654
|
+
"collector_registry",
|
|
1655
|
+
"collector_registry_meta",
|
|
1656
|
+
"collector_grants",
|
|
1657
|
+
"collector_audit",
|
|
1658
|
+
"users",
|
|
1659
|
+
"admin_audit",
|
|
1660
|
+
"admin_meta",
|
|
1661
|
+
"machine_meta",
|
|
1662
|
+
"machine_latest",
|
|
1663
|
+
"inventory",
|
|
1664
|
+
"points_10s",
|
|
1665
|
+
"gaps",
|
|
1666
|
+
"recordings",
|
|
1667
|
+
"alert_rules",
|
|
1668
|
+
"alert_incidents",
|
|
1669
|
+
"alert_jobs",
|
|
1670
|
+
"alert_webhook",
|
|
1671
|
+
"alert_machine_progress"
|
|
1672
|
+
]);
|
|
1673
|
+
function openReadOnly(path) {
|
|
1674
|
+
const db = new import_node_sqlite2.DatabaseSync(path, { readOnly: true, timeout: BUSY_TIMEOUT_MS });
|
|
1675
|
+
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
1676
|
+
return db;
|
|
1677
|
+
}
|
|
1678
|
+
function rowsOf(statement) {
|
|
1679
|
+
return statement.all();
|
|
1680
|
+
}
|
|
1681
|
+
function firstValue(row) {
|
|
1682
|
+
return row === void 0 ? void 0 : Object.values(row)[0];
|
|
1683
|
+
}
|
|
1684
|
+
function integrityResult(db) {
|
|
1685
|
+
const rows = rowsOf(db.prepare("PRAGMA integrity_check"));
|
|
1686
|
+
const lines = rows.map((row) => String(firstValue(row) ?? ""));
|
|
1687
|
+
const text = lines.length === 0 ? "no integrity result" : lines.join("; ");
|
|
1688
|
+
return { text, ok: lines.length === 1 && lines[0] === "ok" };
|
|
1689
|
+
}
|
|
1690
|
+
function tableNames(db) {
|
|
1691
|
+
return rowsOf(
|
|
1692
|
+
db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
|
|
1693
|
+
).map((row) => String(row.name));
|
|
1694
|
+
}
|
|
1695
|
+
function readLedger(db, hasTable) {
|
|
1696
|
+
if (!hasTable) return [];
|
|
1697
|
+
return rowsOf(db.prepare("SELECT version, name FROM schema_migrations ORDER BY version")).map((row) => ({
|
|
1698
|
+
version: Number(row.version),
|
|
1699
|
+
name: String(row.name)
|
|
1700
|
+
}));
|
|
1701
|
+
}
|
|
1702
|
+
function ledgerProblem(ledger) {
|
|
1703
|
+
if (ledger.length === 0) return "schema_migrations exists but has no applied migrations";
|
|
1704
|
+
for (const [index, row] of ledger.entries()) {
|
|
1705
|
+
if (!Number.isSafeInteger(row.version) || row.version < 1) return `invalid migration version ${row.version}`;
|
|
1706
|
+
if (row.version !== index + 1) {
|
|
1707
|
+
return `migration history is not contiguous at version ${row.version}`;
|
|
1708
|
+
}
|
|
1709
|
+
const expected = MIGRATIONS.find((migration) => migration.version === row.version);
|
|
1710
|
+
if (expected === void 0) {
|
|
1711
|
+
return row.version > SUPPORTED_SCHEMA_VERSION ? `schema version ${row.version} is newer than this build (${SUPPORTED_SCHEMA_VERSION})` : `unknown migration version ${row.version}`;
|
|
1712
|
+
}
|
|
1713
|
+
if (row.name !== expected.name) {
|
|
1714
|
+
return `migration ${row.version} is named "${row.name}", expected "${expected.name}"`;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
function describeOpenDatabase(db, path, statMode) {
|
|
1720
|
+
const integrity = integrityResult(db);
|
|
1721
|
+
const tables = tableNames(db);
|
|
1722
|
+
const hasSchemaMigrations = tables.includes("schema_migrations");
|
|
1723
|
+
const ledger = readLedger(db, hasSchemaMigrations);
|
|
1724
|
+
const schemaVersion = ledger.length === 0 ? 0 : ledger[ledger.length - 1].version;
|
|
1725
|
+
const problem = hasSchemaMigrations ? ledgerProblem(ledger) : "schema_migrations is missing";
|
|
1726
|
+
const journal = db.prepare("PRAGMA journal_mode").get();
|
|
1727
|
+
const pageSize = Number(db.prepare("PRAGMA page_size").get()?.page_size ?? 0);
|
|
1728
|
+
const pageCount = Number(db.prepare("PRAGMA page_count").get()?.page_count ?? 0);
|
|
1729
|
+
const counted = tables.map((name) => {
|
|
1730
|
+
if (!COUNTED_TABLES.has(name)) return { name };
|
|
1731
|
+
try {
|
|
1732
|
+
const row = db.prepare(`SELECT COUNT(*) AS n FROM "${name.replace(/"/g, '""')}"`).get();
|
|
1733
|
+
return { name, rows: Number(row?.n ?? 0) };
|
|
1734
|
+
} catch {
|
|
1735
|
+
return { name };
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1738
|
+
return {
|
|
1739
|
+
path,
|
|
1740
|
+
fileBytes: fileSize(path),
|
|
1741
|
+
walBytes: fileSize(`${path}-wal`),
|
|
1742
|
+
shmBytes: fileSize(`${path}-shm`),
|
|
1743
|
+
mode: `0${(statMode & 511).toString(8).padStart(3, "0")}`,
|
|
1744
|
+
integrity: integrity.text,
|
|
1745
|
+
integrityOk: integrity.ok,
|
|
1746
|
+
journalMode: String(firstValue(journal) ?? "unknown"),
|
|
1747
|
+
pageSize,
|
|
1748
|
+
pageCount,
|
|
1749
|
+
hasSchemaMigrations,
|
|
1750
|
+
schemaVersion,
|
|
1751
|
+
supportedSchemaVersion: SUPPORTED_SCHEMA_VERSION,
|
|
1752
|
+
newerSchema: schemaVersion > SUPPORTED_SCHEMA_VERSION,
|
|
1753
|
+
ledger,
|
|
1754
|
+
ledgerProblem: problem,
|
|
1755
|
+
tables: counted,
|
|
1756
|
+
registryPresent: tables.includes("collector_registry"),
|
|
1757
|
+
adminPresent: tables.includes("users"),
|
|
1758
|
+
alertsPresent: tables.includes("alert_rules") && tables.includes("alert_incidents") && tables.includes("alert_jobs")
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
function chmodSidecars(path) {
|
|
1762
|
+
for (const sidecar of [`${path}-wal`, `${path}-shm`]) {
|
|
1763
|
+
try {
|
|
1764
|
+
const stat = (0, import_node_fs4.lstatSync)(sidecar);
|
|
1765
|
+
if (stat.isFile() && !isPrivateMode(stat.mode)) (0, import_node_fs4.chmodSync)(sidecar, PRIVATE_FILE_MODE2);
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function inspectDatabase(path) {
|
|
1771
|
+
const absolute = resolvePath(path, "database");
|
|
1772
|
+
const stat = assertPrivateRegularFile(absolute, "database");
|
|
1773
|
+
const db = openReadOnly(absolute);
|
|
1774
|
+
try {
|
|
1775
|
+
return describeOpenDatabase(db, absolute, stat.mode);
|
|
1776
|
+
} finally {
|
|
1777
|
+
db.close();
|
|
1778
|
+
chmodSidecars(absolute);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
var MACHINE_HISTORY_TABLES = [
|
|
1782
|
+
"points_raw",
|
|
1783
|
+
"points_10s",
|
|
1784
|
+
"points_1m",
|
|
1785
|
+
"points_1h",
|
|
1786
|
+
"sample_receipts",
|
|
1787
|
+
"gaps",
|
|
1788
|
+
"machine_latest",
|
|
1789
|
+
"inventory",
|
|
1790
|
+
"alert_observed_resources",
|
|
1791
|
+
"alert_machine_progress",
|
|
1792
|
+
"alert_incidents",
|
|
1793
|
+
"series"
|
|
1794
|
+
];
|
|
1795
|
+
function clearMachineHistory(database) {
|
|
1796
|
+
const path = resolvePath(database, "database");
|
|
1797
|
+
if (!(0, import_node_fs4.existsSync)(path)) return { deleted: {}, integrity: "absent" };
|
|
1798
|
+
assertPrivateRegularFile(path, "database");
|
|
1799
|
+
assertNoActiveHubLock(path);
|
|
1800
|
+
const db = new import_node_sqlite2.DatabaseSync(path, { timeout: BUSY_TIMEOUT_MS });
|
|
1801
|
+
const deleted = {};
|
|
1802
|
+
try {
|
|
1803
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
1804
|
+
const present = new Set(
|
|
1805
|
+
db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => row.name)
|
|
1806
|
+
);
|
|
1807
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1808
|
+
try {
|
|
1809
|
+
for (const table of MACHINE_HISTORY_TABLES) {
|
|
1810
|
+
if (!present.has(table)) continue;
|
|
1811
|
+
const result = db.prepare(`DELETE FROM ${table}`).run();
|
|
1812
|
+
deleted[table] = Number(result.changes);
|
|
1813
|
+
}
|
|
1814
|
+
db.exec("COMMIT");
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
try {
|
|
1817
|
+
db.exec("ROLLBACK");
|
|
1818
|
+
} catch {
|
|
1819
|
+
}
|
|
1820
|
+
throw error;
|
|
1821
|
+
}
|
|
1822
|
+
const autoVacuum = db.prepare("PRAGMA auto_vacuum").get();
|
|
1823
|
+
if (autoVacuum && Object.values(autoVacuum)[0] === 2) db.exec("PRAGMA incremental_vacuum(1024)");
|
|
1824
|
+
try {
|
|
1825
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
} finally {
|
|
1829
|
+
db.close();
|
|
1830
|
+
}
|
|
1831
|
+
return { deleted, integrity: inspectDatabase(path).integrity };
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/cli/machine-transition.ts
|
|
1835
|
+
var MACHINE_DATA_TRANSITION_FILE = "machine-data-transition.json";
|
|
1836
|
+
var STAGED_QUEUE_PREFIX = "outbox.pre-transition-";
|
|
1837
|
+
function machineDataTransitionPath(stateRoot) {
|
|
1838
|
+
return (0, import_node_path5.join)(stateRoot, MACHINE_DATA_TRANSITION_FILE);
|
|
1839
|
+
}
|
|
1840
|
+
function readMachineDataTransition(stateRoot) {
|
|
1841
|
+
const path = machineDataTransitionPath(stateRoot);
|
|
1842
|
+
try {
|
|
1843
|
+
const stat = (0, import_node_fs5.lstatSync)(path);
|
|
1844
|
+
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0) {
|
|
1845
|
+
throw new Error("marker must be a private regular file");
|
|
1846
|
+
}
|
|
1847
|
+
const value = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
1848
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid marker");
|
|
1849
|
+
const record = value;
|
|
1850
|
+
if (typeof record.transitionedAt !== "string" || !Number.isFinite(Date.parse(record.transitionedAt))) {
|
|
1851
|
+
throw new Error("invalid transition date");
|
|
1852
|
+
}
|
|
1853
|
+
return { transitionedAt: record.transitionedAt, release: typeof record.release === "string" ? record.release : null };
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
if (error.code === "ENOENT") return null;
|
|
1856
|
+
throw new OperationsError(
|
|
1857
|
+
"invalid-transition-marker",
|
|
1858
|
+
`Cannot determine whether the one-time reset completed; preserving machine data. Repair ${path}: ${errorMessage(error)}`,
|
|
1859
|
+
{ cause: error }
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
function recordMachineDataTransition(stateRoot, release, write = (path, content) => writePrivateFile(path, content)) {
|
|
1864
|
+
const marker = { transitionedAt: (/* @__PURE__ */ new Date()).toISOString(), release };
|
|
1865
|
+
write(machineDataTransitionPath(stateRoot), `${JSON.stringify(marker, null, 2)}
|
|
1866
|
+
`);
|
|
1867
|
+
}
|
|
1868
|
+
function newMachineDataCleanup(transitioned) {
|
|
1869
|
+
return {
|
|
1870
|
+
report: { transitioned, databaseRows: null, collectorQueuesRemoved: false, warning: null },
|
|
1871
|
+
stagedQueues: null,
|
|
1872
|
+
databaseCleared: false
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
function performMachineDataCleanup(stateRoot, paths, now = Date.now, deps = {}) {
|
|
1876
|
+
if (readMachineDataTransition(stateRoot) !== null) return newMachineDataCleanup(false);
|
|
1877
|
+
const cleanup = newMachineDataCleanup(true);
|
|
1878
|
+
const clear = deps.clear ?? clearMachineHistory;
|
|
1879
|
+
const warnings = [];
|
|
1880
|
+
try {
|
|
1881
|
+
if ((0, import_node_fs5.existsSync)(paths.queueTree)) {
|
|
1882
|
+
const staged = (0, import_node_path5.join)((0, import_node_path5.dirname)(paths.queueTree), `${STAGED_QUEUE_PREFIX}${timestampSlug(now())}-${(0, import_node_crypto6.randomUUID)()}`);
|
|
1883
|
+
(0, import_node_fs5.renameSync)(paths.queueTree, staged);
|
|
1884
|
+
cleanup.stagedQueues = staged;
|
|
1885
|
+
cleanup.report.collectorQueuesRemoved = true;
|
|
1886
|
+
}
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
warnings.push(`collector queues: ${errorMessage(error)}`);
|
|
1889
|
+
}
|
|
1890
|
+
try {
|
|
1891
|
+
if ((0, import_node_fs5.existsSync)(paths.database)) {
|
|
1892
|
+
cleanup.databaseCleared = true;
|
|
1893
|
+
cleanup.report.databaseRows = clear(paths.database).deleted;
|
|
1894
|
+
}
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
warnings.push(`hub database: ${errorMessage(error)}`);
|
|
1897
|
+
}
|
|
1898
|
+
cleanup.report.warning = warnings.length > 0 ? warnings.join("; ") : null;
|
|
1899
|
+
return cleanup;
|
|
1900
|
+
}
|
|
1901
|
+
function undoMachineDataCleanup(paths, stagedQueues) {
|
|
1902
|
+
if (stagedQueues === null || !(0, import_node_fs5.existsSync)(stagedQueues)) return [];
|
|
1903
|
+
try {
|
|
1904
|
+
if ((0, import_node_fs5.existsSync)(paths.queueTree)) {
|
|
1905
|
+
const failedQueue = `${paths.queueTree}.failed-transition-${(0, import_node_crypto6.randomUUID)()}`;
|
|
1906
|
+
(0, import_node_fs5.renameSync)(paths.queueTree, failedQueue);
|
|
1907
|
+
try {
|
|
1908
|
+
(0, import_node_fs5.renameSync)(stagedQueues, paths.queueTree);
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
(0, import_node_fs5.renameSync)(failedQueue, paths.queueTree);
|
|
1911
|
+
throw error;
|
|
1912
|
+
}
|
|
1913
|
+
return [`restored the collector queue tree; replacement runtime queue preserved at ${failedQueue}`];
|
|
1914
|
+
}
|
|
1915
|
+
(0, import_node_fs5.renameSync)(stagedQueues, paths.queueTree);
|
|
1916
|
+
return ["restored the collector queue tree the transition had staged aside"];
|
|
1917
|
+
} catch (error) {
|
|
1918
|
+
return [`machine data rollback could not restore the collector queue: ${errorMessage(error)}`];
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
function completeMachineDataCleanup(stateRoot, cleanup, release, write) {
|
|
1922
|
+
if (!cleanup.report.transitioned) return;
|
|
1923
|
+
if (cleanup.report.warning !== null) return;
|
|
1924
|
+
try {
|
|
1925
|
+
recordMachineDataTransition(stateRoot, release, write);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
cleanup.report.warning = `could not record transition completion: ${errorMessage(error)}`;
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
if (cleanup.stagedQueues !== null) {
|
|
1931
|
+
try {
|
|
1932
|
+
(0, import_node_fs5.rmSync)(cleanup.stagedQueues, { recursive: true, force: true });
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
cleanup.report.warning = `transition completed; staged queue remains at ${cleanup.stagedQueues}: ${errorMessage(error)}`;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
function machineDataReportRows(report) {
|
|
1939
|
+
if (report.databaseRows === null) return null;
|
|
1940
|
+
return Object.values(report.databaseRows).reduce((sum, rows) => sum + rows, 0);
|
|
1941
|
+
}
|
|
1942
|
+
function machineDataKeptNote() {
|
|
1943
|
+
return "accounts, sessions, enrollment and grants, settings, alert rules/jobs/webhook settings, machine names/tags and recordings were kept";
|
|
1944
|
+
}
|
|
1945
|
+
function describeMachineDataReport(report) {
|
|
1946
|
+
const summary = report.transitioned ? (() => {
|
|
1947
|
+
const rows = machineDataReportRows(report);
|
|
1948
|
+
const parts = [
|
|
1949
|
+
rows === null ? "no hub database" : `${rows} database row(s)`,
|
|
1950
|
+
...report.collectorQueuesRemoved ? ["the collector queue tree"] : []
|
|
1951
|
+
];
|
|
1952
|
+
return `Machine data: one-time RAM-first transition discarded ${parts.join(" and ")}. The ${machineDataKeptNote()}.`;
|
|
1953
|
+
})() : "Machine data: one-time transition already completed; durable history and collector queues preserved.";
|
|
1954
|
+
return {
|
|
1955
|
+
summary,
|
|
1956
|
+
warning: report.warning === null ? null : `Machine-data cleanup warning (${report.warning}). The upgrade is healthy; cleanup without a completion marker is retried on the next upgrade.`
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1023
1960
|
// src/cli/system.ts
|
|
1024
1961
|
var SYSTEM_SCOPE = "system";
|
|
1025
1962
|
var DEFAULT_SYSTEM_ACCOUNTS = {
|
|
@@ -1129,13 +2066,13 @@ function createSystemHost(overrides = {}) {
|
|
|
1129
2066
|
}
|
|
1130
2067
|
function defaultSystemPaths(overrides = {}) {
|
|
1131
2068
|
return {
|
|
1132
|
-
prefix: (0,
|
|
1133
|
-
configDir: (0,
|
|
1134
|
-
stateDir: (0,
|
|
1135
|
-
unitsDir: (0,
|
|
1136
|
-
sysusersDir: (0,
|
|
1137
|
-
udevRulesDir: (0,
|
|
1138
|
-
powercapRoot: (0,
|
|
2069
|
+
prefix: (0, import_node_path6.resolve)(overrides.prefix ?? "/opt/rigscope"),
|
|
2070
|
+
configDir: (0, import_node_path6.resolve)(overrides.configDir ?? "/etc/rigscope"),
|
|
2071
|
+
stateDir: (0, import_node_path6.resolve)(overrides.stateDir ?? "/var/lib/rigscope"),
|
|
2072
|
+
unitsDir: (0, import_node_path6.resolve)(overrides.unitsDir ?? "/etc/systemd/system"),
|
|
2073
|
+
sysusersDir: (0, import_node_path6.resolve)(overrides.sysusersDir ?? "/etc/sysusers.d"),
|
|
2074
|
+
udevRulesDir: (0, import_node_path6.resolve)(overrides.udevRulesDir ?? "/etc/udev/rules.d"),
|
|
2075
|
+
powercapRoot: (0, import_node_path6.resolve)(overrides.powercapRoot ?? "/sys/class/powercap")
|
|
1139
2076
|
};
|
|
1140
2077
|
}
|
|
1141
2078
|
function unitNamesFor(role) {
|
|
@@ -1150,11 +2087,11 @@ function rolesFor(role) {
|
|
|
1150
2087
|
return { hub: role !== "collector", collector: role !== "hub" };
|
|
1151
2088
|
}
|
|
1152
2089
|
function isWithin(parent, candidate) {
|
|
1153
|
-
const rel = (0,
|
|
1154
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
2090
|
+
const rel = (0, import_node_path6.relative)((0, import_node_path6.resolve)(parent), (0, import_node_path6.resolve)(candidate));
|
|
2091
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${import_node_path6.sep}`) && !(0, import_node_path6.isAbsolute)(rel);
|
|
1155
2092
|
}
|
|
1156
2093
|
function ensureAbsolute(path, label) {
|
|
1157
|
-
const absolute = (0,
|
|
2094
|
+
const absolute = (0, import_node_path6.resolve)(path);
|
|
1158
2095
|
if (/[\0\r\n]/.test(absolute)) throw new SystemError("unsafe-path", `${label} contains control characters`);
|
|
1159
2096
|
if (absolute === "/") throw new SystemError("unsafe-path", `${label} must not be the filesystem root`);
|
|
1160
2097
|
for (const forbidden of ["/home", "/root", "/proc", "/sys", "/dev", "/boot"]) {
|
|
@@ -1168,7 +2105,7 @@ function validateSystemPaths(input = {}) {
|
|
|
1168
2105
|
const paths = defaultSystemPaths(input);
|
|
1169
2106
|
for (const [label, value] of Object.entries(paths)) {
|
|
1170
2107
|
if (label === "powercapRoot") {
|
|
1171
|
-
if (!(0,
|
|
2108
|
+
if (!(0, import_node_path6.isAbsolute)(value) || /[\0\r\n]/.test(value) || (0, import_node_path6.resolve)(value) === "/") {
|
|
1172
2109
|
throw new SystemError("unsafe-path", `powercap root is unsafe: ${value}`);
|
|
1173
2110
|
}
|
|
1174
2111
|
} else {
|
|
@@ -1223,19 +2160,19 @@ function runRequired(host, command, args, label, options) {
|
|
|
1223
2160
|
function runMaybe(host, command, args, options) {
|
|
1224
2161
|
return host.run(command, args, options);
|
|
1225
2162
|
}
|
|
1226
|
-
function
|
|
1227
|
-
return (0,
|
|
2163
|
+
function sha256File2(path) {
|
|
2164
|
+
return (0, import_node_crypto7.createHash)("sha256").update((0, import_node_fs6.readFileSync)(path)).digest("hex");
|
|
1228
2165
|
}
|
|
1229
2166
|
function atomicWrite(path, content, mode) {
|
|
1230
|
-
(0,
|
|
1231
|
-
const temporary = (0,
|
|
2167
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(path), { recursive: true, mode: 493 });
|
|
2168
|
+
const temporary = (0, import_node_path6.join)((0, import_node_path6.dirname)(path), `.${(0, import_node_path6.basename)(path)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
1232
2169
|
try {
|
|
1233
|
-
(0,
|
|
1234
|
-
(0,
|
|
1235
|
-
(0,
|
|
2170
|
+
(0, import_node_fs6.writeFileSync)(temporary, content, { mode });
|
|
2171
|
+
(0, import_node_fs6.chmodSync)(temporary, mode);
|
|
2172
|
+
(0, import_node_fs6.renameSync)(temporary, path);
|
|
1236
2173
|
} catch (error) {
|
|
1237
2174
|
try {
|
|
1238
|
-
(0,
|
|
2175
|
+
(0, import_node_fs6.unlinkSync)(temporary);
|
|
1239
2176
|
} catch {
|
|
1240
2177
|
}
|
|
1241
2178
|
throw error;
|
|
@@ -1244,12 +2181,12 @@ function atomicWrite(path, content, mode) {
|
|
|
1244
2181
|
function ensureDirectory(path, mode, privateDirectory = false) {
|
|
1245
2182
|
let stat;
|
|
1246
2183
|
try {
|
|
1247
|
-
stat = (0,
|
|
2184
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
1248
2185
|
} catch (error) {
|
|
1249
2186
|
if (error.code !== "ENOENT") throw error;
|
|
1250
|
-
(0,
|
|
1251
|
-
(0,
|
|
1252
|
-
stat = (0,
|
|
2187
|
+
(0, import_node_fs6.mkdirSync)(path, { recursive: true, mode });
|
|
2188
|
+
(0, import_node_fs6.chmodSync)(path, mode);
|
|
2189
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
1253
2190
|
}
|
|
1254
2191
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
1255
2192
|
throw new SystemError("unsafe-path", `${path} must be a real directory`);
|
|
@@ -1264,8 +2201,8 @@ function isDefaultSystemLayout(paths) {
|
|
|
1264
2201
|
}
|
|
1265
2202
|
function assertManagedPathSafety(paths, enforceRoot) {
|
|
1266
2203
|
for (const path of [paths.prefix, paths.configDir, paths.stateDir, paths.unitsDir, paths.sysusersDir, paths.udevRulesDir]) {
|
|
1267
|
-
if (!(0,
|
|
1268
|
-
const stat = (0,
|
|
2204
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2205
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1269
2206
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
1270
2207
|
throw new SystemError("unsafe-path", `${path} must be a real directory`);
|
|
1271
2208
|
}
|
|
@@ -1278,8 +2215,8 @@ function assertManagedPathSafety(paths, enforceRoot) {
|
|
|
1278
2215
|
}
|
|
1279
2216
|
}
|
|
1280
2217
|
function writeManagedFile(path, content, mode, replace) {
|
|
1281
|
-
if ((0,
|
|
1282
|
-
const stat = (0,
|
|
2218
|
+
if ((0, import_node_fs6.existsSync)(path)) {
|
|
2219
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1283
2220
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1284
2221
|
throw new SystemError("unsafe-path", `${path} must be a regular file, not a symlink`);
|
|
1285
2222
|
}
|
|
@@ -1318,7 +2255,7 @@ function parseEnvValue(value) {
|
|
|
1318
2255
|
function readEnvDocument(path) {
|
|
1319
2256
|
let raw;
|
|
1320
2257
|
try {
|
|
1321
|
-
raw = (0,
|
|
2258
|
+
raw = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
1322
2259
|
} catch (error) {
|
|
1323
2260
|
if (error.code === "ENOENT") return null;
|
|
1324
2261
|
throw error;
|
|
@@ -1365,43 +2302,43 @@ function connectDestinationUrl(value) {
|
|
|
1365
2302
|
return url.href;
|
|
1366
2303
|
}
|
|
1367
2304
|
function hubDatabasePath(paths) {
|
|
1368
|
-
return (0,
|
|
2305
|
+
return (0, import_node_path6.join)(paths.stateDir, "hub", "telemetry.sqlite");
|
|
1369
2306
|
}
|
|
1370
2307
|
function hubEnrollmentPath(paths) {
|
|
1371
|
-
return (0,
|
|
2308
|
+
return (0, import_node_path6.join)(paths.stateDir, "hub", "enrollment.json");
|
|
1372
2309
|
}
|
|
1373
2310
|
function hubSocketPath(paths) {
|
|
1374
|
-
return (0,
|
|
2311
|
+
return (0, import_node_path6.join)(paths.stateDir, "hub", "run", "hub.sock");
|
|
1375
2312
|
}
|
|
1376
2313
|
function collectorStatePath(paths) {
|
|
1377
|
-
return (0,
|
|
2314
|
+
return (0, import_node_path6.join)(paths.stateDir, "collector", "state.json");
|
|
1378
2315
|
}
|
|
1379
2316
|
function hubEnvPath(paths) {
|
|
1380
|
-
return (0,
|
|
2317
|
+
return (0, import_node_path6.join)(paths.configDir, "hub.env");
|
|
1381
2318
|
}
|
|
1382
2319
|
function collectorEnvPath(paths) {
|
|
1383
|
-
return (0,
|
|
2320
|
+
return (0, import_node_path6.join)(paths.configDir, "collector.env");
|
|
1384
2321
|
}
|
|
1385
2322
|
function sysusersPath(paths) {
|
|
1386
|
-
return (0,
|
|
2323
|
+
return (0, import_node_path6.join)(paths.sysusersDir, SYSUSERS_NAME);
|
|
1387
2324
|
}
|
|
1388
2325
|
function powerSysusersPath(paths) {
|
|
1389
|
-
return (0,
|
|
2326
|
+
return (0, import_node_path6.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
|
|
1390
2327
|
}
|
|
1391
2328
|
function raplRulePath(paths) {
|
|
1392
|
-
return (0,
|
|
2329
|
+
return (0, import_node_path6.join)(paths.udevRulesDir, RAPL_RULE_NAME);
|
|
1393
2330
|
}
|
|
1394
2331
|
function stateFilePath(paths) {
|
|
1395
|
-
return (0,
|
|
2332
|
+
return (0, import_node_path6.join)(paths.stateDir, STATE_FILE_NAME);
|
|
1396
2333
|
}
|
|
1397
2334
|
function readCollectorIdentity(path) {
|
|
1398
|
-
if (!(0,
|
|
1399
|
-
const stat = (0,
|
|
2335
|
+
if (!(0, import_node_fs6.existsSync)(path)) return null;
|
|
2336
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1400
2337
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
|
|
1401
2338
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be private (0600)`);
|
|
1402
2339
|
let raw;
|
|
1403
2340
|
try {
|
|
1404
|
-
raw = JSON.parse((0,
|
|
2341
|
+
raw = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
1405
2342
|
} catch {
|
|
1406
2343
|
throw new SystemError("invalid-file", `${path} is not valid JSON`);
|
|
1407
2344
|
}
|
|
@@ -1435,7 +2372,7 @@ function readCollectorIdentity(path) {
|
|
|
1435
2372
|
return { collectorId: record.collectorId, destinations };
|
|
1436
2373
|
}
|
|
1437
2374
|
function writeCollectorIdentity(path, identity) {
|
|
1438
|
-
ensureDirectory((0,
|
|
2375
|
+
ensureDirectory((0, import_node_path6.dirname)(path), 448, true);
|
|
1439
2376
|
atomicWrite(path, `${JSON.stringify(identity)}
|
|
1440
2377
|
`, 384);
|
|
1441
2378
|
}
|
|
@@ -1475,7 +2412,7 @@ function ensureHubOwner(paths) {
|
|
|
1475
2412
|
if (!Array.isArray(parsed)) throw new SystemError("invalid-config", "HUB_BROWSER_USERS must be a JSON array");
|
|
1476
2413
|
if (parsed.length > 0) return void 0;
|
|
1477
2414
|
}
|
|
1478
|
-
const password = (0,
|
|
2415
|
+
const password = (0, import_node_crypto7.randomBytes)(18).toString("base64url");
|
|
1479
2416
|
const users = [{ username: "owner", passwordHash: hashPassword(password), machineIds: "*", admin: true }];
|
|
1480
2417
|
writeEnvValue(hubEnvPath(paths), "HUB_BROWSER_USERS", JSON.stringify(users));
|
|
1481
2418
|
return password;
|
|
@@ -1526,8 +2463,8 @@ function updateHubCollectorRegistry(paths, identity) {
|
|
|
1526
2463
|
}
|
|
1527
2464
|
function registerCollectorInHubDatabase(paths, identity, host) {
|
|
1528
2465
|
const database = hubDatabasePath(paths);
|
|
1529
|
-
if (!(0,
|
|
1530
|
-
const db = new
|
|
2466
|
+
if (!(0, import_node_fs6.existsSync)(database)) return { inserted: false };
|
|
2467
|
+
const db = new import_node_sqlite3.DatabaseSync(database, { timeout: 5e3 });
|
|
1531
2468
|
try {
|
|
1532
2469
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
|
|
1533
2470
|
if (!table) return { inserted: false };
|
|
@@ -1541,8 +2478,8 @@ function registerCollectorInHubDatabase(paths, identity, host) {
|
|
|
1541
2478
|
}
|
|
1542
2479
|
function findCollectorInHubDatabase(paths, collectorId) {
|
|
1543
2480
|
const database = hubDatabasePath(paths);
|
|
1544
|
-
if (!(0,
|
|
1545
|
-
const db = new
|
|
2481
|
+
if (!(0, import_node_fs6.existsSync)(database)) return null;
|
|
2482
|
+
const db = new import_node_sqlite3.DatabaseSync(database, { readOnly: true, timeout: 5e3 });
|
|
1546
2483
|
try {
|
|
1547
2484
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
|
|
1548
2485
|
if (!table) return null;
|
|
@@ -1554,11 +2491,11 @@ function findCollectorInHubDatabase(paths, collectorId) {
|
|
|
1554
2491
|
}
|
|
1555
2492
|
function createLocalEnrollmentGrant(paths, host) {
|
|
1556
2493
|
const database = hubDatabasePath(paths);
|
|
1557
|
-
if (!(0,
|
|
1558
|
-
const token = (0,
|
|
1559
|
-
const tokenHash = (0,
|
|
2494
|
+
if (!(0, import_node_fs6.existsSync)(database)) throw new SystemError("hub-unavailable", "the hub database does not exist yet");
|
|
2495
|
+
const token = (0, import_node_crypto7.randomBytes)(32).toString("base64url");
|
|
2496
|
+
const tokenHash = (0, import_node_crypto7.createHash)("sha256").update(token).digest("hex");
|
|
1560
2497
|
const expiresAt = host.now() + 10 * 60 * 1e3;
|
|
1561
|
-
const db = new
|
|
2498
|
+
const db = new import_node_sqlite3.DatabaseSync(database, { timeout: 5e3 });
|
|
1562
2499
|
try {
|
|
1563
2500
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_grants'").get();
|
|
1564
2501
|
if (!table) throw new SystemError("hub-unavailable", "the hub registry is not initialized");
|
|
@@ -1608,7 +2545,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1608
2545
|
note: "the local hub has no reachable loopback or HTTPS destination"
|
|
1609
2546
|
};
|
|
1610
2547
|
}
|
|
1611
|
-
if (!(0,
|
|
2548
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, UNIT_NAMES.hub))) {
|
|
1612
2549
|
return { paired: false, awaiting: true, hubWasActive: false, note: "the local hub unit is not installed" };
|
|
1613
2550
|
}
|
|
1614
2551
|
const hubWasActive = systemdActive(host, UNIT_NAMES.hub);
|
|
@@ -1621,7 +2558,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1621
2558
|
destinations: [{ url: destination.url, token: registered.token, sharing: { ...SYSTEM_DEFAULT_SHARING } }]
|
|
1622
2559
|
};
|
|
1623
2560
|
writeCollectorIdentity(collectorStatePath(paths), adopted);
|
|
1624
|
-
chownAccount([(0,
|
|
2561
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1625
2562
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1626
2563
|
}
|
|
1627
2564
|
}
|
|
@@ -1629,7 +2566,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1629
2566
|
try {
|
|
1630
2567
|
grant = createLocalEnrollmentGrant(paths, host);
|
|
1631
2568
|
} catch (error) {
|
|
1632
|
-
chownAccount([(0,
|
|
2569
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1633
2570
|
return {
|
|
1634
2571
|
paired: false,
|
|
1635
2572
|
awaiting: true,
|
|
@@ -1647,18 +2584,18 @@ async function pairCollectorWithHub(options) {
|
|
|
1647
2584
|
note: `enrollment through the live hub failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1648
2585
|
};
|
|
1649
2586
|
} finally {
|
|
1650
|
-
chownAccount([(0,
|
|
2587
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1651
2588
|
}
|
|
1652
2589
|
if (!collectorConfigured(paths).configured) {
|
|
1653
2590
|
return { paired: false, awaiting: true, hubWasActive, note: "the hub accepted enrollment but no destination was saved" };
|
|
1654
2591
|
}
|
|
1655
|
-
chownAccount([(0,
|
|
2592
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1656
2593
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1657
2594
|
}
|
|
1658
2595
|
const identity = {
|
|
1659
|
-
collectorId: existing?.collectorId ?? (0,
|
|
1660
|
-
machineId: (0,
|
|
1661
|
-
token: (0,
|
|
2596
|
+
collectorId: existing?.collectorId ?? (0, import_node_crypto7.randomUUID)(),
|
|
2597
|
+
machineId: (0, import_node_crypto7.randomUUID)(),
|
|
2598
|
+
token: (0, import_node_crypto7.randomBytes)(32).toString("base64url")
|
|
1662
2599
|
};
|
|
1663
2600
|
const registry = registerCollectorInHubDatabase(paths, identity, host);
|
|
1664
2601
|
let token = identity.token;
|
|
@@ -1685,7 +2622,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1685
2622
|
}
|
|
1686
2623
|
];
|
|
1687
2624
|
writeCollectorIdentity(collectorStatePath(paths), next);
|
|
1688
|
-
chownAccount([(0,
|
|
2625
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1689
2626
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1690
2627
|
}
|
|
1691
2628
|
function unitCommon(name, description, paths, account, envPath, execStart, extraService = []) {
|
|
@@ -1702,7 +2639,7 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
|
|
|
1702
2639
|
"Type=simple",
|
|
1703
2640
|
`User=${account}`,
|
|
1704
2641
|
`Group=${account}`,
|
|
1705
|
-
`WorkingDirectory=${(0,
|
|
2642
|
+
`WorkingDirectory=${(0, import_node_path6.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
|
|
1706
2643
|
`EnvironmentFile=${envPath.replace(/%/g, "%%")}`,
|
|
1707
2644
|
"Environment=NODE_ENV=production",
|
|
1708
2645
|
"Restart=on-failure",
|
|
@@ -1737,13 +2674,13 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
|
|
|
1737
2674
|
}
|
|
1738
2675
|
function renderSystemHubUnit(options) {
|
|
1739
2676
|
const { paths, accounts } = options;
|
|
1740
|
-
const hubScript = (0,
|
|
2677
|
+
const hubScript = (0, import_node_path6.join)(paths.prefix, "current", "dist", "hub.cjs");
|
|
1741
2678
|
const extra = [
|
|
1742
2679
|
`Environment=${systemdQuote(`HUB_DATABASE_PATH=${options.database}`)}`,
|
|
1743
2680
|
`Environment=${systemdQuote(`HUB_STATE_FILE=${options.enrollment}`)}`,
|
|
1744
2681
|
...options.remote ? [`Environment=${systemdQuote(`HUB_SOCKET_PATH=${options.socket}`)}`] : [],
|
|
1745
|
-
`ReadWritePaths=${systemdQuote((0,
|
|
1746
|
-
`ReadOnlyPaths=${systemdQuote((0,
|
|
2682
|
+
`ReadWritePaths=${systemdQuote((0, import_node_path6.join)(paths.stateDir, "hub"))}`,
|
|
2683
|
+
`ReadOnlyPaths=${systemdQuote((0, import_node_path6.join)(paths.prefix, "current"))}`,
|
|
1747
2684
|
"PrivateDevices=yes",
|
|
1748
2685
|
"TimeoutStopSec=15s"
|
|
1749
2686
|
];
|
|
@@ -1762,14 +2699,14 @@ function renderSystemHubUnit(options) {
|
|
|
1762
2699
|
}
|
|
1763
2700
|
function renderSystemCollectorUnit(options) {
|
|
1764
2701
|
const { paths, accounts } = options;
|
|
1765
|
-
const collectorScript = (0,
|
|
2702
|
+
const collectorScript = (0, import_node_path6.join)(paths.prefix, "current", "dist", "collector.cjs");
|
|
1766
2703
|
const extra = [
|
|
1767
2704
|
...options.powerAccess ? [`SupplementaryGroups=${accounts.powerGroup}`] : [],
|
|
1768
2705
|
`Environment=${systemdQuote(`COLLECTOR_MODE=real`)}`,
|
|
1769
2706
|
`Environment=${systemdQuote(`COLLECTOR_STATE_FILE=${options.collectorState}`)}`,
|
|
1770
|
-
`Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0,
|
|
1771
|
-
`ReadWritePaths=${systemdQuote((0,
|
|
1772
|
-
`ReadOnlyPaths=${systemdQuote((0,
|
|
2707
|
+
`Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0, import_node_path6.join)(paths.stateDir, "collector", "health.json")}`)}`,
|
|
2708
|
+
`ReadWritePaths=${systemdQuote((0, import_node_path6.join)(paths.stateDir, "collector"))}`,
|
|
2709
|
+
`ReadOnlyPaths=${systemdQuote((0, import_node_path6.join)(paths.prefix, "current"))}`,
|
|
1773
2710
|
"TimeoutStopSec=10s"
|
|
1774
2711
|
];
|
|
1775
2712
|
return [
|
|
@@ -1842,11 +2779,15 @@ function collectorEnvTemplate() {
|
|
|
1842
2779
|
"# COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect",
|
|
1843
2780
|
"# COLLECTOR_ENROLL_TOKEN=<one-use grant>",
|
|
1844
2781
|
"# COLLECTOR_MODE=real",
|
|
2782
|
+
"# Unacknowledged samples wait in RAM by default: no queue is written to disk",
|
|
2783
|
+
"# and a collector restart loses what was never acknowledged. Set this to durable",
|
|
2784
|
+
"# to keep them in a per-hub SQLite outbox across collector restarts.",
|
|
2785
|
+
"# COLLECTOR_QUEUE_MODE=durable",
|
|
1845
2786
|
""
|
|
1846
2787
|
].join("\n");
|
|
1847
2788
|
}
|
|
1848
2789
|
function assertPrivateSourceFile(path, label) {
|
|
1849
|
-
const stat = (0,
|
|
2790
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1850
2791
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1851
2792
|
throw new SystemError("unsafe-adoption", `${label} must be a regular file: ${path}`);
|
|
1852
2793
|
}
|
|
@@ -1857,7 +2798,7 @@ function assertPrivateSourceFile(path, label) {
|
|
|
1857
2798
|
function readJsonObject(path) {
|
|
1858
2799
|
let raw;
|
|
1859
2800
|
try {
|
|
1860
|
-
raw = (0,
|
|
2801
|
+
raw = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
1861
2802
|
} catch (error) {
|
|
1862
2803
|
throw new SystemError("missing-file", `cannot read ${path}: ${error.message}`);
|
|
1863
2804
|
}
|
|
@@ -1880,8 +2821,8 @@ function isCanonicalComponents(value) {
|
|
|
1880
2821
|
}
|
|
1881
2822
|
function readSystemState(paths) {
|
|
1882
2823
|
const path = stateFilePath(paths);
|
|
1883
|
-
if (!(0,
|
|
1884
|
-
const stat = (0,
|
|
2824
|
+
if (!(0, import_node_fs6.existsSync)(path)) return null;
|
|
2825
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1885
2826
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
|
|
1886
2827
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be 0600`);
|
|
1887
2828
|
const raw = readJsonObject(path);
|
|
@@ -1913,7 +2854,7 @@ function readSystemState(paths) {
|
|
|
1913
2854
|
}
|
|
1914
2855
|
function systemStateIsLegacy(paths) {
|
|
1915
2856
|
const path = stateFilePath(paths);
|
|
1916
|
-
if (!(0,
|
|
2857
|
+
if (!(0, import_node_fs6.existsSync)(path)) return false;
|
|
1917
2858
|
let raw;
|
|
1918
2859
|
try {
|
|
1919
2860
|
raw = readJsonObject(path);
|
|
@@ -1964,24 +2905,24 @@ function validateVersion(value) {
|
|
|
1964
2905
|
return value;
|
|
1965
2906
|
}
|
|
1966
2907
|
function releaseVersion(root) {
|
|
1967
|
-
const metadata = readJsonObject((0,
|
|
2908
|
+
const metadata = readJsonObject((0, import_node_path6.join)(root, "package.json"));
|
|
1968
2909
|
if (Object.hasOwn(metadata, "rigscopeConfigVersion")) {
|
|
1969
2910
|
throw new SystemError("invalid-release", "release uses unfinished configuration metadata; select a canonical release");
|
|
1970
2911
|
}
|
|
1971
2912
|
return validateVersion(metadata.version);
|
|
1972
2913
|
}
|
|
1973
2914
|
function releaseSchemaVersion(root) {
|
|
1974
|
-
const manifestPath = (0,
|
|
1975
|
-
if (!(0,
|
|
2915
|
+
const manifestPath = (0, import_node_path6.join)(root, "manifest.json");
|
|
2916
|
+
if (!(0, import_node_fs6.existsSync)(manifestPath)) return null;
|
|
1976
2917
|
const manifest = readJsonObject(manifestPath);
|
|
1977
2918
|
return Number.isSafeInteger(manifest.schemaVersion) ? Number(manifest.schemaVersion) : null;
|
|
1978
2919
|
}
|
|
1979
2920
|
function assertReleaseArtifacts(root) {
|
|
1980
2921
|
for (const artifact of REQUIRED_ARTIFACTS) {
|
|
1981
|
-
const path = (0,
|
|
2922
|
+
const path = (0, import_node_path6.join)(root, artifact);
|
|
1982
2923
|
let stat;
|
|
1983
2924
|
try {
|
|
1984
|
-
stat = (0,
|
|
2925
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
1985
2926
|
} catch {
|
|
1986
2927
|
throw new SystemError("missing-artifact", `release is missing ${artifact}`);
|
|
1987
2928
|
}
|
|
@@ -1989,12 +2930,12 @@ function assertReleaseArtifacts(root) {
|
|
|
1989
2930
|
throw new SystemError("invalid-artifact", `release artifact is not a non-empty regular file: ${artifact}`);
|
|
1990
2931
|
}
|
|
1991
2932
|
}
|
|
1992
|
-
const buildId = (0,
|
|
2933
|
+
const buildId = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, ".next", "BUILD_ID"), "utf8").trim();
|
|
1993
2934
|
if (buildId === "" || /\s/.test(buildId)) throw new SystemError("invalid-artifact", ".next/BUILD_ID is invalid");
|
|
1994
2935
|
for (const directory of [".next/server", ".next/static"]) {
|
|
1995
2936
|
let stat;
|
|
1996
2937
|
try {
|
|
1997
|
-
stat = (0,
|
|
2938
|
+
stat = (0, import_node_fs6.lstatSync)((0, import_node_path6.join)(root, directory));
|
|
1998
2939
|
} catch {
|
|
1999
2940
|
throw new SystemError("missing-artifact", `release is missing ${directory}/`);
|
|
2000
2941
|
}
|
|
@@ -2002,10 +2943,10 @@ function assertReleaseArtifacts(root) {
|
|
|
2002
2943
|
}
|
|
2003
2944
|
}
|
|
2004
2945
|
function verifyReleaseManifest(root) {
|
|
2005
|
-
const manifestPath = (0,
|
|
2006
|
-
if (!(0,
|
|
2946
|
+
const manifestPath = (0, import_node_path6.join)(root, "manifest.json");
|
|
2947
|
+
if (!(0, import_node_fs6.existsSync)(manifestPath)) return;
|
|
2007
2948
|
const manifest = readJsonObject(manifestPath);
|
|
2008
|
-
const packageJson = readJsonObject((0,
|
|
2949
|
+
const packageJson = readJsonObject((0, import_node_path6.join)(root, "package.json"));
|
|
2009
2950
|
if (manifest.name !== void 0 && manifest.name !== packageJson.name) {
|
|
2010
2951
|
throw new SystemError("manifest-mismatch", "release manifest name does not match package.json");
|
|
2011
2952
|
}
|
|
@@ -2024,17 +2965,17 @@ function verifyReleaseManifest(root) {
|
|
|
2024
2965
|
if (typeof relativePath !== "string" || relativePath === "" || relativePath.startsWith("/") || relativePath.includes("\\") || relativePath.split("/").includes("..")) {
|
|
2025
2966
|
throw new SystemError("invalid-manifest", `release manifest contains an unsafe path: ${String(relativePath)}`);
|
|
2026
2967
|
}
|
|
2027
|
-
const absolute = (0,
|
|
2028
|
-
const stat = (0,
|
|
2968
|
+
const absolute = (0, import_node_path6.join)(root, relativePath);
|
|
2969
|
+
const stat = (0, import_node_fs6.lstatSync)(absolute);
|
|
2029
2970
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("invalid-manifest", `release artifact is not a regular file: ${relativePath}`);
|
|
2030
2971
|
if (entry.bytes !== stat.size) throw new SystemError("artifact-mismatch", `release artifact size changed: ${relativePath}`);
|
|
2031
|
-
if (entry.sha256 !==
|
|
2972
|
+
if (entry.sha256 !== sha256File2(absolute)) throw new SystemError("artifact-mismatch", `release artifact hash changed: ${relativePath}`);
|
|
2032
2973
|
listed.add(relativePath);
|
|
2033
2974
|
}
|
|
2034
2975
|
const visit = (current) => {
|
|
2035
|
-
for (const entry of (0,
|
|
2036
|
-
const absolute = (0,
|
|
2037
|
-
const rel = (0,
|
|
2976
|
+
for (const entry of (0, import_node_fs6.readdirSync)(current, { withFileTypes: true })) {
|
|
2977
|
+
const absolute = (0, import_node_path6.join)(current, entry.name);
|
|
2978
|
+
const rel = (0, import_node_path6.relative)(root, absolute).split(import_node_path6.sep).join("/");
|
|
2038
2979
|
if (rel === "node_modules" || rel.startsWith("node_modules/") || rel === ".next/cache" || rel.startsWith(".next/cache/")) continue;
|
|
2039
2980
|
if (entry.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${rel}`);
|
|
2040
2981
|
if (entry.isDirectory()) {
|
|
@@ -2049,49 +2990,49 @@ function verifyReleaseManifest(root) {
|
|
|
2049
2990
|
}
|
|
2050
2991
|
function assertNoSymlinks(root) {
|
|
2051
2992
|
const visit = (path) => {
|
|
2052
|
-
const stat = (0,
|
|
2053
|
-
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${(0,
|
|
2993
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
2994
|
+
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${(0, import_node_path6.relative)(root, path)}`);
|
|
2054
2995
|
if (!stat.isDirectory()) return;
|
|
2055
|
-
for (const entry of (0,
|
|
2996
|
+
for (const entry of (0, import_node_fs6.readdirSync)(path)) visit((0, import_node_path6.join)(path, entry));
|
|
2056
2997
|
};
|
|
2057
2998
|
visit(root);
|
|
2058
2999
|
}
|
|
2059
3000
|
function copyReleaseTree(source, destination) {
|
|
2060
3001
|
const visit = (current, target) => {
|
|
2061
|
-
const stat = (0,
|
|
2062
|
-
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${(0,
|
|
3002
|
+
const stat = (0, import_node_fs6.lstatSync)(current);
|
|
3003
|
+
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${(0, import_node_path6.relative)(source, current)}`);
|
|
2063
3004
|
if (stat.isDirectory()) {
|
|
2064
|
-
(0,
|
|
2065
|
-
for (const entry of (0,
|
|
2066
|
-
const child = (0,
|
|
2067
|
-
const rel = (0,
|
|
3005
|
+
(0, import_node_fs6.mkdirSync)(target, { recursive: true, mode: 493 });
|
|
3006
|
+
for (const entry of (0, import_node_fs6.readdirSync)(current)) {
|
|
3007
|
+
const child = (0, import_node_path6.join)(current, entry);
|
|
3008
|
+
const rel = (0, import_node_path6.relative)(source, child);
|
|
2068
3009
|
if (rel === ".git" || rel === ".next-dev" || rel === ".next/cache" || rel === "node_modules" || rel.startsWith("node_modules/")) {
|
|
2069
3010
|
continue;
|
|
2070
3011
|
}
|
|
2071
|
-
visit(child, (0,
|
|
3012
|
+
visit(child, (0, import_node_path6.join)(target, entry));
|
|
2072
3013
|
}
|
|
2073
3014
|
return;
|
|
2074
3015
|
}
|
|
2075
|
-
if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0,
|
|
2076
|
-
(0,
|
|
2077
|
-
(0,
|
|
2078
|
-
(0,
|
|
3016
|
+
if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0, import_node_path6.relative)(source, current)}`);
|
|
3017
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(target), { recursive: true, mode: 493 });
|
|
3018
|
+
(0, import_node_fs6.copyFileSync)(current, target);
|
|
3019
|
+
(0, import_node_fs6.chmodSync)(target, 420);
|
|
2079
3020
|
};
|
|
2080
3021
|
visit(source, destination);
|
|
2081
3022
|
}
|
|
2082
3023
|
function copyReleaseAllowlisted(source, destination) {
|
|
2083
|
-
for (const entry of (0,
|
|
3024
|
+
for (const entry of (0, import_node_fs6.readdirSync)(source)) {
|
|
2084
3025
|
if (!PACKAGE_TOP_LEVEL.has(entry)) continue;
|
|
2085
3026
|
if (entry === "node_modules") continue;
|
|
2086
|
-
const child = (0,
|
|
2087
|
-
const target = (0,
|
|
2088
|
-
const stat = (0,
|
|
3027
|
+
const child = (0, import_node_path6.join)(source, entry);
|
|
3028
|
+
const target = (0, import_node_path6.join)(destination, entry);
|
|
3029
|
+
const stat = (0, import_node_fs6.lstatSync)(child);
|
|
2089
3030
|
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${entry}`);
|
|
2090
3031
|
if (stat.isDirectory()) {
|
|
2091
3032
|
copyReleaseTree(child, target);
|
|
2092
3033
|
} else if (stat.isFile()) {
|
|
2093
|
-
(0,
|
|
2094
|
-
(0,
|
|
3034
|
+
(0, import_node_fs6.copyFileSync)(child, target);
|
|
3035
|
+
(0, import_node_fs6.chmodSync)(target, 420);
|
|
2095
3036
|
}
|
|
2096
3037
|
}
|
|
2097
3038
|
}
|
|
@@ -2126,28 +3067,28 @@ function extractArchive(archive, stage, host) {
|
|
|
2126
3067
|
}
|
|
2127
3068
|
function discardStagedRelease(staged) {
|
|
2128
3069
|
if (staged === null) return;
|
|
2129
|
-
(0,
|
|
3070
|
+
(0, import_node_fs6.rmSync)(staged.stageDir, { recursive: true, force: true });
|
|
2130
3071
|
}
|
|
2131
3072
|
function releaseTargetDir(paths, version) {
|
|
2132
|
-
return (0,
|
|
3073
|
+
return (0, import_node_path6.join)(paths.prefix, "releases", version);
|
|
2133
3074
|
}
|
|
2134
3075
|
function assertReleaseTargetAvailable(paths, releaseDir, replaceRelease) {
|
|
2135
|
-
if (!(0,
|
|
3076
|
+
if (!(0, import_node_fs6.existsSync)(releaseDir)) return;
|
|
2136
3077
|
const current = currentReleaseDir(paths);
|
|
2137
3078
|
const previous = previousReleaseDir(paths);
|
|
2138
3079
|
if (!replaceRelease || current === releaseDir || previous === releaseDir) {
|
|
2139
3080
|
throw new SystemError(
|
|
2140
3081
|
"release-exists",
|
|
2141
|
-
`release ${(0,
|
|
3082
|
+
`release ${(0, import_node_path6.basename)(releaseDir)} is already installed${current === releaseDir || previous === releaseDir ? " or retained" : ""}; choose a new version`
|
|
2142
3083
|
);
|
|
2143
3084
|
}
|
|
2144
3085
|
}
|
|
2145
3086
|
function stageRelease(options) {
|
|
2146
|
-
const source = (0,
|
|
2147
|
-
const stat = (0,
|
|
3087
|
+
const source = (0, import_node_fs6.realpathSync)((0, import_node_path6.resolve)(options.source));
|
|
3088
|
+
const stat = (0, import_node_fs6.lstatSync)(source);
|
|
2148
3089
|
ensureDirectory(options.paths.prefix, 493);
|
|
2149
|
-
const stageDir = (0,
|
|
2150
|
-
(0,
|
|
3090
|
+
const stageDir = (0, import_node_path6.join)(options.paths.prefix, `.stage-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
3091
|
+
(0, import_node_fs6.mkdirSync)(stageDir, { recursive: true, mode: 448 });
|
|
2151
3092
|
try {
|
|
2152
3093
|
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
2153
3094
|
copyReleaseAllowlisted(source, stageDir);
|
|
@@ -2165,7 +3106,7 @@ function stageRelease(options) {
|
|
|
2165
3106
|
artifacts: [...REQUIRED_ARTIFACTS]
|
|
2166
3107
|
};
|
|
2167
3108
|
} catch (error) {
|
|
2168
|
-
(0,
|
|
3109
|
+
(0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
|
|
2169
3110
|
throw error;
|
|
2170
3111
|
}
|
|
2171
3112
|
}
|
|
@@ -2191,13 +3132,13 @@ function prepareStagedRelease(options) {
|
|
|
2191
3132
|
}
|
|
2192
3133
|
}
|
|
2193
3134
|
function commitStagedRelease(options) {
|
|
2194
|
-
const releasesDir = (0,
|
|
3135
|
+
const releasesDir = (0, import_node_path6.join)(options.paths.prefix, "releases");
|
|
2195
3136
|
ensureDirectory(releasesDir, 493);
|
|
2196
3137
|
const releaseDir = releaseTargetDir(options.paths, options.staged.version);
|
|
2197
3138
|
assertReleaseTargetAvailable(options.paths, releaseDir, options.replaceRelease);
|
|
2198
|
-
if ((0,
|
|
2199
|
-
(0,
|
|
2200
|
-
ensureDirectory((0,
|
|
3139
|
+
if ((0, import_node_fs6.existsSync)(releaseDir)) (0, import_node_fs6.rmSync)(releaseDir, { recursive: true, force: true });
|
|
3140
|
+
(0, import_node_fs6.renameSync)(options.staged.stageDir, releaseDir);
|
|
3141
|
+
ensureDirectory((0, import_node_path6.join)(releaseDir, ".next", "cache"), 493);
|
|
2201
3142
|
return {
|
|
2202
3143
|
version: options.staged.version,
|
|
2203
3144
|
releaseDir,
|
|
@@ -2206,20 +3147,20 @@ function commitStagedRelease(options) {
|
|
|
2206
3147
|
};
|
|
2207
3148
|
}
|
|
2208
3149
|
function dependencyNames(root) {
|
|
2209
|
-
const pkg = readJsonObject((0,
|
|
3150
|
+
const pkg = readJsonObject((0, import_node_path6.join)(root, "package.json"));
|
|
2210
3151
|
const dependencies = pkg.dependencies;
|
|
2211
3152
|
if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) return [];
|
|
2212
3153
|
return Object.keys(dependencies).filter((name) => /^[A-Za-z0-9@/._-]+$/.test(name));
|
|
2213
3154
|
}
|
|
2214
3155
|
function missingDependencies(root) {
|
|
2215
|
-
return dependencyNames(root).filter((name) => !(0,
|
|
3156
|
+
return dependencyNames(root).filter((name) => !(0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, "node_modules", name, "package.json")));
|
|
2216
3157
|
}
|
|
2217
3158
|
function installDependencies(root, host, command = "npm") {
|
|
2218
|
-
const hasLock = (0,
|
|
3159
|
+
const hasLock = (0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, "package-lock.json"));
|
|
2219
3160
|
const args = hasLock ? ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
2220
|
-
const siblingNpm = (0,
|
|
2221
|
-
const executable = command === "npm" && (0,
|
|
2222
|
-
const nodeBin = (0,
|
|
3161
|
+
const siblingNpm = (0, import_node_path6.join)((0, import_node_path6.dirname)(process.execPath), "npm");
|
|
3162
|
+
const executable = command === "npm" && (0, import_node_fs6.existsSync)(siblingNpm) ? siblingNpm : command;
|
|
3163
|
+
const nodeBin = (0, import_node_path6.dirname)(process.execPath);
|
|
2223
3164
|
const path = [nodeBin, process.env.PATH ?? ""].filter(Boolean).join(":");
|
|
2224
3165
|
runRequired(host, executable, args, "production dependency installation", {
|
|
2225
3166
|
cwd: root,
|
|
@@ -2228,21 +3169,21 @@ function installDependencies(root, host, command = "npm") {
|
|
|
2228
3169
|
});
|
|
2229
3170
|
}
|
|
2230
3171
|
function currentReleaseDir(paths) {
|
|
2231
|
-
const link = (0,
|
|
3172
|
+
const link = (0, import_node_path6.join)(paths.prefix, "current");
|
|
2232
3173
|
try {
|
|
2233
|
-
const target = (0,
|
|
2234
|
-
const absolute = (0,
|
|
2235
|
-
return isWithin((0,
|
|
3174
|
+
const target = (0, import_node_fs6.readlinkSync)(link);
|
|
3175
|
+
const absolute = (0, import_node_path6.isAbsolute)(target) ? target : (0, import_node_path6.resolve)(paths.prefix, target);
|
|
3176
|
+
return isWithin((0, import_node_path6.join)(paths.prefix, "releases"), absolute) ? absolute : null;
|
|
2236
3177
|
} catch {
|
|
2237
3178
|
return null;
|
|
2238
3179
|
}
|
|
2239
3180
|
}
|
|
2240
3181
|
function previousReleaseDir(paths) {
|
|
2241
|
-
const link = (0,
|
|
3182
|
+
const link = (0, import_node_path6.join)(paths.prefix, "previous");
|
|
2242
3183
|
try {
|
|
2243
|
-
const target = (0,
|
|
2244
|
-
const absolute = (0,
|
|
2245
|
-
return isWithin((0,
|
|
3184
|
+
const target = (0, import_node_fs6.readlinkSync)(link);
|
|
3185
|
+
const absolute = (0, import_node_path6.isAbsolute)(target) ? target : (0, import_node_path6.resolve)(paths.prefix, target);
|
|
3186
|
+
return isWithin((0, import_node_path6.join)(paths.prefix, "releases"), absolute) ? absolute : null;
|
|
2246
3187
|
} catch {
|
|
2247
3188
|
return null;
|
|
2248
3189
|
}
|
|
@@ -2250,11 +3191,11 @@ function previousReleaseDir(paths) {
|
|
|
2250
3191
|
function replaceSymlink(link, target, host) {
|
|
2251
3192
|
const temporary = `${link}.tmp-${process.pid}-${Date.now()}`;
|
|
2252
3193
|
try {
|
|
2253
|
-
(0,
|
|
2254
|
-
(0,
|
|
3194
|
+
(0, import_node_fs6.symlinkSync)(target, temporary);
|
|
3195
|
+
(0, import_node_fs6.renameSync)(temporary, link);
|
|
2255
3196
|
} catch (error) {
|
|
2256
3197
|
try {
|
|
2257
|
-
(0,
|
|
3198
|
+
(0, import_node_fs6.unlinkSync)(temporary);
|
|
2258
3199
|
} catch {
|
|
2259
3200
|
}
|
|
2260
3201
|
throw error;
|
|
@@ -2263,8 +3204,8 @@ function replaceSymlink(link, target, host) {
|
|
|
2263
3204
|
}
|
|
2264
3205
|
function removeSymlink(link) {
|
|
2265
3206
|
try {
|
|
2266
|
-
const stat = (0,
|
|
2267
|
-
if (stat.isSymbolicLink()) (0,
|
|
3207
|
+
const stat = (0, import_node_fs6.lstatSync)(link);
|
|
3208
|
+
if (stat.isSymbolicLink()) (0, import_node_fs6.unlinkSync)(link);
|
|
2268
3209
|
} catch {
|
|
2269
3210
|
}
|
|
2270
3211
|
}
|
|
@@ -2277,18 +3218,18 @@ function nodeVersion(host, nodePath) {
|
|
|
2277
3218
|
return output;
|
|
2278
3219
|
}
|
|
2279
3220
|
function ensureRootOwnedNode(options) {
|
|
2280
|
-
const selected = (0,
|
|
2281
|
-
const real = (0,
|
|
3221
|
+
const selected = (0, import_node_path6.resolve)(options.nodePath ?? process.execPath);
|
|
3222
|
+
const real = (0, import_node_fs6.realpathSync)(selected);
|
|
2282
3223
|
nodeVersion(options.host, real);
|
|
2283
|
-
const stat = (0,
|
|
2284
|
-
const insideRelease = isWithin((0,
|
|
3224
|
+
const stat = (0, import_node_fs6.statSync)(real);
|
|
3225
|
+
const insideRelease = isWithin((0, import_node_path6.join)(options.paths.prefix, "releases"), real);
|
|
2285
3226
|
const unsafe = stat.uid !== 0 || (stat.mode & 18) !== 0 || options.copyNode === true || insideRelease;
|
|
2286
3227
|
if (!unsafe || options.trustedNode === true) return real;
|
|
2287
|
-
const target = (0,
|
|
3228
|
+
const target = (0, import_node_path6.join)(options.paths.prefix, "node", "node");
|
|
2288
3229
|
if (!options.apply) return target;
|
|
2289
|
-
ensureDirectory((0,
|
|
2290
|
-
(0,
|
|
2291
|
-
(0,
|
|
3230
|
+
ensureDirectory((0, import_node_path6.join)(options.paths.prefix, "node"), 493);
|
|
3231
|
+
(0, import_node_fs6.copyFileSync)(real, target);
|
|
3232
|
+
(0, import_node_fs6.chmodSync)(target, 493);
|
|
2292
3233
|
runRequired(options.host, "chown", ["root:root", target], "node ownership");
|
|
2293
3234
|
nodeVersion(options.host, target);
|
|
2294
3235
|
return target;
|
|
@@ -2317,8 +3258,8 @@ function validatedOrigin(value) {
|
|
|
2317
3258
|
return value;
|
|
2318
3259
|
}
|
|
2319
3260
|
function readAdoptionSetup(source) {
|
|
2320
|
-
const configPath = (0,
|
|
2321
|
-
if (!(0,
|
|
3261
|
+
const configPath = (0, import_node_path6.join)(source, "config.json");
|
|
3262
|
+
if (!(0, import_node_fs6.existsSync)(configPath)) {
|
|
2322
3263
|
throw new SystemError("invalid-adoption", "adopted installation has no config.json");
|
|
2323
3264
|
}
|
|
2324
3265
|
assertPrivateSourceFile(configPath, "adopted setup");
|
|
@@ -2366,35 +3307,35 @@ function detectUserInstallations(options = {}) {
|
|
|
2366
3307
|
const roots = options.homeRoots ?? ["/home", "/root"];
|
|
2367
3308
|
const found = [];
|
|
2368
3309
|
for (const homeRoot of roots) {
|
|
2369
|
-
if (!(0,
|
|
2370
|
-
const homes = homeRoot === "/root" ? ["/root"] : (0,
|
|
3310
|
+
if (!(0, import_node_fs6.existsSync)(homeRoot)) continue;
|
|
3311
|
+
const homes = homeRoot === "/root" ? ["/root"] : (0, import_node_fs6.readdirSync)(homeRoot).map((entry) => (0, import_node_path6.join)(homeRoot, entry));
|
|
2371
3312
|
for (const home of homes) {
|
|
2372
3313
|
let stat;
|
|
2373
3314
|
try {
|
|
2374
|
-
stat = (0,
|
|
3315
|
+
stat = (0, import_node_fs6.lstatSync)(home);
|
|
2375
3316
|
} catch {
|
|
2376
3317
|
continue;
|
|
2377
3318
|
}
|
|
2378
3319
|
if (!stat.isDirectory()) continue;
|
|
2379
|
-
const unitDir = (0,
|
|
3320
|
+
const unitDir = (0, import_node_path6.join)(home, ".config", "systemd", "user");
|
|
2380
3321
|
let units = [];
|
|
2381
3322
|
try {
|
|
2382
|
-
units = (0,
|
|
3323
|
+
units = (0, import_node_fs6.readdirSync)(unitDir);
|
|
2383
3324
|
} catch {
|
|
2384
3325
|
units = [];
|
|
2385
3326
|
}
|
|
2386
3327
|
for (const unitName of units) {
|
|
2387
3328
|
if (!/^rigscope(?:-[0-9a-f]{12})?(?:-(?:hub|collector))?\.service$/.test(unitName)) continue;
|
|
2388
|
-
const unitPath = (0,
|
|
3329
|
+
const unitPath = (0, import_node_path6.join)(unitDir, unitName);
|
|
2389
3330
|
let execStart = null;
|
|
2390
3331
|
try {
|
|
2391
|
-
const line = (0,
|
|
3332
|
+
const line = (0, import_node_fs6.readFileSync)(unitPath, "utf8").split("\n").find((item) => item.startsWith("ExecStart="));
|
|
2392
3333
|
execStart = line ? line.slice("ExecStart=".length) : null;
|
|
2393
3334
|
} catch {
|
|
2394
3335
|
}
|
|
2395
|
-
const stateCandidates = [(0,
|
|
2396
|
-
const stateDir = stateCandidates.find((candidate) => (0,
|
|
2397
|
-
found.push({ user: (0,
|
|
3336
|
+
const stateCandidates = [(0, import_node_path6.join)(home, ".local", "state", "rigscope")];
|
|
3337
|
+
const stateDir = stateCandidates.find((candidate) => (0, import_node_fs6.existsSync)(candidate)) ?? null;
|
|
3338
|
+
found.push({ user: (0, import_node_path6.basename)(home), home, unitPath, unitName, execStart, stateDir });
|
|
2398
3339
|
}
|
|
2399
3340
|
}
|
|
2400
3341
|
}
|
|
@@ -2402,9 +3343,9 @@ function detectUserInstallations(options = {}) {
|
|
|
2402
3343
|
}
|
|
2403
3344
|
function lockIsLive(path) {
|
|
2404
3345
|
try {
|
|
2405
|
-
const stat = (0,
|
|
3346
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
2406
3347
|
if (!stat.isFile() || stat.size > 4096) return { exists: true, live: false, pid: null, ambiguous: true };
|
|
2407
|
-
const owner = JSON.parse((0,
|
|
3348
|
+
const owner = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
2408
3349
|
const pid = owner.pid;
|
|
2409
3350
|
if (!Number.isSafeInteger(pid) || pid < 1) return { exists: true, live: false, pid: null, ambiguous: true };
|
|
2410
3351
|
try {
|
|
@@ -2422,9 +3363,9 @@ function lockIsLive(path) {
|
|
|
2422
3363
|
}
|
|
2423
3364
|
function directoryLocks(directory) {
|
|
2424
3365
|
return [
|
|
2425
|
-
(0,
|
|
2426
|
-
(0,
|
|
2427
|
-
(0,
|
|
3366
|
+
(0, import_node_path6.join)(directory, "hub", "telemetry.sqlite.hub.lock"),
|
|
3367
|
+
(0, import_node_path6.join)(directory, "run", "rigscope.lock"),
|
|
3368
|
+
(0, import_node_path6.join)(directory, "collector", "collector-runtime.lock")
|
|
2428
3369
|
];
|
|
2429
3370
|
}
|
|
2430
3371
|
function assertDirectoryStopped(directory, scope = "local") {
|
|
@@ -2455,13 +3396,13 @@ function assertSystemStartAllowed(paths, components) {
|
|
|
2455
3396
|
}
|
|
2456
3397
|
function detectPowerDriver(moduleRoot = "/sys/module") {
|
|
2457
3398
|
const candidates = ["intel_rapl_msr", "intel_rapl_common", "intel_rapl_tpmi", "intel_rapl", "amd_energy"];
|
|
2458
|
-
return candidates.filter((name) => (0,
|
|
3399
|
+
return candidates.filter((name) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(moduleRoot, name)));
|
|
2459
3400
|
}
|
|
2460
3401
|
function readPasswd(path = "/etc/passwd") {
|
|
2461
3402
|
const users = /* @__PURE__ */ new Map();
|
|
2462
3403
|
let lines;
|
|
2463
3404
|
try {
|
|
2464
|
-
lines = (0,
|
|
3405
|
+
lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
|
|
2465
3406
|
} catch {
|
|
2466
3407
|
return users;
|
|
2467
3408
|
}
|
|
@@ -2478,7 +3419,7 @@ function readGroups(path = "/etc/group") {
|
|
|
2478
3419
|
const groups = /* @__PURE__ */ new Map();
|
|
2479
3420
|
let lines;
|
|
2480
3421
|
try {
|
|
2481
|
-
lines = (0,
|
|
3422
|
+
lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
|
|
2482
3423
|
} catch {
|
|
2483
3424
|
return groups;
|
|
2484
3425
|
}
|
|
@@ -2506,7 +3447,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2506
3447
|
const sysusers = options.sysusersPath ?? powerSysusersPath(paths);
|
|
2507
3448
|
let ruleText = null;
|
|
2508
3449
|
try {
|
|
2509
|
-
ruleText = (0,
|
|
3450
|
+
ruleText = (0, import_node_fs6.readFileSync)(rulePath, "utf8");
|
|
2510
3451
|
} catch {
|
|
2511
3452
|
ruleText = null;
|
|
2512
3453
|
}
|
|
@@ -2523,27 +3464,27 @@ function inspectPowerAccess(options = {}) {
|
|
|
2523
3464
|
const domains = [];
|
|
2524
3465
|
let entries = [];
|
|
2525
3466
|
try {
|
|
2526
|
-
entries = (0,
|
|
3467
|
+
entries = (0, import_node_fs6.readdirSync)(paths.powercapRoot);
|
|
2527
3468
|
} catch {
|
|
2528
3469
|
entries = [];
|
|
2529
3470
|
}
|
|
2530
3471
|
for (const entry of entries.filter((name) => /^intel-rapl:\d+$/.test(name)).sort()) {
|
|
2531
|
-
const domainPath = (0,
|
|
3472
|
+
const domainPath = (0, import_node_path6.join)(paths.powercapRoot, entry);
|
|
2532
3473
|
let name = entry;
|
|
2533
3474
|
try {
|
|
2534
|
-
name = (0,
|
|
3475
|
+
name = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(domainPath, "name"), "utf8").trim() || entry;
|
|
2535
3476
|
} catch {
|
|
2536
3477
|
}
|
|
2537
3478
|
if (!/^package/.test(name)) continue;
|
|
2538
|
-
const energyPath = (0,
|
|
2539
|
-
const maxRangePath = (0,
|
|
3479
|
+
const energyPath = (0, import_node_path6.join)(domainPath, "energy_uj");
|
|
3480
|
+
const maxRangePath = (0, import_node_path6.join)(domainPath, "max_energy_range_uj");
|
|
2540
3481
|
const readStat = (path) => {
|
|
2541
3482
|
if (options.statOverride) {
|
|
2542
3483
|
const override = options.statOverride(path);
|
|
2543
3484
|
return override === null ? null : { mode: modeString(override.mode), uid: override.uid, gid: override.gid };
|
|
2544
3485
|
}
|
|
2545
3486
|
try {
|
|
2546
|
-
const stat = (0,
|
|
3487
|
+
const stat = (0, import_node_fs6.statSync)(path);
|
|
2547
3488
|
return { mode: modeString(stat.mode), uid: stat.uid, gid: stat.gid };
|
|
2548
3489
|
} catch {
|
|
2549
3490
|
return null;
|
|
@@ -2577,7 +3518,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2577
3518
|
problems.push(`collector account ${accounts.collector} is not a member of ${accounts.powerGroup}`);
|
|
2578
3519
|
}
|
|
2579
3520
|
if (!compliant) problems.push(`persistent udev rule is missing or not compliant: ${rulePath}`);
|
|
2580
|
-
if (!(0,
|
|
3521
|
+
if (!(0, import_node_fs6.existsSync)(sysusers)) problems.push(`power sysusers definition is missing: ${sysusers}`);
|
|
2581
3522
|
for (const domain of domains) {
|
|
2582
3523
|
if (!domain.energyExists) problems.push(`${domain.name}: energy_uj is unavailable`);
|
|
2583
3524
|
else if (domain.energy?.worldReadable) problems.push(`${domain.name}: energy_uj is world-readable`);
|
|
@@ -2596,7 +3537,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2596
3537
|
collectorGids,
|
|
2597
3538
|
group: { name: accounts.powerGroup, exists: group !== null, gid: group?.gid ?? null, members: group?.members ?? [] },
|
|
2598
3539
|
rule: { path: rulePath, present: ruleText !== null, compliant },
|
|
2599
|
-
sysusers: { path: sysusers, present: (0,
|
|
3540
|
+
sysusers: { path: sysusers, present: (0, import_node_fs6.existsSync)(sysusers) },
|
|
2600
3541
|
collectorCanRead,
|
|
2601
3542
|
persistent: compliant && group !== null && collector !== null && group.members.includes(accounts.collector),
|
|
2602
3543
|
worldReadable,
|
|
@@ -2605,15 +3546,15 @@ function inspectPowerAccess(options = {}) {
|
|
|
2605
3546
|
};
|
|
2606
3547
|
}
|
|
2607
3548
|
async function backupDatabase(source, output, host) {
|
|
2608
|
-
ensureDirectory((0,
|
|
2609
|
-
const db = new
|
|
3549
|
+
ensureDirectory((0, import_node_path6.dirname)(output), 448, true);
|
|
3550
|
+
const db = new import_node_sqlite3.DatabaseSync(source, { readOnly: true, timeout: 5e3 });
|
|
2610
3551
|
try {
|
|
2611
|
-
await (0,
|
|
3552
|
+
await (0, import_node_sqlite3.backup)(db, output);
|
|
2612
3553
|
} finally {
|
|
2613
3554
|
db.close();
|
|
2614
3555
|
}
|
|
2615
|
-
(0,
|
|
2616
|
-
const check = new
|
|
3556
|
+
(0, import_node_fs6.chmodSync)(output, 384);
|
|
3557
|
+
const check = new import_node_sqlite3.DatabaseSync(output, { readOnly: true });
|
|
2617
3558
|
try {
|
|
2618
3559
|
const result = check.prepare("PRAGMA integrity_check").get();
|
|
2619
3560
|
if (!result || Object.values(result)[0] !== "ok") throw new SystemError("invalid-backup", `backup integrity check failed: ${output}`);
|
|
@@ -2623,8 +3564,8 @@ async function backupDatabase(source, output, host) {
|
|
|
2623
3564
|
void host;
|
|
2624
3565
|
}
|
|
2625
3566
|
function databaseSchema(path) {
|
|
2626
|
-
if (!(0,
|
|
2627
|
-
const db = new
|
|
3567
|
+
if (!(0, import_node_fs6.existsSync)(path)) return null;
|
|
3568
|
+
const db = new import_node_sqlite3.DatabaseSync(path, { readOnly: true, timeout: 5e3 });
|
|
2628
3569
|
try {
|
|
2629
3570
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'").get();
|
|
2630
3571
|
if (!table) return null;
|
|
@@ -2636,34 +3577,34 @@ function databaseSchema(path) {
|
|
|
2636
3577
|
}
|
|
2637
3578
|
function replaceDatabaseFromBackup(backupPath, target, safetyBackup, host) {
|
|
2638
3579
|
assertPrivateSourceFile(backupPath, "database backup");
|
|
2639
|
-
const
|
|
3580
|
+
const backup3 = new import_node_sqlite3.DatabaseSync(backupPath, { readOnly: true });
|
|
2640
3581
|
try {
|
|
2641
|
-
if (Object.values(
|
|
3582
|
+
if (Object.values(backup3.prepare("PRAGMA integrity_check").get())[0] !== "ok") throw new SystemError("invalid-backup", "rollback backup integrity failed");
|
|
2642
3583
|
} finally {
|
|
2643
|
-
|
|
3584
|
+
backup3.close();
|
|
2644
3585
|
}
|
|
2645
|
-
ensureDirectory((0,
|
|
2646
|
-
if (safetyBackup !== null && (0,
|
|
3586
|
+
ensureDirectory((0, import_node_path6.dirname)(target), 448, true);
|
|
3587
|
+
if (safetyBackup !== null && (0, import_node_fs6.existsSync)(target)) {
|
|
2647
3588
|
void host;
|
|
2648
3589
|
}
|
|
2649
|
-
const temporary = (0,
|
|
2650
|
-
(0,
|
|
2651
|
-
(0,
|
|
3590
|
+
const temporary = (0, import_node_path6.join)((0, import_node_path6.dirname)(target), `.restore-${process.pid}-${Date.now()}.sqlite`);
|
|
3591
|
+
(0, import_node_fs6.copyFileSync)(backupPath, temporary);
|
|
3592
|
+
(0, import_node_fs6.chmodSync)(temporary, 384);
|
|
2652
3593
|
for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
|
|
2653
3594
|
try {
|
|
2654
|
-
(0,
|
|
3595
|
+
(0, import_node_fs6.unlinkSync)(sidecar);
|
|
2655
3596
|
} catch {
|
|
2656
3597
|
}
|
|
2657
3598
|
}
|
|
2658
|
-
(0,
|
|
2659
|
-
(0,
|
|
3599
|
+
(0, import_node_fs6.renameSync)(temporary, target);
|
|
3600
|
+
(0, import_node_fs6.chmodSync)(target, 384);
|
|
2660
3601
|
}
|
|
2661
3602
|
function newestTimestamp() {
|
|
2662
3603
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2663
3604
|
}
|
|
2664
3605
|
function adoptUserState(options) {
|
|
2665
|
-
const source = (0,
|
|
2666
|
-
if (!(0,
|
|
3606
|
+
const source = (0, import_node_path6.resolve)(options.source);
|
|
3607
|
+
if (!(0, import_node_fs6.existsSync)(source)) throw new SystemError("invalid-adoption", `user installation does not exist: ${source}`);
|
|
2667
3608
|
if (isWithin(options.paths.prefix, source) || isWithin(options.paths.stateDir, source) || isWithin(source, options.paths.stateDir)) {
|
|
2668
3609
|
throw new SystemError("invalid-adoption", "adoption source and system paths must not overlap");
|
|
2669
3610
|
}
|
|
@@ -2673,10 +3614,10 @@ function adoptUserState(options) {
|
|
|
2673
3614
|
const { port, origin } = setup;
|
|
2674
3615
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
|
|
2675
3616
|
if (options.role !== "collector") {
|
|
2676
|
-
const sourceDatabase = (0,
|
|
2677
|
-
if ((0,
|
|
3617
|
+
const sourceDatabase = (0, import_node_path6.join)(source, "hub", "telemetry.sqlite");
|
|
3618
|
+
if ((0, import_node_fs6.existsSync)(sourceDatabase)) {
|
|
2678
3619
|
assertPrivateSourceFile(sourceDatabase, "adopted database");
|
|
2679
|
-
const database = new
|
|
3620
|
+
const database = new import_node_sqlite3.DatabaseSync(sourceDatabase, { readOnly: true });
|
|
2680
3621
|
try {
|
|
2681
3622
|
const row = database.prepare("PRAGMA integrity_check").get();
|
|
2682
3623
|
if (!row || Object.values(row)[0] !== "ok") throw new SystemError("invalid-adoption", "adopted database failed its integrity check");
|
|
@@ -2684,12 +3625,12 @@ function adoptUserState(options) {
|
|
|
2684
3625
|
database.close();
|
|
2685
3626
|
}
|
|
2686
3627
|
}
|
|
2687
|
-
const sourceEnrollment = (0,
|
|
2688
|
-
if ((0,
|
|
3628
|
+
const sourceEnrollment = (0, import_node_path6.join)(source, "hub", "enrollment.json");
|
|
3629
|
+
if ((0, import_node_fs6.existsSync)(sourceEnrollment)) assertPrivateSourceFile(sourceEnrollment, "adopted enrollment");
|
|
2689
3630
|
}
|
|
2690
3631
|
if (options.role !== "hub") {
|
|
2691
|
-
const sourceCollectorState = (0,
|
|
2692
|
-
if ((0,
|
|
3632
|
+
const sourceCollectorState = (0, import_node_path6.join)(source, "collector", "state.json");
|
|
3633
|
+
if ((0, import_node_fs6.existsSync)(sourceCollectorState)) {
|
|
2693
3634
|
assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
|
|
2694
3635
|
readCollectorIdentity(sourceCollectorState);
|
|
2695
3636
|
}
|
|
@@ -2698,7 +3639,7 @@ function adoptUserState(options) {
|
|
|
2698
3639
|
const warnings = [];
|
|
2699
3640
|
const targetDatabase = hubDatabasePath(options.paths);
|
|
2700
3641
|
const targetCollector = collectorStatePath(options.paths);
|
|
2701
|
-
if (options.apply && (0,
|
|
3642
|
+
if (options.apply && (0, import_node_fs6.existsSync)(targetDatabase)) {
|
|
2702
3643
|
throw new SystemError("destination-exists", `refusing to overwrite ${targetDatabase}`);
|
|
2703
3644
|
}
|
|
2704
3645
|
const hubEnv = options.role === "collector" ? null : [
|
|
@@ -2733,62 +3674,42 @@ function adoptUserState(options) {
|
|
|
2733
3674
|
port
|
|
2734
3675
|
};
|
|
2735
3676
|
}
|
|
2736
|
-
function copyAdoptedTree(source, target, copied, skipLocks = true) {
|
|
2737
|
-
const visit = (current, destination) => {
|
|
2738
|
-
const stat = (0, import_node_fs3.lstatSync)(current);
|
|
2739
|
-
if (stat.isSymbolicLink()) return;
|
|
2740
|
-
if (stat.isDirectory()) {
|
|
2741
|
-
(0, import_node_fs3.mkdirSync)(destination, { recursive: true, mode: 448 });
|
|
2742
|
-
for (const entry of (0, import_node_fs3.readdirSync)(current)) visit((0, import_node_path4.join)(current, entry), (0, import_node_path4.join)(destination, entry));
|
|
2743
|
-
return;
|
|
2744
|
-
}
|
|
2745
|
-
if (!stat.isFile()) return;
|
|
2746
|
-
if (skipLocks && /\.(lock|sock)$/.test(current)) return;
|
|
2747
|
-
(0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(destination), { recursive: true, mode: 448 });
|
|
2748
|
-
(0, import_node_fs3.copyFileSync)(current, destination);
|
|
2749
|
-
(0, import_node_fs3.chmodSync)(destination, 384);
|
|
2750
|
-
copied.push({ source: current, target: destination, sha256: sha256File(destination), bytes: stat.size });
|
|
2751
|
-
};
|
|
2752
|
-
visit(source, target);
|
|
2753
|
-
}
|
|
2754
3677
|
async function performAdoptionCopyAsync(options) {
|
|
2755
3678
|
const { source, paths, role, host, copy } = options;
|
|
2756
3679
|
assertDirectoryStopped(source, role);
|
|
2757
3680
|
const targetDatabase = hubDatabasePath(paths);
|
|
2758
|
-
const sourceDatabase = (0,
|
|
2759
|
-
if (role !== "collector" && (0,
|
|
3681
|
+
const sourceDatabase = (0, import_node_path6.join)(source, "hub", "telemetry.sqlite");
|
|
3682
|
+
if (role !== "collector" && (0, import_node_fs6.existsSync)(sourceDatabase) && !(0, import_node_fs6.existsSync)(targetDatabase)) {
|
|
2760
3683
|
assertPrivateSourceFile(sourceDatabase, "adopted database");
|
|
2761
|
-
ensureDirectory((0,
|
|
3684
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
|
|
2762
3685
|
await backupDatabase(sourceDatabase, targetDatabase, host);
|
|
2763
3686
|
copy.record.files.push({
|
|
2764
3687
|
source: sourceDatabase,
|
|
2765
3688
|
target: targetDatabase,
|
|
2766
|
-
sha256:
|
|
2767
|
-
bytes: (0,
|
|
3689
|
+
sha256: sha256File2(targetDatabase),
|
|
3690
|
+
bytes: (0, import_node_fs6.statSync)(targetDatabase).size
|
|
2768
3691
|
});
|
|
2769
3692
|
}
|
|
2770
|
-
const sourceCollectorState = (0,
|
|
2771
|
-
if (role !== "hub" && (0,
|
|
3693
|
+
const sourceCollectorState = (0, import_node_path6.join)(source, "collector", "state.json");
|
|
3694
|
+
if (role !== "hub" && (0, import_node_fs6.existsSync)(sourceCollectorState)) {
|
|
2772
3695
|
assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
|
|
2773
|
-
ensureDirectory((0,
|
|
3696
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
|
|
2774
3697
|
const targetCollector = collectorStatePath(paths);
|
|
2775
|
-
(0,
|
|
2776
|
-
(0,
|
|
3698
|
+
(0, import_node_fs6.copyFileSync)(sourceCollectorState, targetCollector);
|
|
3699
|
+
(0, import_node_fs6.chmodSync)(targetCollector, 384);
|
|
2777
3700
|
copy.record.files.push({
|
|
2778
3701
|
source: sourceCollectorState,
|
|
2779
3702
|
target: targetCollector,
|
|
2780
|
-
sha256:
|
|
2781
|
-
bytes: (0,
|
|
3703
|
+
sha256: sha256File2(targetCollector),
|
|
3704
|
+
bytes: (0, import_node_fs6.statSync)(targetCollector).size
|
|
2782
3705
|
});
|
|
2783
|
-
const outbox = (0, import_node_path4.join)(source, "collector", "outbox");
|
|
2784
|
-
if ((0, import_node_fs3.existsSync)(outbox)) copyAdoptedTree(outbox, (0, import_node_path4.join)(paths.stateDir, "collector", "outbox"), copy.record.files);
|
|
2785
3706
|
}
|
|
2786
|
-
const enrollment = (0,
|
|
2787
|
-
if (role !== "collector" && (0,
|
|
3707
|
+
const enrollment = (0, import_node_path6.join)(source, "hub", "enrollment.json");
|
|
3708
|
+
if (role !== "collector" && (0, import_node_fs6.existsSync)(enrollment)) {
|
|
2788
3709
|
const target = hubEnrollmentPath(paths);
|
|
2789
|
-
(0,
|
|
2790
|
-
(0,
|
|
2791
|
-
copy.record.files.push({ source: enrollment, target, sha256:
|
|
3710
|
+
(0, import_node_fs6.copyFileSync)(enrollment, target);
|
|
3711
|
+
(0, import_node_fs6.chmodSync)(target, 384);
|
|
3712
|
+
copy.record.files.push({ source: enrollment, target, sha256: sha256File2(target), bytes: (0, import_node_fs6.statSync)(target).size });
|
|
2792
3713
|
}
|
|
2793
3714
|
}
|
|
2794
3715
|
function writeSysusersFiles(paths, accounts, role, power, host) {
|
|
@@ -2828,15 +3749,15 @@ function writePowerRule(paths, accounts) {
|
|
|
2828
3749
|
writeManagedFile(raplRulePath(paths), renderRaplRule(accounts), 420, true);
|
|
2829
3750
|
}
|
|
2830
3751
|
function powerOriginalPath(paths) {
|
|
2831
|
-
return (0,
|
|
3752
|
+
return (0, import_node_path6.join)(paths.stateDir, POWER_STATE_FILE_NAME);
|
|
2832
3753
|
}
|
|
2833
3754
|
function capturePowerOriginals(paths) {
|
|
2834
3755
|
const files = {};
|
|
2835
3756
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2836
3757
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2837
|
-
const path = (0,
|
|
3758
|
+
const path = (0, import_node_path6.join)(domain, name);
|
|
2838
3759
|
try {
|
|
2839
|
-
const stat = (0,
|
|
3760
|
+
const stat = (0, import_node_fs6.statSync)(path);
|
|
2840
3761
|
files[path] = { mode: stat.mode & 511, uid: stat.uid, gid: stat.gid };
|
|
2841
3762
|
} catch {
|
|
2842
3763
|
}
|
|
@@ -2848,8 +3769,8 @@ function capturePowerOriginals(paths) {
|
|
|
2848
3769
|
function applyPowerAccess(paths, accounts, host) {
|
|
2849
3770
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2850
3771
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2851
|
-
const path = (0,
|
|
2852
|
-
if (!(0,
|
|
3772
|
+
const path = (0, import_node_path6.join)(domain, name);
|
|
3773
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2853
3774
|
runRequired(host, "chgrp", [accounts.powerGroup, path], `power counter group ${path}`);
|
|
2854
3775
|
runRequired(host, "chmod", ["0440", path], `power counter mode ${path}`);
|
|
2855
3776
|
}
|
|
@@ -2862,9 +3783,9 @@ function resumePowerRule(paths, host) {
|
|
|
2862
3783
|
}
|
|
2863
3784
|
function packageDomainPaths(root) {
|
|
2864
3785
|
try {
|
|
2865
|
-
return (0,
|
|
3786
|
+
return (0, import_node_fs6.readdirSync)(root).filter((entry) => /^intel-rapl:\d+$/.test(entry)).map((entry) => (0, import_node_path6.join)(root, entry)).filter((path) => {
|
|
2866
3787
|
try {
|
|
2867
|
-
return /^package/.test((0,
|
|
3788
|
+
return /^package/.test((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(path, "name"), "utf8").trim());
|
|
2868
3789
|
} catch {
|
|
2869
3790
|
return false;
|
|
2870
3791
|
}
|
|
@@ -2884,8 +3805,8 @@ function restorePowerOriginals(paths, host) {
|
|
|
2884
3805
|
const files = { ...originals?.files ?? {} };
|
|
2885
3806
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2886
3807
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2887
|
-
const path = (0,
|
|
2888
|
-
if (!(0,
|
|
3808
|
+
const path = (0, import_node_path6.join)(domain, name);
|
|
3809
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2889
3810
|
const original = files[path];
|
|
2890
3811
|
if (original) {
|
|
2891
3812
|
runRequired(host, "chown", [`${original.uid}:${original.gid}`, path], "restore counter ownership");
|
|
@@ -2901,7 +3822,7 @@ function restorePowerOriginals(paths, host) {
|
|
|
2901
3822
|
}
|
|
2902
3823
|
}
|
|
2903
3824
|
try {
|
|
2904
|
-
(0,
|
|
3825
|
+
(0, import_node_fs6.unlinkSync)(powerOriginalPath(paths));
|
|
2905
3826
|
} catch {
|
|
2906
3827
|
}
|
|
2907
3828
|
return restored;
|
|
@@ -2925,7 +3846,7 @@ function stopUnits(host, units) {
|
|
|
2925
3846
|
if (units.some((unit) => systemdActive(host, unit))) throw new SystemError("service-active", "system services did not stop; no files were restored");
|
|
2926
3847
|
}
|
|
2927
3848
|
function unitsForCleanup(paths, host) {
|
|
2928
|
-
const legacy = LEGACY_UNIT_NAMES.filter((unit) => (0,
|
|
3849
|
+
const legacy = LEGACY_UNIT_NAMES.filter((unit) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit)) || systemdActive(host, unit));
|
|
2929
3850
|
return [...Object.values(UNIT_NAMES), ...legacy];
|
|
2930
3851
|
}
|
|
2931
3852
|
function startUnits(host, units) {
|
|
@@ -2963,7 +3884,7 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
2963
3884
|
const report = runRequired(
|
|
2964
3885
|
host,
|
|
2965
3886
|
state.nodePath,
|
|
2966
|
-
[(0,
|
|
3887
|
+
[(0, import_node_path6.join)(paths.prefix, "current/dist/ops.cjs"), "inspect", "--database", hubDatabasePath(paths), "--json"],
|
|
2967
3888
|
"activated database verification",
|
|
2968
3889
|
{ timeoutMs: Math.max(100, deadline - Date.now()) }
|
|
2969
3890
|
);
|
|
@@ -2979,8 +3900,8 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
2979
3900
|
let problem = "no sampling marker";
|
|
2980
3901
|
while (Date.now() < deadline) {
|
|
2981
3902
|
try {
|
|
2982
|
-
const marker = readJsonObject((0,
|
|
2983
|
-
const lease = readJsonObject((0,
|
|
3903
|
+
const marker = readJsonObject((0, import_node_path6.join)(paths.stateDir, "collector/health.json"));
|
|
3904
|
+
const lease = readJsonObject((0, import_node_path6.join)(paths.stateDir, "collector/collector-runtime.lock"));
|
|
2984
3905
|
if (marker.pid !== lease.pid || !Number.isSafeInteger(marker.pid)) throw new Error("sampling marker belongs to another process");
|
|
2985
3906
|
process.kill(Number(marker.pid), 0);
|
|
2986
3907
|
if (marker.version !== state.currentVersion || !Number.isFinite(marker.sampledAt) || Date.now() - Number(marker.sampledAt) > 15e3) throw new Error("collector version or sampling freshness is wrong");
|
|
@@ -2988,32 +3909,35 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
2988
3909
|
} catch (error) {
|
|
2989
3910
|
problem = error instanceof Error ? error.message : String(error);
|
|
2990
3911
|
}
|
|
2991
|
-
await new Promise((
|
|
3912
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
2992
3913
|
}
|
|
2993
3914
|
throw new SystemError("collector-unready", `system collector did not sample: ${problem}`);
|
|
2994
3915
|
}
|
|
2995
3916
|
}
|
|
2996
3917
|
function chownAccount(paths, owner, group, host) {
|
|
2997
3918
|
for (const path of paths) {
|
|
2998
|
-
if (!(0,
|
|
3919
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2999
3920
|
runRequired(host, "chown", ["-R", `${owner}:${group}`, path], `ownership ${path}`);
|
|
3000
3921
|
}
|
|
3001
3922
|
}
|
|
3002
3923
|
function chmodTree(path, host) {
|
|
3003
|
-
if (!(0,
|
|
3924
|
+
if (!(0, import_node_fs6.existsSync)(path)) return;
|
|
3004
3925
|
runRequired(host, "chmod", ["-R", "u=rwX,go=rX", path], `release permissions ${path}`);
|
|
3005
3926
|
}
|
|
3006
3927
|
function pickRollbackBackup(state, previousSchema) {
|
|
3007
3928
|
const backups = Object.values(state.backups).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
3008
3929
|
if (previousSchema === null) return null;
|
|
3009
|
-
return [...backups].reverse().find((
|
|
3930
|
+
return [...backups].reverse().find((backup3) => backup3.fromVersion === state.previousVersion && backup3.toVersion === state.currentVersion && backup3.schemaVersion !== null && backup3.schemaVersion <= previousSchema) ?? null;
|
|
3010
3931
|
}
|
|
3011
3932
|
async function recoverFailedUpgrade(options) {
|
|
3012
3933
|
stopUnits(options.host, options.units);
|
|
3013
3934
|
assertDirectoryStopped(options.paths.stateDir);
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3935
|
+
for (const message of undoMachineDataCleanup(systemMachineDataPaths(options.paths), options.machineData?.stagedQueues ?? null)) {
|
|
3936
|
+
console.warn(`rigscope system upgrade recovery: ${message}`);
|
|
3937
|
+
}
|
|
3938
|
+
replaceSymlink((0, import_node_path6.join)(options.paths.prefix, "current"), options.oldCurrent, options.host);
|
|
3939
|
+
if (options.oldPrevious === null) removeSymlink((0, import_node_path6.join)(options.paths.prefix, "previous"));
|
|
3940
|
+
else replaceSymlink((0, import_node_path6.join)(options.paths.prefix, "previous"), options.oldPrevious, options.host);
|
|
3017
3941
|
const database = hubDatabasePath(options.paths);
|
|
3018
3942
|
let afterSchema = null;
|
|
3019
3943
|
try {
|
|
@@ -3021,13 +3945,13 @@ async function recoverFailedUpgrade(options) {
|
|
|
3021
3945
|
} catch {
|
|
3022
3946
|
afterSchema = null;
|
|
3023
3947
|
}
|
|
3024
|
-
if (options.backupPath !== null && (0,
|
|
3025
|
-
await backupDatabase(database, (0,
|
|
3948
|
+
if (options.backupPath !== null && (0, import_node_fs6.existsSync)(options.backupPath) && (afterSchema === null || options.beforeSchema === null || afterSchema > options.beforeSchema || options.machineData?.databaseCleared === true)) {
|
|
3949
|
+
await backupDatabase(database, (0, import_node_path6.join)(options.paths.stateDir, "backups", `failed-upgrade-${newestTimestamp()}.sqlite`), options.host);
|
|
3026
3950
|
replaceDatabaseFromBackup(options.backupPath, database, null, options.host);
|
|
3027
3951
|
runRequired(options.host, "chown", [`${options.state.accounts.hub}:${options.state.accounts.hub}`, database], "restored database ownership");
|
|
3028
3952
|
}
|
|
3029
|
-
options.state.previousVersion = options.oldPrevious === null ? null : (0,
|
|
3030
|
-
options.state.currentVersion = (0,
|
|
3953
|
+
options.state.previousVersion = options.oldPrevious === null ? null : (0, import_node_path6.basename)(options.oldPrevious);
|
|
3954
|
+
options.state.currentVersion = (0, import_node_path6.basename)(options.oldCurrent);
|
|
3031
3955
|
writeSystemState(options.paths, options.state);
|
|
3032
3956
|
if (options.activeComponents.length > 0) {
|
|
3033
3957
|
startUnits(options.host, options.units);
|
|
@@ -3036,14 +3960,14 @@ async function recoverFailedUpgrade(options) {
|
|
|
3036
3960
|
}
|
|
3037
3961
|
function assertManagedFileOwnership(paths, path) {
|
|
3038
3962
|
if (!isDefaultSystemLayout(paths)) return;
|
|
3039
|
-
if (!(0,
|
|
3040
|
-
const stat = (0,
|
|
3963
|
+
if (!(0, import_node_fs6.existsSync)(path)) return;
|
|
3964
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
3041
3965
|
if (stat.isSymbolicLink() || !stat.isFile()) throw new SystemError("unsafe-path", `${path} must be a regular managed file`);
|
|
3042
3966
|
if (stat.uid !== 0) throw new SystemError("insecure-ownership", `${path} must be root-owned before replacement`);
|
|
3043
3967
|
if ((stat.mode & 18) !== 0) throw new SystemError("insecure-permissions", `${path} must not be group/world-writable`);
|
|
3044
3968
|
}
|
|
3045
3969
|
function writeUnitFile(paths, unit, content, replace) {
|
|
3046
|
-
const path = (0,
|
|
3970
|
+
const path = (0, import_node_path6.join)(paths.unitsDir, unit);
|
|
3047
3971
|
assertManagedFileOwnership(paths, path);
|
|
3048
3972
|
writeManagedFile(path, content, 420, replace);
|
|
3049
3973
|
}
|
|
@@ -3058,19 +3982,19 @@ function ensureComponentSetup(options) {
|
|
|
3058
3982
|
ensureDirectory(paths.configDir, 493);
|
|
3059
3983
|
ensureDirectory(paths.stateDir, 493);
|
|
3060
3984
|
ensureDirectory(paths.unitsDir, 493);
|
|
3061
|
-
ensureDirectory((0,
|
|
3062
|
-
ensureDirectory((0,
|
|
3063
|
-
ensureDirectory((0,
|
|
3064
|
-
ensureDirectory((0,
|
|
3985
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
|
|
3986
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub", "run"), 448, true);
|
|
3987
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
|
|
3988
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
|
|
3065
3989
|
const release = currentReleaseOrThrow(paths);
|
|
3066
3990
|
void release;
|
|
3067
3991
|
writeSysusersFiles(paths, accounts, "local", power, host);
|
|
3068
3992
|
if (component === "hub") {
|
|
3069
|
-
if (!(0,
|
|
3993
|
+
if (!(0, import_node_fs6.existsSync)(hubEnvPath(paths))) {
|
|
3070
3994
|
const env = hubEnvTemplate(paths);
|
|
3071
3995
|
writeManagedFile(hubEnvPath(paths), env, 384, false);
|
|
3072
3996
|
}
|
|
3073
|
-
if (!(0,
|
|
3997
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, UNIT_NAMES.hub))) {
|
|
3074
3998
|
const env = readHubEnvironment(paths);
|
|
3075
3999
|
const remote = env.HUB_PUBLIC_ORIGIN !== void 0 && env.HUB_PUBLIC_ORIGIN !== "";
|
|
3076
4000
|
const unit = renderSystemHubUnit({
|
|
@@ -3087,7 +4011,7 @@ function ensureComponentSetup(options) {
|
|
|
3087
4011
|
`, false);
|
|
3088
4012
|
}
|
|
3089
4013
|
} else {
|
|
3090
|
-
if (!(0,
|
|
4014
|
+
if (!(0, import_node_fs6.existsSync)(collectorEnvPath(paths))) {
|
|
3091
4015
|
writeManagedFile(collectorEnvPath(paths), collectorEnvTemplate(), 384, false);
|
|
3092
4016
|
}
|
|
3093
4017
|
const unit = renderSystemCollectorUnit({
|
|
@@ -3103,17 +4027,17 @@ function ensureComponentSetup(options) {
|
|
|
3103
4027
|
const validation = runRequired(
|
|
3104
4028
|
host,
|
|
3105
4029
|
"systemd-analyze",
|
|
3106
|
-
["verify", "--man=no", (0,
|
|
4030
|
+
["verify", "--man=no", (0, import_node_path6.join)(paths.unitsDir, unitFor(component))],
|
|
3107
4031
|
"native systemd unit validation"
|
|
3108
4032
|
);
|
|
3109
4033
|
if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
|
|
3110
4034
|
throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
|
|
3111
4035
|
}
|
|
3112
|
-
if (component === "hub") chownAccount([(0,
|
|
3113
|
-
if (component === "collector") chownAccount([(0,
|
|
4036
|
+
if (component === "hub") chownAccount([(0, import_node_path6.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
|
|
4037
|
+
if (component === "collector") chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
3114
4038
|
}
|
|
3115
4039
|
function ensurePowerAccess(paths, accounts, host) {
|
|
3116
|
-
if (!(0,
|
|
4040
|
+
if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
|
|
3117
4041
|
writePowerRule(paths, accounts);
|
|
3118
4042
|
resumePowerRule(paths, host);
|
|
3119
4043
|
applyPowerAccess(paths, accounts, host);
|
|
@@ -3131,7 +4055,7 @@ function resetUnfinishedSystemState(options) {
|
|
|
3131
4055
|
}
|
|
3132
4056
|
function installPreset(options, role) {
|
|
3133
4057
|
if (options.adoptFrom !== void 0) {
|
|
3134
|
-
return componentSet(readAdoptionSetup((0,
|
|
4058
|
+
return componentSet(readAdoptionSetup((0, import_node_path6.resolve)(options.adoptFrom)).components);
|
|
3135
4059
|
}
|
|
3136
4060
|
return componentsForRole(role);
|
|
3137
4061
|
}
|
|
@@ -3143,11 +4067,11 @@ function planSystemInstall(options) {
|
|
|
3143
4067
|
const power = options.power !== false && preset.collector;
|
|
3144
4068
|
const initial = options.enable === true ? preset : { hub: false, collector: false };
|
|
3145
4069
|
const files = [
|
|
3146
|
-
{ path: (0,
|
|
4070
|
+
{ path: (0, import_node_path6.join)(paths.prefix, "current"), mode: "symlink", purpose: "active root-owned release" },
|
|
3147
4071
|
{ path: hubEnvPath(paths), mode: "0600", purpose: "root-owned hub configuration" },
|
|
3148
4072
|
{ path: collectorEnvPath(paths), mode: "0600", purpose: "root-owned collector configuration" },
|
|
3149
4073
|
{ path: sysusersPath(paths), mode: "0644", purpose: "dedicated nologin accounts" },
|
|
3150
|
-
...COMPONENTS.map((component) => ({ path: (0,
|
|
4074
|
+
...COMPONENTS.map((component) => ({ path: (0, import_node_path6.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }))
|
|
3151
4075
|
];
|
|
3152
4076
|
if (power) {
|
|
3153
4077
|
files.push({ path: raplRulePath(paths), mode: "0644", purpose: "package RAPL read rule" });
|
|
@@ -3162,7 +4086,7 @@ function planSystemInstall(options) {
|
|
|
3162
4086
|
];
|
|
3163
4087
|
const notes = [
|
|
3164
4088
|
"Nothing is changed without --apply and root.",
|
|
3165
|
-
options.adoptFrom ? `Adopts ${(0,
|
|
4089
|
+
options.adoptFrom ? `Adopts ${(0, import_node_path6.resolve)(options.adoptFrom)} by copy only; the original user setup is preserved.` : "Existing user services are not modified.",
|
|
3166
4090
|
"One complete package: both runtimes, units and accounts are installed; --role only sets the initial enabled preset.",
|
|
3167
4091
|
"Hub and collector use separate unprivileged accounts; Node gets no capabilities.",
|
|
3168
4092
|
...options.enable && preset.hub ? ["Enabling the hub creates its owner account once and prints the password once."] : [],
|
|
@@ -3195,8 +4119,8 @@ async function installSystemUnlocked(options) {
|
|
|
3195
4119
|
`existing user service(s) detected (${detail}); stop them and use --adopt-user-state for explicit copy-based adoption`
|
|
3196
4120
|
);
|
|
3197
4121
|
}
|
|
3198
|
-
const source = (0,
|
|
3199
|
-
if (!(0,
|
|
4122
|
+
const source = (0, import_node_path6.resolve)(options.source);
|
|
4123
|
+
if (!(0, import_node_fs6.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
|
|
3200
4124
|
const port = options.port ?? 8081;
|
|
3201
4125
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-argument", "--port must be 1-65535");
|
|
3202
4126
|
parseHubConfig(options.origin === void 0 ? { HUB_HOST: "127.0.0.1", HUB_PORT: String(port) } : { HUB_PUBLIC_ORIGIN: options.origin, HUB_SOCKET_PATH: hubSocketPath(paths) });
|
|
@@ -3217,14 +4141,14 @@ async function installSystemUnlocked(options) {
|
|
|
3217
4141
|
});
|
|
3218
4142
|
try {
|
|
3219
4143
|
if (legacy) resetUnfinishedSystemState({ paths, host, now });
|
|
3220
|
-
ensureDirectory((0,
|
|
4144
|
+
ensureDirectory((0, import_node_path6.join)(paths.prefix, "releases"), 493);
|
|
3221
4145
|
ensureDirectory(paths.stateDir, 493);
|
|
3222
4146
|
ensureDirectory(paths.configDir, 493);
|
|
3223
4147
|
ensureDirectory(paths.unitsDir, 493);
|
|
3224
|
-
ensureDirectory((0,
|
|
3225
|
-
ensureDirectory((0,
|
|
3226
|
-
ensureDirectory((0,
|
|
3227
|
-
ensureDirectory((0,
|
|
4148
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
|
|
4149
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
|
|
4150
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
|
|
4151
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub", "run"), 448, true);
|
|
3228
4152
|
const release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
|
|
3229
4153
|
let adoption = null;
|
|
3230
4154
|
if (options.adoptFrom) {
|
|
@@ -3257,13 +4181,13 @@ async function installSystemUnlocked(options) {
|
|
|
3257
4181
|
`, true);
|
|
3258
4182
|
writeUnitFile(paths, UNIT_NAMES.collector, `${collectorUnit}
|
|
3259
4183
|
`, true);
|
|
3260
|
-
const validation = runRequired(host, "systemd-analyze", ["verify", "--man=no", ...COMPONENTS.map((component) => (0,
|
|
4184
|
+
const validation = runRequired(host, "systemd-analyze", ["verify", "--man=no", ...COMPONENTS.map((component) => (0, import_node_path6.join)(paths.unitsDir, unitFor(component)))], "native systemd unit validation");
|
|
3261
4185
|
if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
|
|
3262
4186
|
throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
|
|
3263
4187
|
}
|
|
3264
4188
|
if (power) ensurePowerAccess(paths, accounts, host);
|
|
3265
|
-
chownAccount([(0,
|
|
3266
|
-
chownAccount([(0,
|
|
4189
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
|
|
4190
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
3267
4191
|
const state = emptyState({ paths, accounts, role, components: { hub: false, collector: false }, nodePath, now });
|
|
3268
4192
|
state.currentVersion = release.version;
|
|
3269
4193
|
state.releases[release.version] = {
|
|
@@ -3273,7 +4197,7 @@ async function installSystemUnlocked(options) {
|
|
|
3273
4197
|
};
|
|
3274
4198
|
state.adoption = adoption?.record ?? null;
|
|
3275
4199
|
state.power = power ? { enabled: true, rulePath: raplRulePath(paths), group: accounts.powerGroup } : null;
|
|
3276
|
-
replaceSymlink((0,
|
|
4200
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), release.releaseDir, host);
|
|
3277
4201
|
writeSystemState(paths, state);
|
|
3278
4202
|
const wantsHub = preset.hub && (options.enable === true || options.start === true);
|
|
3279
4203
|
const wantsCollector = preset.collector && (options.enable === true || options.start === true);
|
|
@@ -3299,6 +4223,7 @@ async function installSystemUnlocked(options) {
|
|
|
3299
4223
|
await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, started);
|
|
3300
4224
|
}
|
|
3301
4225
|
}
|
|
4226
|
+
recordMachineDataTransition(paths.stateDir, release.version, (path, content) => atomicWrite(path, content, 384));
|
|
3302
4227
|
return {
|
|
3303
4228
|
operation: "install",
|
|
3304
4229
|
ok: true,
|
|
@@ -3327,14 +4252,14 @@ function systemStatus(options = {}) {
|
|
|
3327
4252
|
} catch (error) {
|
|
3328
4253
|
stateError = error instanceof Error ? error.message : String(error);
|
|
3329
4254
|
}
|
|
3330
|
-
const presentUnits = [UNIT_NAMES.hub, UNIT_NAMES.collector].filter((unit) => (0,
|
|
4255
|
+
const presentUnits = [UNIT_NAMES.hub, UNIT_NAMES.collector].filter((unit) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit)));
|
|
3331
4256
|
const publicRole = presentUnits.length === 2 ? "local" : presentUnits[0] === UNIT_NAMES.hub ? "hub" : presentUnits[0] === UNIT_NAMES.collector ? "collector" : null;
|
|
3332
4257
|
const role = state?.role ?? publicRole ?? "local";
|
|
3333
4258
|
const current = currentReleaseDir(paths);
|
|
3334
4259
|
let publicVersion = null;
|
|
3335
4260
|
if (current) {
|
|
3336
4261
|
try {
|
|
3337
|
-
const manifest = JSON.parse((0,
|
|
4262
|
+
const manifest = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(current, "package.json"), "utf8"));
|
|
3338
4263
|
if (typeof manifest.version === "string") publicVersion = manifest.version;
|
|
3339
4264
|
} catch {
|
|
3340
4265
|
}
|
|
@@ -3345,7 +4270,7 @@ function systemStatus(options = {}) {
|
|
|
3345
4270
|
const componentReports = {};
|
|
3346
4271
|
for (const component of COMPONENTS) {
|
|
3347
4272
|
const unit = unitFor(component);
|
|
3348
|
-
const present = (0,
|
|
4273
|
+
const present = (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit));
|
|
3349
4274
|
const active = present && systemdActive(host, unit);
|
|
3350
4275
|
const unitEnabled = present && systemdEnabled(host, unit);
|
|
3351
4276
|
const enabled = state !== null ? state.components[component].enabled : unitEnabled;
|
|
@@ -3354,7 +4279,7 @@ function systemStatus(options = {}) {
|
|
|
3354
4279
|
componentReports[component] = {
|
|
3355
4280
|
component,
|
|
3356
4281
|
unit,
|
|
3357
|
-
path: (0,
|
|
4282
|
+
path: (0, import_node_path6.join)(paths.unitsDir, unit),
|
|
3358
4283
|
present,
|
|
3359
4284
|
enabled,
|
|
3360
4285
|
unitEnabled,
|
|
@@ -3398,16 +4323,16 @@ function systemStatus(options = {}) {
|
|
|
3398
4323
|
})),
|
|
3399
4324
|
database: {
|
|
3400
4325
|
path: database,
|
|
3401
|
-
exists: (0,
|
|
4326
|
+
exists: (0, import_node_fs6.existsSync)(database),
|
|
3402
4327
|
schema,
|
|
3403
|
-
mode: (0,
|
|
4328
|
+
mode: (0, import_node_fs6.existsSync)(database) ? modeString((0, import_node_fs6.statSync)(database).mode) : null,
|
|
3404
4329
|
lock
|
|
3405
4330
|
},
|
|
3406
|
-
socket: { path: hubSocketPath(paths), exists: (0,
|
|
4331
|
+
socket: { path: hubSocketPath(paths), exists: (0, import_node_fs6.existsSync)(hubSocketPath(paths)) },
|
|
3407
4332
|
config: {
|
|
3408
4333
|
dir: paths.configDir,
|
|
3409
|
-
hubEnv: { path: hubEnvPath(paths), present: (0,
|
|
3410
|
-
collectorEnv: { path: collectorEnvPath(paths), present: (0,
|
|
4334
|
+
hubEnv: { path: hubEnvPath(paths), present: (0, import_node_fs6.existsSync)(hubEnvPath(paths)) },
|
|
4335
|
+
collectorEnv: { path: collectorEnvPath(paths), present: (0, import_node_fs6.existsSync)(collectorEnvPath(paths)) }
|
|
3411
4336
|
},
|
|
3412
4337
|
adoption: state?.adoption ?? null,
|
|
3413
4338
|
power,
|
|
@@ -3576,7 +4501,7 @@ async function enableSystem(options) {
|
|
|
3576
4501
|
],
|
|
3577
4502
|
files: [
|
|
3578
4503
|
{ path: component === "hub" ? hubEnvPath(paths) : collectorEnvPath(paths), mode: "0600", purpose: `${component} configuration` },
|
|
3579
|
-
{ path: (0,
|
|
4504
|
+
{ path: (0, import_node_path6.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }
|
|
3580
4505
|
],
|
|
3581
4506
|
notes: [
|
|
3582
4507
|
"Enables only the requested component; the other component keeps running.",
|
|
@@ -3644,7 +4569,7 @@ function disableSystem(options) {
|
|
|
3644
4569
|
accounts: state.accounts,
|
|
3645
4570
|
commands: [`systemctl stop ${unitFor(component)}`, `systemctl disable ${unitFor(component)}`],
|
|
3646
4571
|
files: [],
|
|
3647
|
-
notes: ["Settings, credentials
|
|
4572
|
+
notes: ["Settings, credentials and stored history are retained while the component is disabled; unacknowledged samples held in the collector's RAM delivery queue drain with the process."]
|
|
3648
4573
|
};
|
|
3649
4574
|
if (options.apply !== true) return { operation: "disable", ok: true, apply: false, message: "plan only", plan };
|
|
3650
4575
|
assertApply(host);
|
|
@@ -3666,7 +4591,7 @@ function disableSystem(options) {
|
|
|
3666
4591
|
function readPrivateToken(path, read) {
|
|
3667
4592
|
let stat;
|
|
3668
4593
|
try {
|
|
3669
|
-
stat = (0,
|
|
4594
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
3670
4595
|
} catch (error) {
|
|
3671
4596
|
if (error.code === "ENOENT") {
|
|
3672
4597
|
throw new SystemError("missing-grant", `grant file does not exist: ${path}`);
|
|
@@ -3677,7 +4602,7 @@ function readPrivateToken(path, read) {
|
|
|
3677
4602
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `grant file must be private (0600): ${path}`);
|
|
3678
4603
|
if (stat.size < 1 || stat.size > 4096) throw new SystemError("invalid-token", `grant file must contain 1-4096 bytes: ${path}`);
|
|
3679
4604
|
if (!read) return void 0;
|
|
3680
|
-
const token = (0,
|
|
4605
|
+
const token = (0, import_node_fs6.readFileSync)(path, "utf8").trim();
|
|
3681
4606
|
if (token === "" || /[\r\n\0]/.test(token) || !/^[\x21-\x7e]{1,256}$/.test(token)) {
|
|
3682
4607
|
throw new SystemError("invalid-token", "grant file contains an invalid token");
|
|
3683
4608
|
}
|
|
@@ -3689,7 +4614,7 @@ async function connectSystem(options) {
|
|
|
3689
4614
|
const state = readSystemState(paths);
|
|
3690
4615
|
if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
|
|
3691
4616
|
const destination = connectDestinationUrl(options.url);
|
|
3692
|
-
const tokenFile = (0,
|
|
4617
|
+
const tokenFile = (0, import_node_path6.resolve)(options.tokenFile);
|
|
3693
4618
|
const configured = collectorConfigured(paths);
|
|
3694
4619
|
if (configured.configured) {
|
|
3695
4620
|
throw new SystemError(
|
|
@@ -3737,12 +4662,12 @@ async function connectSystem(options) {
|
|
|
3737
4662
|
`collector enrollment failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3738
4663
|
);
|
|
3739
4664
|
} finally {
|
|
3740
|
-
chownAccount([(0,
|
|
4665
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
|
|
3741
4666
|
}
|
|
3742
4667
|
if (!collectorConfigured(paths).configured) {
|
|
3743
4668
|
throw new SystemError("enrollment-failed", "the hub accepted enrollment but no destination was saved; check private state");
|
|
3744
4669
|
}
|
|
3745
|
-
chownAccount([(0,
|
|
4670
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
|
|
3746
4671
|
writeSystemState(paths, state);
|
|
3747
4672
|
const enabled = state.components.collector.enabled;
|
|
3748
4673
|
if (enabled || wasActive) {
|
|
@@ -3759,13 +4684,16 @@ async function connectSystem(options) {
|
|
|
3759
4684
|
state
|
|
3760
4685
|
};
|
|
3761
4686
|
}
|
|
4687
|
+
function systemMachineDataPaths(paths) {
|
|
4688
|
+
return { database: hubDatabasePath(paths), queueTree: (0, import_node_path6.join)(paths.stateDir, "collector", "outbox") };
|
|
4689
|
+
}
|
|
3762
4690
|
async function upgradeSystemUnlocked(options) {
|
|
3763
4691
|
const host = options.host ?? createSystemHost();
|
|
3764
4692
|
const now = options.now ?? Date.now;
|
|
3765
4693
|
const paths = validateSystemPaths(options.paths);
|
|
3766
4694
|
if (systemStateIsLegacy(paths)) {
|
|
3767
|
-
const source2 = (0,
|
|
3768
|
-
if (!(0,
|
|
4695
|
+
const source2 = (0, import_node_path6.resolve)(options.source);
|
|
4696
|
+
if (!(0, import_node_fs6.existsSync)(source2)) throw new SystemError("missing-source", `release source does not exist: ${source2}`);
|
|
3769
4697
|
const role = legacySystemRole(paths);
|
|
3770
4698
|
const preset = componentsForRole(role);
|
|
3771
4699
|
const enable = options.enable ?? (options.start === true || unitNamesForComponents(preset).some((unit) => systemdEnabled(host, unit)));
|
|
@@ -3788,7 +4716,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3788
4716
|
],
|
|
3789
4717
|
files: [
|
|
3790
4718
|
{ path: stateFilePath(paths), mode: "0600", purpose: "canonical replacement state" },
|
|
3791
|
-
{ path: (0,
|
|
4719
|
+
{ path: (0, import_node_path6.join)(paths.stateDir, "backups"), mode: "0700", purpose: "removed with unfinished history" }
|
|
3792
4720
|
],
|
|
3793
4721
|
notes: [
|
|
3794
4722
|
"An unfinished installation is reset, not migrated; its history and credentials are removed.",
|
|
@@ -3821,6 +4749,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3821
4749
|
if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
|
|
3822
4750
|
const activeComponents = COMPONENTS.filter((component) => systemdActive(host, unitFor(component)));
|
|
3823
4751
|
const activeUnits = activeComponents.map(unitFor);
|
|
4752
|
+
const transitionPending = readMachineDataTransition(paths.stateDir) === null;
|
|
3824
4753
|
const plan = {
|
|
3825
4754
|
operation: "upgrade",
|
|
3826
4755
|
apply: options.apply === true,
|
|
@@ -3828,19 +4757,34 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3828
4757
|
components: { hub: state.components.hub.enabled, collector: state.components.collector.enabled },
|
|
3829
4758
|
paths,
|
|
3830
4759
|
accounts: state.accounts,
|
|
3831
|
-
commands: [
|
|
3832
|
-
|
|
4760
|
+
commands: [
|
|
4761
|
+
"stage new release",
|
|
4762
|
+
...activeUnits.map((unit) => `systemctl stop ${unit}`),
|
|
4763
|
+
"backup database",
|
|
4764
|
+
...transitionPending ? ["discard legacy machine data and collector queues once (irreversible after success)"] : [],
|
|
4765
|
+
"switch current",
|
|
4766
|
+
...activeUnits.map((unit) => `systemctl start ${unit}`)
|
|
4767
|
+
],
|
|
4768
|
+
files: [
|
|
4769
|
+
{ path: (0, import_node_path6.join)(paths.stateDir, "backups"), mode: "0700", purpose: "pre-upgrade database backup" },
|
|
4770
|
+
...transitionPending ? [{ path: (0, import_node_path6.join)(paths.stateDir, "machine-data-transition.json"), mode: "0600", purpose: "written once after the transition completes" }] : []
|
|
4771
|
+
],
|
|
3833
4772
|
notes: [
|
|
3834
4773
|
"Staging happens before any service is stopped.",
|
|
3835
4774
|
"The exact active component set is restored; --start does not start a component that was stopped.",
|
|
3836
|
-
"A failed post-upgrade start restores the previous release and matching database backup."
|
|
4775
|
+
"A failed post-upgrade start restores the previous release and matching database backup.",
|
|
4776
|
+
...transitionPending ? [
|
|
4777
|
+
"This upgrade completes the one-time RAM-first machine-data transition: telemetry rows, summaries, receipts, gaps, latest snapshots, inventory and machine-derived alert history are discarded, plus the pre-RAM-first durable collector queue. The transition's database copy restores the rows and the staged queue is renamed back if activation fails.",
|
|
4778
|
+
`The transition always keeps ${machineDataKeptNote()}.`,
|
|
4779
|
+
"Later upgrades keep the marker and preserve durable history and queues."
|
|
4780
|
+
] : ["The one-time machine-data transition already completed; durable history and collector queues are preserved."]
|
|
3837
4781
|
]
|
|
3838
4782
|
};
|
|
3839
4783
|
if (options.apply !== true) return { operation: "upgrade", ok: true, apply: false, message: "plan only", plan };
|
|
3840
4784
|
assertApply(host);
|
|
3841
4785
|
assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
|
|
3842
|
-
const source = (0,
|
|
3843
|
-
if (!(0,
|
|
4786
|
+
const source = (0, import_node_path6.resolve)(options.source);
|
|
4787
|
+
if (!(0, import_node_fs6.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
|
|
3844
4788
|
const staged = prepareStagedRelease({
|
|
3845
4789
|
source,
|
|
3846
4790
|
paths,
|
|
@@ -3859,21 +4803,22 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3859
4803
|
let backupPath = null;
|
|
3860
4804
|
let beforeSchema = null;
|
|
3861
4805
|
let release = null;
|
|
4806
|
+
let machineData = null;
|
|
3862
4807
|
try {
|
|
3863
4808
|
stopUnits(host, activeUnits);
|
|
3864
4809
|
assertDirectoryStopped(paths.stateDir);
|
|
3865
4810
|
const lock = systemLockState(paths);
|
|
3866
4811
|
if (lock.live) throw new SystemError("duplicate-runtime", `hub did not stop cleanly; lock remains at ${lock.path}`);
|
|
3867
4812
|
const database = hubDatabasePath(paths);
|
|
3868
|
-
if ((0,
|
|
3869
|
-
ensureDirectory((0,
|
|
4813
|
+
if ((0, import_node_fs6.existsSync)(database)) {
|
|
4814
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
|
|
3870
4815
|
beforeSchema = databaseSchema(database);
|
|
3871
|
-
backupPath = (0,
|
|
4816
|
+
backupPath = (0, import_node_path6.join)(paths.stateDir, "backups", `pre-upgrade-${staged.version}-${newestTimestamp()}.sqlite`);
|
|
3872
4817
|
await backupDatabase(database, backupPath, host);
|
|
3873
4818
|
}
|
|
3874
4819
|
release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
|
|
3875
|
-
replaceSymlink((0,
|
|
3876
|
-
replaceSymlink((0,
|
|
4820
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "previous"), oldCurrent, host);
|
|
4821
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), release.releaseDir, host);
|
|
3877
4822
|
state.previousVersion = state.currentVersion;
|
|
3878
4823
|
state.currentVersion = release.version;
|
|
3879
4824
|
state.releases[release.version] = {
|
|
@@ -3882,7 +4827,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3882
4827
|
installedAt: new Date(now()).toISOString()
|
|
3883
4828
|
};
|
|
3884
4829
|
if (backupPath !== null) {
|
|
3885
|
-
state.backups[(0,
|
|
4830
|
+
state.backups[(0, import_node_path6.basename)(backupPath)] = {
|
|
3886
4831
|
path: backupPath,
|
|
3887
4832
|
schemaVersion: beforeSchema,
|
|
3888
4833
|
fromSchema: beforeSchema,
|
|
@@ -3893,6 +4838,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3893
4838
|
};
|
|
3894
4839
|
}
|
|
3895
4840
|
writeSystemState(paths, state);
|
|
4841
|
+
machineData = performMachineDataCleanup(paths.stateDir, systemMachineDataPaths(paths), now);
|
|
3896
4842
|
if (activeComponents.length > 0) {
|
|
3897
4843
|
startUnits(host, activeUnits);
|
|
3898
4844
|
await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, activeComponents);
|
|
@@ -3905,6 +4851,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3905
4851
|
oldPrevious,
|
|
3906
4852
|
beforeSchema,
|
|
3907
4853
|
backupPath,
|
|
4854
|
+
machineData,
|
|
3908
4855
|
host,
|
|
3909
4856
|
activeComponents,
|
|
3910
4857
|
units: activeUnits
|
|
@@ -3912,6 +4859,10 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3912
4859
|
throw error;
|
|
3913
4860
|
}
|
|
3914
4861
|
const upgraded = release;
|
|
4862
|
+
if (machineData !== null) {
|
|
4863
|
+
completeMachineDataCleanup(paths.stateDir, machineData, upgraded.version, (path, content) => atomicWrite(path, content, 384));
|
|
4864
|
+
}
|
|
4865
|
+
const machineHistory = machineData?.report;
|
|
3915
4866
|
return {
|
|
3916
4867
|
operation: "upgrade",
|
|
3917
4868
|
ok: true,
|
|
@@ -3921,6 +4872,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3921
4872
|
current: upgraded.releaseDir,
|
|
3922
4873
|
previous: oldCurrent,
|
|
3923
4874
|
backupPath,
|
|
4875
|
+
...machineHistory ? { machineHistory } : {},
|
|
3924
4876
|
state
|
|
3925
4877
|
};
|
|
3926
4878
|
} finally {
|
|
@@ -3928,7 +4880,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3928
4880
|
}
|
|
3929
4881
|
}
|
|
3930
4882
|
function systemOperationLockPath(paths) {
|
|
3931
|
-
return (0,
|
|
4883
|
+
return (0, import_node_path6.join)(paths.prefix, "run", "operation.lock");
|
|
3932
4884
|
}
|
|
3933
4885
|
async function withSystemOperation(options, operation) {
|
|
3934
4886
|
if (!options.apply) return operation();
|
|
@@ -3936,7 +4888,7 @@ async function withSystemOperation(options, operation) {
|
|
|
3936
4888
|
assertApply(host);
|
|
3937
4889
|
const paths = validateSystemPaths(options.paths);
|
|
3938
4890
|
ensureDirectory(paths.prefix, 493);
|
|
3939
|
-
ensureDirectory((0,
|
|
4891
|
+
ensureDirectory((0, import_node_path6.join)(paths.prefix, "run"), 448, true);
|
|
3940
4892
|
const release = acquireProcessLease(systemOperationLockPath(paths));
|
|
3941
4893
|
try {
|
|
3942
4894
|
return await operation();
|
|
@@ -3988,24 +4940,24 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
3988
4940
|
try {
|
|
3989
4941
|
stopUnits(host, units);
|
|
3990
4942
|
assertDirectoryStopped(paths.stateDir);
|
|
3991
|
-
if (systemLockState(paths).live || lockIsLive((0,
|
|
4943
|
+
if (systemLockState(paths).live || lockIsLive((0, import_node_path6.join)(paths.stateDir, "collector/collector-runtime.lock")).live) {
|
|
3992
4944
|
throw new SystemError("runtime-active", "runtime lease remains after stop; rollback refused");
|
|
3993
4945
|
}
|
|
3994
4946
|
const database = hubDatabasePath(paths);
|
|
3995
|
-
const currentSchema = (0,
|
|
4947
|
+
const currentSchema = (0, import_node_fs6.existsSync)(database) ? databaseSchema(database) : null;
|
|
3996
4948
|
let backupToRestore = null;
|
|
3997
4949
|
if (currentSchema !== null && (previousSchema === null || currentSchema > previousSchema)) {
|
|
3998
4950
|
backupToRestore = pickRollbackBackup(state, previousSchema);
|
|
3999
|
-
if (backupToRestore === null || !(0,
|
|
4951
|
+
if (backupToRestore === null || !(0, import_node_fs6.existsSync)(backupToRestore.path)) {
|
|
4000
4952
|
throw new SystemError(
|
|
4001
4953
|
"incompatible-schema",
|
|
4002
4954
|
`current database schema ${currentSchema} is newer than release ${previousVersion}; no matching pre-upgrade backup is recorded`
|
|
4003
4955
|
);
|
|
4004
4956
|
}
|
|
4005
4957
|
}
|
|
4006
|
-
if ((0,
|
|
4007
|
-
ensureDirectory((0,
|
|
4008
|
-
safetyBackup = (0,
|
|
4958
|
+
if ((0, import_node_fs6.existsSync)(database)) {
|
|
4959
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
|
|
4960
|
+
safetyBackup = (0, import_node_path6.join)(paths.stateDir, "backups", `pre-rollback-${newestTimestamp()}.sqlite`);
|
|
4009
4961
|
await backupDatabase(database, safetyBackup, host);
|
|
4010
4962
|
}
|
|
4011
4963
|
if (backupToRestore !== null) {
|
|
@@ -4013,8 +4965,8 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
4013
4965
|
restoredDatabase = true;
|
|
4014
4966
|
runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, database], "restored database ownership");
|
|
4015
4967
|
}
|
|
4016
|
-
replaceSymlink((0,
|
|
4017
|
-
replaceSymlink((0,
|
|
4968
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "previous"), oldCurrent, host);
|
|
4969
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), previous, host);
|
|
4018
4970
|
state.previousVersion = state.currentVersion;
|
|
4019
4971
|
state.currentVersion = previousVersion;
|
|
4020
4972
|
state.updatedAt = new Date(now()).toISOString();
|
|
@@ -4036,10 +4988,10 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
4036
4988
|
};
|
|
4037
4989
|
} catch (error) {
|
|
4038
4990
|
stopUnits(host, units);
|
|
4039
|
-
replaceSymlink((0,
|
|
4040
|
-
replaceSymlink((0,
|
|
4991
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), oldCurrent, host);
|
|
4992
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "previous"), previous, host);
|
|
4041
4993
|
if (restoredDatabase && safetyBackup) {
|
|
4042
|
-
const failed = (0,
|
|
4994
|
+
const failed = (0, import_node_path6.join)(paths.stateDir, "backups", `failed-rollback-${newestTimestamp()}.sqlite`);
|
|
4043
4995
|
await backupDatabase(hubDatabasePath(paths), failed, host);
|
|
4044
4996
|
replaceDatabaseFromBackup(safetyBackup, hubDatabasePath(paths), null, host);
|
|
4045
4997
|
runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, hubDatabasePath(paths)], "restored database ownership");
|
|
@@ -4096,31 +5048,31 @@ function uninstallSystem(options = {}) {
|
|
|
4096
5048
|
assertDirectoryStopped(paths.stateDir);
|
|
4097
5049
|
for (const unit of units) runMaybe(host, "systemctl", ["disable", unit]);
|
|
4098
5050
|
for (const unit of units) {
|
|
4099
|
-
const path = (0,
|
|
5051
|
+
const path = (0, import_node_path6.join)(paths.unitsDir, unit);
|
|
4100
5052
|
assertManagedFileOwnership(paths, path);
|
|
4101
5053
|
try {
|
|
4102
|
-
(0,
|
|
5054
|
+
(0, import_node_fs6.unlinkSync)(path);
|
|
4103
5055
|
} catch {
|
|
4104
5056
|
}
|
|
4105
5057
|
}
|
|
4106
5058
|
runMaybe(host, "systemctl", ["daemon-reload"]);
|
|
4107
|
-
const hadPowerAccess = state?.power?.enabled === true || (0,
|
|
5059
|
+
const hadPowerAccess = state?.power?.enabled === true || (0, import_node_fs6.existsSync)(raplRulePath(paths)) || (0, import_node_fs6.existsSync)(powerOriginalPath(paths));
|
|
4108
5060
|
if (hadPowerAccess) {
|
|
4109
5061
|
try {
|
|
4110
|
-
(0,
|
|
5062
|
+
(0, import_node_fs6.unlinkSync)(raplRulePath(paths));
|
|
4111
5063
|
} catch {
|
|
4112
5064
|
}
|
|
4113
5065
|
try {
|
|
4114
|
-
(0,
|
|
5066
|
+
(0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
|
|
4115
5067
|
} catch {
|
|
4116
5068
|
}
|
|
4117
5069
|
restorePowerOriginals(paths, host);
|
|
4118
5070
|
resumePowerRule(paths, host);
|
|
4119
5071
|
}
|
|
4120
|
-
removeSymlink((0,
|
|
4121
|
-
removeSymlink((0,
|
|
4122
|
-
(0,
|
|
4123
|
-
if (options.preserveNode !== true) (0,
|
|
5072
|
+
removeSymlink((0, import_node_path6.join)(paths.prefix, "current"));
|
|
5073
|
+
removeSymlink((0, import_node_path6.join)(paths.prefix, "previous"));
|
|
5074
|
+
(0, import_node_fs6.rmSync)((0, import_node_path6.join)(paths.prefix, "releases"), { recursive: true, force: true });
|
|
5075
|
+
if (options.preserveNode !== true) (0, import_node_fs6.rmSync)((0, import_node_path6.join)(paths.prefix, "node"), { recursive: true, force: true });
|
|
4124
5076
|
if (state !== null) {
|
|
4125
5077
|
state.active = false;
|
|
4126
5078
|
state.power = null;
|
|
@@ -4133,8 +5085,8 @@ function uninstallSystem(options = {}) {
|
|
|
4133
5085
|
for (const group of [accounts.powerGroup, accounts.hub, accounts.collector]) runMaybe(host, "groupdel", [group]);
|
|
4134
5086
|
}
|
|
4135
5087
|
if (options.purgeData === true) {
|
|
4136
|
-
(0,
|
|
4137
|
-
(0,
|
|
5088
|
+
(0, import_node_fs6.rmSync)(paths.stateDir, { recursive: true, force: true });
|
|
5089
|
+
(0, import_node_fs6.rmSync)(paths.configDir, { recursive: true, force: true });
|
|
4138
5090
|
}
|
|
4139
5091
|
return {
|
|
4140
5092
|
operation: "uninstall",
|
|
@@ -4184,7 +5136,7 @@ function grantPower(options = {}) {
|
|
|
4184
5136
|
writeManagedFile(powerSysusersPath(paths), renderPowerSysusers(paths, accounts), 420, true);
|
|
4185
5137
|
runRequired(host, "systemd-sysusers", [], "power group creation");
|
|
4186
5138
|
assertSystemAccounts(host, accounts, true, ["collector"]);
|
|
4187
|
-
if (!(0,
|
|
5139
|
+
if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
|
|
4188
5140
|
writePowerRule(paths, accounts);
|
|
4189
5141
|
resumePowerRule(paths, host);
|
|
4190
5142
|
applyPowerAccess(paths, accounts, host);
|
|
@@ -4213,11 +5165,11 @@ function revokePower(options = {}) {
|
|
|
4213
5165
|
if (options.apply !== true) return { operation: "power-revoke", ok: true, apply: false, message: "plan only", plan };
|
|
4214
5166
|
assertApply(host);
|
|
4215
5167
|
try {
|
|
4216
|
-
(0,
|
|
5168
|
+
(0, import_node_fs6.unlinkSync)(raplRulePath(paths));
|
|
4217
5169
|
} catch {
|
|
4218
5170
|
}
|
|
4219
5171
|
try {
|
|
4220
|
-
(0,
|
|
5172
|
+
(0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
|
|
4221
5173
|
} catch {
|
|
4222
5174
|
}
|
|
4223
5175
|
restorePowerOriginals(paths, host);
|
|
@@ -4433,6 +5385,12 @@ function printMutationReport(report) {
|
|
|
4433
5385
|
console.log("then run `rigscope system connect https://<hub> --token-file <file> --apply`.");
|
|
4434
5386
|
}
|
|
4435
5387
|
}
|
|
5388
|
+
function printMachineDataOutcome(machineHistory) {
|
|
5389
|
+
if (machineHistory === void 0) return;
|
|
5390
|
+
const described = describeMachineDataReport(machineHistory);
|
|
5391
|
+
console.log(described.summary);
|
|
5392
|
+
if (described.warning) console.log(`Warning: ${described.warning}`);
|
|
5393
|
+
}
|
|
4436
5394
|
async function runSystemCommand(argv) {
|
|
4437
5395
|
const parsed = parseSystemArgs(argv);
|
|
4438
5396
|
if (parsed.command === "help") {
|
|
@@ -4504,7 +5462,7 @@ ${humanPlan(report.plan)}`);
|
|
|
4504
5462
|
if (parsed.command === "logs") {
|
|
4505
5463
|
const selected = validateSystemPaths(paths);
|
|
4506
5464
|
const component = componentFrom(parsed, false);
|
|
4507
|
-
const units = (component ? [unitFor(component)] : [UNIT_NAMES.hub, UNIT_NAMES.collector]).filter((unit) => (0,
|
|
5465
|
+
const units = (component ? [unitFor(component)] : [UNIT_NAMES.hub, UNIT_NAMES.collector]).filter((unit) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(selected.unitsDir, unit)));
|
|
4508
5466
|
if (units.length === 0) throw new SystemError("not-installed", component ? `${component} logs are unavailable; its unit is not installed` : "no system services installed");
|
|
4509
5467
|
const result = (0, import_node_child_process.spawnSync)("journalctl", [...units.flatMap((unit) => ["--unit", unit]), "--lines=100", "--no-pager", ...boolValue(values, "follow") ? ["--follow"] : []], { stdio: "inherit" });
|
|
4510
5468
|
if (result.error) throw result.error;
|
|
@@ -4580,6 +5538,7 @@ ${humanPlan(report.plan)}`);
|
|
|
4580
5538
|
else {
|
|
4581
5539
|
printMutationReport(report);
|
|
4582
5540
|
console.log(`Backup: ${report.backupPath ?? "none"}`);
|
|
5541
|
+
printMachineDataOutcome(report.machineHistory);
|
|
4583
5542
|
}
|
|
4584
5543
|
return 0;
|
|
4585
5544
|
}
|