rigscope 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +3 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/administration/page_client-reference-manifest.js +1 -1
- package/.next/server/app/administration.html +1 -1
- package/.next/server/app/administration.rsc +3 -3
- package/.next/server/app/administration.segments/_full.segment.rsc +3 -3
- package/.next/server/app/administration.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/alerts/page_client-reference-manifest.js +1 -1
- package/.next/server/app/alerts.html +1 -1
- package/.next/server/app/alerts.rsc +3 -3
- package/.next/server/app/alerts.segments/_full.segment.rsc +3 -3
- package/.next/server/app/alerts.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/connections/page_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 +2 -2
- package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/history/page.js +2 -2
- package/.next/server/app/history/page_client-reference-manifest.js +1 -1
- package/.next/server/app/history.html +1 -1
- package/.next/server/app/history.rsc +6 -6
- package/.next/server/app/history.segments/_full.segment.rsc +6 -6
- package/.next/server/app/history.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/processes/page.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 +4 -4
- package/.next/server/app/processes.segments/_full.segment.rsc +4 -4
- package/.next/server/app/processes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app-paths-manifest.json +2 -2
- package/.next/server/chunks/161.js +1 -1
- package/.next/server/chunks/168.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/route-cache/APP_PAGE/16d1ab46157a868f438fa01e04bf53e805c71cddec593ea1535905c59625cd42/$/login.html +1 -0
- package/.next/server/route-cache/APP_PAGE/16d1ab46157a868f438fa01e04bf53e805c71cddec593ea1535905c59625cd42/$/login.meta +1 -0
- package/.next/server/route-cache/APP_PAGE/16d1ab46157a868f438fa01e04bf53e805c71cddec593ea1535905c59625cd42/$/login.rsc +26 -0
- package/.next/server/route-cache/APP_PAGE/16d1ab46157a868f438fa01e04bf53e805c71cddec593ea1535905c59625cd42/$/login.segments/_full.segment.rsc +26 -0
- package/.next/server/route-cache/APP_PAGE/16d1ab46157a868f438fa01e04bf53e805c71cddec593ea1535905c59625cd42/$/login.segments/_tree.segment.rsc +4 -0
- package/.next/server/route-cache/APP_PAGE/16d1ab46157a868f438fa01e04bf53e805c71cddec593ea1535905c59625cd42/$/login.segments/login/__PAGE__.segment.rsc +32 -0
- package/.next/server/route-cache/APP_PAGE/41da0ef926a63354a29f601d8e8d0a5951196c45ebcd28b7d4708fb7f56f3bf1/$/_not-found.html +1 -0
- package/.next/server/route-cache/APP_PAGE/41da0ef926a63354a29f601d8e8d0a5951196c45ebcd28b7d4708fb7f56f3bf1/$/_not-found.meta +1 -0
- package/.next/server/route-cache/APP_PAGE/41da0ef926a63354a29f601d8e8d0a5951196c45ebcd28b7d4708fb7f56f3bf1/$/_not-found.rsc +24 -0
- package/.next/server/route-cache/APP_PAGE/41da0ef926a63354a29f601d8e8d0a5951196c45ebcd28b7d4708fb7f56f3bf1/$/_not-found.segments/_full.segment.rsc +24 -0
- package/.next/server/route-cache/APP_PAGE/41da0ef926a63354a29f601d8e8d0a5951196c45ebcd28b7d4708fb7f56f3bf1/$/_not-found.segments/_not-found/__PAGE__.segment.rsc +30 -0
- package/.next/server/route-cache/APP_PAGE/41da0ef926a63354a29f601d8e8d0a5951196c45ebcd28b7d4708fb7f56f3bf1/$/_not-found.segments/_tree.segment.rsc +4 -0
- package/.next/server/route-cache/APP_PAGE/c6377dd9ce6a1c2cd100eb4344e843eb4639b9ed26f84643f77680e9eb454c4b/$/index.html +1 -0
- package/.next/server/route-cache/APP_PAGE/c6377dd9ce6a1c2cd100eb4344e843eb4639b9ed26f84643f77680e9eb454c4b/$/index.meta +1 -0
- package/.next/server/route-cache/APP_PAGE/c6377dd9ce6a1c2cd100eb4344e843eb4639b9ed26f84643f77680e9eb454c4b/$/index.rsc +26 -0
- package/.next/server/route-cache/APP_PAGE/c6377dd9ce6a1c2cd100eb4344e843eb4639b9ed26f84643f77680e9eb454c4b/$/index.segments/__PAGE__.segment.rsc +31 -0
- package/.next/server/route-cache/APP_PAGE/c6377dd9ce6a1c2cd100eb4344e843eb4639b9ed26f84643f77680e9eb454c4b/$/index.segments/_full.segment.rsc +26 -0
- package/.next/server/route-cache/APP_PAGE/c6377dd9ce6a1c2cd100eb4344e843eb4639b9ed26f84643f77680e9eb454c4b/$/index.segments/_tree.segment.rsc +3 -0
- package/.next/static/chunks/408-4fe7147e38981ee1.js +1 -0
- package/.next/static/chunks/939-514e554087419c71.js +1 -0
- package/.next/static/chunks/app/history/page-69ea64476ee5db85.js +3 -0
- package/.next/static/chunks/app/processes/{page-e471ed23a1d5632f.js → page-0e98a4a64e829c5f.js} +1 -1
- package/.next/static/css/3fbf75e4c471c6a8.css +1 -0
- package/ARCHITECTURE.md +30 -4
- package/CHANGELOG.md +47 -2
- package/dist/cli.cjs +1627 -1042
- package/dist/collector.cjs +138 -226
- package/dist/hub.cjs +7 -20
- package/dist/ops.cjs +160 -33
- package/dist/system.cjs +869 -450
- package/docs/bottlenecks.md +4 -6
- package/docs/configuration.md +2 -0
- package/docs/installation.md +42 -7
- package/docs/operations.md +32 -16
- package/docs/releasing.md +21 -17
- package/docs/system-installation.md +11 -2
- package/package.json +1 -2
- package/.next/static/chunks/408-dc6158e0bb450ef6.js +0 -1
- package/.next/static/chunks/939-2600d7c8e583d5ef.js +0 -1
- package/.next/static/chunks/app/history/page-1475454737279ee0.js +0 -3
- package/.next/static/css/e45c0c7ef5ba0336.css +0 -1
- /package/.next/static/{NE6PGEiJxUnKRceuzUWPc → xjSDCOO8Gpm5iPO9tyRG5}/_buildManifest.js +0 -0
- /package/.next/static/{NE6PGEiJxUnKRceuzUWPc → xjSDCOO8Gpm5iPO9tyRG5}/_ssgManifest.js +0 -0
package/dist/system.cjs
CHANGED
|
@@ -28,7 +28,6 @@ __export(system_exports, {
|
|
|
28
28
|
RAPL_RULE_NAME: () => RAPL_RULE_NAME,
|
|
29
29
|
STATE_FILE_NAME: () => STATE_FILE_NAME,
|
|
30
30
|
SYSTEM_SCOPE: () => SYSTEM_SCOPE,
|
|
31
|
-
SYSTEM_STATE_FORMAT_VERSION: () => SYSTEM_STATE_FORMAT_VERSION,
|
|
32
31
|
SYSUSERS_NAME: () => SYSUSERS_NAME,
|
|
33
32
|
SystemError: () => SystemError,
|
|
34
33
|
UNIT_NAMES: () => UNIT_NAMES,
|
|
@@ -73,6 +72,7 @@ __export(system_exports, {
|
|
|
73
72
|
startSystem: () => startSystem,
|
|
74
73
|
stateFilePath: () => stateFilePath,
|
|
75
74
|
stopSystem: () => stopSystem,
|
|
75
|
+
systemStateIsLegacy: () => systemStateIsLegacy,
|
|
76
76
|
systemStatus: () => systemStatus,
|
|
77
77
|
systemdQuote: () => systemdQuote,
|
|
78
78
|
sysusersPath: () => sysusersPath,
|
|
@@ -89,7 +89,7 @@ module.exports = __toCommonJS(system_exports);
|
|
|
89
89
|
var import_node_fs3 = require("node:fs");
|
|
90
90
|
var import_node_child_process = require("node:child_process");
|
|
91
91
|
var import_node_crypto5 = require("node:crypto");
|
|
92
|
-
var
|
|
92
|
+
var import_node_path4 = require("node:path");
|
|
93
93
|
var import_node_sqlite = require("node:sqlite");
|
|
94
94
|
var import_node_util = require("node:util");
|
|
95
95
|
|
|
@@ -97,12 +97,12 @@ var import_node_util = require("node:util");
|
|
|
97
97
|
var import_node_http = require("node:http");
|
|
98
98
|
var MAX_HEALTH_BODY_BYTES = 64 * 1024;
|
|
99
99
|
function probeHealth(target, timeoutMs = 3e3) {
|
|
100
|
-
return new Promise((
|
|
100
|
+
return new Promise((resolve4) => {
|
|
101
101
|
let settled = false;
|
|
102
102
|
const finish = (result) => {
|
|
103
103
|
if (settled) return;
|
|
104
104
|
settled = true;
|
|
105
|
-
|
|
105
|
+
resolve4(result);
|
|
106
106
|
};
|
|
107
107
|
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
108
|
let req;
|
|
@@ -185,7 +185,7 @@ function bounded(value, min, max) {
|
|
|
185
185
|
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
|
|
186
186
|
}
|
|
187
187
|
function delay(ms) {
|
|
188
|
-
return new Promise((
|
|
188
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
189
189
|
}
|
|
190
190
|
function errorText(error) {
|
|
191
191
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -390,7 +390,6 @@ var ConfigError = class extends Error {
|
|
|
390
390
|
var DEFAULT_COLLECTOR_ID = "mock-collector";
|
|
391
391
|
var MOCK_HOSTNAME = "mock-machine";
|
|
392
392
|
var CONNECT_PATH = "/api/collector/connect";
|
|
393
|
-
var PREVIOUS_CONNECT_PATH = "/api/collector/v1/connect";
|
|
394
393
|
var MAX_DESTINATIONS = 8;
|
|
395
394
|
var DEFAULT_INTERVAL_MS = 1e3;
|
|
396
395
|
var MIN_INTERVAL_MS = 100;
|
|
@@ -467,7 +466,6 @@ function validateHubUrl(raw, label, issues) {
|
|
|
467
466
|
issues.push(`${label}: URL must include a host`);
|
|
468
467
|
return null;
|
|
469
468
|
}
|
|
470
|
-
if (parsed.pathname === PREVIOUS_CONNECT_PATH) parsed.pathname = CONNECT_PATH;
|
|
471
469
|
if (parsed.pathname !== CONNECT_PATH) {
|
|
472
470
|
issues.push(`${label}: path must be exactly ${CONNECT_PATH}`);
|
|
473
471
|
return null;
|
|
@@ -634,18 +632,26 @@ var import_node_path2 = require("node:path");
|
|
|
634
632
|
function readState(path) {
|
|
635
633
|
const stat = (0, import_node_fs2.lstatSync)(path);
|
|
636
634
|
if (!stat.isFile() || stat.size > 64 * 1024 || (stat.mode & 63) !== 0) throw new Error("collector state must be a private regular file");
|
|
637
|
-
const
|
|
638
|
-
if (
|
|
635
|
+
const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
|
|
636
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid collector state");
|
|
637
|
+
const raw = parsed;
|
|
638
|
+
if (Object.hasOwn(raw, "version") || Object.hasOwn(raw, "formatVersion")) {
|
|
639
|
+
throw new Error("collector state uses an older versioned shape; run rigscope install or upgrade to reset it");
|
|
640
|
+
}
|
|
641
|
+
if (typeof raw.collectorId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(raw.collectorId) || !Array.isArray(raw.destinations)) throw new Error("invalid collector state");
|
|
639
642
|
const destinations = raw.destinations.length === 0 ? [] : [...parseCollectorConfig({ COLLECTOR_HUBS: JSON.stringify(raw.destinations) }).destinations];
|
|
640
|
-
|
|
641
|
-
|
|
643
|
+
const pending = raw.pendingEnrollments;
|
|
644
|
+
if (pending !== void 0 && (!Array.isArray(pending) || pending.length > 8 || pending.some((p) => !p || typeof p !== "object" || typeof p.url !== "string" || typeof p.requestId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(p.requestId) || typeof p.recoverySecret !== "string" || !/^[A-Za-z0-9_-]{32,128}$/.test(p.recoverySecret)))) {
|
|
645
|
+
throw new Error("invalid pending enrollment recovery state");
|
|
646
|
+
}
|
|
647
|
+
const pendingEnrollments = pending?.map((value) => {
|
|
648
|
+
const pendingEnrollment = value;
|
|
642
649
|
const issues = [];
|
|
643
|
-
const url = validateHubUrl(
|
|
650
|
+
const url = validateHubUrl(pendingEnrollment.url, "pending enrollment", issues);
|
|
644
651
|
if (!url || issues.length) throw new ConfigError(issues);
|
|
645
|
-
return { ...
|
|
652
|
+
return { ...pendingEnrollment, url: url.href };
|
|
646
653
|
});
|
|
647
654
|
return {
|
|
648
|
-
version: 1,
|
|
649
655
|
collectorId: raw.collectorId,
|
|
650
656
|
destinations,
|
|
651
657
|
...pendingEnrollments === void 0 ? {} : { pendingEnrollments }
|
|
@@ -678,9 +684,14 @@ function updateInstallation(path, change = (s) => s) {
|
|
|
678
684
|
state = readState(path);
|
|
679
685
|
} catch (error) {
|
|
680
686
|
if (error.code !== "ENOENT") throw error;
|
|
681
|
-
state = {
|
|
687
|
+
state = { collectorId: (0, import_node_crypto3.randomUUID)(), destinations: [] };
|
|
682
688
|
}
|
|
683
|
-
const
|
|
689
|
+
const changed = change(state);
|
|
690
|
+
const next = {
|
|
691
|
+
collectorId: changed.collectorId,
|
|
692
|
+
destinations: changed.destinations,
|
|
693
|
+
...changed.pendingEnrollments === void 0 ? {} : { pendingEnrollments: changed.pendingEnrollments }
|
|
694
|
+
};
|
|
684
695
|
temporary = `${path}.${(0, import_node_crypto3.randomUUID)()}.tmp`;
|
|
685
696
|
const out = (0, import_node_fs2.openSync)(temporary, "wx", 384);
|
|
686
697
|
try {
|
|
@@ -775,8 +786,241 @@ async function enrollCollector(path, connectUrl, enrollmentToken, sharing, repla
|
|
|
775
786
|
}
|
|
776
787
|
}
|
|
777
788
|
|
|
789
|
+
// src/hub/config.ts
|
|
790
|
+
var import_node_net = require("node:net");
|
|
791
|
+
var import_node_path3 = require("node:path");
|
|
792
|
+
var MAX_HUB_COLLECTORS_BYTES = 1024 * 1024;
|
|
793
|
+
var MAX_COLLECTOR_ID_LENGTH = 128;
|
|
794
|
+
var MAX_MACHINE_ID_LENGTH = 128;
|
|
795
|
+
var MAX_TOKEN_LENGTH2 = 256;
|
|
796
|
+
var MAX_UNIX_SOCKET_PATH_BYTES = 100;
|
|
797
|
+
var MAX_PUBLIC_ORIGIN_LENGTH = 512;
|
|
798
|
+
var HubConfigError = class extends Error {
|
|
799
|
+
constructor(message) {
|
|
800
|
+
super(message);
|
|
801
|
+
this.name = "HubConfigError";
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
function parseHubConfig(env) {
|
|
805
|
+
const development = env.NODE_ENV === "development";
|
|
806
|
+
const collectors = parseCollectors(env.HUB_COLLECTORS);
|
|
807
|
+
const publicOrigin = nonEmpty(env.HUB_PUBLIC_ORIGIN);
|
|
808
|
+
const socketPath = nonEmpty(env.HUB_SOCKET_PATH);
|
|
809
|
+
let maxCollectorConnections;
|
|
810
|
+
if (env.HUB_MAX_COLLECTOR_CONNECTIONS !== void 0) {
|
|
811
|
+
const value = env.HUB_MAX_COLLECTOR_CONNECTIONS;
|
|
812
|
+
const parsed = Number(value);
|
|
813
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(parsed) || parsed < 1) {
|
|
814
|
+
throw new HubConfigError("HUB_MAX_COLLECTOR_CONNECTIONS must be a positive safe integer");
|
|
815
|
+
}
|
|
816
|
+
maxCollectorConnections = parsed;
|
|
817
|
+
}
|
|
818
|
+
const connectionBudget = maxCollectorConnections === void 0 ? {} : { maxCollectorConnections };
|
|
819
|
+
if (publicOrigin === void 0 !== (socketPath === void 0)) {
|
|
820
|
+
throw new HubConfigError(
|
|
821
|
+
"HUB_PUBLIC_ORIGIN and HUB_SOCKET_PATH must be set together: remote mode needs both the exact HTTPS origin and the Unix socket path"
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
if (publicOrigin !== void 0 && socketPath !== void 0) {
|
|
825
|
+
if (nonEmpty(env.HUB_HOST) !== void 0 || nonEmpty(env.HUB_PORT) !== void 0) {
|
|
826
|
+
throw new HubConfigError(
|
|
827
|
+
"HUB_HOST/HUB_PORT are not valid in remote mode; the hub listens only on HUB_SOCKET_PATH"
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
// Keep stable local defaults for callers that inspect these fields;
|
|
832
|
+
// main.ts never creates a TCP listener when `remote` is set.
|
|
833
|
+
host: "127.0.0.1",
|
|
834
|
+
port: 8081,
|
|
835
|
+
collectors,
|
|
836
|
+
development,
|
|
837
|
+
remote: parseRemoteConfig(publicOrigin, socketPath),
|
|
838
|
+
...connectionBudget
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
host: parseHost(env.HUB_HOST),
|
|
843
|
+
port: parsePort(env.HUB_PORT),
|
|
844
|
+
collectors,
|
|
845
|
+
development,
|
|
846
|
+
remote: null,
|
|
847
|
+
...connectionBudget
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function nonEmpty(value) {
|
|
851
|
+
return value === void 0 || value === "" ? void 0 : value;
|
|
852
|
+
}
|
|
853
|
+
function parsePublicOrigin(value) {
|
|
854
|
+
if (value.length > MAX_PUBLIC_ORIGIN_LENGTH) {
|
|
855
|
+
throw new HubConfigError(
|
|
856
|
+
`HUB_PUBLIC_ORIGIN must be at most ${MAX_PUBLIC_ORIGIN_LENGTH} characters`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
if (!/^[\x21-\x7e]+$/.test(value)) {
|
|
860
|
+
throw new HubConfigError(
|
|
861
|
+
"HUB_PUBLIC_ORIGIN must be printable ASCII without spaces or control characters"
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
let parsed;
|
|
865
|
+
try {
|
|
866
|
+
parsed = new URL(value);
|
|
867
|
+
} catch {
|
|
868
|
+
throw new HubConfigError(
|
|
869
|
+
`HUB_PUBLIC_ORIGIN "${value}" is not a valid absolute origin`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (parsed.protocol !== "https:") {
|
|
873
|
+
throw new HubConfigError("HUB_PUBLIC_ORIGIN must use https://");
|
|
874
|
+
}
|
|
875
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
876
|
+
throw new HubConfigError("HUB_PUBLIC_ORIGIN must not contain userinfo");
|
|
877
|
+
}
|
|
878
|
+
if (parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
|
|
879
|
+
throw new HubConfigError(
|
|
880
|
+
"HUB_PUBLIC_ORIGIN must be exactly scheme://host[:port] with no path, query, or fragment"
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
if (parsed.origin !== value) {
|
|
884
|
+
throw new HubConfigError(
|
|
885
|
+
`HUB_PUBLIC_ORIGIN must be canonical; use "${parsed.origin}"`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (parsed.hostname.length === 0 || parsed.hostname.endsWith(".")) {
|
|
889
|
+
throw new HubConfigError("HUB_PUBLIC_ORIGIN must name a nonempty host");
|
|
890
|
+
}
|
|
891
|
+
if (parsed.host.length > 255) {
|
|
892
|
+
throw new HubConfigError("HUB_PUBLIC_ORIGIN host is too long");
|
|
893
|
+
}
|
|
894
|
+
if (parsed.port === "0") {
|
|
895
|
+
throw new HubConfigError("HUB_PUBLIC_ORIGIN must use a nonzero public port");
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
publicOrigin: value,
|
|
899
|
+
hostHeader: parsed.host,
|
|
900
|
+
hostname: parsed.hostname,
|
|
901
|
+
port: parsed.port === "" ? 443 : Number(parsed.port)
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function parseRemoteConfig(publicOrigin, socketPath) {
|
|
905
|
+
const origin = parsePublicOrigin(publicOrigin);
|
|
906
|
+
validateUnixSocketPathSyntax(socketPath);
|
|
907
|
+
return { ...origin, socketPath };
|
|
908
|
+
}
|
|
909
|
+
function validateUnixSocketPathSyntax(value) {
|
|
910
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
911
|
+
throw new HubConfigError("HUB_SOCKET_PATH must be a nonempty absolute path");
|
|
912
|
+
}
|
|
913
|
+
if (Buffer.byteLength(value, "utf8") > MAX_UNIX_SOCKET_PATH_BYTES) {
|
|
914
|
+
throw new HubConfigError(
|
|
915
|
+
`HUB_SOCKET_PATH must be at most ${MAX_UNIX_SOCKET_PATH_BYTES} bytes for a Unix socket`
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
919
|
+
throw new HubConfigError(
|
|
920
|
+
"HUB_SOCKET_PATH must not contain NUL or control characters"
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
if (!(0, import_node_path3.isAbsolute)(value)) {
|
|
924
|
+
throw new HubConfigError("HUB_SOCKET_PATH must be an absolute path");
|
|
925
|
+
}
|
|
926
|
+
if (value !== (0, import_node_path3.resolve)(value)) {
|
|
927
|
+
throw new HubConfigError("HUB_SOCKET_PATH must be a normalized absolute path");
|
|
928
|
+
}
|
|
929
|
+
const name = (0, import_node_path3.basename)(value);
|
|
930
|
+
if (name === "" || name === "." || name === ".." || value === "/") {
|
|
931
|
+
throw new HubConfigError("HUB_SOCKET_PATH must name a socket file");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function parseHost(value) {
|
|
935
|
+
const host = value === void 0 || value === "" ? "127.0.0.1" : value;
|
|
936
|
+
if (typeof host !== "string" || host.length === 0 || host.length > 45) {
|
|
937
|
+
throw new HubConfigError(`HUB_HOST must be a literal loopback address`);
|
|
938
|
+
}
|
|
939
|
+
if (isIPv4Loopback(host)) return host;
|
|
940
|
+
if (host === "::1") return host;
|
|
941
|
+
throw new HubConfigError(
|
|
942
|
+
`HUB_HOST "${host}" is not a literal loopback address; only 127.0.0.0/8 and ::1 are allowed`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
function isIPv4Loopback(host) {
|
|
946
|
+
if ((0, import_node_net.isIP)(host) !== 4) return false;
|
|
947
|
+
const parts = host.split(".");
|
|
948
|
+
if (parts[0] !== "127" || parts.length !== 4) return false;
|
|
949
|
+
return parts.every((part) => /^\d+$/.test(part) && Number(part) <= 255);
|
|
950
|
+
}
|
|
951
|
+
function parsePort(value) {
|
|
952
|
+
const text = value === void 0 || value === "" ? "8081" : value.trim();
|
|
953
|
+
if (!/^\d+$/.test(text)) {
|
|
954
|
+
throw new HubConfigError(`HUB_PORT must be a nonnegative integer, got "${text}"`);
|
|
955
|
+
}
|
|
956
|
+
const port = Number(text);
|
|
957
|
+
if (port > 65535) {
|
|
958
|
+
throw new HubConfigError(`HUB_PORT must be at most 65535, got ${text}`);
|
|
959
|
+
}
|
|
960
|
+
return port;
|
|
961
|
+
}
|
|
962
|
+
function parseCollectors(value) {
|
|
963
|
+
if (value === void 0 || value === "") return [];
|
|
964
|
+
if (Buffer.byteLength(value, "utf8") > MAX_HUB_COLLECTORS_BYTES) {
|
|
965
|
+
throw new HubConfigError(
|
|
966
|
+
`HUB_COLLECTORS must be at most ${MAX_HUB_COLLECTORS_BYTES} bytes`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
let parsed;
|
|
970
|
+
try {
|
|
971
|
+
parsed = JSON.parse(value);
|
|
972
|
+
} catch {
|
|
973
|
+
throw new HubConfigError(`HUB_COLLECTORS is not valid JSON`);
|
|
974
|
+
}
|
|
975
|
+
if (!Array.isArray(parsed)) {
|
|
976
|
+
throw new HubConfigError(`HUB_COLLECTORS must be a JSON array`);
|
|
977
|
+
}
|
|
978
|
+
const collectors = [];
|
|
979
|
+
const ids = /* @__PURE__ */ new Set();
|
|
980
|
+
const machineIds = /* @__PURE__ */ new Set();
|
|
981
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
982
|
+
for (const [index, item] of parsed.entries()) {
|
|
983
|
+
const message = `HUB_COLLECTORS[${index}]`;
|
|
984
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
985
|
+
throw new HubConfigError(`${message} must be an object`);
|
|
986
|
+
}
|
|
987
|
+
const record = item;
|
|
988
|
+
const keys = Object.keys(record).sort();
|
|
989
|
+
const expected = ["collectorId", "machineId", "token"].sort();
|
|
990
|
+
if (keys.length !== expected.length || keys.some((key, i) => key !== expected[i])) {
|
|
991
|
+
throw new HubConfigError(
|
|
992
|
+
`${message} must contain exactly collectorId, machineId, and token`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
const collectorId = boundedString(record.collectorId, "collectorId", message, MAX_COLLECTOR_ID_LENGTH);
|
|
996
|
+
const machineId = boundedString(record.machineId, "machineId", message, MAX_MACHINE_ID_LENGTH);
|
|
997
|
+
const token = boundedString(record.token, "token", message, MAX_TOKEN_LENGTH2);
|
|
998
|
+
if (ids.has(collectorId)) {
|
|
999
|
+
throw new HubConfigError(`${message}: duplicate collectorId "${collectorId}"`);
|
|
1000
|
+
}
|
|
1001
|
+
if (machineIds.has(machineId)) {
|
|
1002
|
+
throw new HubConfigError(`${message}: duplicate machineId "${machineId}"`);
|
|
1003
|
+
}
|
|
1004
|
+
if (tokens.has(token)) {
|
|
1005
|
+
throw new HubConfigError(`${message}: duplicate token`);
|
|
1006
|
+
}
|
|
1007
|
+
ids.add(collectorId);
|
|
1008
|
+
machineIds.add(machineId);
|
|
1009
|
+
tokens.add(token);
|
|
1010
|
+
collectors.push({ collectorId, machineId, token });
|
|
1011
|
+
}
|
|
1012
|
+
return collectors;
|
|
1013
|
+
}
|
|
1014
|
+
function boundedString(value, field, message, maxLength) {
|
|
1015
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
1016
|
+
throw new HubConfigError(
|
|
1017
|
+
`${message}: ${field} must be a nonempty string of at most ${maxLength} characters`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
return value;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
778
1023
|
// src/cli/system.ts
|
|
779
|
-
var SYSTEM_STATE_FORMAT_VERSION = 2;
|
|
780
1024
|
var SYSTEM_SCOPE = "system";
|
|
781
1025
|
var DEFAULT_SYSTEM_ACCOUNTS = {
|
|
782
1026
|
hub: "rigscope-hub",
|
|
@@ -788,6 +1032,7 @@ var UNIT_NAMES = {
|
|
|
788
1032
|
hub: "rigscope-hub.service",
|
|
789
1033
|
collector: "rigscope-collector.service"
|
|
790
1034
|
};
|
|
1035
|
+
var LEGACY_UNIT_NAMES = ["rigscope.service"];
|
|
791
1036
|
var RAPL_RULE_NAME = "90-rigscope-rapl.rules";
|
|
792
1037
|
var SYSUSERS_NAME = "rigscope.conf";
|
|
793
1038
|
var POWER_SYSUSERS_NAME = "rigscope-rapl.conf";
|
|
@@ -884,13 +1129,13 @@ function createSystemHost(overrides = {}) {
|
|
|
884
1129
|
}
|
|
885
1130
|
function defaultSystemPaths(overrides = {}) {
|
|
886
1131
|
return {
|
|
887
|
-
prefix: (0,
|
|
888
|
-
configDir: (0,
|
|
889
|
-
stateDir: (0,
|
|
890
|
-
unitsDir: (0,
|
|
891
|
-
sysusersDir: (0,
|
|
892
|
-
udevRulesDir: (0,
|
|
893
|
-
powercapRoot: (0,
|
|
1132
|
+
prefix: (0, import_node_path4.resolve)(overrides.prefix ?? "/opt/rigscope"),
|
|
1133
|
+
configDir: (0, import_node_path4.resolve)(overrides.configDir ?? "/etc/rigscope"),
|
|
1134
|
+
stateDir: (0, import_node_path4.resolve)(overrides.stateDir ?? "/var/lib/rigscope"),
|
|
1135
|
+
unitsDir: (0, import_node_path4.resolve)(overrides.unitsDir ?? "/etc/systemd/system"),
|
|
1136
|
+
sysusersDir: (0, import_node_path4.resolve)(overrides.sysusersDir ?? "/etc/sysusers.d"),
|
|
1137
|
+
udevRulesDir: (0, import_node_path4.resolve)(overrides.udevRulesDir ?? "/etc/udev/rules.d"),
|
|
1138
|
+
powercapRoot: (0, import_node_path4.resolve)(overrides.powercapRoot ?? "/sys/class/powercap")
|
|
894
1139
|
};
|
|
895
1140
|
}
|
|
896
1141
|
function unitNamesFor(role) {
|
|
@@ -905,11 +1150,11 @@ function rolesFor(role) {
|
|
|
905
1150
|
return { hub: role !== "collector", collector: role !== "hub" };
|
|
906
1151
|
}
|
|
907
1152
|
function isWithin(parent, candidate) {
|
|
908
|
-
const rel = (0,
|
|
909
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
1153
|
+
const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(parent), (0, import_node_path4.resolve)(candidate));
|
|
1154
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${import_node_path4.sep}`) && !(0, import_node_path4.isAbsolute)(rel);
|
|
910
1155
|
}
|
|
911
1156
|
function ensureAbsolute(path, label) {
|
|
912
|
-
const absolute = (0,
|
|
1157
|
+
const absolute = (0, import_node_path4.resolve)(path);
|
|
913
1158
|
if (/[\0\r\n]/.test(absolute)) throw new SystemError("unsafe-path", `${label} contains control characters`);
|
|
914
1159
|
if (absolute === "/") throw new SystemError("unsafe-path", `${label} must not be the filesystem root`);
|
|
915
1160
|
for (const forbidden of ["/home", "/root", "/proc", "/sys", "/dev", "/boot"]) {
|
|
@@ -923,7 +1168,7 @@ function validateSystemPaths(input = {}) {
|
|
|
923
1168
|
const paths = defaultSystemPaths(input);
|
|
924
1169
|
for (const [label, value] of Object.entries(paths)) {
|
|
925
1170
|
if (label === "powercapRoot") {
|
|
926
|
-
if (!(0,
|
|
1171
|
+
if (!(0, import_node_path4.isAbsolute)(value) || /[\0\r\n]/.test(value) || (0, import_node_path4.resolve)(value) === "/") {
|
|
927
1172
|
throw new SystemError("unsafe-path", `powercap root is unsafe: ${value}`);
|
|
928
1173
|
}
|
|
929
1174
|
} else {
|
|
@@ -982,8 +1227,8 @@ function sha256File(path) {
|
|
|
982
1227
|
return (0, import_node_crypto5.createHash)("sha256").update((0, import_node_fs3.readFileSync)(path)).digest("hex");
|
|
983
1228
|
}
|
|
984
1229
|
function atomicWrite(path, content, mode) {
|
|
985
|
-
(0, import_node_fs3.mkdirSync)((0,
|
|
986
|
-
const temporary = (0,
|
|
1230
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true, mode: 493 });
|
|
1231
|
+
const temporary = (0, import_node_path4.join)((0, import_node_path4.dirname)(path), `.${(0, import_node_path4.basename)(path)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
987
1232
|
try {
|
|
988
1233
|
(0, import_node_fs3.writeFileSync)(temporary, content, { mode });
|
|
989
1234
|
(0, import_node_fs3.chmodSync)(temporary, mode);
|
|
@@ -1115,38 +1360,39 @@ function connectDestinationUrl(value) {
|
|
|
1115
1360
|
}
|
|
1116
1361
|
if (url.search !== "" || url.hash !== "") throw new SystemError("invalid-url", "Hub URLs must not contain a query or fragment.");
|
|
1117
1362
|
if (url.hostname === "localhost") url.hostname = "127.0.0.1";
|
|
1118
|
-
if (url.pathname === "/") url.pathname =
|
|
1363
|
+
if (url.pathname === "/") url.pathname = CONNECT_PATH;
|
|
1364
|
+
if (url.pathname !== CONNECT_PATH) throw new SystemError("invalid-url", `Collector endpoint must be ${CONNECT_PATH}.`);
|
|
1119
1365
|
return url.href;
|
|
1120
1366
|
}
|
|
1121
1367
|
function hubDatabasePath(paths) {
|
|
1122
|
-
return (0,
|
|
1368
|
+
return (0, import_node_path4.join)(paths.stateDir, "hub", "telemetry.sqlite");
|
|
1123
1369
|
}
|
|
1124
1370
|
function hubEnrollmentPath(paths) {
|
|
1125
|
-
return (0,
|
|
1371
|
+
return (0, import_node_path4.join)(paths.stateDir, "hub", "enrollment.json");
|
|
1126
1372
|
}
|
|
1127
1373
|
function hubSocketPath(paths) {
|
|
1128
|
-
return (0,
|
|
1374
|
+
return (0, import_node_path4.join)(paths.stateDir, "hub", "run", "hub.sock");
|
|
1129
1375
|
}
|
|
1130
1376
|
function collectorStatePath(paths) {
|
|
1131
|
-
return (0,
|
|
1377
|
+
return (0, import_node_path4.join)(paths.stateDir, "collector", "state.json");
|
|
1132
1378
|
}
|
|
1133
1379
|
function hubEnvPath(paths) {
|
|
1134
|
-
return (0,
|
|
1380
|
+
return (0, import_node_path4.join)(paths.configDir, "hub.env");
|
|
1135
1381
|
}
|
|
1136
1382
|
function collectorEnvPath(paths) {
|
|
1137
|
-
return (0,
|
|
1383
|
+
return (0, import_node_path4.join)(paths.configDir, "collector.env");
|
|
1138
1384
|
}
|
|
1139
1385
|
function sysusersPath(paths) {
|
|
1140
|
-
return (0,
|
|
1386
|
+
return (0, import_node_path4.join)(paths.sysusersDir, SYSUSERS_NAME);
|
|
1141
1387
|
}
|
|
1142
1388
|
function powerSysusersPath(paths) {
|
|
1143
|
-
return (0,
|
|
1389
|
+
return (0, import_node_path4.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
|
|
1144
1390
|
}
|
|
1145
1391
|
function raplRulePath(paths) {
|
|
1146
|
-
return (0,
|
|
1392
|
+
return (0, import_node_path4.join)(paths.udevRulesDir, RAPL_RULE_NAME);
|
|
1147
1393
|
}
|
|
1148
1394
|
function stateFilePath(paths) {
|
|
1149
|
-
return (0,
|
|
1395
|
+
return (0, import_node_path4.join)(paths.stateDir, STATE_FILE_NAME);
|
|
1150
1396
|
}
|
|
1151
1397
|
function readCollectorIdentity(path) {
|
|
1152
1398
|
if (!(0, import_node_fs3.existsSync)(path)) return null;
|
|
@@ -1161,7 +1407,10 @@ function readCollectorIdentity(path) {
|
|
|
1161
1407
|
}
|
|
1162
1408
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new SystemError("invalid-file", `${path} is invalid`);
|
|
1163
1409
|
const record = raw;
|
|
1164
|
-
if (
|
|
1410
|
+
if (Object.prototype.hasOwnProperty.call(record, "version") || Object.prototype.hasOwnProperty.call(record, "formatVersion")) {
|
|
1411
|
+
throw new SystemError("invalid-file", `${path} is pre-canonical; reinstall or reset collector state`);
|
|
1412
|
+
}
|
|
1413
|
+
if (typeof record.collectorId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(record.collectorId) || !Array.isArray(record.destinations)) {
|
|
1165
1414
|
throw new SystemError("invalid-file", `${path} is not a collector installation state`);
|
|
1166
1415
|
}
|
|
1167
1416
|
const destinations = [];
|
|
@@ -1173,16 +1422,20 @@ function readCollectorIdentity(path) {
|
|
|
1173
1422
|
if (typeof destination.url !== "string" || typeof destination.token !== "string" || destination.token === "") {
|
|
1174
1423
|
throw new SystemError("invalid-file", `${path} contains an invalid destination`);
|
|
1175
1424
|
}
|
|
1425
|
+
const issues = [];
|
|
1426
|
+
if (!validateHubUrl(destination.url, "saved collector destination", issues)) {
|
|
1427
|
+
throw new SystemError("invalid-file", `${path} contains an incompatible collector endpoint; reset or reinstall it`);
|
|
1428
|
+
}
|
|
1176
1429
|
destinations.push({
|
|
1177
1430
|
url: destination.url,
|
|
1178
1431
|
token: destination.token,
|
|
1179
1432
|
...destination.sharing !== null && typeof destination.sharing === "object" && !Array.isArray(destination.sharing) ? { sharing: destination.sharing } : {}
|
|
1180
1433
|
});
|
|
1181
1434
|
}
|
|
1182
|
-
return {
|
|
1435
|
+
return { collectorId: record.collectorId, destinations };
|
|
1183
1436
|
}
|
|
1184
1437
|
function writeCollectorIdentity(path, identity) {
|
|
1185
|
-
ensureDirectory((0,
|
|
1438
|
+
ensureDirectory((0, import_node_path4.dirname)(path), 448, true);
|
|
1186
1439
|
atomicWrite(path, `${JSON.stringify(identity)}
|
|
1187
1440
|
`, 384);
|
|
1188
1441
|
}
|
|
@@ -1355,22 +1608,20 @@ async function pairCollectorWithHub(options) {
|
|
|
1355
1608
|
note: "the local hub has no reachable loopback or HTTPS destination"
|
|
1356
1609
|
};
|
|
1357
1610
|
}
|
|
1358
|
-
if (!(0, import_node_fs3.existsSync)((0,
|
|
1611
|
+
if (!(0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, UNIT_NAMES.hub))) {
|
|
1359
1612
|
return { paired: false, awaiting: true, hubWasActive: false, note: "the local hub unit is not installed" };
|
|
1360
1613
|
}
|
|
1361
1614
|
const hubWasActive = systemdActive(host, UNIT_NAMES.hub);
|
|
1362
1615
|
if (hubWasActive) {
|
|
1363
1616
|
if (existing !== null) {
|
|
1364
|
-
writeCollectorIdentity(collectorStatePath(paths), existing);
|
|
1365
1617
|
const registered = findCollectorInHubDatabase(paths, existing.collectorId);
|
|
1366
1618
|
if (registered !== null) {
|
|
1367
1619
|
const adopted = {
|
|
1368
|
-
version: 1,
|
|
1369
1620
|
collectorId: existing.collectorId,
|
|
1370
1621
|
destinations: [{ url: destination.url, token: registered.token, sharing: { ...SYSTEM_DEFAULT_SHARING } }]
|
|
1371
1622
|
};
|
|
1372
1623
|
writeCollectorIdentity(collectorStatePath(paths), adopted);
|
|
1373
|
-
chownAccount([(0,
|
|
1624
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1374
1625
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1375
1626
|
}
|
|
1376
1627
|
}
|
|
@@ -1378,7 +1629,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1378
1629
|
try {
|
|
1379
1630
|
grant = createLocalEnrollmentGrant(paths, host);
|
|
1380
1631
|
} catch (error) {
|
|
1381
|
-
chownAccount([(0,
|
|
1632
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1382
1633
|
return {
|
|
1383
1634
|
paired: false,
|
|
1384
1635
|
awaiting: true,
|
|
@@ -1396,12 +1647,12 @@ async function pairCollectorWithHub(options) {
|
|
|
1396
1647
|
note: `enrollment through the live hub failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1397
1648
|
};
|
|
1398
1649
|
} finally {
|
|
1399
|
-
chownAccount([(0,
|
|
1650
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1400
1651
|
}
|
|
1401
1652
|
if (!collectorConfigured(paths).configured) {
|
|
1402
1653
|
return { paired: false, awaiting: true, hubWasActive, note: "the hub accepted enrollment but no destination was saved" };
|
|
1403
1654
|
}
|
|
1404
|
-
chownAccount([(0,
|
|
1655
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1405
1656
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1406
1657
|
}
|
|
1407
1658
|
const identity = {
|
|
@@ -1425,7 +1676,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1425
1676
|
}
|
|
1426
1677
|
}
|
|
1427
1678
|
}
|
|
1428
|
-
const next = existing ?? {
|
|
1679
|
+
const next = existing ?? { collectorId: identity.collectorId, destinations: [] };
|
|
1429
1680
|
next.destinations = [
|
|
1430
1681
|
{
|
|
1431
1682
|
url: destination.url,
|
|
@@ -1434,7 +1685,7 @@ async function pairCollectorWithHub(options) {
|
|
|
1434
1685
|
}
|
|
1435
1686
|
];
|
|
1436
1687
|
writeCollectorIdentity(collectorStatePath(paths), next);
|
|
1437
|
-
chownAccount([(0,
|
|
1688
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
1438
1689
|
return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
|
|
1439
1690
|
}
|
|
1440
1691
|
function unitCommon(name, description, paths, account, envPath, execStart, extraService = []) {
|
|
@@ -1451,7 +1702,7 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
|
|
|
1451
1702
|
"Type=simple",
|
|
1452
1703
|
`User=${account}`,
|
|
1453
1704
|
`Group=${account}`,
|
|
1454
|
-
`WorkingDirectory=${(0,
|
|
1705
|
+
`WorkingDirectory=${(0, import_node_path4.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
|
|
1455
1706
|
`EnvironmentFile=${envPath.replace(/%/g, "%%")}`,
|
|
1456
1707
|
"Environment=NODE_ENV=production",
|
|
1457
1708
|
"Restart=on-failure",
|
|
@@ -1486,13 +1737,13 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
|
|
|
1486
1737
|
}
|
|
1487
1738
|
function renderSystemHubUnit(options) {
|
|
1488
1739
|
const { paths, accounts } = options;
|
|
1489
|
-
const hubScript = (0,
|
|
1740
|
+
const hubScript = (0, import_node_path4.join)(paths.prefix, "current", "dist", "hub.cjs");
|
|
1490
1741
|
const extra = [
|
|
1491
1742
|
`Environment=${systemdQuote(`HUB_DATABASE_PATH=${options.database}`)}`,
|
|
1492
1743
|
`Environment=${systemdQuote(`HUB_STATE_FILE=${options.enrollment}`)}`,
|
|
1493
1744
|
...options.remote ? [`Environment=${systemdQuote(`HUB_SOCKET_PATH=${options.socket}`)}`] : [],
|
|
1494
|
-
`ReadWritePaths=${systemdQuote((0,
|
|
1495
|
-
`ReadOnlyPaths=${systemdQuote((0,
|
|
1745
|
+
`ReadWritePaths=${systemdQuote((0, import_node_path4.join)(paths.stateDir, "hub"))}`,
|
|
1746
|
+
`ReadOnlyPaths=${systemdQuote((0, import_node_path4.join)(paths.prefix, "current"))}`,
|
|
1496
1747
|
"PrivateDevices=yes",
|
|
1497
1748
|
"TimeoutStopSec=15s"
|
|
1498
1749
|
];
|
|
@@ -1511,14 +1762,14 @@ function renderSystemHubUnit(options) {
|
|
|
1511
1762
|
}
|
|
1512
1763
|
function renderSystemCollectorUnit(options) {
|
|
1513
1764
|
const { paths, accounts } = options;
|
|
1514
|
-
const collectorScript = (0,
|
|
1765
|
+
const collectorScript = (0, import_node_path4.join)(paths.prefix, "current", "dist", "collector.cjs");
|
|
1515
1766
|
const extra = [
|
|
1516
1767
|
...options.powerAccess ? [`SupplementaryGroups=${accounts.powerGroup}`] : [],
|
|
1517
1768
|
`Environment=${systemdQuote(`COLLECTOR_MODE=real`)}`,
|
|
1518
1769
|
`Environment=${systemdQuote(`COLLECTOR_STATE_FILE=${options.collectorState}`)}`,
|
|
1519
|
-
`Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0,
|
|
1520
|
-
`ReadWritePaths=${systemdQuote((0,
|
|
1521
|
-
`ReadOnlyPaths=${systemdQuote((0,
|
|
1770
|
+
`Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0, import_node_path4.join)(paths.stateDir, "collector", "health.json")}`)}`,
|
|
1771
|
+
`ReadWritePaths=${systemdQuote((0, import_node_path4.join)(paths.stateDir, "collector"))}`,
|
|
1772
|
+
`ReadOnlyPaths=${systemdQuote((0, import_node_path4.join)(paths.prefix, "current"))}`,
|
|
1522
1773
|
"TimeoutStopSec=10s"
|
|
1523
1774
|
];
|
|
1524
1775
|
return [
|
|
@@ -1621,6 +1872,12 @@ function readJsonObject(path) {
|
|
|
1621
1872
|
}
|
|
1622
1873
|
return value;
|
|
1623
1874
|
}
|
|
1875
|
+
function isCanonicalComponents(value) {
|
|
1876
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1877
|
+
const record = value;
|
|
1878
|
+
const enabled = (entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && typeof entry.enabled === "boolean";
|
|
1879
|
+
return enabled(record.hub) && enabled(record.collector);
|
|
1880
|
+
}
|
|
1624
1881
|
function readSystemState(paths) {
|
|
1625
1882
|
const path = stateFilePath(paths);
|
|
1626
1883
|
if (!(0, import_node_fs3.existsSync)(path)) return null;
|
|
@@ -1628,7 +1885,7 @@ function readSystemState(paths) {
|
|
|
1628
1885
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
|
|
1629
1886
|
if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be 0600`);
|
|
1630
1887
|
const raw = readJsonObject(path);
|
|
1631
|
-
if (raw.
|
|
1888
|
+
if (raw.scope !== SYSTEM_SCOPE) {
|
|
1632
1889
|
throw new SystemError("invalid-state", `${path} is not a Rigscope system installation state`);
|
|
1633
1890
|
}
|
|
1634
1891
|
const savedPaths = raw.paths;
|
|
@@ -1640,24 +1897,40 @@ function readSystemState(paths) {
|
|
|
1640
1897
|
if (raw.role !== "local" && raw.role !== "hub" && raw.role !== "collector") {
|
|
1641
1898
|
throw new SystemError("invalid-state", "system installation state has an invalid role");
|
|
1642
1899
|
}
|
|
1643
|
-
|
|
1644
|
-
if (
|
|
1645
|
-
|
|
1646
|
-
components = { hub: { enabled: mapped.hub }, collector: { enabled: mapped.collector } };
|
|
1647
|
-
} else {
|
|
1648
|
-
const saved = raw.components;
|
|
1649
|
-
if (saved === null || typeof saved !== "object" || saved.hub === null || saved.collector === null || typeof saved.hub !== "object" || typeof saved.collector !== "object" || typeof saved.hub.enabled !== "boolean" || typeof saved.collector.enabled !== "boolean") {
|
|
1650
|
-
throw new SystemError("invalid-state", "system installation state has invalid component enabled state");
|
|
1651
|
-
}
|
|
1652
|
-
components = { hub: { enabled: saved.hub.enabled }, collector: { enabled: saved.collector.enabled } };
|
|
1900
|
+
const saved = raw.components;
|
|
1901
|
+
if (!isCanonicalComponents(saved)) {
|
|
1902
|
+
throw new SystemError("invalid-state", "system installation state has invalid component enabled state");
|
|
1653
1903
|
}
|
|
1904
|
+
if (Object.prototype.hasOwnProperty.call(raw, "formatVersion")) {
|
|
1905
|
+
throw new SystemError("invalid-state", "system installation state has an obsolete format version; reinstall or upgrade to reset it");
|
|
1906
|
+
}
|
|
1907
|
+
const components = { hub: { enabled: saved.hub.enabled }, collector: { enabled: saved.collector.enabled } };
|
|
1654
1908
|
return {
|
|
1655
1909
|
...raw,
|
|
1656
|
-
formatVersion: SYSTEM_STATE_FORMAT_VERSION,
|
|
1657
1910
|
accounts,
|
|
1658
1911
|
components
|
|
1659
1912
|
};
|
|
1660
1913
|
}
|
|
1914
|
+
function systemStateIsLegacy(paths) {
|
|
1915
|
+
const path = stateFilePath(paths);
|
|
1916
|
+
if (!(0, import_node_fs3.existsSync)(path)) return false;
|
|
1917
|
+
let raw;
|
|
1918
|
+
try {
|
|
1919
|
+
raw = readJsonObject(path);
|
|
1920
|
+
} catch {
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
if (Object.prototype.hasOwnProperty.call(raw, "formatVersion")) return true;
|
|
1924
|
+
return !isCanonicalComponents(raw.components);
|
|
1925
|
+
}
|
|
1926
|
+
function legacySystemRole(paths) {
|
|
1927
|
+
try {
|
|
1928
|
+
const raw = readJsonObject(stateFilePath(paths));
|
|
1929
|
+
if (raw.role === "local" || raw.role === "hub" || raw.role === "collector") return raw.role;
|
|
1930
|
+
} catch {
|
|
1931
|
+
}
|
|
1932
|
+
return "local";
|
|
1933
|
+
}
|
|
1661
1934
|
function writeSystemState(paths, state) {
|
|
1662
1935
|
ensureDirectory(paths.stateDir, 493, false);
|
|
1663
1936
|
atomicWrite(stateFilePath(paths), `${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
@@ -1667,7 +1940,6 @@ function emptyState(options) {
|
|
|
1667
1940
|
const timestamp = new Date(options.now()).toISOString();
|
|
1668
1941
|
const components = options.components ?? componentsForRole(options.role);
|
|
1669
1942
|
return {
|
|
1670
|
-
formatVersion: SYSTEM_STATE_FORMAT_VERSION,
|
|
1671
1943
|
scope: SYSTEM_SCOPE,
|
|
1672
1944
|
role: options.role,
|
|
1673
1945
|
active: true,
|
|
@@ -1692,17 +1964,21 @@ function validateVersion(value) {
|
|
|
1692
1964
|
return value;
|
|
1693
1965
|
}
|
|
1694
1966
|
function releaseVersion(root) {
|
|
1695
|
-
|
|
1967
|
+
const metadata = readJsonObject((0, import_node_path4.join)(root, "package.json"));
|
|
1968
|
+
if (Object.hasOwn(metadata, "rigscopeConfigVersion")) {
|
|
1969
|
+
throw new SystemError("invalid-release", "release uses unfinished configuration metadata; select a canonical release");
|
|
1970
|
+
}
|
|
1971
|
+
return validateVersion(metadata.version);
|
|
1696
1972
|
}
|
|
1697
1973
|
function releaseSchemaVersion(root) {
|
|
1698
|
-
const manifestPath = (0,
|
|
1974
|
+
const manifestPath = (0, import_node_path4.join)(root, "manifest.json");
|
|
1699
1975
|
if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
|
|
1700
1976
|
const manifest = readJsonObject(manifestPath);
|
|
1701
1977
|
return Number.isSafeInteger(manifest.schemaVersion) ? Number(manifest.schemaVersion) : null;
|
|
1702
1978
|
}
|
|
1703
1979
|
function assertReleaseArtifacts(root) {
|
|
1704
1980
|
for (const artifact of REQUIRED_ARTIFACTS) {
|
|
1705
|
-
const path = (0,
|
|
1981
|
+
const path = (0, import_node_path4.join)(root, artifact);
|
|
1706
1982
|
let stat;
|
|
1707
1983
|
try {
|
|
1708
1984
|
stat = (0, import_node_fs3.lstatSync)(path);
|
|
@@ -1713,12 +1989,12 @@ function assertReleaseArtifacts(root) {
|
|
|
1713
1989
|
throw new SystemError("invalid-artifact", `release artifact is not a non-empty regular file: ${artifact}`);
|
|
1714
1990
|
}
|
|
1715
1991
|
}
|
|
1716
|
-
const buildId = (0, import_node_fs3.readFileSync)((0,
|
|
1992
|
+
const buildId = (0, import_node_fs3.readFileSync)((0, import_node_path4.join)(root, ".next", "BUILD_ID"), "utf8").trim();
|
|
1717
1993
|
if (buildId === "" || /\s/.test(buildId)) throw new SystemError("invalid-artifact", ".next/BUILD_ID is invalid");
|
|
1718
1994
|
for (const directory of [".next/server", ".next/static"]) {
|
|
1719
1995
|
let stat;
|
|
1720
1996
|
try {
|
|
1721
|
-
stat = (0, import_node_fs3.lstatSync)((0,
|
|
1997
|
+
stat = (0, import_node_fs3.lstatSync)((0, import_node_path4.join)(root, directory));
|
|
1722
1998
|
} catch {
|
|
1723
1999
|
throw new SystemError("missing-artifact", `release is missing ${directory}/`);
|
|
1724
2000
|
}
|
|
@@ -1726,10 +2002,10 @@ function assertReleaseArtifacts(root) {
|
|
|
1726
2002
|
}
|
|
1727
2003
|
}
|
|
1728
2004
|
function verifyReleaseManifest(root) {
|
|
1729
|
-
const manifestPath = (0,
|
|
2005
|
+
const manifestPath = (0, import_node_path4.join)(root, "manifest.json");
|
|
1730
2006
|
if (!(0, import_node_fs3.existsSync)(manifestPath)) return;
|
|
1731
2007
|
const manifest = readJsonObject(manifestPath);
|
|
1732
|
-
const packageJson = readJsonObject((0,
|
|
2008
|
+
const packageJson = readJsonObject((0, import_node_path4.join)(root, "package.json"));
|
|
1733
2009
|
if (manifest.name !== void 0 && manifest.name !== packageJson.name) {
|
|
1734
2010
|
throw new SystemError("manifest-mismatch", "release manifest name does not match package.json");
|
|
1735
2011
|
}
|
|
@@ -1748,7 +2024,7 @@ function verifyReleaseManifest(root) {
|
|
|
1748
2024
|
if (typeof relativePath !== "string" || relativePath === "" || relativePath.startsWith("/") || relativePath.includes("\\") || relativePath.split("/").includes("..")) {
|
|
1749
2025
|
throw new SystemError("invalid-manifest", `release manifest contains an unsafe path: ${String(relativePath)}`);
|
|
1750
2026
|
}
|
|
1751
|
-
const absolute = (0,
|
|
2027
|
+
const absolute = (0, import_node_path4.join)(root, relativePath);
|
|
1752
2028
|
const stat = (0, import_node_fs3.lstatSync)(absolute);
|
|
1753
2029
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("invalid-manifest", `release artifact is not a regular file: ${relativePath}`);
|
|
1754
2030
|
if (entry.bytes !== stat.size) throw new SystemError("artifact-mismatch", `release artifact size changed: ${relativePath}`);
|
|
@@ -1757,8 +2033,8 @@ function verifyReleaseManifest(root) {
|
|
|
1757
2033
|
}
|
|
1758
2034
|
const visit = (current) => {
|
|
1759
2035
|
for (const entry of (0, import_node_fs3.readdirSync)(current, { withFileTypes: true })) {
|
|
1760
|
-
const absolute = (0,
|
|
1761
|
-
const rel = (0,
|
|
2036
|
+
const absolute = (0, import_node_path4.join)(current, entry.name);
|
|
2037
|
+
const rel = (0, import_node_path4.relative)(root, absolute).split(import_node_path4.sep).join("/");
|
|
1762
2038
|
if (rel === "node_modules" || rel.startsWith("node_modules/") || rel === ".next/cache" || rel.startsWith(".next/cache/")) continue;
|
|
1763
2039
|
if (entry.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${rel}`);
|
|
1764
2040
|
if (entry.isDirectory()) {
|
|
@@ -1774,30 +2050,30 @@ function verifyReleaseManifest(root) {
|
|
|
1774
2050
|
function assertNoSymlinks(root) {
|
|
1775
2051
|
const visit = (path) => {
|
|
1776
2052
|
const stat = (0, import_node_fs3.lstatSync)(path);
|
|
1777
|
-
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${(0,
|
|
2053
|
+
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${(0, import_node_path4.relative)(root, path)}`);
|
|
1778
2054
|
if (!stat.isDirectory()) return;
|
|
1779
|
-
for (const entry of (0, import_node_fs3.readdirSync)(path)) visit((0,
|
|
2055
|
+
for (const entry of (0, import_node_fs3.readdirSync)(path)) visit((0, import_node_path4.join)(path, entry));
|
|
1780
2056
|
};
|
|
1781
2057
|
visit(root);
|
|
1782
2058
|
}
|
|
1783
2059
|
function copyReleaseTree(source, destination) {
|
|
1784
2060
|
const visit = (current, target) => {
|
|
1785
2061
|
const stat = (0, import_node_fs3.lstatSync)(current);
|
|
1786
|
-
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${(0,
|
|
2062
|
+
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${(0, import_node_path4.relative)(source, current)}`);
|
|
1787
2063
|
if (stat.isDirectory()) {
|
|
1788
2064
|
(0, import_node_fs3.mkdirSync)(target, { recursive: true, mode: 493 });
|
|
1789
2065
|
for (const entry of (0, import_node_fs3.readdirSync)(current)) {
|
|
1790
|
-
const child = (0,
|
|
1791
|
-
const rel = (0,
|
|
2066
|
+
const child = (0, import_node_path4.join)(current, entry);
|
|
2067
|
+
const rel = (0, import_node_path4.relative)(source, child);
|
|
1792
2068
|
if (rel === ".git" || rel === ".next-dev" || rel === ".next/cache" || rel === "node_modules" || rel.startsWith("node_modules/")) {
|
|
1793
2069
|
continue;
|
|
1794
2070
|
}
|
|
1795
|
-
visit(child, (0,
|
|
2071
|
+
visit(child, (0, import_node_path4.join)(target, entry));
|
|
1796
2072
|
}
|
|
1797
2073
|
return;
|
|
1798
2074
|
}
|
|
1799
|
-
if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0,
|
|
1800
|
-
(0, import_node_fs3.mkdirSync)((0,
|
|
2075
|
+
if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0, import_node_path4.relative)(source, current)}`);
|
|
2076
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(target), { recursive: true, mode: 493 });
|
|
1801
2077
|
(0, import_node_fs3.copyFileSync)(current, target);
|
|
1802
2078
|
(0, import_node_fs3.chmodSync)(target, 420);
|
|
1803
2079
|
};
|
|
@@ -1807,8 +2083,8 @@ function copyReleaseAllowlisted(source, destination) {
|
|
|
1807
2083
|
for (const entry of (0, import_node_fs3.readdirSync)(source)) {
|
|
1808
2084
|
if (!PACKAGE_TOP_LEVEL.has(entry)) continue;
|
|
1809
2085
|
if (entry === "node_modules") continue;
|
|
1810
|
-
const child = (0,
|
|
1811
|
-
const target = (0,
|
|
2086
|
+
const child = (0, import_node_path4.join)(source, entry);
|
|
2087
|
+
const target = (0, import_node_path4.join)(destination, entry);
|
|
1812
2088
|
const stat = (0, import_node_fs3.lstatSync)(child);
|
|
1813
2089
|
if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${entry}`);
|
|
1814
2090
|
if (stat.isDirectory()) {
|
|
@@ -1848,59 +2124,102 @@ function extractArchive(archive, stage, host) {
|
|
|
1848
2124
|
runRequired(host, "tar", args, "archive extraction");
|
|
1849
2125
|
assertNoSymlinks(stage);
|
|
1850
2126
|
}
|
|
2127
|
+
function discardStagedRelease(staged) {
|
|
2128
|
+
if (staged === null) return;
|
|
2129
|
+
(0, import_node_fs3.rmSync)(staged.stageDir, { recursive: true, force: true });
|
|
2130
|
+
}
|
|
2131
|
+
function releaseTargetDir(paths, version) {
|
|
2132
|
+
return (0, import_node_path4.join)(paths.prefix, "releases", version);
|
|
2133
|
+
}
|
|
2134
|
+
function assertReleaseTargetAvailable(paths, releaseDir, replaceRelease) {
|
|
2135
|
+
if (!(0, import_node_fs3.existsSync)(releaseDir)) return;
|
|
2136
|
+
const current = currentReleaseDir(paths);
|
|
2137
|
+
const previous = previousReleaseDir(paths);
|
|
2138
|
+
if (!replaceRelease || current === releaseDir || previous === releaseDir) {
|
|
2139
|
+
throw new SystemError(
|
|
2140
|
+
"release-exists",
|
|
2141
|
+
`release ${(0, import_node_path4.basename)(releaseDir)} is already installed${current === releaseDir || previous === releaseDir ? " or retained" : ""}; choose a new version`
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
1851
2145
|
function stageRelease(options) {
|
|
1852
|
-
const source = (0,
|
|
2146
|
+
const source = (0, import_node_fs3.realpathSync)((0, import_node_path4.resolve)(options.source));
|
|
1853
2147
|
const stat = (0, import_node_fs3.lstatSync)(source);
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
(0, import_node_fs3.mkdirSync)(stage, { recursive: true, mode: 448 });
|
|
2148
|
+
ensureDirectory(options.paths.prefix, 493);
|
|
2149
|
+
const stageDir = (0, import_node_path4.join)(options.paths.prefix, `.stage-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
2150
|
+
(0, import_node_fs3.mkdirSync)(stageDir, { recursive: true, mode: 448 });
|
|
1858
2151
|
try {
|
|
1859
2152
|
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
1860
|
-
copyReleaseAllowlisted(source,
|
|
2153
|
+
copyReleaseAllowlisted(source, stageDir);
|
|
1861
2154
|
} else if (stat.isFile() && !stat.isSymbolicLink()) {
|
|
1862
|
-
extractArchive(source,
|
|
2155
|
+
extractArchive(source, stageDir, options.host);
|
|
1863
2156
|
} else {
|
|
1864
2157
|
throw new SystemError("invalid-source", "release source must be a directory or .tar.gz archive");
|
|
1865
2158
|
}
|
|
1866
|
-
assertReleaseArtifacts(
|
|
1867
|
-
verifyReleaseManifest(
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2159
|
+
assertReleaseArtifacts(stageDir);
|
|
2160
|
+
verifyReleaseManifest(stageDir);
|
|
2161
|
+
return {
|
|
2162
|
+
version: releaseVersion(stageDir),
|
|
2163
|
+
stageDir,
|
|
2164
|
+
schemaVersion: releaseSchemaVersion(stageDir),
|
|
2165
|
+
artifacts: [...REQUIRED_ARTIFACTS]
|
|
2166
|
+
};
|
|
2167
|
+
} catch (error) {
|
|
2168
|
+
(0, import_node_fs3.rmSync)(stageDir, { recursive: true, force: true });
|
|
2169
|
+
throw error;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
function prepareStagedRelease(options) {
|
|
2173
|
+
const staged = stageRelease(options);
|
|
2174
|
+
try {
|
|
2175
|
+
if (options.installDeps === true) {
|
|
2176
|
+
installDependencies(staged.stageDir, options.host, options.installDepsCommand ?? "npm");
|
|
1880
2177
|
}
|
|
1881
|
-
(
|
|
1882
|
-
|
|
1883
|
-
|
|
2178
|
+
const missing = missingDependencies(staged.stageDir);
|
|
2179
|
+
if (missing.length > 0) {
|
|
2180
|
+
throw new SystemError(
|
|
2181
|
+
"missing-dependencies",
|
|
2182
|
+
`production dependencies are missing (${missing.join(", ")}); stage with --install-deps or use a built release directory`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
chmodTree(staged.stageDir, options.host);
|
|
2186
|
+
runRequired(options.host, "chown", ["-R", "root:root", staged.stageDir], "release ownership");
|
|
2187
|
+
return staged;
|
|
1884
2188
|
} catch (error) {
|
|
1885
|
-
(
|
|
2189
|
+
discardStagedRelease(staged);
|
|
1886
2190
|
throw error;
|
|
1887
2191
|
}
|
|
1888
2192
|
}
|
|
2193
|
+
function commitStagedRelease(options) {
|
|
2194
|
+
const releasesDir = (0, import_node_path4.join)(options.paths.prefix, "releases");
|
|
2195
|
+
ensureDirectory(releasesDir, 493);
|
|
2196
|
+
const releaseDir = releaseTargetDir(options.paths, options.staged.version);
|
|
2197
|
+
assertReleaseTargetAvailable(options.paths, releaseDir, options.replaceRelease);
|
|
2198
|
+
if ((0, import_node_fs3.existsSync)(releaseDir)) (0, import_node_fs3.rmSync)(releaseDir, { recursive: true, force: true });
|
|
2199
|
+
(0, import_node_fs3.renameSync)(options.staged.stageDir, releaseDir);
|
|
2200
|
+
ensureDirectory((0, import_node_path4.join)(releaseDir, ".next", "cache"), 493);
|
|
2201
|
+
return {
|
|
2202
|
+
version: options.staged.version,
|
|
2203
|
+
releaseDir,
|
|
2204
|
+
schemaVersion: options.staged.schemaVersion,
|
|
2205
|
+
artifacts: options.staged.artifacts
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
1889
2208
|
function dependencyNames(root) {
|
|
1890
|
-
const pkg = readJsonObject((0,
|
|
2209
|
+
const pkg = readJsonObject((0, import_node_path4.join)(root, "package.json"));
|
|
1891
2210
|
const dependencies = pkg.dependencies;
|
|
1892
2211
|
if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) return [];
|
|
1893
2212
|
return Object.keys(dependencies).filter((name) => /^[A-Za-z0-9@/._-]+$/.test(name));
|
|
1894
2213
|
}
|
|
1895
2214
|
function missingDependencies(root) {
|
|
1896
|
-
return dependencyNames(root).filter((name) => !(0, import_node_fs3.existsSync)((0,
|
|
2215
|
+
return dependencyNames(root).filter((name) => !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(root, "node_modules", name, "package.json")));
|
|
1897
2216
|
}
|
|
1898
2217
|
function installDependencies(root, host, command = "npm") {
|
|
1899
|
-
const hasLock = (0, import_node_fs3.existsSync)((0,
|
|
2218
|
+
const hasLock = (0, import_node_fs3.existsSync)((0, import_node_path4.join)(root, "package-lock.json"));
|
|
1900
2219
|
const args = hasLock ? ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
1901
|
-
const siblingNpm = (0,
|
|
2220
|
+
const siblingNpm = (0, import_node_path4.join)((0, import_node_path4.dirname)(process.execPath), "npm");
|
|
1902
2221
|
const executable = command === "npm" && (0, import_node_fs3.existsSync)(siblingNpm) ? siblingNpm : command;
|
|
1903
|
-
const nodeBin = (0,
|
|
2222
|
+
const nodeBin = (0, import_node_path4.dirname)(process.execPath);
|
|
1904
2223
|
const path = [nodeBin, process.env.PATH ?? ""].filter(Boolean).join(":");
|
|
1905
2224
|
runRequired(host, executable, args, "production dependency installation", {
|
|
1906
2225
|
cwd: root,
|
|
@@ -1909,21 +2228,21 @@ function installDependencies(root, host, command = "npm") {
|
|
|
1909
2228
|
});
|
|
1910
2229
|
}
|
|
1911
2230
|
function currentReleaseDir(paths) {
|
|
1912
|
-
const link = (0,
|
|
2231
|
+
const link = (0, import_node_path4.join)(paths.prefix, "current");
|
|
1913
2232
|
try {
|
|
1914
2233
|
const target = (0, import_node_fs3.readlinkSync)(link);
|
|
1915
|
-
const absolute = (0,
|
|
1916
|
-
return isWithin((0,
|
|
2234
|
+
const absolute = (0, import_node_path4.isAbsolute)(target) ? target : (0, import_node_path4.resolve)(paths.prefix, target);
|
|
2235
|
+
return isWithin((0, import_node_path4.join)(paths.prefix, "releases"), absolute) ? absolute : null;
|
|
1917
2236
|
} catch {
|
|
1918
2237
|
return null;
|
|
1919
2238
|
}
|
|
1920
2239
|
}
|
|
1921
2240
|
function previousReleaseDir(paths) {
|
|
1922
|
-
const link = (0,
|
|
2241
|
+
const link = (0, import_node_path4.join)(paths.prefix, "previous");
|
|
1923
2242
|
try {
|
|
1924
2243
|
const target = (0, import_node_fs3.readlinkSync)(link);
|
|
1925
|
-
const absolute = (0,
|
|
1926
|
-
return isWithin((0,
|
|
2244
|
+
const absolute = (0, import_node_path4.isAbsolute)(target) ? target : (0, import_node_path4.resolve)(paths.prefix, target);
|
|
2245
|
+
return isWithin((0, import_node_path4.join)(paths.prefix, "releases"), absolute) ? absolute : null;
|
|
1927
2246
|
} catch {
|
|
1928
2247
|
return null;
|
|
1929
2248
|
}
|
|
@@ -1958,15 +2277,16 @@ function nodeVersion(host, nodePath) {
|
|
|
1958
2277
|
return output;
|
|
1959
2278
|
}
|
|
1960
2279
|
function ensureRootOwnedNode(options) {
|
|
1961
|
-
const selected = (0,
|
|
2280
|
+
const selected = (0, import_node_path4.resolve)(options.nodePath ?? process.execPath);
|
|
1962
2281
|
const real = (0, import_node_fs3.realpathSync)(selected);
|
|
1963
2282
|
nodeVersion(options.host, real);
|
|
1964
2283
|
const stat = (0, import_node_fs3.statSync)(real);
|
|
1965
|
-
const
|
|
2284
|
+
const insideRelease = isWithin((0, import_node_path4.join)(options.paths.prefix, "releases"), real);
|
|
2285
|
+
const unsafe = stat.uid !== 0 || (stat.mode & 18) !== 0 || options.copyNode === true || insideRelease;
|
|
1966
2286
|
if (!unsafe || options.trustedNode === true) return real;
|
|
1967
|
-
const target = (0,
|
|
2287
|
+
const target = (0, import_node_path4.join)(options.paths.prefix, "node", "node");
|
|
1968
2288
|
if (!options.apply) return target;
|
|
1969
|
-
ensureDirectory((0,
|
|
2289
|
+
ensureDirectory((0, import_node_path4.join)(options.paths.prefix, "node"), 493);
|
|
1970
2290
|
(0, import_node_fs3.copyFileSync)(real, target);
|
|
1971
2291
|
(0, import_node_fs3.chmodSync)(target, 493);
|
|
1972
2292
|
runRequired(options.host, "chown", ["root:root", target], "node ownership");
|
|
@@ -1989,60 +2309,44 @@ function normalizedJsonArray(value, label) {
|
|
|
1989
2309
|
function validatedOrigin(value) {
|
|
1990
2310
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
1991
2311
|
if (typeof value !== "string") throw new SystemError("invalid-adoption", "adopted hub origin is not a URL");
|
|
1992
|
-
let parsed;
|
|
1993
2312
|
try {
|
|
1994
|
-
|
|
2313
|
+
parsePublicOrigin(value);
|
|
1995
2314
|
} catch {
|
|
1996
|
-
throw new SystemError("invalid-adoption", "adopted hub origin
|
|
2315
|
+
throw new SystemError("invalid-adoption", "adopted hub origin must be an exact HTTPS origin");
|
|
1997
2316
|
}
|
|
1998
|
-
if (parsed.protocol !== "https:") throw new SystemError("invalid-adoption", "adopted hub origin must be https");
|
|
1999
2317
|
return value;
|
|
2000
2318
|
}
|
|
2001
|
-
function hasCollectorIdentity(source) {
|
|
2002
|
-
return (0, import_node_fs3.existsSync)((0, import_node_path3.join)(source, "collector", "state.json")) || (0, import_node_fs3.existsSync)((0, import_node_path3.join)(source, "collector", "collector.json"));
|
|
2003
|
-
}
|
|
2004
2319
|
function readAdoptionSetup(source) {
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
assertPrivateSourceFile(modernPath, "adopted setup");
|
|
2009
|
-
const setup = readJsonObject(modernPath);
|
|
2010
|
-
const role = setup.role;
|
|
2011
|
-
if (setup.version !== 1 || typeof role !== "string" || !["local", "hub", "collector"].includes(role)) {
|
|
2012
|
-
throw new SystemError("invalid-adoption", "adopted setup version or role is incompatible");
|
|
2013
|
-
}
|
|
2014
|
-
const port = Number(setup.port ?? 8081);
|
|
2015
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
|
|
2016
|
-
return {
|
|
2017
|
-
role,
|
|
2018
|
-
port,
|
|
2019
|
-
origin: validatedOrigin(setup.origin),
|
|
2020
|
-
usersJson: normalizedJsonArray(setup.users, "HUB_BROWSER_USERS"),
|
|
2021
|
-
collectorsJson: normalizedJsonArray(setup.collectors, "HUB_COLLECTORS"),
|
|
2022
|
-
maxConnections: Number.isSafeInteger(setup.maxCollectorConnections) ? Number(setup.maxCollectorConnections) : void 0,
|
|
2023
|
-
legacy: false
|
|
2024
|
-
};
|
|
2320
|
+
const configPath = (0, import_node_path4.join)(source, "config.json");
|
|
2321
|
+
if (!(0, import_node_fs3.existsSync)(configPath)) {
|
|
2322
|
+
throw new SystemError("invalid-adoption", "adopted installation has no config.json");
|
|
2025
2323
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
port: Number(legacy.HUB_PORT ?? 8081),
|
|
2035
|
-
origin: validatedOrigin(legacy.HUB_PUBLIC_ORIGIN),
|
|
2036
|
-
usersJson: normalizedJsonArray(legacy.HUB_BROWSER_USERS, "HUB_BROWSER_USERS"),
|
|
2037
|
-
collectorsJson: normalizedJsonArray(legacy.HUB_COLLECTORS, "HUB_COLLECTORS"),
|
|
2038
|
-
maxConnections: Number.isSafeInteger(Number(legacy.HUB_MAX_COLLECTOR_CONNECTIONS)) ? Number(legacy.HUB_MAX_COLLECTOR_CONNECTIONS) : void 0,
|
|
2039
|
-
legacy: true
|
|
2040
|
-
};
|
|
2324
|
+
assertPrivateSourceFile(configPath, "adopted setup");
|
|
2325
|
+
const setup = readJsonObject(configPath);
|
|
2326
|
+
if (Object.prototype.hasOwnProperty.call(setup, "version") || Object.prototype.hasOwnProperty.call(setup, "formatVersion")) {
|
|
2327
|
+
throw new SystemError("invalid-adoption", "adopted setup is pre-canonical; reset or reinstall it before adoption");
|
|
2328
|
+
}
|
|
2329
|
+
const role = setup.role;
|
|
2330
|
+
if (typeof role !== "string" || !["local", "hub", "collector"].includes(role)) {
|
|
2331
|
+
throw new SystemError("invalid-adoption", "adopted setup role is incompatible");
|
|
2041
2332
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2333
|
+
const port = Number(setup.port);
|
|
2334
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
|
|
2335
|
+
if (!Array.isArray(setup.users) || !Array.isArray(setup.collectors)) {
|
|
2336
|
+
throw new SystemError("invalid-adoption", "adopted setup must contain users and collectors arrays");
|
|
2337
|
+
}
|
|
2338
|
+
if (!isCanonicalComponents(setup.components)) {
|
|
2339
|
+
throw new SystemError("invalid-adoption", "adopted setup has an invalid component enabled state");
|
|
2044
2340
|
}
|
|
2045
|
-
|
|
2341
|
+
return {
|
|
2342
|
+
role,
|
|
2343
|
+
components: componentSet({ hub: setup.components.hub.enabled, collector: setup.components.collector.enabled }),
|
|
2344
|
+
port,
|
|
2345
|
+
origin: validatedOrigin(setup.origin),
|
|
2346
|
+
usersJson: normalizedJsonArray(setup.users, "HUB_BROWSER_USERS"),
|
|
2347
|
+
collectorsJson: normalizedJsonArray(setup.collectors, "HUB_COLLECTORS"),
|
|
2348
|
+
maxConnections: Number.isSafeInteger(setup.maxCollectorConnections) ? Number(setup.maxCollectorConnections) : void 0
|
|
2349
|
+
};
|
|
2046
2350
|
}
|
|
2047
2351
|
function resolveRole(options) {
|
|
2048
2352
|
const role = options.role ?? "local";
|
|
@@ -2063,7 +2367,7 @@ function detectUserInstallations(options = {}) {
|
|
|
2063
2367
|
const found = [];
|
|
2064
2368
|
for (const homeRoot of roots) {
|
|
2065
2369
|
if (!(0, import_node_fs3.existsSync)(homeRoot)) continue;
|
|
2066
|
-
const homes = homeRoot === "/root" ? ["/root"] : (0, import_node_fs3.readdirSync)(homeRoot).map((entry) => (0,
|
|
2370
|
+
const homes = homeRoot === "/root" ? ["/root"] : (0, import_node_fs3.readdirSync)(homeRoot).map((entry) => (0, import_node_path4.join)(homeRoot, entry));
|
|
2067
2371
|
for (const home of homes) {
|
|
2068
2372
|
let stat;
|
|
2069
2373
|
try {
|
|
@@ -2072,7 +2376,7 @@ function detectUserInstallations(options = {}) {
|
|
|
2072
2376
|
continue;
|
|
2073
2377
|
}
|
|
2074
2378
|
if (!stat.isDirectory()) continue;
|
|
2075
|
-
const unitDir = (0,
|
|
2379
|
+
const unitDir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
|
|
2076
2380
|
let units = [];
|
|
2077
2381
|
try {
|
|
2078
2382
|
units = (0, import_node_fs3.readdirSync)(unitDir);
|
|
@@ -2081,16 +2385,16 @@ function detectUserInstallations(options = {}) {
|
|
|
2081
2385
|
}
|
|
2082
2386
|
for (const unitName of units) {
|
|
2083
2387
|
if (!/^rigscope(?:-[0-9a-f]{12})?(?:-(?:hub|collector))?\.service$/.test(unitName)) continue;
|
|
2084
|
-
const unitPath = (0,
|
|
2388
|
+
const unitPath = (0, import_node_path4.join)(unitDir, unitName);
|
|
2085
2389
|
let execStart = null;
|
|
2086
2390
|
try {
|
|
2087
2391
|
const line = (0, import_node_fs3.readFileSync)(unitPath, "utf8").split("\n").find((item) => item.startsWith("ExecStart="));
|
|
2088
2392
|
execStart = line ? line.slice("ExecStart=".length) : null;
|
|
2089
2393
|
} catch {
|
|
2090
2394
|
}
|
|
2091
|
-
const stateCandidates = [(0,
|
|
2395
|
+
const stateCandidates = [(0, import_node_path4.join)(home, ".local", "state", "rigscope")];
|
|
2092
2396
|
const stateDir = stateCandidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate)) ?? null;
|
|
2093
|
-
found.push({ user: (0,
|
|
2397
|
+
found.push({ user: (0, import_node_path4.basename)(home), home, unitPath, unitName, execStart, stateDir });
|
|
2094
2398
|
}
|
|
2095
2399
|
}
|
|
2096
2400
|
}
|
|
@@ -2118,9 +2422,9 @@ function lockIsLive(path) {
|
|
|
2118
2422
|
}
|
|
2119
2423
|
function directoryLocks(directory) {
|
|
2120
2424
|
return [
|
|
2121
|
-
(0,
|
|
2122
|
-
(0,
|
|
2123
|
-
(0,
|
|
2425
|
+
(0, import_node_path4.join)(directory, "hub", "telemetry.sqlite.hub.lock"),
|
|
2426
|
+
(0, import_node_path4.join)(directory, "run", "rigscope.lock"),
|
|
2427
|
+
(0, import_node_path4.join)(directory, "collector", "collector-runtime.lock")
|
|
2124
2428
|
];
|
|
2125
2429
|
}
|
|
2126
2430
|
function assertDirectoryStopped(directory, scope = "local") {
|
|
@@ -2151,7 +2455,7 @@ function assertSystemStartAllowed(paths, components) {
|
|
|
2151
2455
|
}
|
|
2152
2456
|
function detectPowerDriver(moduleRoot = "/sys/module") {
|
|
2153
2457
|
const candidates = ["intel_rapl_msr", "intel_rapl_common", "intel_rapl_tpmi", "intel_rapl", "amd_energy"];
|
|
2154
|
-
return candidates.filter((name) => (0, import_node_fs3.existsSync)((0,
|
|
2458
|
+
return candidates.filter((name) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(moduleRoot, name)));
|
|
2155
2459
|
}
|
|
2156
2460
|
function readPasswd(path = "/etc/passwd") {
|
|
2157
2461
|
const users = /* @__PURE__ */ new Map();
|
|
@@ -2224,15 +2528,15 @@ function inspectPowerAccess(options = {}) {
|
|
|
2224
2528
|
entries = [];
|
|
2225
2529
|
}
|
|
2226
2530
|
for (const entry of entries.filter((name) => /^intel-rapl:\d+$/.test(name)).sort()) {
|
|
2227
|
-
const domainPath = (0,
|
|
2531
|
+
const domainPath = (0, import_node_path4.join)(paths.powercapRoot, entry);
|
|
2228
2532
|
let name = entry;
|
|
2229
2533
|
try {
|
|
2230
|
-
name = (0, import_node_fs3.readFileSync)((0,
|
|
2534
|
+
name = (0, import_node_fs3.readFileSync)((0, import_node_path4.join)(domainPath, "name"), "utf8").trim() || entry;
|
|
2231
2535
|
} catch {
|
|
2232
2536
|
}
|
|
2233
2537
|
if (!/^package/.test(name)) continue;
|
|
2234
|
-
const energyPath = (0,
|
|
2235
|
-
const maxRangePath = (0,
|
|
2538
|
+
const energyPath = (0, import_node_path4.join)(domainPath, "energy_uj");
|
|
2539
|
+
const maxRangePath = (0, import_node_path4.join)(domainPath, "max_energy_range_uj");
|
|
2236
2540
|
const readStat = (path) => {
|
|
2237
2541
|
if (options.statOverride) {
|
|
2238
2542
|
const override = options.statOverride(path);
|
|
@@ -2301,7 +2605,7 @@ function inspectPowerAccess(options = {}) {
|
|
|
2301
2605
|
};
|
|
2302
2606
|
}
|
|
2303
2607
|
async function backupDatabase(source, output, host) {
|
|
2304
|
-
ensureDirectory((0,
|
|
2608
|
+
ensureDirectory((0, import_node_path4.dirname)(output), 448, true);
|
|
2305
2609
|
const db = new import_node_sqlite.DatabaseSync(source, { readOnly: true, timeout: 5e3 });
|
|
2306
2610
|
try {
|
|
2307
2611
|
await (0, import_node_sqlite.backup)(db, output);
|
|
@@ -2338,11 +2642,11 @@ function replaceDatabaseFromBackup(backupPath, target, safetyBackup, host) {
|
|
|
2338
2642
|
} finally {
|
|
2339
2643
|
backup.close();
|
|
2340
2644
|
}
|
|
2341
|
-
ensureDirectory((0,
|
|
2645
|
+
ensureDirectory((0, import_node_path4.dirname)(target), 448, true);
|
|
2342
2646
|
if (safetyBackup !== null && (0, import_node_fs3.existsSync)(target)) {
|
|
2343
2647
|
void host;
|
|
2344
2648
|
}
|
|
2345
|
-
const temporary = (0,
|
|
2649
|
+
const temporary = (0, import_node_path4.join)((0, import_node_path4.dirname)(target), `.restore-${process.pid}-${Date.now()}.sqlite`);
|
|
2346
2650
|
(0, import_node_fs3.copyFileSync)(backupPath, temporary);
|
|
2347
2651
|
(0, import_node_fs3.chmodSync)(temporary, 384);
|
|
2348
2652
|
for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
|
|
@@ -2358,7 +2662,7 @@ function newestTimestamp() {
|
|
|
2358
2662
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2359
2663
|
}
|
|
2360
2664
|
function adoptUserState(options) {
|
|
2361
|
-
const source = (0,
|
|
2665
|
+
const source = (0, import_node_path4.resolve)(options.source);
|
|
2362
2666
|
if (!(0, import_node_fs3.existsSync)(source)) throw new SystemError("invalid-adoption", `user installation does not exist: ${source}`);
|
|
2363
2667
|
if (isWithin(options.paths.prefix, source) || isWithin(options.paths.stateDir, source) || isWithin(source, options.paths.stateDir)) {
|
|
2364
2668
|
throw new SystemError("invalid-adoption", "adoption source and system paths must not overlap");
|
|
@@ -2368,6 +2672,28 @@ function adoptUserState(options) {
|
|
|
2368
2672
|
if (setup.role !== options.role && setup.role !== "local") throw new SystemError("invalid-adoption", "adopted setup role is incompatible");
|
|
2369
2673
|
const { port, origin } = setup;
|
|
2370
2674
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
|
|
2675
|
+
if (options.role !== "collector") {
|
|
2676
|
+
const sourceDatabase = (0, import_node_path4.join)(source, "hub", "telemetry.sqlite");
|
|
2677
|
+
if ((0, import_node_fs3.existsSync)(sourceDatabase)) {
|
|
2678
|
+
assertPrivateSourceFile(sourceDatabase, "adopted database");
|
|
2679
|
+
const database = new import_node_sqlite.DatabaseSync(sourceDatabase, { readOnly: true });
|
|
2680
|
+
try {
|
|
2681
|
+
const row = database.prepare("PRAGMA integrity_check").get();
|
|
2682
|
+
if (!row || Object.values(row)[0] !== "ok") throw new SystemError("invalid-adoption", "adopted database failed its integrity check");
|
|
2683
|
+
} finally {
|
|
2684
|
+
database.close();
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const sourceEnrollment = (0, import_node_path4.join)(source, "hub", "enrollment.json");
|
|
2688
|
+
if ((0, import_node_fs3.existsSync)(sourceEnrollment)) assertPrivateSourceFile(sourceEnrollment, "adopted enrollment");
|
|
2689
|
+
}
|
|
2690
|
+
if (options.role !== "hub") {
|
|
2691
|
+
const sourceCollectorState = (0, import_node_path4.join)(source, "collector", "state.json");
|
|
2692
|
+
if ((0, import_node_fs3.existsSync)(sourceCollectorState)) {
|
|
2693
|
+
assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
|
|
2694
|
+
readCollectorIdentity(sourceCollectorState);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2371
2697
|
const copied = [];
|
|
2372
2698
|
const warnings = [];
|
|
2373
2699
|
const targetDatabase = hubDatabasePath(options.paths);
|
|
@@ -2413,12 +2739,12 @@ function copyAdoptedTree(source, target, copied, skipLocks = true) {
|
|
|
2413
2739
|
if (stat.isSymbolicLink()) return;
|
|
2414
2740
|
if (stat.isDirectory()) {
|
|
2415
2741
|
(0, import_node_fs3.mkdirSync)(destination, { recursive: true, mode: 448 });
|
|
2416
|
-
for (const entry of (0, import_node_fs3.readdirSync)(current)) visit((0,
|
|
2742
|
+
for (const entry of (0, import_node_fs3.readdirSync)(current)) visit((0, import_node_path4.join)(current, entry), (0, import_node_path4.join)(destination, entry));
|
|
2417
2743
|
return;
|
|
2418
2744
|
}
|
|
2419
2745
|
if (!stat.isFile()) return;
|
|
2420
2746
|
if (skipLocks && /\.(lock|sock)$/.test(current)) return;
|
|
2421
|
-
(0, import_node_fs3.mkdirSync)((0,
|
|
2747
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(destination), { recursive: true, mode: 448 });
|
|
2422
2748
|
(0, import_node_fs3.copyFileSync)(current, destination);
|
|
2423
2749
|
(0, import_node_fs3.chmodSync)(destination, 384);
|
|
2424
2750
|
copied.push({ source: current, target: destination, sha256: sha256File(destination), bytes: stat.size });
|
|
@@ -2429,10 +2755,10 @@ async function performAdoptionCopyAsync(options) {
|
|
|
2429
2755
|
const { source, paths, role, host, copy } = options;
|
|
2430
2756
|
assertDirectoryStopped(source, role);
|
|
2431
2757
|
const targetDatabase = hubDatabasePath(paths);
|
|
2432
|
-
const sourceDatabase = (0,
|
|
2758
|
+
const sourceDatabase = (0, import_node_path4.join)(source, "hub", "telemetry.sqlite");
|
|
2433
2759
|
if (role !== "collector" && (0, import_node_fs3.existsSync)(sourceDatabase) && !(0, import_node_fs3.existsSync)(targetDatabase)) {
|
|
2434
2760
|
assertPrivateSourceFile(sourceDatabase, "adopted database");
|
|
2435
|
-
ensureDirectory((0,
|
|
2761
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
|
|
2436
2762
|
await backupDatabase(sourceDatabase, targetDatabase, host);
|
|
2437
2763
|
copy.record.files.push({
|
|
2438
2764
|
source: sourceDatabase,
|
|
@@ -2441,13 +2767,10 @@ async function performAdoptionCopyAsync(options) {
|
|
|
2441
2767
|
bytes: (0, import_node_fs3.statSync)(targetDatabase).size
|
|
2442
2768
|
});
|
|
2443
2769
|
}
|
|
2444
|
-
const sourceCollectorState =
|
|
2445
|
-
|
|
2446
|
-
(0, import_node_path3.join)(source, "collector", "collector.json")
|
|
2447
|
-
].find((candidate) => (0, import_node_fs3.existsSync)(candidate));
|
|
2448
|
-
if (role !== "hub" && sourceCollectorState) {
|
|
2770
|
+
const sourceCollectorState = (0, import_node_path4.join)(source, "collector", "state.json");
|
|
2771
|
+
if (role !== "hub" && (0, import_node_fs3.existsSync)(sourceCollectorState)) {
|
|
2449
2772
|
assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
|
|
2450
|
-
ensureDirectory((0,
|
|
2773
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
|
|
2451
2774
|
const targetCollector = collectorStatePath(paths);
|
|
2452
2775
|
(0, import_node_fs3.copyFileSync)(sourceCollectorState, targetCollector);
|
|
2453
2776
|
(0, import_node_fs3.chmodSync)(targetCollector, 384);
|
|
@@ -2457,10 +2780,10 @@ async function performAdoptionCopyAsync(options) {
|
|
|
2457
2780
|
sha256: sha256File(targetCollector),
|
|
2458
2781
|
bytes: (0, import_node_fs3.statSync)(targetCollector).size
|
|
2459
2782
|
});
|
|
2460
|
-
const outbox = (0,
|
|
2461
|
-
if ((0, import_node_fs3.existsSync)(outbox)) copyAdoptedTree(outbox, (0,
|
|
2783
|
+
const outbox = (0, import_node_path4.join)(source, "collector", "outbox");
|
|
2784
|
+
if ((0, import_node_fs3.existsSync)(outbox)) copyAdoptedTree(outbox, (0, import_node_path4.join)(paths.stateDir, "collector", "outbox"), copy.record.files);
|
|
2462
2785
|
}
|
|
2463
|
-
const enrollment = (0,
|
|
2786
|
+
const enrollment = (0, import_node_path4.join)(source, "hub", "enrollment.json");
|
|
2464
2787
|
if (role !== "collector" && (0, import_node_fs3.existsSync)(enrollment)) {
|
|
2465
2788
|
const target = hubEnrollmentPath(paths);
|
|
2466
2789
|
(0, import_node_fs3.copyFileSync)(enrollment, target);
|
|
@@ -2505,13 +2828,13 @@ function writePowerRule(paths, accounts) {
|
|
|
2505
2828
|
writeManagedFile(raplRulePath(paths), renderRaplRule(accounts), 420, true);
|
|
2506
2829
|
}
|
|
2507
2830
|
function powerOriginalPath(paths) {
|
|
2508
|
-
return (0,
|
|
2831
|
+
return (0, import_node_path4.join)(paths.stateDir, POWER_STATE_FILE_NAME);
|
|
2509
2832
|
}
|
|
2510
2833
|
function capturePowerOriginals(paths) {
|
|
2511
2834
|
const files = {};
|
|
2512
2835
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2513
2836
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2514
|
-
const path = (0,
|
|
2837
|
+
const path = (0, import_node_path4.join)(domain, name);
|
|
2515
2838
|
try {
|
|
2516
2839
|
const stat = (0, import_node_fs3.statSync)(path);
|
|
2517
2840
|
files[path] = { mode: stat.mode & 511, uid: stat.uid, gid: stat.gid };
|
|
@@ -2525,7 +2848,7 @@ function capturePowerOriginals(paths) {
|
|
|
2525
2848
|
function applyPowerAccess(paths, accounts, host) {
|
|
2526
2849
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2527
2850
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2528
|
-
const path = (0,
|
|
2851
|
+
const path = (0, import_node_path4.join)(domain, name);
|
|
2529
2852
|
if (!(0, import_node_fs3.existsSync)(path)) continue;
|
|
2530
2853
|
runRequired(host, "chgrp", [accounts.powerGroup, path], `power counter group ${path}`);
|
|
2531
2854
|
runRequired(host, "chmod", ["0440", path], `power counter mode ${path}`);
|
|
@@ -2539,9 +2862,9 @@ function resumePowerRule(paths, host) {
|
|
|
2539
2862
|
}
|
|
2540
2863
|
function packageDomainPaths(root) {
|
|
2541
2864
|
try {
|
|
2542
|
-
return (0, import_node_fs3.readdirSync)(root).filter((entry) => /^intel-rapl:\d+$/.test(entry)).map((entry) => (0,
|
|
2865
|
+
return (0, import_node_fs3.readdirSync)(root).filter((entry) => /^intel-rapl:\d+$/.test(entry)).map((entry) => (0, import_node_path4.join)(root, entry)).filter((path) => {
|
|
2543
2866
|
try {
|
|
2544
|
-
return /^package/.test((0, import_node_fs3.readFileSync)((0,
|
|
2867
|
+
return /^package/.test((0, import_node_fs3.readFileSync)((0, import_node_path4.join)(path, "name"), "utf8").trim());
|
|
2545
2868
|
} catch {
|
|
2546
2869
|
return false;
|
|
2547
2870
|
}
|
|
@@ -2561,7 +2884,7 @@ function restorePowerOriginals(paths, host) {
|
|
|
2561
2884
|
const files = { ...originals?.files ?? {} };
|
|
2562
2885
|
for (const domain of packageDomainPaths(paths.powercapRoot)) {
|
|
2563
2886
|
for (const name of ["energy_uj", "max_energy_range_uj"]) {
|
|
2564
|
-
const path = (0,
|
|
2887
|
+
const path = (0, import_node_path4.join)(domain, name);
|
|
2565
2888
|
if (!(0, import_node_fs3.existsSync)(path)) continue;
|
|
2566
2889
|
const original = files[path];
|
|
2567
2890
|
if (original) {
|
|
@@ -2601,6 +2924,10 @@ function stopUnits(host, units) {
|
|
|
2601
2924
|
}
|
|
2602
2925
|
if (units.some((unit) => systemdActive(host, unit))) throw new SystemError("service-active", "system services did not stop; no files were restored");
|
|
2603
2926
|
}
|
|
2927
|
+
function unitsForCleanup(paths, host) {
|
|
2928
|
+
const legacy = LEGACY_UNIT_NAMES.filter((unit) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, unit)) || systemdActive(host, unit));
|
|
2929
|
+
return [...Object.values(UNIT_NAMES), ...legacy];
|
|
2930
|
+
}
|
|
2604
2931
|
function startUnits(host, units) {
|
|
2605
2932
|
runRequired(host, "systemctl", ["daemon-reload"], "systemd daemon-reload");
|
|
2606
2933
|
for (const unit of units) runRequired(host, "systemctl", ["start", unit], `starting ${unit}`);
|
|
@@ -2636,7 +2963,7 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
2636
2963
|
const report = runRequired(
|
|
2637
2964
|
host,
|
|
2638
2965
|
state.nodePath,
|
|
2639
|
-
[(0,
|
|
2966
|
+
[(0, import_node_path4.join)(paths.prefix, "current/dist/ops.cjs"), "inspect", "--database", hubDatabasePath(paths), "--json"],
|
|
2640
2967
|
"activated database verification",
|
|
2641
2968
|
{ timeoutMs: Math.max(100, deadline - Date.now()) }
|
|
2642
2969
|
);
|
|
@@ -2652,8 +2979,8 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
2652
2979
|
let problem = "no sampling marker";
|
|
2653
2980
|
while (Date.now() < deadline) {
|
|
2654
2981
|
try {
|
|
2655
|
-
const marker = readJsonObject((0,
|
|
2656
|
-
const lease = readJsonObject((0,
|
|
2982
|
+
const marker = readJsonObject((0, import_node_path4.join)(paths.stateDir, "collector/health.json"));
|
|
2983
|
+
const lease = readJsonObject((0, import_node_path4.join)(paths.stateDir, "collector/collector-runtime.lock"));
|
|
2657
2984
|
if (marker.pid !== lease.pid || !Number.isSafeInteger(marker.pid)) throw new Error("sampling marker belongs to another process");
|
|
2658
2985
|
process.kill(Number(marker.pid), 0);
|
|
2659
2986
|
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");
|
|
@@ -2661,7 +2988,7 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
|
|
|
2661
2988
|
} catch (error) {
|
|
2662
2989
|
problem = error instanceof Error ? error.message : String(error);
|
|
2663
2990
|
}
|
|
2664
|
-
await new Promise((
|
|
2991
|
+
await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
2665
2992
|
}
|
|
2666
2993
|
throw new SystemError("collector-unready", `system collector did not sample: ${problem}`);
|
|
2667
2994
|
}
|
|
@@ -2684,9 +3011,9 @@ function pickRollbackBackup(state, previousSchema) {
|
|
|
2684
3011
|
async function recoverFailedUpgrade(options) {
|
|
2685
3012
|
stopUnits(options.host, options.units);
|
|
2686
3013
|
assertDirectoryStopped(options.paths.stateDir);
|
|
2687
|
-
replaceSymlink((0,
|
|
2688
|
-
if (options.oldPrevious === null) removeSymlink((0,
|
|
2689
|
-
else replaceSymlink((0,
|
|
3014
|
+
replaceSymlink((0, import_node_path4.join)(options.paths.prefix, "current"), options.oldCurrent, options.host);
|
|
3015
|
+
if (options.oldPrevious === null) removeSymlink((0, import_node_path4.join)(options.paths.prefix, "previous"));
|
|
3016
|
+
else replaceSymlink((0, import_node_path4.join)(options.paths.prefix, "previous"), options.oldPrevious, options.host);
|
|
2690
3017
|
const database = hubDatabasePath(options.paths);
|
|
2691
3018
|
let afterSchema = null;
|
|
2692
3019
|
try {
|
|
@@ -2695,12 +3022,12 @@ async function recoverFailedUpgrade(options) {
|
|
|
2695
3022
|
afterSchema = null;
|
|
2696
3023
|
}
|
|
2697
3024
|
if (options.backupPath !== null && (0, import_node_fs3.existsSync)(options.backupPath) && (afterSchema === null || options.beforeSchema === null || afterSchema > options.beforeSchema)) {
|
|
2698
|
-
await backupDatabase(database, (0,
|
|
3025
|
+
await backupDatabase(database, (0, import_node_path4.join)(options.paths.stateDir, "backups", `failed-upgrade-${newestTimestamp()}.sqlite`), options.host);
|
|
2699
3026
|
replaceDatabaseFromBackup(options.backupPath, database, null, options.host);
|
|
2700
3027
|
runRequired(options.host, "chown", [`${options.state.accounts.hub}:${options.state.accounts.hub}`, database], "restored database ownership");
|
|
2701
3028
|
}
|
|
2702
|
-
options.state.previousVersion = options.oldPrevious === null ? null : (0,
|
|
2703
|
-
options.state.currentVersion = (0,
|
|
3029
|
+
options.state.previousVersion = options.oldPrevious === null ? null : (0, import_node_path4.basename)(options.oldPrevious);
|
|
3030
|
+
options.state.currentVersion = (0, import_node_path4.basename)(options.oldCurrent);
|
|
2704
3031
|
writeSystemState(options.paths, options.state);
|
|
2705
3032
|
if (options.activeComponents.length > 0) {
|
|
2706
3033
|
startUnits(options.host, options.units);
|
|
@@ -2716,7 +3043,7 @@ function assertManagedFileOwnership(paths, path) {
|
|
|
2716
3043
|
if ((stat.mode & 18) !== 0) throw new SystemError("insecure-permissions", `${path} must not be group/world-writable`);
|
|
2717
3044
|
}
|
|
2718
3045
|
function writeUnitFile(paths, unit, content, replace) {
|
|
2719
|
-
const path = (0,
|
|
3046
|
+
const path = (0, import_node_path4.join)(paths.unitsDir, unit);
|
|
2720
3047
|
assertManagedFileOwnership(paths, path);
|
|
2721
3048
|
writeManagedFile(path, content, 420, replace);
|
|
2722
3049
|
}
|
|
@@ -2731,10 +3058,10 @@ function ensureComponentSetup(options) {
|
|
|
2731
3058
|
ensureDirectory(paths.configDir, 493);
|
|
2732
3059
|
ensureDirectory(paths.stateDir, 493);
|
|
2733
3060
|
ensureDirectory(paths.unitsDir, 493);
|
|
2734
|
-
ensureDirectory((0,
|
|
2735
|
-
ensureDirectory((0,
|
|
2736
|
-
ensureDirectory((0,
|
|
2737
|
-
ensureDirectory((0,
|
|
3061
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
|
|
3062
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub", "run"), 448, true);
|
|
3063
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
|
|
3064
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
|
|
2738
3065
|
const release = currentReleaseOrThrow(paths);
|
|
2739
3066
|
void release;
|
|
2740
3067
|
writeSysusersFiles(paths, accounts, "local", power, host);
|
|
@@ -2743,7 +3070,7 @@ function ensureComponentSetup(options) {
|
|
|
2743
3070
|
const env = hubEnvTemplate(paths);
|
|
2744
3071
|
writeManagedFile(hubEnvPath(paths), env, 384, false);
|
|
2745
3072
|
}
|
|
2746
|
-
if (!(0, import_node_fs3.existsSync)((0,
|
|
3073
|
+
if (!(0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, UNIT_NAMES.hub))) {
|
|
2747
3074
|
const env = readHubEnvironment(paths);
|
|
2748
3075
|
const remote = env.HUB_PUBLIC_ORIGIN !== void 0 && env.HUB_PUBLIC_ORIGIN !== "";
|
|
2749
3076
|
const unit = renderSystemHubUnit({
|
|
@@ -2776,14 +3103,14 @@ function ensureComponentSetup(options) {
|
|
|
2776
3103
|
const validation = runRequired(
|
|
2777
3104
|
host,
|
|
2778
3105
|
"systemd-analyze",
|
|
2779
|
-
["verify", "--man=no", (0,
|
|
3106
|
+
["verify", "--man=no", (0, import_node_path4.join)(paths.unitsDir, unitFor(component))],
|
|
2780
3107
|
"native systemd unit validation"
|
|
2781
3108
|
);
|
|
2782
3109
|
if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
|
|
2783
3110
|
throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
|
|
2784
3111
|
}
|
|
2785
|
-
if (component === "hub") chownAccount([(0,
|
|
2786
|
-
if (component === "collector") chownAccount([(0,
|
|
3112
|
+
if (component === "hub") chownAccount([(0, import_node_path4.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
|
|
3113
|
+
if (component === "collector") chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
2787
3114
|
}
|
|
2788
3115
|
function ensurePowerAccess(paths, accounts, host) {
|
|
2789
3116
|
if (!(0, import_node_fs3.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
|
|
@@ -2791,19 +3118,36 @@ function ensurePowerAccess(paths, accounts, host) {
|
|
|
2791
3118
|
resumePowerRule(paths, host);
|
|
2792
3119
|
applyPowerAccess(paths, accounts, host);
|
|
2793
3120
|
}
|
|
3121
|
+
function resetUnfinishedSystemState(options) {
|
|
3122
|
+
uninstallSystem({
|
|
3123
|
+
apply: true,
|
|
3124
|
+
paths: options.paths,
|
|
3125
|
+
host: options.host,
|
|
3126
|
+
purgeData: true,
|
|
3127
|
+
confirmPurgeHistory: true,
|
|
3128
|
+
preserveNode: true,
|
|
3129
|
+
now: options.now
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
function installPreset(options, role) {
|
|
3133
|
+
if (options.adoptFrom !== void 0) {
|
|
3134
|
+
return componentSet(readAdoptionSetup((0, import_node_path4.resolve)(options.adoptFrom)).components);
|
|
3135
|
+
}
|
|
3136
|
+
return componentsForRole(role);
|
|
3137
|
+
}
|
|
2794
3138
|
function planSystemInstall(options) {
|
|
2795
3139
|
const paths = validateSystemPaths(options.paths);
|
|
2796
3140
|
const accounts = validateAccounts(options.accounts);
|
|
2797
3141
|
const role = resolveRole(options);
|
|
2798
|
-
const preset = componentSet(
|
|
3142
|
+
const preset = componentSet(installPreset(options, role));
|
|
2799
3143
|
const power = options.power !== false && preset.collector;
|
|
2800
3144
|
const initial = options.enable === true ? preset : { hub: false, collector: false };
|
|
2801
3145
|
const files = [
|
|
2802
|
-
{ path: (0,
|
|
3146
|
+
{ path: (0, import_node_path4.join)(paths.prefix, "current"), mode: "symlink", purpose: "active root-owned release" },
|
|
2803
3147
|
{ path: hubEnvPath(paths), mode: "0600", purpose: "root-owned hub configuration" },
|
|
2804
3148
|
{ path: collectorEnvPath(paths), mode: "0600", purpose: "root-owned collector configuration" },
|
|
2805
3149
|
{ path: sysusersPath(paths), mode: "0644", purpose: "dedicated nologin accounts" },
|
|
2806
|
-
...COMPONENTS.map((component) => ({ path: (0,
|
|
3150
|
+
...COMPONENTS.map((component) => ({ path: (0, import_node_path4.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }))
|
|
2807
3151
|
];
|
|
2808
3152
|
if (power) {
|
|
2809
3153
|
files.push({ path: raplRulePath(paths), mode: "0644", purpose: "package RAPL read rule" });
|
|
@@ -2818,7 +3162,7 @@ function planSystemInstall(options) {
|
|
|
2818
3162
|
];
|
|
2819
3163
|
const notes = [
|
|
2820
3164
|
"Nothing is changed without --apply and root.",
|
|
2821
|
-
options.adoptFrom ? `Adopts ${(0,
|
|
3165
|
+
options.adoptFrom ? `Adopts ${(0, import_node_path4.resolve)(options.adoptFrom)} by copy only; the original user setup is preserved.` : "Existing user services are not modified.",
|
|
2822
3166
|
"One complete package: both runtimes, units and accounts are installed; --role only sets the initial enabled preset.",
|
|
2823
3167
|
"Hub and collector use separate unprivileged accounts; Node gets no capabilities.",
|
|
2824
3168
|
...options.enable && preset.hub ? ["Enabling the hub creates its owner account once and prints the password once."] : [],
|
|
@@ -2826,7 +3170,10 @@ function planSystemInstall(options) {
|
|
|
2826
3170
|
];
|
|
2827
3171
|
return { operation: "install", apply: options.apply === true, role, components: initial, paths, accounts, commands, files, notes };
|
|
2828
3172
|
}
|
|
2829
|
-
|
|
3173
|
+
function installSystem(options) {
|
|
3174
|
+
return withSystemOperation(options, () => installSystemUnlocked(options));
|
|
3175
|
+
}
|
|
3176
|
+
async function installSystemUnlocked(options) {
|
|
2830
3177
|
const host = options.host ?? createSystemHost();
|
|
2831
3178
|
const now = options.now ?? Date.now;
|
|
2832
3179
|
const paths = validateSystemPaths(options.paths);
|
|
@@ -2835,7 +3182,8 @@ async function installSystem(options) {
|
|
|
2835
3182
|
const plan = planSystemInstall({ ...options, paths, accounts, role });
|
|
2836
3183
|
if (options.apply !== true) return { operation: "install", ok: true, apply: false, message: "plan only", plan };
|
|
2837
3184
|
assertApply(host);
|
|
2838
|
-
const
|
|
3185
|
+
const legacy = systemStateIsLegacy(paths);
|
|
3186
|
+
const existing = legacy ? null : readSystemState(paths);
|
|
2839
3187
|
if (existing?.active === true) {
|
|
2840
3188
|
throw new SystemError("already-installed", "a system installation already exists; use `rigscope system upgrade` or uninstall first");
|
|
2841
3189
|
}
|
|
@@ -2847,123 +3195,126 @@ async function installSystem(options) {
|
|
|
2847
3195
|
`existing user service(s) detected (${detail}); stop them and use --adopt-user-state for explicit copy-based adoption`
|
|
2848
3196
|
);
|
|
2849
3197
|
}
|
|
2850
|
-
|
|
2851
|
-
const source = (0, import_node_path3.resolve)(options.source);
|
|
3198
|
+
const source = (0, import_node_path4.resolve)(options.source);
|
|
2852
3199
|
if (!(0, import_node_fs3.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
|
|
2853
|
-
ensureDirectory(paths.prefix, 493);
|
|
2854
|
-
ensureDirectory((0, import_node_path3.join)(paths.prefix, "releases"), 493);
|
|
2855
|
-
ensureDirectory(paths.stateDir, 493);
|
|
2856
|
-
ensureDirectory(paths.configDir, 493);
|
|
2857
|
-
ensureDirectory(paths.unitsDir, 493);
|
|
2858
|
-
ensureDirectory((0, import_node_path3.join)(paths.stateDir, "hub"), 448, true);
|
|
2859
|
-
ensureDirectory((0, import_node_path3.join)(paths.stateDir, "collector"), 448, true);
|
|
2860
|
-
ensureDirectory((0, import_node_path3.join)(paths.stateDir, "backups"), 448, true);
|
|
2861
|
-
ensureDirectory((0, import_node_path3.join)(paths.stateDir, "hub", "run"), 448, true);
|
|
2862
|
-
assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
|
|
2863
|
-
const nodePath = ensureRootOwnedNode({ paths, host, nodePath: options.nodePath, copyNode: options.copyNode, trustedNode: options.trustedNode, apply: true });
|
|
2864
|
-
const stage = stageRelease({ source, paths, host, replaceRelease: options.replaceRelease, now });
|
|
2865
|
-
if (options.installDeps === true) {
|
|
2866
|
-
installDependencies(stage.releaseDir, host, options.installDepsCommand ?? "npm");
|
|
2867
|
-
}
|
|
2868
|
-
const missing = missingDependencies(stage.releaseDir);
|
|
2869
|
-
if (missing.length > 0) {
|
|
2870
|
-
throw new SystemError(
|
|
2871
|
-
"missing-dependencies",
|
|
2872
|
-
`production dependencies are missing (${missing.join(", ")}); stage with --install-deps or use a built release directory`
|
|
2873
|
-
);
|
|
2874
|
-
}
|
|
2875
|
-
chmodTree(stage.releaseDir, host);
|
|
2876
|
-
runRequired(host, "chown", ["-R", "root:root", stage.releaseDir], "release ownership");
|
|
2877
|
-
let adoption = null;
|
|
2878
|
-
if (options.adoptFrom) {
|
|
2879
|
-
adoption = adoptUserState({ source: options.adoptFrom, paths, role, accounts, apply: true, host, now });
|
|
2880
|
-
await performAdoptionCopyAsync({ source: options.adoptFrom, paths, role, host, copy: adoption });
|
|
2881
|
-
}
|
|
2882
3200
|
const port = options.port ?? 8081;
|
|
2883
3201
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-argument", "--port must be 1-65535");
|
|
2884
|
-
|
|
3202
|
+
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) });
|
|
3203
|
+
const preset = componentSet(installPreset(options, role));
|
|
2885
3204
|
const power = options.power !== false && preset.collector;
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
nodePath,
|
|
2895
|
-
database: hubDatabasePath(paths),
|
|
2896
|
-
enrollment: hubEnrollmentPath(paths),
|
|
2897
|
-
socket: hubSocketPath(paths),
|
|
2898
|
-
remote: adoption?.remote === true || typeof options.origin === "string",
|
|
2899
|
-
powerAccess: false
|
|
2900
|
-
});
|
|
2901
|
-
const collectorUnit = renderSystemCollectorUnit({
|
|
3205
|
+
if (options.adoptFrom) {
|
|
3206
|
+
adoptUserState({ source: options.adoptFrom, paths, role, accounts, apply: false, host, now });
|
|
3207
|
+
}
|
|
3208
|
+
assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
|
|
3209
|
+
ensureDirectory(paths.prefix, 493);
|
|
3210
|
+
const nodePath = ensureRootOwnedNode({ paths, host, nodePath: options.nodePath, copyNode: options.copyNode, trustedNode: options.trustedNode, apply: true });
|
|
3211
|
+
const staged = prepareStagedRelease({
|
|
3212
|
+
source,
|
|
2902
3213
|
paths,
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
powerAccess: power
|
|
3214
|
+
host,
|
|
3215
|
+
installDeps: options.installDeps,
|
|
3216
|
+
installDepsCommand: options.installDepsCommand
|
|
2907
3217
|
});
|
|
2908
|
-
|
|
3218
|
+
try {
|
|
3219
|
+
if (legacy) resetUnfinishedSystemState({ paths, host, now });
|
|
3220
|
+
ensureDirectory((0, import_node_path4.join)(paths.prefix, "releases"), 493);
|
|
3221
|
+
ensureDirectory(paths.stateDir, 493);
|
|
3222
|
+
ensureDirectory(paths.configDir, 493);
|
|
3223
|
+
ensureDirectory(paths.unitsDir, 493);
|
|
3224
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
|
|
3225
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
|
|
3226
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
|
|
3227
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub", "run"), 448, true);
|
|
3228
|
+
const release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
|
|
3229
|
+
let adoption = null;
|
|
3230
|
+
if (options.adoptFrom) {
|
|
3231
|
+
adoption = adoptUserState({ source: options.adoptFrom, paths, role, accounts, apply: true, host, now });
|
|
3232
|
+
await performAdoptionCopyAsync({ source: options.adoptFrom, paths, role, host, copy: adoption });
|
|
3233
|
+
}
|
|
3234
|
+
const hubEnv = adoption?.hubEnv ?? hubEnvTemplate(paths, options.origin, port);
|
|
3235
|
+
const collectorEnv = adoption?.collectorEnv ?? collectorEnvTemplate();
|
|
3236
|
+
writeManagedFile(hubEnvPath(paths), hubEnv, 384, false);
|
|
3237
|
+
writeManagedFile(collectorEnvPath(paths), collectorEnv, 384, false);
|
|
3238
|
+
writeSysusersFiles(paths, accounts, "local", power, host);
|
|
3239
|
+
const hubUnit = renderSystemHubUnit({
|
|
3240
|
+
paths,
|
|
3241
|
+
accounts,
|
|
3242
|
+
nodePath,
|
|
3243
|
+
database: hubDatabasePath(paths),
|
|
3244
|
+
enrollment: hubEnrollmentPath(paths),
|
|
3245
|
+
socket: hubSocketPath(paths),
|
|
3246
|
+
remote: adoption?.remote === true || typeof options.origin === "string",
|
|
3247
|
+
powerAccess: false
|
|
3248
|
+
});
|
|
3249
|
+
const collectorUnit = renderSystemCollectorUnit({
|
|
3250
|
+
paths,
|
|
3251
|
+
accounts,
|
|
3252
|
+
nodePath,
|
|
3253
|
+
collectorState: collectorStatePath(paths),
|
|
3254
|
+
powerAccess: power
|
|
3255
|
+
});
|
|
3256
|
+
writeUnitFile(paths, UNIT_NAMES.hub, `${hubUnit}
|
|
2909
3257
|
`, true);
|
|
2910
|
-
|
|
3258
|
+
writeUnitFile(paths, UNIT_NAMES.collector, `${collectorUnit}
|
|
2911
3259
|
`, true);
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
writeSystemState(paths, state);
|
|
2930
|
-
const wantsHub = preset.hub && (options.enable === true || options.start === true);
|
|
2931
|
-
const wantsCollector = preset.collector && (options.enable === true || options.start === true);
|
|
2932
|
-
let password;
|
|
2933
|
-
const awaiting = [];
|
|
2934
|
-
if (wantsHub) password = ensureHubOwner(paths);
|
|
2935
|
-
if (wantsCollector && !collectorConfigured(paths).configured) {
|
|
2936
|
-
const pairing = await pairCollectorWithHub({ paths, accounts, host });
|
|
2937
|
-
if (pairing.awaiting) awaiting.push("collector");
|
|
2938
|
-
}
|
|
2939
|
-
if (options.enable === true) {
|
|
2940
|
-
enableUnits(host, unitNamesForComponents(preset));
|
|
2941
|
-
state.components = { hub: { enabled: preset.hub }, collector: { enabled: preset.collector } };
|
|
3260
|
+
const validation = runRequired(host, "systemd-analyze", ["verify", "--man=no", ...COMPONENTS.map((component) => (0, import_node_path4.join)(paths.unitsDir, unitFor(component)))], "native systemd unit validation");
|
|
3261
|
+
if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
|
|
3262
|
+
throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
|
|
3263
|
+
}
|
|
3264
|
+
if (power) ensurePowerAccess(paths, accounts, host);
|
|
3265
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
|
|
3266
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
|
|
3267
|
+
const state = emptyState({ paths, accounts, role, components: { hub: false, collector: false }, nodePath, now });
|
|
3268
|
+
state.currentVersion = release.version;
|
|
3269
|
+
state.releases[release.version] = {
|
|
3270
|
+
releaseDir: release.releaseDir,
|
|
3271
|
+
schemaVersion: release.schemaVersion,
|
|
3272
|
+
installedAt: new Date(now()).toISOString()
|
|
3273
|
+
};
|
|
3274
|
+
state.adoption = adoption?.record ?? null;
|
|
3275
|
+
state.power = power ? { enabled: true, rulePath: raplRulePath(paths), group: accounts.powerGroup } : null;
|
|
3276
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), release.releaseDir, host);
|
|
2942
3277
|
writeSystemState(paths, state);
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
if (
|
|
2948
|
-
if (
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3278
|
+
const wantsHub = preset.hub && (options.enable === true || options.start === true);
|
|
3279
|
+
const wantsCollector = preset.collector && (options.enable === true || options.start === true);
|
|
3280
|
+
let password;
|
|
3281
|
+
const awaiting = [];
|
|
3282
|
+
if (wantsHub) password = ensureHubOwner(paths);
|
|
3283
|
+
if (wantsCollector && !collectorConfigured(paths).configured) {
|
|
3284
|
+
const pairing = await pairCollectorWithHub({ paths, accounts, host });
|
|
3285
|
+
if (pairing.awaiting) awaiting.push("collector");
|
|
3286
|
+
}
|
|
3287
|
+
if (options.enable === true) {
|
|
3288
|
+
enableUnits(host, unitNamesForComponents(preset));
|
|
3289
|
+
state.components = { hub: { enabled: preset.hub }, collector: { enabled: preset.collector } };
|
|
3290
|
+
writeSystemState(paths, state);
|
|
3291
|
+
}
|
|
3292
|
+
const started = [];
|
|
3293
|
+
if (options.start === true) {
|
|
3294
|
+
assertSystemStartAllowed(paths, componentSet(preset));
|
|
3295
|
+
if (preset.hub) started.push("hub");
|
|
3296
|
+
if (preset.collector && !awaiting.includes("collector")) started.push("collector");
|
|
3297
|
+
if (started.length > 0) {
|
|
3298
|
+
startUnits(host, started.map(unitFor));
|
|
3299
|
+
await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, started);
|
|
3300
|
+
}
|
|
2952
3301
|
}
|
|
3302
|
+
return {
|
|
3303
|
+
operation: "install",
|
|
3304
|
+
ok: true,
|
|
3305
|
+
apply: true,
|
|
3306
|
+
message: `installed ${release.version} for ${role} preset`,
|
|
3307
|
+
version: release.version,
|
|
3308
|
+
current: release.releaseDir,
|
|
3309
|
+
previous: null,
|
|
3310
|
+
state,
|
|
3311
|
+
password,
|
|
3312
|
+
...awaiting.length > 0 ? { awaitingConnection: awaiting } : {},
|
|
3313
|
+
warnings: adoption?.warnings ?? []
|
|
3314
|
+
};
|
|
3315
|
+
} finally {
|
|
3316
|
+
discardStagedRelease(staged);
|
|
2953
3317
|
}
|
|
2954
|
-
return {
|
|
2955
|
-
operation: "install",
|
|
2956
|
-
ok: true,
|
|
2957
|
-
apply: true,
|
|
2958
|
-
message: `installed ${stage.version} for ${role} preset`,
|
|
2959
|
-
version: stage.version,
|
|
2960
|
-
current: stage.releaseDir,
|
|
2961
|
-
previous: null,
|
|
2962
|
-
state,
|
|
2963
|
-
password,
|
|
2964
|
-
...awaiting.length > 0 ? { awaitingConnection: awaiting } : {},
|
|
2965
|
-
warnings: adoption?.warnings ?? []
|
|
2966
|
-
};
|
|
2967
3318
|
}
|
|
2968
3319
|
function systemStatus(options = {}) {
|
|
2969
3320
|
const host = options.host ?? createSystemHost();
|
|
@@ -2976,14 +3327,14 @@ function systemStatus(options = {}) {
|
|
|
2976
3327
|
} catch (error) {
|
|
2977
3328
|
stateError = error instanceof Error ? error.message : String(error);
|
|
2978
3329
|
}
|
|
2979
|
-
const presentUnits = [UNIT_NAMES.hub, UNIT_NAMES.collector].filter((unit) => (0, import_node_fs3.existsSync)((0,
|
|
3330
|
+
const presentUnits = [UNIT_NAMES.hub, UNIT_NAMES.collector].filter((unit) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, unit)));
|
|
2980
3331
|
const publicRole = presentUnits.length === 2 ? "local" : presentUnits[0] === UNIT_NAMES.hub ? "hub" : presentUnits[0] === UNIT_NAMES.collector ? "collector" : null;
|
|
2981
3332
|
const role = state?.role ?? publicRole ?? "local";
|
|
2982
3333
|
const current = currentReleaseDir(paths);
|
|
2983
3334
|
let publicVersion = null;
|
|
2984
3335
|
if (current) {
|
|
2985
3336
|
try {
|
|
2986
|
-
const manifest = JSON.parse((0, import_node_fs3.readFileSync)((0,
|
|
3337
|
+
const manifest = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path4.join)(current, "package.json"), "utf8"));
|
|
2987
3338
|
if (typeof manifest.version === "string") publicVersion = manifest.version;
|
|
2988
3339
|
} catch {
|
|
2989
3340
|
}
|
|
@@ -2994,7 +3345,7 @@ function systemStatus(options = {}) {
|
|
|
2994
3345
|
const componentReports = {};
|
|
2995
3346
|
for (const component of COMPONENTS) {
|
|
2996
3347
|
const unit = unitFor(component);
|
|
2997
|
-
const present = (0, import_node_fs3.existsSync)((0,
|
|
3348
|
+
const present = (0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, unit));
|
|
2998
3349
|
const active = present && systemdActive(host, unit);
|
|
2999
3350
|
const unitEnabled = present && systemdEnabled(host, unit);
|
|
3000
3351
|
const enabled = state !== null ? state.components[component].enabled : unitEnabled;
|
|
@@ -3003,7 +3354,7 @@ function systemStatus(options = {}) {
|
|
|
3003
3354
|
componentReports[component] = {
|
|
3004
3355
|
component,
|
|
3005
3356
|
unit,
|
|
3006
|
-
path: (0,
|
|
3357
|
+
path: (0, import_node_path4.join)(paths.unitsDir, unit),
|
|
3007
3358
|
present,
|
|
3008
3359
|
enabled,
|
|
3009
3360
|
unitEnabled,
|
|
@@ -3225,7 +3576,7 @@ async function enableSystem(options) {
|
|
|
3225
3576
|
],
|
|
3226
3577
|
files: [
|
|
3227
3578
|
{ path: component === "hub" ? hubEnvPath(paths) : collectorEnvPath(paths), mode: "0600", purpose: `${component} configuration` },
|
|
3228
|
-
{ path: (0,
|
|
3579
|
+
{ path: (0, import_node_path4.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }
|
|
3229
3580
|
],
|
|
3230
3581
|
notes: [
|
|
3231
3582
|
"Enables only the requested component; the other component keeps running.",
|
|
@@ -3338,7 +3689,7 @@ async function connectSystem(options) {
|
|
|
3338
3689
|
const state = readSystemState(paths);
|
|
3339
3690
|
if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
|
|
3340
3691
|
const destination = connectDestinationUrl(options.url);
|
|
3341
|
-
const tokenFile = (0,
|
|
3692
|
+
const tokenFile = (0, import_node_path4.resolve)(options.tokenFile);
|
|
3342
3693
|
const configured = collectorConfigured(paths);
|
|
3343
3694
|
if (configured.configured) {
|
|
3344
3695
|
throw new SystemError(
|
|
@@ -3373,10 +3724,6 @@ async function connectSystem(options) {
|
|
|
3373
3724
|
ensurePowerAccess(paths, state.accounts, host);
|
|
3374
3725
|
state.power = { enabled: true, rulePath: raplRulePath(paths), group: state.accounts.powerGroup };
|
|
3375
3726
|
}
|
|
3376
|
-
const existingIdentity = readCollectorIdentity(collectorStatePath(paths));
|
|
3377
|
-
if (existingIdentity !== null) {
|
|
3378
|
-
writeCollectorIdentity(collectorStatePath(paths), existingIdentity);
|
|
3379
|
-
}
|
|
3380
3727
|
const wasActive = systemdActive(host, UNIT_NAMES.collector);
|
|
3381
3728
|
if (wasActive) {
|
|
3382
3729
|
stopUnits(host, [UNIT_NAMES.collector]);
|
|
@@ -3390,12 +3737,12 @@ async function connectSystem(options) {
|
|
|
3390
3737
|
`collector enrollment failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3391
3738
|
);
|
|
3392
3739
|
} finally {
|
|
3393
|
-
chownAccount([(0,
|
|
3740
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
|
|
3394
3741
|
}
|
|
3395
3742
|
if (!collectorConfigured(paths).configured) {
|
|
3396
3743
|
throw new SystemError("enrollment-failed", "the hub accepted enrollment but no destination was saved; check private state");
|
|
3397
3744
|
}
|
|
3398
|
-
chownAccount([(0,
|
|
3745
|
+
chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
|
|
3399
3746
|
writeSystemState(paths, state);
|
|
3400
3747
|
const enabled = state.components.collector.enabled;
|
|
3401
3748
|
if (enabled || wasActive) {
|
|
@@ -3416,6 +3763,60 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3416
3763
|
const host = options.host ?? createSystemHost();
|
|
3417
3764
|
const now = options.now ?? Date.now;
|
|
3418
3765
|
const paths = validateSystemPaths(options.paths);
|
|
3766
|
+
if (systemStateIsLegacy(paths)) {
|
|
3767
|
+
const source2 = (0, import_node_path4.resolve)(options.source);
|
|
3768
|
+
if (!(0, import_node_fs3.existsSync)(source2)) throw new SystemError("missing-source", `release source does not exist: ${source2}`);
|
|
3769
|
+
const role = legacySystemRole(paths);
|
|
3770
|
+
const preset = componentsForRole(role);
|
|
3771
|
+
const enable = options.enable ?? (options.start === true || unitNamesForComponents(preset).some((unit) => systemdEnabled(host, unit)));
|
|
3772
|
+
const units = [...Object.values(UNIT_NAMES), ...LEGACY_UNIT_NAMES];
|
|
3773
|
+
const plan2 = {
|
|
3774
|
+
operation: "upgrade",
|
|
3775
|
+
apply: options.apply === true,
|
|
3776
|
+
role,
|
|
3777
|
+
components: preset,
|
|
3778
|
+
paths,
|
|
3779
|
+
accounts: validateAccounts(options.accounts),
|
|
3780
|
+
commands: [
|
|
3781
|
+
"copy and validate the replacement release",
|
|
3782
|
+
...units.map((unit) => `systemctl stop ${unit}`),
|
|
3783
|
+
"verify every owned runtime lease is stopped",
|
|
3784
|
+
"remove unfinished configuration, history and application releases",
|
|
3785
|
+
"install the canonical layout from the validated replacement",
|
|
3786
|
+
...enable ? unitNamesForComponents(preset).map((unit) => `systemctl enable ${unit}`) : [],
|
|
3787
|
+
...options.start ? unitNamesForComponents(preset).map((unit) => `systemctl start ${unit}`) : []
|
|
3788
|
+
],
|
|
3789
|
+
files: [
|
|
3790
|
+
{ path: stateFilePath(paths), mode: "0600", purpose: "canonical replacement state" },
|
|
3791
|
+
{ path: (0, import_node_path4.join)(paths.stateDir, "backups"), mode: "0700", purpose: "removed with unfinished history" }
|
|
3792
|
+
],
|
|
3793
|
+
notes: [
|
|
3794
|
+
"An unfinished installation is reset, not migrated; its history and credentials are removed.",
|
|
3795
|
+
"The replacement is copied and validated before any unit is stopped or state is removed.",
|
|
3796
|
+
"Both owned units and legacy combined units are stopped before the reset."
|
|
3797
|
+
]
|
|
3798
|
+
};
|
|
3799
|
+
if (options.apply !== true) return { operation: "upgrade", ok: true, apply: false, message: "plan only", plan: plan2 };
|
|
3800
|
+
return installSystemUnlocked({
|
|
3801
|
+
apply: true,
|
|
3802
|
+
source: options.source,
|
|
3803
|
+
role,
|
|
3804
|
+
paths: options.paths,
|
|
3805
|
+
accounts: options.accounts,
|
|
3806
|
+
host: options.host,
|
|
3807
|
+
start: options.start,
|
|
3808
|
+
enable,
|
|
3809
|
+
installDeps: options.installDeps,
|
|
3810
|
+
installDepsCommand: options.installDepsCommand,
|
|
3811
|
+
replaceRelease: options.replaceRelease,
|
|
3812
|
+
startupTimeoutMs: options.startupTimeoutMs,
|
|
3813
|
+
homeRoots: options.homeRoots,
|
|
3814
|
+
// This is an existing system installation; unrelated user-scope units
|
|
3815
|
+
// must not block its reset.
|
|
3816
|
+
allowUserConflicts: options.allowUserConflicts ?? true,
|
|
3817
|
+
now: options.now
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3419
3820
|
const state = readSystemState(paths);
|
|
3420
3821
|
if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
|
|
3421
3822
|
const activeComponents = COMPONENTS.filter((component) => systemdActive(host, unitFor(component)));
|
|
@@ -3428,7 +3829,7 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3428
3829
|
paths,
|
|
3429
3830
|
accounts: state.accounts,
|
|
3430
3831
|
commands: ["stage new release", ...activeUnits.map((unit) => `systemctl stop ${unit}`), "backup database", "switch current", ...activeUnits.map((unit) => `systemctl start ${unit}`)],
|
|
3431
|
-
files: [{ path: (0,
|
|
3832
|
+
files: [{ path: (0, import_node_path4.join)(paths.stateDir, "backups"), mode: "0700", purpose: "pre-upgrade database backup" }],
|
|
3432
3833
|
notes: [
|
|
3433
3834
|
"Staging happens before any service is stopped.",
|
|
3434
3835
|
"The exact active component set is restored; --start does not start a component that was stopped.",
|
|
@@ -3438,91 +3839,105 @@ async function upgradeSystemUnlocked(options) {
|
|
|
3438
3839
|
if (options.apply !== true) return { operation: "upgrade", ok: true, apply: false, message: "plan only", plan };
|
|
3439
3840
|
assertApply(host);
|
|
3440
3841
|
assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
|
|
3441
|
-
const source = (0,
|
|
3842
|
+
const source = (0, import_node_path4.resolve)(options.source);
|
|
3442
3843
|
if (!(0, import_node_fs3.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
|
|
3443
|
-
const
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
chmodTree(stage.releaseDir, host);
|
|
3451
|
-
runRequired(host, "chown", ["-R", "root:root", stage.releaseDir], "release ownership");
|
|
3452
|
-
const oldCurrent = currentReleaseDir(paths);
|
|
3453
|
-
if (oldCurrent === null) throw new SystemError("invalid-state", "current release symlink is missing");
|
|
3454
|
-
const oldPrevious = previousReleaseDir(paths);
|
|
3455
|
-
let backupPath = null;
|
|
3456
|
-
let beforeSchema = null;
|
|
3844
|
+
const staged = prepareStagedRelease({
|
|
3845
|
+
source,
|
|
3846
|
+
paths,
|
|
3847
|
+
host,
|
|
3848
|
+
installDeps: options.installDeps,
|
|
3849
|
+
installDepsCommand: options.installDepsCommand
|
|
3850
|
+
});
|
|
3457
3851
|
try {
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
const lock = systemLockState(paths);
|
|
3461
|
-
if (lock.live) throw new SystemError("duplicate-runtime", `hub did not stop cleanly; lock remains at ${lock.path}`);
|
|
3462
|
-
const database = hubDatabasePath(paths);
|
|
3463
|
-
if ((0, import_node_fs3.existsSync)(database)) {
|
|
3464
|
-
ensureDirectory((0, import_node_path3.join)(paths.stateDir, "backups"), 448, true);
|
|
3465
|
-
beforeSchema = databaseSchema(database);
|
|
3466
|
-
backupPath = (0, import_node_path3.join)(paths.stateDir, "backups", `pre-upgrade-${stage.version}-${newestTimestamp()}.sqlite`);
|
|
3467
|
-
await backupDatabase(database, backupPath, host);
|
|
3852
|
+
if (state.currentVersion === staged.version) {
|
|
3853
|
+
throw new SystemError("same-version", `release ${staged.version} is already current; use a different version`);
|
|
3468
3854
|
}
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
state
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3855
|
+
assertReleaseTargetAvailable(paths, releaseTargetDir(paths, staged.version), options.replaceRelease);
|
|
3856
|
+
const oldCurrent = currentReleaseDir(paths);
|
|
3857
|
+
if (oldCurrent === null) throw new SystemError("invalid-state", "current release symlink is missing");
|
|
3858
|
+
const oldPrevious = previousReleaseDir(paths);
|
|
3859
|
+
let backupPath = null;
|
|
3860
|
+
let beforeSchema = null;
|
|
3861
|
+
let release = null;
|
|
3862
|
+
try {
|
|
3863
|
+
stopUnits(host, activeUnits);
|
|
3864
|
+
assertDirectoryStopped(paths.stateDir);
|
|
3865
|
+
const lock = systemLockState(paths);
|
|
3866
|
+
if (lock.live) throw new SystemError("duplicate-runtime", `hub did not stop cleanly; lock remains at ${lock.path}`);
|
|
3867
|
+
const database = hubDatabasePath(paths);
|
|
3868
|
+
if ((0, import_node_fs3.existsSync)(database)) {
|
|
3869
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
|
|
3870
|
+
beforeSchema = databaseSchema(database);
|
|
3871
|
+
backupPath = (0, import_node_path4.join)(paths.stateDir, "backups", `pre-upgrade-${staged.version}-${newestTimestamp()}.sqlite`);
|
|
3872
|
+
await backupDatabase(database, backupPath, host);
|
|
3873
|
+
}
|
|
3874
|
+
release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
|
|
3875
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), oldCurrent, host);
|
|
3876
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), release.releaseDir, host);
|
|
3877
|
+
state.previousVersion = state.currentVersion;
|
|
3878
|
+
state.currentVersion = release.version;
|
|
3879
|
+
state.releases[release.version] = {
|
|
3880
|
+
releaseDir: release.releaseDir,
|
|
3881
|
+
schemaVersion: release.schemaVersion,
|
|
3882
|
+
installedAt: new Date(now()).toISOString()
|
|
3487
3883
|
};
|
|
3884
|
+
if (backupPath !== null) {
|
|
3885
|
+
state.backups[(0, import_node_path4.basename)(backupPath)] = {
|
|
3886
|
+
path: backupPath,
|
|
3887
|
+
schemaVersion: beforeSchema,
|
|
3888
|
+
fromSchema: beforeSchema,
|
|
3889
|
+
toSchema: release.schemaVersion,
|
|
3890
|
+
createdAt: new Date(now()).toISOString(),
|
|
3891
|
+
fromVersion: state.previousVersion ?? void 0,
|
|
3892
|
+
toVersion: release.version
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
writeSystemState(paths, state);
|
|
3896
|
+
if (activeComponents.length > 0) {
|
|
3897
|
+
startUnits(host, activeUnits);
|
|
3898
|
+
await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, activeComponents);
|
|
3899
|
+
}
|
|
3900
|
+
} catch (error) {
|
|
3901
|
+
await recoverFailedUpgrade({
|
|
3902
|
+
paths,
|
|
3903
|
+
state,
|
|
3904
|
+
oldCurrent,
|
|
3905
|
+
oldPrevious,
|
|
3906
|
+
beforeSchema,
|
|
3907
|
+
backupPath,
|
|
3908
|
+
host,
|
|
3909
|
+
activeComponents,
|
|
3910
|
+
units: activeUnits
|
|
3911
|
+
});
|
|
3912
|
+
throw error;
|
|
3488
3913
|
}
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
oldCurrent,
|
|
3499
|
-
oldPrevious,
|
|
3500
|
-
beforeSchema,
|
|
3914
|
+
const upgraded = release;
|
|
3915
|
+
return {
|
|
3916
|
+
operation: "upgrade",
|
|
3917
|
+
ok: true,
|
|
3918
|
+
apply: true,
|
|
3919
|
+
message: `upgraded ${state.previousVersion ?? "previous"} -> ${upgraded.version}`,
|
|
3920
|
+
version: upgraded.version,
|
|
3921
|
+
current: upgraded.releaseDir,
|
|
3922
|
+
previous: oldCurrent,
|
|
3501
3923
|
backupPath,
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
throw error;
|
|
3924
|
+
state
|
|
3925
|
+
};
|
|
3926
|
+
} finally {
|
|
3927
|
+
discardStagedRelease(staged);
|
|
3507
3928
|
}
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
apply: true,
|
|
3512
|
-
message: `upgraded ${state.previousVersion ?? "previous"} -> ${stage.version}`,
|
|
3513
|
-
version: stage.version,
|
|
3514
|
-
current: stage.releaseDir,
|
|
3515
|
-
previous: oldCurrent,
|
|
3516
|
-
backupPath,
|
|
3517
|
-
state
|
|
3518
|
-
};
|
|
3929
|
+
}
|
|
3930
|
+
function systemOperationLockPath(paths) {
|
|
3931
|
+
return (0, import_node_path4.join)(paths.prefix, "run", "operation.lock");
|
|
3519
3932
|
}
|
|
3520
3933
|
async function withSystemOperation(options, operation) {
|
|
3521
3934
|
if (!options.apply) return operation();
|
|
3522
3935
|
const host = options.host ?? createSystemHost();
|
|
3523
3936
|
assertApply(host);
|
|
3524
3937
|
const paths = validateSystemPaths(options.paths);
|
|
3525
|
-
|
|
3938
|
+
ensureDirectory(paths.prefix, 493);
|
|
3939
|
+
ensureDirectory((0, import_node_path4.join)(paths.prefix, "run"), 448, true);
|
|
3940
|
+
const release = acquireProcessLease(systemOperationLockPath(paths));
|
|
3526
3941
|
try {
|
|
3527
3942
|
return await operation();
|
|
3528
3943
|
} finally {
|
|
@@ -3573,7 +3988,7 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
3573
3988
|
try {
|
|
3574
3989
|
stopUnits(host, units);
|
|
3575
3990
|
assertDirectoryStopped(paths.stateDir);
|
|
3576
|
-
if (systemLockState(paths).live || lockIsLive((0,
|
|
3991
|
+
if (systemLockState(paths).live || lockIsLive((0, import_node_path4.join)(paths.stateDir, "collector/collector-runtime.lock")).live) {
|
|
3577
3992
|
throw new SystemError("runtime-active", "runtime lease remains after stop; rollback refused");
|
|
3578
3993
|
}
|
|
3579
3994
|
const database = hubDatabasePath(paths);
|
|
@@ -3589,8 +4004,8 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
3589
4004
|
}
|
|
3590
4005
|
}
|
|
3591
4006
|
if ((0, import_node_fs3.existsSync)(database)) {
|
|
3592
|
-
ensureDirectory((0,
|
|
3593
|
-
safetyBackup = (0,
|
|
4007
|
+
ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
|
|
4008
|
+
safetyBackup = (0, import_node_path4.join)(paths.stateDir, "backups", `pre-rollback-${newestTimestamp()}.sqlite`);
|
|
3594
4009
|
await backupDatabase(database, safetyBackup, host);
|
|
3595
4010
|
}
|
|
3596
4011
|
if (backupToRestore !== null) {
|
|
@@ -3598,8 +4013,8 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
3598
4013
|
restoredDatabase = true;
|
|
3599
4014
|
runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, database], "restored database ownership");
|
|
3600
4015
|
}
|
|
3601
|
-
replaceSymlink((0,
|
|
3602
|
-
replaceSymlink((0,
|
|
4016
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), oldCurrent, host);
|
|
4017
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), previous, host);
|
|
3603
4018
|
state.previousVersion = state.currentVersion;
|
|
3604
4019
|
state.currentVersion = previousVersion;
|
|
3605
4020
|
state.updatedAt = new Date(now()).toISOString();
|
|
@@ -3621,10 +4036,10 @@ async function rollbackSystemUnlocked(options = {}) {
|
|
|
3621
4036
|
};
|
|
3622
4037
|
} catch (error) {
|
|
3623
4038
|
stopUnits(host, units);
|
|
3624
|
-
replaceSymlink((0,
|
|
3625
|
-
replaceSymlink((0,
|
|
4039
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), oldCurrent, host);
|
|
4040
|
+
replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), previous, host);
|
|
3626
4041
|
if (restoredDatabase && safetyBackup) {
|
|
3627
|
-
const failed = (0,
|
|
4042
|
+
const failed = (0, import_node_path4.join)(paths.stateDir, "backups", `failed-rollback-${newestTimestamp()}.sqlite`);
|
|
3628
4043
|
await backupDatabase(hubDatabasePath(paths), failed, host);
|
|
3629
4044
|
replaceDatabaseFromBackup(safetyBackup, hubDatabasePath(paths), null, host);
|
|
3630
4045
|
runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, hubDatabasePath(paths)], "restored database ownership");
|
|
@@ -3641,8 +4056,9 @@ function uninstallSystem(options = {}) {
|
|
|
3641
4056
|
const host = options.host ?? createSystemHost();
|
|
3642
4057
|
const now = options.now ?? Date.now;
|
|
3643
4058
|
const paths = validateSystemPaths(options.paths);
|
|
3644
|
-
const state = readSystemState(paths);
|
|
3645
|
-
const
|
|
4059
|
+
const state = systemStateIsLegacy(paths) ? null : readSystemState(paths);
|
|
4060
|
+
const plannedUnits = [...Object.values(UNIT_NAMES), ...LEGACY_UNIT_NAMES];
|
|
4061
|
+
const units = options.apply === true ? unitsForCleanup(paths, host) : plannedUnits;
|
|
3646
4062
|
const plan = {
|
|
3647
4063
|
operation: "uninstall",
|
|
3648
4064
|
apply: options.apply === true,
|
|
@@ -3680,7 +4096,7 @@ function uninstallSystem(options = {}) {
|
|
|
3680
4096
|
assertDirectoryStopped(paths.stateDir);
|
|
3681
4097
|
for (const unit of units) runMaybe(host, "systemctl", ["disable", unit]);
|
|
3682
4098
|
for (const unit of units) {
|
|
3683
|
-
const path = (0,
|
|
4099
|
+
const path = (0, import_node_path4.join)(paths.unitsDir, unit);
|
|
3684
4100
|
assertManagedFileOwnership(paths, path);
|
|
3685
4101
|
try {
|
|
3686
4102
|
(0, import_node_fs3.unlinkSync)(path);
|
|
@@ -3701,10 +4117,10 @@ function uninstallSystem(options = {}) {
|
|
|
3701
4117
|
restorePowerOriginals(paths, host);
|
|
3702
4118
|
resumePowerRule(paths, host);
|
|
3703
4119
|
}
|
|
3704
|
-
removeSymlink((0,
|
|
3705
|
-
removeSymlink((0,
|
|
3706
|
-
(0, import_node_fs3.rmSync)((0,
|
|
3707
|
-
(0, import_node_fs3.rmSync)((0,
|
|
4120
|
+
removeSymlink((0, import_node_path4.join)(paths.prefix, "current"));
|
|
4121
|
+
removeSymlink((0, import_node_path4.join)(paths.prefix, "previous"));
|
|
4122
|
+
(0, import_node_fs3.rmSync)((0, import_node_path4.join)(paths.prefix, "releases"), { recursive: true, force: true });
|
|
4123
|
+
if (options.preserveNode !== true) (0, import_node_fs3.rmSync)((0, import_node_path4.join)(paths.prefix, "node"), { recursive: true, force: true });
|
|
3708
4124
|
if (state !== null) {
|
|
3709
4125
|
state.active = false;
|
|
3710
4126
|
state.power = null;
|
|
@@ -4088,7 +4504,7 @@ ${humanPlan(report.plan)}`);
|
|
|
4088
4504
|
if (parsed.command === "logs") {
|
|
4089
4505
|
const selected = validateSystemPaths(paths);
|
|
4090
4506
|
const component = componentFrom(parsed, false);
|
|
4091
|
-
const units = (component ? [unitFor(component)] : [UNIT_NAMES.hub, UNIT_NAMES.collector]).filter((unit) => (0, import_node_fs3.existsSync)((0,
|
|
4507
|
+
const units = (component ? [unitFor(component)] : [UNIT_NAMES.hub, UNIT_NAMES.collector]).filter((unit) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(selected.unitsDir, unit)));
|
|
4092
4508
|
if (units.length === 0) throw new SystemError("not-installed", component ? `${component} logs are unavailable; its unit is not installed` : "no system services installed");
|
|
4093
4509
|
const result = (0, import_node_child_process.spawnSync)("journalctl", [...units.flatMap((unit) => ["--unit", unit]), "--lines=100", "--no-pager", ...boolValue(values, "follow") ? ["--follow"] : []], { stdio: "inherit" });
|
|
4094
4510
|
if (result.error) throw result.error;
|
|
@@ -4153,6 +4569,7 @@ ${humanPlan(report.plan)}`);
|
|
|
4153
4569
|
paths,
|
|
4154
4570
|
host,
|
|
4155
4571
|
start: boolValue(values, "start"),
|
|
4572
|
+
enable: boolValue(values, "enable") || void 0,
|
|
4156
4573
|
installDeps: boolValue(values, "install-deps"),
|
|
4157
4574
|
installDepsCommand: stringValue(values, "install-deps-command"),
|
|
4158
4575
|
replaceRelease: boolValue(values, "replace-release"),
|
|
@@ -4160,8 +4577,10 @@ ${humanPlan(report.plan)}`);
|
|
|
4160
4577
|
});
|
|
4161
4578
|
if (json) console.log(formatSystemReport(report));
|
|
4162
4579
|
else if (report.plan) console.log(humanPlan(report.plan));
|
|
4163
|
-
else
|
|
4164
|
-
|
|
4580
|
+
else {
|
|
4581
|
+
printMutationReport(report);
|
|
4582
|
+
console.log(`Backup: ${report.backupPath ?? "none"}`);
|
|
4583
|
+
}
|
|
4165
4584
|
return 0;
|
|
4166
4585
|
}
|
|
4167
4586
|
if (parsed.command === "rollback") {
|
|
@@ -4274,7 +4693,6 @@ if (isMainModule()) {
|
|
|
4274
4693
|
RAPL_RULE_NAME,
|
|
4275
4694
|
STATE_FILE_NAME,
|
|
4276
4695
|
SYSTEM_SCOPE,
|
|
4277
|
-
SYSTEM_STATE_FORMAT_VERSION,
|
|
4278
4696
|
SYSUSERS_NAME,
|
|
4279
4697
|
SystemError,
|
|
4280
4698
|
UNIT_NAMES,
|
|
@@ -4319,6 +4737,7 @@ if (isMainModule()) {
|
|
|
4319
4737
|
startSystem,
|
|
4320
4738
|
stateFilePath,
|
|
4321
4739
|
stopSystem,
|
|
4740
|
+
systemStateIsLegacy,
|
|
4322
4741
|
systemStatus,
|
|
4323
4742
|
systemdQuote,
|
|
4324
4743
|
sysusersPath,
|