feat: added -r and -l support for tunnels
This commit is contained in:
@@ -828,15 +828,22 @@ async function connectSSHTunnel(
|
||||
return;
|
||||
}
|
||||
|
||||
const tunnelType = tunnelConfig.tunnelType || "remote";
|
||||
const tunnelFlag = tunnelType === "local" ? "-L" : "-R";
|
||||
const portMapping =
|
||||
tunnelType === "local"
|
||||
? `${tunnelConfig.sourcePort}:${tunnelConfig.endpointIP}:${tunnelConfig.endpointPort}`
|
||||
: `${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort}`;
|
||||
|
||||
let tunnelCmd: string;
|
||||
if (
|
||||
resolvedEndpointCredentials.authMethod === "key" &&
|
||||
resolvedEndpointCredentials.sshKey
|
||||
) {
|
||||
const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
||||
tunnelCmd = `echo '${resolvedEndpointCredentials.sshKey}' > ${keyFilePath} && chmod 600 ${keyFilePath} && exec -a "${tunnelMarker}" ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} && rm -f ${keyFilePath}`;
|
||||
tunnelCmd = `echo '${resolvedEndpointCredentials.sshKey}' > ${keyFilePath} && chmod 600 ${keyFilePath} && exec -a "${tunnelMarker}" ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes ${tunnelFlag} ${portMapping} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} && rm -f ${keyFilePath}`;
|
||||
} else {
|
||||
tunnelCmd = `exec -a "${tunnelMarker}" sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}`;
|
||||
tunnelCmd = `exec -a "${tunnelMarker}" sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes ${tunnelFlag} ${portMapping} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}`;
|
||||
}
|
||||
|
||||
conn.exec(tunnelCmd, (err, stream) => {
|
||||
@@ -1302,7 +1309,9 @@ async function killRemoteTunnelByMarker(
|
||||
}
|
||||
|
||||
conn.on("ready", () => {
|
||||
const checkCmd = `ps aux | grep -E '(${tunnelMarker}|ssh.*-R.*${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}|sshpass.*ssh.*-R.*${tunnelConfig.endpointPort})' | grep -v grep`;
|
||||
const tunnelType = tunnelConfig.tunnelType || "remote";
|
||||
const tunnelFlag = tunnelType === "local" ? "-L" : "-R";
|
||||
const checkCmd = `ps aux | grep -E '(${tunnelMarker}|ssh.*${tunnelFlag}.*${tunnelConfig.endpointPort}:.*:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}|sshpass.*ssh.*${tunnelFlag})' | grep -v grep`;
|
||||
|
||||
conn.exec(checkCmd, (_err, stream) => {
|
||||
let foundProcesses = false;
|
||||
@@ -1323,8 +1332,8 @@ async function killRemoteTunnelByMarker(
|
||||
|
||||
const killCmds = [
|
||||
`pkill -TERM -f '${tunnelMarker}'`,
|
||||
`sleep 1 && pkill -f 'ssh.*-R.*${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}'`,
|
||||
`sleep 1 && pkill -f 'sshpass.*ssh.*-R.*${tunnelConfig.endpointPort}'`,
|
||||
`sleep 1 && pkill -f 'ssh.*${tunnelFlag}.*${tunnelConfig.endpointPort}:.*:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}'`,
|
||||
`sleep 1 && pkill -f 'sshpass.*ssh.*${tunnelFlag}.*${tunnelConfig.endpointPort}'`,
|
||||
`sleep 2 && pkill -9 -f '${tunnelMarker}'`,
|
||||
];
|
||||
|
||||
@@ -1929,6 +1938,7 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
tunnelConnection.endpointHost,
|
||||
tunnelConnection.endpointPort,
|
||||
),
|
||||
tunnelType: tunnelConnection.tunnelType || "remote",
|
||||
sourceHostId: host.id,
|
||||
tunnelIndex: tunnelIndex,
|
||||
hostName: host.name || `${host.username}@${host.ip}`,
|
||||
|
||||
@@ -891,6 +891,13 @@
|
||||
"autoStartContainer": "Auto Start on Container Launch",
|
||||
"autoStartDesc": "Automatically start this tunnel when the container launches",
|
||||
"addConnection": "Add Tunnel Connection",
|
||||
"tunnelType": "Tunnel Type",
|
||||
"tunnelTypeLocal": "Local (-L)",
|
||||
"tunnelTypeRemote": "Remote (-R)",
|
||||
"tunnelTypeLocalDesc": "Forward local port to remote endpoint",
|
||||
"tunnelTypeRemoteDesc": "Forward remote port to local machine",
|
||||
"tunnelForwardDescriptionLocal": "This tunnel will forward traffic from local port {{sourcePort}} to port {{endpointPort}} on the endpoint machine.",
|
||||
"tunnelForwardDescriptionRemote": "This tunnel will forward traffic from port {{sourcePort}} on the source machine (current connection details in general tab) to port {{endpointPort}} on the endpoint machine.",
|
||||
"sshpassRequired": "Sshpass Required For Password Authentication",
|
||||
"sshpassRequiredDesc": "For password authentication in tunnels, sshpass must be installed on the system.",
|
||||
"otherInstallMethods": "Other installation methods:",
|
||||
|
||||
@@ -203,6 +203,7 @@ export interface CredentialData {
|
||||
// ============================================================================
|
||||
|
||||
export interface TunnelConnection {
|
||||
tunnelType?: "local" | "remote";
|
||||
sourcePort: number;
|
||||
endpointPort: number;
|
||||
endpointHost: string;
|
||||
@@ -220,6 +221,7 @@ export interface TunnelConnection {
|
||||
|
||||
export interface TunnelConfig {
|
||||
name: string;
|
||||
tunnelType?: "local" | "remote";
|
||||
|
||||
sourceHostId: number;
|
||||
tunnelIndex: number;
|
||||
|
||||
@@ -293,9 +293,18 @@ export function HostManagerEditor({
|
||||
tunnelConnections: z
|
||||
.array(
|
||||
z.object({
|
||||
tunnelType: z
|
||||
.enum(["local", "remote"])
|
||||
.default("remote")
|
||||
.optional(),
|
||||
sourcePort: z.coerce.number().min(1).max(65535),
|
||||
endpointPort: z.coerce.number().min(1).max(65535),
|
||||
endpointHost: z.string().min(1),
|
||||
endpointPassword: z.string().optional(),
|
||||
endpointKey: z.string().optional(),
|
||||
endpointKeyPassword: z.string().optional(),
|
||||
endpointAuthType: z.string().optional(),
|
||||
endpointKeyType: z.string().optional(),
|
||||
maxRetries: z.coerce.number().min(0).max(100).default(3),
|
||||
retryInterval: z.coerce.number().min(1).max(3600).default(10),
|
||||
autoStart: z.boolean().default(false),
|
||||
@@ -667,7 +676,10 @@ export function HostManagerEditor({
|
||||
enableFileManager: Boolean(cleanedHost.enableFileManager),
|
||||
defaultPath: cleanedHost.defaultPath || "/",
|
||||
tunnelConnections: Array.isArray(cleanedHost.tunnelConnections)
|
||||
? cleanedHost.tunnelConnections
|
||||
? cleanedHost.tunnelConnections.map((conn: any) => ({
|
||||
...conn,
|
||||
tunnelType: conn.tunnelType || "remote",
|
||||
}))
|
||||
: [],
|
||||
jumpHosts: Array.isArray(cleanedHost.jumpHosts)
|
||||
? cleanedHost.jumpHosts
|
||||
|
||||
@@ -142,6 +142,57 @@ export function HostTunnelTab({
|
||||
{t("hosts.remove")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`tunnelConnections.${index}.tunnelType`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("hosts.tunnelType")}</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex gap-6">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
value="local"
|
||||
checked={field.value === "local"}
|
||||
onChange={() => field.onChange("local")}
|
||||
className="w-4 h-4 text-primary border-input focus:ring-ring"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium">
|
||||
{t("hosts.tunnelTypeLocal")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("hosts.tunnelTypeLocalDesc")}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
value="remote"
|
||||
checked={field.value === "remote"}
|
||||
onChange={() =>
|
||||
field.onChange("remote")
|
||||
}
|
||||
className="w-4 h-4 text-primary border-input focus:ring-ring"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium">
|
||||
{t("hosts.tunnelTypeRemote")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("hosts.tunnelTypeRemoteDesc")}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -254,16 +305,29 @@ export function HostTunnelTab({
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{t("hosts.tunnelForwardDescription", {
|
||||
sourcePort:
|
||||
form.watch(
|
||||
`tunnelConnections.${index}.sourcePort`,
|
||||
) || "22",
|
||||
endpointPort:
|
||||
form.watch(
|
||||
`tunnelConnections.${index}.endpointPort`,
|
||||
) || "224",
|
||||
})}
|
||||
{form.watch(
|
||||
`tunnelConnections.${index}.tunnelType`,
|
||||
) === "local"
|
||||
? t("hosts.tunnelForwardDescriptionLocal", {
|
||||
sourcePort:
|
||||
form.watch(
|
||||
`tunnelConnections.${index}.sourcePort`,
|
||||
) || "22",
|
||||
endpointPort:
|
||||
form.watch(
|
||||
`tunnelConnections.${index}.endpointPort`,
|
||||
) || "224",
|
||||
})
|
||||
: t("hosts.tunnelForwardDescriptionRemote", {
|
||||
sourcePort:
|
||||
form.watch(
|
||||
`tunnelConnections.${index}.sourcePort`,
|
||||
) || "22",
|
||||
endpointPort:
|
||||
form.watch(
|
||||
`tunnelConnections.${index}.endpointPort`,
|
||||
) || "224",
|
||||
})}
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4 mt-4">
|
||||
@@ -337,6 +401,7 @@ export function HostTunnelTab({
|
||||
field.onChange([
|
||||
...field.value,
|
||||
{
|
||||
tunnelType: "remote",
|
||||
sourcePort: 22,
|
||||
endpointPort: 224,
|
||||
endpointHost: "",
|
||||
|
||||
Reference in New Issue
Block a user