207 lines
6.5 KiB
TypeScript
Executable File
207 lines
6.5 KiB
TypeScript
Executable File
#!/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<string, SshProfile>;
|
|
}
|
|
|
|
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==
|