fix: sync scoped ssh runtime deploy env

This commit is contained in:
Codex
2026-06-02 08:40:58 +00:00
parent 5dba1a06be
commit 7de9782dc2
4 changed files with 55 additions and 1 deletions
@@ -12,6 +12,8 @@ assertCondition(source.includes("downloadRemoteFile(options, remoteArchive, loca
assertCondition(source.includes("runRemoteScriptBackground(options, remoteScript"), "remote docker save must run as a background job");
assertCondition(source.includes('runRemoteScriptBackground(options, deployScript, Math.max(options.timeoutMs, 420_000), "d601-k3s-deploy")'), "D601 k3s deploy must use background polling");
assertCondition(source.includes('"ssh",\n options.providerId,\n "download"'), "download helper must route through UniDesk ssh download");
assertCondition(source.includes('"--chunk-bytes",\n "96000"'), "artifact ssh download must use the largest bounded chunk size");
assertCondition(source.includes("UNIDESK_SSH_CLIENT_TOKEN") && source.includes("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST"), "dev frontend artifact deploy must sync scoped ssh runtime keys");
console.log(JSON.stringify({
ok: true,
@@ -19,6 +21,8 @@ console.log(JSON.stringify({
assertions: [
"no docker-save stdout stream over ssh",
"compose artifact uses verified ssh download",
"remote docker save and k3s deploy use background polling"
"remote docker save and k3s deploy use background polling",
"artifact downloads use the largest bounded ssh chunk size",
"dev frontend artifact deploy syncs scoped ssh runtime keys"
]
}, null, 2));
+8
View File
@@ -17,6 +17,7 @@ import {
type DeployJsonServiceContract,
} from "./deploy-json-contract";
import { d601K3sGuardShellLines } from "./d601-k3s-guard";
import { composeRuntimeEnvValue } from "./runtime-env";
export type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";
type ArtifactDeployEnvironment = "prod" | "dev";
@@ -1676,6 +1677,8 @@ function downloadRemoteFile(options: ArtifactRegistryOptions, remotePath: string
"ssh",
options.providerId,
"download",
"--chunk-bytes",
"96000",
remotePath,
localPath,
], repoRoot, { timeoutMs });
@@ -2455,13 +2458,18 @@ function verifyLocalArtifactLabels(
}
function d601DevFrontendAuthPatchScript(config: UniDeskConfig): string {
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
if (sshClientToken === null) throw new Error("UNIDESK_SSH_CLIENT_TOKEN must be present in .state/docker-compose.env before deploying dev frontend");
const sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
const secretData = {
AUTH_USERNAME: base64(config.auth.username),
AUTH_PASSWORD: base64(config.auth.password),
SESSION_SECRET: base64(config.auth.sessionSecret),
UNIDESK_SSH_CLIENT_TOKEN: base64(sshClientToken),
};
const configData = {
SESSION_TTL_SECONDS: String(config.auth.sessionTtlSeconds),
UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST: sshClientRouteAllowlist,
};
return [
d601K3sGuardScript(),
+6
View File
@@ -10,6 +10,7 @@ import { startJob } from "./jobs";
import { coreInternalFetch } from "./microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "./code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "./d601-k3s-guard";
import { composeRuntimeEnvValue } from "./runtime-env";
import {
compareDeployJsonExecutorMirrors,
deployJsonCommitImage,
@@ -1513,13 +1514,18 @@ function devK3sPrepullImages(service: UniDeskMicroserviceConfig): string[] {
}
function syncDevFrontendAuthScript(config: UniDeskConfig): string {
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
if (sshClientToken === null) throw new Error("UNIDESK_SSH_CLIENT_TOKEN must be present in .state/docker-compose.env before deploying dev frontend");
const sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
const data = {
AUTH_USERNAME: Buffer.from(config.auth.username, "utf8").toString("base64"),
AUTH_PASSWORD: Buffer.from(config.auth.password, "utf8").toString("base64"),
SESSION_SECRET: Buffer.from(config.auth.sessionSecret, "utf8").toString("base64"),
UNIDESK_SSH_CLIENT_TOKEN: Buffer.from(sshClientToken, "utf8").toString("base64"),
};
const runtimeConfig = {
SESSION_TTL_SECONDS: String(config.auth.sessionTtlSeconds),
UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST: sshClientRouteAllowlist,
};
return [
"set -euo pipefail",
+36
View File
@@ -0,0 +1,36 @@
import { existsSync, readFileSync } from "node:fs";
import { rootPath } from "./config";
export function canonicalComposeEnvFile(): string {
return rootPath(".state", "docker-compose.env");
}
function unquoteEnvValue(value: string): string {
const trimmed = value.trim();
if (trimmed.length < 2) return trimmed;
const first = trimmed[0];
const last = trimmed[trimmed.length - 1];
if ((first !== "\"" && first !== "'") || last !== first) return trimmed;
return trimmed.slice(1, -1);
}
export function readEnvFileValues(path: string): Map<string, string> {
const values = new Map<string, string>();
if (!existsSync(path)) return values;
for (const line of readFileSync(path, "utf8").split(/\r?\n/u)) {
if (line.trim().length === 0 || line.trimStart().startsWith("#")) continue;
const index = line.indexOf("=");
if (index <= 0) continue;
const key = line.slice(0, index).trim();
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) continue;
values.set(key, unquoteEnvValue(line.slice(index + 1)));
}
return values;
}
export function composeRuntimeEnvValue(key: string, env: NodeJS.ProcessEnv = process.env): string | null {
const fromProcess = env[key]?.trim();
if (fromProcess !== undefined && fromProcess.length > 0) return fromProcess;
const fromFile = readEnvFileValues(canonicalComposeEnvFile()).get(key)?.trim();
return fromFile === undefined || fromFile.length === 0 ? null : fromFile;
}