@aifabrix/builder 2.47.4 → 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/jest.isolated-projects.js +7 -0
- package/jest.projects.js +16 -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 +4 -3
- package/lib/app/deploy-config.js +6 -4
- package/lib/app/register.js +21 -19
- package/lib/app/rotate-secret.js +39 -16
- 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/build/index.js +12 -49
- package/lib/build/integration-docker-staging.js +35 -0
- package/lib/build/standard-docker-build.js +113 -0
- 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 +5 -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/dev-init.js +18 -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-prompts-platform-mode.js +11 -1
- package/lib/commands/setup.js +60 -7
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +104 -6
- 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 +59 -9
- 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/parameters/infra-kv-discovery.js +4 -3
- package/lib/utils/aifabrix-runtime-config-dir.js +2 -1
- 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 +18 -6
- package/lib/utils/compose-generator.js +34 -15
- package/lib/utils/compose-traefik-ingress-base.js +13 -14
- package/lib/utils/config-paths.js +8 -3
- package/lib/utils/dev-ca-docker-augment.js +54 -0
- package/lib/utils/dev-user-groups.js +1 -0
- package/lib/utils/docker-daemon-tls-ca.js +363 -0
- package/lib/utils/docker-reload-mount.js +12 -1
- package/lib/utils/ensure-dev-certs-for-remote-docker.js +5 -0
- 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/promote-dev-docker-image.js +170 -0
- package/lib/utils/refresh-remote-settings.js +67 -0
- package/lib/utils/remote-docker-env.js +22 -7
- package/lib/utils/remote-host-for-front-door.js +43 -0
- package/lib/utils/run-cli-flags.js +37 -1
- package/lib/utils/secrets-helpers.js +9 -14
- package/lib/utils/secrets-kv-refs.js +47 -3
- package/lib/utils/secrets-missing-error.js +1 -9
- package/lib/utils/token-manager-client-credentials.js +11 -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 +28 -1
- 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
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);
|
|
@@ -124,84 +115,10 @@ function resolveInfraHost(remoteServer) {
|
|
|
124
115
|
const u = new URL(withScheme);
|
|
125
116
|
return u.hostname || 'localhost';
|
|
126
117
|
} catch {
|
|
127
|
-
// Fallback for non-URL strings.
|
|
128
118
|
return raw.replace(/^https?:\/\//i, '').split('/')[0].split(':')[0] || 'localhost';
|
|
129
119
|
}
|
|
130
120
|
}
|
|
131
121
|
|
|
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
122
|
async function getInfraHostAndPorts() {
|
|
206
123
|
const developerId = await config.getDeveloperId();
|
|
207
124
|
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
@@ -269,17 +186,12 @@ async function runGuidedUpMiso(options, handleUpMiso) {
|
|
|
269
186
|
await validateAppErrorLogs(['miso-controller']);
|
|
270
187
|
}
|
|
271
188
|
|
|
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
189
|
async function assertDevInfraForGuidedDataplane() {
|
|
277
190
|
const infraSpin = startSpinner('Checking infrastructure...');
|
|
278
191
|
try {
|
|
279
192
|
await assertDevInfraUp({ quietSuccess: true });
|
|
280
193
|
stopSpinnerSuccess(infraSpin, 'Infrastructure is up');
|
|
281
194
|
} catch (infraErr) {
|
|
282
|
-
// Match up-miso: one error block from handleCommandError only (no spinner.fail / extra hints).
|
|
283
195
|
if (infraSpin) infraSpin.stop();
|
|
284
196
|
throw infraErr;
|
|
285
197
|
}
|
|
@@ -293,17 +205,18 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
|
|
|
293
205
|
logPlatformSetupDurationHint('dataplane');
|
|
294
206
|
logger.log('');
|
|
295
207
|
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
208
|
await runGuidedAuthStep(handleLogin, {}, { startSpinner, stopSpinnerSuccess });
|
|
301
209
|
|
|
302
210
|
logger.log('');
|
|
303
211
|
emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
|
|
304
212
|
const dpSpin = startSpinner('Starting Dataplane...');
|
|
305
|
-
await
|
|
306
|
-
|
|
213
|
+
const controllerUrl = await resolveGuidedAuthControllerUrl({});
|
|
214
|
+
await runGuidedDataplaneWithAuthRecovery(
|
|
215
|
+
{ ...options, platformInstall: true, skipInfraCheck: true },
|
|
216
|
+
handleUpDataplane,
|
|
217
|
+
handleLogin,
|
|
218
|
+
controllerUrl,
|
|
219
|
+
{ onSpinnerText: createSpinnerTextUpdater(dpSpin) }
|
|
307
220
|
);
|
|
308
221
|
stopSpinnerSuccess(dpSpin, 'Dataplane ready');
|
|
309
222
|
await refreshDataplaneVersionAfterPlatformInstall();
|
|
@@ -311,54 +224,6 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
|
|
|
311
224
|
await logDataplaneReadyFooter();
|
|
312
225
|
}
|
|
313
226
|
|
|
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
227
|
async function refreshDataplaneVersionAfterPlatformInstall() {
|
|
363
228
|
try {
|
|
364
229
|
const { tryRefreshDataplaneVersionAfterLogin } = require('../commands/auth-status-dataplane-version');
|
|
@@ -386,20 +251,27 @@ async function runGuidedDataplaneInstallPhase(options, handleUpDataplane, handle
|
|
|
386
251
|
logger.log('');
|
|
387
252
|
emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
|
|
388
253
|
const dpSpin = startSpinner('Starting Dataplane...');
|
|
254
|
+
const onSpinnerText = createSpinnerTextUpdater(dpSpin);
|
|
389
255
|
const platformMode = await config.getSetupPlatformMode();
|
|
390
256
|
if (platformMode === 'full') {
|
|
391
|
-
// Full platform: three dataplane containers + per-env registrations.
|
|
392
257
|
const envKeys = ['pro', 'dev', 'tst'];
|
|
393
258
|
for (const envKey of envKeys) {
|
|
394
259
|
await runGuidedDataplaneWithAuthRecovery(
|
|
395
260
|
{ ...options, envKey, runEnvKey: envKey, allowNonDev: true },
|
|
396
261
|
handleUpDataplane,
|
|
397
262
|
handleLoginFn,
|
|
398
|
-
controllerUrl
|
|
263
|
+
controllerUrl,
|
|
264
|
+
{ onSpinnerText }
|
|
399
265
|
);
|
|
400
266
|
}
|
|
401
267
|
} else {
|
|
402
|
-
await runGuidedDataplaneWithAuthRecovery(
|
|
268
|
+
await runGuidedDataplaneWithAuthRecovery(
|
|
269
|
+
options,
|
|
270
|
+
handleUpDataplane,
|
|
271
|
+
handleLoginFn,
|
|
272
|
+
controllerUrl,
|
|
273
|
+
{ onSpinnerText }
|
|
274
|
+
);
|
|
403
275
|
}
|
|
404
276
|
stopSpinnerSuccess(dpSpin, 'Dataplane ready');
|
|
405
277
|
await refreshDataplaneVersionAfterPlatformInstall();
|
|
@@ -423,6 +295,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
|
|
|
423
295
|
const realmSpin = startSpinner('Waiting for Keycloak realm (aifabrix)...');
|
|
424
296
|
await waitForKeycloakRealmReady({ timeoutSeconds: 180 });
|
|
425
297
|
stopSpinnerSuccess(realmSpin, 'Keycloak realm ready');
|
|
298
|
+
logger.log('');
|
|
426
299
|
}
|
|
427
300
|
await runGuidedAuthStep(handleLogin, authOpts, { startSpinner, stopSpinnerSuccess });
|
|
428
301
|
const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
|
|
@@ -431,17 +304,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
|
|
|
431
304
|
await logPlatformReadyFooter();
|
|
432
305
|
}
|
|
433
306
|
|
|
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) {
|
|
307
|
+
async function runGuidedDownInfra(appName, options, infraMod, appLib) {
|
|
445
308
|
logger.log('');
|
|
446
309
|
logger.log(title('AI Fabrix Shutdown'));
|
|
447
310
|
logger.log(SEPARATOR);
|
|
@@ -453,8 +316,8 @@ async function runGuidedDownInfra(appName, options, infra, appLib) {
|
|
|
453
316
|
await appLib.downApp(appName, { volumes: !!options.volumes });
|
|
454
317
|
return;
|
|
455
318
|
}
|
|
456
|
-
if (options.volumes) await
|
|
457
|
-
else await
|
|
319
|
+
if (options.volumes) await infraMod.stopInfraWithVolumes();
|
|
320
|
+
else await infraMod.stopInfra();
|
|
458
321
|
});
|
|
459
322
|
stopSpinnerSuccess(spin, 'Infrastructure stopped');
|
|
460
323
|
|
|
@@ -479,4 +342,3 @@ module.exports = {
|
|
|
479
342
|
runGuidedUpPlatform,
|
|
480
343
|
runGuidedDownInfra
|
|
481
344
|
};
|
|
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(
|
|
@@ -326,6 +326,10 @@ function setupBuildRunLogsDownCommands(program) {
|
|
|
326
326
|
'--base',
|
|
327
327
|
'Build/run the manifest base image (no -devN suffix). Default local build uses a developer-scoped repository when developer-id > 0.'
|
|
328
328
|
)
|
|
329
|
+
.option(
|
|
330
|
+
'--from-dev',
|
|
331
|
+
'With --base: tag the existing developer-scoped image as the base image instead of rebuilding when possible'
|
|
332
|
+
)
|
|
329
333
|
.action(async(appName, options) => {
|
|
330
334
|
try {
|
|
331
335
|
const imageTag = await app.buildApp(appName, options);
|
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) => {
|
package/lib/commands/dev-init.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
getCertDir,
|
|
17
17
|
readClientCertPem,
|
|
18
18
|
readClientKeyPem,
|
|
19
|
+
readServerCaPem,
|
|
19
20
|
getCertValidNotAfter,
|
|
20
21
|
normalizePemNewlines,
|
|
21
22
|
mergeCaPemBlocks
|
|
@@ -40,6 +41,7 @@ const {
|
|
|
40
41
|
formatEnsureServerTrustedFailure
|
|
41
42
|
} = require('../utils/dev-init-health-messages');
|
|
42
43
|
const { getBadRequestHint, logCertTroubleshootingHint } = require('../utils/dev-init-cert-hints');
|
|
44
|
+
const { augmentAndSaveCaPemForRemoteDocker } = require('../utils/dev-ca-docker-augment');
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Install dev CA into the OS trust store. On Linux without sudo, log and return false so the caller can use in-process PEM.
|
|
@@ -371,6 +373,11 @@ async function runDevInit(options) {
|
|
|
371
373
|
await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
|
|
372
374
|
|
|
373
375
|
await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
|
|
376
|
+
await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
|
|
377
|
+
settings: issueResponse.settings,
|
|
378
|
+
remoteServerUrl: baseUrl,
|
|
379
|
+
warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
|
|
380
|
+
});
|
|
374
381
|
await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
|
|
375
382
|
const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId);
|
|
376
383
|
logOnboardingFinished(sshInfo);
|
|
@@ -413,6 +420,11 @@ async function runCertificateRefresh(auth) {
|
|
|
413
420
|
const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
|
|
414
421
|
await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
|
|
415
422
|
await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
|
|
423
|
+
await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
|
|
424
|
+
settings: issueResponse.settings,
|
|
425
|
+
remoteServerUrl: auth.serverUrl,
|
|
426
|
+
warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
|
|
427
|
+
});
|
|
416
428
|
logger.log(formatSuccessLine('Certificate refreshed and config updated from server.\n'));
|
|
417
429
|
}
|
|
418
430
|
|
|
@@ -433,6 +445,12 @@ async function fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, de
|
|
|
433
445
|
auth.serverCaPem || undefined
|
|
434
446
|
);
|
|
435
447
|
await config.mergeRemoteSettings(settings);
|
|
448
|
+
const existingCa = readServerCaPem(getCertDir(getConfigDirForPaths(), devId));
|
|
449
|
+
await augmentAndSaveCaPemForRemoteDocker(getConfigDirForPaths(), devId, existingCa, {
|
|
450
|
+
settings,
|
|
451
|
+
remoteServerUrl: auth.serverUrl,
|
|
452
|
+
warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
|
|
453
|
+
});
|
|
436
454
|
logger.log(formatSuccessLine('Config updated from server.\n'));
|
|
437
455
|
await displayDevConfig(devId);
|
|
438
456
|
}
|
|
@@ -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
|
|
package/lib/commands/login.js
CHANGED
|
@@ -163,7 +163,10 @@ async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
|
|
|
163
163
|
return await handleDeviceCodeLogin(controllerUrl, environment, options.online, options.scope, {
|
|
164
164
|
compact: Boolean(options.compact),
|
|
165
165
|
exitOnFailure: options.exitOnFailure !== false,
|
|
166
|
-
tokenControllerUrl: options.tokenControllerUrl
|
|
166
|
+
tokenControllerUrl: options.tokenControllerUrl,
|
|
167
|
+
guidedInstall: options.guidedInstall === true,
|
|
168
|
+
onProgressText: options.onProgressText,
|
|
169
|
+
suppressSuccessLine: options.suppressSuccessLine === true
|
|
167
170
|
});
|
|
168
171
|
}
|
|
169
172
|
|
|
@@ -42,11 +42,19 @@ const postgresBootstrap = require('../utils/postgres-platform-bootstrap');
|
|
|
42
42
|
const appLib = require('../app');
|
|
43
43
|
const setupPrompts = require('./setup-prompts');
|
|
44
44
|
const { ensureSetupPlatformAuth, syncPlatformControllerUrlsInConfig } = require('./setup-platform-auth');
|
|
45
|
+
const {
|
|
46
|
+
refreshRemoteSettingsFromBuilderIfPossible
|
|
47
|
+
} = require('../utils/refresh-remote-settings');
|
|
45
48
|
const { handleLogin } = require('./login');
|
|
46
49
|
const infraGuided = require('../cli/infra-guided');
|
|
50
|
+
const {
|
|
51
|
+
applyPlatformTopologyFlagsToConfig,
|
|
52
|
+
platformTopologyNeedsReconcile
|
|
53
|
+
} = require('../core/setup-platform-mode');
|
|
47
54
|
const {
|
|
48
55
|
computeEffectiveInfraOptionalFlags,
|
|
49
|
-
persistMissingInfraOptionalServiceFlags
|
|
56
|
+
persistMissingInfraOptionalServiceFlags,
|
|
57
|
+
topologyFlagsForConfig
|
|
50
58
|
} = require('../utils/infra-optional-service-flags');
|
|
51
59
|
|
|
52
60
|
/** Builder app keys touched by `up-platform --force`. */
|
|
@@ -197,11 +205,19 @@ async function maybePromptForExistingBuilderDir({ force, skipBuilderConflictProm
|
|
|
197
205
|
*/
|
|
198
206
|
async function startInfraFromConfig(overrides = {}) {
|
|
199
207
|
await config.ensureSecretsEncryptionKey();
|
|
200
|
-
|
|
208
|
+
await refreshRemoteSettingsFromBuilderIfPossible({ quiet: true });
|
|
209
|
+
let cfg = await config.getConfig();
|
|
210
|
+
if (platformTopologyNeedsReconcile(cfg)) {
|
|
211
|
+
applyPlatformTopologyFlagsToConfig(cfg, cfg.setupPlatformMode);
|
|
212
|
+
await config.saveConfig(cfg);
|
|
213
|
+
cfg = await config.getConfig();
|
|
214
|
+
}
|
|
201
215
|
const emailOverride = String(overrides.adminEmail || '').trim();
|
|
202
216
|
const emailFromConfig = typeof cfg.adminEmail === 'string' ? cfg.adminEmail.trim() : '';
|
|
203
217
|
const adminEmailMerged = emailOverride || emailFromConfig || undefined;
|
|
204
218
|
const effective = computeEffectiveInfraOptionalFlags(cfg, {});
|
|
219
|
+
const topology = topologyFlagsForConfig(cfg);
|
|
220
|
+
const tlsEnabled = topology ? topology.tlsEnabled : cfg.tlsEnabled === true;
|
|
205
221
|
const legacyPwd =
|
|
206
222
|
overrides.passwordBundle && overrides.passwordBundle.mode === 'single'
|
|
207
223
|
? overrides.passwordBundle.password
|
|
@@ -215,7 +231,7 @@ async function startInfraFromConfig(overrides = {}) {
|
|
|
215
231
|
adminPwd: legacyPwd,
|
|
216
232
|
adminEmail: adminEmailMerged,
|
|
217
233
|
passwordBundle: overrides.passwordBundle,
|
|
218
|
-
tlsEnabled
|
|
234
|
+
tlsEnabled
|
|
219
235
|
});
|
|
220
236
|
});
|
|
221
237
|
await persistMissingInfraOptionalServiceFlags(cfg, effective);
|
|
@@ -14,6 +14,14 @@ const { resolvePlatformControllerUrl } = require('../utils/platform-controller-u
|
|
|
14
14
|
const { isPlatformAuthValidForController } = require('../utils/controller-url');
|
|
15
15
|
const { infoLine } = require('../utils/cli-test-layout-chalk');
|
|
16
16
|
const logger = require('../utils/logger');
|
|
17
|
+
const chalk = require('chalk');
|
|
18
|
+
const {
|
|
19
|
+
refreshRemoteSettingsFromBuilderIfPossible
|
|
20
|
+
} = require('../utils/refresh-remote-settings');
|
|
21
|
+
const {
|
|
22
|
+
applyPlatformTopologyFlagsToConfig,
|
|
23
|
+
platformTopologyNeedsReconcile
|
|
24
|
+
} = require('../core/setup-platform-mode');
|
|
17
25
|
|
|
18
26
|
/**
|
|
19
27
|
* Recompute `controller` and `platform-controller` from traefik / setupPlatformMode / front-door config.
|
|
@@ -24,6 +32,23 @@ const logger = require('../utils/logger');
|
|
|
24
32
|
* @returns {Promise<string>} Normalized platform controller URL
|
|
25
33
|
*/
|
|
26
34
|
async function syncPlatformControllerUrlsInConfig() {
|
|
35
|
+
const refresh = await refreshRemoteSettingsFromBuilderIfPossible({ quiet: true });
|
|
36
|
+
const cfg = await config.getConfig();
|
|
37
|
+
if (platformTopologyNeedsReconcile(cfg)) {
|
|
38
|
+
applyPlatformTopologyFlagsToConfig(cfg, cfg.setupPlatformMode);
|
|
39
|
+
await config.saveConfig(cfg);
|
|
40
|
+
}
|
|
41
|
+
const remoteServer = await config.getRemoteServer();
|
|
42
|
+
const rootDomain = await config.getDeveloperRootDomain();
|
|
43
|
+
if (remoteServer && !rootDomain && !refresh.refreshed) {
|
|
44
|
+
logger.warn(
|
|
45
|
+
chalk.yellow(
|
|
46
|
+
'Platform URLs may be wrong: developer-root-domain is missing. ' +
|
|
47
|
+
'Run "aifabrix dev refresh" (or "aifabrix dev init") to sync from the Builder Server.'
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
const platformControllerUrl = await resolvePlatformControllerUrl();
|
|
28
53
|
await config.setPlatformControllerUrl(platformControllerUrl);
|
|
29
54
|
await config.setControllerUrl(platformControllerUrl);
|
|
@@ -225,6 +225,15 @@ async function promptAdminCredentialsWithProfile(options = {}) {
|
|
|
225
225
|
await config.setSetupInstallationProfile(profile);
|
|
226
226
|
|
|
227
227
|
const adminEmail = await promptAdminEmail(options);
|
|
228
|
+
const cliPassword = String(options.adminPassword || '').trim();
|
|
229
|
+
if (cliPassword) {
|
|
230
|
+
validatePassword(cliPassword);
|
|
231
|
+
const passwordBundle = isProInstallationProfile(profile)
|
|
232
|
+
? singlePasswordBundle(cliPassword)
|
|
233
|
+
: singlePasswordBundle(cliPassword);
|
|
234
|
+
return { adminEmail, passwordBundle, profile };
|
|
235
|
+
}
|
|
236
|
+
|
|
228
237
|
const passwordBundle = isProInstallationProfile(profile)
|
|
229
238
|
? await promptProPasswordBundle(options)
|
|
230
239
|
: await promptDevPasswordBundle();
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const inquirer = require('inquirer');
|
|
12
|
+
const config = require('../core/config');
|
|
12
13
|
const {
|
|
13
14
|
normalizeSetupPlatformMode,
|
|
14
15
|
SETUP_PLATFORM_MODE,
|
|
15
16
|
SETUP_PLATFORM_MODE_CLI_VALUES,
|
|
16
17
|
getSetupPlatformModePromptChoices
|
|
17
18
|
} = require('../core/setup-platform-mode');
|
|
19
|
+
const { isRemoteServerConfigured } = require('../utils/applications-config-defaults');
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Resolve platform mode from CLI options, if provided.
|
|
@@ -51,12 +53,20 @@ async function promptSetupPlatformMode(options = {}) {
|
|
|
51
53
|
const fromCli = resolvePlatformModeFromOptions(options);
|
|
52
54
|
if (fromCli) return fromCli;
|
|
53
55
|
|
|
56
|
+
const remoteServer = await config.getRemoteServer();
|
|
57
|
+
const choices = getSetupPlatformModePromptChoices({
|
|
58
|
+
remoteServerConfigured: isRemoteServerConfigured(remoteServer)
|
|
59
|
+
});
|
|
60
|
+
if (choices.length === 1) {
|
|
61
|
+
return SETUP_PLATFORM_MODE.SINGLE;
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
const { mode } = await inquirer.prompt([
|
|
55
65
|
{
|
|
56
66
|
type: 'list',
|
|
57
67
|
name: 'mode',
|
|
58
68
|
message: 'Platform mode:',
|
|
59
|
-
choices
|
|
69
|
+
choices,
|
|
60
70
|
default: SETUP_PLATFORM_MODE.SINGLE
|
|
61
71
|
}
|
|
62
72
|
]);
|