@camstack/server 1.2.460 → 1.2.462

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.
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getDeployStageRegistry = getDeployStageRegistry;
37
37
  exports.registerDeployBundleRoute = registerDeployBundleRoute;
38
- exports.hubBundleBaseUrl = hubBundleBaseUrl;
39
38
  exports.buildHubHttpSource = buildHubHttpSource;
40
39
  exports.registerAddonUploadRoute = registerAddonUploadRoute;
41
40
  const fs = __importStar(require("node:fs"));
@@ -94,9 +93,14 @@ function readMultipartTextField(fields, name) {
94
93
  const value = field.value;
95
94
  return typeof value === 'string' ? value : null;
96
95
  }
97
- /** Base URL agents pull staged deploy bundles from. */
98
- function hubBundleBaseUrl() {
99
- return process.env['CAMSTACK_HUB_PUBLIC_URL'] ?? 'https://127.0.0.1:4443';
96
+ /** Resolve the agent-facing base URL, turning a refusal into its reason. */
97
+ async function resolveBaseUrlOrReason(resolve) {
98
+ try {
99
+ return { ok: true, baseUrl: await resolve() };
100
+ }
101
+ catch (err) {
102
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
103
+ }
100
104
  }
101
105
  function buildHubHttpSource(buffer, hubBaseUrl) {
102
106
  const staged = getDeployStageRegistry().stage(buffer);
@@ -109,6 +113,11 @@ function buildHubHttpSource(buffer, hubBaseUrl) {
109
113
  };
110
114
  }
111
115
  async function registerAddonUploadRoute(fastify, addonBridge, authService, moleculer, addonRegistry, addonPackageService, logger,
116
+ /**
117
+ * The base URL agents pull the staged bundle from — the `local-network`
118
+ * cap's cluster address (`hub-cluster-url.ts`), resolved per delivery.
119
+ */
120
+ resolveClusterBaseUrl,
112
121
  /**
113
122
  * Marks the "hub is writing to this node" window around every agent
114
123
  * delivery so the addon back-fill cannot mistake the roster gap a deploy
@@ -171,12 +180,11 @@ deliveryLedger) {
171
180
  // the hub AND broadcasts to every connected agent that can run any of
172
181
  // the package's addons (i.e. anything not marked hub-only). Any other
173
182
  // explicit `nodeId` value routes only to that agent via `$agent.deploy`.
174
- const baseUrl = hubBundleBaseUrl();
175
183
  if (!nodeId || nodeId === 'hub') {
176
- return installToHub(reply, addonBridge, addonRegistry, addonPackageService, moleculer, logger, data.filename, buffer, baseUrl, deliveryLedger);
184
+ return installToHub(reply, addonBridge, addonRegistry, addonPackageService, moleculer, logger, data.filename, buffer, resolveClusterBaseUrl, deliveryLedger);
177
185
  }
178
186
  const agentAddonId = addonIdHint ?? manifest.name;
179
- return deployToAgent(reply, moleculer, logger, nodeId, agentAddonId, buffer, baseUrl, deliveryLedger);
187
+ return deployToAgent(reply, moleculer, logger, nodeId, agentAddonId, buffer, resolveClusterBaseUrl, deliveryLedger);
180
188
  });
181
189
  }
182
190
  /**
@@ -214,7 +222,7 @@ function packageHasAgentDeployable(addonsDir, packageName) {
214
222
  * without an agent restart. Agents that fail are reported per-node — one
215
223
  * unreachable agent must not block the others or the hub install.
216
224
  */
217
- async function propagateToAgents(moleculer, logger, packageName, buffer, hubBaseUrl, deliveryLedger) {
225
+ async function propagateToAgents(moleculer, logger, packageName, buffer, resolveClusterBaseUrl, deliveryLedger) {
218
226
  const broker = moleculer.broker;
219
227
  const nodes = broker.registry?.getNodeList?.({ onlyAvailable: true }) ?? [];
220
228
  // Moleculer reports both top-level nodes (`hub`, `dev-agent-0`, …) AND
@@ -233,6 +241,13 @@ async function propagateToAgents(moleculer, logger, packageName, buffer, hubBase
233
241
  });
234
242
  if (agentNodeIds.length === 0)
235
243
  return [];
244
+ const hubBaseUrl = await resolveBaseUrlOrReason(resolveClusterBaseUrl);
245
+ if (!hubBaseUrl.ok) {
246
+ logger.error('propagate: no hub address an agent can reach — nothing delivered', {
247
+ meta: { packageName, targetAgents: agentNodeIds, error: hubBaseUrl.error },
248
+ });
249
+ return agentNodeIds.map((nodeId) => ({ nodeId, success: false, error: hubBaseUrl.error }));
250
+ }
236
251
  const results = [];
237
252
  for (const nodeId of agentNodeIds) {
238
253
  // The hub currently only ever sends the `hub-http` source (agent streams
@@ -246,7 +261,7 @@ async function propagateToAgents(moleculer, logger, packageName, buffer, hubBase
246
261
  logger,
247
262
  nodeId,
248
263
  packageName,
249
- source: buildHubHttpSource(buffer, hubBaseUrl),
264
+ source: buildHubHttpSource(buffer, hubBaseUrl.baseUrl),
250
265
  ...(deliveryLedger === undefined ? {} : { deliveryLedger }),
251
266
  }));
252
267
  }
@@ -261,7 +276,7 @@ async function propagateToAgents(moleculer, logger, packageName, buffer, hubBase
261
276
  * Without this the CLI push was write-to-disk-only and required a server
262
277
  * restart to actually run the new code.
263
278
  */
264
- async function installToHub(reply, addonBridge, addonRegistry, addonPackageService, moleculer, logger, filename, buffer, hubBaseUrl, deliveryLedger) {
279
+ async function installToHub(reply, addonBridge, addonRegistry, addonPackageService, moleculer, logger, filename, buffer, resolveClusterBaseUrl, deliveryLedger) {
265
280
  const tmpDir = path.join(os.tmpdir(), `camstack-addon-upload-${Date.now()}`);
266
281
  fs.mkdirSync(tmpDir, { recursive: true });
267
282
  const tgzPath = path.join(tmpDir, filename);
@@ -386,7 +401,7 @@ async function installToHub(reply, addonBridge, addonRegistry, addonPackageServi
386
401
  });
387
402
  }
388
403
  if (propagatable) {
389
- void propagateToAgents(moleculer, logger, result.name, buffer, hubBaseUrl, deliveryLedger).then((agentResults) => {
404
+ void propagateToAgents(moleculer, logger, result.name, buffer, resolveClusterBaseUrl, deliveryLedger).then((agentResults) => {
390
405
  logger.info('propagation done', {
391
406
  meta: { packageName: result.name, agents: agentResults },
392
407
  });
@@ -417,7 +432,14 @@ async function installToHub(reply, addonBridge, addonRegistry, addonPackageServi
417
432
  * evidence. The response now carries what was reloaded and what was verified,
418
433
  * so the CLI can print facts instead of a bare tick.
419
434
  */
420
- async function deployToAgent(reply, moleculer, logger, nodeId, addonId, buffer, hubBaseUrl, deliveryLedger) {
435
+ async function deployToAgent(reply, moleculer, logger, nodeId, addonId, buffer, resolveClusterBaseUrl, deliveryLedger) {
436
+ const hubBaseUrl = await resolveBaseUrlOrReason(resolveClusterBaseUrl);
437
+ if (!hubBaseUrl.ok) {
438
+ logger.error('agent deploy: no hub address the agent can reach — nothing delivered', {
439
+ meta: { nodeId, addonId, error: hubBaseUrl.error },
440
+ });
441
+ return reply.status(502).send({ error: `Agent deploy failed: ${hubBaseUrl.error}` });
442
+ }
421
443
  // The hub currently only ever sends the `hub-http` source (agent streams the
422
444
  // tgz directly from this hub). The `{kind:'npm'}` source is a reserved
423
445
  // follow-up (published-addon optimization) and is intentionally not produced
@@ -429,7 +451,7 @@ async function deployToAgent(reply, moleculer, logger, nodeId, addonId, buffer,
429
451
  logger,
430
452
  nodeId,
431
453
  packageName: addonId,
432
- source: buildHubHttpSource(buffer, hubBaseUrl),
454
+ source: buildHubHttpSource(buffer, hubBaseUrl.baseUrl),
433
455
  ...(deliveryLedger === undefined ? {} : { deliveryLedger }),
434
456
  });
435
457
  if (!result.success) {
@@ -18,8 +18,9 @@
18
18
  * - {@link OPAQUE_ORIGIN} (`null`): the native app's OTA-bundled embed is a
19
19
  * `file://` page inside a WKWebView, whose origin is opaque. Removing it
20
20
  * breaks live view on iOS;
21
- * - `CAMSTACK_HUB_PUBLIC_URL`, when set, and the operator's extras
22
- * (`server.corsOrigins` in config.yaml / `CAMSTACK_CORS_ORIGINS`).
21
+ * - the operator's extras (`server.corsOrigins` in config.yaml /
22
+ * `CAMSTACK_CORS_ORIGINS`). The hub's own public address needs no entry:
23
+ * a browser on it is the `own-host` case above.
23
24
  *
24
25
  * The answer to an allowed origin REFLECTS it exactly — never `*` — and never
25
26
  * carries `Access-Control-Allow-Credentials`. The cross-origin viewer
@@ -111,13 +112,9 @@ function parseConfiguredOrigins(value) {
111
112
  }
112
113
  return { origins, rejected };
113
114
  }
114
- /** The built-in entries plus the optional public origin. */
115
- function builtInOrigins(publicOrigin) {
116
- const entries = [exports.VIEWER_PWA_ORIGIN, exports.OPAQUE_ORIGIN];
117
- const pub = publicOrigin !== undefined ? normalizeOrigin(publicOrigin) : null;
118
- if (pub !== null && pub !== exports.OPAQUE_ORIGIN)
119
- entries.push(pub);
120
- return entries;
115
+ /** The built-in entries. */
116
+ function builtInOrigins() {
117
+ return [exports.VIEWER_PWA_ORIGIN, exports.OPAQUE_ORIGIN];
121
118
  }
122
119
  /** The first value of a possibly comma-joined forwarding header, lower case. */
123
120
  function firstHost(value) {
@@ -19,7 +19,7 @@ function varyText(value) {
19
19
  return undefined;
20
20
  }
21
21
  function createCorsPolicy(deps) {
22
- const builtIns = (0, cors_policy_js_1.builtInOrigins)(deps.publicOrigin);
22
+ const builtIns = (0, cors_policy_js_1.builtInOrigins)();
23
23
  let lastConfigured = Symbol('unset');
24
24
  let listed = new Set(builtIns);
25
25
  const loggedDenials = new Set();
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HubAddressUnavailableError = void 0;
4
+ exports.resolveHubBaseUrl = resolveHubBaseUrl;
5
+ exports.clusterBaseUrlResolver = clusterBaseUrlResolver;
6
+ /** Thrown when the hub cannot name an address `audience` can reach. */
7
+ class HubAddressUnavailableError extends Error {
8
+ audience;
9
+ reason;
10
+ constructor(audience, reason) {
11
+ super(reason === 'local-network-unavailable'
12
+ ? `cannot name this hub's ${audience} address: the local-network capability is not available yet`
13
+ : `cannot name this hub's ${audience} address: Network lists no reachable LAN address ` +
14
+ '(flag an address for this in Settings → Network, or check the allowed addresses)');
15
+ this.audience = audience;
16
+ this.reason = reason;
17
+ this.name = 'HubAddressUnavailableError';
18
+ }
19
+ }
20
+ exports.HubAddressUnavailableError = HubAddressUnavailableError;
21
+ /**
22
+ * Resolve the hub's base URL for `audience` from the live provider. Called per
23
+ * delivery — interfaces and the allowlist move, and a deploy is rare enough
24
+ * that a cache would only add a way to be stale.
25
+ */
26
+ async function resolveHubBaseUrl(getSource, audience) {
27
+ const source = getSource();
28
+ if (source === null)
29
+ throw new HubAddressUnavailableError(audience, 'local-network-unavailable');
30
+ const baseUrl = (await source.getAddresses({})).roles[audience].resolved;
31
+ if (baseUrl === null)
32
+ throw new HubAddressUnavailableError(audience, 'no-reachable-endpoint');
33
+ return baseUrl;
34
+ }
35
+ function clusterBaseUrlResolver(getSource) {
36
+ return () => resolveHubBaseUrl(getSource, 'agents');
37
+ }
@@ -36,18 +36,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.tarModelFiles = tarModelFiles;
37
37
  exports.distributeModelWith = distributeModelWith;
38
38
  exports.buildModelDistributorProvider = buildModelDistributorProvider;
39
+ const node_child_process_1 = require("node:child_process");
39
40
  const fs = __importStar(require("node:fs"));
40
41
  const path = __importStar(require("node:path"));
41
- const node_child_process_1 = require("node:child_process");
42
42
  const system_1 = require("@camstack/system");
43
43
  const addon_upload_js_1 = require("./addon-upload.js");
44
44
  const AGENT_DISTRIBUTE_TIMEOUT_MS = 60_000;
45
45
  function hubModelsDir() {
46
46
  return path.join(process.env['CAMSTACK_DATA'] ?? 'camstack-data', 'models');
47
47
  }
48
- function hubBundleBaseUrl() {
49
- return process.env['CAMSTACK_HUB_PUBLIC_URL'] ?? 'https://127.0.0.1:4443';
50
- }
51
48
  /**
52
49
  * `tar -czf -` the given files relative to `cwd`, buffering the gzip stream.
53
50
  * Async (spawn, not execFileSync) so a large model doesn't block the event
@@ -87,7 +84,7 @@ async function distributeModelWith(seam, input) {
87
84
  throw new Error(`model ${modelId}/${format} resolved no files to distribute`);
88
85
  }
89
86
  const tgz = await seam.tar(files);
90
- const source = seam.stage(tgz);
87
+ const source = await seam.stage(tgz);
91
88
  const res = await seam.callAgent(targetNodeId, { modelId, format, source });
92
89
  return { ok: true, sha256: source.sha256, bytes: source.bytes, path: res.path ?? '' };
93
90
  }
@@ -97,7 +94,9 @@ async function distributeModelWith(seam, input) {
97
94
  * Moleculer broker + the deploy-stage registry, which a forked addon can't
98
95
  * reach.
99
96
  */
100
- function buildModelDistributorProvider(moleculer, loggingService) {
97
+ function buildModelDistributorProvider(moleculer, loggingService,
98
+ /** The `local-network` cap's cluster address (`hub-cluster-url.ts`). */
99
+ resolveClusterBaseUrl) {
101
100
  const logger = loggingService.createLogger('model-distributor');
102
101
  const modelsDir = hubModelsDir();
103
102
  const seam = {
@@ -105,7 +104,7 @@ function buildModelDistributorProvider(moleculer, loggingService) {
105
104
  isPresent: (entry, format) => (0, system_1.isModelDownloaded)(modelsDir, entry, format),
106
105
  fileExists: (rel) => fs.existsSync(path.join(modelsDir, rel)),
107
106
  tar: (files) => tarModelFiles(modelsDir, files),
108
- stage: (buffer) => (0, addon_upload_js_1.buildHubHttpSource)(buffer, hubBundleBaseUrl()),
107
+ stage: async (buffer) => (0, addon_upload_js_1.buildHubHttpSource)(buffer, await resolveClusterBaseUrl()),
109
108
  callAgent: async (nodeId, payload) => {
110
109
  const broker = moleculer?.broker;
111
110
  if (!broker)
@@ -463,7 +463,7 @@ function registerOauth2Routes(fastify, deps) {
463
463
  // external-access endpoint surfaced by a forked exporter addon) so the
464
464
  // claim the cloud Lambda routes back on is the reachable public URL, not
465
465
  // the hub-global fallback (which defaults to localhost in dev).
466
- hubUrl: descriptor.hubUrl ?? deps.publicHubUrl(),
466
+ hubUrl: descriptor.hubUrl ?? (await deps.publicHubUrl()),
467
467
  ...(v.codeChallenge !== '' ? { codeChallenge: v.codeChallenge } : {}),
468
468
  // The integration declares its own refresh lifetime; omitting the field
469
469
  // keeps the 30-day default. Baked in HERE, at consent, so the link the
@@ -4912,6 +4912,47 @@ function createCapRouter_localNetwork(getProvider, createRemoteProxy) {
4912
4912
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4913
4913
  return p.setViewerEndpoints(methodInput);
4914
4914
  }),
4915
+ getAddresses: trpc_middleware_js_1.protectedProcedure
4916
+ .input(types_65.localNetworkCapability.methods.getAddresses.input)
4917
+ .output(types_65.localNetworkCapability.methods.getAddresses.output)
4918
+ .query(async ({ input, ctx }) => {
4919
+ const p = requireCapProvider('local-network', () => getProvider(ctx));
4920
+ return p.getAddresses(input);
4921
+ }),
4922
+ addCustomAddress: trpc_middleware_js_1.adminProcedure
4923
+ .input(types_65.localNetworkCapability.methods.addCustomAddress.input.loose())
4924
+ .output(types_65.localNetworkCapability.methods.addCustomAddress.output)
4925
+ .mutation(async ({ input, ctx }) => {
4926
+ const { nodeId, ...methodInput } = input;
4927
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
4928
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4929
+ return p.addCustomAddress(methodInput);
4930
+ }),
4931
+ removeCustomAddress: trpc_middleware_js_1.adminProcedure
4932
+ .input(types_65.localNetworkCapability.methods.removeCustomAddress.input.loose())
4933
+ .output(types_65.localNetworkCapability.methods.removeCustomAddress.output)
4934
+ .mutation(async ({ input, ctx }) => {
4935
+ const { nodeId, ...methodInput } = input;
4936
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
4937
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4938
+ return p.removeCustomAddress(methodInput);
4939
+ }),
4940
+ setAddressRole: trpc_middleware_js_1.adminProcedure
4941
+ .input(types_65.localNetworkCapability.methods.setAddressRole.input.loose())
4942
+ .output(types_65.localNetworkCapability.methods.setAddressRole.output)
4943
+ .mutation(async ({ input, ctx }) => {
4944
+ const { nodeId, ...methodInput } = input;
4945
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
4946
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4947
+ return p.setAddressRole(methodInput);
4948
+ }),
4949
+ getHubAddresses: trpc_middleware_js_1.protectedProcedure
4950
+ .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
4951
+ .output(types_65.localNetworkCapability.methods.getHubAddresses.output)
4952
+ .query(async ({ input, ctx }) => {
4953
+ const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
4954
+ return p.getHubAddresses();
4955
+ }),
4915
4956
  getAllowedAddresses: trpc_middleware_js_1.protectedProcedure
4916
4957
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
4917
4958
  .output(types_65.localNetworkCapability.methods.getAllowedAddresses.output)
@@ -11511,6 +11552,15 @@ function createCapRouter_webrtcSession(getProvider, createRemoteProxy) {
11511
11552
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11512
11553
  return p.getSessionState(methodInput);
11513
11554
  }),
11555
+ applyDownlinkProbe: trpc_middleware_js_1.protectedProcedure
11556
+ .input(types_136.webrtcSessionCapability.methods.applyDownlinkProbe.input.loose())
11557
+ .output(types_136.webrtcSessionCapability.methods.applyDownlinkProbe.output)
11558
+ .mutation(async ({ input, ctx }) => {
11559
+ const { nodeId, ...methodInput } = input;
11560
+ const p = resolveProvider('webrtc-session', nodeId, () => getProvider(ctx), createRemoteProxy);
11561
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
11562
+ return p.applyDownlinkProbe(methodInput);
11563
+ }),
11514
11564
  getSessionDebug: trpc_middleware_js_1.protectedProcedure
11515
11565
  .input(types_136.webrtcSessionCapability.methods.getSessionDebug.input.loose())
11516
11566
  .output(types_136.webrtcSessionCapability.methods.getSessionDebug.output)
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * The hub's address is not an environment variable.
4
+ *
5
+ * `CAMSTACK_HUB_PUBLIC_URL` (and its older alias `CAMSTACK_PUBLIC_ORIGIN`)
6
+ * restated something the hub already knows: its interfaces, its listen port,
7
+ * the operator's allowlist, its tunnel and mesh names — the `local-network`
8
+ * capability, shown and edited on the admin Network page. A hub without the
9
+ * variable (every Mac hub: the desktop app has no field for it) handed agents
10
+ * `https://127.0.0.1:4443`, which names the agent, and every addon and model
11
+ * pull to an agent failed. Every reader now asks `local-network`
12
+ * (`getAddresses().roles`); nothing reads the variables.
13
+ *
14
+ * A container that still sets one gets a WARN on every boot naming it as
15
+ * ignored, and the variable is removed from this process's environment before
16
+ * any addon runner is forked — so no child can grow a new reader of it.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.RETIRED_HUB_ADDRESS_ENVS = void 0;
20
+ exports.dropRetiredHubAddressEnv = dropRetiredHubAddressEnv;
21
+ /** Variables that used to name the hub's address; all ignored now. */
22
+ exports.RETIRED_HUB_ADDRESS_ENVS = [
23
+ 'CAMSTACK_HUB_PUBLIC_URL',
24
+ 'CAMSTACK_PUBLIC_ORIGIN',
25
+ ];
26
+ /**
27
+ * Remove every retired address variable from `env` and return the warning to
28
+ * log once a logger exists, or `undefined` when none was set.
29
+ */
30
+ function dropRetiredHubAddressEnv(env) {
31
+ const present = exports.RETIRED_HUB_ADDRESS_ENVS.filter((name) => {
32
+ const value = env[name];
33
+ return value !== undefined && value.trim() !== '';
34
+ });
35
+ for (const name of exports.RETIRED_HUB_ADDRESS_ENVS)
36
+ delete env[name];
37
+ if (present.length === 0)
38
+ return undefined;
39
+ return (`${present.join(' and ')} ${present.length === 1 ? 'is' : 'are'} ignored — the hub address ` +
40
+ 'comes from Network (Settings → Network). Remove it from this container.');
41
+ }
@@ -55,7 +55,7 @@ function decide(pkg, installed, closureVersions, closureProvided) {
55
55
  installedVersion: version,
56
56
  closureVersion,
57
57
  twoVersions: false,
58
- reason: 'provided by the server closure — a copy here would shadow it (D15)',
58
+ reason: 'provided by the server closure — a copy here would shadow it',
59
59
  };
60
60
  }
61
61
  if (version === null) {
@@ -78,7 +78,7 @@ function decide(pkg, installed, closureVersions, closureProvided) {
78
78
  closureVersion,
79
79
  twoVersions,
80
80
  reason: twoVersions
81
- ? `installed ${version}; this closure carries ${closureVersion} — the installed copy is what runs (D90)`
81
+ ? `installed ${version}; this closure carries ${closureVersion} — the installed copy is what runs`
82
82
  : `installed ${version} — kept`,
83
83
  };
84
84
  }
package/dist/main.js CHANGED
@@ -66,6 +66,7 @@ const request_census_settings_1 = require("./api/core/request-census-settings");
66
66
  const register_cors_policy_1 = require("./api/cors/register-cors-policy");
67
67
  const adaptive_probe_routes_1 = require("./api/health/adaptive-probe.routes");
68
68
  const health_routes_1 = require("./api/health/health.routes");
69
+ const hub_cluster_url_1 = require("./api/hub-cluster-url");
69
70
  const model_distributor_js_1 = require("./api/model-distributor.js");
70
71
  const oauth2_routes_js_1 = require("./api/oauth2/oauth2-routes.js");
71
72
  const server_upload_1 = require("./api/server-upload");
@@ -90,9 +91,9 @@ const session_cookie_js_1 = require("./auth/session-cookie.js");
90
91
  const session_mint_js_1 = require("./auth/session-mint.js");
91
92
  const sso_finish_js_1 = require("./auth/sso-finish.js");
92
93
  const boot_config_1 = require("./boot/boot-config");
93
- const public_url_1 = require("./boot/public-url");
94
94
  const integration_id_backfill_1 = require("./boot/integration-id-backfill");
95
95
  const post_boot_service_1 = require("./boot/post-boot.service");
96
+ const retired_public_url_env_1 = require("./boot/retired-public-url-env");
96
97
  const addon_package_service_1 = require("./core/addon/addon-package.service");
97
98
  const addon_registry_service_1 = require("./core/addon/addon-registry.service");
98
99
  const addon_bridge_service_1 = require("./core/addon-bridge/addon-bridge.service");
@@ -242,11 +243,11 @@ async function sendMaybePrecompressed(reply, absolutePath, acceptEncoding) {
242
243
  }
243
244
  // ---- Bootstrap ----
244
245
  async function bootstrap() {
245
- // ONE public-URL variable (CAMSTACK_HUB_PUBLIC_URL). The retired
246
- // CAMSTACK_PUBLIC_ORIGIN is folded into it here — before any addon runner is
247
- // forked, so every child inherits one name — and warned about once a logger
248
- // exists (below). See `boot/public-url.ts`.
249
- const publicUrlWarning = (0, public_url_1.applyHubPublicUrl)(process.env);
246
+ // The hub's address comes from the `local-network` cap, never from env. A
247
+ // retired address variable is dropped here — before any addon runner is
248
+ // forked, so no child can read it — and warned about once a logger exists
249
+ // (below). See `boot/retired-public-url-env.ts`.
250
+ const publicUrlWarning = (0, retired_public_url_env_1.dropRetiredHubAddressEnv)(process.env);
250
251
  // hub-main's own memory, on the record. Deliberately started HERE and not in
251
252
  // the starter: the starter runs from the image's /opt closure, which only
252
253
  // changes with a new image, while this entry is what `applyServerUpdate`
@@ -365,7 +366,6 @@ async function bootstrap() {
365
366
  const corsConfig = app.get(config_service_1.ConfigService);
366
367
  const corsPolicy = (0, register_cors_policy_1.createCorsPolicy)({
367
368
  configuredOrigins: () => corsConfig.get('server.corsOrigins'),
368
- publicOrigin: process.env[public_url_1.HUB_PUBLIC_URL_ENV],
369
369
  logger: app.get(logging_service_1.LoggingService).createLogger('cors'),
370
370
  });
371
371
  (0, register_cors_policy_1.registerCorsPolicy)(fastify, corsPolicy);
@@ -412,7 +412,9 @@ async function bootstrap() {
412
412
  // The delivery ledger marks "the hub is writing addons to this node" for
413
413
  // the whole deploy+reload, so the addon back-fill cannot read the roster
414
414
  // gap a deploy opens and repair the node over the bytes just shipped.
415
- await (0, addon_upload_1.registerAddonUploadRoute)(fastify, uploadAddonBridge, uploadAuthService, uploadMoleculer, uploadAddonRegistry, uploadAddonPackage, uploadLogger, app.get(agent_registry_service_1.AgentRegistryService).deliveryLedger);
415
+ await (0, addon_upload_1.registerAddonUploadRoute)(fastify, uploadAddonBridge, uploadAuthService, uploadMoleculer, uploadAddonRegistry, uploadAddonPackage, uploadLogger, (0, hub_cluster_url_1.clusterBaseUrlResolver)(() => uploadAddonRegistry
416
+ .getCapabilityRegistry()
417
+ .getSingleton('local-network')), app.get(agent_registry_service_1.AgentRegistryService).deliveryLedger);
416
418
  console.log('[bootstrap] Addon upload route registered at POST /api/addons/upload');
417
419
  // Dev server-deploy channel gateway (`camstack deploy-server`). MUST be
418
420
  // registered AFTER registerAddonUploadRoute — it reuses the multipart
@@ -592,7 +594,7 @@ async function bootstrap() {
592
594
  // router) so forked addons (addon-model-studio) can reach it via `ctx.api`,
593
595
  // not only external HTTP clients. Needs the broker + the deploy-stage
594
596
  // registry, which an addon can't access.
595
- capabilityRegistry.registerProvider('model-distributor', '$hub', (0, model_distributor_js_1.buildModelDistributorProvider)(moleculer, loggingService));
597
+ capabilityRegistry.registerProvider('model-distributor', '$hub', (0, model_distributor_js_1.buildModelDistributorProvider)(moleculer, loggingService, (0, hub_cluster_url_1.clusterBaseUrlResolver)(() => capabilityRegistry.getSingleton('local-network'))));
596
598
  // ── Health routes (hub self + agent forwarding) ──────────────────
597
599
  // Registered after app.init() so the AgentRegistryService is wired and
598
600
  // the Moleculer broker is ready to forward `$agent.health` calls.
@@ -1117,7 +1119,10 @@ async function bootstrap() {
1117
1119
  (0, oauth2_routes_js_1.registerOauth2Routes)(fastify, {
1118
1120
  getRegistry: () => capabilityRegistry,
1119
1121
  verifyToken: (t) => authService.verifyToken(t),
1120
- publicHubUrl: () => process.env[public_url_1.HUB_PUBLIC_URL_ENV] ?? `https://localhost:${port}`,
1122
+ // The hub's browser-facing address from the `local-network` cap (tunnel,
1123
+ // then mesh, then LAN). `localhost` only when the palette names nothing —
1124
+ // the dev case, where it is the truth.
1125
+ publicHubUrl: () => (0, hub_cluster_url_1.resolveHubBaseUrl)(() => capabilityRegistry.getSingleton('local-network'), 'signIn').catch(() => `https://localhost:${port}`),
1121
1126
  // Every /authorize refusal is a dropped account-linking attempt. Without
1122
1127
  // this scope the drop is invisible in Loki and the only signal left is an
1123
1128
  // operator noticing that linking stopped working.
@@ -51,6 +51,7 @@ const types_1 = require("@camstack/types");
51
51
  const fastify_1 = __importDefault(require("fastify"));
52
52
  const addon_upload_1 = require("./api/addon-upload");
53
53
  const cap_providers_1 = require("./api/core/cap-providers");
54
+ const hub_cluster_url_1 = require("./api/hub-cluster-url");
54
55
  const init_sequence_1 = require("./boot/init-sequence");
55
56
  const post_boot_service_1 = require("./boot/post-boot.service");
56
57
  const addon_package_service_1 = require("./core/addon/addon-package.service");
@@ -184,6 +185,11 @@ async function bootManual(opts) {
184
185
  // reverted ~1.5s later and read as a stale dist. See `packInstalledPackage`.
185
186
  // Delivery is an RPC pair, never an event (D8/D11).
186
187
  const backfillLogger = loggingService.createLogger('agent-backfill');
188
+ // Where the agent pulls the bundle from: the hub's own cluster address, from
189
+ // the `local-network` cap — never an env var, never loopback.
190
+ const resolveClusterBaseUrl = (0, hub_cluster_url_1.clusterBaseUrlResolver)(() => addonRegistryService
191
+ .getCapabilityRegistry()
192
+ .getSingleton('local-network'));
187
193
  agentRegistryService.setAddonBackfill({
188
194
  pack: async (name, version) => {
189
195
  const installed = await addonPackageService.packInstalledPackage(name);
@@ -200,7 +206,7 @@ async function bootManual(opts) {
200
206
  return (installed ?? (await addonPackageService.packPackage(name, version))).buffer;
201
207
  },
202
208
  deploy: async (nodeId, packageName, bundle) => {
203
- await moleculerService.broker.call('$agent.deploy', { addonId: packageName, source: (0, addon_upload_1.buildHubHttpSource)(bundle, (0, addon_upload_1.hubBundleBaseUrl)()) }, { nodeID: nodeId, timeout: agent_registry_service_1.AGENT_BACKFILL_RPC_TIMEOUT_MS });
209
+ await moleculerService.broker.call('$agent.deploy', { addonId: packageName, source: (0, addon_upload_1.buildHubHttpSource)(bundle, await resolveClusterBaseUrl()) }, { nodeID: nodeId, timeout: agent_registry_service_1.AGENT_BACKFILL_RPC_TIMEOUT_MS });
204
210
  },
205
211
  reload: async (nodeId) => {
206
212
  await moleculerService.broker.call('$agent.reload', {}, { nodeID: nodeId, timeout: agent_registry_service_1.AGENT_BACKFILL_RPC_TIMEOUT_MS });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.460",
3
+ "version": "1.2.462",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -37,18 +37,18 @@
37
37
  ]
38
38
  },
39
39
  "dependencies": {
40
- "@camstack/addon-admin-ui": "1.2.334",
41
- "@camstack/addon-agent-ui": "1.2.176",
42
- "@camstack/addon-auth": "1.2.127",
40
+ "@camstack/addon-admin-ui": "1.2.336",
41
+ "@camstack/addon-agent-ui": "1.2.178",
42
+ "@camstack/addon-auth": "1.2.129",
43
43
  "@camstack/addon-decoder-nodeav": "1.2.119",
44
44
  "@camstack/addon-notifiers": "1.2.128",
45
- "@camstack/addon-pipeline": "1.2.329",
46
- "@camstack/addon-pipeline-orchestrator": "1.2.284",
47
- "@camstack/addon-post-analysis": "1.2.336",
45
+ "@camstack/addon-pipeline": "1.2.331",
46
+ "@camstack/addon-pipeline-orchestrator": "1.2.286",
47
+ "@camstack/addon-post-analysis": "1.2.338",
48
48
  "@camstack/sdk": "1.2.122",
49
- "@camstack/system": "1.2.357",
50
- "@camstack/types": "1.2.308",
51
- "@camstack/ui-library": "1.2.234",
49
+ "@camstack/system": "1.2.359",
50
+ "@camstack/types": "1.2.310",
51
+ "@camstack/ui-library": "1.2.236",
52
52
  "@fastify/compress": "^9.0.0",
53
53
  "@fastify/cookie": "^11.0.2",
54
54
  "@fastify/cors": "^11.2.0",
@@ -1,67 +0,0 @@
1
- "use strict";
2
- /**
3
- * The hub's public URL — ONE environment variable, resolved once at boot.
4
- *
5
- * Two names used to answer the same question ("at which address do clients
6
- * reach this hub?"): `CAMSTACK_HUB_PUBLIC_URL` (addon bundle and model
7
- * distribution, notification links, the Dreame RFC host) and
8
- * `CAMSTACK_PUBLIC_ORIGIN` (magic-link, OIDC callback, WebAuthn relying party,
9
- * the OAuth2 issuer and the CORS allowlist, D707). An operator who set one got
10
- * half a configured hub and no line saying so.
11
- *
12
- * `CAMSTACK_HUB_PUBLIC_URL` is the survivor. `CAMSTACK_PUBLIC_ORIGIN` is still
13
- * accepted, for hubs that already set it, and costs a WARN on every boot until
14
- * it is renamed. The resolution writes the survivor back into `process.env`
15
- * and deletes the retired name, so every forked addon runner — which inherits
16
- * this process's environment — reads exactly one variable.
17
- */
18
- Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.RETIRED_PUBLIC_ORIGIN_ENV = exports.HUB_PUBLIC_URL_ENV = void 0;
20
- exports.resolveHubPublicUrl = resolveHubPublicUrl;
21
- exports.applyHubPublicUrl = applyHubPublicUrl;
22
- /** The single public-URL variable every reader uses. */
23
- exports.HUB_PUBLIC_URL_ENV = 'CAMSTACK_HUB_PUBLIC_URL';
24
- /** Retired alias, accepted with a deprecation warning. */
25
- exports.RETIRED_PUBLIC_ORIGIN_ENV = 'CAMSTACK_PUBLIC_ORIGIN';
26
- function readTrimmed(env, name) {
27
- const raw = env[name]?.trim();
28
- if (raw === undefined || raw === '')
29
- return undefined;
30
- return raw.replace(/\/+$/, '');
31
- }
32
- /** Pure: which URL wins, and what to warn about. */
33
- function resolveHubPublicUrl(env) {
34
- const current = readTrimmed(env, exports.HUB_PUBLIC_URL_ENV);
35
- const retired = readTrimmed(env, exports.RETIRED_PUBLIC_ORIGIN_ENV);
36
- if (retired === undefined)
37
- return { url: current, warning: undefined };
38
- if (current === undefined) {
39
- return {
40
- url: retired,
41
- warning: `${exports.RETIRED_PUBLIC_ORIGIN_ENV} is deprecated and was read as ${exports.HUB_PUBLIC_URL_ENV}=${retired}. ` +
42
- `Rename it to ${exports.HUB_PUBLIC_URL_ENV}; the old name will stop being read in a future release.`,
43
- };
44
- }
45
- const disagree = current !== retired;
46
- return {
47
- url: current,
48
- warning: `${exports.RETIRED_PUBLIC_ORIGIN_ENV} is deprecated and IGNORED because ${exports.HUB_PUBLIC_URL_ENV} is set` +
49
- (disagree ? ` (${current} wins over ${retired})` : '') +
50
- `. Remove ${exports.RETIRED_PUBLIC_ORIGIN_ENV} from this container.`,
51
- };
52
- }
53
- /**
54
- * Apply the resolution to a process environment: the survivor is set (or left
55
- * unset), the retired name is removed. Returns the warning to log once a
56
- * logger exists. Called once, first thing in the hub's bootstrap, before any
57
- * addon runner is forked.
58
- */
59
- function applyHubPublicUrl(env) {
60
- const { url, warning } = resolveHubPublicUrl(env);
61
- if (url === undefined)
62
- delete env[exports.HUB_PUBLIC_URL_ENV];
63
- else
64
- env[exports.HUB_PUBLIC_URL_ENV] = url;
65
- delete env[exports.RETIRED_PUBLIC_ORIGIN_ENV];
66
- return warning;
67
- }