@aifabrix/builder 2.44.5 → 2.45.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/.cursor/rules/cli-layout.mdc +8 -4
- package/.cursor/rules/project-rules.mdc +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +104 -2
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +126 -0
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +58 -19
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +148 -74
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +83 -49
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +460 -0
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +31 -3
- package/lib/cli/setup-auth.js +98 -27
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +132 -118
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +143 -84
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +468 -0
- package/lib/commands/setup-prompts.js +421 -0
- package/lib/commands/setup.js +254 -0
- package/lib/commands/teardown.js +277 -0
- package/lib/commands/up-common.js +113 -19
- package/lib/commands/up-dataplane.js +44 -19
- package/lib/commands/up-miso.js +18 -18
- package/lib/commands/upload.js +111 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +428 -0
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +29 -1
- package/lib/core/secrets-load.js +252 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +9 -2
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/internal/node-fs.js +2 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/external-readme.js +71 -2
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +28 -10
- package/lib/utils/health-check.js +139 -107
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +308 -76
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +49 -4
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +26 -13
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +42 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -388
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +52 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +158 -76
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +5 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +9 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.npmrc.token +0 -1
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -8,6 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Treat undefined/null and whitespace-only strings as no value so prefixed slots
|
|
13
|
+
* cannot shadow unprefixed secrets with empty placeholders (plan 117 scoped KV).
|
|
14
|
+
*
|
|
15
|
+
* @param {*} v
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isKvSlotEmpty(v) {
|
|
19
|
+
if (v === undefined || v === null) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (typeof v === 'string' && v.trim() === '') {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
/**
|
|
12
29
|
* @param {Function} getValueByPath - From secrets-helpers
|
|
13
30
|
* @returns {{ getValueByPathWithEnvScope: Function, mergeSecretsWithPrefixedCopies: Function }}
|
|
@@ -27,7 +44,7 @@ function createScopedKvHelpers(getValueByPath) {
|
|
|
27
44
|
const prefix = `${String(envKey).toLowerCase()}-`;
|
|
28
45
|
const prefixedPath = prefix + pathStr;
|
|
29
46
|
const fromPrefixed = getValueByPath(secrets, prefixedPath);
|
|
30
|
-
if (fromPrefixed
|
|
47
|
+
if (!isKvSlotEmpty(fromPrefixed)) {
|
|
31
48
|
return fromPrefixed;
|
|
32
49
|
}
|
|
33
50
|
return getValueByPath(secrets, pathStr);
|
|
@@ -47,7 +64,7 @@ function createScopedKvHelpers(getValueByPath) {
|
|
|
47
64
|
for (const [k, v] of Object.entries(secrets)) {
|
|
48
65
|
if (typeof k !== 'string' || !k || k.startsWith(prefix)) continue;
|
|
49
66
|
const pk = prefix + k;
|
|
50
|
-
if (merged[pk]
|
|
67
|
+
if (isKvSlotEmpty(merged[pk]) && !isKvSlotEmpty(v)) {
|
|
51
68
|
merged[pk] = v;
|
|
52
69
|
}
|
|
53
70
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist resolved kv:// values into the primary user secrets file so regenerate (.env) stays stable.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview After env.template resolves, copy merged secret values into ~/.aifabrix/secrets.local.yaml
|
|
5
|
+
* when keys are missing or empty locally **and** not already provided by the configured shared store
|
|
6
|
+
* (`aifabrix-secrets` remote API or shared YAML), so shared-only keys are not duplicated into user secrets.
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const logger = require('./logger');
|
|
14
|
+
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
15
|
+
const localSecretsModule = require('./local-secrets');
|
|
16
|
+
const secretsUtils = require('./secrets-utils');
|
|
17
|
+
const { loadConfiguredSharedSecretsStore } = require('./remote-secrets-loader');
|
|
18
|
+
const { decryptSecretsObject } = require('../core/secrets-load');
|
|
19
|
+
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
20
|
+
const {
|
|
21
|
+
resolveKvRefValue,
|
|
22
|
+
mergeSecretsWithPrefixedCopies,
|
|
23
|
+
isKvKeyAllowedEmptyWhenAbsent
|
|
24
|
+
} = require('./secrets-helpers');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* After successful kv resolution, write resolved values to the user secrets file when local slot is empty.
|
|
28
|
+
* Does not overwrite non-empty local values (user wins).
|
|
29
|
+
*
|
|
30
|
+
* @async
|
|
31
|
+
* @param {string} templateContent - Original env.template text (still contains kv:// refs)
|
|
32
|
+
* @param {Object} mergedSecrets - Decrypted secrets map passed to resolveKvReferences (same as loadSecrets output)
|
|
33
|
+
* @param {{ effective?: boolean, envKey?: string }|null} scopedKv - Scoped kv context from generateEnvContent
|
|
34
|
+
* @param {Object} [options]
|
|
35
|
+
* @param {boolean} [options.skipMaterializeKvToLocal] - Skip persistence (tests / programmatic)
|
|
36
|
+
* @returns {Promise<string[]>} Keys materialized (written) to local file
|
|
37
|
+
*/
|
|
38
|
+
function buildScopedMaps(mergedSecrets, scopedKv) {
|
|
39
|
+
const effective = Boolean(scopedKv && scopedKv.effective && scopedKv.envKey);
|
|
40
|
+
const secretsMap = effective ? mergeSecretsWithPrefixedCopies(mergedSecrets, scopedKv.envKey) : mergedSecrets;
|
|
41
|
+
const rawLocal = secretsUtils.loadPrimaryUserSecrets();
|
|
42
|
+
const localMap = effective ? mergeSecretsWithPrefixedCopies(rawLocal, scopedKv.envKey) : rawLocal;
|
|
43
|
+
return { effective, secretsMap, localMap };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shouldSkipPersist(pathStr, resolvedVal, localVal) {
|
|
47
|
+
if (resolvedVal === undefined || resolvedVal === null) return true;
|
|
48
|
+
if (
|
|
49
|
+
typeof resolvedVal === 'string' &&
|
|
50
|
+
resolvedVal.trim() === '' &&
|
|
51
|
+
isKvKeyAllowedEmptyWhenAbsent(pathStr)
|
|
52
|
+
) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (localVal !== undefined && localVal !== null && String(localVal).trim() !== '') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isNonEmptyResolvedKv(val) {
|
|
62
|
+
if (val === undefined || val === null) return false;
|
|
63
|
+
if (typeof val === 'string' && val.trim() === '') return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveSharedScopedMapForMaterialize(scopedKv, effective) {
|
|
68
|
+
try {
|
|
69
|
+
const rawShared = await loadConfiguredSharedSecretsStore();
|
|
70
|
+
const decryptedShared = rawShared ? await decryptSecretsObject(rawShared) : null;
|
|
71
|
+
if (!decryptedShared) return null;
|
|
72
|
+
return effective && scopedKv && scopedKv.envKey
|
|
73
|
+
? mergeSecretsWithPrefixedCopies(decryptedShared, scopedKv.envKey)
|
|
74
|
+
: decryptedShared;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function plaintextKvForLocalPersist(pathStr, secretsMap, localMap, sharedScoped, scopedKv, effective) {
|
|
81
|
+
const resolvedVal = resolveKvRefValue(secretsMap, pathStr, scopedKv?.envKey, effective);
|
|
82
|
+
const localVal = resolveKvRefValue(localMap, pathStr, scopedKv?.envKey, effective);
|
|
83
|
+
if (shouldSkipPersist(pathStr, resolvedVal, localVal)) return null;
|
|
84
|
+
const sharedVal =
|
|
85
|
+
sharedScoped && typeof sharedScoped === 'object'
|
|
86
|
+
? resolveKvRefValue(sharedScoped, pathStr, scopedKv?.envKey, effective)
|
|
87
|
+
: undefined;
|
|
88
|
+
if (isNonEmptyResolvedKv(sharedVal)) return null;
|
|
89
|
+
return typeof resolvedVal === 'string' ? resolvedVal : String(resolvedVal);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function materializeResolvedKvSecretsToUserLocal(templateContent, mergedSecrets, scopedKv, options = {}) {
|
|
93
|
+
if (
|
|
94
|
+
!templateContent ||
|
|
95
|
+
typeof templateContent !== 'string' ||
|
|
96
|
+
options.skipMaterializeKvToLocal === true
|
|
97
|
+
) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const { effective, secretsMap, localMap } = buildScopedMaps(mergedSecrets, scopedKv);
|
|
103
|
+
const sharedScoped = await resolveSharedScopedMapForMaterialize(scopedKv, effective);
|
|
104
|
+
|
|
105
|
+
const paths = collectUniqueKvPathStrings(templateContent);
|
|
106
|
+
const materialized = [];
|
|
107
|
+
|
|
108
|
+
for (const pathStr of paths) {
|
|
109
|
+
const plain = plaintextKvForLocalPersist(pathStr, secretsMap, localMap, sharedScoped, scopedKv, effective);
|
|
110
|
+
if (plain === null) continue;
|
|
111
|
+
await localSecretsModule.saveLocalSecret(pathStr, plain);
|
|
112
|
+
materialized.push(pathStr);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (materialized.length > 0) {
|
|
116
|
+
logger.log(
|
|
117
|
+
formatSuccessLine(
|
|
118
|
+
`Saved ${materialized.length} resolved kv key(s) to local secrets for stable reinstalls: ${materialized.join(', ')}`
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return materialized;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (typeof logger.warn === 'function') {
|
|
126
|
+
logger.warn(`Could not materialize resolved kv secrets to local file: ${err.message}`);
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
materializeResolvedKvSecretsToUserLocal
|
|
134
|
+
};
|
|
@@ -13,6 +13,15 @@ const path = require('path');
|
|
|
13
13
|
const config = require('../core/config');
|
|
14
14
|
const paths = require('./paths');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} s
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isHttpOrHttpsUrl(s) {
|
|
21
|
+
const t = String(s || '').trim();
|
|
22
|
+
return t.startsWith('http://') || t.startsWith('https://');
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Resolves secrets file path when an explicit path is provided.
|
|
18
27
|
* If not provided, returns default fallback under <home>/secrets.yaml.
|
|
@@ -32,9 +41,8 @@ function resolveSecretsPath(secretsPath) {
|
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
/**
|
|
35
|
-
* Determines
|
|
36
|
-
*
|
|
37
|
-
* Uses config.yaml for default secrets path as fallback
|
|
44
|
+
* Determines paths used for default `loadSecrets()` (no explicit path): primary user secrets file
|
|
45
|
+
* and configured `aifabrix-secrets` (shared YAML file path or remote API URL).
|
|
38
46
|
*
|
|
39
47
|
* @async
|
|
40
48
|
* @function getActualSecretsPath
|
|
@@ -42,7 +50,8 @@ function resolveSecretsPath(secretsPath) {
|
|
|
42
50
|
* @param {string} [_appName] - Application name (optional, for backward compatibility, unused)
|
|
43
51
|
* @returns {Promise<Object>} Object with userPath and buildPath (if configured)
|
|
44
52
|
* @returns {string} returns.userPath - User's secrets file path (~/.aifabrix/secrets.local.yaml)
|
|
45
|
-
* @returns {string|null} returns.buildPath -
|
|
53
|
+
* @returns {string|null} returns.buildPath - On-disk shared secrets file (if configured; never an http(s) URL)
|
|
54
|
+
* @returns {string|null} [returns.sharedSecretsApiUrl] - When aifabrix-secrets is an API URL (for error messages; loadSecrets merges this API into the kv:// resolution map)
|
|
46
55
|
*/
|
|
47
56
|
async function getActualSecretsPath(secretsPath, _appName) {
|
|
48
57
|
// If explicit path provided, use it (backward compatibility)
|
|
@@ -50,30 +59,34 @@ async function getActualSecretsPath(secretsPath, _appName) {
|
|
|
50
59
|
const resolvedPath = resolveSecretsPath(secretsPath);
|
|
51
60
|
return {
|
|
52
61
|
userPath: resolvedPath,
|
|
53
|
-
buildPath: null
|
|
62
|
+
buildPath: null,
|
|
63
|
+
sharedSecretsApiUrl: null
|
|
54
64
|
};
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
//
|
|
58
|
-
const userSecretsPath =
|
|
67
|
+
// Default lookup: primary user file plus aifabrix-secrets (file or https URL)
|
|
68
|
+
const userSecretsPath = paths.getPrimaryUserSecretsLocalPath();
|
|
59
69
|
|
|
60
|
-
// Check config.yaml for canonical secrets path
|
|
61
70
|
let buildSecretsPath = null;
|
|
71
|
+
let sharedSecretsApiUrl = null;
|
|
62
72
|
try {
|
|
63
73
|
const canonicalSecretsPath = await config.getAifabrixSecretsPath();
|
|
64
74
|
if (canonicalSecretsPath) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
const raw = String(canonicalSecretsPath).trim();
|
|
76
|
+
if (isHttpOrHttpsUrl(raw)) {
|
|
77
|
+
sharedSecretsApiUrl = raw.replace(/\/+$/, '');
|
|
78
|
+
} else {
|
|
79
|
+
buildSecretsPath = path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
|
80
|
+
}
|
|
68
81
|
}
|
|
69
82
|
} catch (error) {
|
|
70
83
|
// Ignore errors, continue
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
// Return both paths (even if files don't exist) for error messages
|
|
74
86
|
return {
|
|
75
87
|
userPath: userSecretsPath,
|
|
76
|
-
buildPath: buildSecretsPath
|
|
88
|
+
buildPath: buildSecretsPath,
|
|
89
|
+
sharedSecretsApiUrl
|
|
77
90
|
};
|
|
78
91
|
}
|
|
79
92
|
|
|
@@ -58,18 +58,14 @@ async function loadSecretsFromFile(filePath) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Loads user secrets from getPrimaryUserSecretsLocalPath
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Loads user secrets from {@link pathsUtil.getPrimaryUserSecretsLocalPath} (beside `config.yaml`).
|
|
62
|
+
* If that file is missing, reads a legacy file at `getAifabrixHome()/secrets.local.yaml` when paths
|
|
63
|
+
* differ (older CLI when `aifabrix-home` was POSIX home).
|
|
64
64
|
*
|
|
65
65
|
* @function loadPrimaryUserSecrets
|
|
66
66
|
* @returns {Object} Loaded secrets object or empty object
|
|
67
67
|
*/
|
|
68
|
-
function
|
|
69
|
-
const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
70
|
-
if (!fs.existsSync(userSecretsPath)) {
|
|
71
|
-
return {};
|
|
72
|
-
}
|
|
68
|
+
function readPrimaryUserSecretsAtPath(userSecretsPath) {
|
|
73
69
|
ensureSecureFilePermissions(userSecretsPath);
|
|
74
70
|
|
|
75
71
|
try {
|
|
@@ -84,8 +80,22 @@ function loadPrimaryUserSecrets() {
|
|
|
84
80
|
throw error;
|
|
85
81
|
}
|
|
86
82
|
logger.warn(`Warning: Could not read secrets file ${userSecretsPath}: ${error.message}`);
|
|
87
|
-
return
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadPrimaryUserSecrets() {
|
|
88
|
+
const primaryPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
89
|
+
if (fs.existsSync(primaryPath)) {
|
|
90
|
+
const fromPrimary = readPrimaryUserSecretsAtPath(primaryPath);
|
|
91
|
+
return fromPrimary !== null ? fromPrimary : {};
|
|
92
|
+
}
|
|
93
|
+
const legacyPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
94
|
+
if (path.resolve(legacyPath) !== path.resolve(primaryPath) && fs.existsSync(legacyPath)) {
|
|
95
|
+
const fromLegacy = readPrimaryUserSecretsAtPath(legacyPath);
|
|
96
|
+
return fromLegacy !== null ? fromLegacy : {};
|
|
88
97
|
}
|
|
98
|
+
return {};
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
/**
|
|
@@ -128,7 +138,7 @@ function loadDefaultSecrets() {
|
|
|
128
138
|
|
|
129
139
|
/**
|
|
130
140
|
* Creates the primary user secrets file if missing (empty map) for first-run installs.
|
|
131
|
-
* Uses the same directory as {@link loadPrimaryUserSecrets} (config
|
|
141
|
+
* Uses the same directory as {@link loadPrimaryUserSecrets} (beside `config.yaml`).
|
|
132
142
|
*
|
|
133
143
|
* @function ensurePrimaryUserSecretsFileExists
|
|
134
144
|
*/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picks the parent directory for platform `builder/<app>` materialization when the project has no
|
|
3
|
+
* `builder/<app>`. Compares the effective config directory (`getAifabrixSystemDir`) to the resolved
|
|
4
|
+
* AI Fabrix home (`getAifabrixHome`, from `AIFABRIX_HOME` or `aifabrix-home` in config.yaml).
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview System builder root parent (config dir vs home override)
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* When the effective config directory lies under the resolved AI Fabrix home, keep state (including
|
|
17
|
+
* `builder/`) beside `config.yaml` (e.g. `$HOME/.aifabrix/builder` when `AIFABRIX_HOME=$HOME`).
|
|
18
|
+
* When `aifabrix-home` relocates home outside the config tree, materialize under that home instead.
|
|
19
|
+
*
|
|
20
|
+
* Uses a resolved-path prefix check (not `path.relative`) so "nested under home" is stable across
|
|
21
|
+
* platforms (e.g. sibling paths like `/var/aifabrix` vs `/var/aifabrix-config` must not be treated
|
|
22
|
+
* as nested).
|
|
23
|
+
*
|
|
24
|
+
* @param {string} systemDir - Absolute config/state directory (same as `getAifabrixSystemDir()`).
|
|
25
|
+
* @param {string} homeDir - Absolute AI Fabrix home (`getAifabrixHome()`).
|
|
26
|
+
* @returns {string} Absolute directory whose `builder/` subdir is used
|
|
27
|
+
*/
|
|
28
|
+
function resolveSystemBuilderParentDir(systemDir, homeDir) {
|
|
29
|
+
const s = path.resolve(systemDir);
|
|
30
|
+
const h = path.resolve(homeDir);
|
|
31
|
+
if (s === h) {
|
|
32
|
+
return s;
|
|
33
|
+
}
|
|
34
|
+
const sep = path.sep;
|
|
35
|
+
const homePrefix = h.endsWith(sep) ? h : `${h}${sep}`;
|
|
36
|
+
const configUnderHome = s.startsWith(homePrefix);
|
|
37
|
+
return configUnderHome ? s : h;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
resolveSystemBuilderParentDir
|
|
42
|
+
};
|
|
@@ -11,6 +11,26 @@
|
|
|
11
11
|
const { expandFrontDoorHostPlaceholders } = require('./compose-generator');
|
|
12
12
|
const { publishedHostPort, localHostPort } = require('./declarative-url-ports');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Public URL authorities that must use `http://` (browsers hit loopback without TLS in practice).
|
|
16
|
+
*
|
|
17
|
+
* @param {string} hostname - Parsed URL hostname
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isLocalhostPublicSchemeHostname(hostname) {
|
|
21
|
+
const h = String(hostname || '').trim().toLowerCase();
|
|
22
|
+
if (!h) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (h === 'localhost') {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (h === '127.0.0.1') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return h === '::1';
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
/**
|
|
15
35
|
* Expand frontDoorRouting.host placeholders for url:// (same rules as Traefik labels in compose-generator).
|
|
16
36
|
*
|
|
@@ -31,6 +51,17 @@ function hostPortForProfile(profile, listenPort, developerIdNum) {
|
|
|
31
51
|
: localHostPort(listenPort, developerIdNum);
|
|
32
52
|
}
|
|
33
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Scheme for `declarativePublicUrlsUseLocalhost` bases: always **http** for loopback reachability
|
|
56
|
+
* (ignore `tlsEnabled` / `remote-server` https — localhost URLs are not served with TLS in dev).
|
|
57
|
+
* @param {Object} opts - Kept for call-site compatibility (same shape as {@link computePublicUrlBaseString})
|
|
58
|
+
* @returns {'http'}
|
|
59
|
+
*/
|
|
60
|
+
function schemeForDeclarativeLocalhostPublicBase(opts) {
|
|
61
|
+
void opts;
|
|
62
|
+
return 'http';
|
|
63
|
+
}
|
|
64
|
+
|
|
34
65
|
/**
|
|
35
66
|
* Local profile: workstation `+10` applies only to the app being resolved (`currentAppKey`).
|
|
36
67
|
* Cross-app tokens (e.g. `url://keycloak-public` from miso-controller) use `publishedHostPort`
|
|
@@ -62,10 +93,10 @@ function resolveHostPortForDeclarativePublic(opts) {
|
|
|
62
93
|
}
|
|
63
94
|
|
|
64
95
|
/**
|
|
65
|
-
*
|
|
66
|
-
* If the URL already has an explicit port, keep host:port but
|
|
67
|
-
* literal `https://` in `remote-server` when TLS is off
|
|
68
|
-
* If `remote-server` omits a port, append the profile-specific published/listen-derived port.
|
|
96
|
+
* Public authority from `remote-server` when expansion opts in (Traefik on, or per-app `proxy: true` ⇒
|
|
97
|
+
* `declarativePublicUrlsUseLocalhost === false`). If the URL already has an explicit port, keep host:port but
|
|
98
|
+
* **scheme follows `infraTlsEnabled`**, not the literal `https://` in `remote-server` when TLS is off
|
|
99
|
+
* (`up-infra` without `--tls`). If `remote-server` omits a port, append the profile-specific published/listen-derived port.
|
|
69
100
|
*
|
|
70
101
|
* @param {string} rawRemote
|
|
71
102
|
* @param {'docker'|'local'} profile
|
|
@@ -85,7 +116,10 @@ function remotePublicBaseWithoutTraefik(
|
|
|
85
116
|
const raw = String(rawRemote || '').trim().replace(/\/+$/, '');
|
|
86
117
|
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : (infraTlsEnabled ? `https://${raw}` : `http://${raw}`);
|
|
87
118
|
const u = new URL(withScheme);
|
|
88
|
-
|
|
119
|
+
let scheme = infraTlsEnabled ? 'https' : 'http';
|
|
120
|
+
if (isLocalhostPublicSchemeHostname(u.hostname)) {
|
|
121
|
+
scheme = 'http';
|
|
122
|
+
}
|
|
89
123
|
|
|
90
124
|
if (u.port !== '') {
|
|
91
125
|
return `${scheme}://${u.host}`;
|
|
@@ -105,17 +139,31 @@ function remotePublicBaseWithoutTraefik(
|
|
|
105
139
|
return `${scheme}://${u.hostname}:${hostPort}`;
|
|
106
140
|
}
|
|
107
141
|
|
|
142
|
+
/**
|
|
143
|
+
* True when `frontDoorRouting.host` depends on `remote-server` for expansion (`${REMOTE_HOST}`).
|
|
144
|
+
* Without a remote, expansion yields a bare `devNN` label (no DNS) — public `url://` bases must fall back to localhost.
|
|
145
|
+
*
|
|
146
|
+
* @param {string|null|undefined} hostTemplate
|
|
147
|
+
* @returns {boolean}
|
|
148
|
+
*/
|
|
149
|
+
function templateUsesRemoteHostPlaceholder(hostTemplate) {
|
|
150
|
+
return /\$\{REMOTE_HOST\}/.test(String(hostTemplate || ''));
|
|
151
|
+
}
|
|
152
|
+
|
|
108
153
|
/**
|
|
109
154
|
* @param {Object} opts - same shape as computePublicUrlBaseString
|
|
110
155
|
* @returns {string|null}
|
|
111
156
|
*/
|
|
112
157
|
function buildTraefikPublicBaseIfApplicable(opts) {
|
|
113
|
-
const { traefik, pathActive, hostTemplate,
|
|
158
|
+
const { traefik, pathActive, hostTemplate, developerIdRaw, remoteServer, infraTlsEnabled } =
|
|
114
159
|
opts;
|
|
115
160
|
// Plan 124: Traefik host authority only when pathActive (traefik ∧ frontDoorRouting.enabled)
|
|
116
161
|
if (!traefik || !pathActive || !hostTemplate || !String(hostTemplate).trim()) {
|
|
117
162
|
return null;
|
|
118
163
|
}
|
|
164
|
+
if (templateUsesRemoteHostPlaceholder(hostTemplate) && !String(remoteServer || '').trim()) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
119
167
|
const expanded = expandFrontDoorHostTemplateForUrls(hostTemplate, {
|
|
120
168
|
developerIdRaw,
|
|
121
169
|
remoteServer
|
|
@@ -123,8 +171,12 @@ function buildTraefikPublicBaseIfApplicable(opts) {
|
|
|
123
171
|
if (!expanded) {
|
|
124
172
|
return null;
|
|
125
173
|
}
|
|
126
|
-
|
|
127
|
-
const
|
|
174
|
+
// Scheme follows global `tlsEnabled` (`up-infra --tls`) only — not `frontDoorRouting.tls` when infra TLS is off.
|
|
175
|
+
const useHttps = Boolean(infraTlsEnabled);
|
|
176
|
+
let scheme = useHttps ? 'https' : 'http';
|
|
177
|
+
if (isLocalhostPublicSchemeHostname(expanded)) {
|
|
178
|
+
scheme = 'http';
|
|
179
|
+
}
|
|
128
180
|
return `${scheme}://${expanded}`.replace(/\/+$/, '');
|
|
129
181
|
}
|
|
130
182
|
|
|
@@ -140,8 +192,9 @@ function buildTraefikPublicBaseIfApplicable(opts) {
|
|
|
140
192
|
* @param {'docker'|'local'} opts.profile
|
|
141
193
|
* @param {number} opts.listenPort
|
|
142
194
|
* @param {number} opts.developerIdNum
|
|
143
|
-
* @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`);
|
|
195
|
+
* @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); Traefik front-door public bases use **`https`** only when this is **true** (even if `frontDoorRouting.tls` is **false**). When **false**, front-door bases use **`http`** regardless of `frontDoorRouting.tls`. Loopback hostnames stay **http** always.
|
|
144
196
|
* @param {boolean} [opts.pathActive] - traefik ∧ frontDoorRouting.enabled; required for Traefik host branch (plan 124)
|
|
197
|
+
* @param {boolean|undefined} [opts.declarativePublicUrlsUseLocalhost] - **`true`**: force localhost authority (+ port rules below). **`false`**: allow `remote-server` as authority when Traefik is on, or when global `traefik` is not `false` and per-app `proxy` opts in without Traefik. **`undefined`**: default — omit `remote-server` when Traefik is off so published services use `http://localhost:<port>` (e.g. `af setup` / device login URLs). When user config has **`traefik: false`**, expansion forces localhost for public bases regardless of per-app `proxy`.
|
|
145
198
|
* @returns {string}
|
|
146
199
|
*/
|
|
147
200
|
function computePublicUrlBaseString(opts) {
|
|
@@ -159,7 +212,23 @@ function computePublicUrlBaseString(opts) {
|
|
|
159
212
|
return traefikBase;
|
|
160
213
|
}
|
|
161
214
|
|
|
162
|
-
if (
|
|
215
|
+
if (opts.declarativePublicUrlsUseLocalhost) {
|
|
216
|
+
const hostPort = resolveHostPortForDeclarativePublic({
|
|
217
|
+
profile,
|
|
218
|
+
listenPort,
|
|
219
|
+
developerIdNum,
|
|
220
|
+
declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
|
|
221
|
+
declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
|
|
222
|
+
});
|
|
223
|
+
const scheme = schemeForDeclarativeLocalhostPublicBase(opts);
|
|
224
|
+
return `${scheme}://localhost:${hostPort}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const remoteTrimmed = remoteServer && String(remoteServer).trim();
|
|
228
|
+
const useRemotePublicAuthority =
|
|
229
|
+
Boolean(remoteTrimmed) &&
|
|
230
|
+
(Boolean(opts.traefik) || opts.declarativePublicUrlsUseLocalhost === false);
|
|
231
|
+
if (useRemotePublicAuthority) {
|
|
163
232
|
return remotePublicBaseWithoutTraefik(
|
|
164
233
|
remoteServer,
|
|
165
234
|
profile,
|
|
@@ -177,8 +246,7 @@ function computePublicUrlBaseString(opts) {
|
|
|
177
246
|
declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
|
|
178
247
|
declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
|
|
179
248
|
});
|
|
180
|
-
|
|
181
|
-
return `${scheme}://localhost:${hostPort}`;
|
|
249
|
+
return `http://localhost:${hostPort}`;
|
|
182
250
|
}
|
|
183
251
|
|
|
184
252
|
module.exports = {
|