@aifabrix/builder 2.48.0 → 2.49.1
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/skills/README.md +14 -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 +50 -12
- package/lib/cli/infra-guided-footers.js +164 -0
- package/lib/cli/infra-guided-spinners.js +57 -0
- package/lib/cli/infra-guided.js +52 -181
- 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/teardown.js +3 -5
- package/lib/commands/up-common.js +8 -11
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +19 -4
- package/lib/commands/up-dataplane.js +155 -24
- package/lib/commands/up-miso.js +6 -2
- 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 +119 -0
- package/lib/utils/health-check-url.js +54 -34
- package/lib/utils/health-check.js +91 -35
- package/lib/utils/infra-optional-service-flags.js +31 -2
- package/lib/utils/mutagen.js +7 -4
- package/lib/utils/paths.js +26 -36
- 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/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +24 -6
- package/templates/applications/dataplane/rbac.yaml +11 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +5 -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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Footer and header layout for guided infra/platform CLI flows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
const config = require('../core/config');
|
|
10
|
+
const { computeAppBaseUrl, resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
|
|
11
|
+
const { successGlyph } = require('../utils/cli-test-layout-chalk');
|
|
12
|
+
|
|
13
|
+
const SEPARATOR = '────────────────────────────────────────';
|
|
14
|
+
|
|
15
|
+
function title(text) {
|
|
16
|
+
return chalk.bold(text);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function logSection(label, value) {
|
|
20
|
+
logger.log(title(label));
|
|
21
|
+
logger.log(` ${value}`);
|
|
22
|
+
logger.log('');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function logFooterStart(label) {
|
|
26
|
+
logger.log('');
|
|
27
|
+
logger.log(SEPARATOR);
|
|
28
|
+
logger.log(title(label));
|
|
29
|
+
logger.log(SEPARATOR);
|
|
30
|
+
logger.log('');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function logFooterEnd() {
|
|
34
|
+
logger.log(SEPARATOR);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @async
|
|
39
|
+
* @returns {Promise<string>}
|
|
40
|
+
*/
|
|
41
|
+
async function resolvePlatformControllerUrlForDisplay() {
|
|
42
|
+
const stored = await config.getPlatformControllerUrl();
|
|
43
|
+
if (stored) {
|
|
44
|
+
return stored;
|
|
45
|
+
}
|
|
46
|
+
return resolvePlatformControllerUrl();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function logPlatformReadyFooter() {
|
|
50
|
+
const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
|
|
51
|
+
resolvePlatformControllerUrlForDisplay(),
|
|
52
|
+
computeAppBaseUrl('dataplane'),
|
|
53
|
+
computeAppBaseUrl('keycloak')
|
|
54
|
+
]);
|
|
55
|
+
const cfg = await config.getConfig();
|
|
56
|
+
const env = cfg && cfg.environment ? cfg.environment : 'dev';
|
|
57
|
+
|
|
58
|
+
logFooterStart('Platform Ready');
|
|
59
|
+
logSection('Environment', env);
|
|
60
|
+
logSection('Miso Controller', controllerUrl);
|
|
61
|
+
logSection('Dataplane (DEV)', dataplaneUrl);
|
|
62
|
+
logSection('Keycloak', keycloakUrl);
|
|
63
|
+
logSection('API Documentation', `${dataplaneUrl}/api/docs`);
|
|
64
|
+
logSection('Knowledge Base', 'https://docs.aifabrix.ai');
|
|
65
|
+
logSection('Getting Started', 'https://docs.aifabrix.ai/get-started');
|
|
66
|
+
logFooterEnd();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function logMisoReadyFooter() {
|
|
70
|
+
const [controllerUrl, keycloakUrl] = await Promise.all([
|
|
71
|
+
resolvePlatformControllerUrlForDisplay(),
|
|
72
|
+
computeAppBaseUrl('keycloak')
|
|
73
|
+
]);
|
|
74
|
+
logFooterStart('Miso Ready');
|
|
75
|
+
logSection('Miso Controller', controllerUrl);
|
|
76
|
+
logSection('Keycloak', keycloakUrl);
|
|
77
|
+
logFooterEnd();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function logDataplaneReadyFooter() {
|
|
81
|
+
const dataplaneUrl = await computeAppBaseUrl('dataplane');
|
|
82
|
+
const cfg = await config.getConfig();
|
|
83
|
+
const env = cfg && cfg.environment ? cfg.environment : 'dev';
|
|
84
|
+
logFooterStart('Dataplane Ready');
|
|
85
|
+
logSection('Environment', env);
|
|
86
|
+
logSection('Dataplane (DEV)', dataplaneUrl);
|
|
87
|
+
logSection('API Documentation', `${dataplaneUrl}/api/docs`);
|
|
88
|
+
logFooterEnd();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {'platform'|'dataplane'} [scope]
|
|
93
|
+
*/
|
|
94
|
+
function logPlatformSetupDurationHint(scope = 'platform') {
|
|
95
|
+
const line =
|
|
96
|
+
scope === 'dataplane'
|
|
97
|
+
? 'Installation may take a few minutes. Authenticating and starting Dataplane — please wait.'
|
|
98
|
+
: 'Installation may take a few minutes. Keycloak, Miso Controller, and Dataplane are starting — please wait.';
|
|
99
|
+
logger.log(chalk.yellow(`⚠ ${line}`));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function logGuidedPlatformSetupHeader() {
|
|
103
|
+
logger.log('');
|
|
104
|
+
logger.log(title('AI Fabrix Platform Setup'));
|
|
105
|
+
logger.log(SEPARATOR);
|
|
106
|
+
logger.log('');
|
|
107
|
+
logPlatformSetupDurationHint('platform');
|
|
108
|
+
logger.log('');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {{ deviceCleared: number, clientCleared: number, environment: string, defaultControllerUrl: string }} forceSummary
|
|
113
|
+
* @param {string[]} cleanedApps
|
|
114
|
+
*/
|
|
115
|
+
function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
|
|
116
|
+
const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
|
|
117
|
+
if (isSetupQuietOutput()) return;
|
|
118
|
+
const check = successGlyph();
|
|
119
|
+
logger.log(chalk.bold('Clean installation files'));
|
|
120
|
+
logger.log(
|
|
121
|
+
chalk.gray(
|
|
122
|
+
` ${check} Cleared ${forceSummary.deviceCleared} device token(s) and ${forceSummary.clientCleared} client token(s)`
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
logger.log(chalk.gray(` ${check} Environment set to ${forceSummary.environment}`));
|
|
126
|
+
logger.log(
|
|
127
|
+
chalk.gray(
|
|
128
|
+
` ${check} Default controller set to ${forceSummary.defaultControllerUrl} (run aifabrix login after platform is up)`
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
for (const appName of cleanedApps || []) {
|
|
132
|
+
logger.log(chalk.gray(` ${check} Cleaned builder/${appName}`));
|
|
133
|
+
}
|
|
134
|
+
logger.log('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {Object} cfg
|
|
139
|
+
*/
|
|
140
|
+
function logStoppedServices(cfg) {
|
|
141
|
+
logger.log(title('Stopped'));
|
|
142
|
+
logger.log(' postgres');
|
|
143
|
+
logger.log(' redis');
|
|
144
|
+
if (cfg.pgadmin !== false) logger.log(' pgadmin');
|
|
145
|
+
if (cfg.redisCommander !== false) logger.log(' redis-commander');
|
|
146
|
+
if (cfg.traefik) logger.log(' traefik');
|
|
147
|
+
logger.log('');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
SEPARATOR,
|
|
152
|
+
title,
|
|
153
|
+
logSection,
|
|
154
|
+
logFooterStart,
|
|
155
|
+
logFooterEnd,
|
|
156
|
+
resolvePlatformControllerUrlForDisplay,
|
|
157
|
+
logPlatformReadyFooter,
|
|
158
|
+
logMisoReadyFooter,
|
|
159
|
+
logDataplaneReadyFooter,
|
|
160
|
+
logPlatformSetupDurationHint,
|
|
161
|
+
logGuidedPlatformSetupHeader,
|
|
162
|
+
logUpPlatformForceCleanSummary,
|
|
163
|
+
logStoppedServices
|
|
164
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Spinner helpers for guided infra/platform CLI flows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
const { formatProgress, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
|
|
10
|
+
|
|
11
|
+
function shouldUseSpinner() {
|
|
12
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @returns {import('ora').Ora|null}
|
|
18
|
+
*/
|
|
19
|
+
function startSpinner(text) {
|
|
20
|
+
if (!shouldUseSpinner()) {
|
|
21
|
+
logger.log(formatProgress(text));
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return ora({ text, spinner: 'dots' }).start();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('ora').Ora|null} spinner
|
|
29
|
+
* @param {string} text
|
|
30
|
+
*/
|
|
31
|
+
function stopSpinnerSuccess(spinner, text) {
|
|
32
|
+
if (!spinner) {
|
|
33
|
+
logger.log(formatSuccessLine(text));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
spinner.succeed(text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {import('ora').Ora|null} spinner
|
|
41
|
+
* @returns {(text: string) => void}
|
|
42
|
+
*/
|
|
43
|
+
function createSpinnerTextUpdater(spinner) {
|
|
44
|
+
return (text) => {
|
|
45
|
+
if (spinner) {
|
|
46
|
+
spinner.text = text;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
logger.log(formatProgress(text));
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
startSpinner,
|
|
55
|
+
stopSpinnerSuccess,
|
|
56
|
+
createSpinnerTextUpdater
|
|
57
|
+
};
|
package/lib/cli/infra-guided.js
CHANGED
|
@@ -10,10 +10,7 @@ const config = require('../core/config');
|
|
|
10
10
|
const devConfig = require('../utils/dev-config');
|
|
11
11
|
const pathsUtil = require('../utils/paths');
|
|
12
12
|
const { loadConfigFile } = require('../utils/config-format');
|
|
13
|
-
const { computeAppBaseUrl, resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
|
|
14
13
|
const { withMutedLogger } = require('../utils/with-muted-logger');
|
|
15
|
-
const { formatProgress, formatSuccessLine, successGlyph } = require('../utils/cli-test-layout-chalk');
|
|
16
|
-
const ora = require('ora');
|
|
17
14
|
const infra = require('../infrastructure');
|
|
18
15
|
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
19
16
|
const { getLogLevel, passesLevelFilter } = require('../commands/app-logs');
|
|
@@ -23,6 +20,26 @@ const {
|
|
|
23
20
|
runGuidedAuthStep,
|
|
24
21
|
runGuidedDataplaneWithAuthRecovery
|
|
25
22
|
} = require('./infra-guided-auth');
|
|
23
|
+
const {
|
|
24
|
+
startSpinner,
|
|
25
|
+
stopSpinnerSuccess,
|
|
26
|
+
createSpinnerTextUpdater
|
|
27
|
+
} = require('./infra-guided-spinners');
|
|
28
|
+
const {
|
|
29
|
+
SEPARATOR,
|
|
30
|
+
title,
|
|
31
|
+
logSection,
|
|
32
|
+
logFooterStart,
|
|
33
|
+
logFooterEnd,
|
|
34
|
+
resolvePlatformControllerUrlForDisplay,
|
|
35
|
+
logPlatformReadyFooter,
|
|
36
|
+
logMisoReadyFooter,
|
|
37
|
+
logDataplaneReadyFooter,
|
|
38
|
+
logPlatformSetupDurationHint,
|
|
39
|
+
logGuidedPlatformSetupHeader,
|
|
40
|
+
logUpPlatformForceCleanSummary,
|
|
41
|
+
logStoppedServices
|
|
42
|
+
} = require('./infra-guided-footers');
|
|
26
43
|
const healthCheck = require('../utils/health-check');
|
|
27
44
|
const { prepareUrlsLocalRegistryForUpPlatform } = require('../commands/up-common');
|
|
28
45
|
const { assertDevInfraUp } = require('../commands/dev-infra-gate');
|
|
@@ -30,32 +47,6 @@ const {
|
|
|
30
47
|
emitSystemBuilderAppManifestLineIfTTY
|
|
31
48
|
} = require('../utils/manifest-source-emit');
|
|
32
49
|
|
|
33
|
-
const SEPARATOR = '────────────────────────────────────────';
|
|
34
|
-
|
|
35
|
-
function title(text) {
|
|
36
|
-
return chalk.bold(text);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function shouldUseSpinner() {
|
|
40
|
-
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function startSpinner(text) {
|
|
44
|
-
if (!shouldUseSpinner()) {
|
|
45
|
-
logger.log(formatProgress(text));
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
return ora({ text, spinner: 'dots' }).start();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function stopSpinnerSuccess(spinner, text) {
|
|
52
|
-
if (!spinner) {
|
|
53
|
-
logger.log(formatSuccessLine(text));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
spinner.succeed(text);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
50
|
async function getRunningContainerNameForApp(appName) {
|
|
60
51
|
const apps = await infra.getAppStatus();
|
|
61
52
|
const hit = (apps || []).find(a => a && a.name === appName && a.container);
|
|
@@ -113,6 +104,9 @@ async function waitForAppReady(appName, opts = {}) {
|
|
|
113
104
|
if (userCfg && userCfg.traefik === true) {
|
|
114
105
|
healthRunOpts.traefikEnabled = true;
|
|
115
106
|
}
|
|
107
|
+
if (typeof opts.onPollText === 'function') {
|
|
108
|
+
healthRunOpts.onPollText = opts.onPollText;
|
|
109
|
+
}
|
|
116
110
|
await healthCheck.waitForHealthCheck(appName, timeoutSeconds, hostPort, appConfig, false, healthRunOpts);
|
|
117
111
|
}
|
|
118
112
|
|
|
@@ -124,84 +118,10 @@ function resolveInfraHost(remoteServer) {
|
|
|
124
118
|
const u = new URL(withScheme);
|
|
125
119
|
return u.hostname || 'localhost';
|
|
126
120
|
} catch {
|
|
127
|
-
// Fallback for non-URL strings.
|
|
128
121
|
return raw.replace(/^https?:\/\//i, '').split('/')[0].split(':')[0] || 'localhost';
|
|
129
122
|
}
|
|
130
123
|
}
|
|
131
124
|
|
|
132
|
-
/**
|
|
133
|
-
* Prefer stored platform controller URL, then compute from routing rules.
|
|
134
|
-
* @async
|
|
135
|
-
* @returns {Promise<string>}
|
|
136
|
-
*/
|
|
137
|
-
async function resolvePlatformControllerUrlForDisplay() {
|
|
138
|
-
const stored = await config.getPlatformControllerUrl();
|
|
139
|
-
if (stored) {
|
|
140
|
-
return stored;
|
|
141
|
-
}
|
|
142
|
-
return resolvePlatformControllerUrl();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function logSection(label, value) {
|
|
146
|
-
logger.log(title(label));
|
|
147
|
-
logger.log(` ${value}`);
|
|
148
|
-
logger.log('');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function logFooterStart(label) {
|
|
152
|
-
logger.log('');
|
|
153
|
-
logger.log(SEPARATOR);
|
|
154
|
-
logger.log(title(label));
|
|
155
|
-
logger.log(SEPARATOR);
|
|
156
|
-
logger.log('');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function logFooterEnd() {
|
|
160
|
-
logger.log(SEPARATOR);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function logPlatformReadyFooter() {
|
|
164
|
-
const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
|
|
165
|
-
resolvePlatformControllerUrlForDisplay(),
|
|
166
|
-
computeAppBaseUrl('dataplane'),
|
|
167
|
-
computeAppBaseUrl('keycloak')
|
|
168
|
-
]);
|
|
169
|
-
const cfg = await config.getConfig();
|
|
170
|
-
const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
171
|
-
|
|
172
|
-
logFooterStart('Platform Ready');
|
|
173
|
-
logSection('Environment', env);
|
|
174
|
-
logSection('Miso Controller', controllerUrl);
|
|
175
|
-
logSection('Dataplane (DEV)', dataplaneUrl);
|
|
176
|
-
logSection('Keycloak', keycloakUrl);
|
|
177
|
-
logSection('API Documentation', `${dataplaneUrl}/api/docs`);
|
|
178
|
-
logSection('Knowledge Base', 'https://docs.aifabrix.ai');
|
|
179
|
-
logSection('Getting Started', 'https://docs.aifabrix.ai/get-started');
|
|
180
|
-
logFooterEnd();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function logMisoReadyFooter() {
|
|
184
|
-
const [controllerUrl, keycloakUrl] = await Promise.all([
|
|
185
|
-
resolvePlatformControllerUrlForDisplay(),
|
|
186
|
-
computeAppBaseUrl('keycloak')
|
|
187
|
-
]);
|
|
188
|
-
logFooterStart('Miso Ready');
|
|
189
|
-
logSection('Miso Controller', controllerUrl);
|
|
190
|
-
logSection('Keycloak', keycloakUrl);
|
|
191
|
-
logFooterEnd();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function logDataplaneReadyFooter() {
|
|
195
|
-
const dataplaneUrl = await computeAppBaseUrl('dataplane');
|
|
196
|
-
const cfg = await config.getConfig();
|
|
197
|
-
const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
198
|
-
logFooterStart('Dataplane Ready');
|
|
199
|
-
logSection('Environment', env);
|
|
200
|
-
logSection('Dataplane (DEV)', dataplaneUrl);
|
|
201
|
-
logSection('API Documentation', `${dataplaneUrl}/api/docs`);
|
|
202
|
-
logFooterEnd();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
125
|
async function getInfraHostAndPorts() {
|
|
206
126
|
const developerId = await config.getDeveloperId();
|
|
207
127
|
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
@@ -263,23 +183,21 @@ async function runGuidedUpMiso(options, handleUpMiso) {
|
|
|
263
183
|
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
264
184
|
logger.log('');
|
|
265
185
|
const mcSpin = startSpinner('Starting Miso Controller...');
|
|
266
|
-
await waitForAppReady('miso-controller', {
|
|
186
|
+
await waitForAppReady('miso-controller', {
|
|
187
|
+
timeoutSeconds: 120,
|
|
188
|
+
onPollText: createSpinnerTextUpdater(mcSpin)
|
|
189
|
+
});
|
|
267
190
|
stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
|
|
268
191
|
await logMisoReadyFooter();
|
|
269
192
|
await validateAppErrorLogs(['miso-controller']);
|
|
270
193
|
}
|
|
271
194
|
|
|
272
|
-
/**
|
|
273
|
-
* Infra must be checked before auth: device login talks to the controller, which needs
|
|
274
|
-
* Postgres/Redis when the controller runs locally — a down infra looks like "network error".
|
|
275
|
-
*/
|
|
276
195
|
async function assertDevInfraForGuidedDataplane() {
|
|
277
196
|
const infraSpin = startSpinner('Checking infrastructure...');
|
|
278
197
|
try {
|
|
279
198
|
await assertDevInfraUp({ quietSuccess: true });
|
|
280
199
|
stopSpinnerSuccess(infraSpin, 'Infrastructure is up');
|
|
281
200
|
} catch (infraErr) {
|
|
282
|
-
// Match up-miso: one error block from handleCommandError only (no spinner.fail / extra hints).
|
|
283
201
|
if (infraSpin) infraSpin.stop();
|
|
284
202
|
throw infraErr;
|
|
285
203
|
}
|
|
@@ -293,17 +211,18 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
|
|
|
293
211
|
logPlatformSetupDurationHint('dataplane');
|
|
294
212
|
logger.log('');
|
|
295
213
|
await assertDevInfraForGuidedDataplane();
|
|
296
|
-
// Avoid an outer long-running spinner here: up-dataplane has multiple internal
|
|
297
|
-
// phases and may print some output. An outer spinner causes "spinner over spinner"
|
|
298
|
-
// rendering artifacts. Instead, do the same single auth step as up-platform, then
|
|
299
|
-
// show one spinner for the dataplane install/run phase.
|
|
300
214
|
await runGuidedAuthStep(handleLogin, {}, { startSpinner, stopSpinnerSuccess });
|
|
301
215
|
|
|
302
216
|
logger.log('');
|
|
303
217
|
emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
|
|
304
218
|
const dpSpin = startSpinner('Starting Dataplane...');
|
|
305
|
-
await
|
|
306
|
-
|
|
219
|
+
const controllerUrl = await resolveGuidedAuthControllerUrl({});
|
|
220
|
+
await runGuidedDataplaneWithAuthRecovery(
|
|
221
|
+
{ ...options, platformInstall: true, skipInfraCheck: true },
|
|
222
|
+
handleUpDataplane,
|
|
223
|
+
handleLogin,
|
|
224
|
+
controllerUrl,
|
|
225
|
+
{ onSpinnerText: createSpinnerTextUpdater(dpSpin) }
|
|
307
226
|
);
|
|
308
227
|
stopSpinnerSuccess(dpSpin, 'Dataplane ready');
|
|
309
228
|
await refreshDataplaneVersionAfterPlatformInstall();
|
|
@@ -311,54 +230,6 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
|
|
|
311
230
|
await logDataplaneReadyFooter();
|
|
312
231
|
}
|
|
313
232
|
|
|
314
|
-
/**
|
|
315
|
-
* Guided-mode summary after `up-platform --force` (printed under the platform header).
|
|
316
|
-
* @param {{ deviceCleared: number, clientCleared: number, environment: string, defaultControllerUrl: string }} forceSummary
|
|
317
|
-
* @param {string[]} cleanedApps
|
|
318
|
-
*/
|
|
319
|
-
function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
|
|
320
|
-
const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
|
|
321
|
-
if (isSetupQuietOutput()) return;
|
|
322
|
-
const check = successGlyph();
|
|
323
|
-
logger.log(chalk.bold('Clean installation files'));
|
|
324
|
-
logger.log(
|
|
325
|
-
chalk.gray(
|
|
326
|
-
` ${check} Cleared ${forceSummary.deviceCleared} device token(s) and ${forceSummary.clientCleared} client token(s)`
|
|
327
|
-
)
|
|
328
|
-
);
|
|
329
|
-
logger.log(chalk.gray(` ${check} Environment set to ${forceSummary.environment}`));
|
|
330
|
-
logger.log(
|
|
331
|
-
chalk.gray(
|
|
332
|
-
` ${check} Default controller set to ${forceSummary.defaultControllerUrl} (run aifabrix login after platform is up)`
|
|
333
|
-
)
|
|
334
|
-
);
|
|
335
|
-
for (const appName of cleanedApps || []) {
|
|
336
|
-
logger.log(chalk.gray(` ${check} Cleaned builder/${appName}`));
|
|
337
|
-
}
|
|
338
|
-
logger.log('');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Yellow notice before long-running platform/dataplane install steps (setup and guided up-*).
|
|
343
|
-
* @param {'platform'|'dataplane'} [scope]
|
|
344
|
-
*/
|
|
345
|
-
function logPlatformSetupDurationHint(scope = 'platform') {
|
|
346
|
-
const line =
|
|
347
|
-
scope === 'dataplane'
|
|
348
|
-
? 'Installation may take a few minutes. Authenticating and starting Dataplane — please wait.'
|
|
349
|
-
: 'Installation may take a few minutes. Keycloak, Miso Controller, and Dataplane are starting — please wait.';
|
|
350
|
-
logger.log(chalk.yellow(`⚠ ${line}`));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function logGuidedPlatformSetupHeader() {
|
|
354
|
-
logger.log('');
|
|
355
|
-
logger.log(title('AI Fabrix Platform Setup'));
|
|
356
|
-
logger.log(SEPARATOR);
|
|
357
|
-
logger.log('');
|
|
358
|
-
logPlatformSetupDurationHint('platform');
|
|
359
|
-
logger.log('');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
233
|
async function refreshDataplaneVersionAfterPlatformInstall() {
|
|
363
234
|
try {
|
|
364
235
|
const { tryRefreshDataplaneVersionAfterLogin } = require('../commands/auth-status-dataplane-version');
|
|
@@ -377,7 +248,10 @@ async function runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso) {
|
|
|
377
248
|
logger.log('');
|
|
378
249
|
emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
|
|
379
250
|
const mcSpin = startSpinner('Starting Miso Controller...');
|
|
380
|
-
await waitForAppReady('miso-controller', {
|
|
251
|
+
await waitForAppReady('miso-controller', {
|
|
252
|
+
timeoutSeconds: 120,
|
|
253
|
+
onPollText: createSpinnerTextUpdater(mcSpin)
|
|
254
|
+
});
|
|
381
255
|
stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
|
|
382
256
|
logger.log('');
|
|
383
257
|
}
|
|
@@ -386,20 +260,27 @@ async function runGuidedDataplaneInstallPhase(options, handleUpDataplane, handle
|
|
|
386
260
|
logger.log('');
|
|
387
261
|
emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
|
|
388
262
|
const dpSpin = startSpinner('Starting Dataplane...');
|
|
263
|
+
const onSpinnerText = createSpinnerTextUpdater(dpSpin);
|
|
389
264
|
const platformMode = await config.getSetupPlatformMode();
|
|
390
265
|
if (platformMode === 'full') {
|
|
391
|
-
// Full platform: three dataplane containers + per-env registrations.
|
|
392
266
|
const envKeys = ['pro', 'dev', 'tst'];
|
|
393
267
|
for (const envKey of envKeys) {
|
|
394
268
|
await runGuidedDataplaneWithAuthRecovery(
|
|
395
269
|
{ ...options, envKey, runEnvKey: envKey, allowNonDev: true },
|
|
396
270
|
handleUpDataplane,
|
|
397
271
|
handleLoginFn,
|
|
398
|
-
controllerUrl
|
|
272
|
+
controllerUrl,
|
|
273
|
+
{ onSpinnerText }
|
|
399
274
|
);
|
|
400
275
|
}
|
|
401
276
|
} else {
|
|
402
|
-
await runGuidedDataplaneWithAuthRecovery(
|
|
277
|
+
await runGuidedDataplaneWithAuthRecovery(
|
|
278
|
+
options,
|
|
279
|
+
handleUpDataplane,
|
|
280
|
+
handleLoginFn,
|
|
281
|
+
controllerUrl,
|
|
282
|
+
{ onSpinnerText }
|
|
283
|
+
);
|
|
403
284
|
}
|
|
404
285
|
stopSpinnerSuccess(dpSpin, 'Dataplane ready');
|
|
405
286
|
await refreshDataplaneVersionAfterPlatformInstall();
|
|
@@ -423,6 +304,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
|
|
|
423
304
|
const realmSpin = startSpinner('Waiting for Keycloak realm (aifabrix)...');
|
|
424
305
|
await waitForKeycloakRealmReady({ timeoutSeconds: 180 });
|
|
425
306
|
stopSpinnerSuccess(realmSpin, 'Keycloak realm ready');
|
|
307
|
+
logger.log('');
|
|
426
308
|
}
|
|
427
309
|
await runGuidedAuthStep(handleLogin, authOpts, { startSpinner, stopSpinnerSuccess });
|
|
428
310
|
const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
|
|
@@ -431,17 +313,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
|
|
|
431
313
|
await logPlatformReadyFooter();
|
|
432
314
|
}
|
|
433
315
|
|
|
434
|
-
function
|
|
435
|
-
logger.log(title('Stopped'));
|
|
436
|
-
logger.log(' postgres');
|
|
437
|
-
logger.log(' redis');
|
|
438
|
-
if (cfg.pgadmin !== false) logger.log(' pgadmin');
|
|
439
|
-
if (cfg.redisCommander !== false) logger.log(' redis-commander');
|
|
440
|
-
if (cfg.traefik) logger.log(' traefik');
|
|
441
|
-
logger.log('');
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async function runGuidedDownInfra(appName, options, infra, appLib) {
|
|
316
|
+
async function runGuidedDownInfra(appName, options, infraMod, appLib) {
|
|
445
317
|
logger.log('');
|
|
446
318
|
logger.log(title('AI Fabrix Shutdown'));
|
|
447
319
|
logger.log(SEPARATOR);
|
|
@@ -453,8 +325,8 @@ async function runGuidedDownInfra(appName, options, infra, appLib) {
|
|
|
453
325
|
await appLib.downApp(appName, { volumes: !!options.volumes });
|
|
454
326
|
return;
|
|
455
327
|
}
|
|
456
|
-
if (options.volumes) await
|
|
457
|
-
else await
|
|
328
|
+
if (options.volumes) await infraMod.stopInfraWithVolumes();
|
|
329
|
+
else await infraMod.stopInfra();
|
|
458
330
|
});
|
|
459
331
|
stopSpinnerSuccess(spin, 'Infrastructure stopped');
|
|
460
332
|
|
|
@@ -479,4 +351,3 @@ module.exports = {
|
|
|
479
351
|
runGuidedUpPlatform,
|
|
480
352
|
runGuidedDownInfra
|
|
481
353
|
};
|
|
482
|
-
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -297,7 +297,7 @@ Examples:
|
|
|
297
297
|
.option('--reload', 'In dev: mount workspace into container (Mutagen only if docker-endpoint is a remote host)')
|
|
298
298
|
.option(
|
|
299
299
|
'--proxy',
|
|
300
|
-
'Use Traefik/front-door public URL hints when infra has Traefik
|
|
300
|
+
'Use Traefik/front-door public URL hints when infra has Traefik (ignored when traefik: false in config)',
|
|
301
301
|
true
|
|
302
302
|
)
|
|
303
303
|
.option(
|
package/lib/cli/setup-dev.js
CHANGED
|
@@ -39,6 +39,7 @@ Examples:
|
|
|
39
39
|
$ aifabrix dev add --developer-id 02 --name "Jane Doe" --email jane@example.com
|
|
40
40
|
$ aifabrix dev add --developer-id 03 --name "Build admin" --email admin@example.com --groups admin,developer
|
|
41
41
|
$ aifabrix dev add --developer-id 04 --name "CI user" --email ci@example.com --groups secret-manager,developer
|
|
42
|
+
$ aifabrix dev add --developer-id 05 --name "Integrator" --email integrator@example.com --groups developer,secret-reader
|
|
42
43
|
|
|
43
44
|
Requires a configured remote Builder Server and an admin client certificate (same machine setup as dev list / dev pin). After add, run dev pin <id> once to create a PIN for aifabrix dev init.
|
|
44
45
|
`;
|
|
@@ -274,7 +275,7 @@ function setupDevListAddCommands(dev) {
|
|
|
274
275
|
.requiredOption('--developer-id <id>', 'Unique id, digits only (e.g. 02); used with dev init --developer-id')
|
|
275
276
|
.requiredOption('--name <name>', 'Display name')
|
|
276
277
|
.requiredOption('--email <email>', 'Email address')
|
|
277
|
-
.option('--groups <items>', 'Comma-separated roles: admin, secret-manager, developer, docker', 'developer')
|
|
278
|
+
.option('--groups <items>', 'Comma-separated roles: admin, secret-manager, secret-reader, developer, docker', 'developer')
|
|
278
279
|
.addHelpText('after', DEV_ADD_HELP_AFTER)
|
|
279
280
|
.action(async(options) => {
|
|
280
281
|
try {
|
|
@@ -297,7 +298,7 @@ function setupDevUpdatePinDeleteCommands(dev) {
|
|
|
297
298
|
.option('--developer-id <id>', 'Developer ID (same as dev add)')
|
|
298
299
|
.option('--name <name>', 'Display name')
|
|
299
300
|
.option('--email <email>', 'Email address')
|
|
300
|
-
.option('--groups <items>', 'Comma-separated groups (admin, secret-manager, developer, docker)')
|
|
301
|
+
.option('--groups <items>', 'Comma-separated groups (admin, secret-manager, secret-reader, developer, docker)')
|
|
301
302
|
.addHelpText('after', DEV_UPDATE_HELP_AFTER)
|
|
302
303
|
.action(async(developerId, options) => {
|
|
303
304
|
try {
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -34,7 +34,8 @@ const {
|
|
|
34
34
|
|
|
35
35
|
const {
|
|
36
36
|
computeEffectiveInfraOptionalFlags,
|
|
37
|
-
persistMissingInfraOptionalServiceFlags
|
|
37
|
+
persistMissingInfraOptionalServiceFlags,
|
|
38
|
+
resolveEffectiveTlsEnabled
|
|
38
39
|
} = require('../utils/infra-optional-service-flags');
|
|
39
40
|
|
|
40
41
|
const UP_INFRA_HELP_AFTER = `
|
|
@@ -137,7 +138,7 @@ async function runUpInfraCommand(options) {
|
|
|
137
138
|
const { passwordBundleFromCliOptions } = require('../core/admin-secrets-bundle');
|
|
138
139
|
const bundle = passwordBundleFromCliOptions(options);
|
|
139
140
|
const adminPass = options.adminPassword || options.adminPwd;
|
|
140
|
-
const tlsEnabledEffective = cfg
|
|
141
|
+
const tlsEnabledEffective = resolveEffectiveTlsEnabled(cfg, options);
|
|
141
142
|
const effective = computeEffectiveInfraOptionalFlags(cfg, options);
|
|
142
143
|
await infra.startInfra(developerId, {
|
|
143
144
|
traefik: effective.traefik,
|
|
@@ -29,6 +29,8 @@ What this command does:
|
|
|
29
29
|
2) Wipe data — drop every database and DB user, then up-infra + up-platform --force.
|
|
30
30
|
3) Clean installation files — remove user-local secrets, then up-infra + up-platform --force.
|
|
31
31
|
4) Update images — pull the latest infra and platform images, then up-infra + up-platform.
|
|
32
|
+
- With --mode fresh: first-time install when no infra is running (non-interactive; use with --admin-email and --admin-password).
|
|
33
|
+
- With --mode update-images: refresh images when infra is already running (non-interactive).
|
|
32
34
|
|
|
33
35
|
AI tool keys are read from the user-local file (~/.aifabrix/secrets.local.yaml) merged with the shared aifabrix-secrets file. If either source already provides keys, the AI tool prompt is skipped.
|
|
34
36
|
|
|
@@ -59,6 +61,7 @@ function registerSetup(program) {
|
|
|
59
61
|
.option('--pro-password-mode <mode>', 'Pro profile only: autogen or manual')
|
|
60
62
|
.option('--admin-email <email>', 'Admin email (non-interactive fresh install)')
|
|
61
63
|
.option('--admin-password <password>', 'Admin password for dev or pro manual-single (non-interactive)')
|
|
64
|
+
.option('--mode <mode>', 'Non-interactive mode: fresh (no infra) or update-images (infra running)')
|
|
62
65
|
.option('-y, --yes', 'Skip destructive confirmation prompts (re-install, wipe data, teardown)')
|
|
63
66
|
.addHelpText('after', SETUP_HELP_AFTER)
|
|
64
67
|
.action(async(options) => {
|
|
@@ -96,7 +96,7 @@ async function saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, ex
|
|
|
96
96
|
logger.log(chalk.gray(`Environment: ${envKey}`));
|
|
97
97
|
}
|
|
98
98
|
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
99
|
-
} else {
|
|
99
|
+
} else if (!opts.suppressSuccessLine && !opts.guidedInstall) {
|
|
100
100
|
logger.log(formatSuccessLine('Authentication successful'));
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -116,8 +116,10 @@ async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, e
|
|
|
116
116
|
const spinner = useSpinner
|
|
117
117
|
? ora({ text: 'Waiting for approval', spinner: 'dots' }).start()
|
|
118
118
|
: null;
|
|
119
|
-
if (!useSpinner) {
|
|
119
|
+
if (!useSpinner && !opts.onProgressText) {
|
|
120
120
|
logger.log('Waiting for approval...');
|
|
121
|
+
} else if (opts.onProgressText) {
|
|
122
|
+
opts.onProgressText('Waiting for approval...');
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
let pollCount = 0;
|
|
@@ -125,6 +127,8 @@ async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, e
|
|
|
125
127
|
pollCount++;
|
|
126
128
|
if (spinner) {
|
|
127
129
|
spinner.text = `Waiting for approval (attempt ${pollCount})...`;
|
|
130
|
+
} else if (opts.onProgressText) {
|
|
131
|
+
opts.onProgressText(`Waiting for approval (attempt ${pollCount})...`);
|
|
128
132
|
}
|
|
129
133
|
};
|
|
130
134
|
|