677 lines · 26.0 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { expect, it } from "@effect/vitest";import {  HostProcessArguments,  HostProcessExecutablePath,  HostProcessPlatform,  HostProcessUserId,} from "@t3tools/shared/hostProcess";import * as ConfigProvider from "effect/ConfigProvider";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as ProcessRunner from "../processRunner.ts";import * as BootService from "./bootService.ts";import { pinnedRuntimePaths } from "./pinnedRuntime.ts";import {  parseServiceState,  SERVICE_LAUNCHER_PROTOCOL,  serviceStateHasPendingUpdate,} from "./serviceProtocol.ts"; it("keeps systemd pinned to the stable launcher rather than a versioned server", () => {  const unit = BootService.renderBootServiceUnit({    nodePath: "/usr/bin/node",    launcherPath: "/home/theo/.t3/runtime/service-launcher.mjs",    baseDir: "/home/theo/.t3",    logPath: "/home/theo/.t3/userdata/logs/boot-service.log",    unitPath: "/home/theo/.config/systemd/user/t3code.service",  });   expect(unit).toContain("ExecStart=/usr/bin/node /home/theo/.t3/runtime/service-launcher.mjs");  expect(unit).toContain("KillMode=mixed");  expect(unit).not.toContain("versions/1.2.3");}); it("survives the kernel OOM-killing a greedy agent child", () => {  const unit = BootService.renderBootServiceUnit({    nodePath: "/usr/bin/node",    launcherPath: "/home/theo/.t3/runtime/service-launcher.mjs",    baseDir: "/home/theo/.t3",    logPath: "/home/theo/.t3/userdata/logs/boot-service.log",    unitPath: "/home/theo/.config/systemd/user/t3code.service",  });   expect(unit).toContain("OOMPolicy=continue");}); const macPlan = {  nodePath: "/opt/homebrew/bin/node",  launcherPath: "/Users/theo/.t3/runtime/service-launcher.mjs",  baseDir: "/Users/theo/.t3",  logPath: "/Users/theo/.t3/userdata/logs/boot-service.log",  unitPath: "/Users/theo/Library/LaunchAgents/com.t3tools.t3code.service.plist",};const macInstallerPath =  "/opt/homebrew/bin:/Users/theo/.npm-global/bin:/Users/theo/.nvm/versions/node/v22.16.0/bin:/usr/bin:/bin";const macRenderOptions = { homeDir: "/Users/theo", environmentPath: macInstallerPath }; it("keeps launchd pinned to the stable launcher rather than a versioned server", () => {  const plist = BootService.renderBootServicePlist(macPlan, macRenderOptions);   expect(plist).toContain("<string>/opt/homebrew/bin/node</string>");  expect(plist).toContain("<string>/Users/theo/.t3/runtime/service-launcher.mjs</string>");  expect(plist).not.toContain("versions/1.2.3");}); it("preserves the installer's provider search path in the launch agent", () => {  const plist = BootService.renderBootServicePlist(macPlan, macRenderOptions);   expect(plist).toContain(`    <key>PATH</key>\n    <string>${macInstallerPath}</string>`);}); it("restarts the launch agent on the systemd cadence", () => {  const plist = BootService.renderBootServicePlist(macPlan, macRenderOptions);   expect(plist).toContain("<key>RunAtLoad</key>\n  <true/>");  expect(plist).toContain("<key>KeepAlive</key>\n  <true/>");  expect(plist).toContain("<key>ThrottleInterval</key>\n  <integer>5</integer>");  expect(plist).toContain("<key>ExitTimeOut</key>\n  <integer>90</integer>");}); it("appends both stdio streams to the boot service log", () => {  const plist = BootService.renderBootServicePlist(macPlan, macRenderOptions);   expect(plist).toContain(    "<key>StandardOutPath</key>\n  <string>/Users/theo/.t3/userdata/logs/boot-service.log</string>",  );  expect(plist).toContain(    "<key>StandardErrorPath</key>\n  <string>/Users/theo/.t3/userdata/logs/boot-service.log</string>",  );}); it("escapes XML in host paths", () => {  const plist = BootService.renderBootServicePlist(    { ...macPlan, baseDir: "/Users/theo/T3 & <Co>" },    { homeDir: "/Users/theo", environmentPath: "/Users/theo/Tools & <Scripts>:/usr/bin" },  );   expect(plist).toContain("<string>/Users/theo/T3 &amp; &lt;Co&gt;</string>");  expect(plist).toContain("<string>/Users/theo/Tools &amp; &lt;Scripts&gt;:/usr/bin</string>");}); const makeHarness = Effect.fn("test.make_boot_service_harness")(function* (  platform: NodeJS.Platform = "linux",  usePinnedLauncher = false,  installerPath = macInstallerPath,) {  const fs = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-boot-service-test-" });  const baseDir = path.join(home, ".t3");  const sourceLauncher = path.join(home, "service-launcher.mjs");  const statePath = path.join(baseDir, "runtime", "service-state.json");  yield* fs.writeFileString(sourceLauncher, "export {};\n");  const runtime = pinnedRuntimePaths(path, baseDir, "1.2.3");  yield* fs.makeDirectory(path.dirname(runtime.entryPath), { recursive: true });  yield* fs.writeFileString(runtime.entryPath, "export {};\n");  yield* fs.writeFileString(    path.join(path.dirname(runtime.entryPath), "service-launcher.mjs"),    "export const source = 'pinned runtime';\n",  );  yield* fs.writeFileString(runtime.sentinelPath, "1.2.3\n");   const commands: string[] = [];  const timeouts = new Map<string, unknown>();  const control: {    failCommand: string | undefined;    stateAfterStop?: string;    linger: string;    enabled: boolean;    active: boolean;  } = {    failCommand: undefined,    linger: "yes",    enabled: true,    active: true,  };  const runner = ProcessRunner.ProcessRunner.of({    run: Effect.fn("test.run_boot_service_command")(function* (      input: ProcessRunner.ProcessRunInput,    ) {      const command = `${input.command} ${input.args.join(" ")}`;      commands.push(command);      timeouts.set(command, input.timeout);      const failed = command === control.failCommand;      if (!failed && command === "loginctl enable-linger --no-ask-password 501")        control.linger = "yes";      if (!failed && command === "systemctl --user enable t3code.service") control.enabled = true;      if (!failed && command === "systemctl --user restart t3code.service") control.active = true;      if (        control.stateAfterStop !== undefined &&        (command === "systemctl --user stop t3code.service" ||          command.startsWith("launchctl bootout --wait "))      ) {        yield* fs.writeFileString(statePath, control.stateAfterStop).pipe(Effect.orDie);      }      return {        stdout:          input.args[1] === "--version"            ? "t3 v1.2.3\n"            : input.command === "loginctl" && input.args[0] === "show-user"              ? `${control.linger}\n`              : input.args[1] === "is-enabled"                ? control.enabled                  ? "enabled\n"                  : "disabled\n"                : "",        stderr: "",        code: ChildProcessSpawner.ExitCode(          failed || (input.args[1] === "is-active" && !control.active) ? 1 : 0,        ),        timedOut: false,        stdoutTruncated: false,        stderrTruncated: false,        stdoutInvalidUtf8: false,        stderrInvalidUtf8: false,      };    }),  });  const makeService = (environmentPath = installerPath) =>    BootService.make({      baseDir,      logsDir: path.join(baseDir, "userdata", "logs"),      cliVersion: "1.2.3",      host: {        execPath: "/usr/bin/node",        ...(usePinnedLauncher ? {} : { launcherSourcePath: sourceLauncher }),      },    }).pipe(      Effect.provideService(ProcessRunner.ProcessRunner, runner),      Effect.provide(        Layer.mergeAll(          Layer.succeed(HostProcessPlatform, platform),          Layer.succeed(HostProcessUserId, 501),          Layer.succeed(HostProcessExecutablePath, "/usr/bin/node"),          Layer.succeed(HostProcessArguments, ["/usr/bin/node", path.join(home, "bin.mjs")]),          ConfigProvider.layer(            ConfigProvider.fromEnv({              env: { HOME: home, ...(environmentPath === "" ? {} : { PATH: environmentPath }) },            }),          ),        ),      ),    );  const service = yield* makeService();  return { service, makeService, fs, statePath, commands, timeouts, control, runtime };}); it.layer(NodeServices.layer)("boot service install", (it) => {  it.effect(    "fails before installing files or validating a runtime when lingering needs an administrator",    () =>      Effect.gen(function* () {        const { service, fs, statePath, commands, control, runtime } = yield* makeHarness();        const before = yield* service.status;        control.linger = "no";        control.failCommand = "loginctl enable-linger --no-ask-password 501";        yield* fs.remove(runtime.sentinelPath);         const error = yield* service.install().pipe(Effect.flip);         expect(error).toMatchObject({          _tag: "BootServicePrerequisiteError",          problem: "linger-disabled",        });        expect(error.message).toContain('sudo loginctl enable-linger "$(id -un)"');        expect(error.message).toContain("last login session ends");        expect(yield* fs.exists(before.unitPath)).toBe(false);        expect(yield* fs.exists(statePath)).toBe(false);        expect(          commands.some((command) => command.startsWith("npm ") || command.includes("--version")),        ).toBe(false);        expect(          commands.some(            (command) => command.includes("daemon-reload") || command.includes("restart"),          ),        ).toBe(false);        expect(yield* fs.readFileString(before.logPath)).toContain("[linger-disabled]");      }),  );   it.effect(    "detects a partial install and preserves the running service when repair lacks permission",    () =>      Effect.gen(function* () {        const { service, fs, statePath, commands, control } = yield* makeHarness();        const plan = yield* service.install();        const before = yield* fs.readFileString(statePath);        const unit = yield* fs.readFileString(plan.unitPath);        control.linger = "no";        control.failCommand = "loginctl enable-linger --no-ask-password 501";         expect(yield* service.status).toMatchObject({          current: false,          problems: ["linger-disabled"],        });        commands.length = 0;        expect((yield* service.install().pipe(Effect.flip))._tag).toBe(          "BootServicePrerequisiteError",        );        expect(yield* fs.readFileString(statePath)).toBe(before);        expect(yield* fs.readFileString(plan.unitPath)).toBe(unit);        expect(commands).not.toContain("systemctl --user stop t3code.service");      }),  );   it.effect("enables lingering before installing and repairs stopped or disabled services", () =>    Effect.gen(function* () {      const { service, commands, control } = yield* makeHarness();      control.linger = "no";      yield* service.install();      expect(control.linger).toBe("yes");      expect(commands.indexOf("loginctl enable-linger --no-ask-password 501")).toBeLessThan(        commands.indexOf("systemctl --user daemon-reload"),      );       control.enabled = false;      control.active = false;      expect(yield* service.status).toMatchObject({        current: false,        problems: ["service-disabled", "service-stopped"],      });      yield* service.install();      expect((yield* service.status).current).toBe(true);    }),  );   it.effect.each([    { command: "systemctl --user show-environment", problem: "user-manager-unavailable" },    { command: "loginctl show-user 501 --property=Linger --value", problem: "linger-unavailable" },  ])("reports failed prerequisite probes without installing: $command", ({ command, problem }) =>    Effect.gen(function* () {      const { service, fs, statePath, control } = yield* makeHarness();      control.failCommand = command;      expect(yield* service.install().pipe(Effect.flip)).toMatchObject({        _tag: "BootServicePrerequisiteError",        problem,      });      expect(yield* fs.exists(statePath)).toBe(false);    }),  );   it.effect("installs, reports current state, and uninstalls", () =>    Effect.gen(function* () {      const { service, fs, statePath, commands, timeouts } = yield* makeHarness();      const plan = yield* service.install();       expect(parseServiceState(yield* fs.readFileString(statePath))).toEqual({        protocol: SERVICE_LAUNCHER_PROTOCOL,        activeVersion: "1.2.3",      });      expect(yield* fs.readFileString(plan.launcherPath)).toBe("export {};\n");      expect(yield* service.status).toMatchObject({        current: true,        installedVersion: "1.2.3",      });      // @effect-diagnostics-next-line preferSchemaOverJson:off - fixed launcher-owned test document.      const pendingState = JSON.stringify({        protocol: SERVICE_LAUNCHER_PROTOCOL,        activeVersion: "1.2.3",        update: {          id: "u",          fromVersion: "1.2.3",          targetVersion: "1.2.4",          dbPath: "/tmp/state.sqlite",          status: "pending",        },      });      yield* fs.writeFileString(statePath, pendingState);      expect((yield* service.status).current).toBe(false);      expect(yield* service.uninstall).toBe(true);      expect((yield* service.status).installed).toBe(false);      expect(commands.some((command) => command.startsWith("npm "))).toBe(false);      // The stop can block up to systemd's 90s TimeoutStopSec; the runner's      // 60s default would cancel it mid-shutdown.      expect(timeouts.get("systemctl --user disable --now t3code.service")).toEqual(        Duration.seconds(120),      );    }),  );   it.effect.each(["linux", "darwin"] as const)(    "reports the installed version across launcher protocols on %s",    (platform) =>      Effect.gen(function* () {        const { service, fs, statePath } = yield* makeHarness(platform);        yield* service.install();         for (const protocol of [SERVICE_LAUNCHER_PROTOCOL - 1, SERVICE_LAUNCHER_PROTOCOL + 1]) {          yield* fs.writeFileString(            statePath,            `{"protocol":${protocol},"activeVersion":"1.2.4-nightly.1","update":{"status":"unknown"}}`,          );          expect(yield* service.status).toMatchObject({            current: false,            installedVersion: "1.2.4-nightly.1",          });        }      }),  );   it.effect("reports an unknown version for invalid service state", () =>    Effect.gen(function* () {      const { service, fs, statePath } = yield* makeHarness();      yield* service.install();       for (const stateText of [        "{",        '{"activeVersion":"latest"}',        '{"activeVersion":"1.2"}',        '{"activeVersion":123}',      ]) {        yield* fs.writeFileString(statePath, stateText);        const status = yield* service.status;        expect(status.current).toBe(false);        expect(status.installedVersion).toBeUndefined();      }    }),  );   it.effect.each(["linux", "darwin"] as const)(    "preserves a newer version that finishes updating during stop on %s",    (platform) =>      Effect.gen(function* () {        const { service, fs, statePath, commands, control } = yield* makeHarness(platform);        const plan = yield* service.install();        const launcher = yield* fs.readFileString(plan.launcherPath);        const unit = yield* fs.readFileString(plan.unitPath);        control.stateAfterStop = `{"protocol":${SERVICE_LAUNCHER_PROTOCOL + 1},"activeVersion":"1.2.4"}`;        commands.length = 0;         const error = yield* service.install().pipe(Effect.flip);         expect(error).toMatchObject({          _tag: "BootServiceDowngradeRefusedError",          installedVersion: "1.2.4",          targetVersion: "1.2.3",        });        expect(yield* fs.readFileString(statePath)).toBe(control.stateAfterStop);        expect(yield* fs.readFileString(plan.launcherPath)).toBe(launcher);        expect(yield* fs.readFileString(plan.unitPath)).toBe(unit);        expect(          commands.filter(            (command) =>              command.startsWith(platform === "linux" ? "systemctl " : "launchctl ") &&              !command.includes("show-environment"),          ),        ).toEqual(          platform === "linux"            ? ["systemctl --user stop t3code.service", "systemctl --user restart t3code.service"]            : [                "launchctl bootout --wait gui/501/com.t3tools.t3code.service",                `launchctl bootstrap gui/501 ${plan.unitPath}`,              ],        );      }),  );   it.effect("allows an explicit downgrade", () =>    Effect.gen(function* () {      const { service, fs, statePath } = yield* makeHarness();      yield* service.install();      yield* fs.writeFileString(        statePath,        `{"protocol":${SERVICE_LAUNCHER_PROTOCOL},"activeVersion":"1.2.4"}`,      );       yield* service.install({ allowDowngrade: true });       expect(parseServiceState(yield* fs.readFileString(statePath))?.activeVersion).toBe("1.2.3");      expect((yield* service.status).current).toBe(true);    }),  );   it.effect("repairs versions with equal SemVer precedence without an override", () =>    Effect.gen(function* () {      const { service, fs, statePath } = yield* makeHarness();      yield* service.install();      yield* fs.writeFileString(        statePath,        `{"protocol":${SERVICE_LAUNCHER_PROTOCOL},"activeVersion":"1.2.3+previous-build"}`,      );       yield* service.install();       expect((yield* service.status).current).toBe(true);    }),  );   it.effect("copies the launcher from the prepared pinned runtime", () =>    Effect.gen(function* () {      const { service, fs } = yield* makeHarness("linux", true);      const plan = yield* service.install();       expect(yield* fs.readFileString(plan.launcherPath)).toBe(        "export const source = 'pinned runtime';\n",      );    }),  );   it.effect("restarts an installed service when repair fails", () =>    Effect.gen(function* () {      const { service, commands, control } = yield* makeHarness();      yield* service.install();      commands.length = 0;      control.failCommand = "systemctl --user daemon-reload";       const error = yield* service.install().pipe(Effect.flip);      expect(error._tag).toBe("BootServiceCommandError");      expect(        commands.filter(          (command) => command.startsWith("systemctl ") && !command.includes("show-environment"),        ),      ).toEqual([        "systemctl --user stop t3code.service",        "systemctl --user daemon-reload",        "systemctl --user restart t3code.service",      ]);    }),  );   it.effect("restarts without overwriting a pending remote update", () =>    Effect.gen(function* () {      const { service, fs, statePath, commands } = yield* makeHarness();      yield* service.install();      // @effect-diagnostics-next-line preferSchemaOverJson:off - fixed launcher-owned test document.      const pendingState = JSON.stringify({        protocol: SERVICE_LAUNCHER_PROTOCOL - 1,        activeVersion: "1.2.3",        update: {          id: "remote-update",          fromVersion: "1.2.3",          targetVersion: "1.2.4",          status: "pending",        },      });

Lines 1–500 of 677.