fix: persist auth profile env refs for daemon install

This commit is contained in:
Peter Steinberger 2026-03-13 19:50:00 +00:00
parent 549cb65ba4
commit ba9fb4d994
No known key found for this signature in database
2 changed files with 106 additions and 0 deletions

View File

@ -1,6 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
loadAuthProfileStoreForSecretsRuntime: vi.fn(),
resolvePreferredNodePath: vi.fn(),
resolveGatewayProgramArguments: vi.fn(),
resolveSystemNodeInfo: vi.fn(),
@ -8,6 +9,10 @@ const mocks = vi.hoisted(() => ({
buildServiceEnvironment: vi.fn(),
}));
vi.mock("../agents/auth-profiles.js", () => ({
loadAuthProfileStoreForSecretsRuntime: mocks.loadAuthProfileStoreForSecretsRuntime,
}));
vi.mock("../daemon/runtime-paths.js", () => ({
resolvePreferredNodePath: mocks.resolvePreferredNodePath,
resolveSystemNodeInfo: mocks.resolveSystemNodeInfo,
@ -63,6 +68,10 @@ function mockNodeGatewayPlanFixture(
programArguments: ["node", "gateway"],
workingDirectory,
});
mocks.loadAuthProfileStoreForSecretsRuntime.mockReturnValue({
version: 1,
profiles: {},
});
mocks.resolveSystemNodeInfo.mockResolvedValue({
path: "/opt/node",
version,
@ -232,6 +241,67 @@ describe("buildGatewayInstallPlan", () => {
expect(plan.environment.HOME).toBe("/Users/service");
expect(plan.environment.OPENCLAW_PORT).toBe("3000");
});
it("merges env-backed auth-profile refs into the service environment", async () => {
mockNodeGatewayPlanFixture({
serviceEnvironment: {
OPENCLAW_PORT: "3000",
},
});
mocks.loadAuthProfileStoreForSecretsRuntime.mockReturnValue({
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
"anthropic:default": {
type: "token",
provider: "anthropic",
tokenRef: { source: "env", provider: "default", id: "ANTHROPIC_TOKEN" },
},
},
});
const plan = await buildGatewayInstallPlan({
env: {
OPENAI_API_KEY: "sk-openai-test", // pragma: allowlist secret
ANTHROPIC_TOKEN: "ant-test-token",
},
port: 3000,
runtime: "node",
});
expect(plan.environment.OPENAI_API_KEY).toBe("sk-openai-test");
expect(plan.environment.ANTHROPIC_TOKEN).toBe("ant-test-token");
});
it("skips unresolved auth-profile env refs", async () => {
mockNodeGatewayPlanFixture({
serviceEnvironment: {
OPENCLAW_PORT: "3000",
},
});
mocks.loadAuthProfileStoreForSecretsRuntime.mockReturnValue({
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
},
});
const plan = await buildGatewayInstallPlan({
env: {},
port: 3000,
runtime: "node",
});
expect(plan.environment.OPENAI_API_KEY).toBeUndefined();
});
});
describe("gatewayInstallErrorHint", () => {

View File

@ -1,3 +1,7 @@
import {
loadAuthProfileStoreForSecretsRuntime,
type AuthProfileStore,
} from "../agents/auth-profiles.js";
import { formatCliCommand } from "../cli/command-format.js";
import { collectConfigServiceEnvVars } from "../config/env-vars.js";
import type { OpenClawConfig } from "../config/types.js";
@ -19,6 +23,33 @@ export type GatewayInstallPlan = {
environment: Record<string, string | undefined>;
};
function collectAuthProfileServiceEnvVars(params: {
env: Record<string, string | undefined>;
authStore?: AuthProfileStore;
}): Record<string, string> {
const authStore = params.authStore ?? loadAuthProfileStoreForSecretsRuntime();
const entries: Record<string, string> = {};
for (const credential of Object.values(authStore.profiles)) {
const ref =
credential.type === "api_key"
? credential.keyRef
: credential.type === "token"
? credential.tokenRef
: undefined;
if (!ref || ref.source !== "env") {
continue;
}
const value = params.env[ref.id]?.trim();
if (!value) {
continue;
}
entries[ref.id] = value;
}
return entries;
}
export async function buildGatewayInstallPlan(params: {
env: Record<string, string | undefined>;
port: number;
@ -28,6 +59,7 @@ export async function buildGatewayInstallPlan(params: {
warn?: DaemonInstallWarnFn;
/** Full config to extract env vars from (env vars + inline env keys). */
config?: OpenClawConfig;
authStore?: AuthProfileStore;
}): Promise<GatewayInstallPlan> {
const { devMode, nodePath } = await resolveDaemonInstallRuntimeInputs({
env: params.env,
@ -61,6 +93,10 @@ export async function buildGatewayInstallPlan(params: {
// Config env vars are added first so service-specific vars take precedence.
const environment: Record<string, string | undefined> = {
...collectConfigServiceEnvVars(params.config),
...collectAuthProfileServiceEnvVars({
env: params.env,
authStore: params.authStore,
}),
};
Object.assign(environment, serviceEnvironment);