diff --git a/single-scripts/ssh.ts b/single-scripts/ssh.ts index 9807a80..d43f2d5 100755 --- a/single-scripts/ssh.ts +++ b/single-scripts/ssh.ts @@ -3,11 +3,14 @@ // reference: https://git.hatter.ink/rust-scripts/scriptbase/src/branch/main/ssh-rs/src/main.rs const { spawn } = require("node:child_process"); +const { parseArgs } = require("node:util"); +const fs = require("node:fs"); +const os = require("node:os"); class SshTsArgs { - forward_agent?: boolean; + forwardAgent?: boolean; proxy?: boolean; - username_and_host?: string; + host?: string; } interface SshConfig { @@ -26,30 +29,87 @@ interface SshProfile { comment?: string; } -if (process.argv.length <= 2) { - console.log("help message"); - return; -} -const args = process.argv.slice(2); - -console.log(args); - -spawn("ssh", ["root@example.com"], { - shell: true, - stdio: ["inherit", "inherit", "inherit"], -}); - interface UsernameAndHost { username?: string; host: string; } +function printSshConfig(sshConfig: SshConfig) { + const allProfiles = []; + let maxProfileNameLength = 0; + let maxProfileHostLength = 0; + for (const k in sshConfig.profiles) { + const sshProfile = sshConfig.profiles[k]; + allProfiles.push(k); + if (k.length > maxProfileNameLength) { + maxProfileNameLength = k.length; + } + if (sshProfile.host.length > maxProfileHostLength) { + maxProfileHostLength = sshProfile.host.length; + } + } + // reference: https://bun.com/docs/runtime/color + const GREEN = Bun.color("green", "ansi"); + const BLUE = Bun.color("lightblue", "ansi"); + const YELLOW = Bun.color("yellow", "ansi"); + const RESET = "\x1B[0m"; + console.log( + `${GREEN}[OK ]${RESET} Total ${allProfiles.length} server(s):`, + ); + allProfiles.sort((a, b) => a.localeCompare(b)); + for (let i = 0; i < allProfiles.length; i++) { + const k = allProfiles[i]; + const sshProfile = sshConfig.profiles[k]; + console.log( + ` - ${k}${ + " ".repeat(maxProfileNameLength - k.length) + } : ${BLUE}${sshProfile.host}${ + " ".repeat(maxProfileHostLength - sshProfile.host.length) + }${RESET} ${YELLOW}${ + (sshProfile.alias && sshProfile.alias.length == 1) + ? "alias " + : "aliases" + }: [${ + (sshProfile.alias && sshProfile.alias.join(", ")) || "" + }]${RESET} # ${sshProfile.comment}`, + ); + } +} + +function loadSshConfig(): SshConfig { + const configFile = os.homedir() + "/.config/ssh-rs-config.json"; + try { + const sshConfigText = fs.readFileSync(configFile, "utf8"); + return JSON.parse(sshConfigText); + } catch (e) { + console.error(`Load config file: ${configFile} failed.`, e); + } +} + +function matchProfile(sshConfig: SshConfig, host: string): SshProfile { + let profiles = []; + for (const k in sshConfig.profiles) { + const sshProfile = sshConfig.profiles[k]; + if (k === host) { + profiles.push(sshProfile); + } else if (sshProfile.alias && sshProfile.alias.includes(host)) { + profiles.push(sshProfile); + } + } + if (profiles.length === 0) { + return null; + } else if (profiles.length > 1) { + throw new Error("Find multiple profiles"); + } + return profiles[0]; +} + function parseUsernameAndHost(usernameAndHost: string): UsernameAndHost { if (!usernameAndHost) { - throw new Error('Empty username@host'); + throw new Error("Empty username@host"); } - const usernameAndHostParts = usernameAndHost.split('@'); - if (usernameAndHostParts.length ==1 ) { + const usernameAndHostParts = usernameAndHost.split("@"); + if (usernameAndHostParts.length == 1) { return { host: usernameAndHostParts[0], }; @@ -62,3 +122,70 @@ function parseUsernameAndHost(usernameAndHost: string): UsernameAndHost { host: usernameAndHostParts[1], }; } + +async function main() { + const sshConfig = loadSshConfig(); + + if (process.argv.length <= 2) { + printSshConfig(sshConfig); + return; + } + const args = process.argv.slice(2); + const options = { + "forward-agent": { + type: "boolean", + short: "f", + }, + "proxy": { + type: "boolean", + short: "p", + }, + "host": { + type: "string", + short: "h", + }, + }; + + const { values, tokens } = parseArgs({ args, options, tokens: true }); + const sshTsArgs = values as SshTsArgs; + sshTsArgs.forwardAgent = values["forward-agent"]; + if (!sshTsArgs.host) { + console.error("[ERROR] --host required"); + return; + } + + const { username, host } = parseUsernameAndHost(sshTsArgs.host); + const sshProfile = matchProfile(sshConfig, host); + if (sshProfile === null) { + console.error("[ERROR] No ssh profile found."); + return; + } + + const sshCommand = "ssh"; + const sshArgs = []; + const sshUsername = username || sshProfile.default_username || + sshConfig.default_username || "root"; + const sshForwardAgent = sshTsArgs.forwardAgent || + sshProfile.forward_agent || + sshConfig.default_forward_agent || true; + const sshProxy = sshTsArgs.proxy || sshProfile.proxy || + sshConfig.default_proxy || false; + if (sshForwardAgent) { + sshArgs.push("-o"); + sshArgs.push("ForwardAgent=yes"); + } + if (sshProxy) { + sshArgs.push("-o"); + sshArgs.push('"ProxyCommand=nc -X 5 -x 127.0.0.1:1080 %h %p"'); + } + sshArgs.push( + `${sshUsername}@${sshProfile.host}`, + ); + + console.log(`Command: ${sshCommand} ${sshArgs.join(" ")}`); + spawn(sshCommand, sshArgs, { + shell: true, + stdio: ["inherit", "inherit", "inherit"], + }); +} +await main();