/** * Service install dispatcher — PLAN_RELAY_AGENT T17. Detects the platform, writes the unit, and * loads it. REFUSES to install as root (EXPLORE §4d least privilege). All IO is injected so the * logic is unit-testable without touching the real system. */ import type { AgentConfig } from '../config/agentConfig.js' import { buildLaunchdPlist, launchdLoadCommand, launchdPlistPath, launchdUnloadCommand, } from './launchd.js' import { buildSystemdUnit, systemdDisableCommand, systemdEnableCommand, systemdUnitPath, } from './systemd.js' export type ServicePlatform = 'launchd' | 'systemd' export class RootRefusedError extends Error { constructor() { super('refusing to install the agent service as root — run as the logged-in user (least privilege)') this.name = 'RootRefusedError' } } export interface InstallDeps { writeFile(path: string, content: string): void runCommand(cmd: string, args: readonly string[]): Promise getuid(): number homedir(): string username(): string binPath(): string } /** Map a Node platform to its service manager, or null if unsupported. */ export function detectPlatform(os: NodeJS.Platform): ServicePlatform | null { if (os === 'darwin') return 'launchd' if (os === 'linux') return 'systemd' return null } /** Write + load the service unit for `platform`. Throws RootRefusedError if running as root. */ export async function installService( _cfg: AgentConfig, platform: ServicePlatform, deps: InstallDeps, ): Promise { if (deps.getuid() === 0) throw new RootRefusedError() const bin = deps.binPath() if (platform === 'launchd') { const path = launchdPlistPath(deps.homedir()) deps.writeFile(path, buildLaunchdPlist(bin)) const { cmd, args } = launchdLoadCommand(path) await deps.runCommand(cmd, args) return } const path = systemdUnitPath(deps.homedir()) deps.writeFile(path, buildSystemdUnit(bin, deps.username())) const { cmd, args } = systemdEnableCommand() await deps.runCommand(cmd, args) } /** Unload the service unit for `platform`. */ export async function uninstallService( platform: ServicePlatform, deps: Pick, ): Promise { if (platform === 'launchd') { const { cmd, args } = launchdUnloadCommand(launchdPlistPath(deps.homedir())) await deps.runCommand(cmd, args) return } const { cmd, args } = systemdDisableCommand() await deps.runCommand(cmd, args) }