@camstack/server 1.2.566 → 1.2.567
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.
|
@@ -10,7 +10,8 @@ exports.createAgentNodeCertificates = createAgentNodeCertificates;
|
|
|
10
10
|
* leaves it) and keeps it in memory only. When the signed leaf arrives it is persisted
|
|
11
11
|
* into `agent.json` (0600) BESIDE the old one (`meshTlsPrevious`) together with the
|
|
12
12
|
* swap's state (`meshSwap`: which leaf, until when), then the agent reconnects through
|
|
13
|
-
* the same `reconnect()` the UI uses, which
|
|
13
|
+
* the same `reconnect()` the UI uses, which replaces the broker transport from the file
|
|
14
|
+
* (the broker, its services and its child runners stay).
|
|
14
15
|
*
|
|
15
16
|
* A swap is PENDING from install until the hub confirms. Only while it is pending may
|
|
16
17
|
* the node go back, and only to a previous leaf that is still unexpired:
|
package/dist/agent/main.js
CHANGED
|
@@ -213,7 +213,7 @@ async function startAgent(configPath) {
|
|
|
213
213
|
// The HTTP/HTTPS listener on the agent port (D856); set once it is up.
|
|
214
214
|
let agentHttpListener = null;
|
|
215
215
|
console.log(`[Agent] Mesh transport: ${currentMeshTls !== null ? 'tls' : 'clear'}`);
|
|
216
|
-
|
|
216
|
+
const broker = (0, system_1.createBroker)({
|
|
217
217
|
nodeID: config.nodeId,
|
|
218
218
|
mode: 'agent',
|
|
219
219
|
hubAddress: config.hubAddress,
|
|
@@ -223,32 +223,40 @@ async function startAgent(configPath) {
|
|
|
223
223
|
});
|
|
224
224
|
meshJoin.attach(broker);
|
|
225
225
|
/**
|
|
226
|
-
* Reconnect:
|
|
227
|
-
*
|
|
228
|
-
*
|
|
226
|
+
* Reconnect: reload the config from file and replace the broker's TRANSPORT (the new hub
|
|
227
|
+
* address, secret or mesh leaf) IN PLACE. The broker itself is never rebuilt: its services
|
|
228
|
+
* (`$agent`, `$process`, `$agent-cap-fwd`, the event bus, the addon services), its registries and
|
|
229
|
+
* the forked child runners all outlive the wire. Rebuilding the broker stopped `$process`, which
|
|
230
|
+
* SIGTERMed every runner, and left the node capless with no `$agent-cap-fwd` (D856 amendment,
|
|
231
|
+
* 2026-10-15 incident). The HTTP server stays alive throughout.
|
|
229
232
|
*/
|
|
230
233
|
// 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
|
|
234
|
+
// all ask for one, and two interleaved disconnect/connect cycles would leave two wires or none.
|
|
232
235
|
const reconnect = (0, serial_queue_js_1.createSerialQueue)(() => reconnectNow(), {
|
|
233
236
|
deadlineMs: RECONNECT_DEADLINE_MS,
|
|
234
237
|
onTimeout: () => {
|
|
235
|
-
// A
|
|
236
|
-
// the rollback of a failed leaf swap):
|
|
237
|
-
|
|
238
|
+
// A transport whose connect never settles must not hold every later reconnect (including
|
|
239
|
+
// the rollback of a failed leaf swap): drop the wire and move on. The broker, and the
|
|
240
|
+
// runners behind it, stay.
|
|
241
|
+
consoleLogger.error('Reconnect did not finish in time - dropping the half-connected transport', {
|
|
238
242
|
meta: { deadlineMs: RECONNECT_DEADLINE_MS },
|
|
239
243
|
});
|
|
240
|
-
void
|
|
244
|
+
void (0, system_1.disconnectBrokerTransport)(broker).catch(() => undefined);
|
|
241
245
|
},
|
|
242
246
|
});
|
|
243
247
|
// The cycle itself runs through a generation guard: a run the queue abandoned at its deadline
|
|
244
|
-
// that resumes later
|
|
248
|
+
// that resumes later installs nothing (agent-reconnect.ts), so two transports never share a broker.
|
|
245
249
|
let pendingConfig = null;
|
|
246
250
|
const reconnectNow = (0, agent_reconnect_js_1.createReconnectRunner)({
|
|
247
|
-
current: () => broker,
|
|
248
|
-
assign: (next) =>
|
|
249
|
-
|
|
251
|
+
current: () => (0, system_1.currentBrokerTransporter)(broker),
|
|
252
|
+
assign: (next) => (0, system_1.installBrokerTransporter)(broker, next),
|
|
253
|
+
stop: async (target) => {
|
|
254
|
+
// Only the transport installed right now: a late, abandoned run must not tear down a
|
|
255
|
+
// newer run's transport.
|
|
256
|
+
if ((0, system_1.isInstalledTransporter)(broker, target)) {
|
|
257
|
+
await (0, system_1.disconnectBrokerTransport)(broker);
|
|
258
|
+
}
|
|
250
259
|
},
|
|
251
|
-
stop: (target) => target.stop(),
|
|
252
260
|
prepare: () => {
|
|
253
261
|
console.log('[Agent] Reconnecting with updated config...');
|
|
254
262
|
// Reload config from file (UI may have written new hubAddress/secret)
|
|
@@ -274,24 +282,21 @@ async function startAgent(configPath) {
|
|
|
274
282
|
const fresh = pendingConfig;
|
|
275
283
|
if (fresh === null)
|
|
276
284
|
throw new Error('reconnect: no configuration was prepared');
|
|
277
|
-
|
|
285
|
+
return (0, system_1.buildTransporter)((0, system_1.createAgentTransport)({
|
|
278
286
|
nodeID: config.nodeId,
|
|
279
287
|
mode: 'agent',
|
|
280
288
|
hubAddress: fresh.hubAddress,
|
|
281
289
|
logLevel: fresh.logLevel,
|
|
282
|
-
middlewares: meshJoin.middlewares,
|
|
283
290
|
...(currentMeshTls !== null ? { meshTls: currentMeshTls } : {}),
|
|
284
|
-
});
|
|
285
|
-
meshJoin.attach(next);
|
|
286
|
-
return next;
|
|
291
|
+
}));
|
|
287
292
|
},
|
|
288
|
-
// The new
|
|
289
|
-
//
|
|
290
|
-
start: async (
|
|
291
|
-
await
|
|
293
|
+
// The new wire's hub connection runs a join, and its settle re-sends the manifest
|
|
294
|
+
// (`registration.onJoinSettled`): the subtree union, every child included (D3).
|
|
295
|
+
start: async () => {
|
|
296
|
+
await (0, system_1.connectBrokerTransport)(broker);
|
|
292
297
|
console.log('[Agent] Reconnected successfully');
|
|
293
298
|
},
|
|
294
|
-
onSuperseded: (stage) => consoleLogger.warn('A superseded reconnect stopped without
|
|
299
|
+
onSuperseded: (stage) => consoleLogger.warn('A superseded reconnect stopped without installing a second transport', {
|
|
295
300
|
meta: { stage },
|
|
296
301
|
}),
|
|
297
302
|
});
|
|
@@ -461,6 +461,7 @@ function createCertificatesRouter(deps) {
|
|
|
461
461
|
crl: crl(),
|
|
462
462
|
peers,
|
|
463
463
|
registrations,
|
|
464
|
+
...(deps.nodeAddonIds !== undefined ? { expectedAddons: deps.nodeAddonIds } : {}),
|
|
464
465
|
log: deps.log,
|
|
465
466
|
...(deps.reconnectTimeoutMs !== undefined
|
|
466
467
|
? { reconnectTimeoutMs: deps.reconnectTimeoutMs }
|
|
@@ -310,6 +310,7 @@ function buildCapabilityRouters(services) {
|
|
|
310
310
|
registry: services.capabilityRegistry,
|
|
311
311
|
agentRegistry: services.agentRegistry,
|
|
312
312
|
nodeClient: (nodeId) => (0, node_certificates_client_js_1.createNodeCertificatesClient)(services.moleculer, nodeId),
|
|
313
|
+
nodeAddonIds: (nodeId) => services.moleculer.getNodeAddonIds(nodeId),
|
|
313
314
|
hubDataDir: path.resolve(services.configService.get('server.dataPath') ?? 'camstack-data'),
|
|
314
315
|
log: services.loggingService.createLogger('certificates'),
|
|
315
316
|
}),
|
|
@@ -929,9 +929,12 @@ class MoleculerService {
|
|
|
929
929
|
else
|
|
930
930
|
this.nodeOwners.set(params.nodeId, owner);
|
|
931
931
|
this.applyNodeManifest(params, previousManifest, owner, previousOwner);
|
|
932
|
+
// The leaf this node presents NOW is recorded, and a renewal leaf it no longer presents (it
|
|
933
|
+
// reverted to its previous one) is set aside at once.
|
|
934
|
+
this.observePresentedMeshLeaf(params.nodeId);
|
|
932
935
|
// The node's capabilities are routable now: whoever waits for this registration (a mesh
|
|
933
936
|
// leaf renewal confirming the node came back WITH the new leaf) is told, with no polling.
|
|
934
|
-
(0, system_1.emitProcessNodeRegistered)(params.nodeId);
|
|
937
|
+
(0, system_1.emitProcessNodeRegistered)(params.nodeId, params.addons.map((addon) => addon.addonId));
|
|
935
938
|
// Notify AgentRegistryService to reconcile placement for bare-ID
|
|
936
939
|
// agent nodes (no '/' = not a hub child worker, not the hub itself).
|
|
937
940
|
// The handshake is the authoritative completeness signal — the full
|
|
@@ -1299,6 +1302,34 @@ class MoleculerService {
|
|
|
1299
1302
|
isNodeRegistered(nodeId) {
|
|
1300
1303
|
return this.nodeRegistry.getNodeManifest(nodeId) !== undefined;
|
|
1301
1304
|
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Record the mesh leaf `nodeId` presents on its live connection (D856). Fire and forget: a
|
|
1307
|
+
* registry that is not up, or a write that fails, must not fail the node's registration.
|
|
1308
|
+
*/
|
|
1309
|
+
observePresentedMeshLeaf(nodeId) {
|
|
1310
|
+
const registry = (0, system_1.getProcessMeshCertificateRegistry)();
|
|
1311
|
+
const leaf = (0, system_1.getProcessPeerLeaf)(nodeId);
|
|
1312
|
+
if (registry === null || leaf === undefined)
|
|
1313
|
+
return;
|
|
1314
|
+
registry
|
|
1315
|
+
.recordObserved(nodeId, {
|
|
1316
|
+
cn: nodeId,
|
|
1317
|
+
serial: leaf.serial,
|
|
1318
|
+
fingerprint: leaf.fingerprint,
|
|
1319
|
+
notBefore: leaf.notBefore,
|
|
1320
|
+
notAfter: leaf.notAfter,
|
|
1321
|
+
})
|
|
1322
|
+
.catch((error) => {
|
|
1323
|
+
this.logger.warn('Could not record the mesh leaf the node presents', {
|
|
1324
|
+
tags: { nodeId },
|
|
1325
|
+
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
1326
|
+
});
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
/** The addon ids a node's registration holds (its own and its children's); empty when none. */
|
|
1330
|
+
getNodeAddonIds(nodeId) {
|
|
1331
|
+
return (this.nodeRegistry.getNodeManifest(nodeId) ?? []).map((entry) => entry.addonId);
|
|
1332
|
+
}
|
|
1302
1333
|
/**
|
|
1303
1334
|
* The root-package identity a node reported in its `registerNode`
|
|
1304
1335
|
* manifest (runtime-updatable node packages: version visibility), or null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.567",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
+
"@camstack/addon-admin-ui": "1.2.421",
|
|
42
42
|
"@camstack/addon-agent-ui": "1.2.231",
|
|
43
43
|
"@camstack/addon-auth": "1.2.144",
|
|
44
44
|
"@camstack/addon-decoder-nodeav": "1.2.123",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"@camstack/addon-post-analysis": "1.2.369",
|
|
49
49
|
"@camstack/develop": "0.1.8",
|
|
50
50
|
"@camstack/sdk": "1.2.131",
|
|
51
|
-
"@camstack/system": "1.2.
|
|
52
|
-
"@camstack/types": "1.2.
|
|
51
|
+
"@camstack/system": "1.2.431",
|
|
52
|
+
"@camstack/types": "1.2.367",
|
|
53
53
|
"@camstack/ui-library": "1.2.288",
|
|
54
54
|
"@fastify/compress": "^9.0.0",
|
|
55
55
|
"@fastify/cookie": "^11.0.2",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@trpc/server": "^11.16.0",
|
|
61
61
|
"fastify": "^5",
|
|
62
62
|
"js-yaml": "^4",
|
|
63
|
-
"moleculer": "
|
|
63
|
+
"moleculer": "0.15.0",
|
|
64
64
|
"node-av": "^6.0.0",
|
|
65
65
|
"node-pty": "^1.0.0",
|
|
66
66
|
"sharp": "^0.35.2",
|