This commit is contained in:
2026-02-07 01:00:45 +08:00
parent 63d2a5b9cd
commit 6739f5b86b
3 changed files with 258 additions and 45 deletions

View File

@@ -218,6 +218,15 @@
"publish_time": 1737272626138, "publish_time": 1737272626138,
"update_time": 1737650670432 "update_time": 1737650670432
}, },
"ssh-bun.ts": {
"script_name": "ssh-bun.ts",
"script_length": 6813,
"script_sha256": "d65f2335b82fc5f5376f1df34057d9feae12fe5f74dbfb572ce18f9a089b3a3a",
"script_full_url": "https://git.hatter.ink/hatter/ts-scripts/raw/branch/main/single-scripts/ssh-bun.ts",
"single_script_file": true,
"publish_time": 1770397234563,
"update_time": 1770397234563
},
"ssh-verify.ts": { "ssh-verify.ts": {
"script_name": "ssh-verify.ts", "script_name": "ssh-verify.ts",
"script_length": 2854, "script_length": 2854,
@@ -229,12 +238,12 @@
}, },
"ssh.ts": { "ssh.ts": {
"script_name": "ssh.ts", "script_name": "ssh.ts",
"script_length": 6813, "script_length": 6600,
"script_sha256": "d65f2335b82fc5f5376f1df34057d9feae12fe5f74dbfb572ce18f9a089b3a3a", "script_sha256": "32133f8fd4d0804dfed9f0db65dc4d4f34dd851ab7b7886fd572aa2ea4795a1a",
"script_full_url": "https://git.hatter.ink/hatter/ts-scripts/raw/branch/main/single-scripts/ssh.ts", "script_full_url": "https://git.hatter.ink/hatter/ts-scripts/raw/branch/main/single-scripts/ssh.ts",
"single_script_file": true, "single_script_file": true,
"publish_time": 1768111677531, "publish_time": 1768111677531,
"update_time": 1769359601445 "update_time": 1770397234557
}, },
"tree.ts": { "tree.ts": {
"script_name": "tree.ts", "script_name": "tree.ts",

216
single-scripts/ssh-bun.ts Executable file
View File

@@ -0,0 +1,216 @@
#!/usr/bin/env runts -- --runtime-bun
// reference: https://git.hatter.ink/rust-scripts/scriptbase/src/branch/main/ssh-rs/src/main.rs
import { spawn } from "node:child_process";
import { parseArgs } from "node:util";
import fs from "node:fs";
import os from "node:os";
import JSON5 from "json5";
// 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";
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(
`${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];
const features = [];
if (sshProfile.proxy) features.push("proxy");
if (sshProfile.forward_agent) features.push("forward_agent");
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}${
(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(
`\n[INFO ] Global default features: [${features.join(" ")}]`,
);
}
}
function loadSshConfig(): SshConfig {
const configFile = os.homedir() + "/.config/ssh-rs-config.json";
try {
const sshConfigText = fs.readFileSync(configFile, "utf8");
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 = 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(`${GREEN}[OK ]${RESET} ${sshCommand} ${sshArgs.join(" ")}`);
spawn(sshCommand, sshArgs, {
shell: true,
stdio: ["inherit", "inherit", "inherit"],
});
}
await main();
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260126T004612+08:00.MEQCIFxHjeu7+pkPpSWVoCTb
// BbzbHhc+dsAG6vqx+i0H8MqLAiAYcK0dcCVECG1je0Q/ZCIYohHCNrvDIOr3kiNj7XWoXg==

View File

@@ -1,19 +1,11 @@
#!/usr/bin/env runts -- --runtime-bun #!/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 // reference: https://git.hatter.ink/rust-scripts/scriptbase/src/branch/main/ssh-rs/src/main.rs
import { spawn } from "node:child_process"; import {parseArgs} from "node:util";
import { parseArgs } from "node:util";
import fs from "node:fs"; import {log, readFileToString, term, execCommandShell} from "../libraries/deno-commons-mod.ts";
import os from "node:os"; import JSON5 from "npm:json5";
import JSON5 from "json5";
// 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";
class SshTsArgs { class SshTsArgs {
forwardAgent?: boolean; forwardAgent?: boolean;
@@ -57,9 +49,7 @@ function printSshConfig(sshConfig: SshConfig) {
maxProfileHostLength = sshProfile.host.length; maxProfileHostLength = sshProfile.host.length;
} }
} }
console.log( console.log(term.auto("[green][OK ][/green] Total 10 server(s):"));
`${GREEN}[OK ]${RESET} Total ${allProfiles.length} server(s):`,
);
allProfiles.sort((a, b) => a.localeCompare(b)); allProfiles.sort((a, b) => a.localeCompare(b));
for (let i = 0; i < allProfiles.length; i++) { for (let i = 0; i < allProfiles.length; i++) {
const k = allProfiles[i]; const k = allProfiles[i];
@@ -67,37 +57,38 @@ function printSshConfig(sshConfig: SshConfig) {
const features = []; const features = [];
if (sshProfile.proxy) features.push("proxy"); if (sshProfile.proxy) features.push("proxy");
if (sshProfile.forward_agent) features.push("forward_agent"); if (sshProfile.forward_agent) features.push("forward_agent");
console.log( const nameWithPad = `${k}${
`- ${k}${
" ".repeat(maxProfileNameLength - k.length) " ".repeat(maxProfileNameLength - k.length)
} : ${BLUE}${sshProfile.host}${ }`;
const hostWithPad = `${sshProfile.host}${
" ".repeat(maxProfileHostLength - sshProfile.host.length) " ".repeat(maxProfileHostLength - sshProfile.host.length)
}${RESET} ${YELLOW}${ }`;
const alias = `${
(sshProfile.alias && sshProfile.alias.length == 1) (sshProfile.alias && sshProfile.alias.length == 1)
? "alias " ? "alias "
: "aliases" : "aliases"
}: [${ }: [${(sshProfile.alias && sshProfile.alias.join(", ")) || ""}]`;
(sshProfile.alias && sshProfile.alias.join(", ")) || "" console.log(term.auto(
}]${RESET} # ${sshProfile.comment}${ `- ${nameWithPad} : [blue]${hostWithPad}[/blue] [yellow]${alias}[/yellow] # ${sshProfile.comment}${
(features.length > 0) ? (" ;[" + features.join(" ") + "]") : "" (features.length > 0) ? (" ;[" + features.join(" ") + "]") : ""
}`, }`,
); ));
} }
if (sshConfig.default_proxy || sshConfig.default_forward_agent) { if (sshConfig.default_proxy || sshConfig.default_forward_agent) {
const features = []; const features = [];
if (sshConfig.default_proxy) features.push("proxy"); if (sshConfig.default_proxy) features.push("proxy");
if (sshConfig.default_forward_agent) features.push("forward_agent"); if (sshConfig.default_forward_agent) features.push("forward_agent");
console.log( console.log();
`\n[INFO ] Global default features: [${features.join(" ")}]`, log.info(`Global default features: [${features.join(" ")}]`);
);
} }
} }
function loadSshConfig(): SshConfig { async function loadSshConfig(): SshConfig {
const configFile = os.homedir() + "/.config/ssh-rs-config.json";
try { try {
const sshConfigText = fs.readFileSync(configFile, "utf8"); const sshConfigText = await readFileToString(
"~/.config/ssh-rs-config.json",
);
return JSON5.parse(sshConfigText); return JSON5.parse(sshConfigText);
} catch (e) { } catch (e) {
throw `Load config file: ${configFile} failed: ${e}`; throw `Load config file: ${configFile} failed: ${e}`;
@@ -137,7 +128,7 @@ function parseUsernameAndHost(usernameAndHost: string): UsernameAndHost {
} }
async function main() { async function main() {
const sshConfig = loadSshConfig(); const sshConfig = await loadSshConfig();
if (process.argv.length <= 2) { if (process.argv.length <= 2) {
printSshConfig(sshConfig); printSshConfig(sshConfig);
@@ -204,13 +195,10 @@ async function main() {
sshConfig.default_username || "root"; sshConfig.default_username || "root";
sshArgs.push(`${sshUsername}@${sshProfile.host}`); sshArgs.push(`${sshUsername}@${sshProfile.host}`);
console.log(`${GREEN}[OK ]${RESET} ${sshCommand} ${sshArgs.join(" ")}`); console.log(term.auto(`[green][OK ][/green] ${sshCommand} ${sshArgs.join(" ")}`));
spawn(sshCommand, sshArgs, { await execCommandShell(sshCommand, sshArgs);
shell: true,
stdio: ["inherit", "inherit", "inherit"],
});
} }
await main(); await main();
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260126T004612+08:00.MEQCIFxHjeu7+pkPpSWVoCTb // @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260207T010008+08:00.MEUCIEmqd5Xb7955nioxaPgT
// BbzbHhc+dsAG6vqx+i0H8MqLAiAYcK0dcCVECG1je0Q/ZCIYohHCNrvDIOr3kiNj7XWoXg== // iHA794kvFjktf5GysW6x83aQAiEAkYRhu9ycPCRjMZa+iJRmNTxYz14O0WawQ3X32MQVIc0=