melbot / src /cli /daemon-cli /lifecycle.ts
amos-fernandes's picture
Upload 4501 files
3a65265 verified
import { resolveIsNixMode } from "../../config/paths.js";
import { resolveGatewayService } from "../../daemon/service.js";
import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js";
import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js";
import { isWSL } from "../../infra/wsl.js";
import { defaultRuntime } from "../../runtime.js";
import { buildDaemonServiceSnapshot, createNullWriter, emitDaemonActionJson } from "./response.js";
import { renderGatewayServiceStartHints } from "./shared.js";
import type { DaemonLifecycleOptions } from "./types.js";
export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) {
const json = Boolean(opts.json);
const stdout = json ? createNullWriter() : process.stdout;
const emit = (payload: {
ok: boolean;
result?: string;
message?: string;
error?: string;
service?: {
label: string;
loaded: boolean;
loadedText: string;
notLoadedText: string;
};
}) => {
if (!json) return;
emitDaemonActionJson({ action: "uninstall", ...payload });
};
const fail = (message: string) => {
if (json) emit({ ok: false, error: message });
else defaultRuntime.error(message);
defaultRuntime.exit(1);
};
if (resolveIsNixMode(process.env)) {
fail("Nix mode detected; service uninstall is disabled.");
return;
}
const service = resolveGatewayService();
let loaded = false;
try {
loaded = await service.isLoaded({ env: process.env });
} catch {
loaded = false;
}
if (loaded) {
try {
await service.stop({ env: process.env, stdout });
} catch {
// Best-effort stop; final loaded check gates success.
}
}
try {
await service.uninstall({ env: process.env, stdout });
} catch (err) {
fail(`Gateway uninstall failed: ${String(err)}`);
return;
}
loaded = false;
try {
loaded = await service.isLoaded({ env: process.env });
} catch {
loaded = false;
}
if (loaded) {
fail("Gateway service still loaded after uninstall.");
return;
}
emit({
ok: true,
result: "uninstalled",
service: buildDaemonServiceSnapshot(service, loaded),
});
}
export async function runDaemonStart(opts: DaemonLifecycleOptions = {}) {
const json = Boolean(opts.json);
const stdout = json ? createNullWriter() : process.stdout;
const emit = (payload: {
ok: boolean;
result?: string;
message?: string;
error?: string;
hints?: string[];
service?: {
label: string;
loaded: boolean;
loadedText: string;
notLoadedText: string;
};
}) => {
if (!json) return;
emitDaemonActionJson({ action: "start", ...payload });
};
const fail = (message: string, hints?: string[]) => {
if (json) emit({ ok: false, error: message, hints });
else defaultRuntime.error(message);
defaultRuntime.exit(1);
};
const service = resolveGatewayService();
let loaded = false;
try {
loaded = await service.isLoaded({ env: process.env });
} catch (err) {
fail(`Gateway service check failed: ${String(err)}`);
return;
}
if (!loaded) {
let hints = renderGatewayServiceStartHints();
if (process.platform === "linux") {
const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false);
if (!systemdAvailable) {
hints = [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })];
}
}
emit({
ok: true,
result: "not-loaded",
message: `Gateway service ${service.notLoadedText}.`,
hints,
service: buildDaemonServiceSnapshot(service, loaded),
});
if (!json) {
defaultRuntime.log(`Gateway service ${service.notLoadedText}.`);
for (const hint of hints) {
defaultRuntime.log(`Start with: ${hint}`);
}
}
return;
}
try {
await service.restart({ env: process.env, stdout });
} catch (err) {
const hints = renderGatewayServiceStartHints();
fail(`Gateway start failed: ${String(err)}`, hints);
return;
}
let started = true;
try {
started = await service.isLoaded({ env: process.env });
} catch {
started = true;
}
emit({
ok: true,
result: "started",
service: buildDaemonServiceSnapshot(service, started),
});
}
export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) {
const json = Boolean(opts.json);
const stdout = json ? createNullWriter() : process.stdout;
const emit = (payload: {
ok: boolean;
result?: string;
message?: string;
error?: string;
service?: {
label: string;
loaded: boolean;
loadedText: string;
notLoadedText: string;
};
}) => {
if (!json) return;
emitDaemonActionJson({ action: "stop", ...payload });
};
const fail = (message: string) => {
if (json) emit({ ok: false, error: message });
else defaultRuntime.error(message);
defaultRuntime.exit(1);
};
const service = resolveGatewayService();
let loaded = false;
try {
loaded = await service.isLoaded({ env: process.env });
} catch (err) {
fail(`Gateway service check failed: ${String(err)}`);
return;
}
if (!loaded) {
emit({
ok: true,
result: "not-loaded",
message: `Gateway service ${service.notLoadedText}.`,
service: buildDaemonServiceSnapshot(service, loaded),
});
if (!json) {
defaultRuntime.log(`Gateway service ${service.notLoadedText}.`);
}
return;
}
try {
await service.stop({ env: process.env, stdout });
} catch (err) {
fail(`Gateway stop failed: ${String(err)}`);
return;
}
let stopped = false;
try {
stopped = await service.isLoaded({ env: process.env });
} catch {
stopped = false;
}
emit({
ok: true,
result: "stopped",
service: buildDaemonServiceSnapshot(service, stopped),
});
}
/**
* Restart the gateway service service.
* @returns `true` if restart succeeded, `false` if the service was not loaded.
* Throws/exits on check or restart failures.
*/
export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promise<boolean> {
const json = Boolean(opts.json);
const stdout = json ? createNullWriter() : process.stdout;
const emit = (payload: {
ok: boolean;
result?: string;
message?: string;
error?: string;
hints?: string[];
service?: {
label: string;
loaded: boolean;
loadedText: string;
notLoadedText: string;
};
}) => {
if (!json) return;
emitDaemonActionJson({ action: "restart", ...payload });
};
const fail = (message: string, hints?: string[]) => {
if (json) emit({ ok: false, error: message, hints });
else defaultRuntime.error(message);
defaultRuntime.exit(1);
};
const service = resolveGatewayService();
let loaded = false;
try {
loaded = await service.isLoaded({ env: process.env });
} catch (err) {
fail(`Gateway service check failed: ${String(err)}`);
return false;
}
if (!loaded) {
let hints = renderGatewayServiceStartHints();
if (process.platform === "linux") {
const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false);
if (!systemdAvailable) {
hints = [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })];
}
}
emit({
ok: true,
result: "not-loaded",
message: `Gateway service ${service.notLoadedText}.`,
hints,
service: buildDaemonServiceSnapshot(service, loaded),
});
if (!json) {
defaultRuntime.log(`Gateway service ${service.notLoadedText}.`);
for (const hint of hints) {
defaultRuntime.log(`Start with: ${hint}`);
}
}
return false;
}
try {
await service.restart({ env: process.env, stdout });
let restarted = true;
try {
restarted = await service.isLoaded({ env: process.env });
} catch {
restarted = true;
}
emit({
ok: true,
result: "restarted",
service: buildDaemonServiceSnapshot(service, restarted),
});
return true;
} catch (err) {
const hints = renderGatewayServiceStartHints();
fail(`Gateway restart failed: ${String(err)}`, hints);
return false;
}
}