@camstack/server 1.2.459 → 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/THIRD_PARTY_MODELS.md +2 -2
- 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/boot-config.js +40 -15
- package/dist/boot/retired-public-url-env.js +41 -0
- package/dist/first-boot-addon-plan.js +2 -2
- package/dist/main.js +27 -10
- package/dist/manual-boot.js +7 -1
- package/package.json +10 -10
- package/dist/boot/public-url.js +0 -67
package/THIRD_PARTY_MODELS.md
CHANGED
|
@@ -134,7 +134,7 @@ entry kept resolvable for a saved selection but no longer offered.
|
|
|
134
134
|
|
|
135
135
|
| Catalog ids | Upstream | Code / weights | Training data | CamStack changes | Downloaded from |
|
|
136
136
|
| --- | --- | --- | --- | --- | --- |
|
|
137
|
-
| `yunet-2023mar` (default) | [OpenCV Zoo YuNet](https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet) | BSD-3-Clause (`BSD-3-Clause.txt`) / MIT (`MIT-yunet-reference.txt`) | WIDER FACE: research-oriented terms; images CC BY-NC-ND | input contract baked into the graph (the pool RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather); internal tensor names cleared in the OpenVINO IR; ONNX fp32, OpenVINO IR fp32, CoreML fp16 (scripts/build-yunet-model.py) | mirror `faceDetection/yunet` |
|
|
137
|
+
| `yunet-2023mar` (default) | [OpenCV Zoo YuNet](https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet) | BSD-3-Clause (`BSD-3-Clause.txt`) / MIT (`MIT-yunet-reference.txt`) | WIDER FACE: research-oriented terms; images CC BY-NC-ND | input contract baked into the graph (the pool RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather); internal tensor names cleared in the OpenVINO IR; ONNX fp32, OpenVINO IR fp32, CoreML fp16 (scripts/build-yunet-model.py); Edge TPU build: the bake folded into the first Conv, full-integer int8 post-training quantisation calibrated on CamStack person crops, compiled with edgetpu_compiler 14 (scripts/build-yunet-edgetpu.py) | mirror `faceDetection/yunet` |
|
|
138
138
|
|
|
139
139
|
### Face recognition
|
|
140
140
|
|
|
@@ -146,7 +146,7 @@ entry kept resolvable for a saved selection but no longer offered.
|
|
|
146
146
|
|
|
147
147
|
| Catalog ids | Upstream | Code / weights | Training data | CamStack changes | Downloaded from |
|
|
148
148
|
| --- | --- | --- | --- | --- | --- |
|
|
149
|
-
| `yolox-tiny-plate` (default) | [open-lpr models (faisalthaheem/open-lpr-models), YOLOX-tiny](https://huggingface.co/faisalthaheem/open-lpr-models) | Apache-2.0 / Apache-2.0 (`Apache-2.0.txt`) | Simanno annotated plate corpus plus community-contributed photographs, pseudo-labelled by a teacher model (not published; the upstream NOTICE says the photographs were uploaded without consent obtained): UNKNOWN | input contract baked into the graph (static 1x3x640x640 RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather) and the output reshaped to the YOLOv8 layout (cx, cy, w, h, obj*cls); ONNX fp32, OpenVINO IR fp16, CoreML fp16 (scripts/build-yolox-plate-model.py) | mirror `plateDetection/yolox-tiny-plate` |
|
|
149
|
+
| `yolox-tiny-plate` (default) | [open-lpr models (faisalthaheem/open-lpr-models), YOLOX-tiny](https://huggingface.co/faisalthaheem/open-lpr-models) | Apache-2.0 / Apache-2.0 (`Apache-2.0.txt`) | Simanno annotated plate corpus plus community-contributed photographs, pseudo-labelled by a teacher model (not published; the upstream NOTICE says the photographs were uploaded without consent obtained): UNKNOWN | input contract baked into the graph (static 1x3x640x640 RGB /255 becomes the upstream BGR 0..255: Mul(255) plus a channel Gather) and the output reshaped to the YOLOv8 layout (cx, cy, w, h, obj*cls); ONNX fp32, OpenVINO IR fp16, CoreML fp16 (scripts/build-yolox-plate-model.py); Edge TPU build: the Focus stem rewritten as the equivalent 6x6 stride-2 Conv with the input bake folded into it, the graph cut before the grid decode (raw per-stride head), full-integer int8 post-training quantisation calibrated on CamStack vehicle crops, compiled with edgetpu_compiler 14 (scripts/build-yolox-plate-edgetpu.py) | mirror `plateDetection/yolox-tiny-plate` |
|
|
150
150
|
|
|
151
151
|
### Licence plate reading
|
|
152
152
|
|
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)
|
package/dist/boot/boot-config.js
CHANGED
|
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
};
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.ignoredTlsCertSources = ignoredTlsCertSources;
|
|
41
42
|
exports.loadBootstrapConfig = loadBootstrapConfig;
|
|
42
43
|
exports.autoGenerateJwtSecret = autoGenerateJwtSecret;
|
|
43
44
|
exports.setupInfra = setupInfra;
|
|
@@ -67,9 +68,15 @@ const ENV_VAR_MAP = {
|
|
|
67
68
|
CAMSTACK_ADMIN_USER: 'auth.adminUsername',
|
|
68
69
|
CAMSTACK_ADMIN_PASS: 'auth.adminPassword',
|
|
69
70
|
CAMSTACK_TLS_ENABLED: 'tls.enabled',
|
|
70
|
-
CAMSTACK_TLS_CERT: 'tls.certPath',
|
|
71
|
-
CAMSTACK_TLS_KEY: 'tls.keyPath',
|
|
72
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Env variables that once set the node's certificate. Ignored since D726:
|
|
74
|
+
* the admin UI (Network → Local access) is the one way to set it. Still
|
|
75
|
+
* named here so a node that has them set is told, not silently re-keyed.
|
|
76
|
+
*/
|
|
77
|
+
const IGNORED_TLS_CERT_ENV = ['CAMSTACK_TLS_CERT', 'CAMSTACK_TLS_KEY'];
|
|
78
|
+
/** The `config.yaml` keys those variables were mapped onto — ignored too. */
|
|
79
|
+
const IGNORED_TLS_CERT_KEYS = ['certPath', 'keyPath'];
|
|
73
80
|
// ---------------------------------------------------------------------------
|
|
74
81
|
// Helpers
|
|
75
82
|
// ---------------------------------------------------------------------------
|
|
@@ -82,6 +89,20 @@ function setNested(obj, p, value) {
|
|
|
82
89
|
const child = (0, types_1.asJsonObject)(obj[head]) ?? {};
|
|
83
90
|
return { ...obj, [head]: setNested(child, rest.join('.'), value) };
|
|
84
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Every env variable and `config.yaml` key that would once have set the
|
|
94
|
+
* certificate and is now ignored (D726). Blank env values count as unset,
|
|
95
|
+
* exactly as the override loop treats them.
|
|
96
|
+
*/
|
|
97
|
+
function ignoredTlsCertSources(env, raw) {
|
|
98
|
+
const fromEnv = IGNORED_TLS_CERT_ENV.filter((name) => {
|
|
99
|
+
const value = env[name];
|
|
100
|
+
return value !== undefined && value !== '';
|
|
101
|
+
});
|
|
102
|
+
const tls = (0, types_1.asJsonObject)(raw['tls']) ?? {};
|
|
103
|
+
const fromYaml = IGNORED_TLS_CERT_KEYS.filter((key) => tls[key] !== undefined).map((key) => `tls.${key}`);
|
|
104
|
+
return [...fromEnv, ...fromYaml];
|
|
105
|
+
}
|
|
85
106
|
// ---------------------------------------------------------------------------
|
|
86
107
|
// loadBootstrapConfig
|
|
87
108
|
// ---------------------------------------------------------------------------
|
|
@@ -145,6 +166,11 @@ function loadBootstrapConfig(configPath) {
|
|
|
145
166
|
raw = setNested(raw, configPath_, coerced);
|
|
146
167
|
console.log(`[Phase1] Env override: ${envKey} → ${configPath_}`);
|
|
147
168
|
}
|
|
169
|
+
const ignored = ignoredTlsCertSources(process.env, raw);
|
|
170
|
+
if (ignored.length > 0) {
|
|
171
|
+
console.warn(`[Phase1] WARN ${ignored.join(', ')} ignored — the certificate is set only in the admin UI. ` +
|
|
172
|
+
'Upload the certificate in Network → Local access (D726).');
|
|
173
|
+
}
|
|
148
174
|
return config_schema_1.bootstrapSchema.parse(raw);
|
|
149
175
|
}
|
|
150
176
|
// ---------------------------------------------------------------------------
|
|
@@ -210,22 +236,21 @@ async function setupInfra(configPath, bootstrapConfig) {
|
|
|
210
236
|
// broken chunks with require("fs") when leaked .js files exist in core/src/.
|
|
211
237
|
// CJS build works correctly and tsx supports require().
|
|
212
238
|
const core = require('@camstack/system');
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
// The certificate comes only from the admin UI (D726): the generated
|
|
240
|
+
// local CA → leaf (D227), or an operator upload served verbatim.
|
|
241
|
+
const extraSans = [...core.readExtraSans(dataPath)];
|
|
242
|
+
const boot = await core.prepareBootTls(dataPath, extraSans.length > 0 ? { extraSans } : undefined);
|
|
243
|
+
if (boot.uploadedMissing) {
|
|
244
|
+
console.warn(`[Phase2c] WARN the uploaded TLS certificate is missing from ${path.join(dataPath, 'tls')} — serving the generated one. Upload it again in Network → Local access.`);
|
|
245
|
+
}
|
|
246
|
+
if (boot.ensured !== undefined) {
|
|
247
|
+
tlsCert = boot.ensured;
|
|
248
|
+
logTlsCert(boot.ensured);
|
|
219
249
|
}
|
|
220
250
|
else {
|
|
221
|
-
|
|
222
|
-
const extraSans = [...(core.readExtraSans?.(dataPath) ?? [])];
|
|
223
|
-
const tlsResult = await ensureTlsCert(dataPath, extraSans.length > 0 ? { extraSans } : undefined);
|
|
224
|
-
tlsCert = tlsResult;
|
|
225
|
-
logTlsCert(tlsResult);
|
|
226
|
-
const pair = loadTlsCert(tlsResult.certPath, tlsResult.keyPath);
|
|
227
|
-
tlsOptions = { key: pair.key, cert: pair.cert };
|
|
251
|
+
console.log(`[Phase2c] Serving the uploaded TLS certificate from ${path.join(dataPath, 'tls')}`);
|
|
228
252
|
}
|
|
253
|
+
tlsOptions = { key: boot.pair.key, cert: boot.pair.cert };
|
|
229
254
|
}
|
|
230
255
|
return {
|
|
231
256
|
bootstrapConfig: config,
|
|
@@ -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
|
@@ -40,6 +40,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
40
40
|
const node_child_process_1 = require("node:child_process");
|
|
41
41
|
const fs = __importStar(require("node:fs"));
|
|
42
42
|
const path = __importStar(require("node:path"));
|
|
43
|
+
const node_tls_1 = require("node:tls");
|
|
43
44
|
// Boot-time capability declaration runs over the auto-generated
|
|
44
45
|
// `ALL_CAPABILITY_DEFINITIONS` array — every `*.cap.ts` file that ships
|
|
45
46
|
// with `@camstack/types` is included automatically. Adding a new cap
|
|
@@ -65,6 +66,7 @@ const request_census_settings_1 = require("./api/core/request-census-settings");
|
|
|
65
66
|
const register_cors_policy_1 = require("./api/cors/register-cors-policy");
|
|
66
67
|
const adaptive_probe_routes_1 = require("./api/health/adaptive-probe.routes");
|
|
67
68
|
const health_routes_1 = require("./api/health/health.routes");
|
|
69
|
+
const hub_cluster_url_1 = require("./api/hub-cluster-url");
|
|
68
70
|
const model_distributor_js_1 = require("./api/model-distributor.js");
|
|
69
71
|
const oauth2_routes_js_1 = require("./api/oauth2/oauth2-routes.js");
|
|
70
72
|
const server_upload_1 = require("./api/server-upload");
|
|
@@ -89,9 +91,9 @@ const session_cookie_js_1 = require("./auth/session-cookie.js");
|
|
|
89
91
|
const session_mint_js_1 = require("./auth/session-mint.js");
|
|
90
92
|
const sso_finish_js_1 = require("./auth/sso-finish.js");
|
|
91
93
|
const boot_config_1 = require("./boot/boot-config");
|
|
92
|
-
const public_url_1 = require("./boot/public-url");
|
|
93
94
|
const integration_id_backfill_1 = require("./boot/integration-id-backfill");
|
|
94
95
|
const post_boot_service_1 = require("./boot/post-boot.service");
|
|
96
|
+
const retired_public_url_env_1 = require("./boot/retired-public-url-env");
|
|
95
97
|
const addon_package_service_1 = require("./core/addon/addon-package.service");
|
|
96
98
|
const addon_registry_service_1 = require("./core/addon/addon-registry.service");
|
|
97
99
|
const addon_bridge_service_1 = require("./core/addon-bridge/addon-bridge.service");
|
|
@@ -241,11 +243,11 @@ async function sendMaybePrecompressed(reply, absolutePath, acceptEncoding) {
|
|
|
241
243
|
}
|
|
242
244
|
// ---- Bootstrap ----
|
|
243
245
|
async function bootstrap() {
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
// forked, so
|
|
247
|
-
//
|
|
248
|
-
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);
|
|
249
251
|
// hub-main's own memory, on the record. Deliberately started HERE and not in
|
|
250
252
|
// the starter: the starter runs from the image's /opt closure, which only
|
|
251
253
|
// changes with a new image, while this entry is what `applyServerUpdate`
|
|
@@ -364,7 +366,6 @@ async function bootstrap() {
|
|
|
364
366
|
const corsConfig = app.get(config_service_1.ConfigService);
|
|
365
367
|
const corsPolicy = (0, register_cors_policy_1.createCorsPolicy)({
|
|
366
368
|
configuredOrigins: () => corsConfig.get('server.corsOrigins'),
|
|
367
|
-
publicOrigin: process.env[public_url_1.HUB_PUBLIC_URL_ENV],
|
|
368
369
|
logger: app.get(logging_service_1.LoggingService).createLogger('cors'),
|
|
369
370
|
});
|
|
370
371
|
(0, register_cors_policy_1.registerCorsPolicy)(fastify, corsPolicy);
|
|
@@ -411,7 +412,9 @@ async function bootstrap() {
|
|
|
411
412
|
// The delivery ledger marks "the hub is writing addons to this node" for
|
|
412
413
|
// the whole deploy+reload, so the addon back-fill cannot read the roster
|
|
413
414
|
// gap a deploy opens and repair the node over the bytes just shipped.
|
|
414
|
-
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);
|
|
415
418
|
console.log('[bootstrap] Addon upload route registered at POST /api/addons/upload');
|
|
416
419
|
// Dev server-deploy channel gateway (`camstack deploy-server`). MUST be
|
|
417
420
|
// registered AFTER registerAddonUploadRoute — it reuses the multipart
|
|
@@ -591,7 +594,7 @@ async function bootstrap() {
|
|
|
591
594
|
// router) so forked addons (addon-model-studio) can reach it via `ctx.api`,
|
|
592
595
|
// not only external HTTP clients. Needs the broker + the deploy-stage
|
|
593
596
|
// registry, which an addon can't access.
|
|
594
|
-
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'))));
|
|
595
598
|
// ── Health routes (hub self + agent forwarding) ──────────────────
|
|
596
599
|
// Registered after app.init() so the AgentRegistryService is wired and
|
|
597
600
|
// the Moleculer broker is ready to forward `$agent.health` calls.
|
|
@@ -1116,7 +1119,10 @@ async function bootstrap() {
|
|
|
1116
1119
|
(0, oauth2_routes_js_1.registerOauth2Routes)(fastify, {
|
|
1117
1120
|
getRegistry: () => capabilityRegistry,
|
|
1118
1121
|
verifyToken: (t) => authService.verifyToken(t),
|
|
1119
|
-
|
|
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}`),
|
|
1120
1126
|
// Every /authorize refusal is a dropped account-linking attempt. Without
|
|
1121
1127
|
// this scope the drop is invisible in Loki and the only signal left is an
|
|
1122
1128
|
// operator noticing that linking stopped working.
|
|
@@ -1489,6 +1495,17 @@ async function bootstrap() {
|
|
|
1489
1495
|
const logger = app.get(logging_service_1.LoggingService).createLogger('System');
|
|
1490
1496
|
const protocol = tlsOptions ? 'https' : 'http';
|
|
1491
1497
|
logger.info('CamStack server listening', { meta: { protocol, host, port, trpcRegistered } });
|
|
1498
|
+
// Certificate actions (Network → Local access) swap the served certificate
|
|
1499
|
+
// in place through this registration — without it an upload sat on disk
|
|
1500
|
+
// while the listener kept serving the boot-time leaf (D726).
|
|
1501
|
+
if (tlsOptions) {
|
|
1502
|
+
if (fastify.server instanceof node_tls_1.Server) {
|
|
1503
|
+
(0, system_1.registerHttpsListener)(fastify.server, tlsOptions);
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
logger.warn('HTTPS listener is not a TLS server — certificate changes need a restart');
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1492
1509
|
// LAN HTTP sibling — same Fastify request graph, dedicated port. Handler
|
|
1493
1510
|
// is registered as soon as we have the instance; bind happens after HTTPS
|
|
1494
1511
|
// is up so a failed HTTPS listen does not leave HTTP serving alone.
|
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
|
-
}
|