#!/usr/bin/env runts -- --allow-env --allow-read --allow-run // reference: https://git.hatter.ink/rust-scripts/scriptbase/src/branch/main/ssh-rs/src/main.rs import {parseArgs} from "node:util"; import {execCommandShell, log, readFileToString, term,} from "https://script.hatter.ink/@29/deno-commons-mod.ts"; import JSON5 from "npm:json5"; class SshTsArgs { forwardAgent?: boolean; proxy?: boolean; help?: boolean; host?: string; } interface SshConfig { default_forward_agent?: boolean; default_proxy?: boolean; default_username?: string; profiles: Record; } interface SshProfile { default_username?: string; alias?: string[]; host: string; proxy?: boolean; forward_agent?: boolean; comment?: string; } 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; } } console.log(term.auto("[green][OK ][/green] Total 10 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]; const features = []; if (sshProfile.proxy) features.push("proxy"); if (sshProfile.forward_agent) features.push("forward_agent"); const nameWithPad = `${k}${ " ".repeat(maxProfileNameLength - k.length) }`; const hostWithPad = `${sshProfile.host}${ " ".repeat(maxProfileHostLength - sshProfile.host.length) }`; const alias = `${ (sshProfile.alias && sshProfile.alias.length == 1) ? "alias " : "aliases" }: [${(sshProfile.alias && sshProfile.alias.join(", ")) || ""}]`; console.log(term.auto( `- ${nameWithPad} : [blue]${hostWithPad}[/blue] [yellow]${alias}[/yellow] # ${sshProfile.comment}${ (features.length > 0) ? (" ;[" + features.join(" ") + "]") : "" }`, )); } if (sshConfig.default_proxy || sshConfig.default_forward_agent) { const features = []; if (sshConfig.default_proxy) features.push("proxy"); if (sshConfig.default_forward_agent) features.push("forward_agent"); console.log(); log.info(`Global default features: [${features.join(" ")}]`); } } async function loadSshConfig(): SshConfig { try { const sshConfigText = await readFileToString( "~/.config/ssh-rs-config.json", ); return JSON5.parse(sshConfigText); } catch (e) { throw `Load config file: ${configFile} failed: ${e}`; } } function matchProfile(sshConfig: SshConfig, host: string): SshProfile | null { 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"); } const usernameAndHostParts = usernameAndHost.split("@"); if (usernameAndHostParts.length == 1) { return { host: usernameAndHostParts[0] }; } if (usernameAndHostParts.length > 2) { throw new Error(`Base username@host: ${usernameAndHost}`); } return { username: usernameAndHostParts[0], host: usernameAndHostParts[1] }; } async function main() { const sshConfig = await 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" }, "help": { type: "boolean", short: "h" }, "host": { type: "string", short: "H" }, }; const { values, positionals } = parseArgs({ args, options, allowPositionals: true, tokens: true, }); const sshTsArgs = values as SshTsArgs; if (sshTsArgs.help) { console.log("ssh.ts [-h|--help]"); console.log( "ssh.ts [-f|--forward-agent] [-p|--proxy] [-H|--host] [username@]host", ); console.log("ssh.ts [-f|--forward-agent] [-p|--proxy] [username@]host"); return; } sshTsArgs.forwardAgent = values["forward-agent"]; if (!sshTsArgs.host && positionals && positionals.length > 0) { sshTsArgs.host = positionals[0]; } 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 sshForwardAgent = sshTsArgs.forwardAgent || sshProfile.forward_agent || sshConfig.default_forward_agent || true; if (sshForwardAgent) { sshArgs.push("-o"); sshArgs.push("ForwardAgent=yes"); } const sshProxy = sshTsArgs.proxy || sshProfile.proxy || sshConfig.default_proxy || false; if (sshProxy) { sshArgs.push("-o"); sshArgs.push('"ProxyCommand=nc -X 5 -x 127.0.0.1:1080 %h %p"'); } const sshUsername = username || sshProfile.default_username || sshConfig.default_username || "root"; sshArgs.push(`${sshUsername}@${sshProfile.host}`); console.log( term.auto(`[green][OK ][/green] ${sshCommand} ${sshArgs.join(" ")}`), ); await execCommandShell(sshCommand, sshArgs); } await main(); // @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260207T010415+08:00.MEQCIA7cstLzTU4tF1mjKXOr // tqeukQDU8vBSZldr26ZpDt1/AiAO4a6KcJWDPl1fFnrk+AIipEicFaE5EZEvSbb2wsTnkQ==