Files
OmniRoute/bin/cli/commands/stop.mjs
diegosouzapw a5fa94bdcb feat(cli): crash recovery com backoff exponencial e PID granular (Fase 1.9)
Adiciona ServerSupervisor (bin/cli/runtime/processSupervisor.mjs) que reinicia o
servidor com backoff exponencial (1s, 2s, 4s... cap 10s) em caso de crash.
Após maxRestarts falhas em 30s exibe crash log e encerra. Detecta MITM como
causa do crash via heurística e desabilita automaticamente.

PID management agora é granular por subprocesso (~/.omniroute/{service}/.pid)
suportando server, mitm e tunnel/cloudflared|tailscale. `stop` e
`killAllSubprocesses` encerram todos os serviços registrados.

Novas opções em `serve`: --log (passa stdout/stderr inline), --no-recovery
(comportamento legado sem supervisor), --max-restarts <n> (padrão 2).
2026-05-15 00:18:53 -03:00

97 lines
2.2 KiB
JavaScript

import { execFile } from "node:child_process";
import { promisify } from "node:util";
import {
readPidFile,
isPidRunning,
cleanupPidFile,
killAllSubprocesses,
sleep,
} from "../utils/pid.mjs";
import { t } from "../i18n.mjs";
const execFileAsync = promisify(execFile);
export function registerStop(program) {
program
.command("stop")
.description(t("stop.description"))
.action(async (opts) => {
const exitCode = await runStopCommand(opts);
if (exitCode !== 0) process.exit(exitCode);
});
}
export async function runStopCommand(opts = {}) {
const pid = readPidFile("server");
if (pid && isPidRunning(pid)) {
console.log(t("stop.stopping", { pid }));
try {
process.kill(pid, "SIGTERM");
let waited = 0;
while (waited < 5000 && isPidRunning(pid)) {
await sleep(100);
waited += 100;
}
if (isPidRunning(pid)) {
process.kill(pid, "SIGKILL");
await sleep(500);
}
killAllSubprocesses();
cleanupPidFile("server");
console.log(t("stop.stopped"));
return 0;
} catch (err) {
console.error(
t("common.error", { message: err instanceof Error ? err.message : String(err) })
);
return 1;
}
}
const port = opts.port ? parseInt(String(opts.port), 10) : 20128;
if (pid === null) {
console.log(t("stop.portFallback"));
await killByPort(port);
killAllSubprocesses();
cleanupPidFile("server");
console.log(t("stop.stopped"));
return 0;
}
console.log(t("stop.notRunning"));
return 0;
}
async function killByPort(port) {
if (process.platform === "win32") return;
try {
const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`]);
const pids = stdout
.trim()
.split("\n")
.map((p) => parseInt(p, 10))
.filter((p) => Number.isFinite(p) && p > 0);
for (const p of pids) {
try {
process.kill(p, "SIGTERM");
} catch {}
}
if (pids.length > 0) {
await sleep(1000);
for (const p of pids) {
try {
if (isPidRunning(p)) process.kill(p, "SIGKILL");
} catch {}
}
}
} catch {
// lsof not available or no process on port
}
}