feat: ssh.ts
This commit is contained in:
@@ -3,11 +3,14 @@
|
|||||||
// reference: https://git.hatter.ink/rust-scripts/scriptbase/src/branch/main/ssh-rs/src/main.rs
|
// reference: https://git.hatter.ink/rust-scripts/scriptbase/src/branch/main/ssh-rs/src/main.rs
|
||||||
|
|
||||||
const { spawn } = require("node:child_process");
|
const { spawn } = require("node:child_process");
|
||||||
|
const { parseArgs } = require("node:util");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const os = require("node:os");
|
||||||
|
|
||||||
class SshTsArgs {
|
class SshTsArgs {
|
||||||
forward_agent?: boolean;
|
forwardAgent?: boolean;
|
||||||
proxy?: boolean;
|
proxy?: boolean;
|
||||||
username_and_host?: string;
|
host?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SshConfig {
|
interface SshConfig {
|
||||||
@@ -26,30 +29,87 @@ interface SshProfile {
|
|||||||
comment?: string;
|
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 {
|
interface UsernameAndHost {
|
||||||
username?: string;
|
username?: string;
|
||||||
host: 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 {
|
function parseUsernameAndHost(usernameAndHost: string): UsernameAndHost {
|
||||||
if (!usernameAndHost) {
|
if (!usernameAndHost) {
|
||||||
throw new Error('Empty username@host');
|
throw new Error("Empty username@host");
|
||||||
}
|
}
|
||||||
const usernameAndHostParts = usernameAndHost.split('@');
|
const usernameAndHostParts = usernameAndHost.split("@");
|
||||||
if (usernameAndHostParts.length ==1 ) {
|
if (usernameAndHostParts.length == 1) {
|
||||||
return {
|
return {
|
||||||
host: usernameAndHostParts[0],
|
host: usernameAndHostParts[0],
|
||||||
};
|
};
|
||||||
@@ -62,3 +122,70 @@ function parseUsernameAndHost(usernameAndHost: string): UsernameAndHost {
|
|||||||
host: usernameAndHostParts[1],
|
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user