ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: ops-deploy-01 <[email protected]>
131 lines
5.0 KiB
TypeScript
131 lines
5.0 KiB
TypeScript
/**
|
|
* Thin wrapper over the unified first-run stages.
|
|
*
|
|
* `mosaic gateway install` is kept as a standalone entry point for users who
|
|
* already went through `mosaic wizard` and only need to (re)configure the
|
|
* gateway daemon. It builds a minimal `WizardState`, invokes
|
|
* `gatewayConfigStage` and `gatewayBootstrapStage` directly, and returns.
|
|
*
|
|
* The heavy lifting — prompts, env writes, daemon lifecycle, bootstrap POST —
|
|
* lives in `packages/mosaic/src/stages/gateway-config.ts` and
|
|
* `packages/mosaic/src/stages/gateway-bootstrap.ts` so that the same code
|
|
* path runs under both the unified wizard and this standalone command.
|
|
*/
|
|
|
|
import { homedir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { ClackPrompter } from '../../prompter/clack-prompter.js';
|
|
import type { VerifyResult } from './verify.js';
|
|
import type { WizardState } from '../../types.js';
|
|
|
|
interface InstallOpts {
|
|
host: string;
|
|
port: number;
|
|
skipInstall?: boolean;
|
|
}
|
|
|
|
function isHeadlessRun(): boolean {
|
|
return process.env['MOSAIC_ASSUME_YES'] === '1' || !process.stdin.isTTY;
|
|
}
|
|
|
|
export async function runInstall(opts: InstallOpts): Promise<void> {
|
|
const mosaicHome = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic');
|
|
|
|
const prompter = new ClackPrompter();
|
|
|
|
const state: WizardState = {
|
|
mosaicHome,
|
|
sourceDir: mosaicHome,
|
|
mode: 'quick',
|
|
installAction: 'fresh',
|
|
soul: {},
|
|
user: {},
|
|
tools: {},
|
|
runtimes: { detected: [], mcpConfigured: false },
|
|
selectedSkills: [],
|
|
};
|
|
|
|
const { gatewayConfigStage } = await import('../../stages/gateway-config.js');
|
|
const { gatewayBootstrapStage } = await import('../../stages/gateway-bootstrap.js');
|
|
|
|
// Preserve the legacy "explicit --port wins over saved config" semantic:
|
|
// commander defaults the port to 14242, so any other value is treated as
|
|
// an explicit user override that the config stage should honor even on
|
|
// resume.
|
|
const portOverride = opts.port !== 14242 ? opts.port : undefined;
|
|
|
|
const headless = isHeadlessRun();
|
|
|
|
try {
|
|
const configResult = await gatewayConfigStage(prompter, state, {
|
|
host: opts.host,
|
|
defaultPort: opts.port,
|
|
portOverride,
|
|
skipInstall: opts.skipInstall,
|
|
});
|
|
|
|
if (!configResult.ready || !configResult.host || configResult.port === undefined) {
|
|
// In headless/scripted installs, a non-ready config stage is a fatal
|
|
// error — we must not report "complete" when the gateway was never
|
|
// configured. Exit non-zero so CI notices.
|
|
if (headless) {
|
|
prompter.warn('Gateway configuration failed in headless mode — aborting.');
|
|
process.exit(1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
|
host: configResult.host,
|
|
port: configResult.port,
|
|
});
|
|
|
|
if (!bootstrapResult.completed && headless) {
|
|
prompter.warn('Admin bootstrap failed in headless mode — aborting.');
|
|
process.exit(1);
|
|
}
|
|
|
|
prompter.log('─── Installation Complete ───');
|
|
prompter.log(` Endpoint: http://${configResult.host}:${configResult.port.toString()}`);
|
|
prompter.log(` Logs: mosaic gateway logs`);
|
|
prompter.log(` Status: mosaic gateway status`);
|
|
|
|
// Post-install verification (CU-07-03). Health/token/bootstrap failures
|
|
// stay non-fatal (courtesy checks), but a FAILED database schema check is
|
|
// fatal (#1392): an install that reports success over an empty/partial
|
|
// database is the exact T63 failure this command must never reproduce.
|
|
let verifyResult: VerifyResult | undefined;
|
|
let verificationThrew = false;
|
|
try {
|
|
const { runPostInstallVerification } = await import('./verify.js');
|
|
verifyResult = await runPostInstallVerification(configResult.host, configResult.port);
|
|
} catch (err) {
|
|
// Health/token/bootstrap courtesy failures are non-fatal, but a THROWN
|
|
// schema verification must not let install report success either (N2,
|
|
// rev-code-02 review 285): mark it and treat as fatal below.
|
|
verificationThrew = true;
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
prompter.warn(`Post-install verification errored: ${msg}`);
|
|
}
|
|
if (verifyResult && verifyResult.schemaMigrated === false) {
|
|
prompter.warn(
|
|
'Gateway install ABORTED: database schema verification failed (remediation above).',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
if (verificationThrew) {
|
|
prompter.warn(
|
|
'Gateway install ABORTED: post-install verification errored (see above); refusing to report success on an unverified database.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
} catch (err) {
|
|
// Stages normally return structured results for expected failures.
|
|
// Anything that reaches here is an unexpected runtime error — render a
|
|
// concise warning AND re-throw so the command exits non-zero. Silent
|
|
// swallowing would let scripted installs report success on failure.
|
|
prompter.warn(`Gateway install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
throw err;
|
|
}
|
|
}
|