List ~/.ssh/config hosts in the panel source dropdown
Read-only group below saved connections; hosts resolve HostName/User/ Port/IdentityFile (Include, wildcards, %-tokens) and connect on click. New ssh-agent auth kind covers hosts without a usable identity file; ProxyJump/ProxyCommand hosts are shown disabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+25
-12
@@ -1,9 +1,9 @@
|
||||
import dns from "node:dns/promises";
|
||||
import { readFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { Client, type ConnectConfig, type SFTPWrapper } from "ssh2";
|
||||
import type { ConnectionConfig, EntryType, FileEntry } from "../../shared/types";
|
||||
import { expandTilde } from "../store/ssh-config";
|
||||
import type { FileSystemProvider } from "./provider";
|
||||
|
||||
const S_IFMT = 0o170000;
|
||||
@@ -17,12 +17,6 @@ function typeFromMode(mode: number): EntryType {
|
||||
return "file";
|
||||
}
|
||||
|
||||
function expandTilde(p: string): string {
|
||||
if (p === "~") return os.homedir();
|
||||
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
||||
return p;
|
||||
}
|
||||
|
||||
export class SftpProvider implements FileSystemProvider {
|
||||
readonly kind = "sftp" as const;
|
||||
readonly fsKey: string;
|
||||
@@ -49,11 +43,30 @@ export class SftpProvider implements FileSystemProvider {
|
||||
keepaliveInterval: 15_000,
|
||||
keepaliveCountMax: 3,
|
||||
};
|
||||
if (config.auth.kind === "password") {
|
||||
connectConfig.password = config.auth.password;
|
||||
} else {
|
||||
connectConfig.privateKey = readFileSync(expandTilde(config.auth.privateKeyPath));
|
||||
if (config.auth.passphrase) connectConfig.passphrase = config.auth.passphrase;
|
||||
const agentSock =
|
||||
process.env.SSH_AUTH_SOCK ??
|
||||
(process.platform === "win32" ? "\\\\.\\pipe\\openssh-ssh-agent" : undefined);
|
||||
switch (config.auth.kind) {
|
||||
case "password":
|
||||
connectConfig.password = config.auth.password;
|
||||
break;
|
||||
case "agent":
|
||||
if (!agentSock) throw new Error("ssh-agent not available (SSH_AUTH_SOCK is not set)");
|
||||
connectConfig.agent = agentSock;
|
||||
break;
|
||||
case "key": {
|
||||
// Alongside a passphrase-less key file, offer the agent too — it
|
||||
// covers encrypted keys already loaded via ssh-add/UseKeychain.
|
||||
try {
|
||||
connectConfig.privateKey = readFileSync(expandTilde(config.auth.privateKeyPath));
|
||||
} catch {
|
||||
if (!agentSock)
|
||||
throw new Error(`identity file not found: ${config.auth.privateKeyPath}`);
|
||||
}
|
||||
if (config.auth.passphrase) connectConfig.passphrase = config.auth.passphrase;
|
||||
else if (agentSock) connectConfig.agent = agentSock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Filesystem identity: resolve the host so a hostname and its IP compare
|
||||
|
||||
Reference in New Issue
Block a user