rigscope 0.2.0 → 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 +2 -2
- 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 +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- 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 +2 -2
- package/.next/server/app/administration.segments/_full.segment.rsc +2 -2
- package/.next/server/app/administration.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +2 -2
- 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 +2 -2
- package/.next/server/app/alerts.segments/_full.segment.rsc +2 -2
- package/.next/server/app/alerts.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +2 -2
- 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 +3 -3
- package/.next/server/app/connections.segments/_full.segment.rsc +3 -3
- package/.next/server/app/connections.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +3 -3
- 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 +2 -2
- package/.next/server/app/history.segments/_full.segment.rsc +2 -2
- package/.next/server/app/history.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- 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 +2 -2
- package/.next/server/app/processes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/processes.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +2 -2
- package/.next/server/app-paths-manifest.json +2 -2
- package/.next/server/chunks/161.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/939-92930fde48061e20.js +1 -0
- package/.next/static/chunks/app/connections/page-7d18b901f2338f20.js +1 -0
- package/ARCHITECTURE.md +18 -6
- package/CHANGELOG.md +35 -0
- package/dist/cli.cjs +762 -509
- package/dist/collector.cjs +581 -115
- package/dist/hub.cjs +7 -3
- package/dist/system.cjs +1257 -363
- package/docs/configuration.md +1 -0
- package/docs/installation.md +9 -3
- package/docs/monitoring.md +6 -3
- package/docs/operations.md +40 -13
- package/docs/system-installation.md +11 -4
- package/docs/telemetry-storage.md +80 -9
- package/package.json +1 -1
- package/packaging/collector.env.example +6 -0
- package/packaging/system/rigscope-collector.service.example +3 -0
- package/.next/static/chunks/939-514e554087419c71.js +0 -1
- package/.next/static/chunks/app/connections/page-75abd16fc495fcbd.js +0 -1
- /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → JbaLjudQSxrMUD_VHPrnD}/_buildManifest.js +0 -0
- /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → 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
|
|
|
@@ -1085,6 +1112,851 @@ function boundedString(value, field, message, maxLength) {
|
|
|
1085
1112
|
return value;
|
|
1086
1113
|
}
|
|
1087
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
|
+
|
|
1088
1960
|
// src/cli/system.ts
|
|
1089
1961
|
var SYSTEM_SCOPE = "system";
|
|
1090
1962
|
var DEFAULT_SYSTEM_ACCOUNTS = {
|
|
@@ -1194,13 +2066,13 @@ function createSystemHost(overrides = {}) {
|
|
|
1194
2066
|
}
|
|
1195
2067
|
function defaultSystemPaths(overrides = {}) {
|
|
1196
2068
|
return {
|
|
1197
|
-
prefix: (0,
|
|
1198
|
-
configDir: (0,
|
|
1199
|
-
stateDir: (0,
|
|
1200
|
-
unitsDir: (0,
|
|
1201
|
-
sysusersDir: (0,
|
|
1202
|
-
udevRulesDir: (0,
|
|
1203
|
-
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")
|
|
1204
2076
|
};
|
|
1205
2077
|
}
|
|
1206
2078
|
function unitNamesFor(role) {
|
|
@@ -1215,11 +2087,11 @@ function rolesFor(role) {
|
|
|
1215
2087
|
return { hub: role !== "collector", collector: role !== "hub" };
|
|
1216
2088
|
}
|
|
1217
2089
|
function isWithin(parent, candidate) {
|
|
1218
|
-
const rel = (0,
|
|
1219
|
-
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);
|
|
1220
2092
|
}
|
|
1221
2093
|
function ensureAbsolute(path, label) {
|
|
1222
|
-
const absolute = (0,
|
|
2094
|
+
const absolute = (0, import_node_path6.resolve)(path);
|
|
1223
2095
|
if (/[\0\r\n]/.test(absolute)) throw new SystemError("unsafe-path", `${label} contains control characters`);
|
|
1224
2096
|
if (absolute === "/") throw new SystemError("unsafe-path", `${label} must not be the filesystem root`);
|
|
1225
2097
|
for (const forbidden of ["/home", "/root", "/proc", "/sys", "/dev", "/boot"]) {
|
|
@@ -1233,7 +2105,7 @@ function validateSystemPaths(input = {}) {
|
|
|
1233
2105
|
const paths = defaultSystemPaths(input);
|
|
1234
2106
|
for (const [label, value] of Object.entries(paths)) {
|
|
1235
2107
|
if (label === "powercapRoot") {
|
|
1236
|
-
if (!(0,
|
|
2108
|
+
if (!(0, import_node_path6.isAbsolute)(value) || /[\0\r\n]/.test(value) || (0, import_node_path6.resolve)(value) === "/") {
|
|
1237
2109
|
throw new SystemError("unsafe-path", `powercap root is unsafe: ${value}`);
|
|
1238
2110
|
}
|
|
1239
2111
|
} else {
|
|
@@ -1288,19 +2160,19 @@ function runRequired(host, command, args, label, options) {
|
|
|
1288
2160
|
function runMaybe(host, command, args, options) {
|
|
1289
2161
|
return host.run(command, args, options);
|
|
1290
2162
|
}
|
|
1291
|
-
function
|
|
1292
|
-
return (0,
|
|
2163
|
+
function sha256File2(path) {
|
|
2164
|
+
return (0, import_node_crypto7.createHash)("sha256").update((0, import_node_fs6.readFileSync)(path)).digest("hex");
|
|
1293
2165
|
}
|
|
1294
2166
|
function atomicWrite(path, content, mode) {
|
|
1295
|
-
(0,
|
|
1296
|
-
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)}`);
|
|
1297
2169
|
try {
|
|
1298
|
-
(0,
|
|
1299
|
-
(0,
|
|
1300
|
-
(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);
|
|
1301
2173
|
} catch (error) {
|
|
1302
2174
|
try {
|
|
1303
|
-
(0,
|
|
2175
|
+
(0, import_node_fs6.unlinkSync)(temporary);
|
|
1304
2176
|
} catch {
|
|
1305
2177
|
}
|
|
1306
2178
|
throw error;
|
|
@@ -1309,12 +2181,12 @@ function atomicWrite(path, content, mode) {
|
|
|
1309
2181
|
function ensureDirectory(path, mode, privateDirectory = false) {
|
|
1310
2182
|
let stat;
|
|
1311
2183
|
try {
|
|
1312
|
-
stat = (0,
|
|
2184
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
1313
2185
|
} catch (error) {
|
|
1314
2186
|
if (error.code !== "ENOENT") throw error;
|
|
1315
|
-
(0,
|
|
1316
|
-
(0,
|
|
1317
|
-
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);
|
|
1318
2190
|
}
|
|
1319
2191
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
1320
2192
|
throw new SystemError("unsafe-path", `${path} must be a real directory`);
|
|
@@ -1329,8 +2201,8 @@ function isDefaultSystemLayout(paths) {
|
|
|
1329
2201
|
}
|
|
1330
2202
|
function assertManagedPathSafety(paths, enforceRoot) {
|
|
1331
2203
|
for (const path of [paths.prefix, paths.configDir, paths.stateDir, paths.unitsDir, paths.sysusersDir, paths.udevRulesDir]) {
|
|
1332
|
-
if (!(0,
|
|
1333
|
-
const stat = (0,
|
|
2204
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2205
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1334
2206
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
1335
2207
|
throw new SystemError("unsafe-path", `${path} must be a real directory`);
|
|
1336
2208
|
}
|
|
@@ -1343,8 +2215,8 @@ function assertManagedPathSafety(paths, enforceRoot) {
|
|
|
1343
2215
|
}
|
|
1344
2216
|
}
|
|
1345
2217
|
function writeManagedFile(path, content, mode, replace) {
|
|
1346
|
-
if ((0,
|
|
1347
|
-
const stat = (0,
|
|
2218
|
+
if ((0, import_node_fs6.existsSync)(path)) {
|
|
2219
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1348
2220
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1349
2221
|
throw new SystemError("unsafe-path", `${path} must be a regular file, not a symlink`);
|
|
1350
2222
|
}
|
|
@@ -1383,7 +2255,7 @@ function parseEnvValue(value) {
|
|
|
1383
2255
|
function readEnvDocument(path) {
|
|
1384
2256
|
let raw;
|
|
1385
2257
|
try {
|
|
1386
|
-
raw = (0,
|
|
2258
|
+
raw = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
1387
2259
|
} catch (error) {
|
|
1388
2260
|
if (error.code === "ENOENT") return null;
|
|
1389
2261
|
throw error;
|
|
@@ -1430,43 +2302,43 @@ function connectDestinationUrl(value) {
|
|
|
1430
2302
|
return url.href;
|
|
1431
2303
|
}
|
|
1432
2304
|
function hubDatabasePath(paths) {
|
|
1433
|
-
return (0,
|
|
2305
|
+
return (0, import_node_path6.join)(paths.stateDir, "hub", "telemetry.sqlite");
|
|
1434
2306
|
}
|
|
1435
2307
|
function hubEnrollmentPath(paths) {
|
|
1436
|
-
return (0,
|
|
2308
|
+
return (0, import_node_path6.join)(paths.stateDir, "hub", "enrollment.json");
|
|
1437
2309
|
}
|
|
1438
2310
|
function hubSocketPath(paths) {
|
|
1439
|
-
return (0,
|
|
2311
|
+
return (0, import_node_path6.join)(paths.stateDir, "hub", "run", "hub.sock");
|
|
1440
2312
|
}
|
|
1441
2313
|
function collectorStatePath(paths) {
|
|
1442
|
-
return (0,
|
|
2314
|
+
return (0, import_node_path6.join)(paths.stateDir, "collector", "state.json");
|
|
1443
2315
|
}
|
|
1444
2316
|
function hubEnvPath(paths) {
|
|
1445
|
-
return (0,
|
|
2317
|
+
return (0, import_node_path6.join)(paths.configDir, "hub.env");
|
|
1446
2318
|
}
|
|
1447
2319
|
function collectorEnvPath(paths) {
|
|
1448
|
-
return (0,
|
|
2320
|
+
return (0, import_node_path6.join)(paths.configDir, "collector.env");
|
|
1449
2321
|
}
|
|
1450
2322
|
function sysusersPath(paths) {
|
|
1451
|
-
return (0,
|
|
2323
|
+
return (0, import_node_path6.join)(paths.sysusersDir, SYSUSERS_NAME);
|
|
1452
2324
|
}
|
|
1453
2325
|
function powerSysusersPath(paths) {
|
|
1454
|
-
return (0,
|
|
2326
|
+
return (0, import_node_path6.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
|
|
1455
2327
|
}
|
|
1456
2328
|
function raplRulePath(paths) {
|
|
1457
|
-
return (0,
|
|
2329
|
+
return (0, import_node_path6.join)(paths.udevRulesDir, RAPL_RULE_NAME);
|
|
1458
2330
|
}
|
|
1459
2331
|
function stateFilePath(paths) {
|
|
1460
|
-
return (0,
|
|
2332
|
+
return (0, import_node_path6.join)(paths.stateDir, STATE_FILE_NAME);
|
|
1461
2333
|
}
|
|
1462
2334
|
function readCollectorIdentity(path) {
|
|
1463
|
-
if (!(0,
|
|
1464
|
-
const stat = (0,
|
|
2335
|
+
if (!(0, import_node_fs6.existsSync)(path)) return null;
|
|
2336
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1465
2337
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
|
|
1466
2338
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be private (0600)`);
|
|
1467
2339
|
let raw;
|
|
1468
2340
|
try {
|
|
1469
|
-
raw = JSON.parse((0,
|
|
2341
|
+
raw = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
1470
2342
|
} catch {
|
|
1471
2343
|
throw new SystemError("invalid-file", `${path} is not valid JSON`);
|
|
1472
2344
|
}
|
|
@@ -1500,7 +2372,7 @@ function readCollectorIdentity(path) {
|
|
|
1500
2372
|
return { collectorId: record.collectorId, destinations };
|
|
1501
2373
|
}
|
|
1502
2374
|
function writeCollectorIdentity(path, identity) {
|
|
1503
|
-
ensureDirectory((0,
|
|
2375
|
+
ensureDirectory((0, import_node_path6.dirname)(path), 448, true);
|
|
1504
2376
|
atomicWrite(path, `${JSON.stringify(identity)}
|
|
1505
2377
|
`, 384);
|
|
1506
2378
|
}
|
|
@@ -1540,7 +2412,7 @@ function ensureHubOwner(paths) {
|
|
|
1540
2412
|
if (!Array.isArray(parsed)) throw new SystemError("invalid-config", "HUB_BROWSER_USERS must be a JSON array");
|
|
1541
2413
|
if (parsed.length > 0) return void 0;
|
|
1542
2414
|
}
|
|
1543
|
-
const password = (0,
|
|
2415
|
+
const password = (0, import_node_crypto7.randomBytes)(18).toString("base64url");
|
|
1544
2416
|
const users = [{ username: "owner", passwordHash: hashPassword(password), machineIds: "*", admin: true }];
|
|
1545
2417
|
writeEnvValue(hubEnvPath(paths), "HUB_BROWSER_USERS", JSON.stringify(users));
|
|
1546
2418
|
return password;
|
|
@@ -1591,8 +2463,8 @@ function updateHubCollectorRegistry(paths, identity) {
|
|
|
1591
2463
|
}
|
|
1592
2464
|
function registerCollectorInHubDatabase(paths, identity, host) {
|
|
1593
2465
|
const database = hubDatabasePath(paths);
|
|
1594
|
-
if (!(0,
|
|
1595
|
-
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 });
|
|
1596
2468
|
try {
|
|
1597
2469
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
|
|
1598
2470
|
if (!table) return { inserted: false };
|
|
@@ -1606,8 +2478,8 @@ function registerCollectorInHubDatabase(paths, identity, host) {
|
|
|
1606
2478
|
}
|
|
1607
2479
|
function findCollectorInHubDatabase(paths, collectorId) {
|
|
1608
2480
|
const database = hubDatabasePath(paths);
|
|
1609
|
-
if (!(0,
|
|
1610
|
-
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 });
|
|
1611
2483
|
try {
|
|
1612
2484
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
|
|
1613
2485
|
if (!table) return null;
|
|
@@ -1619,11 +2491,11 @@ function findCollectorInHubDatabase(paths, collectorId) {
|
|
|
1619
2491
|
}
|
|
1620
2492
|
function createLocalEnrollmentGrant(paths, host) {
|
|
1621
2493
|
const database = hubDatabasePath(paths);
|
|
1622
|
-
if (!(0,
|
|
1623
|
-
const token = (0,
|
|
1624
|
-
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");
|
|
1625
2497
|
const expiresAt = host.now() + 10 * 60 * 1e3;
|
|
1626
|
-
const db = new
|
|
2498
|
+
const db = new import_node_sqlite3.DatabaseSync(database, { timeout: 5e3 });
|
|
1627
2499
|
try {
|
|
1628
2500
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_grants'").get();
|
|
1629
2501
|
if (!table) throw new SystemError("hub-unavailable", "the hub registry is not initialized");
|
|
@@ -1673,7 +2545,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1673
2545
|
note: "the local hub has no reachable loopback or HTTPS destination"
|
|
1674
2546
|
};
|
|
1675
2547
|
}
|
|
1676
|
-
if (!(0,
|
|
2548
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, UNIT_NAMES.hub))) {
|
|
1677
2549
|
return { paired: false, awaiting: true, hubWasActive: false, note: "the local hub unit is not installed" };
|
|
1678
2550
|
}
|
|
1679
2551
|
const hubWasActive = systemdActive(host, UNIT_NAMES.hub);
|
|
@@ -1686,7 +2558,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1686
2558
|
destinations: [{ url: destination.url, token: registered.token, sharing: { ...SYSTEM_DEFAULT_SHARING } }]
|
|
1687
2559
|
};
|
|
1688
2560
|
writeCollectorIdentity(collectorStatePath(paths), adopted);
|
|
1689
|
-
chownAccount([(0,
|
|
2561
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1690
2562
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1691
2563
|
}
|
|
1692
2564
|
}
|
|
@@ -1694,7 +2566,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1694
2566
|
try {
|
|
1695
2567
|
grant = createLocalEnrollmentGrant(paths, host);
|
|
1696
2568
|
} catch (error) {
|
|
1697
|
-
chownAccount([(0,
|
|
2569
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1698
2570
|
return {
|
|
1699
2571
|
paired: false,
|
|
1700
2572
|
awaiting: true,
|
|
@@ -1712,18 +2584,18 @@ async function pairCollectorWithHub(options) {
|
|
|
1712
2584
|
note: `enrollment through the live hub failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1713
2585
|
};
|
|
1714
2586
|
} finally {
|
|
1715
|
-
chownAccount([(0,
|
|
2587
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1716
2588
|
}
|
|
1717
2589
|
if (!collectorConfigured(paths).configured) {
|
|
1718
2590
|
return { paired: false, awaiting: true, hubWasActive, note: "the hub accepted enrollment but no destination was saved" };
|
|
1719
2591
|
}
|
|
1720
|
-
chownAccount([(0,
|
|
2592
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1721
2593
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1722
2594
|
}
|
|
1723
2595
|
const identity = {
|
|
1724
|
-
collectorId: existing?.collectorId ?? (0,
|
|
1725
|
-
machineId: (0,
|
|
1726
|
-
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")
|
|
1727
2599
|
};
|
|
1728
2600
|
const registry = registerCollectorInHubDatabase(paths, identity, host);
|
|
1729
2601
|
let token = identity.token;
|
|
@@ -1750,7 +2622,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1750
2622
|
}
|
|
1751
2623
|
];
|
|
1752
2624
|
writeCollectorIdentity(collectorStatePath(paths), next);
|
|
1753
|
-
chownAccount([(0,
|
|
2625
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1754
2626
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1755
2627
|
}
|
|
1756
2628
|
function unitCommon(name, description, paths, account, envPath, execStart, extraService = []) {
|
|
@@ -1767,7 +2639,7 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
|
|
|
1767
2639
|
"Type=simple",
|
|
1768
2640
|
`User=${account}`,
|
|
1769
2641
|
`Group=${account}`,
|
|
1770
|
-
`WorkingDirectory=${(0,
|
|
2642
|
+
`WorkingDirectory=${(0, import_node_path6.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
|
|
1771
2643
|
`EnvironmentFile=${envPath.replace(/%/g, "%%")}`,
|
|
1772
2644
|
"Environment=NODE_ENV=production",
|
|
1773
2645
|
"Restart=on-failure",
|
|
@@ -1802,13 +2674,13 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
|
|
|
1802
2674
|
}
|
|
1803
2675
|
function renderSystemHubUnit(options) {
|
|
1804
2676
|
const { paths, accounts } = options;
|
|
1805
|
-
const hubScript = (0,
|
|
2677
|
+
const hubScript = (0, import_node_path6.join)(paths.prefix, "current", "dist", "hub.cjs");
|
|
1806
2678
|
const extra = [
|
|
1807
2679
|
`Environment=${systemdQuote(`HUB_DATABASE_PATH=${options.database}`)}`,
|
|
1808
2680
|
`Environment=${systemdQuote(`HUB_STATE_FILE=${options.enrollment}`)}`,
|
|
1809
2681
|
...options.remote ? [`Environment=${systemdQuote(`HUB_SOCKET_PATH=${options.socket}`)}`] : [],
|
|
1810
|
-
`ReadWritePaths=${systemdQuote((0,
|
|
1811
|
-
`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"))}`,
|
|
1812
2684
|
"PrivateDevices=yes",
|
|
1813
2685
|
"TimeoutStopSec=15s"
|
|
1814
2686
|
];
|
|
@@ -1827,14 +2699,14 @@ function renderSystemHubUnit(options) {
|
|
|
1827
2699
|
}
|
|
1828
2700
|
function renderSystemCollectorUnit(options) {
|
|
1829
2701
|
const { paths, accounts } = options;
|
|
1830
|
-
const collectorScript = (0,
|
|
2702
|
+
const collectorScript = (0, import_node_path6.join)(paths.prefix, "current", "dist", "collector.cjs");
|
|
1831
2703
|
const extra = [
|
|
1832
2704
|
...options.powerAccess ? [`SupplementaryGroups=${accounts.powerGroup}`] : [],
|
|
1833
2705
|
`Environment=${systemdQuote(`COLLECTOR_MODE=real`)}`,
|
|
1834
2706
|
`Environment=${systemdQuote(`COLLECTOR_STATE_FILE=${options.collectorState}`)}`,
|
|
1835
|
-
`Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0,
|
|
1836
|
-
`ReadWritePaths=${systemdQuote((0,
|
|
1837
|
-
`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"))}`,
|
|
1838
2710
|
"TimeoutStopSec=10s"
|
|
1839
2711
|
];
|
|
1840
2712
|
return [
|
|
@@ -1907,11 +2779,15 @@ function collectorEnvTemplate() {
|
|
|
1907
2779
|
"# COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect",
|
|
1908
2780
|
"# COLLECTOR_ENROLL_TOKEN=<one-use grant>",
|
|
1909
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",
|
|
1910
2786
|
""
|
|
1911
2787
|
].join("\n");
|
|
1912
2788
|
}
|
|
1913
2789
|
function assertPrivateSourceFile(path, label) {
|
|
1914
|
-
const stat = (0,
|
|
2790
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1915
2791
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1916
2792
|
throw new SystemError("unsafe-adoption", `${label} must be a regular file: ${path}`);
|
|
1917
2793
|
}
|
|
@@ -1922,7 +2798,7 @@ function assertPrivateSourceFile(path, label) {
|
|
|
1922
2798
|
function readJsonObject(path) {
|
|
1923
2799
|
let raw;
|
|
1924
2800
|
try {
|
|
1925
|
-
raw = (0,
|
|
2801
|
+
raw = (0, import_node_fs6.readFileSync)(path, "utf8");
|
|
1926
2802
|
} catch (error) {
|
|
1927
2803
|
throw new SystemError("missing-file", `cannot read ${path}: ${error.message}`);
|
|
1928
2804
|
}
|
|
@@ -1945,8 +2821,8 @@ function isCanonicalComponents(value) {
|
|
|
1945
2821
|
}
|
|
1946
2822
|
function readSystemState(paths) {
|
|
1947
2823
|
const path = stateFilePath(paths);
|
|
1948
|
-
if (!(0,
|
|
1949
|
-
const stat = (0,
|
|
2824
|
+
if (!(0, import_node_fs6.existsSync)(path)) return null;
|
|
2825
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
1950
2826
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
|
|
1951
2827
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be 0600`);
|
|
1952
2828
|
const raw = readJsonObject(path);
|
|
@@ -1978,7 +2854,7 @@ function readSystemState(paths) {
|
|
|
1978
2854
|
}
|
|
1979
2855
|
function systemStateIsLegacy(paths) {
|
|
1980
2856
|
const path = stateFilePath(paths);
|
|
1981
|
-
if (!(0,
|
|
2857
|
+
if (!(0, import_node_fs6.existsSync)(path)) return false;
|
|
1982
2858
|
let raw;
|
|
1983
2859
|
try {
|
|
1984
2860
|
raw = readJsonObject(path);
|
|
@@ -2029,24 +2905,24 @@ function validateVersion(value) {
|
|
|
2029
2905
|
return value;
|
|
2030
2906
|
}
|
|
2031
2907
|
function releaseVersion(root) {
|
|
2032
|
-
const metadata = readJsonObject((0,
|
|
2908
|
+
const metadata = readJsonObject((0, import_node_path6.join)(root, "package.json"));
|
|
2033
2909
|
if (Object.hasOwn(metadata, "rigscopeConfigVersion")) {
|
|
2034
2910
|
throw new SystemError("invalid-release", "release uses unfinished configuration metadata; select a canonical release");
|
|
2035
2911
|
}
|
|
2036
2912
|
return validateVersion(metadata.version);
|
|
2037
2913
|
}
|
|
2038
2914
|
function releaseSchemaVersion(root) {
|
|
2039
|
-
const manifestPath = (0,
|
|
2040
|
-
if (!(0,
|
|
2915
|
+
const manifestPath = (0, import_node_path6.join)(root, "manifest.json");
|
|
2916
|
+
if (!(0, import_node_fs6.existsSync)(manifestPath)) return null;
|
|
2041
2917
|
const manifest = readJsonObject(manifestPath);
|
|
2042
2918
|
return Number.isSafeInteger(manifest.schemaVersion) ? Number(manifest.schemaVersion) : null;
|
|
2043
2919
|
}
|
|
2044
2920
|
function assertReleaseArtifacts(root) {
|
|
2045
2921
|
for (const artifact of REQUIRED_ARTIFACTS) {
|
|
2046
|
-
const path = (0,
|
|
2922
|
+
const path = (0, import_node_path6.join)(root, artifact);
|
|
2047
2923
|
let stat;
|
|
2048
2924
|
try {
|
|
2049
|
-
stat = (0,
|
|
2925
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
2050
2926
|
} catch {
|
|
2051
2927
|
throw new SystemError("missing-artifact", `release is missing ${artifact}`);
|
|
2052
2928
|
}
|
|
@@ -2054,12 +2930,12 @@ function assertReleaseArtifacts(root) {
|
|
|
2054
2930
|
throw new SystemError("invalid-artifact", `release artifact is not a non-empty regular file: ${artifact}`);
|
|
2055
2931
|
}
|
|
2056
2932
|
}
|
|
2057
|
-
const buildId = (0,
|
|
2933
|
+
const buildId = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, ".next", "BUILD_ID"), "utf8").trim();
|
|
2058
2934
|
if (buildId === "" || /\s/.test(buildId)) throw new SystemError("invalid-artifact", ".next/BUILD_ID is invalid");
|
|
2059
2935
|
for (const directory of [".next/server", ".next/static"]) {
|
|
2060
2936
|
let stat;
|
|
2061
2937
|
try {
|
|
2062
|
-
stat = (0,
|
|
2938
|
+
stat = (0, import_node_fs6.lstatSync)((0, import_node_path6.join)(root, directory));
|
|
2063
2939
|
} catch {
|
|
2064
2940
|
throw new SystemError("missing-artifact", `release is missing ${directory}/`);
|
|
2065
2941
|
}
|
|
@@ -2067,10 +2943,10 @@ function assertReleaseArtifacts(root) {
|
|
|
2067
2943
|
}
|
|
2068
2944
|
}
|
|
2069
2945
|
function verifyReleaseManifest(root) {
|
|
2070
|
-
const manifestPath = (0,
|
|
2071
|
-
if (!(0,
|
|
2946
|
+
const manifestPath = (0, import_node_path6.join)(root, "manifest.json");
|
|
2947
|
+
if (!(0, import_node_fs6.existsSync)(manifestPath)) return;
|
|
2072
2948
|
const manifest = readJsonObject(manifestPath);
|
|
2073
|
-
const packageJson = readJsonObject((0,
|
|
2949
|
+
const packageJson = readJsonObject((0, import_node_path6.join)(root, "package.json"));
|
|
2074
2950
|
if (manifest.name !== void 0 && manifest.name !== packageJson.name) {
|
|
2075
2951
|
throw new SystemError("manifest-mismatch", "release manifest name does not match package.json");
|
|
2076
2952
|
}
|
|
@@ -2089,17 +2965,17 @@ function verifyReleaseManifest(root) {
|
|
|
2089
2965
|
if (typeof relativePath !== "string" || relativePath === "" || relativePath.startsWith("/") || relativePath.includes("\\") || relativePath.split("/").includes("..")) {
|
|
2090
2966
|
throw new SystemError("invalid-manifest", `release manifest contains an unsafe path: ${String(relativePath)}`);
|
|
2091
2967
|
}
|
|
2092
|
-
const absolute = (0,
|
|
2093
|
-
const stat = (0,
|
|
2968
|
+
const absolute = (0, import_node_path6.join)(root, relativePath);
|
|
2969
|
+
const stat = (0, import_node_fs6.lstatSync)(absolute);
|
|
2094
2970
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("invalid-manifest", `release artifact is not a regular file: ${relativePath}`);
|
|
2095
2971
|
if (entry.bytes !== stat.size) throw new SystemError("artifact-mismatch", `release artifact size changed: ${relativePath}`);
|
|
2096
|
-
if (entry.sha256 !==
|
|
2972
|
+
if (entry.sha256 !== sha256File2(absolute)) throw new SystemError("artifact-mismatch", `release artifact hash changed: ${relativePath}`);
|
|
2097
2973
|
listed.add(relativePath);
|
|
2098
2974
|
}
|
|
2099
2975
|
const visit = (current) => {
|
|
2100
|
-
for (const entry of (0,
|
|
2101
|
-
const absolute = (0,
|
|
2102
|
-
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("/");
|
|
2103
2979
|
if (rel === "node_modules" || rel.startsWith("node_modules/") || rel === ".next/cache" || rel.startsWith(".next/cache/")) continue;
|
|
2104
2980
|
if (entry.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${rel}`);
|
|
2105
2981
|
if (entry.isDirectory()) {
|
|
@@ -2114,49 +2990,49 @@ function verifyReleaseManifest(root) {
|
|
|
2114
2990
|
}
|
|
2115
2991
|
function assertNoSymlinks(root) {
|
|
2116
2992
|
const visit = (path) => {
|
|
2117
|
-
const stat = (0,
|
|
2118
|
-
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)}`);
|
|
2119
2995
|
if (!stat.isDirectory()) return;
|
|
2120
|
-
for (const entry of (0,
|
|
2996
|
+
for (const entry of (0, import_node_fs6.readdirSync)(path)) visit((0, import_node_path6.join)(path, entry));
|
|
2121
2997
|
};
|
|
2122
2998
|
visit(root);
|
|
2123
2999
|
}
|
|
2124
3000
|
function copyReleaseTree(source, destination) {
|
|
2125
3001
|
const visit = (current, target) => {
|
|
2126
|
-
const stat = (0,
|
|
2127
|
-
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)}`);
|
|
2128
3004
|
if (stat.isDirectory()) {
|
|
2129
|
-
(0,
|
|
2130
|
-
for (const entry of (0,
|
|
2131
|
-
const child = (0,
|
|
2132
|
-
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);
|
|
2133
3009
|
if (rel === ".git" || rel === ".next-dev" || rel === ".next/cache" || rel === "node_modules" || rel.startsWith("node_modules/")) {
|
|
2134
3010
|
continue;
|
|
2135
3011
|
}
|
|
2136
|
-
visit(child, (0,
|
|
3012
|
+
visit(child, (0, import_node_path6.join)(target, entry));
|
|
2137
3013
|
}
|
|
2138
3014
|
return;
|
|
2139
3015
|
}
|
|
2140
|
-
if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0,
|
|
2141
|
-
(0,
|
|
2142
|
-
(0,
|
|
2143
|
-
(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);
|
|
2144
3020
|
};
|
|
2145
3021
|
visit(source, destination);
|
|
2146
3022
|
}
|
|
2147
3023
|
function copyReleaseAllowlisted(source, destination) {
|
|
2148
|
-
for (const entry of (0,
|
|
3024
|
+
for (const entry of (0, import_node_fs6.readdirSync)(source)) {
|
|
2149
3025
|
if (!PACKAGE_TOP_LEVEL.has(entry)) continue;
|
|
2150
3026
|
if (entry === "node_modules") continue;
|
|
2151
|
-
const child = (0,
|
|
2152
|
-
const target = (0,
|
|
2153
|
-
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);
|
|
2154
3030
|
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${entry}`);
|
|
2155
3031
|
if (stat.isDirectory()) {
|
|
2156
3032
|
copyReleaseTree(child, target);
|
|
2157
3033
|
} else if (stat.isFile()) {
|
|
2158
|
-
(0,
|
|
2159
|
-
(0,
|
|
3034
|
+
(0, import_node_fs6.copyFileSync)(child, target);
|
|
3035
|
+
(0, import_node_fs6.chmodSync)(target, 420);
|
|
2160
3036
|
}
|
|
2161
3037
|
}
|
|
2162
3038
|
}
|
|
@@ -2191,28 +3067,28 @@ function extractArchive(archive, stage, host) {
|
|
|
2191
3067
|
}
|
|
2192
3068
|
function discardStagedRelease(staged) {
|
|
2193
3069
|
if (staged === null) return;
|
|
2194
|
-
(0,
|
|
3070
|
+
(0, import_node_fs6.rmSync)(staged.stageDir, { recursive: true, force: true });
|
|
2195
3071
|
}
|
|
2196
3072
|
function releaseTargetDir(paths, version) {
|
|
2197
|
-
return (0,
|
|
3073
|
+
return (0, import_node_path6.join)(paths.prefix, "releases", version);
|
|
2198
3074
|
}
|
|
2199
3075
|
function assertReleaseTargetAvailable(paths, releaseDir, replaceRelease) {
|
|
2200
|
-
if (!(0,
|
|
3076
|
+
if (!(0, import_node_fs6.existsSync)(releaseDir)) return;
|
|
2201
3077
|
const current = currentReleaseDir(paths);
|
|
2202
3078
|
const previous = previousReleaseDir(paths);
|
|
2203
3079
|
if (!replaceRelease || current === releaseDir || previous === releaseDir) {
|
|
2204
3080
|
throw new SystemError(
|
|
2205
3081
|
"release-exists",
|
|
2206
|
-
`release ${(0,
|
|
3082
|
+
`release ${(0, import_node_path6.basename)(releaseDir)} is already installed${current === releaseDir || previous === releaseDir ? " or retained" : ""}; choose a new version`
|
|
2207
3083
|
);
|
|
2208
3084
|
}
|
|
2209
3085
|
}
|
|
2210
3086
|
function stageRelease(options) {
|
|
2211
|
-
const source = (0,
|
|
2212
|
-
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);
|
|
2213
3089
|
ensureDirectory(options.paths.prefix, 493);
|
|
2214
|
-
const stageDir = (0,
|
|
2215
|
-
(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 });
|
|
2216
3092
|
try {
|
|
2217
3093
|
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
2218
3094
|
copyReleaseAllowlisted(source, stageDir);
|
|
@@ -2230,7 +3106,7 @@ function stageRelease(options) {
|
|
|
2230
3106
|
artifacts: [...REQUIRED_ARTIFACTS]
|
|
2231
3107
|
};
|
|
2232
3108
|
} catch (error) {
|
|
2233
|
-
(0,
|
|
3109
|
+
(0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
|
|
2234
3110
|
throw error;
|
|
2235
3111
|
}
|
|
2236
3112
|
}
|
|
@@ -2256,13 +3132,13 @@ function prepareStagedRelease(options) {
|
|
|
2256
3132
|
}
|
|
2257
3133
|
}
|
|
2258
3134
|
function commitStagedRelease(options) {
|
|
2259
|
-
const releasesDir = (0,
|
|
3135
|
+
const releasesDir = (0, import_node_path6.join)(options.paths.prefix, "releases");
|
|
2260
3136
|
ensureDirectory(releasesDir, 493);
|
|
2261
3137
|
const releaseDir = releaseTargetDir(options.paths, options.staged.version);
|
|
2262
3138
|
assertReleaseTargetAvailable(options.paths, releaseDir, options.replaceRelease);
|
|
2263
|
-
if ((0,
|
|
2264
|
-
(0,
|
|
2265
|
-
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);
|
|
2266
3142
|
return {
|
|
2267
3143
|
version: options.staged.version,
|
|
2268
3144
|
releaseDir,
|
|
@@ -2271,20 +3147,20 @@ function commitStagedRelease(options) {
|
|
|
2271
3147
|
};
|
|
2272
3148
|
}
|
|
2273
3149
|
function dependencyNames(root) {
|
|
2274
|
-
const pkg = readJsonObject((0,
|
|
3150
|
+
const pkg = readJsonObject((0, import_node_path6.join)(root, "package.json"));
|
|
2275
3151
|
const dependencies = pkg.dependencies;
|
|
2276
3152
|
if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) return [];
|
|
2277
3153
|
return Object.keys(dependencies).filter((name) => /^[A-Za-z0-9@/._-]+$/.test(name));
|
|
2278
3154
|
}
|
|
2279
3155
|
function missingDependencies(root) {
|
|
2280
|
-
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")));
|
|
2281
3157
|
}
|
|
2282
3158
|
function installDependencies(root, host, command = "npm") {
|
|
2283
|
-
const hasLock = (0,
|
|
3159
|
+
const hasLock = (0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, "package-lock.json"));
|
|
2284
3160
|
const args = hasLock ? ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
2285
|
-
const siblingNpm = (0,
|
|
2286
|
-
const executable = command === "npm" && (0,
|
|
2287
|
-
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);
|
|
2288
3164
|
const path = [nodeBin, process.env.PATH ?? ""].filter(Boolean).join(":");
|
|
2289
3165
|
runRequired(host, executable, args, "production dependency installation", {
|
|
2290
3166
|
cwd: root,
|
|
@@ -2293,21 +3169,21 @@ function installDependencies(root, host, command = "npm") {
|
|
|
2293
3169
|
});
|
|
2294
3170
|
}
|
|
2295
3171
|
function currentReleaseDir(paths) {
|
|
2296
|
-
const link = (0,
|
|
3172
|
+
const link = (0, import_node_path6.join)(paths.prefix, "current");
|
|
2297
3173
|
try {
|
|
2298
|
-
const target = (0,
|
|
2299
|
-
const absolute = (0,
|
|
2300
|
-
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;
|
|
2301
3177
|
} catch {
|
|
2302
3178
|
return null;
|
|
2303
3179
|
}
|
|
2304
3180
|
}
|
|
2305
3181
|
function previousReleaseDir(paths) {
|
|
2306
|
-
const link = (0,
|
|
3182
|
+
const link = (0, import_node_path6.join)(paths.prefix, "previous");
|
|
2307
3183
|
try {
|
|
2308
|
-
const target = (0,
|
|
2309
|
-
const absolute = (0,
|
|
2310
|
-
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;
|
|
2311
3187
|
} catch {
|
|
2312
3188
|
return null;
|
|
2313
3189
|
}
|
|
@@ -2315,11 +3191,11 @@ function previousReleaseDir(paths) {
|
|
|
2315
3191
|
function replaceSymlink(link, target, host) {
|
|
2316
3192
|
const temporary = `${link}.tmp-${process.pid}-${Date.now()}`;
|
|
2317
3193
|
try {
|
|
2318
|
-
(0,
|
|
2319
|
-
(0,
|
|
3194
|
+
(0, import_node_fs6.symlinkSync)(target, temporary);
|
|
3195
|
+
(0, import_node_fs6.renameSync)(temporary, link);
|
|
2320
3196
|
} catch (error) {
|
|
2321
3197
|
try {
|
|
2322
|
-
(0,
|
|
3198
|
+
(0, import_node_fs6.unlinkSync)(temporary);
|
|
2323
3199
|
} catch {
|
|
2324
3200
|
}
|
|
2325
3201
|
throw error;
|
|
@@ -2328,8 +3204,8 @@ function replaceSymlink(link, target, host) {
|
|
|
2328
3204
|
}
|
|
2329
3205
|
function removeSymlink(link) {
|
|
2330
3206
|
try {
|
|
2331
|
-
const stat = (0,
|
|
2332
|
-
if (stat.isSymbolicLink()) (0,
|
|
3207
|
+
const stat = (0, import_node_fs6.lstatSync)(link);
|
|
3208
|
+
if (stat.isSymbolicLink()) (0, import_node_fs6.unlinkSync)(link);
|
|
2333
3209
|
} catch {
|
|
2334
3210
|
}
|
|
2335
3211
|
}
|
|
@@ -2342,18 +3218,18 @@ function nodeVersion(host, nodePath) {
|
|
|
2342
3218
|
return output;
|
|
2343
3219
|
}
|
|
2344
3220
|
function ensureRootOwnedNode(options) {
|
|
2345
|
-
const selected = (0,
|
|
2346
|
-
const real = (0,
|
|
3221
|
+
const selected = (0, import_node_path6.resolve)(options.nodePath ?? process.execPath);
|
|
3222
|
+
const real = (0, import_node_fs6.realpathSync)(selected);
|
|
2347
3223
|
nodeVersion(options.host, real);
|
|
2348
|
-
const stat = (0,
|
|
2349
|
-
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);
|
|
2350
3226
|
const unsafe = stat.uid !== 0 || (stat.mode & 18) !== 0 || options.copyNode === true || insideRelease;
|
|
2351
3227
|
if (!unsafe || options.trustedNode === true) return real;
|
|
2352
|
-
const target = (0,
|
|
3228
|
+
const target = (0, import_node_path6.join)(options.paths.prefix, "node", "node");
|
|
2353
3229
|
if (!options.apply) return target;
|
|
2354
|
-
ensureDirectory((0,
|
|
2355
|
-
(0,
|
|
2356
|
-
(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);
|
|
2357
3233
|
runRequired(options.host, "chown", ["root:root", target], "node ownership");
|
|
2358
3234
|
nodeVersion(options.host, target);
|
|
2359
3235
|
return target;
|
|
@@ -2382,8 +3258,8 @@ function validatedOrigin(value) {
|
|
|
2382
3258
|
return value;
|
|
2383
3259
|
}
|
|
2384
3260
|
function readAdoptionSetup(source) {
|
|
2385
|
-
const configPath = (0,
|
|
2386
|
-
if (!(0,
|
|
3261
|
+
const configPath = (0, import_node_path6.join)(source, "config.json");
|
|
3262
|
+
if (!(0, import_node_fs6.existsSync)(configPath)) {
|
|
2387
3263
|
throw new SystemError("invalid-adoption", "adopted installation has no config.json");
|
|
2388
3264
|
}
|
|
2389
3265
|
assertPrivateSourceFile(configPath, "adopted setup");
|
|
@@ -2431,35 +3307,35 @@ function detectUserInstallations(options = {}) {
|
|
|
2431
3307
|
const roots = options.homeRoots ?? ["/home", "/root"];
|
|
2432
3308
|
const found = [];
|
|
2433
3309
|
for (const homeRoot of roots) {
|
|
2434
|
-
if (!(0,
|
|
2435
|
-
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));
|
|
2436
3312
|
for (const home of homes) {
|
|
2437
3313
|
let stat;
|
|
2438
3314
|
try {
|
|
2439
|
-
stat = (0,
|
|
3315
|
+
stat = (0, import_node_fs6.lstatSync)(home);
|
|
2440
3316
|
} catch {
|
|
2441
3317
|
continue;
|
|
2442
3318
|
}
|
|
2443
3319
|
if (!stat.isDirectory()) continue;
|
|
2444
|
-
const unitDir = (0,
|
|
3320
|
+
const unitDir = (0, import_node_path6.join)(home, ".config", "systemd", "user");
|
|
2445
3321
|
let units = [];
|
|
2446
3322
|
try {
|
|
2447
|
-
units = (0,
|
|
3323
|
+
units = (0, import_node_fs6.readdirSync)(unitDir);
|
|
2448
3324
|
} catch {
|
|
2449
3325
|
units = [];
|
|
2450
3326
|
}
|
|
2451
3327
|
for (const unitName of units) {
|
|
2452
3328
|
if (!/^rigscope(?:-[0-9a-f]{12})?(?:-(?:hub|collector))?\.service$/.test(unitName)) continue;
|
|
2453
|
-
const unitPath = (0,
|
|
3329
|
+
const unitPath = (0, import_node_path6.join)(unitDir, unitName);
|
|
2454
3330
|
let execStart = null;
|
|
2455
3331
|
try {
|
|
2456
|
-
const line = (0,
|
|
3332
|
+
const line = (0, import_node_fs6.readFileSync)(unitPath, "utf8").split("\n").find((item) => item.startsWith("ExecStart="));
|
|
2457
3333
|
execStart = line ? line.slice("ExecStart=".length) : null;
|
|
2458
3334
|
} catch {
|
|
2459
3335
|
}
|
|
2460
|
-
const stateCandidates = [(0,
|
|
2461
|
-
const stateDir = stateCandidates.find((candidate) => (0,
|
|
2462
|
-
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 });
|
|
2463
3339
|
}
|
|
2464
3340
|
}
|
|
2465
3341
|
}
|
|
@@ -2467,9 +3343,9 @@ function detectUserInstallations(options = {}) {
|
|
|
2467
3343
|
}
|
|
2468
3344
|
function lockIsLive(path) {
|
|
2469
3345
|
try {
|
|
2470
|
-
const stat = (0,
|
|
3346
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
2471
3347
|
if (!stat.isFile() || stat.size > 4096) return { exists: true, live: false, pid: null, ambiguous: true };
|
|
2472
|
-
const owner = JSON.parse((0,
|
|
3348
|
+
const owner = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
2473
3349
|
const pid = owner.pid;
|
|
2474
3350
|
if (!Number.isSafeInteger(pid) || pid < 1) return { exists: true, live: false, pid: null, ambiguous: true };
|
|
2475
3351
|
try {
|
|
@@ -2487,9 +3363,9 @@ function lockIsLive(path) {
|
|
|
2487
3363
|
}
|
|
2488
3364
|
function directoryLocks(directory) {
|
|
2489
3365
|
return [
|
|
2490
|
-
(0,
|
|
2491
|
-
(0,
|
|
2492
|
-
(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")
|
|
2493
3369
|
];
|
|
2494
3370
|
}
|
|
2495
3371
|
function assertDirectoryStopped(directory, scope = "local") {
|
|
@@ -2520,13 +3396,13 @@ function assertSystemStartAllowed(paths, components) {
|
|
|
2520
3396
|
}
|
|
2521
3397
|
function detectPowerDriver(moduleRoot = "/sys/module") {
|
|
2522
3398
|
const candidates = ["intel_rapl_msr", "intel_rapl_common", "intel_rapl_tpmi", "intel_rapl", "amd_energy"];
|
|
2523
|
-
return candidates.filter((name) => (0,
|
|
3399
|
+
return candidates.filter((name) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(moduleRoot, name)));
|
|
2524
3400
|
}
|
|
2525
3401
|
function readPasswd(path = "/etc/passwd") {
|
|
2526
3402
|
const users = /* @__PURE__ */ new Map();
|
|
2527
3403
|
let lines;
|
|
2528
3404
|
try {
|
|
2529
|
-
lines = (0,
|
|
3405
|
+
lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
|
|
2530
3406
|
} catch {
|
|
2531
3407
|
return users;
|
|
2532
3408
|
}
|
|
@@ -2543,7 +3419,7 @@ function readGroups(path = "/etc/group") {
|
|
|
2543
3419
|
const groups = /* @__PURE__ */ new Map();
|
|
2544
3420
|
let lines;
|
|
2545
3421
|
try {
|
|
2546
|
-
lines = (0,
|
|
3422
|
+
lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
|
|
2547
3423
|
} catch {
|
|
2548
3424
|
return groups;
|
|
2549
3425
|
}
|
|
@@ -2571,7 +3447,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2571
3447
|
const sysusers = options.sysusersPath ?? powerSysusersPath(paths);
|
|
2572
3448
|
let ruleText = null;
|
|
2573
3449
|
try {
|
|
2574
|
-
ruleText = (0,
|
|
3450
|
+
ruleText = (0, import_node_fs6.readFileSync)(rulePath, "utf8");
|
|
2575
3451
|
} catch {
|
|
2576
3452
|
ruleText = null;
|
|
2577
3453
|
}
|
|
@@ -2588,27 +3464,27 @@ function inspectPowerAccess(options = {}) {
|
|
|
2588
3464
|
const domains = [];
|
|
2589
3465
|
let entries = [];
|
|
2590
3466
|
try {
|
|
2591
|
-
entries = (0,
|
|
3467
|
+
entries = (0, import_node_fs6.readdirSync)(paths.powercapRoot);
|
|
2592
3468
|
} catch {
|
|
2593
3469
|
entries = [];
|
|
2594
3470
|
}
|
|
2595
3471
|
for (const entry of entries.filter((name) => /^intel-rapl:\d+$/.test(name)).sort()) {
|
|
2596
|
-
const domainPath = (0,
|
|
3472
|
+
const domainPath = (0, import_node_path6.join)(paths.powercapRoot, entry);
|
|
2597
3473
|
let name = entry;
|
|
2598
3474
|
try {
|
|
2599
|
-
name = (0,
|
|
3475
|
+
name = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(domainPath, "name"), "utf8").trim() || entry;
|
|
2600
3476
|
} catch {
|
|
2601
3477
|
}
|
|
2602
3478
|
if (!/^package/.test(name)) continue;
|
|
2603
|
-
const energyPath = (0,
|
|
2604
|
-
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");
|
|
2605
3481
|
const readStat = (path) => {
|
|
2606
3482
|
if (options.statOverride) {
|
|
2607
3483
|
const override = options.statOverride(path);
|
|
2608
3484
|
return override === null ? null : { mode: modeString(override.mode), uid: override.uid, gid: override.gid };
|
|
2609
3485
|
}
|
|
2610
3486
|
try {
|
|
2611
|
-
const stat = (0,
|
|
3487
|
+
const stat = (0, import_node_fs6.statSync)(path);
|
|
2612
3488
|
return { mode: modeString(stat.mode), uid: stat.uid, gid: stat.gid };
|
|
2613
3489
|
} catch {
|
|
2614
3490
|
return null;
|
|
@@ -2642,7 +3518,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2642
3518
|
problems.push(`collector account ${accounts.collector} is not a member of ${accounts.powerGroup}`);
|
|
2643
3519
|
}
|
|
2644
3520
|
if (!compliant) problems.push(`persistent udev rule is missing or not compliant: ${rulePath}`);
|
|
2645
|
-
if (!(0,
|
|
3521
|
+
if (!(0, import_node_fs6.existsSync)(sysusers)) problems.push(`power sysusers definition is missing: ${sysusers}`);
|
|
2646
3522
|
for (const domain of domains) {
|
|
2647
3523
|
if (!domain.energyExists) problems.push(`${domain.name}: energy_uj is unavailable`);
|
|
2648
3524
|
else if (domain.energy?.worldReadable) problems.push(`${domain.name}: energy_uj is world-readable`);
|
|
@@ -2661,7 +3537,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2661
3537
|
collectorGids,
|
|
2662
3538
|
group: { name: accounts.powerGroup, exists: group !== null, gid: group?.gid ?? null, members: group?.members ?? [] },
|
|
2663
3539
|
rule: { path: rulePath, present: ruleText !== null, compliant },
|
|
2664
|
-
sysusers: { path: sysusers, present: (0,
|
|
3540
|
+
sysusers: { path: sysusers, present: (0, import_node_fs6.existsSync)(sysusers) },
|
|
2665
3541
|
collectorCanRead,
|
|
2666
3542
|
persistent: compliant && group !== null && collector !== null && group.members.includes(accounts.collector),
|
|
2667
3543
|
worldReadable,
|
|
@@ -2670,15 +3546,15 @@ function inspectPowerAccess(options = {}) {
|
|
|
2670
3546
|
};
|
|
2671
3547
|
}
|
|
2672
3548
|
async function backupDatabase(source, output, host) {
|
|
2673
|
-
ensureDirectory((0,
|
|
2674
|
-
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 });
|
|
2675
3551
|
try {
|
|
2676
|
-
await (0,
|
|
3552
|
+
await (0, import_node_sqlite3.backup)(db, output);
|
|
2677
3553
|
} finally {
|
|
2678
3554
|
db.close();
|
|
2679
3555
|
}
|
|
2680
|
-
(0,
|
|
2681
|
-
const check = new
|
|
3556
|
+
(0, import_node_fs6.chmodSync)(output, 384);
|
|
3557
|
+
const check = new import_node_sqlite3.DatabaseSync(output, { readOnly: true });
|
|
2682
3558
|
try {
|
|
2683
3559
|
const result = check.prepare("PRAGMA integrity_check").get();
|
|
2684
3560
|
if (!result || Object.values(result)[0] !== "ok") throw new SystemError("invalid-backup", `backup integrity check failed: ${output}`);
|
|
@@ -2688,8 +3564,8 @@ async function backupDatabase(source, output, host) {
|
|
|
2688
3564
|
void host;
|
|
2689
3565
|
}
|
|
2690
3566
|
function databaseSchema(path) {
|
|
2691
|
-
if (!(0,
|
|
2692
|
-
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 });
|
|
2693
3569
|
try {
|
|
2694
3570
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'").get();
|
|
2695
3571
|
if (!table) return null;
|
|
@@ -2701,34 +3577,34 @@ function databaseSchema(path) {
|
|
|
2701
3577
|
}
|
|
2702
3578
|
function replaceDatabaseFromBackup(backupPath, target, safetyBackup, host) {
|
|
2703
3579
|
assertPrivateSourceFile(backupPath, "database backup");
|
|
2704
|
-
const
|
|
3580
|
+
const backup3 = new import_node_sqlite3.DatabaseSync(backupPath, { readOnly: true });
|
|
2705
3581
|
try {
|
|
2706
|
-
if (Object.values(
|
|
3582
|
+
if (Object.values(backup3.prepare("PRAGMA integrity_check").get())[0] !== "ok") throw new SystemError("invalid-backup", "rollback backup integrity failed");
|
|
2707
3583
|
} finally {
|
|
2708
|
-
|
|
3584
|
+
backup3.close();
|
|
2709
3585
|
}
|
|
2710
|
-
ensureDirectory((0,
|
|
2711
|
-
if (safetyBackup !== null && (0,
|
|
3586
|
+
ensureDirectory((0, import_node_path6.dirname)(target), 448, true);
|
|
3587
|
+
if (safetyBackup !== null && (0, import_node_fs6.existsSync)(target)) {
|
|
2712
3588
|
void host;
|
|
2713
3589
|
}
|
|
2714
|
-
const temporary = (0,
|
|
2715
|
-
(0,
|
|
2716
|
-
(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);
|
|
2717
3593
|
for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
|
|
2718
3594
|
try {
|
|
2719
|
-
(0,
|
|
3595
|
+
(0, import_node_fs6.unlinkSync)(sidecar);
|
|
2720
3596
|
} catch {
|
|
2721
3597
|
}
|
|
2722
3598
|
}
|
|
2723
|
-
(0,
|
|
2724
|
-
(0,
|
|
3599
|
+
(0, import_node_fs6.renameSync)(temporary, target);
|
|
3600
|
+
(0, import_node_fs6.chmodSync)(target, 384);
|
|
2725
3601
|
}
|
|
2726
3602
|
function newestTimestamp() {
|
|
2727
3603
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2728
3604
|
}
|
|
2729
3605
|
function adoptUserState(options) {
|
|
2730
|
-
const source = (0,
|
|
2731
|
-
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}`);
|
|
2732
3608
|
if (isWithin(options.paths.prefix, source) || isWithin(options.paths.stateDir, source) || isWithin(source, options.paths.stateDir)) {
|
|
2733
3609
|
throw new SystemError("invalid-adoption", "adoption source and system paths must not overlap");
|
|
2734
3610
|
}
|
|
@@ -2738,10 +3614,10 @@ function adoptUserState(options) {
|
|
|
2738
3614
|
const { port, origin } = setup;
|
|
2739
3615
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
|
|
2740
3616
|
if (options.role !== "collector") {
|
|
2741
|
-
const sourceDatabase = (0,
|
|
2742
|
-
if ((0,
|
|
3617
|
+
const sourceDatabase = (0, import_node_path6.join)(source, "hub", "telemetry.sqlite");
|
|
3618
|
+
if ((0, import_node_fs6.existsSync)(sourceDatabase)) {
|
|
2743
3619
|
assertPrivateSourceFile(sourceDatabase, "adopted database");
|
|
2744
|
-
const database = new
|
|
3620
|
+
const database = new import_node_sqlite3.DatabaseSync(sourceDatabase, { readOnly: true });
|
|
2745
3621
|
try {
|
|
2746
3622
|
const row = database.prepare("PRAGMA integrity_check").get();
|
|
2747
3623
|
if (!row || Object.values(row)[0] !== "ok") throw new SystemError("invalid-adoption", "adopted database failed its integrity check");
|
|
@@ -2749,12 +3625,12 @@ function adoptUserState(options) {
|
|
|
2749
3625
|
database.close();
|
|
2750
3626
|
}
|
|
2751
3627
|
}
|
|
2752
|
-
const sourceEnrollment = (0,
|
|
2753
|
-
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");
|
|
2754
3630
|
}
|
|
2755
3631
|
if (options.role !== "hub") {
|
|
2756
|
-
const sourceCollectorState = (0,
|
|
2757
|
-
if ((0,
|
|
3632
|
+
const sourceCollectorState = (0, import_node_path6.join)(source, "collector", "state.json");
|
|
3633
|
+
if ((0, import_node_fs6.existsSync)(sourceCollectorState)) {
|
|
2758
3634
|
assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
|
|
2759
3635
|
readCollectorIdentity(sourceCollectorState);
|
|
2760
3636
|
}
|
|
@@ -2763,7 +3639,7 @@ function adoptUserState(options) {
|
|
|
2763
3639
|
const warnings = [];
|
|
2764
3640
|
const targetDatabase = hubDatabasePath(options.paths);
|
|
2765
3641
|
const targetCollector = collectorStatePath(options.paths);
|
|
2766
|
-
if (options.apply && (0,
|
|
3642
|
+
if (options.apply && (0, import_node_fs6.existsSync)(targetDatabase)) {
|
|
2767
3643
|
throw new SystemError("destination-exists", `refusing to overwrite ${targetDatabase}`);
|
|
2768
3644
|
}
|
|
2769
3645
|
const hubEnv = options.role === "collector" ? null : [
|
|
@@ -2798,62 +3674,42 @@ function adoptUserState(options) {
|
|
|
2798
3674
|
port
|
|
2799
3675
|
};
|
|
2800
3676
|
}
|
|
2801
|
-
function copyAdoptedTree(source, target, copied, skipLocks = true) {
|
|
2802
|
-
const visit = (current, destination) => {
|
|
2803
|
-
const stat = (0, import_node_fs3.lstatSync)(current);
|
|
2804
|
-
if (stat.isSymbolicLink()) return;
|
|
2805
|
-
if (stat.isDirectory()) {
|
|
2806
|
-
(0, import_node_fs3.mkdirSync)(destination, { recursive: true, mode: 448 });
|
|
2807
|
-
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));
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2810
|
-
if (!stat.isFile()) return;
|
|
2811
|
-
if (skipLocks && /\.(lock|sock)$/.test(current)) return;
|
|
2812
|
-
(0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(destination), { recursive: true, mode: 448 });
|
|
2813
|
-
(0, import_node_fs3.copyFileSync)(current, destination);
|
|
2814
|
-
(0, import_node_fs3.chmodSync)(destination, 384);
|
|
2815
|
-
copied.push({ source: current, target: destination, sha256: sha256File(destination), bytes: stat.size });
|
|
2816
|
-
};
|
|
2817
|
-
visit(source, target);
|
|
2818
|
-
}
|
|
2819
3677
|
async function performAdoptionCopyAsync(options) {
|
|
2820
3678
|
const { source, paths, role, host, copy } = options;
|
|
2821
3679
|
assertDirectoryStopped(source, role);
|
|
2822
3680
|
const targetDatabase = hubDatabasePath(paths);
|
|
2823
|
-
const sourceDatabase = (0,
|
|
2824
|
-
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)) {
|
|
2825
3683
|
assertPrivateSourceFile(sourceDatabase, "adopted database");
|
|
2826
|
-
ensureDirectory((0,
|
|
3684
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
|
|
2827
3685
|
await backupDatabase(sourceDatabase, targetDatabase, host);
|
|
2828
3686
|
copy.record.files.push({
|
|
2829
3687
|
source: sourceDatabase,
|
|
2830
3688
|
target: targetDatabase,
|
|
2831
|
-
sha256:
|
|
2832
|
-
bytes: (0,
|
|
3689
|
+
sha256: sha256File2(targetDatabase),
|
|
3690
|
+
bytes: (0, import_node_fs6.statSync)(targetDatabase).size
|
|
2833
3691
|
});
|
|
2834
3692
|
}
|
|
2835
|
-
const sourceCollectorState = (0,
|
|
2836
|
-
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)) {
|
|
2837
3695
|
assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
|
|
2838
|
-
ensureDirectory((0,
|
|
3696
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
|
|
2839
3697
|
const targetCollector = collectorStatePath(paths);
|
|
2840
|
-
(0,
|
|
2841
|
-
(0,
|
|
3698
|
+
(0, import_node_fs6.copyFileSync)(sourceCollectorState, targetCollector);
|
|
3699
|
+
(0, import_node_fs6.chmodSync)(targetCollector, 384);
|
|
2842
3700
|
copy.record.files.push({
|
|
2843
3701
|
source: sourceCollectorState,
|
|
2844
3702
|
target: targetCollector,
|
|
2845
|
-
sha256:
|
|
2846
|
-
bytes: (0,
|
|
3703
|
+
sha256: sha256File2(targetCollector),
|
|
3704
|
+
bytes: (0, import_node_fs6.statSync)(targetCollector).size
|
|
2847
3705
|
});
|
|
2848
|
-
const outbox = (0, import_node_path4.join)(source, "collector", "outbox");
|
|
2849
|
-
if ((0, import_node_fs3.existsSync)(outbox)) copyAdoptedTree(outbox, (0, import_node_path4.join)(paths.stateDir, "collector", "outbox"), copy.record.files);
|
|
2850
3706
|
}
|
|
2851
|
-
const enrollment = (0,
|
|
2852
|
-
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)) {
|
|
2853
3709
|
const target = hubEnrollmentPath(paths);
|
|
2854
|
-
(0,
|
|
2855
|
-
(0,
|
|
2856
|
-
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 });
|
|
2857
3713
|
}
|
|
2858
3714
|
}
|
|
2859
3715
|
function writeSysusersFiles(paths, accounts, role, power, host) {
|
|
@@ -2893,15 +3749,15 @@ function writePowerRule(paths, accounts) {
|
|
|
2893
3749
|
writeManagedFile(raplRulePath(paths), renderRaplRule(accounts), 420, true);
|
|
2894
3750
|
}
|
|
2895
3751
|
function powerOriginalPath(paths) {
|
|
2896
|
-
return (0,
|
|
3752
|
+
return (0, import_node_path6.join)(paths.stateDir, POWER_STATE_FILE_NAME);
|
|
2897
3753
|
}
|
|
2898
3754
|
function capturePowerOriginals(paths) {
|
|
2899
3755
|
const files = {};
|
|
2900
3756
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2901
3757
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2902
|
-
const path = (0,
|
|
3758
|
+
const path = (0, import_node_path6.join)(domain, name);
|
|
2903
3759
|
try {
|
|
2904
|
-
const stat = (0,
|
|
3760
|
+
const stat = (0, import_node_fs6.statSync)(path);
|
|
2905
3761
|
files[path] = { mode: stat.mode & 511, uid: stat.uid, gid: stat.gid };
|
|
2906
3762
|
} catch {
|
|
2907
3763
|
}
|
|
@@ -2913,8 +3769,8 @@ function capturePowerOriginals(paths) {
|
|
|
2913
3769
|
function applyPowerAccess(paths, accounts, host) {
|
|
2914
3770
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2915
3771
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2916
|
-
const path = (0,
|
|
2917
|
-
if (!(0,
|
|
3772
|
+
const path = (0, import_node_path6.join)(domain, name);
|
|
3773
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2918
3774
|
runRequired(host, "chgrp", [accounts.powerGroup, path], `power counter group ${path}`);
|
|
2919
3775
|
runRequired(host, "chmod", ["0440", path], `power counter mode ${path}`);
|
|
2920
3776
|
}
|
|
@@ -2927,9 +3783,9 @@ function resumePowerRule(paths, host) {
|
|
|
2927
3783
|
}
|
|
2928
3784
|
function packageDomainPaths(root) {
|
|
2929
3785
|
try {
|
|
2930
|
-
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) => {
|
|
2931
3787
|
try {
|
|
2932
|
-
return /^package/.test((0,
|
|
3788
|
+
return /^package/.test((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(path, "name"), "utf8").trim());
|
|
2933
3789
|
} catch {
|
|
2934
3790
|
return false;
|
|
2935
3791
|
}
|
|
@@ -2949,8 +3805,8 @@ function restorePowerOriginals(paths, host) {
|
|
|
2949
3805
|
const files = { ...originals?.files ?? {} };
|
|
2950
3806
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2951
3807
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2952
|
-
const path = (0,
|
|
2953
|
-
if (!(0,
|
|
3808
|
+
const path = (0, import_node_path6.join)(domain, name);
|
|
3809
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
2954
3810
|
const original = files[path];
|
|
2955
3811
|
if (original) {
|
|
2956
3812
|
runRequired(host, "chown", [`${original.uid}:${original.gid}`, path], "restore counter ownership");
|
|
@@ -2966,7 +3822,7 @@ function restorePowerOriginals(paths, host) {
|
|
|
2966
3822
|
}
|
|
2967
3823
|
}
|
|
2968
3824
|
try {
|
|
2969
|
-
(0,
|
|
3825
|
+
(0, import_node_fs6.unlinkSync)(powerOriginalPath(paths));
|
|
2970
3826
|
} catch {
|
|
2971
3827
|
}
|
|
2972
3828
|
return restored;
|
|
@@ -2990,7 +3846,7 @@ function stopUnits(host, units) {
|
|
|
2990
3846
|
if (units.some((unit) => systemdActive(host, unit))) throw new SystemError("service-active", "system services did not stop; no files were restored");
|
|
2991
3847
|
}
|
|
2992
3848
|
function unitsForCleanup(paths, host) {
|
|
2993
|
-
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));
|
|
2994
3850
|
return [...Object.values(UNIT_NAMES), ...legacy];
|
|
2995
3851
|
}
|
|
2996
3852
|
function startUnits(host, units) {
|
|
@@ -3028,7 +3884,7 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
3028
3884
|
const report = runRequired(
|
|
3029
3885
|
host,
|
|
3030
3886
|
state.nodePath,
|
|
3031
|
-
[(0,
|
|
3887
|
+
[(0, import_node_path6.join)(paths.prefix, "current/dist/ops.cjs"), "inspect", "--database", hubDatabasePath(paths), "--json"],
|
|
3032
3888
|
"activated database verification",
|
|
3033
3889
|
{ timeoutMs: Math.max(100, deadline - Date.now()) }
|
|
3034
3890
|
);
|
|
@@ -3044,8 +3900,8 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
3044
3900
|
let problem = "no sampling marker";
|
|
3045
3901
|
while (Date.now() < deadline) {
|
|
3046
3902
|
try {
|
|
3047
|
-
const marker = readJsonObject((0,
|
|
3048
|
-
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"));
|
|
3049
3905
|
if (marker.pid !== lease.pid || !Number.isSafeInteger(marker.pid)) throw new Error("sampling marker belongs to another process");
|
|
3050
3906
|
process.kill(Number(marker.pid), 0);
|
|
3051
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");
|
|
@@ -3053,32 +3909,35 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
3053
3909
|
} catch (error) {
|
|
3054
3910
|
problem = error instanceof Error ? error.message : String(error);
|
|
3055
3911
|
}
|
|
3056
|
-
await new Promise((
|
|
3912
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
3057
3913
|
}
|
|
3058
3914
|
throw new SystemError("collector-unready", `system collector did not sample: ${problem}`);
|
|
3059
3915
|
}
|
|
3060
3916
|
}
|
|
3061
3917
|
function chownAccount(paths, owner, group, host) {
|
|
3062
3918
|
for (const path of paths) {
|
|
3063
|
-
if (!(0,
|
|
3919
|
+
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
3064
3920
|
runRequired(host, "chown", ["-R", `${owner}:${group}`, path], `ownership ${path}`);
|
|
3065
3921
|
}
|
|
3066
3922
|
}
|
|
3067
3923
|
function chmodTree(path, host) {
|
|
3068
|
-
if (!(0,
|
|
3924
|
+
if (!(0, import_node_fs6.existsSync)(path)) return;
|
|
3069
3925
|
runRequired(host, "chmod", ["-R", "u=rwX,go=rX", path], `release permissions ${path}`);
|
|
3070
3926
|
}
|
|
3071
3927
|
function pickRollbackBackup(state, previousSchema) {
|
|
3072
3928
|
const backups = Object.values(state.backups).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
3073
3929
|
if (previousSchema === null) return null;
|
|
3074
|
-
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;
|
|
3075
3931
|
}
|
|
3076
3932
|
async function recoverFailedUpgrade(options) {
|
|
3077
3933
|
stopUnits(options.host, options.units);
|
|
3078
3934
|
assertDirectoryStopped(options.paths.stateDir);
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
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);
|
|
3082
3941
|
const database = hubDatabasePath(options.paths);
|
|
3083
3942
|
let afterSchema = null;
|
|
3084
3943
|
try {
|
|
@@ -3086,13 +3945,13 @@ async function recoverFailedUpgrade(options) {
|
|
|
3086
3945
|
} catch {
|
|
3087
3946
|
afterSchema = null;
|
|
3088
3947
|
}
|
|
3089
|
-
if (options.backupPath !== null && (0,
|
|
3090
|
-
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);
|
|
3091
3950
|
replaceDatabaseFromBackup(options.backupPath, database, null, options.host);
|
|
3092
3951
|
runRequired(options.host, "chown", [`${options.state.accounts.hub}:${options.state.accounts.hub}`, database], "restored database ownership");
|
|
3093
3952
|
}
|
|
3094
|
-
options.state.previousVersion = options.oldPrevious === null ? null : (0,
|
|
3095
|
-
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);
|
|
3096
3955
|
writeSystemState(options.paths, options.state);
|
|
3097
3956
|
if (options.activeComponents.length > 0) {
|
|
3098
3957
|
startUnits(options.host, options.units);
|
|
@@ -3101,14 +3960,14 @@ async function recoverFailedUpgrade(options) {
|
|
|
3101
3960
|
}
|
|
3102
3961
|
function assertManagedFileOwnership(paths, path) {
|
|
3103
3962
|
if (!isDefaultSystemLayout(paths)) return;
|
|
3104
|
-
if (!(0,
|
|
3105
|
-
const stat = (0,
|
|
3963
|
+
if (!(0, import_node_fs6.existsSync)(path)) return;
|
|
3964
|
+
const stat = (0, import_node_fs6.lstatSync)(path);
|
|
3106
3965
|
if (stat.isSymbolicLink() || !stat.isFile()) throw new SystemError("unsafe-path", `${path} must be a regular managed file`);
|
|
3107
3966
|
if (stat.uid !== 0) throw new SystemError("insecure-ownership", `${path} must be root-owned before replacement`);
|
|
3108
3967
|
if ((stat.mode & 18) !== 0) throw new SystemError("insecure-permissions", `${path} must not be group/world-writable`);
|
|
3109
3968
|
}
|
|
3110
3969
|
function writeUnitFile(paths, unit, content, replace) {
|
|
3111
|
-
const path = (0,
|
|
3970
|
+
const path = (0, import_node_path6.join)(paths.unitsDir, unit);
|
|
3112
3971
|
assertManagedFileOwnership(paths, path);
|
|
3113
3972
|
writeManagedFile(path, content, 420, replace);
|
|
3114
3973
|
}
|
|
@@ -3123,19 +3982,19 @@ function ensureComponentSetup(options) {
|
|
|
3123
3982
|
ensureDirectory(paths.configDir, 493);
|
|
3124
3983
|
ensureDirectory(paths.stateDir, 493);
|
|
3125
3984
|
ensureDirectory(paths.unitsDir, 493);
|
|
3126
|
-
ensureDirectory((0,
|
|
3127
|
-
ensureDirectory((0,
|
|
3128
|
-
ensureDirectory((0,
|
|
3129
|
-
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);
|
|
3130
3989
|
const release = currentReleaseOrThrow(paths);
|
|
3131
3990
|
void release;
|
|
3132
3991
|
writeSysusersFiles(paths, accounts, "local", power, host);
|
|
3133
3992
|
if (component === "hub") {
|
|
3134
|
-
if (!(0,
|
|
3993
|
+
if (!(0, import_node_fs6.existsSync)(hubEnvPath(paths))) {
|
|
3135
3994
|
const env = hubEnvTemplate(paths);
|
|
3136
3995
|
writeManagedFile(hubEnvPath(paths), env, 384, false);
|
|
3137
3996
|
}
|
|
3138
|
-
if (!(0,
|
|
3997
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, UNIT_NAMES.hub))) {
|
|
3139
3998
|
const env = readHubEnvironment(paths);
|
|
3140
3999
|
const remote = env.HUB_PUBLIC_ORIGIN !== void 0 && env.HUB_PUBLIC_ORIGIN !== "";
|
|
3141
4000
|
const unit = renderSystemHubUnit({
|
|
@@ -3152,7 +4011,7 @@ function ensureComponentSetup(options) {
|
|
|
3152
4011
|
`, false);
|
|
3153
4012
|
}
|
|
3154
4013
|
} else {
|
|
3155
|
-
if (!(0,
|
|
4014
|
+
if (!(0, import_node_fs6.existsSync)(collectorEnvPath(paths))) {
|
|
3156
4015
|
writeManagedFile(collectorEnvPath(paths), collectorEnvTemplate(), 384, false);
|
|
3157
4016
|
}
|
|
3158
4017
|
const unit = renderSystemCollectorUnit({
|
|
@@ -3168,17 +4027,17 @@ function ensureComponentSetup(options) {
|
|
|
3168
4027
|
const validation = runRequired(
|
|
3169
4028
|
host,
|
|
3170
4029
|
"systemd-analyze",
|
|
3171
|
-
["verify", "--man=no", (0,
|
|
4030
|
+
["verify", "--man=no", (0, import_node_path6.join)(paths.unitsDir, unitFor(component))],
|
|
3172
4031
|
"native systemd unit validation"
|
|
3173
4032
|
);
|
|
3174
4033
|
if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
|
|
3175
4034
|
throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
|
|
3176
4035
|
}
|
|
3177
|
-
if (component === "hub") chownAccount([(0,
|
|
3178
|
-
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);
|
|
3179
4038
|
}
|
|
3180
4039
|
function ensurePowerAccess(paths, accounts, host) {
|
|
3181
|
-
if (!(0,
|
|
4040
|
+
if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
|
|
3182
4041
|
writePowerRule(paths, accounts);
|
|
3183
4042
|
resumePowerRule(paths, host);
|
|
3184
4043
|
applyPowerAccess(paths, accounts, host);
|
|
@@ -3196,7 +4055,7 @@ function resetUnfinishedSystemState(options) {
|
|
|
3196
4055
|
}
|
|
3197
4056
|
function installPreset(options, role) {
|
|
3198
4057
|
if (options.adoptFrom !== void 0) {
|
|
3199
|
-
return componentSet(readAdoptionSetup((0,
|
|
4058
|
+
return componentSet(readAdoptionSetup((0, import_node_path6.resolve)(options.adoptFrom)).components);
|
|
3200
4059
|
}
|
|
3201
4060
|
return componentsForRole(role);
|
|
3202
4061
|
}
|
|
@@ -3208,11 +4067,11 @@ function planSystemInstall(options) {
|
|
|
3208
4067
|
const power = options.power !== false && preset.collector;
|
|
3209
4068
|
const initial = options.enable === true ? preset : { hub: false, collector: false };
|
|
3210
4069
|
const files = [
|
|
3211
|
-
{ path: (0,
|
|
4070
|
+
{ path: (0, import_node_path6.join)(paths.prefix, "current"), mode: "symlink", purpose: "active root-owned release" },
|
|
3212
4071
|
{ path: hubEnvPath(paths), mode: "0600", purpose: "root-owned hub configuration" },
|
|
3213
4072
|
{ path: collectorEnvPath(paths), mode: "0600", purpose: "root-owned collector configuration" },
|
|
3214
4073
|
{ path: sysusersPath(paths), mode: "0644", purpose: "dedicated nologin accounts" },
|
|
3215
|
-
...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` }))
|
|
3216
4075
|
];
|
|
3217
4076
|
if (power) {
|
|
3218
4077
|
files.push({ path: raplRulePath(paths), mode: "0644", purpose: "package RAPL read rule" });
|
|
@@ -3227,7 +4086,7 @@ function planSystemInstall(options) {
|
|
|
3227
4086
|
];
|
|
3228
4087
|
const notes = [
|
|
3229
4088
|
"Nothing is changed without --apply and root.",
|
|
3230
|
-
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.",
|
|
3231
4090
|
"One complete package: both runtimes, units and accounts are installed; --role only sets the initial enabled preset.",
|
|
3232
4091
|
"Hub and collector use separate unprivileged accounts; Node gets no capabilities.",
|
|
3233
4092
|
...options.enable && preset.hub ? ["Enabling the hub creates its owner account once and prints the password once."] : [],
|
|
@@ -3260,8 +4119,8 @@ async function installSystemUnlocked(options) {
|
|
|
3260
4119
|
`existing user service(s) detected (${detail}); stop them and use --adopt-user-state for explicit copy-based adoption`
|
|
3261
4120
|
);
|
|
3262
4121
|
}
|
|
3263
|
-
const source = (0,
|
|
3264
|
-
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}`);
|
|
3265
4124
|
const port = options.port ?? 8081;
|
|
3266
4125
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-argument", "--port must be 1-65535");
|
|
3267
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) });
|
|
@@ -3282,14 +4141,14 @@ async function installSystemUnlocked(options) {
|
|
|
3282
4141
|
});
|
|
3283
4142
|
try {
|
|
3284
4143
|
if (legacy) resetUnfinishedSystemState({ paths, host, now });
|
|
3285
|
-
ensureDirectory((0,
|
|
4144
|
+
ensureDirectory((0, import_node_path6.join)(paths.prefix, "releases"), 493);
|
|
3286
4145
|
ensureDirectory(paths.stateDir, 493);
|
|
3287
4146
|
ensureDirectory(paths.configDir, 493);
|
|
3288
4147
|
ensureDirectory(paths.unitsDir, 493);
|
|
3289
|
-
ensureDirectory((0,
|
|
3290
|
-
ensureDirectory((0,
|
|
3291
|
-
ensureDirectory((0,
|
|
3292
|
-
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);
|
|
3293
4152
|
const release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
|
|
3294
4153
|
let adoption = null;
|
|
3295
4154
|
if (options.adoptFrom) {
|
|
@@ -3322,13 +4181,13 @@ async function installSystemUnlocked(options) {
|
|
|
3322
4181
|
`, true);
|
|
3323
4182
|
writeUnitFile(paths, UNIT_NAMES.collector, `${collectorUnit}
|
|
3324
4183
|
`, true);
|
|
3325
|
-
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");
|
|
3326
4185
|
if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
|
|
3327
4186
|
throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
|
|
3328
4187
|
}
|
|
3329
4188
|
if (power) ensurePowerAccess(paths, accounts, host);
|
|
3330
|
-
chownAccount([(0,
|
|
3331
|
-
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);
|
|
3332
4191
|
const state = emptyState({ paths, accounts, role, components: { hub: false, collector: false }, nodePath, now });
|
|
3333
4192
|
state.currentVersion = release.version;
|
|
3334
4193
|
state.releases[release.version] = {
|
|
@@ -3338,7 +4197,7 @@ async function installSystemUnlocked(options) {
|
|
|
3338
4197
|
};
|
|
3339
4198
|
state.adoption = adoption?.record ?? null;
|
|
3340
4199
|
state.power = power ? { enabled: true, rulePath: raplRulePath(paths), group: accounts.powerGroup } : null;
|
|
3341
|
-
replaceSymlink((0,
|
|
4200
|
+
replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), release.releaseDir, host);
|
|
3342
4201
|
writeSystemState(paths, state);
|
|
3343
4202
|
const wantsHub = preset.hub && (options.enable === true || options.start === true);
|
|
3344
4203
|
const wantsCollector = preset.collector && (options.enable === true || options.start === true);
|
|
@@ -3364,6 +4223,7 @@ async function installSystemUnlocked(options) {
|
|
|
3364
4223
|
await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, started);
|
|
3365
4224
|
}
|
|
3366
4225
|
}
|
|
4226
|
+
recordMachineDataTransition(paths.stateDir, release.version, (path, content) => atomicWrite(path, content, 384));
|
|
3367
4227
|
return {
|
|
3368
4228
|
operation: "install",
|
|
3369
4229
|
ok: true,
|
|
@@ -3392,14 +4252,14 @@ function systemStatus(options = {}) {
|
|
|
3392
4252
|
} catch (error) {
|
|
3393
4253
|
stateError = error instanceof Error ? error.message : String(error);
|
|
3394
4254
|
}
|
|
3395
|
-
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)));
|
|
3396
4256
|
const publicRole = presentUnits.length === 2 ? "local" : presentUnits[0] === UNIT_NAMES.hub ? "hub" : presentUnits[0] === UNIT_NAMES.collector ? "collector" : null;
|
|
3397
4257
|
const role = state?.role ?? publicRole ?? "local";
|
|
3398
4258
|
const current = currentReleaseDir(paths);
|
|
3399
4259
|
let publicVersion = null;
|
|
3400
4260
|
if (current) {
|
|
3401
4261
|
try {
|
|
3402
|
-
const manifest = JSON.parse((0,
|
|
4262
|
+
const manifest = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(current, "package.json"), "utf8"));
|
|
3403
4263
|
if (typeof manifest.version === "string") publicVersion = manifest.version;
|
|
3404
4264
|
} catch {
|
|
3405
4265
|
}
|
|
@@ -3410,7 +4270,7 @@ function systemStatus(options = {}) {
|
|
|
3410
4270
|
const componentReports = {};
|
|
3411
4271
|
for (const component of COMPONENTS) {
|
|
3412
4272
|
const unit = unitFor(component);
|
|
3413
|
-
const present = (0,
|
|
4273
|
+
const present = (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit));
|
|
3414
4274
|
const active = present && systemdActive(host, unit);
|
|
3415
4275
|
const unitEnabled = present && systemdEnabled(host, unit);
|
|
3416
4276
|
const enabled = state !== null ? state.components[component].enabled : unitEnabled;
|
|
@@ -3419,7 +4279,7 @@ function systemStatus(options = {}) {
|
|
|
3419
4279
|
componentReports[component] = {
|
|
3420
4280
|
component,
|
|
3421
4281
|
unit,
|
|
3422
|
-
path: (0,
|
|
4282
|
+
path: (0, import_node_path6.join)(paths.unitsDir, unit),
|
|
3423
4283
|
present,
|
|
3424
4284
|
enabled,
|
|
3425
4285
|
unitEnabled,
|
|
@@ -3463,16 +4323,16 @@ function systemStatus(options = {}) {
|
|
|
3463
4323
|
})),
|
|
3464
4324
|
database: {
|
|
3465
4325
|
path: database,
|
|
3466
|
-
exists: (0,
|
|
4326
|
+
exists: (0, import_node_fs6.existsSync)(database),
|
|
3467
4327
|
schema,
|
|
3468
|
-
mode: (0,
|
|
4328
|
+
mode: (0, import_node_fs6.existsSync)(database) ? modeString((0, import_node_fs6.statSync)(database).mode) : null,
|
|
3469
4329
|
lock
|
|
3470
4330
|
},
|
|
3471
|
-
socket: { path: hubSocketPath(paths), exists: (0,
|
|
4331
|
+
socket: { path: hubSocketPath(paths), exists: (0, import_node_fs6.existsSync)(hubSocketPath(paths)) },
|
|
3472
4332
|
config: {
|
|
3473
4333
|
dir: paths.configDir,
|
|
3474
|
-
hubEnv: { path: hubEnvPath(paths), present: (0,
|
|
3475
|
-
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)) }
|
|
3476
4336
|
},
|
|
3477
4337
|
adoption: state?.adoption ?? null,
|
|
3478
4338
|
power,
|
|
@@ -3641,7 +4501,7 @@ async function enableSystem(options) {
|
|
|
3641
4501
|
],
|
|
3642
4502
|
files: [
|
|
3643
4503
|
{ path: component === "hub" ? hubEnvPath(paths) : collectorEnvPath(paths), mode: "0600", purpose: `${component} configuration` },
|
|
3644
|
-
{ path: (0,
|
|
4504
|
+
{ path: (0, import_node_path6.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }
|
|
3645
4505
|
],
|
|
3646
4506
|
notes: [
|
|
3647
4507
|
"Enables only the requested component; the other component keeps running.",
|
|
@@ -3709,7 +4569,7 @@ function disableSystem(options) {
|
|
|
3709
4569
|
accounts: state.accounts,
|
|
3710
4570
|
commands: [`systemctl stop ${unitFor(component)}`, `systemctl disable ${unitFor(component)}`],
|
|
3711
4571
|
files: [],
|
|
3712
|
-
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."]
|
|
3713
4573
|
};
|
|
3714
4574
|
if (options.apply !== true) return { operation: "disable", ok: true, apply: false, message: "plan only", plan };
|
|
3715
4575
|
assertApply(host);
|
|
@@ -3731,7 +4591,7 @@ function disableSystem(options) {
|
|
|
3731
4591
|
function readPrivateToken(path, read) {
|
|
3732
4592
|
let stat;
|
|
3733
4593
|
try {
|
|
3734
|
-
stat = (0,
|
|
4594
|
+
stat = (0, import_node_fs6.lstatSync)(path);
|
|
3735
4595
|
} catch (error) {
|
|
3736
4596
|
if (error.code === "ENOENT") {
|
|
3737
4597
|
throw new SystemError("missing-grant", `grant file does not exist: ${path}`);
|
|
@@ -3742,7 +4602,7 @@ function readPrivateToken(path, read) {
|
|
|
3742
4602
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `grant file must be private (0600): ${path}`);
|
|
3743
4603
|
if (stat.size < 1 || stat.size > 4096) throw new SystemError("invalid-token", `grant file must contain 1-4096 bytes: ${path}`);
|
|
3744
4604
|
if (!read) return void 0;
|
|
3745
|
-
const token = (0,
|
|
4605
|
+
const token = (0, import_node_fs6.readFileSync)(path, "utf8").trim();
|
|
3746
4606
|
if (token === "" || /[\r\n\0]/.test(token) || !/^[\x21-\x7e]{1,256}$/.test(token)) {
|
|
3747
4607
|
throw new SystemError("invalid-token", "grant file contains an invalid token");
|
|
3748
4608
|
}
|
|
@@ -3754,7 +4614,7 @@ async function connectSystem(options) {
|
|
|
3754
4614
|
const state = readSystemState(paths);
|
|
3755
4615
|
if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
|
|
3756
4616
|
const destination = connectDestinationUrl(options.url);
|
|
3757
|
-
const tokenFile = (0,
|
|
4617
|
+
const tokenFile = (0, import_node_path6.resolve)(options.tokenFile);
|
|
3758
4618
|
const configured = collectorConfigured(paths);
|
|
3759
4619
|
if (configured.configured) {
|
|
3760
4620
|
throw new SystemError(
|
|
@@ -3802,12 +4662,12 @@ async function connectSystem(options) {
|
|
|
3802
4662
|
`collector enrollment failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3803
4663
|
);
|
|
3804
4664
|
} finally {
|
|
3805
|
-
chownAccount([(0,
|
|
4665
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
|
|
3806
4666
|
}
|
|
3807
4667
|
if (!collectorConfigured(paths).configured) {
|
|
3808
4668
|
throw new SystemError("enrollment-failed", "the hub accepted enrollment but no destination was saved; check private state");
|
|
3809
4669
|
}
|
|
3810
|
-
chownAccount([(0,
|
|
4670
|
+
chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
|
|
3811
4671
|
writeSystemState(paths, state);
|
|
3812
4672
|
const enabled = state.components.collector.enabled;
|
|
3813
4673
|
if (enabled || wasActive) {
|
|
@@ -3824,13 +4684,16 @@ async function connectSystem(options) {
|
|
|
3824
4684
|
state
|
|
3825
4685
|
};
|
|
3826
4686
|
}
|
|
4687
|
+
function systemMachineDataPaths(paths) {
|
|
4688
|
+
return { database: hubDatabasePath(paths), queueTree: (0, import_node_path6.join)(paths.stateDir, "collector", "outbox") };
|
|
4689
|
+
}
|
|
3827
4690
|
async function upgradeSystemUnlocked(options) {
|
|
3828
4691
|
const host = options.host ?? createSystemHost();
|
|
3829
4692
|
const now = options.now ?? Date.now;
|
|
3830
4693
|
const paths = validateSystemPaths(options.paths);
|
|
3831
4694
|
if (systemStateIsLegacy(paths)) {
|
|
3832
|
-
const source2 = (0,
|
|
3833
|
-
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}`);
|
|
3834
4697
|
const role = legacySystemRole(paths);
|
|
3835
4698
|
const preset = componentsForRole(role);
|
|
3836
4699
|
const enable = options.enable ?? (options.start === true || unitNamesForComponents(preset).some((unit) => systemdEnabled(host, unit)));
|
|
@@ -3853,7 +4716,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3853
4716
|
],
|
|
3854
4717
|
files: [
|
|
3855
4718
|
{ path: stateFilePath(paths), mode: "0600", purpose: "canonical replacement state" },
|
|
3856
|
-
{ path: (0,
|
|
4719
|
+
{ path: (0, import_node_path6.join)(paths.stateDir, "backups"), mode: "0700", purpose: "removed with unfinished history" }
|
|
3857
4720
|
],
|
|
3858
4721
|
notes: [
|
|
3859
4722
|
"An unfinished installation is reset, not migrated; its history and credentials are removed.",
|
|
@@ -3886,6 +4749,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3886
4749
|
if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
|
|
3887
4750
|
const activeComponents = COMPONENTS.filter((component) => systemdActive(host, unitFor(component)));
|
|
3888
4751
|
const activeUnits = activeComponents.map(unitFor);
|
|
4752
|
+
const transitionPending = readMachineDataTransition(paths.stateDir) === null;
|
|
3889
4753
|
const plan = {
|
|
3890
4754
|
operation: "upgrade",
|
|
3891
4755
|
apply: options.apply === true,
|
|
@@ -3893,19 +4757,34 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3893
4757
|
components: { hub: state.components.hub.enabled, collector: state.components.collector.enabled },
|
|
3894
4758
|
paths,
|
|
3895
4759
|
accounts: state.accounts,
|
|
3896
|
-
commands: [
|
|
3897
|
-
|
|
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
|
+
],
|
|
3898
4772
|
notes: [
|
|
3899
4773
|
"Staging happens before any service is stopped.",
|
|
3900
4774
|
"The exact active component set is restored; --start does not start a component that was stopped.",
|
|
3901
|
-
"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."]
|
|
3902
4781
|
]
|
|
3903
4782
|
};
|
|
3904
4783
|
if (options.apply !== true) return { operation: "upgrade", ok: true, apply: false, message: "plan only", plan };
|
|
3905
4784
|
assertApply(host);
|
|
3906
4785
|
assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
|
|
3907
|
-
const source = (0,
|
|
3908
|
-
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}`);
|
|
3909
4788
|
const staged = prepareStagedRelease({
|
|
3910
4789
|
source,
|
|
3911
4790
|
paths,
|
|
@@ -3924,21 +4803,22 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3924
4803
|
let backupPath = null;
|
|
3925
4804
|
let beforeSchema = null;
|
|
3926
4805
|
let release = null;
|
|
4806
|
+
let machineData = null;
|
|
3927
4807
|
try {
|
|
3928
4808
|
stopUnits(host, activeUnits);
|
|
3929
4809
|
assertDirectoryStopped(paths.stateDir);
|
|
3930
4810
|
const lock = systemLockState(paths);
|
|
3931
4811
|
if (lock.live) throw new SystemError("duplicate-runtime", `hub did not stop cleanly; lock remains at ${lock.path}`);
|
|
3932
4812
|
const database = hubDatabasePath(paths);
|
|
3933
|
-
if ((0,
|
|
3934
|
-
ensureDirectory((0,
|
|
4813
|
+
if ((0, import_node_fs6.existsSync)(database)) {
|
|
4814
|
+
ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
|
|
3935
4815
|
beforeSchema = databaseSchema(database);
|
|
3936
|
-
backupPath = (0,
|
|
4816
|
+
backupPath = (0, import_node_path6.join)(paths.stateDir, "backups", `pre-upgrade-${staged.version}-${newestTimestamp()}.sqlite`);
|
|
3937
4817
|
await backupDatabase(database, backupPath, host);
|
|
3938
4818
|
}
|
|
3939
4819
|
release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
|
|
3940
|
-
replaceSymlink((0,
|
|
3941
|
-
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);
|
|
3942
4822
|
state.previousVersion = state.currentVersion;
|
|
3943
4823
|
state.currentVersion = release.version;
|
|
3944
4824
|
state.releases[release.version] = {
|
|
@@ -3947,7 +4827,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3947
4827
|
installedAt: new Date(now()).toISOString()
|
|
3948
4828
|
};
|
|
3949
4829
|
if (backupPath !== null) {
|
|
3950
|
-
state.backups[(0,
|
|
4830
|
+
state.backups[(0, import_node_path6.basename)(backupPath)] = {
|
|
3951
4831
|
path: backupPath,
|
|
3952
4832
|
schemaVersion: beforeSchema,
|
|
3953
4833
|
fromSchema: beforeSchema,
|
|
@@ -3958,6 +4838,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3958
4838
|
};
|
|
3959
4839
|
}
|
|
3960
4840
|
writeSystemState(paths, state);
|
|
4841
|
+
machineData = performMachineDataCleanup(paths.stateDir, systemMachineDataPaths(paths), now);
|
|
3961
4842
|
if (activeComponents.length > 0) {
|
|
3962
4843
|
startUnits(host, activeUnits);
|
|
3963
4844
|
await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, activeComponents);
|
|
@@ -3970,6 +4851,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3970
4851
|
oldPrevious,
|
|
3971
4852
|
beforeSchema,
|
|
3972
4853
|
backupPath,
|
|
4854
|
+
machineData,
|
|
3973
4855
|
host,
|
|
3974
4856
|
activeComponents,
|
|
3975
4857
|
units: activeUnits
|
|
@@ -3977,6 +4859,10 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3977
4859
|
throw error;
|
|
3978
4860
|
}
|
|
3979
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;
|
|
3980
4866
|
return {
|
|
3981
4867
|
operation: "upgrade",
|
|
3982
4868
|
ok: true,
|
|
@@ -3986,6 +4872,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3986
4872
|
current: upgraded.releaseDir,
|
|
3987
4873
|
previous: oldCurrent,
|
|
3988
4874
|
backupPath,
|
|
4875
|
+
...machineHistory ? { machineHistory } : {},
|
|
3989
4876
|
state
|
|
3990
4877
|
};
|
|
3991
4878
|
} finally {
|
|
@@ -3993,7 +4880,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3993
4880
|
}
|
|
3994
4881
|
}
|
|
3995
4882
|
function systemOperationLockPath(paths) {
|
|
3996
|
-
return (0,
|
|
4883
|
+
return (0, import_node_path6.join)(paths.prefix, "run", "operation.lock");
|
|
3997
4884
|
}
|
|
3998
4885
|
async function withSystemOperation(options, operation) {
|
|
3999
4886
|
if (!options.apply) return operation();
|
|
@@ -4001,7 +4888,7 @@ async function withSystemOperation(options, operation) {
|
|
|
4001
4888
|
assertApply(host);
|
|
4002
4889
|
const paths = validateSystemPaths(options.paths);
|
|
4003
4890
|
ensureDirectory(paths.prefix, 493);
|
|
4004
|
-
ensureDirectory((0,
|
|
4891
|
+
ensureDirectory((0, import_node_path6.join)(paths.prefix, "run"), 448, true);
|
|
4005
4892
|
const release = acquireProcessLease(systemOperationLockPath(paths));
|
|
4006
4893
|
try {
|
|
4007
4894
|
return await operation();
|
|
@@ -4053,24 +4940,24 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
4053
4940
|
try {
|
|
4054
4941
|
stopUnits(host, units);
|
|
4055
4942
|
assertDirectoryStopped(paths.stateDir);
|
|
4056
|
-
if (systemLockState(paths).live || lockIsLive((0,
|
|
4943
|
+
if (systemLockState(paths).live || lockIsLive((0, import_node_path6.join)(paths.stateDir, "collector/collector-runtime.lock")).live) {
|
|
4057
4944
|
throw new SystemError("runtime-active", "runtime lease remains after stop; rollback refused");
|
|
4058
4945
|
}
|
|
4059
4946
|
const database = hubDatabasePath(paths);
|
|
4060
|
-
const currentSchema = (0,
|
|
4947
|
+
const currentSchema = (0, import_node_fs6.existsSync)(database) ? databaseSchema(database) : null;
|
|
4061
4948
|
let backupToRestore = null;
|
|
4062
4949
|
if (currentSchema !== null && (previousSchema === null || currentSchema > previousSchema)) {
|
|
4063
4950
|
backupToRestore = pickRollbackBackup(state, previousSchema);
|
|
4064
|
-
if (backupToRestore === null || !(0,
|
|
4951
|
+
if (backupToRestore === null || !(0, import_node_fs6.existsSync)(backupToRestore.path)) {
|
|
4065
4952
|
throw new SystemError(
|
|
4066
4953
|
"incompatible-schema",
|
|
4067
4954
|
`current database schema ${currentSchema} is newer than release ${previousVersion}; no matching pre-upgrade backup is recorded`
|
|
4068
4955
|
);
|
|
4069
4956
|
}
|
|
4070
4957
|
}
|
|
4071
|
-
if ((0,
|
|
4072
|
-
ensureDirectory((0,
|
|
4073
|
-
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`);
|
|
4074
4961
|
await backupDatabase(database, safetyBackup, host);
|
|
4075
4962
|
}
|
|
4076
4963
|
if (backupToRestore !== null) {
|
|
@@ -4078,8 +4965,8 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
4078
4965
|
restoredDatabase = true;
|
|
4079
4966
|
runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, database], "restored database ownership");
|
|
4080
4967
|
}
|
|
4081
|
-
replaceSymlink((0,
|
|
4082
|
-
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);
|
|
4083
4970
|
state.previousVersion = state.currentVersion;
|
|
4084
4971
|
state.currentVersion = previousVersion;
|
|
4085
4972
|
state.updatedAt = new Date(now()).toISOString();
|
|
@@ -4101,10 +4988,10 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
4101
4988
|
};
|
|
4102
4989
|
} catch (error) {
|
|
4103
4990
|
stopUnits(host, units);
|
|
4104
|
-
replaceSymlink((0,
|
|
4105
|
-
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);
|
|
4106
4993
|
if (restoredDatabase && safetyBackup) {
|
|
4107
|
-
const failed = (0,
|
|
4994
|
+
const failed = (0, import_node_path6.join)(paths.stateDir, "backups", `failed-rollback-${newestTimestamp()}.sqlite`);
|
|
4108
4995
|
await backupDatabase(hubDatabasePath(paths), failed, host);
|
|
4109
4996
|
replaceDatabaseFromBackup(safetyBackup, hubDatabasePath(paths), null, host);
|
|
4110
4997
|
runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, hubDatabasePath(paths)], "restored database ownership");
|
|
@@ -4161,31 +5048,31 @@ function uninstallSystem(options = {}) {
|
|
|
4161
5048
|
assertDirectoryStopped(paths.stateDir);
|
|
4162
5049
|
for (const unit of units) runMaybe(host, "systemctl", ["disable", unit]);
|
|
4163
5050
|
for (const unit of units) {
|
|
4164
|
-
const path = (0,
|
|
5051
|
+
const path = (0, import_node_path6.join)(paths.unitsDir, unit);
|
|
4165
5052
|
assertManagedFileOwnership(paths, path);
|
|
4166
5053
|
try {
|
|
4167
|
-
(0,
|
|
5054
|
+
(0, import_node_fs6.unlinkSync)(path);
|
|
4168
5055
|
} catch {
|
|
4169
5056
|
}
|
|
4170
5057
|
}
|
|
4171
5058
|
runMaybe(host, "systemctl", ["daemon-reload"]);
|
|
4172
|
-
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));
|
|
4173
5060
|
if (hadPowerAccess) {
|
|
4174
5061
|
try {
|
|
4175
|
-
(0,
|
|
5062
|
+
(0, import_node_fs6.unlinkSync)(raplRulePath(paths));
|
|
4176
5063
|
} catch {
|
|
4177
5064
|
}
|
|
4178
5065
|
try {
|
|
4179
|
-
(0,
|
|
5066
|
+
(0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
|
|
4180
5067
|
} catch {
|
|
4181
5068
|
}
|
|
4182
5069
|
restorePowerOriginals(paths, host);
|
|
4183
5070
|
resumePowerRule(paths, host);
|
|
4184
5071
|
}
|
|
4185
|
-
removeSymlink((0,
|
|
4186
|
-
removeSymlink((0,
|
|
4187
|
-
(0,
|
|
4188
|
-
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 });
|
|
4189
5076
|
if (state !== null) {
|
|
4190
5077
|
state.active = false;
|
|
4191
5078
|
state.power = null;
|
|
@@ -4198,8 +5085,8 @@ function uninstallSystem(options = {}) {
|
|
|
4198
5085
|
for (const group of [accounts.powerGroup, accounts.hub, accounts.collector]) runMaybe(host, "groupdel", [group]);
|
|
4199
5086
|
}
|
|
4200
5087
|
if (options.purgeData === true) {
|
|
4201
|
-
(0,
|
|
4202
|
-
(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 });
|
|
4203
5090
|
}
|
|
4204
5091
|
return {
|
|
4205
5092
|
operation: "uninstall",
|
|
@@ -4249,7 +5136,7 @@ function grantPower(options = {}) {
|
|
|
4249
5136
|
writeManagedFile(powerSysusersPath(paths), renderPowerSysusers(paths, accounts), 420, true);
|
|
4250
5137
|
runRequired(host, "systemd-sysusers", [], "power group creation");
|
|
4251
5138
|
assertSystemAccounts(host, accounts, true, ["collector"]);
|
|
4252
|
-
if (!(0,
|
|
5139
|
+
if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
|
|
4253
5140
|
writePowerRule(paths, accounts);
|
|
4254
5141
|
resumePowerRule(paths, host);
|
|
4255
5142
|
applyPowerAccess(paths, accounts, host);
|
|
@@ -4278,11 +5165,11 @@ function revokePower(options = {}) {
|
|
|
4278
5165
|
if (options.apply !== true) return { operation: "power-revoke", ok: true, apply: false, message: "plan only", plan };
|
|
4279
5166
|
assertApply(host);
|
|
4280
5167
|
try {
|
|
4281
|
-
(0,
|
|
5168
|
+
(0, import_node_fs6.unlinkSync)(raplRulePath(paths));
|
|
4282
5169
|
} catch {
|
|
4283
5170
|
}
|
|
4284
5171
|
try {
|
|
4285
|
-
(0,
|
|
5172
|
+
(0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
|
|
4286
5173
|
} catch {
|
|
4287
5174
|
}
|
|
4288
5175
|
restorePowerOriginals(paths, host);
|
|
@@ -4498,6 +5385,12 @@ function printMutationReport(report) {
|
|
|
4498
5385
|
console.log("then run `rigscope system connect https://<hub> --token-file <file> --apply`.");
|
|
4499
5386
|
}
|
|
4500
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
|
+
}
|
|
4501
5394
|
async function runSystemCommand(argv) {
|
|
4502
5395
|
const parsed = parseSystemArgs(argv);
|
|
4503
5396
|
if (parsed.command === "help") {
|
|
@@ -4569,7 +5462,7 @@ ${humanPlan(report.plan)}`);
|
|
|
4569
5462
|
if (parsed.command === "logs") {
|
|
4570
5463
|
const selected = validateSystemPaths(paths);
|
|
4571
5464
|
const component = componentFrom(parsed, false);
|
|
4572
|
-
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)));
|
|
4573
5466
|
if (units.length === 0) throw new SystemError("not-installed", component ? `${component} logs are unavailable; its unit is not installed` : "no system services installed");
|
|
4574
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" });
|
|
4575
5468
|
if (result.error) throw result.error;
|
|
@@ -4645,6 +5538,7 @@ ${humanPlan(report.plan)}`);
|
|
|
4645
5538
|
else {
|
|
4646
5539
|
printMutationReport(report);
|
|
4647
5540
|
console.log(`Backup: ${report.backupPath ?? "none"}`);
|
|
5541
|
+
printMachineDataOutcome(report.machineHistory);
|
|
4648
5542
|
}
|
|
4649
5543
|
return 0;
|
|
4650
5544
|
}
|