mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:41:36 +00:00
refactor(daemon): share service lifecycle runner
This commit is contained in:
265
src/cli/daemon-cli/lifecycle-core.ts
Normal file
265
src/cli/daemon-cli/lifecycle-core.ts
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import type { GatewayService } from "../../daemon/service.js";
|
||||||
|
import { resolveIsNixMode } from "../../config/paths.js";
|
||||||
|
import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js";
|
||||||
|
import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js";
|
||||||
|
import { isWSL } from "../../infra/wsl.js";
|
||||||
|
import { defaultRuntime } from "../../runtime.js";
|
||||||
|
import {
|
||||||
|
buildDaemonServiceSnapshot,
|
||||||
|
createNullWriter,
|
||||||
|
type DaemonAction,
|
||||||
|
emitDaemonActionJson,
|
||||||
|
} from "./response.js";
|
||||||
|
|
||||||
|
type DaemonLifecycleOptions = {
|
||||||
|
json?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function maybeAugmentSystemdHints(hints: string[]): Promise<string[]> {
|
||||||
|
if (process.platform !== "linux") {
|
||||||
|
return hints;
|
||||||
|
}
|
||||||
|
const systemdAvailable = await isSystemdUserServiceAvailable().catch(() => false);
|
||||||
|
if (systemdAvailable) {
|
||||||
|
return hints;
|
||||||
|
}
|
||||||
|
return [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL() })];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createActionIO(params: { action: DaemonAction; json: boolean }) {
|
||||||
|
const stdout = params.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 (!params.json) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emitDaemonActionJson({ action: params.action, ...payload });
|
||||||
|
};
|
||||||
|
const fail = (message: string, hints?: string[]) => {
|
||||||
|
if (params.json) {
|
||||||
|
emit({ ok: false, error: message, hints });
|
||||||
|
} else {
|
||||||
|
defaultRuntime.error(message);
|
||||||
|
}
|
||||||
|
defaultRuntime.exit(1);
|
||||||
|
};
|
||||||
|
return { stdout, emit, fail };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runServiceUninstall(params: {
|
||||||
|
serviceNoun: string;
|
||||||
|
service: GatewayService;
|
||||||
|
opts?: DaemonLifecycleOptions;
|
||||||
|
stopBeforeUninstall: boolean;
|
||||||
|
assertNotLoadedAfterUninstall: boolean;
|
||||||
|
}) {
|
||||||
|
const json = Boolean(params.opts?.json);
|
||||||
|
const { stdout, emit, fail } = createActionIO({ action: "uninstall", json });
|
||||||
|
|
||||||
|
if (resolveIsNixMode(process.env)) {
|
||||||
|
fail("Nix mode detected; service uninstall is disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
try {
|
||||||
|
loaded = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch {
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
if (loaded && params.stopBeforeUninstall) {
|
||||||
|
try {
|
||||||
|
await params.service.stop({ env: process.env, stdout });
|
||||||
|
} catch {
|
||||||
|
// Best-effort stop; final loaded check gates success when enabled.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await params.service.uninstall({ env: process.env, stdout });
|
||||||
|
} catch (err) {
|
||||||
|
fail(`${params.serviceNoun} uninstall failed: ${String(err)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
try {
|
||||||
|
loaded = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch {
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
if (loaded && params.assertNotLoadedAfterUninstall) {
|
||||||
|
fail(`${params.serviceNoun} service still loaded after uninstall.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "uninstalled",
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, loaded),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runServiceStart(params: {
|
||||||
|
serviceNoun: string;
|
||||||
|
service: GatewayService;
|
||||||
|
renderStartHints: () => string[];
|
||||||
|
opts?: DaemonLifecycleOptions;
|
||||||
|
}) {
|
||||||
|
const json = Boolean(params.opts?.json);
|
||||||
|
const { stdout, emit, fail } = createActionIO({ action: "start", json });
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
try {
|
||||||
|
loaded = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch (err) {
|
||||||
|
fail(`${params.serviceNoun} service check failed: ${String(err)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!loaded) {
|
||||||
|
const hints = await maybeAugmentSystemdHints(params.renderStartHints());
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "not-loaded",
|
||||||
|
message: `${params.serviceNoun} service ${params.service.notLoadedText}.`,
|
||||||
|
hints,
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, loaded),
|
||||||
|
});
|
||||||
|
if (!json) {
|
||||||
|
defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`);
|
||||||
|
for (const hint of hints) {
|
||||||
|
defaultRuntime.log(`Start with: ${hint}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await params.service.restart({ env: process.env, stdout });
|
||||||
|
} catch (err) {
|
||||||
|
const hints = params.renderStartHints();
|
||||||
|
fail(`${params.serviceNoun} start failed: ${String(err)}`, hints);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let started = true;
|
||||||
|
try {
|
||||||
|
started = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch {
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "started",
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, started),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runServiceStop(params: {
|
||||||
|
serviceNoun: string;
|
||||||
|
service: GatewayService;
|
||||||
|
opts?: DaemonLifecycleOptions;
|
||||||
|
}) {
|
||||||
|
const json = Boolean(params.opts?.json);
|
||||||
|
const { stdout, emit, fail } = createActionIO({ action: "stop", json });
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
try {
|
||||||
|
loaded = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch (err) {
|
||||||
|
fail(`${params.serviceNoun} service check failed: ${String(err)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!loaded) {
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "not-loaded",
|
||||||
|
message: `${params.serviceNoun} service ${params.service.notLoadedText}.`,
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, loaded),
|
||||||
|
});
|
||||||
|
if (!json) {
|
||||||
|
defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await params.service.stop({ env: process.env, stdout });
|
||||||
|
} catch (err) {
|
||||||
|
fail(`${params.serviceNoun} stop failed: ${String(err)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stopped = false;
|
||||||
|
try {
|
||||||
|
stopped = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch {
|
||||||
|
stopped = false;
|
||||||
|
}
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "stopped",
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, stopped),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runServiceRestart(params: {
|
||||||
|
serviceNoun: string;
|
||||||
|
service: GatewayService;
|
||||||
|
renderStartHints: () => string[];
|
||||||
|
opts?: DaemonLifecycleOptions;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
const json = Boolean(params.opts?.json);
|
||||||
|
const { stdout, emit, fail } = createActionIO({ action: "restart", json });
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
try {
|
||||||
|
loaded = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch (err) {
|
||||||
|
fail(`${params.serviceNoun} service check failed: ${String(err)}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!loaded) {
|
||||||
|
const hints = await maybeAugmentSystemdHints(params.renderStartHints());
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "not-loaded",
|
||||||
|
message: `${params.serviceNoun} service ${params.service.notLoadedText}.`,
|
||||||
|
hints,
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, loaded),
|
||||||
|
});
|
||||||
|
if (!json) {
|
||||||
|
defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`);
|
||||||
|
for (const hint of hints) {
|
||||||
|
defaultRuntime.log(`Start with: ${hint}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await params.service.restart({ env: process.env, stdout });
|
||||||
|
let restarted = true;
|
||||||
|
try {
|
||||||
|
restarted = await params.service.isLoaded({ env: process.env });
|
||||||
|
} catch {
|
||||||
|
restarted = true;
|
||||||
|
}
|
||||||
|
emit({
|
||||||
|
ok: true,
|
||||||
|
result: "restarted",
|
||||||
|
service: buildDaemonServiceSnapshot(params.service, restarted),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
const hints = params.renderStartHints();
|
||||||
|
fail(`${params.serviceNoun} restart failed: ${String(err)}`, hints);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,233 +1,37 @@
|
|||||||
import type { DaemonLifecycleOptions } from "./types.js";
|
import type { DaemonLifecycleOptions } from "./types.js";
|
||||||
import { resolveIsNixMode } from "../../config/paths.js";
|
|
||||||
import { resolveGatewayService } from "../../daemon/service.js";
|
import { resolveGatewayService } from "../../daemon/service.js";
|
||||||
import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js";
|
import {
|
||||||
import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js";
|
runServiceRestart,
|
||||||
import { isWSL } from "../../infra/wsl.js";
|
runServiceStart,
|
||||||
import { defaultRuntime } from "../../runtime.js";
|
runServiceStop,
|
||||||
import { buildDaemonServiceSnapshot, createNullWriter, emitDaemonActionJson } from "./response.js";
|
runServiceUninstall,
|
||||||
|
} from "./lifecycle-core.js";
|
||||||
import { renderGatewayServiceStartHints } from "./shared.js";
|
import { renderGatewayServiceStartHints } from "./shared.js";
|
||||||
|
|
||||||
export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) {
|
export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceUninstall({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Gateway",
|
||||||
const emit = (payload: {
|
service: resolveGatewayService(),
|
||||||
ok: boolean;
|
opts,
|
||||||
result?: string;
|
stopBeforeUninstall: true,
|
||||||
message?: string;
|
assertNotLoadedAfterUninstall: true,
|
||||||
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 = {}) {
|
export async function runDaemonStart(opts: DaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceStart({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Gateway",
|
||||||
const emit = (payload: {
|
service: resolveGatewayService(),
|
||||||
ok: boolean;
|
renderStartHints: renderGatewayServiceStartHints,
|
||||||
result?: string;
|
opts,
|
||||||
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 = {}) {
|
export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceStop({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Gateway",
|
||||||
const emit = (payload: {
|
service: resolveGatewayService(),
|
||||||
ok: boolean;
|
opts,
|
||||||
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),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,83 +41,10 @@ export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) {
|
|||||||
* Throws/exits on check or restart failures.
|
* Throws/exits on check or restart failures.
|
||||||
*/
|
*/
|
||||||
export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promise<boolean> {
|
export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promise<boolean> {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceRestart({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Gateway",
|
||||||
const emit = (payload: {
|
service: resolveGatewayService(),
|
||||||
ok: boolean;
|
renderStartHints: renderGatewayServiceStartHints,
|
||||||
result?: string;
|
opts,
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,16 @@ import {
|
|||||||
} from "../../daemon/constants.js";
|
} from "../../daemon/constants.js";
|
||||||
import { resolveGatewayLogPaths } from "../../daemon/launchd.js";
|
import { resolveGatewayLogPaths } from "../../daemon/launchd.js";
|
||||||
import { resolveNodeService } from "../../daemon/node-service.js";
|
import { resolveNodeService } from "../../daemon/node-service.js";
|
||||||
import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js";
|
|
||||||
import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js";
|
|
||||||
import { isWSL } from "../../infra/wsl.js";
|
|
||||||
import { loadNodeHostConfig } from "../../node-host/config.js";
|
import { loadNodeHostConfig } from "../../node-host/config.js";
|
||||||
import { defaultRuntime } from "../../runtime.js";
|
import { defaultRuntime } from "../../runtime.js";
|
||||||
import { colorize, isRich, theme } from "../../terminal/theme.js";
|
import { colorize, isRich, theme } from "../../terminal/theme.js";
|
||||||
import { formatCliCommand } from "../command-format.js";
|
import { formatCliCommand } from "../command-format.js";
|
||||||
|
import {
|
||||||
|
runServiceRestart,
|
||||||
|
runServiceStart,
|
||||||
|
runServiceStop,
|
||||||
|
runServiceUninstall,
|
||||||
|
} from "../daemon-cli/lifecycle-core.js";
|
||||||
import {
|
import {
|
||||||
buildDaemonServiceSnapshot,
|
buildDaemonServiceSnapshot,
|
||||||
createNullWriter,
|
createNullWriter,
|
||||||
@@ -228,290 +231,38 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runNodeDaemonUninstall(opts: NodeDaemonLifecycleOptions = {}) {
|
export async function runNodeDaemonUninstall(opts: NodeDaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceUninstall({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Node",
|
||||||
const emit = (payload: {
|
service: resolveNodeService(),
|
||||||
ok: boolean;
|
opts,
|
||||||
result?: string;
|
stopBeforeUninstall: false,
|
||||||
message?: string;
|
assertNotLoadedAfterUninstall: false,
|
||||||
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 = resolveNodeService();
|
|
||||||
try {
|
|
||||||
await service.uninstall({ env: process.env, stdout });
|
|
||||||
} catch (err) {
|
|
||||||
fail(`Node uninstall failed: ${String(err)}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let loaded = false;
|
|
||||||
try {
|
|
||||||
loaded = await service.isLoaded({ env: process.env });
|
|
||||||
} catch {
|
|
||||||
loaded = false;
|
|
||||||
}
|
|
||||||
emit({
|
|
||||||
ok: true,
|
|
||||||
result: "uninstalled",
|
|
||||||
service: buildDaemonServiceSnapshot(service, loaded),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runNodeDaemonStart(opts: NodeDaemonLifecycleOptions = {}) {
|
export async function runNodeDaemonStart(opts: NodeDaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceStart({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Node",
|
||||||
const emit = (payload: {
|
service: resolveNodeService(),
|
||||||
ok: boolean;
|
renderStartHints: renderNodeServiceStartHints,
|
||||||
result?: string;
|
opts,
|
||||||
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 = resolveNodeService();
|
|
||||||
let loaded = false;
|
|
||||||
try {
|
|
||||||
loaded = await service.isLoaded({ env: process.env });
|
|
||||||
} catch (err) {
|
|
||||||
fail(`Node service check failed: ${String(err)}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!loaded) {
|
|
||||||
let hints = renderNodeServiceStartHints();
|
|
||||||
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: `Node service ${service.notLoadedText}.`,
|
|
||||||
hints,
|
|
||||||
service: buildDaemonServiceSnapshot(service, loaded),
|
|
||||||
});
|
|
||||||
if (!json) {
|
|
||||||
defaultRuntime.log(`Node 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 = renderNodeServiceStartHints();
|
|
||||||
fail(`Node 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 runNodeDaemonRestart(opts: NodeDaemonLifecycleOptions = {}) {
|
export async function runNodeDaemonRestart(opts: NodeDaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
await runServiceRestart({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Node",
|
||||||
const emit = (payload: {
|
service: resolveNodeService(),
|
||||||
ok: boolean;
|
renderStartHints: renderNodeServiceStartHints,
|
||||||
result?: string;
|
opts,
|
||||||
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 = resolveNodeService();
|
|
||||||
let loaded = false;
|
|
||||||
try {
|
|
||||||
loaded = await service.isLoaded({ env: process.env });
|
|
||||||
} catch (err) {
|
|
||||||
fail(`Node service check failed: ${String(err)}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!loaded) {
|
|
||||||
let hints = renderNodeServiceStartHints();
|
|
||||||
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: `Node service ${service.notLoadedText}.`,
|
|
||||||
hints,
|
|
||||||
service: buildDaemonServiceSnapshot(service, loaded),
|
|
||||||
});
|
|
||||||
if (!json) {
|
|
||||||
defaultRuntime.log(`Node 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 = renderNodeServiceStartHints();
|
|
||||||
fail(`Node restart failed: ${String(err)}`, hints);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let restarted = true;
|
|
||||||
try {
|
|
||||||
restarted = await service.isLoaded({ env: process.env });
|
|
||||||
} catch {
|
|
||||||
restarted = true;
|
|
||||||
}
|
|
||||||
emit({
|
|
||||||
ok: true,
|
|
||||||
result: "restarted",
|
|
||||||
service: buildDaemonServiceSnapshot(service, restarted),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runNodeDaemonStop(opts: NodeDaemonLifecycleOptions = {}) {
|
export async function runNodeDaemonStop(opts: NodeDaemonLifecycleOptions = {}) {
|
||||||
const json = Boolean(opts.json);
|
return await runServiceStop({
|
||||||
const stdout = json ? createNullWriter() : process.stdout;
|
serviceNoun: "Node",
|
||||||
const emit = (payload: {
|
service: resolveNodeService(),
|
||||||
ok: boolean;
|
opts,
|
||||||
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 = resolveNodeService();
|
|
||||||
let loaded = false;
|
|
||||||
try {
|
|
||||||
loaded = await service.isLoaded({ env: process.env });
|
|
||||||
} catch (err) {
|
|
||||||
fail(`Node service check failed: ${String(err)}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!loaded) {
|
|
||||||
emit({
|
|
||||||
ok: true,
|
|
||||||
result: "not-loaded",
|
|
||||||
message: `Node service ${service.notLoadedText}.`,
|
|
||||||
service: buildDaemonServiceSnapshot(service, loaded),
|
|
||||||
});
|
|
||||||
if (!json) {
|
|
||||||
defaultRuntime.log(`Node service ${service.notLoadedText}.`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await service.stop({ env: process.env, stdout });
|
|
||||||
} catch (err) {
|
|
||||||
fail(`Node 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),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user