Build review P2: the supervisor ignores a lost reply pipe and keeps supervising. Security P2: a stop signal during start is held until the start settles, and the CLI's start timeout signals the supervisor's process group. Security P1 (part): console start --dev-local-owner refuses inside an agent run. Tests cover review, confirm and reverse on the org autonomy route; TOOLS.md and the webui README are corrected.
40 lines
2.3 KiB
JavaScript
40 lines
2.3 KiB
JavaScript
// D1 (#1873): the supervisor of a detached dev-local-owner Console. `mosaic console start
|
|
// --dev-local-owner --detach` starts this process in its own session (org-console.mjs
|
|
// detachedSupervisor). It starts the Console exactly as a foreground start does, with --supervised,
|
|
// and relaunches it when the owner restarts it (F3). Its one stdout line is the start's reply:
|
|
// { receipt, warnings } or { error, exitCode, warnings }. Later warnings go to stderr, the log.
|
|
import { consoleStart } from './org-console.mjs';
|
|
import { CliError } from './errors.mjs';
|
|
|
|
const { configDir, dataRoot, instance, port, bind, allowHosts } = JSON.parse(process.argv[2]);
|
|
const warnings = [];
|
|
let started = false;
|
|
const warn = s => { if (started) process.stderr.write(s); else warnings.push(s); };
|
|
// The starting CLI can be gone before the reply (Ctrl-C, a wrapper's timeout). The Console is
|
|
// started and its state names it, so a reply nobody reads is dropped and supervision goes on.
|
|
process.stdout.on('error', () => {});
|
|
const reply = value => new Promise(resolve => process.stdout.write(`${JSON.stringify(value)}\n`, () => resolve()));
|
|
// A stop signal during the start (the CLI's start timeout signals this process group) is kept and
|
|
// acted on once the start settles, so a failed start still releases the start lock.
|
|
let stopAsked = false;
|
|
const early = () => { stopAsked = true; };
|
|
for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, early);
|
|
let receipt;
|
|
try {
|
|
receipt = await consoleStart({ configDir, dataRoot, instance, env: process.env }, { devLocalOwner: true, port, bind, allowHosts, warn });
|
|
} catch (e) {
|
|
const exitCode = e instanceof CliError ? e.exitCode : 1;
|
|
if (!(e instanceof CliError)) process.stderr.write(`${e?.stack ?? e}\n`);
|
|
await reply({ ...(e instanceof CliError ? { error: e.message } : {}), exitCode, warnings });
|
|
process.exit(exitCode);
|
|
}
|
|
started = true;
|
|
// A stop signal ends supervision cleanly, as in the foreground (cli.mjs consoleCommand).
|
|
const stop = () => receipt.stop();
|
|
for (const signal of ['SIGINT', 'SIGTERM']) { process.off(signal, early); process.on(signal, stop); }
|
|
if (stopAsked) stop();
|
|
await reply({ receipt, warnings });
|
|
const code = await receipt.done;
|
|
if (code !== 0) process.stderr.write(`mosaic: console stopped unexpectedly (${code})\n`);
|
|
process.exit(code);
|