Download src/cli/daemon-cli/install.reinstall.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/cli/daemon-cli/install.reinstall.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/cli/daemon-cli/install.reinstall.test.ts
-
curl -L -o install.reinstall.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/cli/daemon-cli/install.reinstall.test.ts
15.6 kB
| import "./install.test-support.js"; | |
| import { describe, expect, it } from "vitest"; | |
| import { resolveTestNodeExecPath } from "../../test-utils/node-process.js"; | |
| import { createInstallPlanFixture, nodeProbeOutput } from "./install.test-helpers.js"; | |
| const { | |
| actionState, | |
| buildGatewayInstallPlanMock, | |
| expectFields, | |
| expectLastEmittedResult, | |
| installDaemonServiceAndEmitMock, | |
| readConfigFileSnapshotMock, | |
| readFirstInstallPlanArg, | |
| readFirstNodeStartupTlsEnvironmentArg, | |
| replaceConfigFileMock, | |
| resolveNodeStartupTlsEnvironmentMock, | |
| runDaemonInstall, | |
| runExecMock, | |
| service, | |
| setupInstallTests, | |
| } = await import("./install.test-support.js"); | |
| describe("runDaemonInstall reinstall", () => { | |
| setupInstallTests(); | |
| it.each([ | |
| { mode: "local", installedOverride: false, plannedOverride: false }, | |
| { mode: "remote", installedOverride: true, plannedOverride: true }, | |
| { mode: "local", installedOverride: true, plannedOverride: false }, | |
| ])( | |
| "refreshes only changed service start mode with $mode primary", | |
| async ({ mode, installedOverride, plannedOverride }) => { | |
| service.isLoaded.mockResolvedValue(true); | |
| readConfigFileSnapshotMock.mockResolvedValue({ | |
| valid: true, | |
| sourceConfig: { gateway: { mode, auth: { mode: "token" } } }, | |
| }); | |
| const command = (override: boolean) => | |
| ["openclaw", "gateway", "run"].concat(override ? ["--allow-unconfigured"] : []); | |
| const environment = { NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt" }; | |
| resolveNodeStartupTlsEnvironmentMock.mockReturnValue(environment); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: command(installedOverride), | |
| environment, | |
| }); | |
| buildGatewayInstallPlanMock.mockResolvedValue({ | |
| programArguments: command(plannedOverride), | |
| environment, | |
| workingDirectory: "/tmp", | |
| }); | |
| await runDaemonInstall({ json: true }); | |
| expect(actionState.failed).toEqual([]); | |
| if (installedOverride !== plannedOverride) { | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledOnce(); | |
| } else { | |
| expect(installDaemonServiceAndEmitMock).not.toHaveBeenCalled(); | |
| expect(actionState.emitted.at(-1)).toMatchObject({ result: "already-installed" }); | |
| } | |
| }, | |
| ); | |
| it.each([ | |
| { failure: "probe", message: "openclaw gateway install --force" }, | |
| { failure: "no-replacement", message: "No supported Node runtime is available" }, | |
| { failure: "sealed-definition", message: "SERVICE_DEFINITION_UNKNOWN" }, | |
| ])( | |
| "refuses runtime repair on $failure without claiming success", | |
| async ({ failure, message }) => { | |
| service.isLoaded.mockResolvedValue(true); | |
| const oldNode = failure === "probe" ? resolveTestNodeExecPath() : "/opt/old/bin/node"; | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: [oldNode, "/opt/openclaw/dist/index.js", "gateway"], | |
| }); | |
| runExecMock.mockImplementation(async (file: string) => { | |
| if (failure === "probe") { | |
| throw new Error("runtime probe timed out"); | |
| } | |
| return nodeProbeOutput( | |
| failure === "sealed-definition" && file !== oldNode ? "26.8.1" : "22.23.1", | |
| ); | |
| }); | |
| if (failure === "sealed-definition") { | |
| service.readDefinitionMutationCapability.mockRejectedValue(new Error("sealed")); | |
| } | |
| await runDaemonInstall({ json: true }); | |
| expect(actionState.failed[0]?.message).toContain(message); | |
| expect(actionState.emitted).toEqual([]); | |
| expect(installDaemonServiceAndEmitMock).not.toHaveBeenCalled(); | |
| }, | |
| ); | |
| it("reinstalls when the loaded service still embeds OPENCLAW_GATEWAY_TOKEN", async () => { | |
| const programArguments = [ | |
| "/usr/bin/node", | |
| "--max-old-space-size=24576", | |
| "--require=/tmp/service-preload.js", | |
| "/usr/local/bin/openclaw", | |
| "gateway", | |
| ]; | |
| service.isLoaded.mockResolvedValue(true); | |
| const managedDefinition = { | |
| programArguments, | |
| environment: { | |
| OPENCLAW_GATEWAY_TOKEN: "stale-service-token", | |
| }, | |
| }; | |
| const existingCommand = { | |
| ...managedDefinition, | |
| environment: { NODE_OPTIONS: "--max-old-space-size=512" }, | |
| managedDefinition, | |
| managedOverrides: { environment: { keys: ["NODE_OPTIONS"] } }, | |
| }; | |
| service.readCommand.mockResolvedValue(existingCommand as never); | |
| await runDaemonInstall({ json: true }); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| for (const [options] of buildGatewayInstallPlanMock.mock.calls) { | |
| expect(options).toEqual(expect.objectContaining({ existingCommand })); | |
| } | |
| expect(actionState.warnings).toContain( | |
| "Gateway service OPENCLAW_GATEWAY_TOKEN differs from the current install plan; refreshing the install.", | |
| ); | |
| }); | |
| it("returns already-installed when the embedded gateway token matches the install plan", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| environment: { | |
| OPENCLAW_GATEWAY_TOKEN: "durable-token", | |
| }, | |
| } as never); | |
| buildGatewayInstallPlanMock.mockResolvedValueOnce({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| workingDirectory: "/tmp", | |
| environment: { | |
| OPENCLAW_GATEWAY_TOKEN: "durable-token", | |
| }, | |
| }); | |
| await runDaemonInstall({ json: true }); | |
| expect(buildGatewayInstallPlanMock).toHaveBeenCalledTimes(1); | |
| expect(replaceConfigFileMock).not.toHaveBeenCalled(); | |
| expect(installDaemonServiceAndEmitMock).not.toHaveBeenCalled(); | |
| expectLastEmittedResult("already-installed"); | |
| }); | |
| it("preserves managed base wrapper, environment, and provenance during forced reinstall", async () => { | |
| for (const key of ["OPENAI_API_KEY", "OPENCLAW_WRAPPER"]) { | |
| delete process.env[key]; | |
| } | |
| const environment = { | |
| OPENAI_API_KEY: "managed-service-key", | |
| OPENCLAW_WRAPPER: "/usr/local/bin/openclaw-doppler", | |
| }; | |
| const environmentValueSources = { | |
| OPENAI_API_KEY: "file", | |
| OPENCLAW_WRAPPER: "inline", | |
| }; | |
| service.isLoaded.mockResolvedValue(false); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["/operator/drop-in-wrapper", "gateway", "run"], | |
| environment: { | |
| OPENAI_API_KEY: "operator-drop-in-key", | |
| OPENCLAW_WRAPPER: "/operator/drop-in-wrapper", | |
| }, | |
| environmentValueSources: { OPENAI_API_KEY: "inline" }, | |
| managedDefinition: { | |
| programArguments: [environment.OPENCLAW_WRAPPER, "gateway", "run"], | |
| environment, | |
| environmentValueSources, | |
| }, | |
| } as never); | |
| await runDaemonInstall({ json: true, force: true }); | |
| expect(service.readCommand).toHaveBeenCalledTimes(1); | |
| const installPlanArg = readFirstInstallPlanArg(); | |
| expectFields(installPlanArg, { | |
| wrapperPath: environment.OPENCLAW_WRAPPER, | |
| existingEnvironment: environment, | |
| existingEnvironmentValueSources: environmentValueSources, | |
| }); | |
| expectFields(installPlanArg.env, environment); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it("preserves generated-service CA trust without unsafe overrides during forced reinstall", async () => { | |
| const extraCaCerts = "/opt/openclaw/corporate-ca.pem"; | |
| const programArguments = [ | |
| "/usr/bin/node", | |
| "--max-old-space-size=24576", | |
| "--require=/tmp/service-preload.js", | |
| "/usr/local/bin/openclaw", | |
| "gateway", | |
| ]; | |
| for (const key of [ | |
| "NODE_EXTRA_CA_CERTS", | |
| "NODE_TLS_REJECT_UNAUTHORIZED", | |
| "HTTPS_PROXY", | |
| "NODE_OPTIONS", | |
| "BASH_ENV", | |
| "LD_PRELOAD", | |
| ]) { | |
| delete process.env[key]; | |
| } | |
| service.isLoaded.mockResolvedValue(true); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments, | |
| environment: { | |
| NODE_EXTRA_CA_CERTS: extraCaCerts, | |
| NODE_TLS_REJECT_UNAUTHORIZED: "0", | |
| HTTPS_PROXY: "https://attacker.invalid", | |
| NODE_OPTIONS: "--require /tmp/untrusted.js", | |
| BASH_ENV: "/tmp/untrusted.sh", | |
| LD_PRELOAD: "/tmp/untrusted.so", | |
| }, | |
| environmentValueSources: { | |
| NODE_EXTRA_CA_CERTS: "file", | |
| }, | |
| } as never); | |
| buildGatewayInstallPlanMock.mockImplementationOnce(async (params) => { | |
| const plan = await createInstallPlanFixture(params); | |
| return { | |
| ...plan, | |
| environment: { | |
| ...plan.environment, | |
| NODE_EXTRA_CA_CERTS: params?.env?.NODE_EXTRA_CA_CERTS ?? "/etc/ssl/cert.pem", | |
| }, | |
| }; | |
| }); | |
| installDaemonServiceAndEmitMock.mockImplementationOnce(async (params?: unknown) => { | |
| await (params as { install: () => Promise<void> }).install(); | |
| }); | |
| await runDaemonInstall({ json: true, force: true }); | |
| const installPlanArg = readFirstInstallPlanArg(); | |
| expect(installPlanArg.existingCommand).toEqual(expect.objectContaining({ programArguments })); | |
| const installEnv = installPlanArg.env as Record<string, string | undefined>; | |
| expect(installEnv.NODE_EXTRA_CA_CERTS).toBe(extraCaCerts); | |
| expect(installEnv.NODE_TLS_REJECT_UNAUTHORIZED).toBeUndefined(); | |
| expect(installEnv.HTTPS_PROXY).toBeUndefined(); | |
| expect(installEnv.NODE_OPTIONS).toBeUndefined(); | |
| expect(installEnv.BASH_ENV).toBeUndefined(); | |
| expect(installEnv.LD_PRELOAD).toBeUndefined(); | |
| expectFields(installPlanArg.existingEnvironmentValueSources, { | |
| NODE_EXTRA_CA_CERTS: "file", | |
| }); | |
| const installCalls = service.install.mock.calls as unknown as Array< | |
| [{ environment?: Record<string, string | undefined> }] | |
| >; | |
| expect(installCalls[0]?.[0].environment?.NODE_EXTRA_CA_CERTS).toBe(extraCaCerts); | |
| }); | |
| it("reinstalls when wrapper command matches but wrapper env is missing", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["/usr/local/bin/openclaw-doppler", "gateway", "run"], | |
| environment: {}, | |
| } as never); | |
| await runDaemonInstall({ | |
| json: true, | |
| wrapper: "/usr/local/bin/openclaw-doppler", | |
| }); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| expect(actionState.warnings).toContain( | |
| "Gateway service OPENCLAW_WRAPPER differs from the current wrapper install plan; refreshing the install.", | |
| ); | |
| }); | |
| it("reinstalls when the embedded gateway token differs from the install plan", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| environment: { | |
| OPENCLAW_GATEWAY_TOKEN: "stale-service-token", | |
| }, | |
| } as never); | |
| buildGatewayInstallPlanMock.mockResolvedValueOnce({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| workingDirectory: "/tmp", | |
| environment: { | |
| OPENCLAW_GATEWAY_TOKEN: "fresh-token", | |
| }, | |
| }); | |
| await runDaemonInstall({ json: true }); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| expect(actionState.warnings).toContain( | |
| "Gateway service OPENCLAW_GATEWAY_TOKEN differs from the current install plan; refreshing the install.", | |
| ); | |
| }); | |
| it.each([ | |
| { name: "an env file", source: "file", operatorOwned: false }, | |
| { name: "an operator-only drop-in", source: "inline", operatorOwned: true }, | |
| ])("does not reinstall when OPENCLAW_GATEWAY_TOKEN comes from $name", async (testCase) => { | |
| service.isLoaded.mockResolvedValue(true); | |
| const programArguments = ["openclaw", "gateway", "run"]; | |
| service.readCommand.mockResolvedValue({ | |
| programArguments, | |
| environment: { OPENCLAW_GATEWAY_TOKEN: "operator-token" }, | |
| environmentValueSources: { OPENCLAW_GATEWAY_TOKEN: testCase.source }, | |
| ...(testCase.operatorOwned && { | |
| managedDefinition: { programArguments, environment: {} }, | |
| }), | |
| } as never); | |
| await runDaemonInstall({ json: true }); | |
| expect(buildGatewayInstallPlanMock).not.toHaveBeenCalled(); | |
| expect(installDaemonServiceAndEmitMock).not.toHaveBeenCalled(); | |
| expectLastEmittedResult("already-installed"); | |
| }); | |
| it("reinstalls when an existing service is missing the nvm TLS CA bundle", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| resolveNodeStartupTlsEnvironmentMock.mockReturnValue({ | |
| NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt", | |
| NODE_USE_SYSTEM_CA: undefined, | |
| }); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| environment: {}, | |
| } as never); | |
| await runDaemonInstall({ json: true }); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it("reinstalls when the installed service still runs from nvm even if the installer runtime does not", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| resolveNodeStartupTlsEnvironmentMock.mockImplementation(({ execPath }) => ({ | |
| NODE_EXTRA_CA_CERTS: | |
| typeof execPath === "string" && execPath.includes("/.nvm/") | |
| ? "/etc/ssl/certs/ca-certificates.crt" | |
| : undefined, | |
| NODE_USE_SYSTEM_CA: undefined, | |
| })); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["/home/test/.nvm/versions/node/v22.19.0/bin/node", "dist/entry.js"], | |
| environment: {}, | |
| } as never); | |
| await runDaemonInstall({ json: true }); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| expectFields(readFirstNodeStartupTlsEnvironmentArg(), { | |
| execPath: "/home/test/.nvm/versions/node/v22.19.0/bin/node", | |
| }); | |
| }); | |
| it("reuses env-backed service secrets during forced reinstall when the current shell is missing them", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| environment: { | |
| OPENAI_API_KEY: "service-openai-key", | |
| }, | |
| } as never); | |
| delete process.env.OPENAI_API_KEY; | |
| process.env.NODE_OPTIONS = "--require /tmp/untrusted.js"; | |
| await runDaemonInstall({ json: true, force: true }); | |
| expectFields(readFirstInstallPlanArg().env, { | |
| OPENAI_API_KEY: "service-openai-key", | |
| }); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it("does not reuse stale service control env during forced reinstall", async () => { | |
| service.isLoaded.mockResolvedValue(true); | |
| service.readCommand.mockResolvedValue({ | |
| programArguments: ["openclaw", "gateway", "run"], | |
| environment: { | |
| OPENCLAW_STATE_DIR: "/tmp/openclaw-doctor-manual", | |
| OPENCLAW_CONFIG_PATH: "/tmp/openclaw-doctor-manual/openclaw.json", | |
| OPENCLAW_GATEWAY_TOKEN: "stale-service-token", | |
| PATH: "/tmp/doctor-bin:/usr/bin", | |
| NODE_OPTIONS: "--require /tmp/evil.js", | |
| OPENAI_API_KEY: "service-openai-key", | |
| }, | |
| } as never); | |
| delete process.env.OPENAI_API_KEY; | |
| await runDaemonInstall({ json: true, force: true }); | |
| expectFields(readFirstInstallPlanArg().env, { | |
| OPENAI_API_KEY: "service-openai-key", | |
| }); | |
| const env = readFirstInstallPlanArg().env as Record<string, string | undefined>; | |
| expect(env.OPENCLAW_STATE_DIR).toBeUndefined(); | |
| expect(env.OPENCLAW_CONFIG_PATH).toBeUndefined(); | |
| expect(env.OPENCLAW_GATEWAY_TOKEN).toBeUndefined(); | |
| expect(env.NODE_OPTIONS).toBeUndefined(); | |
| expect(env.PATH).not.toContain("/tmp/doctor-bin"); | |
| expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1); | |
| }); | |
| }); | |