From c94d3d617b15dc4f57bd7733a655a510894cf3ac Mon Sep 17 00:00:00 2001 From: Alexander Coffin Date: Sat, 21 Sep 2024 11:28:06 -0700 Subject: [PATCH] wip --- src/lib/ssh.ts | 65 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/src/lib/ssh.ts b/src/lib/ssh.ts index 87c3253..09d698f 100644 --- a/src/lib/ssh.ts +++ b/src/lib/ssh.ts @@ -1,6 +1,8 @@ import { expect } from "bun:test"; import os from "node:os"; import path from "node:path"; +import util from "node:util"; +import type { SpawnOptions } from "bun"; import type { Command } from "commander"; import { apiClient } from "../apiClient"; import { isLoggedIn } from "../helpers/config"; @@ -10,15 +12,49 @@ import { unreachable, } from "../helpers/errors"; -// biome-ignore lint/suspicious/noExplicitAny: better than using as -type Constructor = new (...args: any[]) => T; - // openssh-client doesn't check $HOME while homedir() does. This function is to // make it easy to fix if it causes issues. function ssh_homedir(): string { return os.homedir(); } +// Bun 1.1.29 does not handle empty arguments properly, for now us an `sh` +// wrapper. +function spawn_wrapper( + cmds: string[], + options?: Opts, +): SpawnOptions.OptionsToSubprocess { + let sh_cmd = ""; + for (const cmd of cmds) { + if (sh_cmd.length > 0) { + sh_cmd += " "; + } + sh_cmd += '"'; + + // utf-16 code points are fine as we will ignore surrogates, and we don't + // care about anything other than characters that don't require surrogates. + for (const c of cmd) { + switch (c) { + case "$": + case "\\": + case "`": + // @ts-ignore + // biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional fallthrough + case '"': { + sh_cmd += "\\"; + // fallthrough + } + default: { + sh_cmd += c; + break; + } + } + } + sh_cmd += '"'; + } + return Bun.spawn(["sh", "-c", sh_cmd], options); +} + // Returns an absolute path (symbolic links, ".", and ".." are left // unnormalized). function normalize_ssh_config_path(ssh_path: string): string { @@ -93,7 +129,7 @@ async function find_default_key(): Promise { ]; { - const proc = Bun.spawn(["ssh", "-V"], { + const proc = spawn_wrapper(["ssh", "-V"], { stdin: null, stdout: null, stderr: null, @@ -115,7 +151,7 @@ async function find_default_key(): Promise { let key_supported_ed25519 = false; let key_supported_rsa = false; - const proc = Bun.spawn(["ssh", "-G", ""], { + const proc = spawn_wrapper(["ssh", "-G", ""], { stdin: null, stdout: "pipe", stderr: null, @@ -134,8 +170,21 @@ async function find_default_key(): Promise { for (const line of stdout_str.split("\n")) { const prefix = "identityfile "; if (line.startsWith(prefix)) { + const line_suffix = line.slice(prefix.length); + if ( + line_suffix === "~/.ssh/id_ed25519" || + line_suffix === path.join(ssh_homedir(), ".ssh/id_ed25519") + ) { + key_supported_ed25519 = true; + } + if ( + line_suffix === "~/.ssh/id_rsa" || + line_suffix === path.join(ssh_homedir(), ".ssh/id_rsa") + ) { + key_supported_rsa = true; + } const potential_identity_file = normalize_ssh_config_path( - line.slice(prefix.length) + ".pub", + line_suffix + ".pub", ); ssh_g_parsed_success = true; if (await Bun.file(potential_identity_file).exists()) { @@ -183,7 +232,7 @@ async function find_default_key(): Promise { ); } - const proc = Bun.spawn( + const proc = spawn_wrapper( ["ssh-keygen", "-N", "", "-q", "-f", priv_ssh_key_path].concat( extra_ssh_options, ), @@ -199,7 +248,7 @@ async function find_default_key(): Promise { "The ssh-keygen command is not installed, please install it before trying again.", ); } - + console.log(util.format("Generated key %s", priv_ssh_key_path)); identity_file = priv_ssh_key_path + ".pub"; }