@camstack/server 1.2.688 → 1.2.690
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/CHANGELOG.md +23 -0
- package/dist/agent/agent-heap-watch.js +38 -0
- package/dist/agent/agent-service.js +8 -3
- package/dist/agent/main.js +7 -1
- package/dist/api/core/cap-providers.js +5 -3
- package/dist/api/core/cluster-nodes.router.js +3 -3
- package/dist/api/core/server-management.provider.js +14 -1
- package/dist/api/trpc/generated-cap-mounts.js +4 -1
- package/dist/api/trpc/generated-cap-routers.js +1270 -1134
- package/dist/api/trpc/trpc.router.js +1 -1
- package/dist/core/agent/agent-registry.service.js +30 -22
- package/dist/core/moleculer/moleculer.service.js +8 -0
- package/dist/launch-args.js +255 -0
- package/package.json +9 -8
|
@@ -254,7 +254,7 @@ function buildServerProviders(services) {
|
|
|
254
254
|
integrations: wrap(() => (0, cap_providers_js_1.buildIntegrationsProvider)(services.addonRegistry, services.eventBus, services.loggingService, services.capabilityRegistry)),
|
|
255
255
|
nodes: wrap(() => (0, cap_providers_js_1.buildNodesProvider)(services.agentRegistry, services.moleculer, services.addonRegistry, (0, cap_providers_js_1.createNodeRootPackageLookup)(services.moleculer, services.serverUpdateService), services.loggingService.createLogger('nodes'))),
|
|
256
256
|
// server-management — runtime-updatable root package (phase 1: hub only).
|
|
257
|
-
'server-management': wrap(() => (0, server_management_provider_js_1.buildServerManagementProvider)(services.serverUpdateService)),
|
|
257
|
+
'server-management': wrap(() => (0, server_management_provider_js_1.buildServerManagementProvider)(services.serverUpdateService, path.resolve(process.env['CAMSTACK_DATA'] ?? 'camstack-data'))),
|
|
258
258
|
addons: wrap((ctx) => (0, cap_providers_js_1.buildAddonsProvider)(services.addonRegistry, services.addonPackageService, services.loggingService, services.moleculer, services.configService, ctx, services.agentRegistry)),
|
|
259
259
|
// webrtc-session — the resolved broker singleton, wrapped per request to
|
|
260
260
|
// inject the server-read User-Agent into signaling attribution (the broker
|
|
@@ -418,27 +418,17 @@ class AgentRegistryService {
|
|
|
418
418
|
* nothing re-announces it, and the operator has no reason to wait for a
|
|
419
419
|
* timer they cannot see.
|
|
420
420
|
*
|
|
421
|
-
* An ONLINE node is
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
421
|
+
* An ONLINE node is forgotten too (operator ruling 2026-10-29, D966). The old refusal said a
|
|
422
|
+
* live node re-registers at once; since D857 its leaf is on the CRL, so it cannot. The order
|
|
423
|
+
* matters: (1) revoke (the revoker also closes the node's mutual-TLS sockets, because the CRL is
|
|
424
|
+
* only read at a handshake), (2) `disconnected` in the catalog, which unregisters its services and
|
|
425
|
+
* rejects the cap calls still waiting on it with `RequestRejectedError` - a bare `nodes.delete`
|
|
426
|
+
* would leave both behind, (3) the delete and the rest of the cleanup. The agent finds out on
|
|
427
|
+
* its own: refused at the next handshake, it asks the hub (`agent-membership-watch`) and resets
|
|
428
|
+
* to unpaired after two CA-signed `revoked` answers. `stillListed` is read back at the end, so a
|
|
429
|
+
* node that came straight back (revocation failed) is reported as listed, never as forgotten.
|
|
425
430
|
*/
|
|
426
431
|
async forgetClusterNode(nodeId) {
|
|
427
|
-
if (this.isNodeOnline(nodeId)) {
|
|
428
|
-
const message = `Refused: ${nodeId} is ONLINE. Take the node down first — a live node re-registers immediately.`;
|
|
429
|
-
this.logger?.warn('forget cluster node refused', {
|
|
430
|
-
tags: { nodeId },
|
|
431
|
-
meta: { reason: 'node-online' },
|
|
432
|
-
});
|
|
433
|
-
return {
|
|
434
|
-
forgotten: false,
|
|
435
|
-
historyRemoved: false,
|
|
436
|
-
registryRemoved: false,
|
|
437
|
-
stillListed: true,
|
|
438
|
-
certificates: { outcome: 'not-attempted' },
|
|
439
|
-
message,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
432
|
// First: a forgotten node whose leaf stays valid walks back in on its next boot (D857).
|
|
443
433
|
const certificates = await this.revokeForgottenNodeCertificates(nodeId);
|
|
444
434
|
const historyOutcome = this.historyStore
|
|
@@ -446,6 +436,8 @@ class AgentRegistryService {
|
|
|
446
436
|
: 'store-unavailable';
|
|
447
437
|
// The mirror still lists the forgotten node: the next directory re-reads.
|
|
448
438
|
this.historyMirror?.invalidate();
|
|
439
|
+
if (this.isNodeOnline(nodeId))
|
|
440
|
+
this.disconnectFromNodeCatalog(nodeId);
|
|
449
441
|
const registryRemoved = this.removeFromNodeCatalog(nodeId);
|
|
450
442
|
// Its intended set is gone with the row, so the back-fill breaker's memory
|
|
451
443
|
// of failed deliveries to it is meaningless — drop it too.
|
|
@@ -499,6 +491,20 @@ class AgentRegistryService {
|
|
|
499
491
|
message,
|
|
500
492
|
};
|
|
501
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* Take a LIVE node out of routing the way Moleculer does when its transport drops: services
|
|
496
|
+
* unregistered, pending requests rejected (typed, not left to time out). Idempotent.
|
|
497
|
+
*/
|
|
498
|
+
disconnectFromNodeCatalog(nodeId) {
|
|
499
|
+
const nodes = this.nodeCatalogRegistry()?.nodes;
|
|
500
|
+
if (nodes?.disconnected === undefined) {
|
|
501
|
+
this.logger?.warn('cluster node catalog does not expose disconnected - calls may hang', {
|
|
502
|
+
tags: { nodeId },
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
nodes.disconnected(nodeId, false);
|
|
507
|
+
}
|
|
502
508
|
/** Is the node currently AVAILABLE in Moleculer's catalog? */
|
|
503
509
|
isNodeOnline(nodeId) {
|
|
504
510
|
return this.catalogNodeIds({ onlyAvailable: true }).includes(nodeId);
|
|
@@ -1173,7 +1179,8 @@ class AgentRegistryService {
|
|
|
1173
1179
|
status: {
|
|
1174
1180
|
activeCameras: 0,
|
|
1175
1181
|
cpuPercent: status.cpuPercent ?? 0,
|
|
1176
|
-
memoryPercent: status.memoryPercent
|
|
1182
|
+
memoryPercent: typeof status.memoryPercent === 'number' ? status.memoryPercent : null,
|
|
1183
|
+
processRssBytes: typeof status.processRssBytes === 'number' ? status.processRssBytes : null,
|
|
1177
1184
|
fps: {},
|
|
1178
1185
|
errors: [],
|
|
1179
1186
|
},
|
|
@@ -1237,7 +1244,7 @@ class AgentRegistryService {
|
|
|
1237
1244
|
status: {
|
|
1238
1245
|
activeCameras: 0,
|
|
1239
1246
|
cpuPercent: 0,
|
|
1240
|
-
memoryPercent:
|
|
1247
|
+
memoryPercent: null,
|
|
1241
1248
|
fps: {},
|
|
1242
1249
|
errors: [],
|
|
1243
1250
|
},
|
|
@@ -1254,7 +1261,7 @@ class AgentRegistryService {
|
|
|
1254
1261
|
// The cap contract only exposes async snapshots; the cached read is cheap
|
|
1255
1262
|
// because the addon's background sampler keeps the snapshot warm.
|
|
1256
1263
|
let cpuPercent = 0;
|
|
1257
|
-
let memoryPercent =
|
|
1264
|
+
let memoryPercent = null;
|
|
1258
1265
|
const registry = this.capabilityService.getRegistry();
|
|
1259
1266
|
if (registry) {
|
|
1260
1267
|
const metricsProvider = registry.getSingleton('metrics-provider');
|
|
@@ -1283,6 +1290,7 @@ class AgentRegistryService {
|
|
|
1283
1290
|
activeCameras: 0,
|
|
1284
1291
|
cpuPercent,
|
|
1285
1292
|
memoryPercent,
|
|
1293
|
+
processRssBytes: process.memoryUsage().rss,
|
|
1286
1294
|
fps: {},
|
|
1287
1295
|
errors: [],
|
|
1288
1296
|
},
|
|
@@ -759,6 +759,14 @@ class MoleculerService {
|
|
|
759
759
|
// agents (non-retaining nodes).
|
|
760
760
|
const hubChildRegistry = this.localChildRegistry;
|
|
761
761
|
(0, system_1.setNodeEventInterest)(this.broker, () => hubChildRegistry.aggregateEventInterest());
|
|
762
|
+
// Producer-side suppression (D8): tell each forked child which categories
|
|
763
|
+
// someone else wants — siblings plus the hub's own in-process subscribers
|
|
764
|
+
// (the admin live stream is one) — so a high-frequency category nobody
|
|
765
|
+
// wants never leaves its process. Hub only: an agent cannot see the hub's
|
|
766
|
+
// subscribers, so its children keep sending everything. A remote node's
|
|
767
|
+
// interest in a ring-denied category is NOT visible here (ADR 0969).
|
|
768
|
+
hubChildRegistry.setEventInterestSource(() => (0, system_1.getLocalEventInterest)(this.broker));
|
|
769
|
+
(0, system_1.onLocalEventInterestChange)(this.broker, () => hubChildRegistry.refreshEventInterest());
|
|
762
770
|
}
|
|
763
771
|
}
|
|
764
772
|
/**
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ALLOCATOR_ENV_DEFAULTS = exports.OVERRIDE_MAX_MB = exports.OVERRIDE_MIN_MB = exports.AGENT_HEAP_CAP_MB = exports.AGENT_HEAP_FLOOR_MB = exports.HEAP_CEILING_SOURCE_ENV = exports.LAUNCH_OVERRIDES_FILE = void 0;
|
|
37
|
+
exports.computedAgentHeapMb = computedAgentHeapMb;
|
|
38
|
+
exports.planLaunch = planLaunch;
|
|
39
|
+
exports.renderLaunchPlan = renderLaunchPlan;
|
|
40
|
+
exports.overridesPath = overridesPath;
|
|
41
|
+
exports.readHeapOverrideMb = readHeapOverrideMb;
|
|
42
|
+
exports.writeHeapOverrideMb = writeHeapOverrideMb;
|
|
43
|
+
exports.readCgroupLimitBytes = readCgroupLimitBytes;
|
|
44
|
+
exports.runLaunchCli = runLaunchCli;
|
|
45
|
+
/**
|
|
46
|
+
* NODE LAUNCH ARGUMENTS — decided by the server closure, executed by the image.
|
|
47
|
+
*
|
|
48
|
+
* `node <closure>/dist/launch-args.js --role hub|agent [--allocator-env]` prints
|
|
49
|
+
* what the launcher should add to the node's command line, so the Docker
|
|
50
|
+
* entrypoint, the Electron shell and the Windows service supervisor carry NO copy
|
|
51
|
+
* of the formula: changing it (or a node's override) needs a closure update and a
|
|
52
|
+
* restart, never a new image or app.
|
|
53
|
+
*
|
|
54
|
+
* Zero dependencies (node built-ins only): it runs BEFORE the closure's own
|
|
55
|
+
* `node_modules` resolution, from either the active closure or the baked seed.
|
|
56
|
+
*
|
|
57
|
+
* ## Output (stdout, one record per line, `\n` separated)
|
|
58
|
+
*
|
|
59
|
+
* arg --max-old-space-size=2048 an extra node argv entry (flags only)
|
|
60
|
+
* env NAME=value an environment variable to export
|
|
61
|
+
* source override|legacy-env|computed|v8-default
|
|
62
|
+
* warn <free text> something the launcher should log
|
|
63
|
+
*
|
|
64
|
+
* Nothing else is printed. A launcher treats any line it does not recognise as
|
|
65
|
+
* absent, and a non-zero exit, a timeout or empty output as "no extra args".
|
|
66
|
+
*
|
|
67
|
+
* ## The heap ceiling
|
|
68
|
+
*
|
|
69
|
+
* - `override` — `<dataDir>/launch-overrides.json` `{ "heapMb": N }`, set from
|
|
70
|
+
* the hub (`serverManagement.setLaunchOverrides`) and persisted on the node.
|
|
71
|
+
* - `legacy-env` — `CAMSTACK_HUB_HEAP_MB` (hub) / `CAMSTACK_AGENT_HEAP_MB`
|
|
72
|
+
* (agent), still honoured when no override is set.
|
|
73
|
+
* - `computed` — AGENT only: clamp(min(RAM, cgroup limit) / 4, 1536, 4096) MB.
|
|
74
|
+
* V8's own quarter-of-RAM rule with a floor above every measured agent RSS
|
|
75
|
+
* (0.8-1.4 GB), so it bounds a runaway and never a healthy root.
|
|
76
|
+
* - `v8-default` — HUB only, nothing set: no flag. A hardcoded 1536 killed the
|
|
77
|
+
* hub four times in 15 h (see the history in the Dockerfile); V8's default
|
|
78
|
+
* here was 4288 MB and is still bounded.
|
|
79
|
+
*
|
|
80
|
+
* Set on argv, never NODE_OPTIONS: forked addon runners are sized separately.
|
|
81
|
+
*/
|
|
82
|
+
const fs = __importStar(require("node:fs"));
|
|
83
|
+
const os = __importStar(require("node:os"));
|
|
84
|
+
const path = __importStar(require("node:path"));
|
|
85
|
+
exports.LAUNCH_OVERRIDES_FILE = 'launch-overrides.json';
|
|
86
|
+
/** Env var carrying {@link CeilingSource} to the node so its heartbeat can say who chose. */
|
|
87
|
+
exports.HEAP_CEILING_SOURCE_ENV = 'CAMSTACK_HEAP_CEILING_SOURCE';
|
|
88
|
+
exports.AGENT_HEAP_FLOOR_MB = 1536;
|
|
89
|
+
exports.AGENT_HEAP_CAP_MB = 4096;
|
|
90
|
+
/** An override outside this range is ignored with a warning, never applied. */
|
|
91
|
+
exports.OVERRIDE_MIN_MB = 512;
|
|
92
|
+
exports.OVERRIDE_MAX_MB = 65536;
|
|
93
|
+
/**
|
|
94
|
+
* The native allocator pair, welded: glibc reads MALLOC_ARENA_MAX once at malloc
|
|
95
|
+
* init, so it must exist before `exec node`; sharp restores libvips concurrency
|
|
96
|
+
* from 1 to `availableParallelism()` the moment it appears, so the pair travels
|
|
97
|
+
* together or not at all. Defaults only: an operator value already in the
|
|
98
|
+
* environment wins (the launcher applies them with `: "${NAME:=value}"` semantics).
|
|
99
|
+
*/
|
|
100
|
+
exports.ALLOCATOR_ENV_DEFAULTS = {
|
|
101
|
+
MALLOC_ARENA_MAX: '2',
|
|
102
|
+
VIPS_CONCURRENCY: '1',
|
|
103
|
+
};
|
|
104
|
+
const BYTES_PER_MB = 1024 * 1024;
|
|
105
|
+
/** cgroup v1 reports "unlimited" as a value near 2^63. */
|
|
106
|
+
const CGROUP_UNLIMITED_BYTES = 2 ** 60;
|
|
107
|
+
function validMb(value) {
|
|
108
|
+
return Number.isInteger(value) && value >= exports.OVERRIDE_MIN_MB && value <= exports.OVERRIDE_MAX_MB;
|
|
109
|
+
}
|
|
110
|
+
/** The agent's computed ceiling for `host`, in MB. */
|
|
111
|
+
function computedAgentHeapMb(host) {
|
|
112
|
+
const limits = [host.totalMemBytes, host.cgroupLimitBytes ?? Number.POSITIVE_INFINITY].filter((n) => Number.isFinite(n) && n > 0);
|
|
113
|
+
if (limits.length === 0)
|
|
114
|
+
return exports.AGENT_HEAP_FLOOR_MB;
|
|
115
|
+
const quarterMb = Math.floor(Math.min(...limits) / BYTES_PER_MB / 4);
|
|
116
|
+
return Math.min(exports.AGENT_HEAP_CAP_MB, Math.max(exports.AGENT_HEAP_FLOOR_MB, quarterMb));
|
|
117
|
+
}
|
|
118
|
+
function planLaunch(input) {
|
|
119
|
+
const warnings = [];
|
|
120
|
+
let source = input.role === 'agent' ? 'computed' : 'v8-default';
|
|
121
|
+
let heapMb = input.role === 'agent' ? computedAgentHeapMb(input.host) : null;
|
|
122
|
+
const legacy = input.legacyEnv;
|
|
123
|
+
if (legacy !== undefined && legacy !== '') {
|
|
124
|
+
const mb = /^[1-9][0-9]*$/.test(legacy) ? Number(legacy) : Number.NaN;
|
|
125
|
+
if (validMb(mb)) {
|
|
126
|
+
source = 'legacy-env';
|
|
127
|
+
heapMb = mb;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
warnings.push(`ignoring the legacy heap env "${legacy}": not an integer in ${exports.OVERRIDE_MIN_MB}-${exports.OVERRIDE_MAX_MB} MB`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (input.overrideMb !== null) {
|
|
134
|
+
if (validMb(input.overrideMb)) {
|
|
135
|
+
source = 'override';
|
|
136
|
+
heapMb = input.overrideMb;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
warnings.push(`ignoring the heap override ${input.overrideMb}: not an integer in ${exports.OVERRIDE_MIN_MB}-${exports.OVERRIDE_MAX_MB} MB`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
args: heapMb === null ? [] : [`--max-old-space-size=${heapMb}`],
|
|
144
|
+
env: {
|
|
145
|
+
[exports.HEAP_CEILING_SOURCE_ENV]: source,
|
|
146
|
+
...(input.allocatorEnv ? exports.ALLOCATOR_ENV_DEFAULTS : {}),
|
|
147
|
+
},
|
|
148
|
+
source,
|
|
149
|
+
warnings,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function renderLaunchPlan(plan) {
|
|
153
|
+
const lines = [
|
|
154
|
+
...plan.args.map((a) => `arg ${a}`),
|
|
155
|
+
...Object.entries(plan.env).map(([k, v]) => `env ${k}=${v}`),
|
|
156
|
+
`source ${plan.source}`,
|
|
157
|
+
...plan.warnings.map((w) => `warn ${w.replace(/[\r\n]+/g, ' ')}`),
|
|
158
|
+
];
|
|
159
|
+
return `${lines.join('\n')}\n`;
|
|
160
|
+
}
|
|
161
|
+
const realFs = {
|
|
162
|
+
readFile(file) {
|
|
163
|
+
try {
|
|
164
|
+
return fs.readFileSync(file, 'utf8');
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
function overridesPath(dataDir) {
|
|
172
|
+
return path.join(dataDir, exports.LAUNCH_OVERRIDES_FILE);
|
|
173
|
+
}
|
|
174
|
+
/** The persisted heap override in MB; `null` when absent, unreadable or not a number. */
|
|
175
|
+
function readHeapOverrideMb(dataDir, io = realFs) {
|
|
176
|
+
const raw = io.readFile(overridesPath(dataDir));
|
|
177
|
+
if (raw === null)
|
|
178
|
+
return null;
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(raw);
|
|
181
|
+
if (typeof parsed !== 'object' || parsed === null || !('heapMb' in parsed))
|
|
182
|
+
return null;
|
|
183
|
+
const value = parsed.heapMb;
|
|
184
|
+
return typeof value === 'number' ? value : null;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Persist (or, with `null`, clear) the heap override. Validated against the same
|
|
192
|
+
* range the launcher enforces, so a value the next launch would ignore is refused
|
|
193
|
+
* here instead of silently doing nothing. Written atomically (temp + rename).
|
|
194
|
+
*/
|
|
195
|
+
function writeHeapOverrideMb(dataDir, heapMb) {
|
|
196
|
+
const file = overridesPath(dataDir);
|
|
197
|
+
if (heapMb === null) {
|
|
198
|
+
fs.rmSync(file, { force: true });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!validMb(heapMb)) {
|
|
202
|
+
throw new Error(`heap override must be an integer in ${exports.OVERRIDE_MIN_MB}-${exports.OVERRIDE_MAX_MB} MB, got ${heapMb}`);
|
|
203
|
+
}
|
|
204
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
205
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
206
|
+
fs.writeFileSync(tmp, `${JSON.stringify({ heapMb })}\n`, { mode: 0o600 });
|
|
207
|
+
fs.renameSync(tmp, file);
|
|
208
|
+
}
|
|
209
|
+
function readCgroupLimitBytes(io = realFs, files) {
|
|
210
|
+
for (const file of files ?? [
|
|
211
|
+
'/sys/fs/cgroup/memory.max',
|
|
212
|
+
'/sys/fs/cgroup/memory/memory.limit_in_bytes',
|
|
213
|
+
]) {
|
|
214
|
+
const raw = io.readFile(file)?.trim();
|
|
215
|
+
if (raw === undefined || !/^[0-9]+$/.test(raw))
|
|
216
|
+
continue;
|
|
217
|
+
const bytes = Number(raw);
|
|
218
|
+
if (bytes > 0 && bytes < CGROUP_UNLIMITED_BYTES)
|
|
219
|
+
return bytes;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function parseRole(argv) {
|
|
224
|
+
const i = argv.indexOf('--role');
|
|
225
|
+
const value = i >= 0 ? argv[i + 1] : undefined;
|
|
226
|
+
return value === 'hub' || value === 'agent' ? value : null;
|
|
227
|
+
}
|
|
228
|
+
/** Run the CLI; returns the stdout text and the exit code. Never throws. */
|
|
229
|
+
function runLaunchCli(argv, deps) {
|
|
230
|
+
const role = parseRole(argv);
|
|
231
|
+
if (role === null)
|
|
232
|
+
return { stdout: '', code: 2 };
|
|
233
|
+
const dataDir = path.resolve(deps.env['CAMSTACK_DATA'] ?? 'camstack-data');
|
|
234
|
+
const legacyName = role === 'hub' ? 'CAMSTACK_HUB_HEAP_MB' : 'CAMSTACK_AGENT_HEAP_MB';
|
|
235
|
+
const plan = planLaunch({
|
|
236
|
+
role,
|
|
237
|
+
host: {
|
|
238
|
+
totalMemBytes: deps.totalMemBytes,
|
|
239
|
+
cgroupLimitBytes: readCgroupLimitBytes(deps.io, deps.cgroupFiles),
|
|
240
|
+
},
|
|
241
|
+
overrideMb: readHeapOverrideMb(dataDir, deps.io),
|
|
242
|
+
legacyEnv: deps.env[legacyName],
|
|
243
|
+
allocatorEnv: argv.includes('--allocator-env'),
|
|
244
|
+
});
|
|
245
|
+
return { stdout: renderLaunchPlan(plan), code: 0 };
|
|
246
|
+
}
|
|
247
|
+
if (require.main === module) {
|
|
248
|
+
const result = runLaunchCli(process.argv.slice(2), {
|
|
249
|
+
env: process.env,
|
|
250
|
+
io: realFs,
|
|
251
|
+
totalMemBytes: os.totalmem(),
|
|
252
|
+
});
|
|
253
|
+
process.stdout.write(result.stdout);
|
|
254
|
+
process.exitCode = result.code;
|
|
255
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.690",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"./package.json": "./package.json",
|
|
25
25
|
"./main.js": "./dist/main.js",
|
|
26
26
|
"./launcher.js": "./dist/launcher.js",
|
|
27
|
-
"./starter.js": "./dist/starter.js"
|
|
27
|
+
"./starter.js": "./dist/starter.js",
|
|
28
|
+
"./launch-args.js": "./dist/launch-args.js"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "../../scripts/tsc7 -p tsconfig.build.json && tsup && node scripts/copy-closure-patches.mjs",
|
|
@@ -49,10 +50,10 @@
|
|
|
49
50
|
]
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
53
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
53
|
+
"@camstack/addon-admin-ui": "1.2.518",
|
|
54
|
+
"@camstack/addon-agent-ui": "1.2.293",
|
|
54
55
|
"@camstack/addon-auth": "1.2.170",
|
|
55
|
-
"@camstack/addon-benchmark": "1.2.
|
|
56
|
+
"@camstack/addon-benchmark": "1.2.292",
|
|
56
57
|
"@camstack/addon-decoder-nodeav": "1.2.144",
|
|
57
58
|
"@camstack/addon-notifiers": "1.2.623",
|
|
58
59
|
"@camstack/addon-pipeline": "1.2.634",
|
|
@@ -60,9 +61,9 @@
|
|
|
60
61
|
"@camstack/addon-post-analysis": "1.2.651",
|
|
61
62
|
"@camstack/develop": "0.1.39",
|
|
62
63
|
"@camstack/sdk": "1.2.154",
|
|
63
|
-
"@camstack/system": "1.2.
|
|
64
|
-
"@camstack/types": "1.2.
|
|
65
|
-
"@camstack/ui-library": "1.2.
|
|
64
|
+
"@camstack/system": "1.2.519",
|
|
65
|
+
"@camstack/types": "1.2.445",
|
|
66
|
+
"@camstack/ui-library": "1.2.351",
|
|
66
67
|
"@fastify/compress": "^9.0.0",
|
|
67
68
|
"@fastify/cookie": "^11.0.2",
|
|
68
69
|
"@fastify/cors": "^11.2.0",
|