fix: honor trans backend config for ssh

This commit is contained in:
Codex
2026-06-29 14:56:01 +00:00
parent 350aa68174
commit 1cdcf3c9a8
4 changed files with 202 additions and 41 deletions
+61 -5
View File
@@ -23,6 +23,7 @@ import {
type SshRemoteCommandStreamHandlers,
} from "./ssh-file-transfer";
import { readTransHostProxyEnvRule, type TransHostProxyEnvRule } from "./trans-host-proxy";
import { readTransSshBackendConfig, type TransSshBackendConfig } from "./trans-config";
export interface ParsedSshArgs {
remoteCommand: string | null;
@@ -74,6 +75,7 @@ export interface SshCaptureBackendPlan {
dockerExecutable: boolean;
backendCoreContainer: boolean;
error: string | null;
source?: string;
};
}
@@ -196,6 +198,8 @@ const windowsCmdExeNativePath = "C:\\Windows\\System32\\cmd.exe";
const defaultSshSlowWarningMs = 10_000;
const defaultSshRuntimeTimeoutMs = 60_000;
const maxSshRuntimeTimeoutMs = 60_000;
const defaultSshBackendCoreDetectTimeoutMs = 15_000;
const maxSshBackendCoreDetectTimeoutMs = 60_000;
const defaultSshStdoutStreamMaxBytes = 256 * 1024;
const minSshStdoutStreamMaxBytes = 4 * 1024;
const maxSshStdoutStreamMaxBytes = 16 * 1024 * 1024;
@@ -3551,14 +3555,46 @@ async function runRemoteSshCapture(config: UniDeskConfig, host: string, target:
}
export function sshCaptureBackendPlan(config: UniDeskConfig, env: NodeJS.ProcessEnv = process.env): SshCaptureBackendPlan {
const localBackendCore = detectLocalBackendCoreStatus();
const configuredBackend = readTransSshBackendConfig(env);
const remoteHost = sshCaptureRemoteHost(config, env);
if (configuredBackend !== null) return configuredSshCaptureBackendPlan(configuredBackend, remoteHost);
const localBackendCore = detectLocalBackendCoreStatus(env);
const runnerEnv = isRunnerEnvironment(env);
if (runnerEnv && remoteHost !== null) return { backend: "remote-frontend-websocket", remoteHost, reason: "runner-environment", localBackendCore };
if (!localBackendCore.backendCoreContainer && remoteHost !== null) return { backend: "remote-frontend-websocket", remoteHost, reason: "local-backend-core-unavailable", localBackendCore };
return { backend: "local-backend-core-broker", remoteHost: null, reason: "main-server-local-backend-core", localBackendCore };
}
function configuredSshCaptureBackendPlan(configuredBackend: TransSshBackendConfig, remoteHost: string | null): SshCaptureBackendPlan {
if (configuredBackend.backend === "local-backend-core-broker") {
return {
backend: "local-backend-core-broker",
remoteHost: null,
reason: `configured:${configuredBackend.sourceRef}`,
localBackendCore: {
dockerExecutable: true,
backendCoreContainer: true,
error: null,
source: configuredBackend.sourceRef,
},
};
}
if (remoteHost === null) {
throw new Error(`${configuredBackend.sourceRef} selects ${configuredBackend.raw}, but no remote host is configured for frontend websocket SSH`);
}
return {
backend: "remote-frontend-websocket",
remoteHost,
reason: `configured:${configuredBackend.sourceRef}`,
localBackendCore: {
dockerExecutable: false,
backendCoreContainer: false,
error: null,
source: configuredBackend.sourceRef,
},
};
}
function sshCaptureRemoteHost(config: UniDeskConfig, env: NodeJS.ProcessEnv): string | null {
return normalizeRemoteHost(env.UNIDESK_MAIN_SERVER_IP)
?? normalizeRemoteHost(env.UNIDESK_MAIN_SERVER_HOST)
@@ -3583,17 +3619,37 @@ function isRunnerEnvironment(env: NodeJS.ProcessEnv): boolean {
);
}
function detectLocalBackendCoreStatus(): SshCaptureBackendPlan["localBackendCore"] {
const result = spawnSync("docker", ["ps", "--format", "{{.Names}}"], { encoding: "utf8", timeout: 2000 });
if (result.error !== undefined) return { dockerExecutable: false, backendCoreContainer: false, error: result.error.message };
function detectLocalBackendCoreStatus(env: NodeJS.ProcessEnv = process.env): SshCaptureBackendPlan["localBackendCore"] {
const timeout = sshBackendCoreDetectTimeoutMs(env);
const inspect = spawnSync("docker", ["inspect", "unidesk-backend-core", "--format", "{{.State.Running}}"], { encoding: "utf8", timeout });
if (inspect.error !== undefined) return { dockerExecutable: false, backendCoreContainer: false, error: inspect.error.message, source: "docker-inspect" };
const inspectOutput = `${inspect.stdout ?? ""}\n${inspect.stderr ?? ""}`.trim();
if (inspect.status === 0) {
const running = String(inspect.stdout ?? "").trim() === "true";
return {
dockerExecutable: true,
backendCoreContainer: running,
error: running ? null : inspectOutput || "docker inspect unidesk-backend-core reported not running",
source: "docker-inspect",
};
}
const result = spawnSync("docker", ["ps", "--format", "{{.Names}}"], { encoding: "utf8", timeout });
if (result.error !== undefined) return { dockerExecutable: false, backendCoreContainer: false, error: result.error.message, source: "docker-ps" };
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
return {
dockerExecutable: result.status === 0,
backendCoreContainer: result.status === 0 && String(result.stdout ?? "").split(/\r?\n/u).includes("unidesk-backend-core"),
error: result.status === 0 ? null : output || `docker ps exited ${result.status ?? "unknown"}`,
error: result.status === 0 ? null : inspectOutput || output || `docker ps exited ${result.status ?? "unknown"}`,
source: "docker-ps",
};
}
function sshBackendCoreDetectTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
const parsed = Number(env.UNIDESK_SSH_BACKEND_CORE_DETECT_TIMEOUT_MS);
if (!Number.isFinite(parsed) || parsed <= 0) return defaultSshBackendCoreDetectTimeoutMs;
return Math.min(maxSshBackendCoreDetectTimeoutMs, Math.max(2000, Math.trunc(parsed)));
}
function isLocalSshCaptureBackendUnavailable(result: SshCaptureResult): boolean {
return /No such container: unidesk-backend-core|failed to start broker|Executable not found.*"docker"|docker: not found|Cannot connect to the Docker daemon/iu.test(result.stderr);
}