From f452e8324f4fa474d3a78b7605ab7cf219dfdaef Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Mon, 30 Sep 2024 02:38:32 +0530 Subject: [PATCH] overhaul identity file detection and resolution --- tribes-cli/cli.ts | 7 ++-- tribes-cli/utils/mod.ts | 17 ++-------- tribes-cli/utils/ssh/identity.ts | 57 ++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 tribes-cli/utils/ssh/identity.ts diff --git a/tribes-cli/cli.ts b/tribes-cli/cli.ts index 43496f4d..59eada19 100644 --- a/tribes-cli/cli.ts +++ b/tribes-cli/cli.ts @@ -7,18 +7,19 @@ async function main() { .subcommand(tribe) .subcommand(remote) .option('-i --identity-file', { - description: 'The ssh identity file to use. Defaults to ~/.ssh/id_rsa.', - default: await defaultIdentityFile(), + description: 'The ssh identity file to use. You will be prompted if this is not supplied.', }); const { parsed } = tribes.parse(Deno.args); + parsed['identity-file'] = await defaultIdentityFile(parsed['identity-file'] as string); + const [s, v] = parsed._.slice(1); let cmd = tribes; if (s) cmd = tribes.getSubcommand(s); if (v) cmd = cmd.getSubcommand(v); - await cmd.action(parsed); + await cmd.doAction(parsed); } if (import.meta.main) { diff --git a/tribes-cli/utils/mod.ts b/tribes-cli/utils/mod.ts index 036eeb5a..15927034 100644 --- a/tribes-cli/utils/mod.ts +++ b/tribes-cli/utils/mod.ts @@ -1,18 +1,7 @@ -import { join } from '@std/path'; import { ParsedArgs } from './parsing.ts'; import { Command } from './command.ts'; - -export const defaultIdentityFile = async () => { - const home = Deno.env.get('HOME'); - if (!home) throw new Error('tribes-cli: unable to find default identity file'); - const path = join(home, '.ssh', 'id_rsa'); - try { - await Deno.stat(path); - return path; - } catch {} - - throw new Error('tribes-cli: unable to find default identity file.'); -}; +import { defaultIdentityFile } from './ssh/identity.ts'; +import { connect } from './ssh/mod.ts'; export type Option = & { description: string } @@ -27,5 +16,5 @@ export const cleanArg = (arg: string) => { return arg.replace(/^--?/g, ''); }; -export { Command }; +export { Command, connect, defaultIdentityFile }; export type { ParsedArgs }; diff --git a/tribes-cli/utils/ssh/identity.ts b/tribes-cli/utils/ssh/identity.ts new file mode 100644 index 00000000..0a8abbaa --- /dev/null +++ b/tribes-cli/utils/ssh/identity.ts @@ -0,0 +1,57 @@ +import { join } from '@std/path'; +import { expandGlob } from '@std/fs/expand-glob'; +import question from 'question-deno'; + +const resolveIdentityFile = async () => { + const home = Deno.env.get('HOME'); + if (!home) throw new Error('tribes-cli: unable to find default identity file'); + const path = join(home, '.ssh', '*.pub'); + const found: string[] = []; + for await (const file of expandGlob(path)) { + try { + const pkey = file.path.replace(/.pub$/, ''); + const info = await Deno.stat(pkey); + if (info.isFile) found.push(pkey); + } catch {} + } + if (found.length) { + const chosen = await question('list', 'There were multiple ssh keys found. Which do you want to use?', [ + ...found, + 'Something else', + ]); + if (chosen?.startsWith('/')) return chosen; + } + + const input = await question('input', 'Enter the path of the ssh identity file to use.'); + try { + const stat = await Deno.stat(input || ''); + if (stat.isFile) return input!; + } catch { + throw new Error(`tribes-cli: could not stat "${input}"`); + } + + throw new Error('tribes-cli: unable to find valid identity file, or selected path is a directory'); +}; + +export const defaultIdentityFile = async (supplied?: string) => { + const cached = localStorage.getItem('identity-file-path'); + if (cached && cached !== supplied && await question('confirm', `Found identity file ${cached}. Use it?`)) { + return cached; + } + + let identityFile = ''; + + if (supplied) { + try { + const stat = await Deno.stat(supplied); + if (stat.isFile) identityFile = supplied; + } catch { + throw new Error(`tribes-cli: supplied identity file ${supplied} does not exist or is not a file.`); + } + } + + if (!identityFile) identityFile = await resolveIdentityFile(); + localStorage.setItem('identity-file-path', identityFile); + + return identityFile; +};