import { execFile } from "node:child_process";import { readFileSync } from "node:fs";import { homedir, userInfo } from "node:os";import { FileSystem, Path } from "effect";import type { PlatformError } from "effect/PlatformError";import * as Effect from "effect/Effect"; import { resolveExecutorDataDir } from "./local-server-manifest"; // ---------------------------------------------------------------------------// OS service backends for the supervised Executor daemon.//// The long-lived gateway must outlive the GUI app and survive machine restarts.// That means the OS service manager — not a foreground process — owns its// lifecycle: launchd on macOS, systemd --user on Linux, Task Scheduler on// Windows. Each backend registers the SAME running contract: spawn// `<executor> daemon run --foreground --port <p>`, bind loopback, write// `server.json`, and get restarted on crash but not on a clean stop.//// By default each backend starts the daemon at LOGIN with no elevation: launchd// RunAtLoad, systemd --user, and a Windows per-user logon Scheduled Task. Where// the platform can also survive a reboot before any login, that is layered on// best-effort (Linux lingering) or behind an explicit, elevation-requiring opt// in (Windows `--boot`: a BootTrigger/S4U task). Windows registration goes// through `schtasks.exe` (Task Scheduler RPC), not the CIM `*-ScheduledTask`// cmdlets, so a non-admin install from the compiled binary is not blocked by a// DCOM local-activation denial.// --------------------------------------------------------------------------- export const SERVICE_LABEL = "sh.executor.daemon"; /** * The supervised service binds this port by default. It matches the desktop * connect-card port (4789, not the `executor daemon run` default of 4788) so * existing desktop MCP-client configs keep resolving. The exact value is * low-stakes: clients discover the live port from `server.json`. */export const DEFAULT_SERVICE_PORT = 4789; export interface ServiceDescriptor { /** Absolute path to the `executor` binary the service should run. */ readonly executablePath: string; readonly port: number; /** Installing CLI version, baked in for drift detection on upgrade. */ readonly version: string; /** * Opt in to boot-survival-before-login (Windows only, requires elevation). * Default (false) installs a per-user, login-triggered task that needs no * Administrator shell, matching launchd RunAtLoad and systemd --user. When * true the Windows backend registers an AtStartup/S4U task that runs at boot * before any logon, which Task Scheduler only lets an elevated shell create. * launchd and systemd ignore this flag (they always start at login, with * Linux lingering best-effort upgrading that to boot). */ readonly boot?: boolean;} // No secret is part of the descriptor: the supervised daemon mints/loads its// bearer token from the 0600 `auth.json` (under EXECUTOR_DATA_DIR) on start, and// clients read the same file. Keeping the secret out of the plist/unit means// `launchctl print`/`list` and `systemctl cat` never expose it. export type ServicePlatform = "darwin" | "linux" | "win32" | "unsupported"; export interface ServiceStatus { readonly platform: ServicePlatform; /** The OS manager has a unit/plist/task on disk for the service. */ readonly registered: boolean; /** The OS manager reports the service currently loaded/active. */ readonly running: boolean; readonly pid: number | null; /** Extra human-readable lines (e.g. manual steps on unsupported platforms). */ readonly detail: ReadonlyArray<string>;} export interface ServiceBackend { readonly platform: ServicePlatform; /** True when this backend actually drives the OS manager (vs. printing steps). */ readonly automated: boolean; readonly install: ( descriptor: ServiceDescriptor, ) => Effect.Effect<void, Error | PlatformError, FileSystem.FileSystem | Path.Path>; readonly uninstall: () => Effect.Effect< void, Error | PlatformError, FileSystem.FileSystem | Path.Path >; readonly status: () => Effect.Effect< ServiceStatus, Error | PlatformError, FileSystem.FileSystem | Path.Path >; readonly restart: () => Effect.Effect<void, Error, FileSystem.FileSystem | Path.Path>;} // ---------------------------------------------------------------------------// Process helper — run an OS command and capture (stdout, stderr, exit code).// Resolves on a non-zero exit so callers can branch; fails only when the// command itself cannot be spawned (e.g. launchctl missing).// --------------------------------------------------------------------------- interface CommandResult { readonly stdout: string; readonly stderr: string; readonly code: number;} const runCommand = ( cmd: string, args: ReadonlyArray<string>, env?: Record<string, string | undefined>,): Effect.Effect<CommandResult, Error> => Effect.callback<CommandResult, Error>((resume) => { const options = env ? { encoding: "utf8" as const, env: { ...process.env, ...env } } : { encoding: "utf8" as const }; execFile(cmd, [...args], options, (error, stdout, stderr) => { // A string `code` (ENOENT etc.) means the command could not be spawned. if (error && typeof (error as { code?: unknown }).code === "string") { resume( Effect.fail(new Error(`Failed to run \`${cmd}\`: ${(error as { code: string }).code}`)), ); return; } const code = error && typeof (error as { code?: unknown }).code === "number" ? (error as { code: number }).code : 0; resume(Effect.succeed({ stdout: stdout ?? "", stderr: stderr ?? "", code })); }); }); const currentUid = (): number => { const getuid = (process as { getuid?: () => number }).getuid; if (typeof getuid === "function") return getuid.call(process); return userInfo().uid;}; const xmlEscape = (value: string): string => value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); const xmlUnescape = (value: string): string => value .replaceAll("'", "'") .replaceAll(""", '"') .replaceAll(">", ">") .replaceAll("<", "<") .replaceAll("&", "&"); // ---------------------------------------------------------------------------// Shared service environment + program args// --------------------------------------------------------------------------- const serviceProgramArguments = (descriptor: ServiceDescriptor): ReadonlyArray<string> => [ descriptor.executablePath, "daemon", "run", "--foreground", "--port", String(descriptor.port), "--hostname", "127.0.0.1",]; const serviceEnvironment = ( descriptor: ServiceDescriptor, dataDir: string,): Record<string, string> => { const passThroughKeys = [ "EXECUTOR_CLIENT", "EXECUTOR_SENTRY_DSN", "EXECUTOR_SENTRY_RELEASE", "EXECUTOR_SENTRY_ENVIRONMENT", "EXECUTOR_RUN_ID", // Analytics opt-out must survive into the supervised unit's minimal env, // or an opted-out install would silently re-enable analytics under launchd. "DO_NOT_TRACK", "EXECUTOR_DISABLE_ANALYTICS", // Preserve the caller's TLS trust configuration. Corporate/intercepting // CAs commonly live outside the OS trust store; dropping these paths in a // minimal launchd/systemd environment makes HTTPS integrations fail only // after service installation while the same CLI call succeeds. "NODE_EXTRA_CA_CERTS", "SSL_CERT_FILE", "SSL_CERT_DIR", ] as const; const passThrough = Object.fromEntries( passThroughKeys.flatMap((key) => { const value = process.env[key]; return value ? [[key, value] as const] : []; }), ); return { // Marks the process as OS-supervised so the daemon resolves its bearer token // from the durable 0600 auth.json (the secret is never in the unit itself). EXECUTOR_SUPERVISED: "1", // Pin the data/scope dirs explicitly: launchd/systemd give a minimal // environment and we never want the daemon to fall back to a different home // or cwd than the user's singleton local service. EXECUTOR_DATA_DIR: dataDir, EXECUTOR_SCOPE_DIR: process.env.EXECUTOR_SCOPE_DIR ?? dataDir, // Stamp the installing version so `service status` can flag drift after an // upgrade where the unit still points at an older binary path. EXECUTOR_SERVICE_VERSION: descriptor.version, // A launchd/systemd unit starts with a bare PATH — without the user's PATH // the daemon can't find pyenv/nvm/volta/Homebrew tools that integrations may // shell out to. `service install` runs from the user's shell, so its own // PATH is the right one to bake in. (Reference: opencode shell-env capture.) ...(process.env.PATH ? { PATH: process.env.PATH } : {}), ...passThrough, };}; // ---------------------------------------------------------------------------// macOS — launchd LaunchAgent (fully built)// --------------------------------------------------------------------------- const launchAgentsDir = (path: Path.Path): string => path.join(homedir(), "Library", "LaunchAgents"); const launchdPlistPath = (path: Path.Path): string => path.join(launchAgentsDir(path), `${SERVICE_LABEL}.plist`); const serviceLogDir = (path: Path.Path): string => path.join(resolveExecutorDataDir(path), "logs"); const DESKTOP_APP_BUNDLE_IDENTIFIER = "sh.executor.desktop"; const enclosingAppBundlePath = (executablePath: string): string | null => { const match = executablePath.match(/^(.*\.app)\/Contents\//); return match?.[1] ?? null;}; const readBundleIdentifier = (appBundlePath: string): string | null => { try { const plist = readFileSync(`${appBundlePath}/Contents/Info.plist`, "utf8"); const match = plist.match(/<key>\s*CFBundleIdentifier\s*<\/key>\s*<string>([^<]*)<\/string>/); const bundleIdentifier = match ? xmlUnescape(match[1].trim()) : ""; return bundleIdentifier.length > 0 ? bundleIdentifier : null; } catch { return null; }}; const associatedBundleIdentifier = (programArguments: ReadonlyArray<string>): string | null => { const executablePath = programArguments[0]; if (!executablePath) return null; const appBundlePath = enclosingAppBundlePath(executablePath); if (!appBundlePath) return null; const bundleIdentifier = readBundleIdentifier(appBundlePath); if (bundleIdentifier) return bundleIdentifier; return /(?:^|\/)Executor\.app\/Contents\//.test(executablePath) ? DESKTOP_APP_BUNDLE_IDENTIFIER : null;}; export interface LaunchdPlistOptions { readonly label: string; readonly programArguments: ReadonlyArray<string>; readonly environment: Record<string, string>; readonly stdoutPath: string; readonly stderrPath: string; readonly workingDirectory: string;} /** * Render a user LaunchAgent plist. KeepAlive uses * `SuccessfulExit=false` so launchd restarts the daemon on a crash/non-zero * exit but leaves it stopped after a clean `bootout` (which sends SIGTERM → * the daemon exits 0). RunAtLoad starts it on login; ProcessType=Background * keeps it off the foreground scheduler. */export const generateLaunchdPlist = (options: LaunchdPlistOptions): string => { const programArgs = options.programArguments .map((arg) => ` <string>${xmlEscape(arg)}</string>`) .join("\n"); const bundleIdentifier = associatedBundleIdentifier(options.programArguments); const associatedBundleIdentifiers = bundleIdentifier ? ` <key>AssociatedBundleIdentifiers</key>\n <array>\n <string>${xmlEscape(bundleIdentifier)}</string>\n </array>\n` : ""; const envEntries = Object.entries(options.environment) .map( ([key, value]) => ` <key>${xmlEscape(key)}</key>\n <string>${xmlEscape(value)}</string>`, ) .join("\n"); return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>${xmlEscape(options.label)}</string>${associatedBundleIdentifiers} <key>ProgramArguments</key> <array>${programArgs} </array> <key>EnvironmentVariables</key> <dict>${envEntries} </dict> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>ProcessType</key> <string>Background</string> <key>WorkingDirectory</key> <string>${xmlEscape(options.workingDirectory)}</string> <key>StandardOutPath</key> <string>${xmlEscape(options.stdoutPath)}</string> <key>StandardErrorPath</key> <string>${xmlEscape(options.stderrPath)}</string></dict></plist>`;}; const parseLaunchctlPid = (printOutput: string): number | null => { const match = printOutput.match(/\bpid\s*=\s*(\d+)/); if (!match) return null; const pid = Number.parseInt(match[1], 10); return Number.isInteger(pid) && pid > 0 ? pid : null;}; const makeLaunchdBackend = (): ServiceBackend => { const serviceTarget = (uid: number): string => `gui/${uid}/${SERVICE_LABEL}`; return { platform: "darwin", automated: true, install: (descriptor) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const uid = currentUid(); const dataDir = resolveExecutorDataDir(path); const logs = serviceLogDir(path); yield* fs.makeDirectory(launchAgentsDir(path), { recursive: true }); yield* fs.makeDirectory(logs, { recursive: true }); const plist = generateLaunchdPlist({ label: SERVICE_LABEL, programArguments: serviceProgramArguments(descriptor), environment: serviceEnvironment(descriptor, dataDir), stdoutPath: path.join(logs, "daemon.log"), stderrPath: path.join(logs, "daemon.error.log"), workingDirectory: dataDir, }); const plistFile = launchdPlistPath(path); // 0600: the plist is owner-only. It carries no secret — the daemon reads // the bearer from auth.json at boot — but stays tight regardless. yield* fs.writeFileString(plistFile, plist, { mode: 0o600 }); // Re-bootstrap cleanly: a stale registration from a prior install would // make `bootstrap` fail with "service already loaded". `service // uninstall` also records the label as disabled in launchd's override // database; clear that before bootstrapping or a reinstall can fail with // launchctl's generic "Bootstrap failed: 5" error. yield* runCommand("launchctl", ["bootout", serviceTarget(uid)]).pipe(Effect.ignore); yield* runCommand("launchctl", ["enable", serviceTarget(uid)]).pipe(Effect.ignore); const bootstrap = yield* runCommand("launchctl", ["bootstrap", `gui/${uid}`, plistFile]); if (bootstrap.code !== 0) { return yield* Effect.fail( new Error( `launchctl bootstrap failed (exit ${bootstrap.code}): ${bootstrap.stderr.trim() || bootstrap.stdout.trim()}`, ), ); } }), uninstall: () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const uid = currentUid(); yield* runCommand("launchctl", ["bootout", serviceTarget(uid)]).pipe(Effect.ignore); yield* runCommand("launchctl", ["disable", serviceTarget(uid)]).pipe(Effect.ignore); yield* fs.remove(launchdPlistPath(path), { force: true }); }), status: () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const uid = currentUid(); const registered = yield* fs.exists(launchdPlistPath(path)); const print = yield* runCommand("launchctl", ["print", serviceTarget(uid)]); const running = print.code === 0; return { platform: "darwin" as const, registered, running, pid: running ? parseLaunchctlPid(print.stdout) : null, detail: [], }; }), restart: () => Effect.gen(function* () { const uid = currentUid(); const result = yield* runCommand("launchctl", ["kickstart", "-k", serviceTarget(uid)]); if (result.code !== 0) { return yield* Effect.fail( new Error( `launchctl kickstart failed (exit ${result.code}): ${result.stderr.trim() || result.stdout.trim()}`, ), ); } }), };}; // ---------------------------------------------------------------------------// Linux — systemd --user + lingering (reboot-survival verified in an Ubuntu VM)// --------------------------------------------------------------------------- const systemdUnitDir = (path: Path.Path): string => path.join(homedir(), ".config", "systemd", "user"); const systemdUnitPath = (path: Path.Path): string => path.join(systemdUnitDir(path), `${SERVICE_LABEL}.service`); export interface SystemdUnitOptions { readonly execStart: ReadonlyArray<string>; readonly environment: Record<string, string>; readonly workingDirectory: string; readonly stdoutPath: string; readonly stderrPath: string;} const SYSTEMD_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/; const systemdQuote = (value: string): string => { if (SYSTEMD_BARE_VALUE.test(value)) return value; const escaped = value .replaceAll("\\", "\\\\") .replaceAll('"', '\\"') .replaceAll("\n", "\\n") .replaceAll("\r", "\\r") .replaceAll("\t", "\\t"); return `"${escaped}"`;}; /** Render a systemd --user unit. Pure (snapshot-tested). */export const generateSystemdUnit = (options: SystemdUnitOptions): string => { const execStart = options.execStart.map(systemdQuote).join(" "); const env = Object.entries(options.environment) .map(([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}`) .join("\n"); return `[Unit]Description=Executor supervised daemonAfter=default.target [Service]Type=simpleExecStart=${execStart}${env}WorkingDirectory=${systemdQuote(options.workingDirectory)}StandardOutput=${systemdQuote(`append:${options.stdoutPath}`)}StandardError=${systemdQuote(`append:${options.stderrPath}`)}Restart=on-failureRestartSec=5s [Install]WantedBy=default.target`;}; const makeSystemdBackend = (): ServiceBackend => { const unitName = `${SERVICE_LABEL}.service`; return { platform: "linux", automated: true, install: (descriptor) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dataDir = resolveExecutorDataDir(path); const logs = serviceLogDir(path); yield* fs.makeDirectory(systemdUnitDir(path), { recursive: true }); yield* fs.makeDirectory(logs, { recursive: true }); const unit = generateSystemdUnit({ execStart: serviceProgramArguments(descriptor), environment: serviceEnvironment(descriptor, dataDir), workingDirectory: dataDir, stdoutPath: path.join(logs, "daemon.log"), stderrPath: path.join(logs, "daemon.error.log"), }); yield* fs.writeFileString(systemdUnitPath(path), unit, { mode: 0o600 }); // `systemctl --user` needs XDG_RUNTIME_DIR to reach the user bus. Supply // it if the caller's environment lacks it (e.g. a non-login shell) so // install is robust regardless of how it was invoked. const username = userInfo().username; const sdEnv = {Lines 1–500 of 983. Load more
import { execFile } from "node:child_process"; import { readFileSync } from "node:fs"; import { homedir, userInfo } from "node:os"; import { FileSystem, Path } from "effect"; import type { PlatformError } from "effect/PlatformError"; import * as Effect from "effect/Effect"; import { resolveExecutorDataDir } from "./local-server-manifest"; // --------------------------------------------------------------------------- // OS service backends for the supervised Executor daemon. // // The long-lived gateway must outlive the GUI app and survive machine restarts. // That means the OS service manager — not a foreground process — owns its // lifecycle: launchd on macOS, systemd --user on Linux, Task Scheduler on // Windows. Each backend registers the SAME running contract: spawn // `<executor> daemon run --foreground --port <p>`, bind loopback, write // `server.json`, and get restarted on crash but not on a clean stop. // // By default each backend starts the daemon at LOGIN with no elevation: launchd // RunAtLoad, systemd --user, and a Windows per-user logon Scheduled Task. Where // the platform can also survive a reboot before any login, that is layered on // best-effort (Linux lingering) or behind an explicit, elevation-requiring opt // in (Windows `--boot`: a BootTrigger/S4U task). Windows registration goes // through `schtasks.exe` (Task Scheduler RPC), not the CIM `*-ScheduledTask` // cmdlets, so a non-admin install from the compiled binary is not blocked by a // DCOM local-activation denial. // --------------------------------------------------------------------------- export const SERVICE_LABEL = "sh.executor.daemon"; /** * The supervised service binds this port by default. It matches the desktop * connect-card port (4789, not the `executor daemon run` default of 4788) so * existing desktop MCP-client configs keep resolving. The exact value is * low-stakes: clients discover the live port from `server.json`. */ export const DEFAULT_SERVICE_PORT = 4789; export interface ServiceDescriptor { /** Absolute path to the `executor` binary the service should run. */ readonly executablePath: string; readonly port: number; /** Installing CLI version, baked in for drift detection on upgrade. */ readonly version: string; /** * Opt in to boot-survival-before-login (Windows only, requires elevation). * Default (false) installs a per-user, login-triggered task that needs no * Administrator shell, matching launchd RunAtLoad and systemd --user. When * true the Windows backend registers an AtStartup/S4U task that runs at boot * before any logon, which Task Scheduler only lets an elevated shell create. * launchd and systemd ignore this flag (they always start at login, with * Linux lingering best-effort upgrading that to boot). */ readonly boot?: boolean; } // No secret is part of the descriptor: the supervised daemon mints/loads its // bearer token from the 0600 `auth.json` (under EXECUTOR_DATA_DIR) on start, and // clients read the same file. Keeping the secret out of the plist/unit means // `launchctl print`/`list` and `systemctl cat` never expose it. export type ServicePlatform = "darwin" | "linux" | "win32" | "unsupported"; export interface ServiceStatus { readonly platform: ServicePlatform; /** The OS manager has a unit/plist/task on disk for the service. */ readonly registered: boolean; /** The OS manager reports the service currently loaded/active. */ readonly running: boolean; readonly pid: number | null; /** Extra human-readable lines (e.g. manual steps on unsupported platforms). */ readonly detail: ReadonlyArray<string>; } export interface ServiceBackend { readonly platform: ServicePlatform; /** True when this backend actually drives the OS manager (vs. printing steps). */ readonly automated: boolean; readonly install: ( descriptor: ServiceDescriptor, ) => Effect.Effect<void, Error | PlatformError, FileSystem.FileSystem | Path.Path>; readonly uninstall: () => Effect.Effect< void, Error | PlatformError, FileSystem.FileSystem | Path.Path >; readonly status: () => Effect.Effect< ServiceStatus, Error | PlatformError, FileSystem.FileSystem | Path.Path >; readonly restart: () => Effect.Effect<void, Error, FileSystem.FileSystem | Path.Path>; } // --------------------------------------------------------------------------- // Process helper — run an OS command and capture (stdout, stderr, exit code). // Resolves on a non-zero exit so callers can branch; fails only when the // command itself cannot be spawned (e.g. launchctl missing). // --------------------------------------------------------------------------- interface CommandResult { readonly stdout: string; readonly stderr: string; readonly code: number; } const runCommand = ( cmd: string, args: ReadonlyArray<string>, env?: Record<string, string | undefined>, ): Effect.Effect<CommandResult, Error> => Effect.callback<CommandResult, Error>((resume) => { const options = env ? { encoding: "utf8" as const, env: { ...process.env, ...env } } : { encoding: "utf8" as const }; execFile(cmd, [...args], options, (error, stdout, stderr) => { // A string `code` (ENOENT etc.) means the command could not be spawned. if (error && typeof (error as { code?: unknown }).code === "string") { resume( Effect.fail(new Error(`Failed to run \`${cmd}\`: ${(error as { code: string }).code}`)), ); return; } const code = error && typeof (error as { code?: unknown }).code === "number" ? (error as { code: number }).code : 0; resume(Effect.succeed({ stdout: stdout ?? "", stderr: stderr ?? "", code })); }); }); const currentUid = (): number => { const getuid = (process as { getuid?: () => number }).getuid; if (typeof getuid === "function") return getuid.call(process); return userInfo().uid; }; const xmlEscape = (value: string): string => value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); const xmlUnescape = (value: string): string => value .replaceAll("'", "'") .replaceAll(""", '"') .replaceAll(">", ">") .replaceAll("<", "<") .replaceAll("&", "&"); // --------------------------------------------------------------------------- // Shared service environment + program args // --------------------------------------------------------------------------- const serviceProgramArguments = (descriptor: ServiceDescriptor): ReadonlyArray<string> => [ descriptor.executablePath, "daemon", "run", "--foreground", "--port", String(descriptor.port), "--hostname", "127.0.0.1", ]; const serviceEnvironment = ( descriptor: ServiceDescriptor, dataDir: string, ): Record<string, string> => { const passThroughKeys = [ "EXECUTOR_CLIENT", "EXECUTOR_SENTRY_DSN", "EXECUTOR_SENTRY_RELEASE", "EXECUTOR_SENTRY_ENVIRONMENT", "EXECUTOR_RUN_ID", // Analytics opt-out must survive into the supervised unit's minimal env, // or an opted-out install would silently re-enable analytics under launchd. "DO_NOT_TRACK", "EXECUTOR_DISABLE_ANALYTICS", // Preserve the caller's TLS trust configuration. Corporate/intercepting // CAs commonly live outside the OS trust store; dropping these paths in a // minimal launchd/systemd environment makes HTTPS integrations fail only // after service installation while the same CLI call succeeds. "NODE_EXTRA_CA_CERTS", "SSL_CERT_FILE", "SSL_CERT_DIR", ] as const; const passThrough = Object.fromEntries( passThroughKeys.flatMap((key) => { const value = process.env[key]; return value ? [[key, value] as const] : []; }), ); return { // Marks the process as OS-supervised so the daemon resolves its bearer token // from the durable 0600 auth.json (the secret is never in the unit itself). EXECUTOR_SUPERVISED: "1", // Pin the data/scope dirs explicitly: launchd/systemd give a minimal // environment and we never want the daemon to fall back to a different home // or cwd than the user's singleton local service. EXECUTOR_DATA_DIR: dataDir, EXECUTOR_SCOPE_DIR: process.env.EXECUTOR_SCOPE_DIR ?? dataDir, // Stamp the installing version so `service status` can flag drift after an // upgrade where the unit still points at an older binary path. EXECUTOR_SERVICE_VERSION: descriptor.version, // A launchd/systemd unit starts with a bare PATH — without the user's PATH // the daemon can't find pyenv/nvm/volta/Homebrew tools that integrations may // shell out to. `service install` runs from the user's shell, so its own // PATH is the right one to bake in. (Reference: opencode shell-env capture.) ...(process.env.PATH ? { PATH: process.env.PATH } : {}), ...passThrough, }; }; // --------------------------------------------------------------------------- // macOS — launchd LaunchAgent (fully built) // --------------------------------------------------------------------------- const launchAgentsDir = (path: Path.Path): string => path.join(homedir(), "Library", "LaunchAgents"); const launchdPlistPath = (path: Path.Path): string => path.join(launchAgentsDir(path), `${SERVICE_LABEL}.plist`); const serviceLogDir = (path: Path.Path): string => path.join(resolveExecutorDataDir(path), "logs"); const DESKTOP_APP_BUNDLE_IDENTIFIER = "sh.executor.desktop"; const enclosingAppBundlePath = (executablePath: string): string | null => { const match = executablePath.match(/^(.*\.app)\/Contents\//); return match?.[1] ?? null; }; const readBundleIdentifier = (appBundlePath: string): string | null => { try { const plist = readFileSync(`${appBundlePath}/Contents/Info.plist`, "utf8"); const match = plist.match(/<key>\s*CFBundleIdentifier\s*<\/key>\s*<string>([^<]*)<\/string>/); const bundleIdentifier = match ? xmlUnescape(match[1].trim()) : ""; return bundleIdentifier.length > 0 ? bundleIdentifier : null; } catch { return null; } }; const associatedBundleIdentifier = (programArguments: ReadonlyArray<string>): string | null => { const executablePath = programArguments[0]; if (!executablePath) return null; const appBundlePath = enclosingAppBundlePath(executablePath); if (!appBundlePath) return null; const bundleIdentifier = readBundleIdentifier(appBundlePath); if (bundleIdentifier) return bundleIdentifier; return /(?:^|\/)Executor\.app\/Contents\//.test(executablePath) ? DESKTOP_APP_BUNDLE_IDENTIFIER : null; }; export interface LaunchdPlistOptions { readonly label: string; readonly programArguments: ReadonlyArray<string>; readonly environment: Record<string, string>; readonly stdoutPath: string; readonly stderrPath: string; readonly workingDirectory: string; } /** * Render a user LaunchAgent plist. KeepAlive uses * `SuccessfulExit=false` so launchd restarts the daemon on a crash/non-zero * exit but leaves it stopped after a clean `bootout` (which sends SIGTERM → * the daemon exits 0). RunAtLoad starts it on login; ProcessType=Background * keeps it off the foreground scheduler. */ export const generateLaunchdPlist = (options: LaunchdPlistOptions): string => { const programArgs = options.programArguments .map((arg) => ` <string>${xmlEscape(arg)}</string>`) .join("\n"); const bundleIdentifier = associatedBundleIdentifier(options.programArguments); const associatedBundleIdentifiers = bundleIdentifier ? ` <key>AssociatedBundleIdentifiers</key>\n <array>\n <string>${xmlEscape(bundleIdentifier)}</string>\n </array>\n` : ""; const envEntries = Object.entries(options.environment) .map( ([key, value]) => ` <key>${xmlEscape(key)}</key>\n <string>${xmlEscape(value)}</string>`, ) .join("\n"); return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${xmlEscape(options.label)}</string> ${associatedBundleIdentifiers} <key>ProgramArguments</key> <array> ${programArgs} </array> <key>EnvironmentVariables</key> <dict> ${envEntries} </dict> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>ProcessType</key> <string>Background</string> <key>WorkingDirectory</key> <string>${xmlEscape(options.workingDirectory)}</string> <key>StandardOutPath</key> <string>${xmlEscape(options.stdoutPath)}</string> <key>StandardErrorPath</key> <string>${xmlEscape(options.stderrPath)}</string> </dict> </plist> `; }; const parseLaunchctlPid = (printOutput: string): number | null => { const match = printOutput.match(/\bpid\s*=\s*(\d+)/); if (!match) return null; const pid = Number.parseInt(match[1], 10); return Number.isInteger(pid) && pid > 0 ? pid : null; }; const makeLaunchdBackend = (): ServiceBackend => { const serviceTarget = (uid: number): string => `gui/${uid}/${SERVICE_LABEL}`; return { platform: "darwin", automated: true, install: (descriptor) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const uid = currentUid(); const dataDir = resolveExecutorDataDir(path); const logs = serviceLogDir(path); yield* fs.makeDirectory(launchAgentsDir(path), { recursive: true }); yield* fs.makeDirectory(logs, { recursive: true }); const plist = generateLaunchdPlist({ label: SERVICE_LABEL, programArguments: serviceProgramArguments(descriptor), environment: serviceEnvironment(descriptor, dataDir), stdoutPath: path.join(logs, "daemon.log"), stderrPath: path.join(logs, "daemon.error.log"), workingDirectory: dataDir, }); const plistFile = launchdPlistPath(path); // 0600: the plist is owner-only. It carries no secret — the daemon reads // the bearer from auth.json at boot — but stays tight regardless. yield* fs.writeFileString(plistFile, plist, { mode: 0o600 }); // Re-bootstrap cleanly: a stale registration from a prior install would // make `bootstrap` fail with "service already loaded". `service // uninstall` also records the label as disabled in launchd's override // database; clear that before bootstrapping or a reinstall can fail with // launchctl's generic "Bootstrap failed: 5" error. yield* runCommand("launchctl", ["bootout", serviceTarget(uid)]).pipe(Effect.ignore); yield* runCommand("launchctl", ["enable", serviceTarget(uid)]).pipe(Effect.ignore); const bootstrap = yield* runCommand("launchctl", ["bootstrap", `gui/${uid}`, plistFile]); if (bootstrap.code !== 0) { return yield* Effect.fail( new Error( `launchctl bootstrap failed (exit ${bootstrap.code}): ${bootstrap.stderr.trim() || bootstrap.stdout.trim()}`, ), ); } }), uninstall: () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const uid = currentUid(); yield* runCommand("launchctl", ["bootout", serviceTarget(uid)]).pipe(Effect.ignore); yield* runCommand("launchctl", ["disable", serviceTarget(uid)]).pipe(Effect.ignore); yield* fs.remove(launchdPlistPath(path), { force: true }); }), status: () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const uid = currentUid(); const registered = yield* fs.exists(launchdPlistPath(path)); const print = yield* runCommand("launchctl", ["print", serviceTarget(uid)]); const running = print.code === 0; return { platform: "darwin" as const, registered, running, pid: running ? parseLaunchctlPid(print.stdout) : null, detail: [], }; }), restart: () => Effect.gen(function* () { const uid = currentUid(); const result = yield* runCommand("launchctl", ["kickstart", "-k", serviceTarget(uid)]); if (result.code !== 0) { return yield* Effect.fail( new Error( `launchctl kickstart failed (exit ${result.code}): ${result.stderr.trim() || result.stdout.trim()}`, ), ); } }), }; }; // --------------------------------------------------------------------------- // Linux — systemd --user + lingering (reboot-survival verified in an Ubuntu VM) // --------------------------------------------------------------------------- const systemdUnitDir = (path: Path.Path): string => path.join(homedir(), ".config", "systemd", "user"); const systemdUnitPath = (path: Path.Path): string => path.join(systemdUnitDir(path), `${SERVICE_LABEL}.service`); export interface SystemdUnitOptions { readonly execStart: ReadonlyArray<string>; readonly environment: Record<string, string>; readonly workingDirectory: string; readonly stdoutPath: string; readonly stderrPath: string; } const SYSTEMD_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/; const systemdQuote = (value: string): string => { if (SYSTEMD_BARE_VALUE.test(value)) return value; const escaped = value .replaceAll("\\", "\\\\") .replaceAll('"', '\\"') .replaceAll("\n", "\\n") .replaceAll("\r", "\\r") .replaceAll("\t", "\\t"); return `"${escaped}"`; }; /** Render a systemd --user unit. Pure (snapshot-tested). */ export const generateSystemdUnit = (options: SystemdUnitOptions): string => { const execStart = options.execStart.map(systemdQuote).join(" "); const env = Object.entries(options.environment) .map(([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}`) .join("\n"); return `[Unit] Description=Executor supervised daemon After=default.target [Service] Type=simple ExecStart=${execStart} ${env} WorkingDirectory=${systemdQuote(options.workingDirectory)} StandardOutput=${systemdQuote(`append:${options.stdoutPath}`)} StandardError=${systemdQuote(`append:${options.stderrPath}`)} Restart=on-failure RestartSec=5s [Install] WantedBy=default.target `; }; const makeSystemdBackend = (): ServiceBackend => { const unitName = `${SERVICE_LABEL}.service`; return { platform: "linux", automated: true, install: (descriptor) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dataDir = resolveExecutorDataDir(path); const logs = serviceLogDir(path); yield* fs.makeDirectory(systemdUnitDir(path), { recursive: true }); yield* fs.makeDirectory(logs, { recursive: true }); const unit = generateSystemdUnit({ execStart: serviceProgramArguments(descriptor), environment: serviceEnvironment(descriptor, dataDir), workingDirectory: dataDir, stdoutPath: path.join(logs, "daemon.log"), stderrPath: path.join(logs, "daemon.error.log"), }); yield* fs.writeFileString(systemdUnitPath(path), unit, { mode: 0o600 }); // `systemctl --user` needs XDG_RUNTIME_DIR to reach the user bus. Supply // it if the caller's environment lacks it (e.g. a non-login shell) so // install is robust regardless of how it was invoked. const username = userInfo().username; const sdEnv = {