@camstack/server 1.0.0 → 1.0.1

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.
Files changed (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 1: Bootstrap config loading and infrastructure setup.
4
+ *
5
+ * Extracted from main.ts — pure extraction, no behavior change.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.loadBootstrapConfig = loadBootstrapConfig;
42
+ exports.autoGenerateJwtSecret = autoGenerateJwtSecret;
43
+ exports.setupInfra = setupInfra;
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const yaml = __importStar(require("js-yaml"));
47
+ const node_crypto_1 = require("node:crypto");
48
+ const types_1 = require("@camstack/types");
49
+ const config_schema_1 = require("../core/config/config.schema");
50
+ const storage_location_manager_1 = require("../core/storage/storage-location-manager");
51
+ // ---------------------------------------------------------------------------
52
+ // Constants
53
+ // ---------------------------------------------------------------------------
54
+ const CONFIG_DEFAULTS = {
55
+ server: { port: 4443, host: '0.0.0.0', dataPath: 'camstack-data' },
56
+ auth: {
57
+ jwtSecret: null,
58
+ adminUsername: 'admin',
59
+ adminPassword: 'changeme',
60
+ },
61
+ };
62
+ const ENV_VAR_MAP = {
63
+ CAMSTACK_PORT: 'server.port',
64
+ CAMSTACK_HOST: 'server.host',
65
+ CAMSTACK_DATA: 'server.dataPath',
66
+ CAMSTACK_JWT_SECRET: 'auth.jwtSecret',
67
+ CAMSTACK_ADMIN_USER: 'auth.adminUsername',
68
+ CAMSTACK_ADMIN_PASS: 'auth.adminPassword',
69
+ CAMSTACK_HUB_URL: 'hub.url',
70
+ CAMSTACK_HUB_TOKEN: 'hub.token',
71
+ CAMSTACK_AGENT_NAME: 'agent.name',
72
+ CAMSTACK_TLS_ENABLED: 'tls.enabled',
73
+ CAMSTACK_TLS_CERT: 'tls.certPath',
74
+ CAMSTACK_TLS_KEY: 'tls.keyPath',
75
+ };
76
+ // ---------------------------------------------------------------------------
77
+ // Helpers
78
+ // ---------------------------------------------------------------------------
79
+ function setNested(obj, p, value) {
80
+ const [head, ...rest] = p.split('.');
81
+ if (!head)
82
+ return obj;
83
+ if (rest.length === 0)
84
+ return { ...obj, [head]: value };
85
+ const child = (0, types_1.asJsonObject)(obj[head]) ?? {};
86
+ return { ...obj, [head]: setNested(child, rest.join('.'), value) };
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // loadBootstrapConfig
90
+ // ---------------------------------------------------------------------------
91
+ /**
92
+ * Load bootstrap config from a YAML file, apply env-var overrides, and
93
+ * validate via the bootstrap Zod schema.
94
+ */
95
+ function loadBootstrapConfig(configPath) {
96
+ // Only bootstrap sections live in config.yaml.
97
+ // All runtime settings are stored in the SQL system_settings table.
98
+ let raw;
99
+ if (fs.existsSync(configPath)) {
100
+ const content = fs.readFileSync(configPath, 'utf-8');
101
+ raw = (0, types_1.asJsonObject)(yaml.load(content)) ?? {};
102
+ // Merge in any missing bootstrap sections (server, auth only)
103
+ let updated = false;
104
+ for (const [key, defaults] of Object.entries(CONFIG_DEFAULTS)) {
105
+ if (!(key in raw)) {
106
+ raw[key] = defaults;
107
+ updated = true;
108
+ }
109
+ }
110
+ if (updated) {
111
+ try {
112
+ const tmpPath = `${configPath}.tmp`;
113
+ fs.writeFileSync(tmpPath, yaml.dump(raw, { lineWidth: 120, indent: 2, quotingType: '"' }), 'utf-8');
114
+ fs.renameSync(tmpPath, configPath);
115
+ console.log(`[Phase1] Updated config.yaml with missing bootstrap defaults`);
116
+ }
117
+ catch (err) {
118
+ console.warn(`[Phase1] Could not update config.yaml:`, err);
119
+ }
120
+ }
121
+ console.log(`[Phase1] Loaded bootstrap config from: ${configPath}`);
122
+ }
123
+ else {
124
+ console.log(`[Phase1] Config file not found at: ${configPath} — writing defaults`);
125
+ const defaults = { ...CONFIG_DEFAULTS };
126
+ try {
127
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
128
+ const tmpPath = `${configPath}.tmp`;
129
+ fs.writeFileSync(tmpPath, yaml.dump(defaults, { lineWidth: 120, indent: 2, quotingType: '"' }), 'utf-8');
130
+ fs.renameSync(tmpPath, configPath);
131
+ console.log(`[Phase1] Default config.yaml written to: ${configPath}`);
132
+ }
133
+ catch (err) {
134
+ console.warn(`[Phase1] Could not write default config.yaml:`, err);
135
+ }
136
+ raw = defaults;
137
+ }
138
+ // Apply env var overrides for bootstrap keys
139
+ for (const [envKey, configPath_] of Object.entries(ENV_VAR_MAP)) {
140
+ const envValue = process.env[envKey];
141
+ if (envValue === undefined || envValue === '')
142
+ continue;
143
+ const coerced = configPath_ === 'server.port'
144
+ ? Number(envValue)
145
+ : configPath_ === 'tls.enabled'
146
+ ? envValue === 'true'
147
+ : envValue;
148
+ raw = setNested(raw, configPath_, coerced);
149
+ console.log(`[Phase1] Env override: ${envKey} → ${configPath_}`);
150
+ }
151
+ return config_schema_1.bootstrapSchema.parse(raw);
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // autoGenerateJwtSecret
155
+ // ---------------------------------------------------------------------------
156
+ /**
157
+ * If `jwtSecret` is null in the parsed config, generate a random secret,
158
+ * persist it to the YAML file, and return an updated config.
159
+ */
160
+ function autoGenerateJwtSecret(configPath, bootstrapConfig) {
161
+ if (bootstrapConfig.auth.jwtSecret !== null) {
162
+ return bootstrapConfig;
163
+ }
164
+ const secret = (0, node_crypto_1.randomBytes)(32).toString('hex');
165
+ console.log('[Phase1] jwtSecret is null — auto-generating and writing to config.yaml');
166
+ let raw = {};
167
+ if (fs.existsSync(configPath)) {
168
+ raw = (0, types_1.asJsonObject)(yaml.load(fs.readFileSync(configPath, 'utf-8'))) ?? {};
169
+ }
170
+ const authSection = (0, types_1.asJsonObject)(raw.auth) ?? {};
171
+ raw.auth = { ...authSection, jwtSecret: secret };
172
+ const tmpPath = `${configPath}.tmp`;
173
+ try {
174
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
175
+ fs.writeFileSync(tmpPath, yaml.dump(raw, { lineWidth: 120, indent: 2, quotingType: '"' }), 'utf-8');
176
+ fs.renameSync(tmpPath, configPath);
177
+ }
178
+ catch (err) {
179
+ console.warn('[Phase1] Could not write auto-generated jwtSecret to config.yaml:', err);
180
+ }
181
+ return {
182
+ ...bootstrapConfig,
183
+ auth: { ...bootstrapConfig.auth, jwtSecret: secret },
184
+ };
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // setupInfra
188
+ // ---------------------------------------------------------------------------
189
+ /**
190
+ * Phase 2: Create StorageLocationManager, ensure directories, configure TLS.
191
+ * Returns the full InfraContext needed by subsequent boot phases.
192
+ */
193
+ async function setupInfra(configPath, bootstrapConfig) {
194
+ // Auto-generate jwtSecret if not set
195
+ const config = autoGenerateJwtSecret(configPath, bootstrapConfig);
196
+ const dataPath = path.resolve(config.server.dataPath);
197
+ const port = config.server.port;
198
+ const host = config.server.host;
199
+ console.log(`[Phase1] Bootstrap: port=${port}, host=${host}, dataPath=${dataPath}`);
200
+ // --- Phase 2: Init StorageLocationManager → ensure all dirs exist ---
201
+ console.log('[Phase2] Initializing storage locations…');
202
+ const locationManager = new storage_location_manager_1.StorageLocationManager(dataPath);
203
+ await locationManager.initializeDefaults();
204
+ const locationStatus = locationManager.getStatus();
205
+ for (const { name, available, path: locPath } of locationStatus) {
206
+ console.log(`[Phase2] Location "${name}": ${available ? 'OK' : 'UNAVAILABLE'} → ${locPath}`);
207
+ }
208
+ // --- Phase 2c: TLS certificate setup ---
209
+ let tlsOptions;
210
+ if (config.tls.enabled) {
211
+ // Use require() instead of import() — the ESM build of @camstack/core has
212
+ // broken chunks with require("fs") when leaked .js files exist in core/src/.
213
+ // CJS build works correctly and tsx supports require().
214
+ const core = require('@camstack/core');
215
+ const { ensureTlsCert, loadTlsCert } = core;
216
+ if (config.tls.certPath && config.tls.keyPath) {
217
+ // User-provided cert
218
+ console.log(`[Phase2c] Loading custom TLS cert from ${config.tls.certPath}`);
219
+ const pair = loadTlsCert(config.tls.certPath, config.tls.keyPath);
220
+ tlsOptions = { key: pair.key, cert: pair.cert };
221
+ }
222
+ else {
223
+ // Auto-generate self-signed
224
+ const tlsResult = await ensureTlsCert(dataPath);
225
+ if (tlsResult.generated) {
226
+ console.log(`[Phase2c] Generated self-signed TLS cert at ${tlsResult.certPath}`);
227
+ }
228
+ else {
229
+ console.log(`[Phase2c] Using existing TLS cert at ${tlsResult.certPath}`);
230
+ }
231
+ const pair = loadTlsCert(tlsResult.certPath, tlsResult.keyPath);
232
+ tlsOptions = { key: pair.key, cert: pair.cert };
233
+ }
234
+ }
235
+ return {
236
+ bootstrapConfig: config,
237
+ dataPath,
238
+ locationManager,
239
+ tlsOptions,
240
+ };
241
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * One-time integration-id backfill for devices created before the
4
+ * device-manager forwarder started stamping `integrationId` (camera
5
+ * providers). Maps each addon that hosts EXACTLY ONE integration to that
6
+ * integration id, then stamps top-level untagged devices of those addons.
7
+ * Multi-instance addons (e.g. Home Assistant with several brokers) are
8
+ * ambiguous on `addonId` alone and are skipped — they stamp going forward.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.planIntegrationIdBackfill = planIntegrationIdBackfill;
12
+ exports.planDeleteTimeStamps = planDeleteTimeStamps;
13
+ exports.runIntegrationIdBackfill = runIntegrationIdBackfill;
14
+ function planIntegrationIdBackfill(integrations, devices) {
15
+ const singleByAddon = new Map();
16
+ const ambiguous = new Set();
17
+ for (const integration of integrations) {
18
+ if (ambiguous.has(integration.addonId))
19
+ continue;
20
+ if (singleByAddon.has(integration.addonId)) {
21
+ singleByAddon.delete(integration.addonId);
22
+ ambiguous.add(integration.addonId);
23
+ continue;
24
+ }
25
+ singleByAddon.set(integration.addonId, integration.id);
26
+ }
27
+ const stamps = [];
28
+ for (const device of devices) {
29
+ if (device.parentDeviceId !== null)
30
+ continue;
31
+ if (device.integrationId !== undefined && device.integrationId !== '')
32
+ continue;
33
+ const integrationId = singleByAddon.get(device.addonId);
34
+ if (integrationId === undefined)
35
+ continue;
36
+ stamps.push({ deviceId: device.id, integrationId });
37
+ }
38
+ return stamps;
39
+ }
40
+ /**
41
+ * Stamps to apply at integration-DELETE time so a cascade removes legacy
42
+ * un-tagged devices too. The boot backfill only runs on startup; a device
43
+ * created before stamping (or whose provider never stamps, e.g. `provider-rtsp`)
44
+ * keeps no `integrationId`, so once its integration is deleted it would orphan
45
+ * forever — `removeByIntegration` matches on `integrationId` and finds nothing.
46
+ *
47
+ * Run this in the delete handler BEFORE deleting the integration record (while
48
+ * it is still present in `integrations`), then stamp the returned devices and
49
+ * let `removeByIntegration` cascade them. Reuses the boot backfill's safety
50
+ * rule (only addons hosting exactly ONE integration are unambiguous) and
51
+ * filters to the integration being deleted so siblings are never touched.
52
+ */
53
+ function planDeleteTimeStamps(integrationId, integrations, devices) {
54
+ return planIntegrationIdBackfill(integrations, devices).filter((stamp) => stamp.integrationId === integrationId);
55
+ }
56
+ async function runIntegrationIdBackfill(deps) {
57
+ const [integrations, devices] = await Promise.all([deps.listIntegrations(), deps.listDevices()]);
58
+ const stamps = planIntegrationIdBackfill(integrations, devices);
59
+ let stamped = 0;
60
+ for (const stamp of stamps) {
61
+ try {
62
+ await deps.setIntegrationId(stamp.deviceId, stamp.integrationId);
63
+ stamped++;
64
+ }
65
+ catch (err) {
66
+ deps.logger.warn('integrationId backfill: stamp failed', {
67
+ deviceId: stamp.deviceId,
68
+ integrationId: stamp.integrationId,
69
+ error: err instanceof Error ? err.message : String(err),
70
+ });
71
+ }
72
+ }
73
+ if (stamped > 0)
74
+ deps.logger.info('integrationId backfill complete', { stamped });
75
+ return { stamped };
76
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostBootService = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const kernel_1 = require("@camstack/kernel");
6
+ const types_1 = require("@camstack/types");
7
+ class PostBootService {
8
+ eventBus;
9
+ logger;
10
+ /**
11
+ * Holds the marker that fired `system.restart-completed` on this boot
12
+ * for `LAST_RESTART_RETENTION_MS`. Lets clients query the marker
13
+ * after they reconnect — the live event itself is emitted before the
14
+ * WS resubscribe has a chance to land.
15
+ */
16
+ static lastRestart = null;
17
+ /** How long the last-restart marker stays queryable after boot (5 min). */
18
+ static LAST_RESTART_RETENTION_MS = 5 * 60_000;
19
+ constructor(_addonRegistry, eventBus, loggingService) {
20
+ this.eventBus = eventBus;
21
+ this.logger = loggingService.createLogger('PostBoot');
22
+ }
23
+ /**
24
+ * Snapshot of the most-recent restart marker, or `null` after the
25
+ * retention window. The hub's `addons.getLastRestart` cap method
26
+ * delegates here.
27
+ */
28
+ static getLastRestart() {
29
+ const entry = PostBootService.lastRestart;
30
+ if (entry === null)
31
+ return null;
32
+ if (entry.expiresAt <= Date.now()) {
33
+ PostBootService.lastRestart = null;
34
+ return null;
35
+ }
36
+ return entry.payload;
37
+ }
38
+ async run(context) {
39
+ const { port, host, dataPath, trpcRegistered } = context;
40
+ // Device stream wiring moved into `addon-stream-broker` — the addon
41
+ // does its own initial sync against `ctx.deviceRegistry` plus listens
42
+ // to `DeviceRegistered` events for future registrations. No kernel
43
+ // orchestration shim needed here anymore.
44
+ // Emit system.boot event
45
+ this.eventBus.emit({
46
+ id: (0, node_crypto_1.randomUUID)(),
47
+ timestamp: new Date(),
48
+ source: { type: 'core', id: 'system' },
49
+ category: types_1.EventCategory.SystemBoot,
50
+ data: { port, host, trpcRegistered, dataPath },
51
+ });
52
+ // If the previous shutdown was driven by RestartCoordinator, emit a
53
+ // `system.restart-completed` event so the admin UI can surface a
54
+ // success toast describing what changed (framework update, manual
55
+ // restart, …). `readPendingRestart` clears the marker atomically so
56
+ // we never re-fire on a crash-loop boot.
57
+ this.emitRestartCompletedIfPending(dataPath);
58
+ }
59
+ emitRestartCompletedIfPending(dataDir) {
60
+ const marker = (0, kernel_1.readPendingRestart)(dataDir);
61
+ if (marker === null)
62
+ return;
63
+ const payload = {
64
+ kind: marker.kind,
65
+ requestedAt: marker.requestedAt,
66
+ ...(marker.packageName !== undefined ? { packageName: marker.packageName } : {}),
67
+ ...(marker.fromVersion !== undefined ? { fromVersion: marker.fromVersion } : {}),
68
+ ...(marker.toVersion !== undefined ? { toVersion: marker.toVersion } : {}),
69
+ ...(marker.requestedBy !== undefined ? { requestedBy: marker.requestedBy } : {}),
70
+ };
71
+ this.logger.info('Restart completed', { meta: payload });
72
+ PostBootService.lastRestart = {
73
+ payload,
74
+ expiresAt: Date.now() + PostBootService.LAST_RESTART_RETENTION_MS,
75
+ };
76
+ this.eventBus.emit({
77
+ id: (0, node_crypto_1.randomUUID)(),
78
+ timestamp: new Date(),
79
+ source: { type: 'core', id: 'system' },
80
+ category: types_1.EventCategory.SystemRestartCompleted,
81
+ data: payload,
82
+ });
83
+ }
84
+ }
85
+ exports.PostBootService = PostBootService;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AddonCallGateway = void 0;
4
+ const REMOTE_TIMEOUT_MS = 10_000;
5
+ class AddonCallGateway {
6
+ deps;
7
+ constructor(deps) {
8
+ this.deps = deps;
9
+ }
10
+ /**
11
+ * Classify where an addon runs. `nodeId: 'hub'` from a caller means "the hub
12
+ * cluster", NOT "force in-process" — a forked hub-local addon is still a
13
+ * `hub-local-child` (UDS), never the in-process path (which is only the
14
+ * `@camstack/core` builtins that have no forked runner).
15
+ */
16
+ classify(addonId, explicitNodeId) {
17
+ const resolved = this.deps.resolveNode(addonId);
18
+ const onHub = resolved === 'hub' || resolved === this.deps.hubNodeId;
19
+ if (onHub) {
20
+ const childRegistry = this.deps.getChildRegistry();
21
+ if (childRegistry !== null && childRegistry.isChildKnown(addonId)) {
22
+ return { kind: 'hub-local-child' };
23
+ }
24
+ return { kind: 'in-process' };
25
+ }
26
+ const onThisHub = explicitNodeId === 'hub' || explicitNodeId === this.deps.hubNodeId;
27
+ const baseNodeId = explicitNodeId && !onThisHub ? explicitNodeId : resolved;
28
+ return { kind: 'remote-agent', baseNodeId };
29
+ }
30
+ /** True when the addon is an in-process hub builtin (caller invokes directly). */
31
+ isInProcess(addonId, explicitNodeId) {
32
+ return this.classify(addonId, explicitNodeId).kind === 'in-process';
33
+ }
34
+ /**
35
+ * Dispatch a forked addon-level call to wherever the addon runs:
36
+ * - `hub-local-child` → UDS `LocalChildRegistry.callAddonOnChild`
37
+ * - `remote-agent` → Moleculer `broker.call`
38
+ * Throws for `in-process` — that addon has no forked surface, so the caller
39
+ * must invoke the in-process instance directly (the invocation is
40
+ * surface-specific; only the ROUTING is centralised here).
41
+ */
42
+ async callForked(addonId, input, explicitNodeId) {
43
+ const dest = this.classify(addonId, explicitNodeId);
44
+ const fullInput = { ...input, addonId };
45
+ switch (dest.kind) {
46
+ case 'hub-local-child': {
47
+ const childRegistry = this.deps.getChildRegistry();
48
+ if (childRegistry === null) {
49
+ throw new Error(`AddonCallGateway: child registry unavailable for "${addonId}"`);
50
+ }
51
+ return childRegistry.callAddonOnChild(addonId, fullInput);
52
+ }
53
+ case 'remote-agent':
54
+ return this.callRemoteAgent(addonId, dest.baseNodeId, fullInput);
55
+ case 'in-process':
56
+ throw new Error(`AddonCallGateway: addon "${addonId}" runs in-process — invoke it directly`);
57
+ default: {
58
+ const _exhaustive = dest;
59
+ throw new Error(`AddonCallGateway: unhandled destination ${JSON.stringify(_exhaustive)}`);
60
+ }
61
+ }
62
+ }
63
+ /** Map an addon-level call to the remote agent's Moleculer action. */
64
+ async callRemoteAgent(addonId, baseNodeId, input) {
65
+ const workerNodeId = this.resolveWorkerNodeId(addonId, baseNodeId);
66
+ const opts = workerNodeId
67
+ ? { nodeID: workerNodeId, timeout: REMOTE_TIMEOUT_MS }
68
+ : { timeout: REMOTE_TIMEOUT_MS };
69
+ if (input.target === 'settings') {
70
+ if (input.method == null) {
71
+ throw new Error(`AddonCallGateway: settings call to "${addonId}" missing method`);
72
+ }
73
+ return this.deps.broker.call(`${addonId}.settings.${input.method}`, (input.args ?? {}), opts);
74
+ }
75
+ // routes/custom are hub-local-child surfaces (mounted / invoked on the
76
+ // owning node); they are not proxied to a remote agent through this gateway.
77
+ throw new Error(`AddonCallGateway: target "${input.target}" not supported for remote agent "${baseNodeId}"`);
78
+ }
79
+ /**
80
+ * Resolve the Moleculer nodeID that actually hosts an addon's service.
81
+ * Forkable addons register under `${baseNodeId}/${addonId}`; in-process
82
+ * addons under the base nodeId. The registry is the ground truth — baseNodeId
83
+ * is a hint. (Moved verbatim from `addon-settings-provider.ts`.)
84
+ */
85
+ resolveWorkerNodeId(addonId, baseNodeId) {
86
+ const registry = this.deps.broker.registry;
87
+ const services = registry.getServiceList({ onlyAvailable: true });
88
+ const exactNode = `${baseNodeId}/${addonId}`;
89
+ const preferred = services.find((s) => s.name === addonId && s.nodeID === exactNode);
90
+ if (preferred)
91
+ return preferred.nodeID;
92
+ const anyForBase = services.find((s) => s.name === addonId && s.nodeID === baseNodeId);
93
+ if (anyForBase)
94
+ return anyForBase.nodeID;
95
+ const anyWithName = services.find((s) => s.name === addonId);
96
+ return anyWithName?.nodeID ?? null;
97
+ }
98
+ }
99
+ exports.AddonCallGateway = AddonCallGateway;