refactor: centralize node startup tls planning

This commit is contained in:
Peter Steinberger
2026-03-21 15:57:44 -07:00
parent 5b31b3400e
commit 29b165e456
9 changed files with 179 additions and 49 deletions

View File

@@ -1,8 +1,8 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { captureFullEnv } from "../../test-utils/env.js";
import type { DaemonActionResponse } from "./response.js";
import { captureFullEnv } from "../../test-utils/env.js";
const resolveAutoNodeExtraCaCertsMock = vi.hoisted(() => vi.fn());
const resolveNodeStartupTlsEnvironmentMock = vi.hoisted(() => vi.fn());
const loadConfigMock = vi.hoisted(() => vi.fn());
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
const resolveGatewayPortMock = vi.hoisted(() => vi.fn(() => 18789));
@@ -51,8 +51,8 @@ const service = vi.hoisted(() => ({
readRuntime: vi.fn(async () => ({ status: "stopped" as const })),
}));
vi.mock("../../bootstrap/node-extra-ca-certs.js", () => ({
resolveAutoNodeExtraCaCerts: resolveAutoNodeExtraCaCertsMock,
vi.mock("../../bootstrap/node-startup-env.js", () => ({
resolveNodeStartupTlsEnvironment: resolveNodeStartupTlsEnvironmentMock,
}));
vi.mock("../../config/config.js", () => ({
@@ -156,7 +156,7 @@ const envSnapshot = captureFullEnv();
describe("runDaemonInstall", () => {
beforeEach(() => {
loadConfigMock.mockReset();
resolveAutoNodeExtraCaCertsMock.mockReset();
resolveNodeStartupTlsEnvironmentMock.mockReset();
readConfigFileSnapshotMock.mockReset();
resolveGatewayPortMock.mockClear();
writeConfigFileMock.mockReset();
@@ -198,7 +198,10 @@ describe("runDaemonInstall", () => {
installDaemonServiceAndEmitMock.mockResolvedValue(undefined);
service.isLoaded.mockResolvedValue(false);
service.readCommand.mockResolvedValue(null);
resolveAutoNodeExtraCaCertsMock.mockReturnValue(undefined);
resolveNodeStartupTlsEnvironmentMock.mockReturnValue({
NODE_EXTRA_CA_CERTS: undefined,
NODE_USE_SYSTEM_CA: undefined,
});
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.CLAWDBOT_GATEWAY_TOKEN;
});
@@ -300,7 +303,10 @@ describe("runDaemonInstall", () => {
it("returns already-installed when the service already has the expected TLS env", async () => {
service.isLoaded.mockResolvedValue(true);
resolveAutoNodeExtraCaCertsMock.mockReturnValue("/etc/ssl/certs/ca-certificates.crt");
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: {
@@ -316,7 +322,10 @@ describe("runDaemonInstall", () => {
it("reinstalls when an existing service is missing the nvm TLS CA bundle", async () => {
service.isLoaded.mockResolvedValue(true);
resolveAutoNodeExtraCaCertsMock.mockReturnValue("/etc/ssl/certs/ca-certificates.crt");
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: {},
@@ -329,11 +338,13 @@ describe("runDaemonInstall", () => {
it("reinstalls when the installed service still runs from nvm even if the installer runtime does not", async () => {
service.isLoaded.mockResolvedValue(true);
resolveAutoNodeExtraCaCertsMock.mockImplementation(({ execPath }) =>
typeof execPath === "string" && execPath.includes("/.nvm/")
? "/etc/ssl/certs/ca-certificates.crt"
: undefined,
);
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.18.0/bin/node", "dist/entry.js"],
environment: {},
@@ -342,7 +353,7 @@ describe("runDaemonInstall", () => {
await runDaemonInstall({ json: true });
expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1);
expect(resolveAutoNodeExtraCaCertsMock).toHaveBeenCalledWith(
expect(resolveNodeStartupTlsEnvironmentMock).toHaveBeenCalledWith(
expect.objectContaining({
execPath: "/home/test/.nvm/versions/node/v22.18.0/bin/node",
}),

View File

@@ -1,4 +1,6 @@
import { resolveAutoNodeExtraCaCerts } from "../../bootstrap/node-extra-ca-certs.js";
import type { DaemonInstallOptions } from "./types.js";
import type { DaemonInstallOptions } from "./types.js";
import { resolveNodeStartupTlsEnvironment } from "../../bootstrap/node-startup-env.js";
import { buildGatewayInstallPlan } from "../../commands/daemon-install-helpers.js";
import {
DEFAULT_GATEWAY_DAEMON_RUNTIME,
@@ -16,7 +18,6 @@ import {
failIfNixDaemonInstallMode,
parsePort,
} from "./shared.js";
import type { DaemonInstallOptions } from "./types.js";
export async function runDaemonInstall(opts: DaemonInstallOptions) {
const { json, stdout, warnings, emit, fail } = createDaemonInstallActionContext(opts.json);
@@ -146,14 +147,15 @@ async function gatewayServiceNeedsAutoNodeExtraCaCertsRefresh(params: {
}
const currentEnvironment = currentCommand.environment ?? {};
const currentNodeExtraCaCerts = currentEnvironment.NODE_EXTRA_CA_CERTS?.trim();
const expectedNodeExtraCaCerts = resolveAutoNodeExtraCaCerts({
const expectedNodeExtraCaCerts = resolveNodeStartupTlsEnvironment({
env: {
...params.env,
...currentEnvironment,
NODE_EXTRA_CA_CERTS: undefined,
},
execPath: currentExecPath,
});
includeDarwinDefaults: false,
}).NODE_EXTRA_CA_CERTS;
if (!expectedNodeExtraCaCerts) {
return false;
}