118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
import { readMeta } from './daemon.js';
|
|
|
|
// ANSI colour helpers (gracefully degrade when not a TTY)
|
|
const isTTY = Boolean(process.stdout.isTTY);
|
|
const G = isTTY ? '\x1b[0;32m' : '';
|
|
const R = isTTY ? '\x1b[0;31m' : '';
|
|
const BOLD = isTTY ? '\x1b[1m' : '';
|
|
const RESET = isTTY ? '\x1b[0m' : '';
|
|
|
|
function ok(label: string): void {
|
|
process.stdout.write(` ${G}✔${RESET} ${label.padEnd(36)}${G}[ok]${RESET}\n`);
|
|
}
|
|
|
|
function fail(label: string, hint: string): void {
|
|
process.stdout.write(` ${R}✖${RESET} ${label.padEnd(36)}${R}[FAIL]${RESET}\n`);
|
|
process.stdout.write(` ${R}↳ ${hint}${RESET}\n`);
|
|
}
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function fetchWithRetry(
|
|
url: string,
|
|
opts: RequestInit = {},
|
|
retries = 3,
|
|
delayMs = 1000,
|
|
): Promise<Response | null> {
|
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
try {
|
|
const res = await fetch(url, opts);
|
|
// Retry on non-OK responses too — the gateway may still be starting up
|
|
// (e.g. 503 before the app bootstrap completes).
|
|
if (res.ok) return res;
|
|
} catch {
|
|
// Network-level error — not ready yet, will retry
|
|
}
|
|
if (attempt < retries - 1) await sleep(delayMs);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export interface VerifyResult {
|
|
gatewayHealthy: boolean;
|
|
adminTokenOnFile: boolean;
|
|
bootstrapReachable: boolean;
|
|
allPassed: boolean;
|
|
}
|
|
|
|
/**
|
|
* Run post-install verification checks.
|
|
*
|
|
* @param host - Gateway hostname (e.g. "localhost")
|
|
* @param port - Gateway port (e.g. 14242)
|
|
* @returns VerifyResult — callers can inspect individual flags
|
|
*/
|
|
export async function runPostInstallVerification(
|
|
host: string,
|
|
port: number,
|
|
): Promise<VerifyResult> {
|
|
const baseUrl = `http://${host}:${port.toString()}`;
|
|
|
|
console.log(`\n${BOLD}Mosaic installation verified:${RESET}`);
|
|
|
|
// ─── Check 1: Gateway /health ─────────────────────────────────────────────
|
|
const healthRes = await fetchWithRetry(`${baseUrl}/health`);
|
|
const gatewayHealthy = healthRes !== null && healthRes.ok;
|
|
if (gatewayHealthy) {
|
|
ok('gateway healthy');
|
|
} else {
|
|
fail('gateway healthy', 'Run: mosaic gateway status / mosaic gateway logs');
|
|
}
|
|
|
|
// ─── Check 2: Admin token on file ─────────────────────────────────────────
|
|
const meta = readMeta();
|
|
const adminTokenOnFile = Boolean(meta?.adminToken && meta.adminToken.length > 0);
|
|
if (adminTokenOnFile) {
|
|
ok('admin token on file');
|
|
} else {
|
|
fail('admin token on file', 'Run: mosaic gateway config recover-token');
|
|
}
|
|
|
|
// ─── Check 3: Bootstrap endpoint reachable ────────────────────────────────
|
|
const bootstrapRes = await fetchWithRetry(`${baseUrl}/api/bootstrap/status`);
|
|
const bootstrapReachable = bootstrapRes !== null && bootstrapRes.ok;
|
|
if (bootstrapReachable) {
|
|
ok('bootstrap endpoint reach');
|
|
} else {
|
|
fail('bootstrap endpoint reach', 'Run: mosaic gateway status / mosaic gateway logs');
|
|
}
|
|
|
|
const allPassed = gatewayHealthy && adminTokenOnFile && bootstrapReachable;
|
|
|
|
if (!allPassed) {
|
|
console.log(
|
|
`\n${R}One or more checks failed.${RESET} Recovery commands listed above.\n` +
|
|
`Use ${BOLD}mosaic gateway status${RESET} and ${BOLD}mosaic gateway config recover-token${RESET} to investigate.\n`,
|
|
);
|
|
}
|
|
|
|
return { gatewayHealthy, adminTokenOnFile, bootstrapReachable, allPassed };
|
|
}
|
|
|
|
/**
|
|
* Standalone entry point for `mosaic gateway verify`.
|
|
* Reads host/port from meta.json if not provided.
|
|
*/
|
|
export async function runVerify(opts: { host?: string; port?: number }): Promise<void> {
|
|
const meta = readMeta();
|
|
const host = opts.host ?? meta?.host ?? 'localhost';
|
|
const port = opts.port ?? meta?.port ?? 14242;
|
|
|
|
const result = await runPostInstallVerification(host, port);
|
|
if (!result.allPassed) {
|
|
process.exit(1);
|
|
}
|
|
}
|