
























@@ -1299,6 +1299,37 @@ describe("stageSystemdService", () => {
12991299});
13001300});
130113011302+it("does not re-stage unresolved inline-and-file values from preserved service env (#88274)", async () => {
1303+await withStageFixture(async ({ env, unitPath, envFilePath }) => {
1304+await fs.writeFile(envFilePath, "LLM_API_KEY=$SECRET_FROM_SHELL\n", {
1305+encoding: "utf8",
1306+mode: 0o600,
1307+});
1308+1309+mockSystemctlStatusOk();
1310+1311+await stageSystemdService({
1312+ env,
1313+stdout: { write: vi.fn() } as unknown as NodeJS.WritableStream,
1314+programArguments: ["/usr/bin/openclaw", "gateway", "run"],
1315+workingDirectory: "/tmp",
1316+environment: {
1317+LLM_API_KEY: "$SECRET_FROM_SHELL",
1318+OPENCLAW_GATEWAY_PORT: "18789",
1319+},
1320+environmentValueSources: {
1321+LLM_API_KEY: "inline-and-file",
1322+},
1323+});
1324+1325+const unit = await fs.readFile(unitPath, "utf8");
1326+expect(unit).not.toContain("EnvironmentFile=");
1327+expect(unit).not.toContain("LLM_API_KEY");
1328+expect(unit).not.toContain("$SECRET_FROM_SHELL");
1329+await expect(fs.access(envFilePath)).rejects.toThrow();
1330+});
1331+});
1332+13021333it("sanitizes file-backed managed values out of the backup unit on re-stage", async () => {
13031334await withStageFixture(async ({ env, unitPath }) => {
13041335await fs.mkdir(path.dirname(unitPath), { recursive: true });
@@ -1492,6 +1523,99 @@ describe("stageSystemdService", () => {
14921523expect(envFile).toContain("LLM_API_KEY=new-value");
14931524});
14941525});
1526+1527+it("removes a stale literal reference on re-stage when state-dir .env now skips that key (#88274)", async () => {
1528+await withStageFixture(async ({ env, stateDir, envFilePath }) => {
1529+// A prior install generated a literal reference for LLM_API_KEY (an unexpanded
1530+// $VAR that dotenv stored verbatim) and an operator-managed provider secret.
1531+await fs.writeFile(
1532+envFilePath,
1533+["LLM_API_KEY=$SECRET_FROM_SHELL", "OPENROUTER_API_KEY=or-operator-key"].join("\n") + "\n",
1534+{ encoding: "utf8", mode: 0o600 },
1535+);
1536+1537+// The state-dir .env still declares LLM_API_KEY but now as an unresolved
1538+// shell reference, so the parser skips it from the managed environment.
1539+await fs.writeFile(path.join(stateDir, ".env"), "LLM_API_KEY=$SECRET_FROM_SHELL\n", "utf8");
1540+1541+mockSystemctlStatusOk();
1542+1543+await stageSystemdService({
1544+ env,
1545+stdout: { write: vi.fn() } as unknown as NodeJS.WritableStream,
1546+programArguments: ["/usr/bin/openclaw", "gateway", "run"],
1547+workingDirectory: "/tmp",
1548+environment: { OPENCLAW_GATEWAY_PORT: "18789" },
1549+});
1550+1551+const envFile = await fs.readFile(envFilePath, "utf8");
1552+// The stale literal reference for the skipped managed key is dropped...
1553+expect(envFile).not.toContain("LLM_API_KEY");
1554+expect(envFile).not.toContain("$SECRET_FROM_SHELL");
1555+// ...while operator-only secrets (never in state-dir .env) are preserved.
1556+expect(envFile).toContain("OPENROUTER_API_KEY=or-operator-key");
1557+});
1558+});
1559+1560+it("removes a stale literal reference after the state-dir .env line is removed (#88274)", async () => {
1561+await withStageFixture(async ({ env, envFilePath }) => {
1562+await fs.writeFile(
1563+envFilePath,
1564+["LLM_API_KEY=$SECRET_FROM_SHELL", "OPENROUTER_API_KEY=or-operator-key"].join("\n") + "\n",
1565+{ encoding: "utf8", mode: 0o600 },
1566+);
1567+1568+mockSystemctlStatusOk();
1569+1570+await stageSystemdService({
1571+ env,
1572+stdout: { write: vi.fn() } as unknown as NodeJS.WritableStream,
1573+programArguments: ["/usr/bin/openclaw", "gateway", "run"],
1574+workingDirectory: "/tmp",
1575+environment: { OPENCLAW_GATEWAY_PORT: "18789" },
1576+});
1577+1578+const envFile = await fs.readFile(envFilePath, "utf8");
1579+expect(envFile).not.toContain("LLM_API_KEY");
1580+expect(envFile).not.toContain("$SECRET_FROM_SHELL");
1581+expect(envFile).toContain("OPENROUTER_API_KEY=or-operator-key");
1582+});
1583+});
1584+1585+it("keeps an operator secret that merely shares a name absent from state-dir .env (#88274)", async () => {
1586+await withStageFixture(async ({ env, stateDir, envFilePath }) => {
1587+// Operator-managed env file holds two secrets; neither is in state-dir .env.
1588+await fs.writeFile(
1589+envFilePath,
1590+[
1591+"ANTHROPIC_API_KEY=sk-ant-operator-secret",
1592+"OPENROUTER_API_KEY=or-operator-key",
1593+"LOWERCASE_LITERAL_API_KEY=$ecret123",
1594+].join("\n") + "\n",
1595+{ encoding: "utf8", mode: 0o600 },
1596+);
1597+1598+// State-dir .env only skips an unrelated key (LLM_API_KEY). Operator keys must
1599+// not be treated as stale just because they are absent from the staged env.
1600+await fs.writeFile(path.join(stateDir, ".env"), "LLM_API_KEY=${UNRESOLVED}\n", "utf8");
1601+1602+mockSystemctlStatusOk();
1603+1604+await stageSystemdService({
1605+ env,
1606+stdout: { write: vi.fn() } as unknown as NodeJS.WritableStream,
1607+programArguments: ["/usr/bin/openclaw", "gateway", "run"],
1608+workingDirectory: "/tmp",
1609+environment: { OPENCLAW_GATEWAY_PORT: "18789" },
1610+});
1611+1612+const envFile = await fs.readFile(envFilePath, "utf8");
1613+expect(envFile).toContain("ANTHROPIC_API_KEY=sk-ant-operator-secret");
1614+expect(envFile).toContain("OPENROUTER_API_KEY=or-operator-key");
1615+expect(envFile).toContain("LOWERCASE_LITERAL_API_KEY=$ecret123");
1616+expect(envFile).not.toContain("LLM_API_KEY");
1617+});
1618+});
14951619});
1496162014971621describe("systemd service install and uninstall", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。