fix: validate provider bootstrap SSH bridge
This commit is contained in:
+4
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.",
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user