@camstack/server 1.2.460 → 1.2.461
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/dist/api/addon-upload.js +35 -13
- package/dist/api/cors/cors-policy.js +6 -9
- package/dist/api/cors/register-cors-policy.js +1 -1
- package/dist/api/hub-cluster-url.js +37 -0
- package/dist/api/model-distributor.js +6 -7
- package/dist/api/oauth2/oauth2-routes.js +1 -1
- package/dist/api/trpc/generated-cap-routers.js +16 -0
- package/dist/boot/retired-public-url-env.js +41 -0
- package/dist/first-boot-addon-plan.js +2 -2
- package/dist/main.js +15 -10
- package/dist/manual-boot.js +7 -1
- package/package.json +10 -10
- package/dist/boot/public-url.js +0 -67
package/dist/api/addon-upload.js
CHANGED
|
@@ -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
|
-
/**
|
|
98
|
-
function
|
|
99
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
* -
|
|
22
|
-
*
|
|
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
|
|
115
|
-
function builtInOrigins(
|
|
116
|
-
|
|
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)(
|
|
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
|
+
'(set the hub address 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.getHubAddresses())[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, 'cluster');
|
|
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,
|
|
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,22 @@ 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
|
+
getHubAddresses: trpc_middleware_js_1.protectedProcedure
|
|
4916
|
+
.input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
|
|
4917
|
+
.output(types_65.localNetworkCapability.methods.getHubAddresses.output)
|
|
4918
|
+
.query(async ({ input, ctx }) => {
|
|
4919
|
+
const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4920
|
+
return p.getHubAddresses();
|
|
4921
|
+
}),
|
|
4922
|
+
setHubAddress: trpc_middleware_js_1.adminProcedure
|
|
4923
|
+
.input(types_65.localNetworkCapability.methods.setHubAddress.input.loose())
|
|
4924
|
+
.output(types_65.localNetworkCapability.methods.setHubAddress.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.setHubAddress(methodInput);
|
|
4930
|
+
}),
|
|
4915
4931
|
getAllowedAddresses: trpc_middleware_js_1.protectedProcedure
|
|
4916
4932
|
.input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
|
|
4917
4933
|
.output(types_65.localNetworkCapability.methods.getAllowedAddresses.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
|
+
* (`pickHubBaseUrl`); 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
|
|
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
|
|
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
|
-
//
|
|
246
|
-
//
|
|
247
|
-
// forked, so
|
|
248
|
-
//
|
|
249
|
-
const publicUrlWarning = (0,
|
|
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,
|
|
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
|
-
|
|
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'), 'browser').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.
|
package/dist/manual-boot.js
CHANGED
|
@@ -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,
|
|
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.
|
|
3
|
+
"version": "1.2.461",
|
|
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.
|
|
41
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
42
|
-
"@camstack/addon-auth": "1.2.
|
|
40
|
+
"@camstack/addon-admin-ui": "1.2.335",
|
|
41
|
+
"@camstack/addon-agent-ui": "1.2.177",
|
|
42
|
+
"@camstack/addon-auth": "1.2.128",
|
|
43
43
|
"@camstack/addon-decoder-nodeav": "1.2.119",
|
|
44
44
|
"@camstack/addon-notifiers": "1.2.128",
|
|
45
|
-
"@camstack/addon-pipeline": "1.2.
|
|
46
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
47
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
45
|
+
"@camstack/addon-pipeline": "1.2.330",
|
|
46
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.285",
|
|
47
|
+
"@camstack/addon-post-analysis": "1.2.337",
|
|
48
48
|
"@camstack/sdk": "1.2.122",
|
|
49
|
-
"@camstack/system": "1.2.
|
|
50
|
-
"@camstack/types": "1.2.
|
|
51
|
-
"@camstack/ui-library": "1.2.
|
|
49
|
+
"@camstack/system": "1.2.358",
|
|
50
|
+
"@camstack/types": "1.2.309",
|
|
51
|
+
"@camstack/ui-library": "1.2.235",
|
|
52
52
|
"@fastify/compress": "^9.0.0",
|
|
53
53
|
"@fastify/cookie": "^11.0.2",
|
|
54
54
|
"@fastify/cors": "^11.2.0",
|
package/dist/boot/public-url.js
DELETED
|
@@ -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
|
-
}
|