import { installLaunchAgent, isLaunchAgentLoaded, readLaunchAgentProgramArguments, readLaunchAgentRuntime, restartLaunchAgent, stageLaunchAgent, stopLaunchAgent, uninstallLaunchAgent, } from "./launchd.js"; import { installScheduledTask, isScheduledTaskInstalled, readScheduledTaskCommand, readScheduledTaskRuntime, restartScheduledTask, stageScheduledTask, stopScheduledTask, uninstallScheduledTask, } from "./schtasks.js"; import type { GatewayServiceRuntime } from "./service-runtime.js"; import type { GatewayServiceCommandConfig, GatewayServiceControlArgs, GatewayServiceEnv, GatewayServiceEnvArgs, GatewayServiceInstallArgs, GatewayServiceManageArgs, GatewayServiceRestartResult, GatewayServiceStartResult, GatewayServiceStageArgs, GatewayServiceState, } from "./service-types.js"; import { installSystemdService, isSystemdServiceEnabled, readSystemdServiceExecStart, readSystemdServiceRuntime, restartSystemdService, stageSystemdService, stopSystemdService, uninstallSystemdService, } from "./systemd.js"; export type { GatewayServiceCommandConfig, GatewayServiceControlArgs, GatewayServiceEnv, GatewayServiceEnvArgs, GatewayServiceInstallArgs, GatewayServiceManageArgs, GatewayServiceRestartResult, GatewayServiceStartResult, GatewayServiceStageArgs, GatewayServiceState, } from "./service-types.js"; function ignoreServiceWriteResult( write: (args: TArgs) => Promise, ): (args: TArgs) => Promise { return async (args: TArgs) => { await write(args); }; } export type GatewayService = { label: string; loadedText: string; notLoadedText: string; stage: (args: GatewayServiceStageArgs) => Promise; install: (args: GatewayServiceInstallArgs) => Promise; uninstall: (args: GatewayServiceManageArgs) => Promise; stop: (args: GatewayServiceControlArgs) => Promise; restart: (args: GatewayServiceControlArgs) => Promise; isLoaded: (args: GatewayServiceEnvArgs) => Promise; readCommand: (env: GatewayServiceEnv) => Promise; readRuntime: (env: GatewayServiceEnv) => Promise; }; function mergeGatewayServiceEnv( baseEnv: GatewayServiceEnv, command: GatewayServiceCommandConfig | null, ): GatewayServiceEnv { if (!command?.environment) { return baseEnv; } return { ...baseEnv, ...command.environment, }; } export async function readGatewayServiceState( service: GatewayService, args: GatewayServiceEnvArgs = {}, ): Promise { const baseEnv = args.env ?? (process.env as GatewayServiceEnv); const command = await service.readCommand(baseEnv).catch(() => null); const env = mergeGatewayServiceEnv(baseEnv, command); const [loaded, runtime] = await Promise.all([ service.isLoaded({ env }).catch(() => false), service.readRuntime(env).catch(() => undefined), ]); return { installed: command !== null, loaded, running: runtime?.status === "running", env, command, runtime, }; } export async function startGatewayService( service: GatewayService, args: GatewayServiceControlArgs, ): Promise { const state = await readGatewayServiceState(service, { env: args.env }); if (!state.loaded && !state.installed) { return { outcome: "missing-install", state, }; } try { const restartResult = await service.restart({ ...args, env: state.env }); const nextState = await readGatewayServiceState(service, { env: state.env }); return { outcome: restartResult.outcome === "scheduled" ? "scheduled" : "started", state: nextState, }; } catch (err) { const nextState = await readGatewayServiceState(service, { env: state.env }); if (!nextState.installed) { return { outcome: "missing-install", state: nextState, }; } throw err; } } export function describeGatewayServiceRestart( serviceNoun: string, result: GatewayServiceRestartResult, ): { scheduled: boolean; daemonActionResult: "restarted" | "scheduled"; message: string; progressMessage: string; } { if (result.outcome === "scheduled") { return { scheduled: true, daemonActionResult: "scheduled", message: `restart scheduled, ${serviceNoun.toLowerCase()} will restart momentarily`, progressMessage: `${serviceNoun} service restart scheduled.`, }; } return { scheduled: false, daemonActionResult: "restarted", message: `${serviceNoun} service restarted.`, progressMessage: `${serviceNoun} service restarted.`, }; } type SupportedGatewayServicePlatform = "darwin" | "linux" | "win32"; const GATEWAY_SERVICE_REGISTRY: Record = { darwin: { label: "LaunchAgent", loadedText: "loaded", notLoadedText: "not loaded", stage: ignoreServiceWriteResult(stageLaunchAgent), install: ignoreServiceWriteResult(installLaunchAgent), uninstall: uninstallLaunchAgent, stop: stopLaunchAgent, restart: restartLaunchAgent, isLoaded: isLaunchAgentLoaded, readCommand: readLaunchAgentProgramArguments, readRuntime: readLaunchAgentRuntime, }, linux: { label: "systemd", loadedText: "enabled", notLoadedText: "disabled", stage: ignoreServiceWriteResult(stageSystemdService), install: ignoreServiceWriteResult(installSystemdService), uninstall: uninstallSystemdService, stop: stopSystemdService, restart: restartSystemdService, isLoaded: isSystemdServiceEnabled, readCommand: readSystemdServiceExecStart, readRuntime: readSystemdServiceRuntime, }, win32: { label: "Scheduled Task", loadedText: "registered", notLoadedText: "missing", stage: ignoreServiceWriteResult(stageScheduledTask), install: ignoreServiceWriteResult(installScheduledTask), uninstall: uninstallScheduledTask, stop: stopScheduledTask, restart: restartScheduledTask, isLoaded: isScheduledTaskInstalled, readCommand: readScheduledTaskCommand, readRuntime: readScheduledTaskRuntime, }, }; function isSupportedGatewayServicePlatform( platform: NodeJS.Platform, ): platform is SupportedGatewayServicePlatform { return Object.hasOwn(GATEWAY_SERVICE_REGISTRY, platform); } export function resolveGatewayService(): GatewayService { if (isSupportedGatewayServicePlatform(process.platform)) { return GATEWAY_SERVICE_REGISTRY[process.platform]; } throw new Error(`Gateway service install not supported on ${process.platform}`); }