Skip to main content
Glama
ssh-tunnel.ts5.04 kB
import { Client, ConnectConfig } from 'ssh2'; import { readFileSync } from 'fs'; import { Server, createServer } from 'net'; import type { SSHTunnelConfig, SSHTunnelOptions, SSHTunnelInfo } from '../types/ssh.js'; /** * SSH Tunnel implementation for secure database connections */ export class SSHTunnel { private sshClient: Client | null = null; private localServer: Server | null = null; private tunnelInfo: SSHTunnelInfo | null = null; private isConnected: boolean = false; /** * Establish an SSH tunnel * @param config SSH connection configuration * @param options Tunnel options including target host and port * @returns Promise resolving to tunnel information including local port */ async establish( config: SSHTunnelConfig, options: SSHTunnelOptions ): Promise<SSHTunnelInfo> { if (this.isConnected) { throw new Error('SSH tunnel is already established'); } return new Promise((resolve, reject) => { this.sshClient = new Client(); // Build SSH connection config const sshConfig: ConnectConfig = { host: config.host, port: config.port || 22, username: config.username, }; // Configure authentication if (config.password) { sshConfig.password = config.password; } else if (config.privateKey) { try { const privateKey = readFileSync(config.privateKey); sshConfig.privateKey = privateKey; if (config.passphrase) { sshConfig.passphrase = config.passphrase; } } catch (error) { reject(new Error(`Failed to read private key file: ${error instanceof Error ? error.message : String(error)}`)); return; } } else { reject(new Error('Either password or privateKey must be provided for SSH authentication')); return; } // Handle SSH connection errors this.sshClient.on('error', (err) => { this.cleanup(); reject(new Error(`SSH connection error: ${err.message}`)); }); // When SSH connection is ready, create the tunnel this.sshClient.on('ready', () => { console.error('SSH connection established'); // Create local server for the tunnel this.localServer = createServer((localSocket) => { this.sshClient!.forwardOut( '127.0.0.1', 0, options.targetHost, options.targetPort, (err, stream) => { if (err) { console.error('SSH forward error:', err); localSocket.end(); return; } // Pipe data between local socket and SSH stream localSocket.pipe(stream).pipe(localSocket); // Handle stream errors stream.on('error', (err) => { console.error('SSH stream error:', err); localSocket.end(); }); localSocket.on('error', (err) => { console.error('Local socket error:', err); stream.end(); }); } ); }); // Listen on local port const localPort = options.localPort || 0; this.localServer.listen(localPort, '127.0.0.1', () => { const address = this.localServer!.address(); if (!address || typeof address === 'string') { this.cleanup(); reject(new Error('Failed to get local server address')); return; } this.tunnelInfo = { localPort: address.port, targetHost: options.targetHost, targetPort: options.targetPort, }; this.isConnected = true; console.error(`SSH tunnel established: localhost:${address.port} -> ${options.targetHost}:${options.targetPort}`); resolve(this.tunnelInfo); }); // Handle local server errors this.localServer.on('error', (err) => { this.cleanup(); reject(new Error(`Local server error: ${err.message}`)); }); }); // Connect to SSH server this.sshClient.connect(sshConfig); }); } /** * Close the SSH tunnel and clean up resources */ async close(): Promise<void> { if (!this.isConnected) { return; } return new Promise((resolve) => { this.cleanup(); this.isConnected = false; console.error('SSH tunnel closed'); resolve(); }); } /** * Clean up resources */ private cleanup(): void { if (this.localServer) { this.localServer.close(); this.localServer = null; } if (this.sshClient) { this.sshClient.end(); this.sshClient = null; } this.tunnelInfo = null; } /** * Get current tunnel information */ getTunnelInfo(): SSHTunnelInfo | null { return this.tunnelInfo; } /** * Check if tunnel is connected */ getIsConnected(): boolean { return this.isConnected; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bytebase/dbhub'

If you have feedback or need assistance with the MCP directory API, please join our Discord server