972 lines · 39.3 KB · TypeScript
RawDownload
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("&", "&amp;")    .replaceAll("<", "&lt;")    .replaceAll(">", "&gt;")    .replaceAll('"', "&quot;")    .replaceAll("'", "&apos;"); const xmlUnescape = (value: string): string =>  value    .replaceAll("&apos;", "'")    .replaceAll("&quot;", '"')    .replaceAll("&gt;", ">")    .replaceAll("&lt;", "<")    .replaceAll("&amp;", "&"); // ---------------------------------------------------------------------------// 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",  ] 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 = {          XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${currentUid()}`,        };        yield* runCommand("systemctl", ["--user", "daemon-reload"], sdEnv).pipe(Effect.ignore);        const enable = yield* runCommand(          "systemctl",          ["--user", "enable", "--now", unitName],          sdEnv,        );        if (enable.code !== 0) {          return yield* Effect.fail(            new Error(

Lines 1–500 of 972.