@camstack/server 1.2.483 → 1.2.485
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/.env.example +3 -7
- package/dist/agent/agent-config.js +6 -4
- package/dist/agent/agent-http.js +3 -1
- package/dist/agent/main.js +1 -3
- package/dist/api/trpc/generated-cap-routers.js +9 -0
- package/dist/api/trpc/trpc.context.js +5 -2
- package/dist/boot/boot-config.js +46 -46
- package/dist/core/moleculer/moleculer.service.js +6 -13
- package/package.json +9 -9
package/.env.example
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
# CamStack Server Environment
|
|
2
2
|
# Copy to .env and customize. Loaded via Node.js --env-file flag.
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Admin credentials
|
|
9
|
-
CAMSTACK_ADMIN_USER=admin
|
|
10
|
-
CAMSTACK_ADMIN_PASS=changeme
|
|
4
|
+
# No secret is configured through the environment (D797): the session signing
|
|
5
|
+
# key and the cluster secret live in the hub's keychain, and a fresh hub seeds
|
|
6
|
+
# the admin account admin / changeme (the first sign-in forces a change).
|
|
11
7
|
|
|
12
8
|
# Data directory (default: camstack-data)
|
|
13
9
|
# CAMSTACK_DATA=camstack-data
|
|
@@ -73,7 +73,8 @@ function ensurePersistedNodeId(configPath, dataDir) {
|
|
|
73
73
|
raw.nodeId = id;
|
|
74
74
|
try {
|
|
75
75
|
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
76
|
-
|
|
76
|
+
// 0600: the same file holds the cluster secret.
|
|
77
|
+
fs.writeFileSync(resolvedPath, JSON.stringify(raw, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
77
78
|
}
|
|
78
79
|
catch {
|
|
79
80
|
/* best-effort */
|
|
@@ -89,9 +90,10 @@ function loadAgentConfig(configPath, dataDirOverride) {
|
|
|
89
90
|
const envName = process.env.CAMSTACK_NODE_ID ??
|
|
90
91
|
process.env.CAMSTACK_AGENT_NAME ??
|
|
91
92
|
`${os.hostname()}-${os.arch()}`;
|
|
92
|
-
// Environment
|
|
93
|
+
// Environment variable for the hub connection (optional — agent starts
|
|
94
|
+
// without). The cluster secret is NOT one of them (D797): it lives only in
|
|
95
|
+
// agent.json.
|
|
93
96
|
const envHubAddress = process.env.CAMSTACK_HUB_ADDRESS || undefined;
|
|
94
|
-
const envSecret = process.env.CAMSTACK_CLUSTER_SECRET || undefined;
|
|
95
97
|
const configFullPath = path.resolve(resolvedDataDir, filePath);
|
|
96
98
|
const nodeId = ensurePersistedNodeId(filePath, resolvedDataDir);
|
|
97
99
|
// Read persisted config — these override env vars
|
|
@@ -112,7 +114,7 @@ function loadAgentConfig(configPath, dataDirOverride) {
|
|
|
112
114
|
// Config file wins over env for all user-editable fields
|
|
113
115
|
const effectiveName = fileName ?? envName;
|
|
114
116
|
const effectiveHub = fileHubAddress ?? envHubAddress;
|
|
115
|
-
const effectiveSecret = fileSecret
|
|
117
|
+
const effectiveSecret = fileSecret;
|
|
116
118
|
return {
|
|
117
119
|
nodeId,
|
|
118
120
|
name: effectiveName,
|
package/dist/agent/agent-http.js
CHANGED
|
@@ -151,9 +151,11 @@ function readConfigFile(configPath) {
|
|
|
151
151
|
return {};
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
/** agent.json holds the cluster secret (D797): always 0600. */
|
|
154
155
|
function writeConfigFile(configPath, data) {
|
|
155
156
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
156
|
-
fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
157
|
+
fs.writeFileSync(configPath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
158
|
+
fs.chmodSync(configPath, 0o600);
|
|
157
159
|
}
|
|
158
160
|
/** Live Moleculer registry snapshot — reused by agent-bootstrap's Option A. */
|
|
159
161
|
function getRegistryNodes(broker) {
|
package/dist/agent/main.js
CHANGED
|
@@ -149,7 +149,6 @@ async function startAgent(configPath) {
|
|
|
149
149
|
mode: 'agent',
|
|
150
150
|
hubAddress: config.hubAddress,
|
|
151
151
|
logLevel: config.logLevel,
|
|
152
|
-
secret: config.secret,
|
|
153
152
|
});
|
|
154
153
|
/**
|
|
155
154
|
* Reconnect: stop the current broker, reload config from file,
|
|
@@ -184,7 +183,6 @@ async function startAgent(configPath) {
|
|
|
184
183
|
mode: 'agent',
|
|
185
184
|
hubAddress: fresh.hubAddress,
|
|
186
185
|
logLevel: fresh.logLevel,
|
|
187
|
-
secret: fresh.secret,
|
|
188
186
|
});
|
|
189
187
|
await broker.start();
|
|
190
188
|
console.log('[Agent] Reconnected successfully');
|
|
@@ -327,7 +325,7 @@ async function startAgent(configPath) {
|
|
|
327
325
|
})
|
|
328
326
|
.catch((err) => {
|
|
329
327
|
if ((0, system_1.isClusterSecretMismatchError)(err)) {
|
|
330
|
-
consoleLogger.error('hub registration rejected: cluster secret mismatch —
|
|
328
|
+
consoleLogger.error('hub registration rejected: cluster secret mismatch — enter the hub\'s cluster secret (System → Cluster) in this agent\'s setup (agent.json) and restart the agent');
|
|
331
329
|
// Surface the mismatch in the UI status.
|
|
332
330
|
hubConnectionStateOverride = 'secret-mismatch';
|
|
333
331
|
return;
|
|
@@ -4522,6 +4522,15 @@ function createCapRouter_keychainAdmin(getProvider, createRemoteProxy) {
|
|
|
4522
4522
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4523
4523
|
return p.reveal(methodInput);
|
|
4524
4524
|
}),
|
|
4525
|
+
setSystemSecret: trpc_middleware_js_1.adminProcedure
|
|
4526
|
+
.input(types_62.keychainAdminCapability.methods.setSystemSecret.input.loose())
|
|
4527
|
+
.output(types_62.keychainAdminCapability.methods.setSystemSecret.output)
|
|
4528
|
+
.mutation(async ({ input, ctx }) => {
|
|
4529
|
+
const { nodeId, ...methodInput } = input;
|
|
4530
|
+
const p = resolveProvider('keychain-admin', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4531
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4532
|
+
return p.setSystemSecret(methodInput);
|
|
4533
|
+
}),
|
|
4525
4534
|
deleteOrphans: trpc_middleware_js_1.adminProcedure
|
|
4526
4535
|
.input(types_62.keychainAdminCapability.methods.deleteOrphans.input.loose())
|
|
4527
4536
|
.output(types_62.keychainAdminCapability.methods.deleteOrphans.output)
|
|
@@ -282,8 +282,11 @@ function makeDeviceScopeLookup(addonRegistry) {
|
|
|
282
282
|
* Context factory for hub-internal calls originating from the trusted
|
|
283
283
|
* Moleculer mesh (the `$core-caps` bridge service in `core-cap-bridge.ts`).
|
|
284
284
|
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
285
|
+
* A mesh-originated call is treated as a fully-trusted admin. NOTE (D797):
|
|
286
|
+
* the cluster secret (System → Cluster) gates `$hub.registerNode` only — the
|
|
287
|
+
* Moleculer TCP transport itself authenticates no peer, so anything that can
|
|
288
|
+
* reach the hub's mesh port can call this. Keep that port off untrusted
|
|
289
|
+
* networks; transport-level peer authentication is on the roadmap. It carries
|
|
287
290
|
* a synthetic `isAdmin` user, which makes `protectedProcedure` /
|
|
288
291
|
* `adminProcedure` pass without a JWT and skips the scope-access
|
|
289
292
|
* matcher entirely. There is no HTTP request behind the call.
|
package/dist/boot/boot-config.js
CHANGED
|
@@ -39,10 +39,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.ignoredTlsCertSources = ignoredTlsCertSources;
|
|
42
|
+
exports.retiredSecretSources = retiredSecretSources;
|
|
42
43
|
exports.loadBootstrapConfig = loadBootstrapConfig;
|
|
43
|
-
exports.autoGenerateJwtSecret = autoGenerateJwtSecret;
|
|
44
44
|
exports.setupInfra = setupInfra;
|
|
45
|
-
const node_crypto_1 = require("node:crypto");
|
|
46
45
|
const fs = __importStar(require("node:fs"));
|
|
47
46
|
const path = __importStar(require("node:path"));
|
|
48
47
|
const types_1 = require("@camstack/types");
|
|
@@ -54,21 +53,34 @@ const storage_location_manager_1 = require("../core/storage/storage-location-man
|
|
|
54
53
|
// ---------------------------------------------------------------------------
|
|
55
54
|
const CONFIG_DEFAULTS = {
|
|
56
55
|
server: { port: 4443, host: '0.0.0.0', dataPath: 'camstack-data' },
|
|
57
|
-
auth: {
|
|
58
|
-
jwtSecret: null,
|
|
59
|
-
adminUsername: 'admin',
|
|
60
|
-
adminPassword: 'changeme',
|
|
61
|
-
},
|
|
62
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Bootstrap env overrides. No SECRET is among them (D783, D797): the session
|
|
59
|
+
* signing key and the cluster secret live in the keychain, and the first-boot
|
|
60
|
+
* admin is fixed (`admin` / `changeme`).
|
|
61
|
+
*/
|
|
63
62
|
const ENV_VAR_MAP = {
|
|
64
63
|
CAMSTACK_PORT: 'server.port',
|
|
65
64
|
CAMSTACK_HOST: 'server.host',
|
|
66
65
|
CAMSTACK_DATA: 'server.dataPath',
|
|
67
|
-
CAMSTACK_JWT_SECRET: 'auth.jwtSecret',
|
|
68
|
-
CAMSTACK_ADMIN_USER: 'auth.adminUsername',
|
|
69
|
-
CAMSTACK_ADMIN_PASS: 'auth.adminPassword',
|
|
70
66
|
CAMSTACK_TLS_ENABLED: 'tls.enabled',
|
|
71
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Env variables that once carried a secret into the hub. Ignored since D797 —
|
|
70
|
+
* still named so a container template that sets them is told, not silently
|
|
71
|
+
* obeyed or silently dropped. Only their PRESENCE is read, never the value.
|
|
72
|
+
*/
|
|
73
|
+
const RETIRED_SECRET_ENV = [
|
|
74
|
+
'CAMSTACK_JWT_SECRET',
|
|
75
|
+
'CAMSTACK_ADMIN_USER',
|
|
76
|
+
'CAMSTACK_ADMIN_PASS',
|
|
77
|
+
'CAMSTACK_CLUSTER_SECRET',
|
|
78
|
+
'CAMSTACK_SECRET_KEY',
|
|
79
|
+
'HF_TOKEN',
|
|
80
|
+
'HUGGING_FACE_HUB_TOKEN',
|
|
81
|
+
];
|
|
82
|
+
/** `config.yaml` `auth.*` keys that once held a credential — ignored too (D797). */
|
|
83
|
+
const RETIRED_AUTH_KEYS = ['jwtSecret', 'adminUsername', 'adminPassword'];
|
|
72
84
|
/**
|
|
73
85
|
* Env variables that once set the node's certificate. Ignored since D726:
|
|
74
86
|
* the admin UI (Network → Local access) is the one way to set it. Still
|
|
@@ -103,6 +115,19 @@ function ignoredTlsCertSources(env, raw) {
|
|
|
103
115
|
const fromYaml = IGNORED_TLS_CERT_KEYS.filter((key) => tls[key] !== undefined).map((key) => `tls.${key}`);
|
|
104
116
|
return [...fromEnv, ...fromYaml];
|
|
105
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Every retired secret source still present: env variables set (non-blank) and
|
|
120
|
+
* `config.yaml` `auth.*` keys. Names only — a value is never read into a log.
|
|
121
|
+
*/
|
|
122
|
+
function retiredSecretSources(env, raw) {
|
|
123
|
+
const fromEnv = RETIRED_SECRET_ENV.filter((name) => {
|
|
124
|
+
const value = env[name];
|
|
125
|
+
return value !== undefined && value !== '';
|
|
126
|
+
});
|
|
127
|
+
const auth = (0, types_1.asJsonObject)(raw['auth']) ?? {};
|
|
128
|
+
const fromYaml = RETIRED_AUTH_KEYS.filter((key) => auth[key] !== undefined && auth[key] !== null).map((key) => `auth.${key}`);
|
|
129
|
+
return [...fromEnv, ...fromYaml];
|
|
130
|
+
}
|
|
106
131
|
// ---------------------------------------------------------------------------
|
|
107
132
|
// loadBootstrapConfig
|
|
108
133
|
// ---------------------------------------------------------------------------
|
|
@@ -166,6 +191,13 @@ function loadBootstrapConfig(configPath) {
|
|
|
166
191
|
raw = setNested(raw, configPath_, coerced);
|
|
167
192
|
console.log(`[Phase1] Env override: ${envKey} → ${configPath_}`);
|
|
168
193
|
}
|
|
194
|
+
const retired = retiredSecretSources(process.env, raw);
|
|
195
|
+
if (retired.length > 0) {
|
|
196
|
+
console.warn(`[Phase1] WARN ${retired.join(', ')} ignored — no secret is read from the environment or ` +
|
|
197
|
+
'config.yaml any more (D797). The session signing key and the cluster secret live in the ' +
|
|
198
|
+
'keychain (System → Cluster for the cluster secret); a fresh hub seeds admin / changeme. ' +
|
|
199
|
+
'Remove these entries — after importing their values with scripts/ops/keychain-migrate-hub.mjs.');
|
|
200
|
+
}
|
|
169
201
|
const ignored = ignoredTlsCertSources(process.env, raw);
|
|
170
202
|
if (ignored.length > 0) {
|
|
171
203
|
console.warn(`[Phase1] WARN ${ignored.join(', ')} ignored — the certificate is set only in the admin UI. ` +
|
|
@@ -174,48 +206,16 @@ function loadBootstrapConfig(configPath) {
|
|
|
174
206
|
return config_schema_1.bootstrapSchema.parse(raw);
|
|
175
207
|
}
|
|
176
208
|
// ---------------------------------------------------------------------------
|
|
177
|
-
// autoGenerateJwtSecret
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
/**
|
|
180
|
-
* If `jwtSecret` is null in the parsed config, generate a random secret,
|
|
181
|
-
* persist it to the YAML file, and return an updated config.
|
|
182
|
-
*/
|
|
183
|
-
function autoGenerateJwtSecret(configPath, bootstrapConfig) {
|
|
184
|
-
if (bootstrapConfig.auth.jwtSecret !== null) {
|
|
185
|
-
return bootstrapConfig;
|
|
186
|
-
}
|
|
187
|
-
const secret = (0, node_crypto_1.randomBytes)(32).toString('hex');
|
|
188
|
-
console.log('[Phase1] jwtSecret is null — auto-generating and writing to config.yaml');
|
|
189
|
-
let raw = {};
|
|
190
|
-
if (fs.existsSync(configPath)) {
|
|
191
|
-
raw = (0, types_1.asJsonObject)(yaml.load(fs.readFileSync(configPath, 'utf-8'))) ?? {};
|
|
192
|
-
}
|
|
193
|
-
const authSection = (0, types_1.asJsonObject)(raw.auth) ?? {};
|
|
194
|
-
raw.auth = { ...authSection, jwtSecret: secret };
|
|
195
|
-
const tmpPath = `${configPath}.tmp`;
|
|
196
|
-
try {
|
|
197
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
198
|
-
fs.writeFileSync(tmpPath, yaml.dump(raw, { lineWidth: 120, indent: 2, quotingType: '"' }), 'utf-8');
|
|
199
|
-
fs.renameSync(tmpPath, configPath);
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
console.warn('[Phase1] Could not write auto-generated jwtSecret to config.yaml:', err);
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
...bootstrapConfig,
|
|
206
|
-
auth: { ...bootstrapConfig.auth, jwtSecret: secret },
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
209
|
// setupInfra
|
|
211
210
|
// ---------------------------------------------------------------------------
|
|
212
211
|
/**
|
|
213
212
|
* Phase 2: Create StorageLocationManager, ensure directories, configure TLS.
|
|
214
213
|
* Returns the full InfraContext needed by subsequent boot phases.
|
|
215
214
|
*/
|
|
216
|
-
async function setupInfra(
|
|
217
|
-
//
|
|
218
|
-
|
|
215
|
+
async function setupInfra(_configPath, bootstrapConfig) {
|
|
216
|
+
// No secret is generated here: the session signing key is the keychain's
|
|
217
|
+
// (local-auth loads or creates it once the keychain is up — D797).
|
|
218
|
+
const config = bootstrapConfig;
|
|
219
219
|
const dataPath = path.resolve(config.server.dataPath);
|
|
220
220
|
const port = config.server.port;
|
|
221
221
|
const host = config.server.host;
|
|
@@ -85,13 +85,6 @@ class MoleculerService {
|
|
|
85
85
|
* hub consumers share the same snapshot.
|
|
86
86
|
*/
|
|
87
87
|
readinessRegistry;
|
|
88
|
-
/**
|
|
89
|
-
* Resolved cluster secret (`CAMSTACK_CLUSTER_SECRET` env, else
|
|
90
|
-
* `cluster.secret` config), or `undefined` when none is configured.
|
|
91
|
-
* Threaded both into the broker factory and the hub's
|
|
92
|
-
* `expectedClusterSecretHash` so `$hub.registerNode` can gate on it.
|
|
93
|
-
*/
|
|
94
|
-
clusterSecret;
|
|
95
88
|
/**
|
|
96
89
|
* UDS server that listens for addon-runners spawned by this hub node.
|
|
97
90
|
* `null` when the UDS server failed to start (children run broker-only).
|
|
@@ -193,13 +186,14 @@ class MoleculerService {
|
|
|
193
186
|
// moleculer→eventemitter2 whose types are unresolvable at this
|
|
194
187
|
// boundary, so the inference falls to `error` and trips
|
|
195
188
|
// `no-unsafe-assignment`. Going via `unknown` documents the boundary.
|
|
196
|
-
|
|
197
|
-
|
|
189
|
+
// No cluster secret here (D797): the broker is created before the keychain
|
|
190
|
+
// can answer, and the Moleculer namespace was never authentication (TCP
|
|
191
|
+
// ignores it; UDP discovery broadcasts it in clear). The secret gates
|
|
192
|
+
// `$hub.registerNode`, read from the keychain at call time.
|
|
198
193
|
const broker = (0, system_1.createBroker)({
|
|
199
194
|
nodeID: 'hub',
|
|
200
195
|
mode: 'hub',
|
|
201
196
|
logLevel: this.config.get('moleculer.logLevel') ?? 'warn',
|
|
202
|
-
secret: this.clusterSecret,
|
|
203
197
|
...(tcpPort && !Number.isNaN(tcpPort) ? { tcpPort } : {}),
|
|
204
198
|
...(udpPort && !Number.isNaN(udpPort) ? { udpPort } : {}),
|
|
205
199
|
});
|
|
@@ -274,9 +268,8 @@ class MoleculerService {
|
|
|
274
268
|
onUnregisterNode: (nodeId) => {
|
|
275
269
|
this.removeNodeFromRegistry(nodeId);
|
|
276
270
|
},
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
: undefined,
|
|
271
|
+
// System → Cluster, from the keychain (D797): off / on / unknown.
|
|
272
|
+
clusterSecretGate: system_1.getProcessClusterSecretGate,
|
|
280
273
|
};
|
|
281
274
|
const hubService = (0, system_1.createHubService)(hubDeps);
|
|
282
275
|
this.brokerSafe.createService(hubService);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.485",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
42
|
-
"@camstack/addon-auth": "1.2.
|
|
40
|
+
"@camstack/addon-admin-ui": "1.2.358",
|
|
41
|
+
"@camstack/addon-agent-ui": "1.2.194",
|
|
42
|
+
"@camstack/addon-auth": "1.2.133",
|
|
43
43
|
"@camstack/addon-decoder-nodeav": "1.2.121",
|
|
44
44
|
"@camstack/addon-notifiers": "1.2.128",
|
|
45
45
|
"@camstack/addon-pipeline": "1.2.346",
|
|
46
46
|
"@camstack/addon-pipeline-orchestrator": "1.2.298",
|
|
47
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
48
|
-
"@camstack/sdk": "1.2.
|
|
49
|
-
"@camstack/system": "1.2.
|
|
50
|
-
"@camstack/types": "1.2.
|
|
51
|
-
"@camstack/ui-library": "1.2.
|
|
47
|
+
"@camstack/addon-post-analysis": "1.2.346",
|
|
48
|
+
"@camstack/sdk": "1.2.127",
|
|
49
|
+
"@camstack/system": "1.2.380",
|
|
50
|
+
"@camstack/types": "1.2.331",
|
|
51
|
+
"@camstack/ui-library": "1.2.252",
|
|
52
52
|
"@fastify/compress": "^9.0.0",
|
|
53
53
|
"@fastify/cookie": "^11.0.2",
|
|
54
54
|
"@fastify/cors": "^11.2.0",
|