@camstack/server 1.2.564 → 1.2.565
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/agent/agent-http.js +23 -2
- package/dist/agent/agent-mesh-enroll.js +2 -0
- package/dist/agent/agent-node-certificates.js +294 -0
- package/dist/agent/agent-reconnect.js +34 -0
- package/dist/agent/agent-sniffing-listener.js +263 -0
- package/dist/agent/agent-update-service.js +4 -1
- package/dist/agent/main.js +101 -37
- package/dist/agent/serial-queue.js +49 -0
- package/dist/api/core/auth-device-cert.procedures.js +435 -0
- package/dist/api/core/auth-sessions.procedures.js +20 -2
- package/dist/api/core/auth.router.js +190 -7
- package/dist/api/core/certificates.router.js +505 -0
- package/dist/api/mesh/enroll.routes.js +7 -1
- package/dist/api/trpc/generated-cap-mounts.js +4 -1
- package/dist/api/trpc/generated-cap-routers.js +2006 -1859
- package/dist/api/trpc/trpc.router.js +45 -0
- package/dist/auth/device-enroll-proofs.js +148 -0
- package/dist/auth/hub-sessions.js +2 -0
- package/dist/auth/session-mint.js +24 -2
- package/dist/auth/sso-routes.js +1 -0
- package/dist/core/agent/agent-registry.service.js +7 -0
- package/dist/core/agent/node-certificates-client.js +38 -0
- package/dist/core/auth/handoff-code.service.js +27 -4
- package/dist/core/moleculer/moleculer.service.js +5 -0
- package/package.json +8 -8
package/dist/agent/main.js
CHANGED
|
@@ -50,6 +50,9 @@ const agent_config_js_1 = require("./agent-config.js");
|
|
|
50
50
|
const agent_mesh_enroll_js_1 = require("./agent-mesh-enroll.js");
|
|
51
51
|
const agent_group_runner_js_1 = require("./agent-group-runner.js");
|
|
52
52
|
const agent_http_js_1 = require("./agent-http.js");
|
|
53
|
+
const agent_node_certificates_js_1 = require("./agent-node-certificates.js");
|
|
54
|
+
const agent_reconnect_js_1 = require("./agent-reconnect.js");
|
|
55
|
+
const serial_queue_js_1 = require("./serial-queue.js");
|
|
53
56
|
const agent_infra_capabilities_js_1 = require("./agent-infra-capabilities.js");
|
|
54
57
|
const agent_service_js_1 = require("./agent-service.js");
|
|
55
58
|
const agent_hub_addon_update_js_1 = require("./agent-hub-addon-update.js");
|
|
@@ -65,6 +68,8 @@ const register_agent_cap_dispatch_js_1 = require("./register-agent-cap-dispatch.
|
|
|
65
68
|
// entries flow through it (console output + hub forwarding).
|
|
66
69
|
// ---------------------------------------------------------------------------
|
|
67
70
|
const system_2 = require("@camstack/system");
|
|
71
|
+
/** A reconnect (stop + rebuild + start) that takes longer than this is abandoned. */
|
|
72
|
+
const RECONNECT_DEADLINE_MS = 90_000;
|
|
68
73
|
const agentLogManager = new system_2.LogManager(5000);
|
|
69
74
|
/**
|
|
70
75
|
* Caps whose `nodeId` arg is provider DATA (`nodeIdMode: 'data'` —
|
|
@@ -205,6 +210,8 @@ async function startAgent(configPath) {
|
|
|
205
210
|
// Mesh mTLS (Release A): with a certificate the broker dials the hub's TLS
|
|
206
211
|
// port; without one it keeps dialing clear 6000.
|
|
207
212
|
let currentMeshTls = config.meshTls ?? null;
|
|
213
|
+
// The HTTP/HTTPS listener on the agent port (D856); set once it is up.
|
|
214
|
+
let agentHttpListener = null;
|
|
208
215
|
console.log(`[Agent] Mesh transport: ${currentMeshTls !== null ? 'tls' : 'clear'}`);
|
|
209
216
|
let broker = (0, system_1.createBroker)({
|
|
210
217
|
nodeID: config.nodeId,
|
|
@@ -220,45 +227,74 @@ async function startAgent(configPath) {
|
|
|
220
227
|
* create a new broker with the updated hub/secret, and restart.
|
|
221
228
|
* The HTTP server stays alive throughout.
|
|
222
229
|
*/
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
230
|
+
// ONE reconnect at a time (serial-queue.ts): the UI, a mesh leaf swap and its rollback can
|
|
231
|
+
// all ask for one, and two interleaved stop/rebuild cycles would leave two brokers or none.
|
|
232
|
+
const reconnect = (0, serial_queue_js_1.createSerialQueue)(() => reconnectNow(), {
|
|
233
|
+
deadlineMs: RECONNECT_DEADLINE_MS,
|
|
234
|
+
onTimeout: () => {
|
|
235
|
+
// A broker whose start() never settles must not hold every later reconnect (including
|
|
236
|
+
// the rollback of a failed leaf swap): tear down what was half-built and move on.
|
|
237
|
+
consoleLogger.error('Reconnect did not finish in time - destroying the half-built broker', {
|
|
238
|
+
meta: { deadlineMs: RECONNECT_DEADLINE_MS },
|
|
239
|
+
});
|
|
240
|
+
void broker.stop().catch(() => undefined);
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
// The cycle itself runs through a generation guard: a run the queue abandoned at its deadline
|
|
244
|
+
// that resumes later builds nothing (agent-reconnect.ts), so two brokers never share a node id.
|
|
245
|
+
let pendingConfig = null;
|
|
246
|
+
const reconnectNow = (0, agent_reconnect_js_1.createReconnectRunner)({
|
|
247
|
+
current: () => broker,
|
|
248
|
+
assign: (next) => {
|
|
249
|
+
broker = next;
|
|
250
|
+
},
|
|
251
|
+
stop: (target) => target.stop(),
|
|
252
|
+
prepare: () => {
|
|
253
|
+
console.log('[Agent] Reconnecting with updated config...');
|
|
254
|
+
// Reload config from file (UI may have written new hubAddress/secret)
|
|
255
|
+
const fresh = (0, agent_config_js_1.loadAgentConfig)(undefined, config.dataDir);
|
|
256
|
+
pendingConfig = fresh;
|
|
257
|
+
console.log(`[Agent] New config: hub=${fresh.hubAddress ?? 'none'}, secret=${fresh.secret ? 'yes' : 'none'}, ` +
|
|
258
|
+
`mesh=${fresh.meshTls !== undefined ? 'tls' : 'clear'}`);
|
|
259
|
+
// Re-derive CAMSTACK_HUB_URL when the dashboard has just written a hubAddress. Explicit operator values
|
|
260
|
+
// still win via the boot-captured `hubUrlWasExplicit` flag.
|
|
261
|
+
// KNOWN LIMITATION (Stage-0): runners already spawned before this reconnect
|
|
262
|
+
// keep the env they inherited; picking up a changed hub address in live
|
|
263
|
+
// children would need a runner respawn. Boot-time — the actual trap — is
|
|
264
|
+
// fully covered.
|
|
265
|
+
const freshHubUrl = (0, derive_hub_url_js_1.deriveHubUrlForExport)(hubUrlWasExplicit, fresh.hubAddress);
|
|
266
|
+
if (freshHubUrl !== undefined && process.env['CAMSTACK_HUB_URL'] !== freshHubUrl) {
|
|
267
|
+
process.env['CAMSTACK_HUB_URL'] = freshHubUrl;
|
|
268
|
+
console.log(`[Agent] Derived CAMSTACK_HUB_URL=${freshHubUrl} from hub address "${fresh.hubAddress}"`);
|
|
269
|
+
}
|
|
270
|
+
currentSecret = fresh.secret ?? null;
|
|
271
|
+
currentMeshTls = fresh.meshTls ?? null;
|
|
272
|
+
},
|
|
273
|
+
build: () => {
|
|
274
|
+
const fresh = pendingConfig;
|
|
275
|
+
if (fresh === null)
|
|
276
|
+
throw new Error('reconnect: no configuration was prepared');
|
|
277
|
+
const next = (0, system_1.createBroker)({
|
|
278
|
+
nodeID: config.nodeId,
|
|
279
|
+
mode: 'agent',
|
|
280
|
+
hubAddress: fresh.hubAddress,
|
|
281
|
+
logLevel: fresh.logLevel,
|
|
282
|
+
middlewares: meshJoin.middlewares,
|
|
283
|
+
...(currentMeshTls !== null ? { meshTls: currentMeshTls } : {}),
|
|
284
|
+
});
|
|
285
|
+
meshJoin.attach(next);
|
|
286
|
+
return next;
|
|
287
|
+
},
|
|
257
288
|
// The new broker's hub connection runs a join, and its settle re-sends
|
|
258
289
|
// the manifest (`registration.onJoinSettled`).
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
290
|
+
start: async (next) => {
|
|
291
|
+
await next.start();
|
|
292
|
+
console.log('[Agent] Reconnected successfully');
|
|
293
|
+
},
|
|
294
|
+
onSuperseded: (stage) => consoleLogger.warn('A superseded reconnect stopped without building a second broker', {
|
|
295
|
+
meta: { stage },
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
262
298
|
const loadedAddons = new Map();
|
|
263
299
|
// D3 subtree registry: accumulates manifests from every group-runner child
|
|
264
300
|
// that calls `$agent.registerNode`. The agent re-registers the UNION of its
|
|
@@ -444,6 +480,9 @@ async function startAgent(configPath) {
|
|
|
444
480
|
types_1.audioAnalyzerCapability,
|
|
445
481
|
types_1.platformProbeCapability,
|
|
446
482
|
types_1.serverManagementCapability,
|
|
483
|
+
// Certificates of THIS node, called by the hub (D856): mesh leaf renewal and the
|
|
484
|
+
// hub-issued HTTPS leaf. Provided by the runtime, like `server-management`.
|
|
485
|
+
types_1.nodeCertificatesCapability,
|
|
447
486
|
];
|
|
448
487
|
for (const cap of agentCapabilities) {
|
|
449
488
|
capabilityRegistry.declareCapability(cap);
|
|
@@ -460,6 +499,22 @@ async function startAgent(configPath) {
|
|
|
460
499
|
dataDir: config.dataDir,
|
|
461
500
|
});
|
|
462
501
|
capabilityRegistry.registerProvider('server-management', agent_update_service_js_1.AGENT_RUNTIME_ADDON_ID, (0, server_management_provider_js_1.buildServerManagementProvider)(agentUpdateService));
|
|
502
|
+
// The agent's certificates (D856): mesh leaf swap with automatic rollback, and the
|
|
503
|
+
// hub-issued HTTPS leaf hot-swapped into the sniffing listener. The listener is
|
|
504
|
+
// created later in boot, so it is read through a getter.
|
|
505
|
+
capabilityRegistry.registerProvider('node-certificates', agent_update_service_js_1.AGENT_RUNTIME_ADDON_ID, (0, agent_node_certificates_js_1.createAgentNodeCertificates)({
|
|
506
|
+
nodeId: config.nodeId,
|
|
507
|
+
dataDir: config.dataDir,
|
|
508
|
+
configPath: config.configPath,
|
|
509
|
+
reconnect,
|
|
510
|
+
getHttpsListener: () => agentHttpListener,
|
|
511
|
+
hostFacts: (0, system_2.systemHostFacts)(),
|
|
512
|
+
log: {
|
|
513
|
+
info: (message, extras) => consoleLogger.info(message, extras),
|
|
514
|
+
warn: (message, extras) => consoleLogger.warn(message, extras),
|
|
515
|
+
error: (message, extras) => consoleLogger.error(message, extras),
|
|
516
|
+
},
|
|
517
|
+
}));
|
|
463
518
|
// Boot-health confirmation: on the agent's FIRST acked `$hub.registerNode`,
|
|
464
519
|
// run the single-copy GC sweep once. Under the single-copy model there is NO
|
|
465
520
|
// probation boot, NO N-1 promotion, and NO auto-rollback — the starter
|
|
@@ -838,6 +893,11 @@ async function startAgent(configPath) {
|
|
|
838
893
|
},
|
|
839
894
|
};
|
|
840
895
|
void (0, agent_http_js_1.startAgentHttpServer)(getBrokerFn, {
|
|
896
|
+
log: {
|
|
897
|
+
info: (message, extras) => consoleLogger.info(message, extras),
|
|
898
|
+
warn: (message, extras) => consoleLogger.warn(message, extras),
|
|
899
|
+
error: (message, extras) => consoleLogger.error(message, extras),
|
|
900
|
+
},
|
|
841
901
|
port: config.statusPort ?? 4444,
|
|
842
902
|
nodeId: config.nodeId,
|
|
843
903
|
dataDir: config.dataDir,
|
|
@@ -846,6 +906,8 @@ async function startAgent(configPath) {
|
|
|
846
906
|
getHubConnectionState,
|
|
847
907
|
getMeshTransport: () => (currentMeshTls !== null ? 'tls' : 'clear'),
|
|
848
908
|
updateControls,
|
|
909
|
+
}).then((listener) => {
|
|
910
|
+
agentHttpListener = listener;
|
|
849
911
|
});
|
|
850
912
|
// ── Phase 1: Load core infrastructure addons (in-process) ──
|
|
851
913
|
// storage + settings + metrics + hub-forwarder (log destination)
|
|
@@ -922,6 +984,8 @@ async function startAgent(configPath) {
|
|
|
922
984
|
}
|
|
923
985
|
}
|
|
924
986
|
}
|
|
987
|
+
// Stop accepting new HTTP/HTTPS connections before the broker goes away.
|
|
988
|
+
await agentHttpListener?.close().catch(() => undefined);
|
|
925
989
|
await broker.stop();
|
|
926
990
|
process.exit(0);
|
|
927
991
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SerialQueueTimeoutError = void 0;
|
|
4
|
+
exports.createSerialQueue = createSerialQueue;
|
|
5
|
+
class SerialQueueTimeoutError extends Error {
|
|
6
|
+
deadlineMs;
|
|
7
|
+
constructor(deadlineMs) {
|
|
8
|
+
super(`the job did not finish within ${deadlineMs} ms and was abandoned`);
|
|
9
|
+
this.deadlineMs = deadlineMs;
|
|
10
|
+
this.name = 'SerialQueueTimeoutError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.SerialQueueTimeoutError = SerialQueueTimeoutError;
|
|
14
|
+
function defaultSetTimer(fn, ms) {
|
|
15
|
+
const timer = setTimeout(fn, ms);
|
|
16
|
+
timer.unref?.();
|
|
17
|
+
return { cancel: () => clearTimeout(timer) };
|
|
18
|
+
}
|
|
19
|
+
function createSerialQueue(job, options = {}) {
|
|
20
|
+
let tail = Promise.resolve();
|
|
21
|
+
const setTimer = options.setTimer ?? defaultSetTimer;
|
|
22
|
+
const guarded = () => {
|
|
23
|
+
const deadlineMs = options.deadlineMs;
|
|
24
|
+
if (deadlineMs === undefined)
|
|
25
|
+
return job();
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const timer = setTimer(() => {
|
|
28
|
+
try {
|
|
29
|
+
options.onTimeout?.();
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
reject(new SerialQueueTimeoutError(deadlineMs));
|
|
33
|
+
}
|
|
34
|
+
}, deadlineMs);
|
|
35
|
+
job().then((value) => {
|
|
36
|
+
timer.cancel();
|
|
37
|
+
resolve(value);
|
|
38
|
+
}, (error) => {
|
|
39
|
+
timer.cancel();
|
|
40
|
+
reject(error);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
return () => {
|
|
45
|
+
const run = tail.then(guarded, guarded);
|
|
46
|
+
tail = run.catch(() => undefined);
|
|
47
|
+
return run;
|
|
48
|
+
};
|
|
49
|
+
}
|