fix: validate provider bootstrap SSH bridge
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / unidesk-host- Success

This commit is contained in:
李昂
2026-07-10 14:51:57 +08:00
parent bfa2ac8fac
commit aa454c6fe1
3 changed files with 72 additions and 14 deletions
+4 -1
View File
@@ -631,7 +631,10 @@ async function main(): Promise<void> {
}
if (top === "provider") {
emitJson(commandName, await runProviderCommand(config, args.slice(1)));
const result = await runProviderCommand(config, args.slice(1));
const ok = resultOk(result);
emitJson(commandName, result, ok);
if (!ok) process.exitCode = 1;
return;
}
+54 -3
View File
@@ -27,6 +27,16 @@ interface ProviderAttachContainerValidation {
command: string[];
exitCode: number | null;
stderr: string;
hostSshKeyPath: string;
hostSshKeyPresent: boolean;
hostSshKeyCommand: string[];
hostSshKeyExitCode: number | null;
hostSshTarget: string;
hostSshProbeOk: boolean;
hostSshProbeCommand: string[];
hostSshProbeExitCode: number | null;
hostSshProbeStdout: string;
hostSshProbeStderr: string;
}
function optionValue(args: string[], name: string): string | null {
@@ -178,6 +188,8 @@ function writeGeneratedFile(path: string, content: string, force: boolean): "cre
function inspectAttachedContainer(options: ProviderAttachOptions): ProviderAttachContainerValidation {
const containerName = `unidesk-provider-gateway-${safeProviderSlug(options.providerId)}`;
const hostSshKeyPath = "/run/host-ssh/id_ed25519";
const hostSshTarget = `${defaultHostSshUser(repoRoot)}@host.docker.internal`;
const command = [
"docker",
"inspect",
@@ -189,8 +201,35 @@ function inspectAttachedContainer(options: ProviderAttachOptions): ProviderAttac
const [restartPolicy = "", pidMode = "", state = ""] = result.stdout.trim().split("\t");
const restartPolicyOk = restartPolicy === "always";
const pidModeOk = pidMode === "host";
const containerReady = result.exitCode === 0 && restartPolicyOk && pidModeOk && state === "running";
const hostSshKeyCommand = ["docker", "exec", containerName, "test", "-s", hostSshKeyPath];
const hostSshKeyResult = containerReady
? runCommand(hostSshKeyCommand, repoRoot, { timeoutMs: 10_000 })
: null;
const hostSshKeyPresent = hostSshKeyResult?.exitCode === 0;
const hostSshProbeCommand = [
"docker",
"exec",
containerName,
"ssh",
"-T",
"-i",
hostSshKeyPath,
"-o",
"BatchMode=yes",
"-o",
"StrictHostKeyChecking=accept-new",
"-o",
"ConnectTimeout=10",
hostSshTarget,
"true",
];
const hostSshProbeResult = hostSshKeyPresent
? runCommand(hostSshProbeCommand, repoRoot, { timeoutMs: 15_000 })
: null;
const hostSshProbeOk = hostSshProbeResult?.exitCode === 0;
return {
ok: result.exitCode === 0 && restartPolicyOk && pidModeOk && state === "running",
ok: containerReady && hostSshKeyPresent && hostSshProbeOk,
containerName,
restartPolicy,
restartPolicyOk,
@@ -200,6 +239,16 @@ function inspectAttachedContainer(options: ProviderAttachOptions): ProviderAttac
command,
exitCode: result.exitCode,
stderr: result.stderr.trim(),
hostSshKeyPath,
hostSshKeyPresent,
hostSshKeyCommand,
hostSshKeyExitCode: hostSshKeyResult?.exitCode ?? null,
hostSshTarget,
hostSshProbeOk,
hostSshProbeCommand,
hostSshProbeExitCode: hostSshProbeResult?.exitCode ?? null,
hostSshProbeStdout: hostSshProbeResult?.stdout.trim() ?? "",
hostSshProbeStderr: hostSshProbeResult?.stderr.trim() ?? "",
};
}
@@ -237,6 +286,7 @@ export async function runProviderCommand(config: UniDeskConfig, args: string[]):
const result = options.up ? runCommand(composeCommand, repoRoot) : null;
const containerValidation = options.up ? inspectAttachedContainer(options) : null;
return {
ok: result === null || (result.exitCode === 0 && containerValidation?.ok === true),
providerId: options.providerId,
masterServer: options.masterServer,
envFile: options.envFile,
@@ -251,9 +301,10 @@ export async function runProviderCommand(config: UniDeskConfig, args: string[]):
result,
containerValidation,
notes: [
"The generated env file keeps only UNIDESK_MASTER_SERVER and PROVIDER_ID unless --provider-token is supplied.",
"The generated env file records the master address, provider identity, upgrade coordinates, and Host SSH target; PROVIDER_TOKEN is added only when supplied.",
"Place a maintenance SSH private key at hostSshKeyPath if this provider should expose host.ssh.",
"When --up is used, containerValidation must show restartPolicy=always and pidMode=host before the node is accepted as durable across Docker daemon restarts.",
"When --up is used, containerValidation must show restartPolicy=always, pidMode=host, hostSshKeyPresent=true, and hostSshProbeOk=true before the node is accepted.",
"For Docker Desktop with a WSL key directory, run provider attach from that WSL distro so the bind mount resolves to the distro filesystem.",
envStatus === "skipped" || composeStatus === "skipped" ? "Existing generated files differed and were not overwritten; rerun with --force to replace them." : "Generated files are ready.",
],
};