@camstack/server 1.2.29 → 1.2.30
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.
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- pre-existing lint debt across this 800-line provider-factory module. The flagged sites delegate into Moleculer/EventBus/IntegrationRegistry surfaces typed as `unknown` to break circular dependencies; runtime contracts are validated by the cap-mount-helper layer above. Tracked separately. */
|
|
3
|
+
/**
|
|
4
|
+
* Capability provider factories for the Phase E core caps —
|
|
5
|
+
* `system`, `network-quality`, `toast`, `nodes`, `integrations`,
|
|
6
|
+
* `addons`. Each factory builds a fresh provider object that
|
|
7
|
+
* fulfils the cap's `InferProvider<...>` contract by delegating
|
|
8
|
+
* to the existing backend services.
|
|
9
|
+
*
|
|
10
|
+
* Why factories instead of static singletons?
|
|
11
|
+
* - `addons.custom` needs per-request `ctx.user` for per-action
|
|
12
|
+
* auth checks. The cap-router codegen already passes `ctx`
|
|
13
|
+
* into `getProvider(ctx)`, so closing over it is cheap and
|
|
14
|
+
* keeps the auth surface tight.
|
|
15
|
+
* - The other caps don't need ctx, but we keep the signature
|
|
16
|
+
* uniform for symmetry.
|
|
17
|
+
*
|
|
18
|
+
* No addon "owns" these surfaces — they manage cluster state
|
|
19
|
+
* (cluster topology, integrations, addon packages) or expose
|
|
20
|
+
* server-level singletons (toast bus, network-quality tracker,
|
|
21
|
+
* feature flags + retention controls).
|
|
22
|
+
*
|
|
23
|
+
* Phase E (2026-05-06): replaces the hand-written core routers in
|
|
24
|
+
* `server/backend/src/api/core/{system,network-quality,toast,
|
|
25
|
+
* nodes,integrations,addons}.router.ts`.
|
|
26
|
+
*/
|
|
2
27
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
28
|
if (k2 === undefined) k2 = k;
|
|
4
29
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -43,42 +68,17 @@ exports.buildNodesProvider = buildNodesProvider;
|
|
|
43
68
|
exports.buildIntegrationsProvider = buildIntegrationsProvider;
|
|
44
69
|
exports.dispatchCustomAction = dispatchCustomAction;
|
|
45
70
|
exports.buildAddonsProvider = buildAddonsProvider;
|
|
46
|
-
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- pre-existing lint debt across this 800-line provider-factory module. The flagged sites delegate into Moleculer/EventBus/IntegrationRegistry surfaces typed as `unknown` to break circular dependencies; runtime contracts are validated by the cap-mount-helper layer above. Tracked separately. */
|
|
47
|
-
/**
|
|
48
|
-
* Capability provider factories for the Phase E core caps —
|
|
49
|
-
* `system`, `network-quality`, `toast`, `nodes`, `integrations`,
|
|
50
|
-
* `addons`. Each factory builds a fresh provider object that
|
|
51
|
-
* fulfils the cap's `InferProvider<...>` contract by delegating
|
|
52
|
-
* to the existing backend services.
|
|
53
|
-
*
|
|
54
|
-
* Why factories instead of static singletons?
|
|
55
|
-
* - `addons.custom` needs per-request `ctx.user` for per-action
|
|
56
|
-
* auth checks. The cap-router codegen already passes `ctx`
|
|
57
|
-
* into `getProvider(ctx)`, so closing over it is cheap and
|
|
58
|
-
* keeps the auth surface tight.
|
|
59
|
-
* - The other caps don't need ctx, but we keep the signature
|
|
60
|
-
* uniform for symmetry.
|
|
61
|
-
*
|
|
62
|
-
* No addon "owns" these surfaces — they manage cluster state
|
|
63
|
-
* (cluster topology, integrations, addon packages) or expose
|
|
64
|
-
* server-level singletons (toast bus, network-quality tracker,
|
|
65
|
-
* feature flags + retention controls).
|
|
66
|
-
*
|
|
67
|
-
* Phase E (2026-05-06): replaces the hand-written core routers in
|
|
68
|
-
* `server/backend/src/api/core/{system,network-quality,toast,
|
|
69
|
-
* nodes,integrations,addons}.router.ts`.
|
|
70
|
-
*/
|
|
71
|
-
const os = __importStar(require("node:os"));
|
|
72
71
|
const node_child_process_1 = require("node:child_process");
|
|
73
|
-
const node_util_1 = require("node:util");
|
|
74
72
|
const node_crypto_1 = require("node:crypto");
|
|
75
|
-
const
|
|
76
|
-
const
|
|
73
|
+
const os = __importStar(require("node:os"));
|
|
74
|
+
const node_util_1 = require("node:util");
|
|
77
75
|
const system_1 = require("@camstack/system");
|
|
76
|
+
const types_1 = require("@camstack/types");
|
|
77
|
+
const server_1 = require("@trpc/server");
|
|
78
78
|
const integration_id_backfill_1 = require("../../boot/integration-id-backfill");
|
|
79
|
-
const collection_preference_js_1 = require("./collection-preference.js");
|
|
80
79
|
const addon_package_service_js_1 = require("../../core/addon/addon-package.service.js");
|
|
81
80
|
const lifecycle_runner_singleton_js_1 = require("../../core/lifecycle/lifecycle-runner.singleton.js");
|
|
81
|
+
const collection_preference_js_1 = require("./collection-preference.js");
|
|
82
82
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
83
83
|
// ── system ──────────────────────────────────────────────────────────
|
|
84
84
|
function getRetention(registry) {
|
|
@@ -358,7 +358,7 @@ function createNodeRootPackageLookup(moleculer, serverUpdate) {
|
|
|
358
358
|
return moleculer.getNodeRootPackage(nodeId);
|
|
359
359
|
};
|
|
360
360
|
}
|
|
361
|
-
function buildNodesProvider(agentRegistry, moleculer, addonRegistry, getNodeRootPackage) {
|
|
361
|
+
function buildNodesProvider(agentRegistry, moleculer, addonRegistry, getNodeRootPackage, logger) {
|
|
362
362
|
const broker = moleculer.broker;
|
|
363
363
|
return {
|
|
364
364
|
topology: async () => computeTopology(agentRegistry, addonRegistry, getNodeRootPackage),
|
|
@@ -414,6 +414,15 @@ function buildNodesProvider(agentRegistry, moleculer, addonRegistry, getNodeRoot
|
|
|
414
414
|
},
|
|
415
415
|
shutdownNode: async (input) => {
|
|
416
416
|
if (input.nodeId === 'hub') {
|
|
417
|
+
// A whole-hub shutdown used to be COMPLETELY silent — process.exit(0)
|
|
418
|
+
// with not one log line. On 2026-08-01 the hub container went down
|
|
419
|
+
// cleanly at 15:02:55Z (docker restart policy brought it back 9s
|
|
420
|
+
// later, viewer stalled through the boot) and the initiator was
|
|
421
|
+
// unrecoverable from the logs precisely because this branch said
|
|
422
|
+
// nothing. An operator-facing kill switch logs before it fires.
|
|
423
|
+
logger?.warn('shutdownNode: HUB shutdown requested via API — exiting in 500ms', {
|
|
424
|
+
meta: { nodeId: input.nodeId },
|
|
425
|
+
});
|
|
417
426
|
setTimeout(() => process.exit(0), 500);
|
|
418
427
|
return { success: true };
|
|
419
428
|
}
|
|
@@ -260,7 +260,7 @@ function mountAllCaps(services) {
|
|
|
260
260
|
modelConvert: (0, generated_cap_routers_js_1.createCapRouter_modelConvert)((_ctx) => reg?.getSingleton('model-convert') ?? null, remoteCapProxy),
|
|
261
261
|
modelDistributor: (0, generated_cap_routers_js_1.createCapRouter_modelDistributor)((_ctx) => reg?.getSingleton('model-distributor') ??
|
|
262
262
|
null, remoteCapProxy),
|
|
263
|
-
motion: (0, generated_cap_routers_js_1.createCapRouter_motion)((_ctx) => reg
|
|
263
|
+
motion: (0, generated_cap_routers_js_1.createCapRouter_motion)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'motion'), remoteCapProxy),
|
|
264
264
|
motionDetection: (0, generated_cap_routers_js_1.createCapRouter_motionDetection)((_ctx) => reg?.getSingleton('motion-detection') ??
|
|
265
265
|
null, remoteCapProxy),
|
|
266
266
|
motionTrigger: (0, generated_cap_routers_js_1.createCapRouter_motionTrigger)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'motion-trigger'), remoteCapProxy),
|
|
@@ -8,15 +8,15 @@ exports.buildAppRouter = buildAppRouter;
|
|
|
8
8
|
const addon_settings_router_js_1 = require("../core/addon-settings.router.js");
|
|
9
9
|
const auth_router_js_1 = require("../core/auth.router.js");
|
|
10
10
|
const cap_providers_js_1 = require("../core/cap-providers.js");
|
|
11
|
-
const server_management_provider_js_1 = require("../core/server-management.provider.js");
|
|
12
11
|
const capabilities_router_js_1 = require("../core/capabilities.router.js");
|
|
12
|
+
const cluster_nodes_router_js_1 = require("../core/cluster-nodes.router.js");
|
|
13
13
|
const event_bus_proxy_router_js_1 = require("../core/event-bus-proxy.router.js");
|
|
14
14
|
const hwaccel_router_js_1 = require("../core/hwaccel.router.js");
|
|
15
15
|
const live_events_router_js_1 = require("../core/live-events.router.js");
|
|
16
16
|
const logs_router_js_1 = require("../core/logs.router.js");
|
|
17
17
|
const notifications_router_js_1 = require("../core/notifications.router.js");
|
|
18
|
-
const cluster_nodes_router_js_1 = require("../core/cluster-nodes.router.js");
|
|
19
18
|
const repl_router_js_1 = require("../core/repl.router.js");
|
|
19
|
+
const server_management_provider_js_1 = require("../core/server-management.provider.js");
|
|
20
20
|
const settings_backend_router_js_1 = require("../core/settings-backend.router.js");
|
|
21
21
|
const stream_probe_router_js_1 = require("../core/stream-probe.router.js");
|
|
22
22
|
const system_events_router_js_1 = require("../core/system-events.router.js");
|
|
@@ -195,7 +195,7 @@ function buildServerProviders(services) {
|
|
|
195
195
|
system: wrap(() => (0, cap_providers_js_1.buildSystemProvider)(services.featureService, services.capabilityRegistry)),
|
|
196
196
|
toast: wrap((ctx) => (0, cap_providers_js_1.buildToastProvider)(services.toastService, ctx)),
|
|
197
197
|
integrations: wrap(() => (0, cap_providers_js_1.buildIntegrationsProvider)(services.addonRegistry, services.eventBus, services.loggingService, services.capabilityRegistry)),
|
|
198
|
-
nodes: wrap(() => (0, cap_providers_js_1.buildNodesProvider)(services.agentRegistry, services.moleculer, services.addonRegistry, (0, cap_providers_js_1.createNodeRootPackageLookup)(services.moleculer, services.serverUpdateService))),
|
|
198
|
+
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'))),
|
|
199
199
|
// server-management — runtime-updatable root package (phase 1: hub only).
|
|
200
200
|
'server-management': wrap(() => (0, server_management_provider_js_1.buildServerManagementProvider)(services.serverUpdateService)),
|
|
201
201
|
addons: wrap((ctx) => (0, cap_providers_js_1.buildAddonsProvider)(services.addonRegistry, services.addonPackageService, services.loggingService, services.moleculer, services.configService, ctx)),
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hub bootstrap package selection.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `launcher.ts` so it can be unit-tested: the launcher
|
|
6
|
+
* self-executes `launch()` at import time, so nothing inside it is reachable
|
|
7
|
+
* from a spec. Same shape as `framework-nodepath.ts`.
|
|
8
|
+
*
|
|
9
|
+
* The selection MUST stay in lockstep with
|
|
10
|
+
* `AddonInstaller.deriveBootstrapList` in `@camstack/system` — that one reads
|
|
11
|
+
* the same `@camstack/server` manifest through `require.resolve`, this one
|
|
12
|
+
* reads the launcher's own `package.json` by absolute path (the slim image
|
|
13
|
+
* strips the `@camstack/*` symlinks, so `require.resolve` yields an empty list
|
|
14
|
+
* at launcher import time). Two derivations of the same list is exactly how
|
|
15
|
+
* `@camstack/addon-agent-ui` ended up installed on the hub: the installer's
|
|
16
|
+
* copy honoured `camstack.agentOnlyBootstrapAddons`, this one did not, and the
|
|
17
|
+
* hub is the side that actually boots through here.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.selectHubBootstrapPackages = selectHubBootstrapPackages;
|
|
21
|
+
/**
|
|
22
|
+
* The packages a HUB-role node bootstrap-installs: every `@camstack/addon-*`
|
|
23
|
+
* runtime dependency plus `@camstack/system` (the carrier of the required
|
|
24
|
+
* infrastructure builtins — storage-provider / settings / logging — without
|
|
25
|
+
* which boot aborts).
|
|
26
|
+
*
|
|
27
|
+
* `camstack.agentOnlyBootstrapAddons` is subtracted. Those addons are declared
|
|
28
|
+
* as runtime dependencies so the published `@camstack/server` closure CARRIES
|
|
29
|
+
* them for agent-role nodes to seed, but they are `execution.placement:
|
|
30
|
+
* 'agent-only'` and the hub must never install them.
|
|
31
|
+
*/
|
|
32
|
+
function selectHubBootstrapPackages(pkg) {
|
|
33
|
+
const agentOnly = new Set(pkg.camstack?.agentOnlyBootstrapAddons ?? []);
|
|
34
|
+
return Object.keys(pkg.dependencies ?? {}).filter((name) => !agentOnly.has(name) && (name.startsWith('@camstack/addon-') || name === '@camstack/system'));
|
|
35
|
+
}
|
package/dist/launcher.js
CHANGED
|
@@ -56,6 +56,7 @@ const path = __importStar(require("node:path"));
|
|
|
56
56
|
const tar = __importStar(require("tar"));
|
|
57
57
|
const yaml = __importStar(require("js-yaml"));
|
|
58
58
|
const framework_nodepath_js_1 = require("./framework-nodepath.js");
|
|
59
|
+
const bootstrap_packages_js_1 = require("./bootstrap-packages.js");
|
|
59
60
|
/** Path of the manifest file embedded inside every archive. */
|
|
60
61
|
const ARCHIVE_MANIFEST_NAME = '.camstack-backup-manifest.json';
|
|
61
62
|
/** Resolve the data directory from env or default */
|
|
@@ -190,39 +191,42 @@ function readBootstrapInstallSource(dataDir, bootstrapSchema) {
|
|
|
190
191
|
return undefined;
|
|
191
192
|
return parseInstallSource(validation.data.bootstrap.installSource);
|
|
192
193
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Read `bootstrap.requiredAddons` from `<dataDir>/config.yaml` if present.
|
|
195
|
-
* Returns the parsed array, an empty array when explicitly set to `[]`,
|
|
196
|
-
* or `null` when the field is absent (caller should fall back to the
|
|
197
|
-
* kernel's REQUIRED_PACKAGES constant).
|
|
198
|
-
*
|
|
199
|
-
* Inline YAML parse rather than going through ConfigManager: the
|
|
200
|
-
* launcher runs before @camstack/system is imported and must stay free of
|
|
201
|
-
* core dependencies. Just yaml.load + bootstrapSchema (kernel) for
|
|
202
|
-
* shape validation.
|
|
203
|
-
*
|
|
204
|
-
* `bootstrapSchema` is passed in (not imported at module top) so this
|
|
205
|
-
* helper can be called after the dynamic @camstack/system import in launch().
|
|
206
|
-
*/
|
|
207
194
|
/**
|
|
208
195
|
* Derive the bootstrap package list from THIS server's own package.json — the
|
|
209
196
|
* `@camstack/addon-*` deps plus `@camstack/system` (which ships the required
|
|
210
|
-
* infrastructure builtins). Read by
|
|
211
|
-
* level up from this launcher's dist
|
|
212
|
-
* the `@camstack/*` node_modules
|
|
213
|
-
* `AddonInstaller.deriveBootstrapList`, but
|
|
197
|
+
* infrastructure builtins), MINUS `camstack.agentOnlyBootstrapAddons`. Read by
|
|
198
|
+
* absolute path (the package.json sits one level up from this launcher's dist
|
|
199
|
+
* dir), so it works in the slim image where the `@camstack/*` node_modules
|
|
200
|
+
* symlinks are stripped. Mirrors `AddonInstaller.deriveBootstrapList`, but
|
|
201
|
+
* without `require.resolve` — the selection itself lives in
|
|
202
|
+
* `bootstrap-packages.ts` so it is unit-testable (this module self-executes
|
|
203
|
+
* `launch()` on import, so nothing declared here can be imported by a spec).
|
|
214
204
|
*/
|
|
215
205
|
function deriveBootstrapFromSelf() {
|
|
216
206
|
try {
|
|
217
207
|
const pkgPath = path.resolve(__dirname, '..', 'package.json');
|
|
218
208
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
219
|
-
return
|
|
209
|
+
return (0, bootstrap_packages_js_1.selectHubBootstrapPackages)(pkg);
|
|
220
210
|
}
|
|
221
211
|
catch (err) {
|
|
222
212
|
console.warn(`[launcher] could not derive bootstrap list from package.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
213
|
return [];
|
|
224
214
|
}
|
|
225
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Read `bootstrap.requiredAddons` from `<dataDir>/config.yaml` if present.
|
|
218
|
+
* Returns the parsed array, an empty array when explicitly set to `[]`,
|
|
219
|
+
* or `null` when the field is absent (caller should fall back to the
|
|
220
|
+
* kernel's REQUIRED_PACKAGES constant).
|
|
221
|
+
*
|
|
222
|
+
* Inline YAML parse rather than going through ConfigManager: the
|
|
223
|
+
* launcher runs before @camstack/system is imported and must stay free of
|
|
224
|
+
* core dependencies. Just yaml.load + bootstrapSchema (kernel) for
|
|
225
|
+
* shape validation.
|
|
226
|
+
*
|
|
227
|
+
* `bootstrapSchema` is passed in (not imported at module top) so this
|
|
228
|
+
* helper can be called after the dynamic @camstack/system import in launch().
|
|
229
|
+
*/
|
|
226
230
|
function readBootstrapRequiredAddons(dataDir, bootstrapSchema) {
|
|
227
231
|
const raw = readConfigYaml(dataDir);
|
|
228
232
|
if (raw === null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -33,19 +33,19 @@
|
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
37
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
38
|
-
"@camstack/addon-auth": "1.2.
|
|
39
|
-
"@camstack/addon-decoder-nodeav": "1.2.
|
|
40
|
-
"@camstack/addon-notifiers": "1.2.
|
|
41
|
-
"@camstack/addon-pipeline": "1.2.
|
|
42
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
43
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
44
|
-
"@camstack/sdk": "1.2.
|
|
45
|
-
"@camstack/shm-ring": "1.1.
|
|
46
|
-
"@camstack/system": "1.2.
|
|
47
|
-
"@camstack/types": "1.2.
|
|
48
|
-
"@camstack/ui-library": "1.2.
|
|
36
|
+
"@camstack/addon-admin-ui": "1.2.20",
|
|
37
|
+
"@camstack/addon-agent-ui": "1.2.8",
|
|
38
|
+
"@camstack/addon-auth": "1.2.8",
|
|
39
|
+
"@camstack/addon-decoder-nodeav": "1.2.8",
|
|
40
|
+
"@camstack/addon-notifiers": "1.2.10",
|
|
41
|
+
"@camstack/addon-pipeline": "1.2.36",
|
|
42
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.17",
|
|
43
|
+
"@camstack/addon-post-analysis": "1.2.21",
|
|
44
|
+
"@camstack/sdk": "1.2.8",
|
|
45
|
+
"@camstack/shm-ring": "1.1.8",
|
|
46
|
+
"@camstack/system": "1.2.27",
|
|
47
|
+
"@camstack/types": "1.2.19",
|
|
48
|
+
"@camstack/ui-library": "1.2.15",
|
|
49
49
|
"@fastify/compress": "^9.0.0",
|
|
50
50
|
"@fastify/cookie": "^11.0.2",
|
|
51
51
|
"@fastify/cors": "^11.2.0",
|