@camstack/system 1.2.377 → 1.2.379
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/auth/auth-manager.d.ts +10 -2
- package/dist/auth/jwt-signing-key.d.ts +42 -0
- package/dist/auth/user-manager.d.ts +19 -19
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +67 -11
- package/dist/builtins/keychain/index.mjs +67 -11
- package/dist/builtins/keychain/keychain-service.d.ts +14 -2
- package/dist/builtins/keychain/system-held-secrets.d.ts +11 -0
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/admin-account-boot.d.ts +1 -1
- package/dist/builtins/local-auth/auth-config-reader.d.ts +9 -7
- package/dist/builtins/local-auth/jwt-signing-key-keeper.d.ts +30 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts +2 -6
- package/dist/builtins/local-auth/local-auth.addon.js +127 -221
- package/dist/builtins/local-auth/local-auth.addon.mjs +126 -220
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/cluster-secret-keeper.d.ts +36 -0
- package/dist/builtins/system-config/system-config.addon.d.ts +13 -0
- package/dist/builtins/system-config/system-config.addon.js +244 -53
- package/dist/builtins/system-config/system-config.addon.mjs +244 -54
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-DlDSoqm1.js → child-cap-dispatch-CZeRUgYF.js} +1 -1
- package/dist/{child-cap-dispatch-CgmOo0yl.mjs → child-cap-dispatch-CpjohToR.mjs} +1 -1
- package/dist/cluster-secret-BDue-uJj.mjs +83 -0
- package/dist/cluster-secret-Dg0OOOSr.js +148 -0
- package/dist/{composition-sources-BNyXPSs_.js → composition-sources-CHvLMrFI.js} +1 -1
- package/dist/{composition-sources-D39bl0nP.mjs → composition-sources-DT_uJwI3.mjs} +1 -1
- package/dist/{dist-BWNoS-1S.mjs → dist-B9oFw2jh.mjs} +200 -3
- package/dist/{dist-D_a9mUiM.js → dist-Cc6KOdY-.js} +211 -2
- package/dist/{event-loop-stall-monitor-ClPhfx7Y.mjs → event-loop-stall-monitor-anUH2IE1.mjs} +21 -20
- package/dist/{event-loop-stall-monitor-DuOn8H1n.js → event-loop-stall-monitor-mk6WGxir.js} +21 -20
- package/dist/index.d.ts +4 -2
- package/dist/index.js +192 -62
- package/dist/index.mjs +171 -55
- package/dist/jwt-signing-key-C2rE3wH6.js +174 -0
- package/dist/jwt-signing-key-Qhde8r82.mjs +127 -0
- package/dist/kernel/config-manager.d.ts +1 -1
- package/dist/kernel/config-schema.d.ts +4 -11
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/moleculer/broker-factory.d.ts +0 -2
- package/dist/kernel/moleculer/cluster-secret.d.ts +31 -0
- package/dist/kernel/moleculer/hub-service.d.ts +7 -4
- package/dist/kernel/moleculer/node-registry.d.ts +1 -1
- package/dist/kernel/system-settings-policy.d.ts +7 -9
- package/dist/{retired-settings-keys-iGcmzwLV.mjs → retired-settings-keys-DlZ3VV6x.mjs} +1 -1
- package/dist/{retired-settings-keys-CDyOGAje.js → retired-settings-keys-YOOKLA3v.js} +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as StorageLocationModeSchema, Bt as isLocationEnabled, D as LegacyStorageLocationDefaultSchema, Dt as evictionPolicyOfLocation, Fn as storageMigrationCapability, H as StorageMigrationJobSchema, Hr as secretTreeFromPaths, Kn as withLocationMode, Kr as transformSecretWrite, Pn as storageCapability, Pr as parseJsonObject, Pt as gbToBytes, V as StorageLocationTypeSchema, an as modeMayWrite, jn as settingsStoreCapability, jr as isSecretMarker, lr as BaseAddon, nn as mayWriteToLocation, pr as REDACTED_SECRET, qr as EventCategory, qt as legacyModeOf, tn as mayReadLocation, wn as resolveLocationMode, xr as collectSecretConfigKeys, z as StorageCleanupJobSchema, zr as resolveSecretMarkers } from "../../dist-B9oFw2jh.mjs";
|
|
2
2
|
import { t as HUB_CAP_FWD_ACTION } from "../../hub-cap-forward-CH6_VWVO.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SecretsAccess } from '@camstack/types';
|
|
2
|
+
/** The field key inside the `cluster` section. */
|
|
3
|
+
export declare const CLUSTER_SECRET_FIELD = "clusterSecret";
|
|
4
|
+
export declare const CLUSTER_SECRET_RETRY_MS = 30000;
|
|
5
|
+
interface KeeperLogger {
|
|
6
|
+
info(message: string, meta?: {
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
}): void;
|
|
9
|
+
error(message: string, meta?: {
|
|
10
|
+
meta?: Record<string, unknown>;
|
|
11
|
+
}): void;
|
|
12
|
+
}
|
|
13
|
+
export interface ClusterSecretKeeperDeps {
|
|
14
|
+
/** Keychain handle for owner `system`; `null` without a keychain door. */
|
|
15
|
+
readonly secrets: SecretsAccess | null;
|
|
16
|
+
/** The stored `cluster` section (markers, never values). Throws when unreadable. */
|
|
17
|
+
readonly readSection: () => Promise<Record<string, unknown>>;
|
|
18
|
+
readonly logger: KeeperLogger;
|
|
19
|
+
readonly retryMs?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class ClusterSecretKeeper {
|
|
22
|
+
private readonly deps;
|
|
23
|
+
private retryTimer;
|
|
24
|
+
private unsubscribe;
|
|
25
|
+
private stopped;
|
|
26
|
+
private last;
|
|
27
|
+
constructor(deps: ClusterSecretKeeperDeps);
|
|
28
|
+
start(): Promise<void>;
|
|
29
|
+
stop(): void;
|
|
30
|
+
/** Re-read the row and the keychain; publish the gate. */
|
|
31
|
+
reload(): Promise<void>;
|
|
32
|
+
private compute;
|
|
33
|
+
private report;
|
|
34
|
+
private scheduleRetry;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { ConfigUISchemaWithValues, BaseAddon } from '@camstack/types';
|
|
2
|
+
/** The section id of the cluster form — System → Cluster renders only this one. */
|
|
3
|
+
export declare const SYSTEM_CONFIG_CLUSTER_SECTION_ID = "system-config-cluster";
|
|
2
4
|
export declare class SystemConfigAddon extends BaseAddon {
|
|
3
5
|
readonly id = "system-config";
|
|
6
|
+
private clusterSecretKeeper;
|
|
4
7
|
constructor();
|
|
5
8
|
protected onInitialize(): Promise<void>;
|
|
9
|
+
protected onShutdown(): Promise<void>;
|
|
10
|
+
/** The keychain handle for owner `system`; `null` where there is no door. */
|
|
11
|
+
private systemSecrets;
|
|
6
12
|
private buildGlobalSchema;
|
|
7
13
|
getGlobalSettings(): Promise<ConfigUISchemaWithValues>;
|
|
8
14
|
updateGlobalSettings(patch: Record<string, unknown>): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* The cluster secret's save path: the value goes to the keychain (owner
|
|
17
|
+
* `system`), the row gets the marker; `''` / a view keeps, `$secretClear`
|
|
18
|
+
* clears. A value the keychain cannot store is REFUSED — never written
|
|
19
|
+
* inline (D787, P5).
|
|
20
|
+
*/
|
|
21
|
+
private writeClusterSection;
|
|
9
22
|
}
|
|
10
23
|
export default SystemConfigAddon;
|
|
@@ -3,7 +3,129 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cc6KOdY-.js");
|
|
7
|
+
const require_cluster_secret = require("../../cluster-secret-Dg0OOOSr.js");
|
|
8
|
+
//#region src/builtins/system-config/cluster-secret-keeper.ts
|
|
9
|
+
/**
|
|
10
|
+
* Keeps the hub's cluster-secret gate in step with System → Cluster (D797).
|
|
11
|
+
*
|
|
12
|
+
* The field is a `password` of `system-config`: its `system-settings` row
|
|
13
|
+
* (`cluster.clusterSecret`) holds a keychain marker, the value lives in the
|
|
14
|
+
* keychain (owner `system`). The gate the `$hub.registerNode` handler reads:
|
|
15
|
+
*
|
|
16
|
+
* - no marker → `off` — every node may register, exactly as a hub
|
|
17
|
+
* that never had a secret;
|
|
18
|
+
* - marker + value → `on` — nodes present the matching hash;
|
|
19
|
+
* - marker, no answer → `unknown` — refused, retryably, and retried here.
|
|
20
|
+
* A locked keychain or a value lost to a reset NEVER opens the gate.
|
|
21
|
+
*/
|
|
22
|
+
/** The field key inside the `cluster` section. */
|
|
23
|
+
var CLUSTER_SECRET_FIELD = "clusterSecret";
|
|
24
|
+
var ClusterSecretKeeper = class {
|
|
25
|
+
deps;
|
|
26
|
+
retryTimer = null;
|
|
27
|
+
unsubscribe = null;
|
|
28
|
+
stopped = false;
|
|
29
|
+
last = null;
|
|
30
|
+
constructor(deps) {
|
|
31
|
+
this.deps = deps;
|
|
32
|
+
}
|
|
33
|
+
async start() {
|
|
34
|
+
this.unsubscribe = this.deps.secrets?.onChange((ref) => {
|
|
35
|
+
if (ref === require_cluster_secret.CLUSTER_SECRET_REF) this.reload();
|
|
36
|
+
}) ?? null;
|
|
37
|
+
await this.reload();
|
|
38
|
+
}
|
|
39
|
+
stop() {
|
|
40
|
+
this.stopped = true;
|
|
41
|
+
if (this.retryTimer !== null) clearTimeout(this.retryTimer);
|
|
42
|
+
this.retryTimer = null;
|
|
43
|
+
this.unsubscribe?.();
|
|
44
|
+
this.unsubscribe = null;
|
|
45
|
+
}
|
|
46
|
+
/** Re-read the row and the keychain; publish the gate. */
|
|
47
|
+
async reload() {
|
|
48
|
+
if (this.stopped) return;
|
|
49
|
+
const gate = await this.compute();
|
|
50
|
+
if (this.stopped) return;
|
|
51
|
+
require_cluster_secret.setProcessClusterSecretGate(gate);
|
|
52
|
+
this.report(gate);
|
|
53
|
+
if (gate.kind === "unknown") this.scheduleRetry();
|
|
54
|
+
}
|
|
55
|
+
async compute() {
|
|
56
|
+
let stored;
|
|
57
|
+
try {
|
|
58
|
+
stored = (await this.deps.readSection())[CLUSTER_SECRET_FIELD];
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
kind: "unknown",
|
|
62
|
+
reason: `settings unreadable: ${errText(err)}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (stored === void 0 || stored === null || stored === "") return { kind: "off" };
|
|
66
|
+
if (!require_dist.isSecretMarker(stored)) return {
|
|
67
|
+
kind: "unknown",
|
|
68
|
+
reason: "stored inline, not in the keychain — set it again in System → Cluster"
|
|
69
|
+
};
|
|
70
|
+
const secrets = this.deps.secrets;
|
|
71
|
+
if (secrets === null) return {
|
|
72
|
+
kind: "unknown",
|
|
73
|
+
reason: "no keychain door in this process"
|
|
74
|
+
};
|
|
75
|
+
let entry;
|
|
76
|
+
try {
|
|
77
|
+
entry = (await secrets.resolve([require_cluster_secret.CLUSTER_SECRET_REF]))[require_cluster_secret.CLUSTER_SECRET_REF];
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "unknown",
|
|
81
|
+
reason: errText(err)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (entry === void 0) return {
|
|
85
|
+
kind: "unknown",
|
|
86
|
+
reason: "no-answer"
|
|
87
|
+
};
|
|
88
|
+
switch (entry.kind) {
|
|
89
|
+
case "value": return {
|
|
90
|
+
kind: "on",
|
|
91
|
+
hash: require_cluster_secret.hashClusterSecret(entry.value)
|
|
92
|
+
};
|
|
93
|
+
case "unavailable": return {
|
|
94
|
+
kind: "unknown",
|
|
95
|
+
reason: entry.reason
|
|
96
|
+
};
|
|
97
|
+
case "missing": return {
|
|
98
|
+
kind: "unknown",
|
|
99
|
+
reason: "the stored value is gone — set it again in System → Cluster"
|
|
100
|
+
};
|
|
101
|
+
case "forbidden": return {
|
|
102
|
+
kind: "unknown",
|
|
103
|
+
reason: "forbidden"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
report(gate) {
|
|
108
|
+
const changed = this.last !== gate.kind;
|
|
109
|
+
this.last = gate.kind;
|
|
110
|
+
if (gate.kind === "unknown") {
|
|
111
|
+
this.deps.logger.error("Agents cannot register: a cluster secret is set but cannot be read from the keychain. Registrations are refused until it can (no fallback).", { meta: { reason: gate.reason } });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (changed) this.deps.logger.info(gate.kind === "on" ? "Cluster secret set — agents must present it to register" : "No cluster secret set — any node may register");
|
|
115
|
+
}
|
|
116
|
+
scheduleRetry() {
|
|
117
|
+
if (this.stopped || this.retryTimer !== null) return;
|
|
118
|
+
this.retryTimer = setTimeout(() => {
|
|
119
|
+
this.retryTimer = null;
|
|
120
|
+
this.reload();
|
|
121
|
+
}, this.deps.retryMs ?? 3e4);
|
|
122
|
+
this.retryTimer.unref?.();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function errText(err) {
|
|
126
|
+
return err instanceof Error ? err.message : String(err);
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
7
129
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
8
130
|
/**
|
|
9
131
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -17,6 +139,9 @@ const require_dist = require("../../dist-D_a9mUiM.js");
|
|
|
17
139
|
* Sections exposed — this list is the whole of it:
|
|
18
140
|
* - server (port, host, dataPath — READ-ONLY, loaded from config.yaml at bootstrap)
|
|
19
141
|
* - auth (tokenExpiry)
|
|
142
|
+
* - cluster (clusterSecret — a `password` field: the value goes to the
|
|
143
|
+
* keychain, owner `system`; the row keeps a marker. Also drawn
|
|
144
|
+
* on System → Cluster. D797)
|
|
20
145
|
*
|
|
21
146
|
* Sections NOT exposed (each owned by the addon that implements them):
|
|
22
147
|
* - logging → `winston-logging` addon (level + retention)
|
|
@@ -45,75 +170,112 @@ const require_dist = require("../../dist-D_a9mUiM.js");
|
|
|
45
170
|
/** Section key → user-facing section title. */
|
|
46
171
|
var SECTION_TITLES = {
|
|
47
172
|
server: "Server",
|
|
48
|
-
auth: "Authentication"
|
|
173
|
+
auth: "Authentication",
|
|
174
|
+
cluster: "Cluster"
|
|
49
175
|
};
|
|
176
|
+
/** The keychain owner of the hub's own secrets (D797). */
|
|
177
|
+
var SYSTEM_SECRET_OWNER = "system";
|
|
50
178
|
/** Keys that map 1:1 to a given yml section — used by `updateGlobalSettings`
|
|
51
179
|
* to figure out which section owns each key in the incoming patch. */
|
|
52
180
|
var KEY_TO_SECTION = {
|
|
53
181
|
port: "server",
|
|
54
182
|
host: "server",
|
|
55
183
|
dataPath: "server",
|
|
56
|
-
tokenExpiry: "auth"
|
|
184
|
+
tokenExpiry: "auth",
|
|
185
|
+
[CLUSTER_SECRET_FIELD]: "cluster"
|
|
57
186
|
};
|
|
187
|
+
/** The section id of the cluster form — System → Cluster renders only this one. */
|
|
188
|
+
var SYSTEM_CONFIG_CLUSTER_SECTION_ID = "system-config-cluster";
|
|
58
189
|
var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
59
190
|
id = "system-config";
|
|
191
|
+
clusterSecretKeeper = null;
|
|
60
192
|
constructor() {
|
|
61
193
|
super({});
|
|
62
194
|
}
|
|
63
195
|
async onInitialize() {
|
|
196
|
+
this.clusterSecretKeeper = new ClusterSecretKeeper({
|
|
197
|
+
secrets: this.systemSecrets(),
|
|
198
|
+
readSection: async () => await this.ctx.settings?.getSection("cluster") ?? {},
|
|
199
|
+
logger: this.ctx.logger
|
|
200
|
+
});
|
|
201
|
+
await this.clusterSecretKeeper.start();
|
|
64
202
|
this.ctx.logger.info("Initialized — exposes yml-backed sections via getGlobalSettings");
|
|
65
203
|
}
|
|
204
|
+
async onShutdown() {
|
|
205
|
+
this.clusterSecretKeeper?.stop();
|
|
206
|
+
this.clusterSecretKeeper = null;
|
|
207
|
+
}
|
|
208
|
+
/** The keychain handle for owner `system`; `null` where there is no door. */
|
|
209
|
+
systemSecrets() {
|
|
210
|
+
const forOwner = this.ctx.secrets?.forOwner ?? null;
|
|
211
|
+
return forOwner === null ? null : forOwner(SYSTEM_SECRET_OWNER);
|
|
212
|
+
}
|
|
66
213
|
buildGlobalSchema() {
|
|
67
|
-
return { sections: [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
214
|
+
return { sections: [
|
|
215
|
+
{
|
|
216
|
+
id: "system-config-server",
|
|
217
|
+
title: SECTION_TITLES["server"],
|
|
218
|
+
description: "Core server connection settings — read-only, loaded from config.yaml at bootstrap.",
|
|
219
|
+
columns: 2,
|
|
220
|
+
fields: [
|
|
221
|
+
{
|
|
222
|
+
type: "info",
|
|
223
|
+
key: "server-restart-note",
|
|
224
|
+
label: "Restart required",
|
|
225
|
+
content: "Server settings are read-only. Change them in config.yaml and restart.",
|
|
226
|
+
variant: "warning"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
key: "port",
|
|
231
|
+
label: "Port",
|
|
232
|
+
description: "Listening port",
|
|
233
|
+
disabled: true
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: "text",
|
|
237
|
+
key: "host",
|
|
238
|
+
label: "Host",
|
|
239
|
+
description: "Bind address",
|
|
240
|
+
disabled: true
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
type: "text",
|
|
244
|
+
key: "dataPath",
|
|
245
|
+
label: "Data Path",
|
|
246
|
+
description: "Root data directory",
|
|
247
|
+
disabled: true,
|
|
248
|
+
span: 2
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "system-config-auth",
|
|
254
|
+
title: SECTION_TITLES["auth"],
|
|
255
|
+
description: "Token and session settings.",
|
|
256
|
+
columns: 1,
|
|
257
|
+
fields: [{
|
|
81
258
|
type: "text",
|
|
82
|
-
key: "
|
|
83
|
-
label: "
|
|
84
|
-
description: "
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
type: "
|
|
96
|
-
key:
|
|
97
|
-
label: "
|
|
98
|
-
description: "
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
]
|
|
103
|
-
}, {
|
|
104
|
-
id: "system-config-auth",
|
|
105
|
-
title: SECTION_TITLES["auth"],
|
|
106
|
-
description: "Token and session settings.",
|
|
107
|
-
columns: 1,
|
|
108
|
-
fields: [{
|
|
109
|
-
type: "text",
|
|
110
|
-
key: "tokenExpiry",
|
|
111
|
-
label: "Token Expiry",
|
|
112
|
-
description: "Lifetime of a session token, as a duration string (e.g. 24h, 7d, 30d). A bare number is rejected — it would be read as milliseconds. Applies to tokens issued from the next login or silent refresh; tokens already handed out keep the lifetime they were signed with.",
|
|
113
|
-
placeholder: "30d",
|
|
114
|
-
default: "30d"
|
|
115
|
-
}]
|
|
116
|
-
}] };
|
|
259
|
+
key: "tokenExpiry",
|
|
260
|
+
label: "Token Expiry",
|
|
261
|
+
description: "Lifetime of a session token, as a duration string (e.g. 24h, 7d, 30d). A bare number is rejected — it would be read as milliseconds. Applies to tokens issued from the next login or silent refresh; tokens already handed out keep the lifetime they were signed with.",
|
|
262
|
+
placeholder: "30d",
|
|
263
|
+
default: "30d"
|
|
264
|
+
}]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: SYSTEM_CONFIG_CLUSTER_SECTION_ID,
|
|
268
|
+
title: SECTION_TITLES["cluster"],
|
|
269
|
+
description: "Agents joining this hub. Optional: with no cluster secret, any node that reaches the hub may register.",
|
|
270
|
+
columns: 1,
|
|
271
|
+
fields: [{
|
|
272
|
+
type: "password",
|
|
273
|
+
key: CLUSTER_SECRET_FIELD,
|
|
274
|
+
label: "Cluster secret",
|
|
275
|
+
description: "When set, an agent must present it to register. Stored in the keychain. To pair an agent: Reveal and copy it here, then enter it in the agent at first run (its status page or the desktop setup window). Changing it disconnects agents that still hold the old one at their next registration."
|
|
276
|
+
}]
|
|
277
|
+
}
|
|
278
|
+
] };
|
|
117
279
|
}
|
|
118
280
|
async getGlobalSettings() {
|
|
119
281
|
const schema = this.buildGlobalSchema();
|
|
@@ -130,7 +292,7 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
130
292
|
} });
|
|
131
293
|
throw err;
|
|
132
294
|
}
|
|
133
|
-
return require_dist.hydrateSchema(schema, merged);
|
|
295
|
+
return require_dist.hydrateSchema(schema, merged, { inlineSecretsAsViews: true });
|
|
134
296
|
}
|
|
135
297
|
async updateGlobalSettings(patch) {
|
|
136
298
|
if (!this.ctx.settings) throw new Error("system-config: ctx.settings not available — cannot write sections");
|
|
@@ -153,6 +315,10 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
153
315
|
bucket[key] = value;
|
|
154
316
|
}
|
|
155
317
|
for (const [section, sectionPatch] of perSection) {
|
|
318
|
+
if (section === "cluster") {
|
|
319
|
+
await this.writeClusterSection(sectionPatch);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
156
322
|
await this.ctx.settings.setSection(section, sectionPatch);
|
|
157
323
|
this.ctx.logger.info("Wrote keys to section", { meta: {
|
|
158
324
|
section,
|
|
@@ -160,7 +326,32 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
160
326
|
} });
|
|
161
327
|
}
|
|
162
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* The cluster secret's save path: the value goes to the keychain (owner
|
|
331
|
+
* `system`), the row gets the marker; `''` / a view keeps, `$secretClear`
|
|
332
|
+
* clears. A value the keychain cannot store is REFUSED — never written
|
|
333
|
+
* inline (D787, P5).
|
|
334
|
+
*/
|
|
335
|
+
async writeClusterSection(patch) {
|
|
336
|
+
const settings = this.ctx.settings;
|
|
337
|
+
const secrets = this.systemSecrets();
|
|
338
|
+
if (secrets === null) throw new Error("system-config: no keychain door — the cluster secret cannot be stored");
|
|
339
|
+
const stored = await settings.getSection("cluster");
|
|
340
|
+
const result = await require_dist.transformSecretWrite({
|
|
341
|
+
tree: require_dist.secretTreeFromSchema(this.buildGlobalSchema()),
|
|
342
|
+
secrets,
|
|
343
|
+
patch,
|
|
344
|
+
stored,
|
|
345
|
+
operatorWrite: true
|
|
346
|
+
});
|
|
347
|
+
if (Object.keys(result.patch).length > 0) await settings.setSection("cluster", result.patch);
|
|
348
|
+
const { orphaned } = await result.afterWrite();
|
|
349
|
+
if (orphaned.length > 0) this.ctx.logger.warn("cluster secret cleared, but its old keychain value could not be deleted", { meta: { refs: orphaned } });
|
|
350
|
+
this.ctx.logger.info("Cluster secret updated");
|
|
351
|
+
await this.clusterSecretKeeper?.reload();
|
|
352
|
+
}
|
|
163
353
|
};
|
|
164
354
|
//#endregion
|
|
355
|
+
exports.SYSTEM_CONFIG_CLUSTER_SECTION_ID = SYSTEM_CONFIG_CLUSTER_SECTION_ID;
|
|
165
356
|
exports.SystemConfigAddon = SystemConfigAddon;
|
|
166
357
|
exports.default = SystemConfigAddon;
|