@aifabrix/builder 2.48.0 → 2.49.0
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/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/jest.isolated-projects.js +3 -0
- package/jest.projects.js +12 -0
- package/lib/api/applications.api.js +23 -0
- package/lib/api/dev.api.js +1 -1
- package/lib/api/index.js +5 -2
- package/lib/api/types/dev.types.js +3 -2
- package/lib/app/deploy-config.js +6 -4
- package/lib/app/register.js +1 -1
- package/lib/app/run-container-start.js +0 -9
- package/lib/app/run-helpers.js +8 -4
- package/lib/app/run-reload-sync.js +16 -18
- package/lib/app/run-stop-containers.js +48 -5
- package/lib/app/run.js +6 -3
- package/lib/app/show.js +2 -2
- package/lib/cli/infra-guided-auth.js +42 -6
- package/lib/cli/infra-guided-footers.js +164 -0
- package/lib/cli/infra-guided-spinners.js +57 -0
- package/lib/cli/infra-guided.js +41 -179
- package/lib/cli/setup-app.js +1 -1
- package/lib/cli/setup-dev.js +3 -2
- package/lib/cli/setup-infra.js +3 -2
- package/lib/cli/setup-platform.js +3 -0
- package/lib/commands/login-device.js +6 -2
- package/lib/commands/login.js +4 -1
- package/lib/commands/setup-modes.js +19 -3
- package/lib/commands/setup-platform-auth.js +25 -0
- package/lib/commands/setup-prompts-admin.js +9 -0
- package/lib/commands/setup.js +60 -8
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +19 -4
- package/lib/commands/up-dataplane.js +74 -11
- package/lib/core/secrets-env-declarative-expand.js +35 -23
- package/lib/core/setup-platform-mode.js +43 -0
- package/lib/deployment/deployer-status.js +26 -3
- package/lib/deployment/deployer.js +7 -14
- package/lib/external-system/download-helpers.js +10 -1
- package/lib/infrastructure/compose.js +40 -12
- package/lib/infrastructure/services.js +15 -3
- package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
- package/lib/utils/app-bootstrap-api.js +53 -0
- package/lib/utils/app-run-containers.js +14 -3
- package/lib/utils/applications-config-defaults.js +19 -3
- package/lib/utils/compose-generate-docker-compose.js +16 -5
- package/lib/utils/compose-generator.js +34 -15
- package/lib/utils/compose-traefik-ingress-base.js +13 -14
- package/lib/utils/config-paths.js +6 -1
- package/lib/utils/dev-user-groups.js +1 -0
- package/lib/utils/docker-daemon-tls-ca.js +94 -57
- package/lib/utils/docker-reload-mount.js +12 -1
- package/lib/utils/external-env-template.js +53 -18
- package/lib/utils/health-check-poll-ui.js +84 -0
- package/lib/utils/health-check-url.js +54 -34
- package/lib/utils/health-check.js +43 -16
- package/lib/utils/infra-optional-service-flags.js +31 -2
- package/lib/utils/mutagen.js +7 -4
- package/lib/utils/platform-controller-url.js +23 -1
- package/lib/utils/refresh-remote-settings.js +67 -0
- package/lib/utils/remote-host-for-front-door.js +43 -0
- package/lib/utils/run-cli-flags.js +37 -1
- package/lib/utils/url-declarative-public-base.js +11 -4
- package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
- package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
- package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
- package/lib/utils/url-declarative-resolve.js +2 -0
- package/lib/utils/urls-local-registry-scan.js +95 -10
- package/lib/utils/urls-local-registry.js +24 -1
- package/lib/utils/worker-sidecars.js +2 -2
- package/lib/validation/external-system-auth-rules.js +45 -7
- package/package.json +2 -1
- package/scripts/test-dataplane-bootstrap.js +331 -0
- package/templates/applications/dataplane/env.template +22 -5
- package/templates/applications/dataplane/rbac.yaml +11 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -0
- package/templates/infra/compose.yaml.hbs +13 -9
- package/templates/python/docker-compose.hbs +3 -0
- package/templates/typescript/docker-compose.hbs +3 -0
|
@@ -116,7 +116,7 @@ function buildImageConfig(config, appName, imageOverride) {
|
|
|
116
116
|
* @param {string|null|undefined} [readinessSuffix] - Path after vdir (default /health/ready or healthCheck.readinessPathSuffix)
|
|
117
117
|
* @returns {string} Absolute path starting with /
|
|
118
118
|
*/
|
|
119
|
-
function computeAlignedHealthCheckPath(config, devId, scopeOpts, remoteServer, readinessSuffix) {
|
|
119
|
+
function computeAlignedHealthCheckPath(config, devId, scopeOpts, remoteServer, readinessSuffix, developerRootDomain) {
|
|
120
120
|
const hc = config.healthCheck || {};
|
|
121
121
|
let suffix = '/health/ready';
|
|
122
122
|
if (
|
|
@@ -128,7 +128,7 @@ function computeAlignedHealthCheckPath(config, devId, scopeOpts, remoteServer, r
|
|
|
128
128
|
} else if (typeof hc.readinessPathSuffix === 'string' && hc.readinessPathSuffix.startsWith('/')) {
|
|
129
129
|
suffix = hc.readinessPathSuffix;
|
|
130
130
|
}
|
|
131
|
-
const traefik = buildTraefikIngressBase(config, devId, scopeOpts, remoteServer);
|
|
131
|
+
const traefik = buildTraefikIngressBase(config, devId, scopeOpts, remoteServer, developerRootDomain);
|
|
132
132
|
if (traefik.enabled === true) {
|
|
133
133
|
const p = traefik.path;
|
|
134
134
|
if (!p || p === '/') {
|
|
@@ -158,7 +158,7 @@ function computeAlignedHealthCheckPath(config, devId, scopeOpts, remoteServer, r
|
|
|
158
158
|
* @param {{ skipVdirMergeWhenPathIsBareHealth?: boolean }} [opts] - When `skipVdirMergeWhenPathIsBareHealth`, path `/health` is left as-is (in-container probe; ingress strips vdir for miso/dataplane). Deployment JSON health paths do **not** use this helper — see `buildHealthCheck` in `builders.js` (YAML paths only).
|
|
159
159
|
* @returns {string} Absolute path starting with /
|
|
160
160
|
*/
|
|
161
|
-
function resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remoteServer, opts) {
|
|
161
|
+
function resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remoteServer, opts, developerRootDomain) {
|
|
162
162
|
const hc = config.healthCheck || {};
|
|
163
163
|
const raw = typeof hc.path === 'string' && hc.path.startsWith('/') ? hc.path : '/health';
|
|
164
164
|
const fd = config.frontDoorRouting;
|
|
@@ -168,7 +168,7 @@ function resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remot
|
|
|
168
168
|
if (opts && opts.skipVdirMergeWhenPathIsBareHealth === true && raw === '/health') {
|
|
169
169
|
return raw;
|
|
170
170
|
}
|
|
171
|
-
const traefik = buildTraefikIngressBase(config, devId, scopeOpts, remoteServer);
|
|
171
|
+
const traefik = buildTraefikIngressBase(config, devId, scopeOpts, remoteServer, developerRootDomain);
|
|
172
172
|
if (traefik.enabled !== true) {
|
|
173
173
|
return raw;
|
|
174
174
|
}
|
|
@@ -178,7 +178,7 @@ function resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remot
|
|
|
178
178
|
? ''
|
|
179
179
|
: String(vdirRaw).replace(/\/+$/, '');
|
|
180
180
|
if (!vdir) {
|
|
181
|
-
return computeAlignedHealthCheckPath(config, devId, scopeOpts, remoteServer, raw);
|
|
181
|
+
return computeAlignedHealthCheckPath(config, devId, scopeOpts, remoteServer, raw, developerRootDomain);
|
|
182
182
|
}
|
|
183
183
|
if (raw === vdir || raw.startsWith(`${vdir}/`)) {
|
|
184
184
|
return raw;
|
|
@@ -192,13 +192,14 @@ function resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remot
|
|
|
192
192
|
* @param {string|number} devId - Developer id
|
|
193
193
|
* @param {Object|null} scopeOpts - Env-scoped compose options
|
|
194
194
|
* @param {string|null|undefined} remoteServer - Remote server URL
|
|
195
|
+
* @param {string|null|undefined} [developerRootDomain] - App hostname root
|
|
195
196
|
* @returns {Object} Health check configuration
|
|
196
197
|
*/
|
|
197
|
-
function buildHealthCheckConfig(config, devId, scopeOpts, remoteServer) {
|
|
198
|
+
function buildHealthCheckConfig(config, devId, scopeOpts, remoteServer, developerRootDomain) {
|
|
198
199
|
const hc = config.healthCheck || {};
|
|
199
200
|
const path = resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remoteServer, {
|
|
200
201
|
skipVdirMergeWhenPathIsBareHealth: true
|
|
201
|
-
});
|
|
202
|
+
}, developerRootDomain);
|
|
202
203
|
return {
|
|
203
204
|
path,
|
|
204
205
|
interval: hc.interval || 30,
|
|
@@ -230,10 +231,11 @@ function buildDevUsername(devId) {
|
|
|
230
231
|
* @param {Object|null} scopeOpts - Env-scoped Traefik path
|
|
231
232
|
* @param {string|null|undefined} remoteServer - From config (for ${REMOTE_HOST})
|
|
232
233
|
* @param {string|undefined} [resolvedHealthPathForStrip] - Compose health path; when omitted, derived via resolveHealthCheckPathWithFrontDoorVdir (same as probe)
|
|
234
|
+
* @param {string|null|undefined} [developerRootDomain] - App hostname root from settings
|
|
233
235
|
* @returns {Object} Traefik configuration object
|
|
234
236
|
*/
|
|
235
|
-
function buildTraefikConfig(config, devId, scopeOpts = null, remoteServer = null, resolvedHealthPathForStrip) {
|
|
236
|
-
const base = buildTraefikIngressBase(config, devId, scopeOpts, remoteServer);
|
|
237
|
+
function buildTraefikConfig(config, devId, scopeOpts = null, remoteServer = null, resolvedHealthPathForStrip, developerRootDomain) {
|
|
238
|
+
const base = buildTraefikIngressBase(config, devId, scopeOpts, remoteServer, developerRootDomain);
|
|
237
239
|
if (!base.enabled) {
|
|
238
240
|
return { enabled: false };
|
|
239
241
|
}
|
|
@@ -242,7 +244,7 @@ function buildTraefikConfig(config, devId, scopeOpts = null, remoteServer = null
|
|
|
242
244
|
? String(resolvedHealthPathForStrip)
|
|
243
245
|
: resolveHealthCheckPathWithFrontDoorVdir(config, devId, scopeOpts, remoteServer, {
|
|
244
246
|
skipVdirMergeWhenPathIsBareHealth: true
|
|
245
|
-
});
|
|
247
|
+
}, developerRootDomain);
|
|
246
248
|
const devNum = parseDeveloperIdNum(devId);
|
|
247
249
|
// Shared builder host: nginx terminates TLS on :443 and proxies HTTP to per-dev Traefik :80 (e.g. 280).
|
|
248
250
|
// Traefik must only attach routers to `web` for developer-id > 0; `websecure`+tls labels without
|
|
@@ -282,6 +284,7 @@ function buildRequiresConfig(config) {
|
|
|
282
284
|
* @param {string} [runExtras.imageOverride] - Full image reference for run (e.g. from --image)
|
|
283
285
|
* @param {Object|null} [runExtras.scopeOpts] - Traefik / scoped compose options
|
|
284
286
|
* @param {string|null|undefined} [runExtras.remoteServer] - For ${REMOTE_HOST} in frontDoorRouting.host
|
|
287
|
+
* @param {string|null|undefined} [runExtras.developerRootDomain] - App hostname root (overrides remote-server host)
|
|
285
288
|
* @param {boolean} [runExtras.omitAppTraefikLabels] - When true (user config `traefik: false`), omit Traefik labels and BASE_PATH env from compose even if application.yaml enables frontDoorRouting
|
|
286
289
|
* @returns {Object} Service configuration
|
|
287
290
|
*/
|
|
@@ -290,6 +293,7 @@ function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
|
|
|
290
293
|
imageOverride = null,
|
|
291
294
|
scopeOpts = null,
|
|
292
295
|
remoteServer = null,
|
|
296
|
+
developerRootDomain = null,
|
|
293
297
|
omitAppTraefikLabels = false
|
|
294
298
|
} = runExtras;
|
|
295
299
|
const containerPortValue = getContainerPort(config, 3000);
|
|
@@ -303,7 +307,7 @@ function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
|
|
|
303
307
|
? composeTraefikServiceKey(appName, scopeOpts.runEnvKey)
|
|
304
308
|
: null;
|
|
305
309
|
const scopeForHealthAndTraefik = useTraefikScope ? scopeOpts : null;
|
|
306
|
-
const healthCheck = buildHealthCheckConfig(config, devId, scopeForHealthAndTraefik, remoteServer);
|
|
310
|
+
const healthCheck = buildHealthCheckConfig(config, devId, scopeForHealthAndTraefik, remoteServer, developerRootDomain);
|
|
307
311
|
return {
|
|
308
312
|
app: buildAppConfig(appName, config, composeAppKey),
|
|
309
313
|
image: buildImageConfig(config, appName, imageOverride),
|
|
@@ -319,7 +323,8 @@ function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
|
|
|
319
323
|
devId,
|
|
320
324
|
scopeForHealthAndTraefik,
|
|
321
325
|
remoteServer,
|
|
322
|
-
healthCheck.path
|
|
326
|
+
healthCheck.path,
|
|
327
|
+
developerRootDomain
|
|
323
328
|
),
|
|
324
329
|
...buildRequiresConfig(config)
|
|
325
330
|
};
|
|
@@ -379,20 +384,34 @@ function buildNetworkAndContainerNames(appName, devId, idNum, scopeOpts = null)
|
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
const { createGenerateDockerCompose } = require('./compose-generate-docker-compose');
|
|
382
|
-
async function
|
|
387
|
+
async function getFrontDoorHostContextForCompose() {
|
|
383
388
|
try {
|
|
384
389
|
const rs = await config.getRemoteServer();
|
|
385
|
-
|
|
390
|
+
const remoteServer = rs && String(rs).trim() ? String(rs).trim() : null;
|
|
391
|
+
let developerRootDomain = null;
|
|
392
|
+
try {
|
|
393
|
+
const root = await config.getDeveloperRootDomain();
|
|
394
|
+
developerRootDomain = root && String(root).trim() ? String(root).trim() : null;
|
|
395
|
+
} catch {
|
|
396
|
+
developerRootDomain = null;
|
|
397
|
+
}
|
|
398
|
+
return { remoteServer, developerRootDomain };
|
|
386
399
|
} catch {
|
|
387
|
-
return null;
|
|
400
|
+
return { remoteServer: null, developerRootDomain: null };
|
|
388
401
|
}
|
|
389
402
|
}
|
|
390
403
|
|
|
404
|
+
async function getRemoteServerForCompose() {
|
|
405
|
+
const ctx = await getFrontDoorHostContextForCompose();
|
|
406
|
+
return ctx.remoteServer;
|
|
407
|
+
}
|
|
408
|
+
|
|
391
409
|
const generateDockerCompose = createGenerateDockerCompose({
|
|
392
410
|
loadDockerComposeTemplate,
|
|
393
411
|
resolveComposeImageOverrideString,
|
|
394
412
|
getDeveloperIdAndNumeric,
|
|
395
413
|
getRemoteServerForCompose,
|
|
414
|
+
getFrontDoorHostContextForCompose,
|
|
396
415
|
buildNetworkAndContainerNames,
|
|
397
416
|
buildServiceConfig,
|
|
398
417
|
buildVolumesConfig,
|
|
@@ -10,6 +10,10 @@ const {
|
|
|
10
10
|
buildEnvScopedTraefikPath
|
|
11
11
|
} = require('./environment-scoped-resources');
|
|
12
12
|
const { parseDeveloperIdNum } = require('./declarative-url-ports');
|
|
13
|
+
const {
|
|
14
|
+
resolveRemoteHostForFrontDoor,
|
|
15
|
+
hasFrontDoorRemoteHostContext
|
|
16
|
+
} = require('./remote-host-for-front-door');
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Derives base path from routing pattern by removing trailing wildcards
|
|
@@ -66,21 +70,15 @@ function buildDevUsernameForFrontDoorHost(devId) {
|
|
|
66
70
|
* @param {string} template
|
|
67
71
|
* @param {string|number|null|undefined} developerIdRaw
|
|
68
72
|
* @param {string|null|undefined} remoteServer
|
|
73
|
+
* @param {string|null|undefined} [developerRootDomain] - App hostname root (overrides remote-server hostname for ${REMOTE_HOST})
|
|
69
74
|
* @returns {string}
|
|
70
75
|
*/
|
|
71
|
-
function expandFrontDoorHostPlaceholders(template, developerIdRaw, remoteServer) {
|
|
76
|
+
function expandFrontDoorHostPlaceholders(template, developerIdRaw, remoteServer, developerRootDomain) {
|
|
72
77
|
let t = String(template || '');
|
|
73
78
|
t = t.replace(/\$\{DEV_USERNAME\}\$\{REMOTE_HOST\}/g, '${DEV_USERNAME}.${REMOTE_HOST}');
|
|
74
79
|
const devU = buildDevUsernameForFrontDoorHost(developerIdRaw);
|
|
75
80
|
t = t.replace(/\$\{DEV_USERNAME\}/g, devU);
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
if (remoteServer && String(remoteServer).trim()) {
|
|
79
|
-
remoteHost = new URL(String(remoteServer).trim()).hostname;
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
remoteHost = '';
|
|
83
|
-
}
|
|
81
|
+
const remoteHost = resolveRemoteHostForFrontDoor({ remoteServer, developerRootDomain });
|
|
84
82
|
t = t.replace(/\$\{REMOTE_HOST\}/g, remoteHost);
|
|
85
83
|
t = t.replace(/^\.+/g, '').replace(/\.{2,}/g, '.').replace(/\.+$/g, '').trim();
|
|
86
84
|
return t;
|
|
@@ -90,13 +88,13 @@ function templateUsesRemoteHostPlaceholder(hostTemplate) {
|
|
|
90
88
|
return /\$\{REMOTE_HOST\}/.test(String(hostTemplate || ''));
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
function resolveTraefikRouterHost(hostTemplate, developerIdRaw, remoteServer) {
|
|
91
|
+
function resolveTraefikRouterHost(hostTemplate, developerIdRaw, remoteServer, developerRootDomain) {
|
|
94
92
|
const usesRemote = templateUsesRemoteHostPlaceholder(hostTemplate);
|
|
95
|
-
const hasRemote =
|
|
93
|
+
const hasRemote = hasFrontDoorRemoteHostContext({ remoteServer, developerRootDomain });
|
|
96
94
|
if (usesRemote && !hasRemote) {
|
|
97
95
|
return 'localhost';
|
|
98
96
|
}
|
|
99
|
-
return expandFrontDoorHostPlaceholders(hostTemplate, developerIdRaw, remoteServer);
|
|
97
|
+
return expandFrontDoorHostPlaceholders(hostTemplate, developerIdRaw, remoteServer, developerRootDomain);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
/**
|
|
@@ -108,9 +106,10 @@ function resolveTraefikRouterHost(hostTemplate, developerIdRaw, remoteServer) {
|
|
|
108
106
|
* @param {string|number} devId - Developer id for host expansion
|
|
109
107
|
* @param {Object|null} scopeOpts - Env-scoped Traefik path (effectiveEnvironmentScopedResources, runEnvKey)
|
|
110
108
|
* @param {string|null|undefined} remoteServer - For ${REMOTE_HOST}
|
|
109
|
+
* @param {string|null|undefined} [developerRootDomain] - App hostname root from settings
|
|
111
110
|
* @returns {{ enabled: false } | { enabled: true, host: string, path: string, tls: boolean, certStore: string|null }}
|
|
112
111
|
*/
|
|
113
|
-
function buildTraefikIngressBase(config, devId, scopeOpts, remoteServer) {
|
|
112
|
+
function buildTraefikIngressBase(config, devId, scopeOpts, remoteServer, developerRootDomain) {
|
|
114
113
|
const frontDoor = config.frontDoorRouting;
|
|
115
114
|
if (!frontDoor || frontDoor.enabled !== true) {
|
|
116
115
|
return { enabled: false };
|
|
@@ -118,7 +117,7 @@ function buildTraefikIngressBase(config, devId, scopeOpts, remoteServer) {
|
|
|
118
117
|
if (!frontDoor.host || typeof frontDoor.host !== 'string') {
|
|
119
118
|
throw new Error('frontDoorRouting.host is required when frontDoorRouting.enabled is true');
|
|
120
119
|
}
|
|
121
|
-
const host = resolveTraefikRouterHost(frontDoor.host, devId, remoteServer);
|
|
120
|
+
const host = resolveTraefikRouterHost(frontDoor.host, devId, remoteServer, developerRootDomain);
|
|
122
121
|
const basePath = derivePathFromPattern(frontDoor.pattern);
|
|
123
122
|
let pathOut = basePath;
|
|
124
123
|
if (
|
|
@@ -22,6 +22,7 @@ const SETTINGS_RESPONSE_KEYS = [
|
|
|
22
22
|
'aifabrix-env-config',
|
|
23
23
|
'remote-server',
|
|
24
24
|
'docker-endpoint',
|
|
25
|
+
'developer-root-domain',
|
|
25
26
|
'docker-tls-skip-verify',
|
|
26
27
|
'sync-ssh-user',
|
|
27
28
|
'sync-ssh-host'
|
|
@@ -290,6 +291,9 @@ function createRemoteConfigGetters(getConfigFn) {
|
|
|
290
291
|
},
|
|
291
292
|
async getSyncSshHost() {
|
|
292
293
|
return getPathConfig(getConfigFn, 'sync-ssh-host');
|
|
294
|
+
},
|
|
295
|
+
async getDeveloperRootDomain() {
|
|
296
|
+
return getPathConfig(getConfigFn, 'developer-root-domain');
|
|
293
297
|
}
|
|
294
298
|
};
|
|
295
299
|
}
|
|
@@ -424,6 +428,7 @@ module.exports = {
|
|
|
424
428
|
resolveEnvConfigPathToAbsolute,
|
|
425
429
|
SETTINGS_RESPONSE_KEYS,
|
|
426
430
|
applySecretsUrlFromRemote,
|
|
427
|
-
applySyncAndDockerFromHost
|
|
431
|
+
applySyncAndDockerFromHost,
|
|
432
|
+
hostnameFromUrl
|
|
428
433
|
};
|
|
429
434
|
|
|
@@ -51,6 +51,68 @@ function collectIssuerChainPems(peerCert) {
|
|
|
51
51
|
return pems;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} rest - Host[:port] after tcp://
|
|
56
|
+
* @returns {{ host: string, port: number }|null}
|
|
57
|
+
*/
|
|
58
|
+
function parseBracketedTcpHostPort(rest) {
|
|
59
|
+
if (!rest.startsWith('[')) return null;
|
|
60
|
+
const end = rest.indexOf(']');
|
|
61
|
+
if (end === -1) return null;
|
|
62
|
+
const host = rest.slice(1, end);
|
|
63
|
+
let port = DEFAULT_DOCKER_TLS_PORT;
|
|
64
|
+
const after = rest.slice(end + 1);
|
|
65
|
+
if (after.startsWith(':')) {
|
|
66
|
+
const p = parseInt(after.slice(1), 10);
|
|
67
|
+
if (!Number.isNaN(p)) port = p;
|
|
68
|
+
}
|
|
69
|
+
return { host, port };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} rest - Host[:port] after tcp://
|
|
74
|
+
* @returns {{ host: string, port: number }}
|
|
75
|
+
*/
|
|
76
|
+
function parsePlainTcpHostPort(rest) {
|
|
77
|
+
const colonIdx = rest.lastIndexOf(':');
|
|
78
|
+
if (colonIdx > 0 && /^\d+$/.test(rest.slice(colonIdx + 1))) {
|
|
79
|
+
return {
|
|
80
|
+
host: rest.slice(0, colonIdx),
|
|
81
|
+
port: parseInt(rest.slice(colonIdx + 1), 10)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { host: rest, port: DEFAULT_DOCKER_TLS_PORT };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} trimmed
|
|
89
|
+
* @returns {{ host: string, port: number }|null}
|
|
90
|
+
*/
|
|
91
|
+
function parseTcpSchemeEndpoint(trimmed) {
|
|
92
|
+
const rest = trimmed.slice(6).split('/')[0];
|
|
93
|
+
return parseBracketedTcpHostPort(rest) || parsePlainTcpHostPort(rest);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} trimmed
|
|
98
|
+
* @returns {{ host: string, port: number }|null}
|
|
99
|
+
*/
|
|
100
|
+
function parseDockerEndpointViaUrl(trimmed) {
|
|
101
|
+
try {
|
|
102
|
+
const u = new URL(trimmed.includes('://') ? trimmed : `tcp://${trimmed}`);
|
|
103
|
+
const host = String(u.hostname || '').trim();
|
|
104
|
+
if (!host) return null;
|
|
105
|
+
let port = DEFAULT_DOCKER_TLS_PORT;
|
|
106
|
+
if (u.port) {
|
|
107
|
+
const p = parseInt(u.port, 10);
|
|
108
|
+
if (!Number.isNaN(p)) port = p;
|
|
109
|
+
}
|
|
110
|
+
return { host, port };
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
54
116
|
/**
|
|
55
117
|
* Parse docker-endpoint (tcp://host:port) into TLS connect parameters.
|
|
56
118
|
* @param {string} endpoint
|
|
@@ -62,45 +124,11 @@ function parseDockerTlsEndpoint(endpoint) {
|
|
|
62
124
|
const lower = trimmed.toLowerCase();
|
|
63
125
|
if (lower.startsWith('unix:')) return null;
|
|
64
126
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
if (rest.startsWith('[')) {
|
|
71
|
-
const end = rest.indexOf(']');
|
|
72
|
-
if (end === -1) return null;
|
|
73
|
-
host = rest.slice(1, end);
|
|
74
|
-
const after = rest.slice(end + 1);
|
|
75
|
-
if (after.startsWith(':')) {
|
|
76
|
-
const p = parseInt(after.slice(1), 10);
|
|
77
|
-
if (!Number.isNaN(p)) port = p;
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
const colonIdx = rest.lastIndexOf(':');
|
|
81
|
-
if (colonIdx > 0 && /^\d+$/.test(rest.slice(colonIdx + 1))) {
|
|
82
|
-
port = parseInt(rest.slice(colonIdx + 1), 10);
|
|
83
|
-
host = rest.slice(0, colonIdx);
|
|
84
|
-
} else {
|
|
85
|
-
host = rest;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
try {
|
|
90
|
-
const u = new URL(trimmed.includes('://') ? trimmed : `tcp://${trimmed}`);
|
|
91
|
-
host = u.hostname;
|
|
92
|
-
if (u.port) {
|
|
93
|
-
const p = parseInt(u.port, 10);
|
|
94
|
-
if (!Number.isNaN(p)) port = p;
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
host = String(host || '').trim();
|
|
102
|
-
if (!host) return null;
|
|
103
|
-
return { host, port, servername: host };
|
|
127
|
+
const parsed = lower.startsWith('tcp://')
|
|
128
|
+
? parseTcpSchemeEndpoint(trimmed)
|
|
129
|
+
: parseDockerEndpointViaUrl(trimmed);
|
|
130
|
+
if (!parsed || !parsed.host) return null;
|
|
131
|
+
return { host: parsed.host, port: parsed.port, servername: parsed.host };
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
/**
|
|
@@ -262,6 +290,32 @@ async function writeDockerDaemonCaMarker(certDir) {
|
|
|
262
290
|
await fs.writeFile(markerPath, `augmentedAt=${new Date().toISOString()}\n`, { mode: 0o600 });
|
|
263
291
|
}
|
|
264
292
|
|
|
293
|
+
/**
|
|
294
|
+
* @param {string} certDir
|
|
295
|
+
* @param {string} existingCaPem
|
|
296
|
+
* @param {string} dockerEndpoint
|
|
297
|
+
* @param {{ timeoutMs?: number }} fetchOpts
|
|
298
|
+
* @returns {Promise<boolean>}
|
|
299
|
+
*/
|
|
300
|
+
async function augmentCaPemForDockerVerify(certDir, existingCaPem, dockerEndpoint, fetchOpts) {
|
|
301
|
+
const fs = require('fs');
|
|
302
|
+
const path = require('path');
|
|
303
|
+
if (await caPemTrustsDockerDaemon(existingCaPem, dockerEndpoint, fetchOpts)) {
|
|
304
|
+
await writeDockerDaemonCaMarker(certDir);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
const augmented = await augmentCaPemWithDockerDaemonChain(existingCaPem, dockerEndpoint, fetchOpts);
|
|
308
|
+
if (!augmented || augmented === existingCaPem) return false;
|
|
309
|
+
const { normalizePemNewlines } = require('./dev-cert-helper');
|
|
310
|
+
await fs.promises.writeFile(
|
|
311
|
+
path.join(certDir, 'ca.pem'),
|
|
312
|
+
normalizePemNewlines(augmented),
|
|
313
|
+
{ mode: 0o600 }
|
|
314
|
+
);
|
|
315
|
+
await writeDockerDaemonCaMarker(certDir);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
265
319
|
/**
|
|
266
320
|
* Decide whether Docker CLI should verify the daemon certificate (DOCKER_TLS_VERIFY=1).
|
|
267
321
|
* When ca.pem holds only the internal issue-cert CA but the daemon uses a public/wildcard cert,
|
|
@@ -288,24 +342,7 @@ async function resolveRemoteDockerDaemonVerify(ctx) {
|
|
|
288
342
|
if (!dockerEndpoint) return true;
|
|
289
343
|
|
|
290
344
|
const fetchOpts = { timeoutMs: DEFAULT_FETCH_TIMEOUT_MS };
|
|
291
|
-
|
|
292
|
-
await writeDockerDaemonCaMarker(certDir);
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const augmented = await augmentCaPemWithDockerDaemonChain(existingCaPem, dockerEndpoint, fetchOpts);
|
|
297
|
-
if (augmented && augmented !== existingCaPem) {
|
|
298
|
-
const { normalizePemNewlines } = require('./dev-cert-helper');
|
|
299
|
-
await fs.promises.writeFile(
|
|
300
|
-
path.join(certDir, 'ca.pem'),
|
|
301
|
-
normalizePemNewlines(augmented),
|
|
302
|
-
{ mode: 0o600 }
|
|
303
|
-
);
|
|
304
|
-
await writeDockerDaemonCaMarker(certDir);
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return false;
|
|
345
|
+
return augmentCaPemForDockerVerify(certDir, existingCaPem, dockerEndpoint, fetchOpts);
|
|
309
346
|
}
|
|
310
347
|
|
|
311
348
|
module.exports = {
|
|
@@ -101,6 +101,7 @@ function hostnameMatchesDockerHost(dockerHost) {
|
|
|
101
101
|
const hnShort = hn.split('.')[0];
|
|
102
102
|
const hShort = h.split('.')[0];
|
|
103
103
|
if (h === hnShort || hn === hShort) return true;
|
|
104
|
+
if (hShort && hnShort && hShort === hnShort) return true;
|
|
104
105
|
return false;
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -120,8 +121,18 @@ function isReloadBindMountOnEngineHost(dockerEndpoint) {
|
|
|
120
121
|
return hostnameMatchesDockerHost(host);
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* True when host refers to this machine (loopback, FQDN, or short hostname match).
|
|
126
|
+
* @param {string} host
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
function isHostOnThisMachine(host) {
|
|
130
|
+
return hostnameMatchesDockerHost(host);
|
|
131
|
+
}
|
|
132
|
+
|
|
123
133
|
module.exports = {
|
|
124
134
|
extractHostFromDockerEndpoint,
|
|
125
135
|
isReloadBindMountOnEngineHost,
|
|
126
|
-
isLocalhostSyncSshHost
|
|
136
|
+
isLocalhostSyncSshHost,
|
|
137
|
+
isHostOnThisMachine
|
|
127
138
|
};
|
|
@@ -54,6 +54,36 @@ const FALLBACK_SECURITY_BY_AUTH = {
|
|
|
54
54
|
none: []
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @param {Array<{name: string, value: string}>} authSecureVars
|
|
59
|
+
* @param {Record<string, unknown>} security
|
|
60
|
+
* @param {string} prefix
|
|
61
|
+
* @param {string} systemKey
|
|
62
|
+
*/
|
|
63
|
+
function pushSecureVarsFromSecurityMap(authSecureVars, security, prefix, systemKey) {
|
|
64
|
+
for (const key of Object.keys(security)) {
|
|
65
|
+
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
66
|
+
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
67
|
+
authSecureVars.push({ name: envName, value: pathVal || `kv://${systemKey}/${key}` });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {Array<{name: string, value: string}>} authSecureVars
|
|
73
|
+
* @param {string} authMethod
|
|
74
|
+
* @param {string} prefix
|
|
75
|
+
* @param {string} systemKey
|
|
76
|
+
*/
|
|
77
|
+
function pushFallbackSecureVars(authSecureVars, authMethod, prefix, systemKey) {
|
|
78
|
+
const keys = FALLBACK_SECURITY_BY_AUTH[authMethod] || FALLBACK_SECURITY_BY_AUTH.apikey;
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
authSecureVars.push({
|
|
81
|
+
name: `KV_${prefix}_${securityKeyToVar(key)}`,
|
|
82
|
+
value: `kv://${systemKey}/${key}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
/**
|
|
58
88
|
* Builds authSecureVars array from system authentication.security (or fallback by auth type).
|
|
59
89
|
* @param {Object} system - System object with key and authentication
|
|
@@ -65,6 +95,9 @@ function buildAuthSecureVarsFromSystem(system) {
|
|
|
65
95
|
const prefix = systemKeyToKvPrefix(systemKey);
|
|
66
96
|
if (!prefix) return authSecureVars;
|
|
67
97
|
const authBlock = system?.authentication || system?.auth;
|
|
98
|
+
if (authBlock?.authType === 'apiProvider' || authBlock?.authType === 'none') {
|
|
99
|
+
return authSecureVars;
|
|
100
|
+
}
|
|
68
101
|
const credential = getAuthCredentialTemplate(authBlock);
|
|
69
102
|
const security = credential?.security || authBlock?.security;
|
|
70
103
|
const authMethod = (
|
|
@@ -74,19 +107,9 @@ function buildAuthSecureVarsFromSystem(system) {
|
|
|
74
107
|
'apikey'
|
|
75
108
|
).toLowerCase();
|
|
76
109
|
if (security && typeof security === 'object' && Object.keys(security).length > 0) {
|
|
77
|
-
|
|
78
|
-
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
79
|
-
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
80
|
-
authSecureVars.push({ name: envName, value: pathVal || `kv://${systemKey}/${key}` });
|
|
81
|
-
}
|
|
110
|
+
pushSecureVarsFromSecurityMap(authSecureVars, security, prefix, systemKey);
|
|
82
111
|
} else {
|
|
83
|
-
|
|
84
|
-
for (const key of keys) {
|
|
85
|
-
authSecureVars.push({
|
|
86
|
-
name: `KV_${prefix}_${securityKeyToVar(key)}`,
|
|
87
|
-
value: `kv://${systemKey}/${key}`
|
|
88
|
-
});
|
|
89
|
-
}
|
|
112
|
+
pushFallbackSecureVars(authSecureVars, authMethod, prefix, systemKey);
|
|
90
113
|
}
|
|
91
114
|
return authSecureVars;
|
|
92
115
|
}
|
|
@@ -116,13 +139,25 @@ function buildConfigurationEntries(system) {
|
|
|
116
139
|
* @returns {{ authMethod: string, authSecureVars: Array<{name: string, value: string}>, authNonSecureVarNames: string[], configuration: Array<{name: string, value: string, comment: string}> }}
|
|
117
140
|
*/
|
|
118
141
|
function buildExternalEnvTemplateContext(system) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'
|
|
142
|
+
const authBlock = system?.authentication || system?.auth;
|
|
143
|
+
const credential = getAuthCredentialTemplate(authBlock);
|
|
144
|
+
let authMethod = 'apikey';
|
|
145
|
+
if (authBlock?.authType === 'apiProvider') {
|
|
146
|
+
authMethod = 'apiprovider';
|
|
147
|
+
} else if (authBlock?.authType === 'none') {
|
|
148
|
+
authMethod = 'none';
|
|
149
|
+
} else {
|
|
150
|
+
authMethod = (
|
|
151
|
+
credential?.method ||
|
|
152
|
+
authBlock?.method ||
|
|
153
|
+
authBlock?.type ||
|
|
154
|
+
'apikey'
|
|
155
|
+
).toLowerCase();
|
|
156
|
+
}
|
|
124
157
|
const authSecureVars = buildAuthSecureVarsFromSystem(system);
|
|
125
|
-
const authVars =
|
|
158
|
+
const authVars =
|
|
159
|
+
(credential?.variables && typeof credential.variables === 'object' ? credential.variables : null) ||
|
|
160
|
+
(authBlock?.variables && typeof authBlock.variables === 'object' ? authBlock.variables : {});
|
|
126
161
|
const authNonSecureVarNames = Object.keys(authVars);
|
|
127
162
|
const configuration = buildConfigurationEntries(system);
|
|
128
163
|
return {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-place health-check wait line (TTY ora or carriage-return refresh).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Health check poll presentation helpers
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
function shouldUseHealthCheckPollSpinner() {
|
|
15
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {number} attemptIndex - Zero-based attempt index after a failed probe
|
|
20
|
+
* @param {number} maxAttempts
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function buildHealthCheckPollText(attemptIndex, maxAttempts) {
|
|
24
|
+
return chalk.yellow(
|
|
25
|
+
`Waiting for health check… (${attemptIndex + 1}/${maxAttempts})`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {number} maxAttempts
|
|
31
|
+
* @returns {{ usesSpinner: boolean, onWaiting: (attemptIndex: number) => void, finish: () => void }}
|
|
32
|
+
*/
|
|
33
|
+
function createHealthCheckPollHandlers(maxAttempts) {
|
|
34
|
+
if (shouldUseHealthCheckPollSpinner()) {
|
|
35
|
+
const ora = require('ora');
|
|
36
|
+
const spinner = ora({
|
|
37
|
+
text: buildHealthCheckPollText(0, maxAttempts),
|
|
38
|
+
spinner: 'dots'
|
|
39
|
+
});
|
|
40
|
+
let started = false;
|
|
41
|
+
return {
|
|
42
|
+
usesSpinner: true,
|
|
43
|
+
onWaiting: (attemptIndex) => {
|
|
44
|
+
if (!started) {
|
|
45
|
+
spinner.start();
|
|
46
|
+
started = true;
|
|
47
|
+
}
|
|
48
|
+
spinner.text = buildHealthCheckPollText(attemptIndex, maxAttempts);
|
|
49
|
+
},
|
|
50
|
+
finish: () => {
|
|
51
|
+
if (started) {
|
|
52
|
+
spinner.stop();
|
|
53
|
+
process.stdout.write('\n');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let lastLine = '';
|
|
60
|
+
return {
|
|
61
|
+
usesSpinner: false,
|
|
62
|
+
onWaiting: (attemptIndex) => {
|
|
63
|
+
const line = buildHealthCheckPollText(attemptIndex, maxAttempts);
|
|
64
|
+
if (process.stdout && process.stdout.isTTY) {
|
|
65
|
+
process.stdout.write(`\r${line}`);
|
|
66
|
+
lastLine = line;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
logger.log(line);
|
|
70
|
+
},
|
|
71
|
+
finish: () => {
|
|
72
|
+
if (lastLine && process.stdout && process.stdout.isTTY) {
|
|
73
|
+
process.stdout.write('\n');
|
|
74
|
+
lastLine = '';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
buildHealthCheckPollText,
|
|
82
|
+
createHealthCheckPollHandlers,
|
|
83
|
+
shouldUseHealthCheckPollSpinner
|
|
84
|
+
};
|