SSH Target Overview
Implementation Referenceβ
Source Files:
packages/core/src/adapters/ssh-adapter.ts
- SSH execution adapterpackages/core/src/ssh/ssh-client.ts
- SSH client implementationpackages/core/src/ssh/connection-pool.ts
- Connection pooling logicpackages/core/src/ssh/port-forwarding.ts
- SSH tunnelingpackages/core/src/ssh/auth.ts
- Authentication methodsapps/xec/src/config/types.ts
- SSH target configuration (lines 53-75)
Key Classes:
SSHAdapter
- SSH command execution adapterSSHClient
- Core SSH client wrapperSSHConnectionPool
- Connection pool managerSSHTunnel
- Port forwarding implementation
Key Functions:
SSHAdapter.execute()
- Execute commands via SSH (lines 30-95)SSHConnectionPool.acquire()
- Get pooled connection (lines 45-78)SSHConnectionPool.release()
- Return connection to pool (lines 80-95)SSHClient.connect()
- Establish SSH connection (lines 25-68)createTunnel()
- Create SSH tunnel (lines 15-42)
Overviewβ
SSH targets enable remote command execution on servers via SSH protocol. Xec provides advanced SSH features including connection pooling, automatic reconnection, port forwarding, and secure credential management.
Target Configurationβ
Basic SSH Targetβ
# .xec/config.yaml
targets:
production:
type: ssh
host: prod.example.com
user: deploy
port: 22 # Optional, default: 22
staging:
type: ssh
host: staging.example.com
user: ubuntu
privateKey: ~/.ssh/id_rsa # Key authentication
Advanced Configurationβ
targets:
secure-server:
type: ssh
host: secure.example.com
user: admin
port: 2222
privateKey: ~/.ssh/deploy_key
passphrase: ${SSH_KEY_PASSPHRASE} # From environment
# Connection options
connectionTimeout: 30000 # 30 seconds
readyTimeout: 10000 # 10 seconds
keepaliveInterval: 5000 # 5 seconds
# Retry configuration
retries: 3
retryDelay: 1000
# Jump host (bastion)
jumpHost:
host: bastion.example.com
user: jump-user
privateKey: ~/.ssh/bastion_key
# Environment
env:
LANG: en_US.UTF-8
TERM: xterm-256color
Connection Managementβ
Connection Poolingβ
SSH connections are automatically pooled for performance (from connection-pool.ts
):
// Pool configuration
const poolConfig = {
maxConnections: 10, // Maximum connections per host
minConnections: 1, // Minimum idle connections
idleTimeout: 300000, // 5 minutes idle timeout
acquisitionTimeout: 30000, // 30 seconds to acquire
testOnBorrow: true // Test connection before use
};
Pool Behavior:
- Connections are reused across commands
- Idle connections are maintained (min pool size)
- Connections are tested before use
- Failed connections are automatically replaced
- Pool grows on demand up to max size
Connection Lifecycleβ
// Automatic pooling (recommended)
await $.ssh('user@host')`ls -la`; // Creates/reuses connection
await $.ssh('user@host')`pwd`; // Reuses same connection
// Manual connection management
const ssh = await SSHClient.connect({
host: 'example.com',
user: 'admin',
privateKey: readFileSync('~/.ssh/id_rsa')
});
try {
await ssh.exec('ls -la');
await ssh.exec('pwd');
} finally {
await ssh.disconnect();
}
Authentication Methodsβ
Private Key Authenticationβ
targets:
key-auth:
type: ssh
host: server.example.com
user: deploy
privateKey: ~/.ssh/id_rsa # Path to key file
passphrase: ${SSH_PASSPHRASE} # Optional key passphrase
// Programmatic key auth
await $.ssh({
host: 'server.example.com',
user: 'deploy',
privateKey: await fs.readFile('~/.ssh/id_rsa', 'utf8'),
passphrase: process.env.SSH_PASSPHRASE
})`command`;
Password Authenticationβ
targets:
password-auth:
type: ssh
host: server.example.com
user: admin
password: ${SSH_PASSWORD} # From environment variable
Security Note: Avoid hardcoding passwords. Use environment variables or secret management.
SSH Agentβ
targets:
agent-auth:
type: ssh
host: server.example.com
user: deploy
agent: true # Use SSH agent (default: true if available)
agentSocket: ${SSH_AUTH_SOCK} # Optional custom socket
Multiple Authentication Methodsβ
// Try multiple auth methods (order: agent β key β password)
await $.ssh({
host: 'server.example.com',
user: 'admin',
agent: true, // Try agent first
privateKey: '~/.ssh/id_rsa', // Then try key
password: process.env.FALLBACK_PASSWORD // Finally password
})`command`;
Command Executionβ
Basic Executionβ
// Simple command
await $.ssh('user@host')`ls -la`;
// With configuration object
await $.ssh({
host: 'server.example.com',
user: 'deploy'
})`df -h`;
// Using configured target
await $.target('production')`systemctl status nginx`;
Advanced Executionβ
// With environment variables
await $.ssh('user@host').env({
NODE_ENV: 'production',
PORT: '3000'
})`npm start`;
// With working directory
await $.ssh('user@host').cwd('/app')`git pull`;
// With timeout
await $.ssh('user@host').timeout(60000)`./long-script.sh`;
// With sudo
await $.ssh('user@host')`sudo systemctl restart nginx`;
Stream Processingβ
// Stream output
await $.ssh('user@host')`tail -f /var/log/app.log`
.pipe(process.stdout);
// Process lines
await $.ssh('user@host')`cat large-file.txt`
.lines(async (line) => {
console.log(`Processing: ${line}`);
});
// Pipe between commands
await $.ssh('user@host')`cat data.csv`
.pipe($.ssh('user@host2')`import-data`);
File Transferβ
Upload Filesβ
// Upload single file
await $.ssh('user@host').upload('local-file.txt', '/remote/path/file.txt');
// Upload directory
await $.ssh('user@host').upload('./dist/', '/var/www/html/');
// Upload with progress
await $.ssh('user@host').upload('large-file.zip', '/tmp/', {
onProgress: (transferred, total) => {
console.log(`Uploaded: ${transferred}/${total} bytes`);
}
});
Download Filesβ
// Download single file
await $.ssh('user@host').download('/remote/file.txt', './local-file.txt');
// Download directory
await $.ssh('user@host').download('/var/log/', './logs/');
// Download with compression
await $.ssh('user@host')`tar czf - /app/`
.pipe(fs.createWriteStream('backup.tar.gz'));
Port Forwardingβ
Local Port Forwardingβ
// Forward local port to remote service
const tunnel = await $.ssh('user@host').forward({
localPort: 8080,
remoteHost: 'localhost',
remotePort: 3000
});
// Access remote service locally
await fetch('http://localhost:8080');
// Close tunnel
await tunnel.close();
Dynamic Port Forwarding (SOCKS)β
// Create SOCKS proxy
const proxy = await $.ssh('user@host').socks({
port: 1080
});
// Use proxy for HTTP requests
const agent = new SocksProxyAgent('socks://localhost:1080');
await fetch('http://internal-service', { agent });
await proxy.close();
Reverse Port Forwardingβ
// Expose local service to remote
const tunnel = await $.ssh('user@host').reverseForward({
remotePort: 8080,
localHost: 'localhost',
localPort: 3000
});
// Remote can now access local service
Jump Hosts (Bastion)β
Configurationβ
targets:
private-server:
type: ssh
host: 10.0.1.50 # Private IP
user: admin
jumpHost:
host: bastion.example.com
user: jump-user
privateKey: ~/.ssh/bastion_key
Programmatic Jump Hostβ
// Connect through bastion
await $.ssh({
host: '10.0.1.50',
user: 'admin',
jumpHost: {
host: 'bastion.example.com',
user: 'jump-user',
privateKey: '~/.ssh/bastion_key'
}
})`command`;
// Multiple jump hosts
await $.ssh({
host: 'final-destination',
jumpHosts: [
{ host: 'jump1.example.com', user: 'user1' },
{ host: 'jump2.example.com', user: 'user2' }
]
})`command`;
Performance Characteristicsβ
Connection Performanceβ
Based on implementation measurements:
Operation | First Time | Pooled | Notes |
---|---|---|---|
Connection | 200-500ms | <10ms | Pooled connections ready instantly |
Authentication | 100-300ms | 0ms | Already authenticated |
Command Execution | +5-20ms | +5-20ms | Network latency |
File Transfer | Varies | Varies | Depends on file size and bandwidth |
Optimization Strategiesβ
- Connection Pooling (automatic):
// All commands to same host share connection
for (const cmd of commands) {
await $.ssh('user@host')`${cmd}`; // Reuses connection
}
- Batch Commands:
// Multiple commands in one round-trip
await $.ssh('user@host')`
cd /app &&
git pull &&
npm install &&
npm run build
`;
- Parallel Execution:
// Execute on multiple hosts in parallel
const hosts = ['host1', 'host2', 'host3'];
await Promise.all(
hosts.map(host => $.ssh(`user@${host}`)`uptime`)
);
- Keep-Alive:
targets:
long-running:
type: ssh
keepaliveInterval: 10000 # Send keep-alive every 10s
keepaliveCountMax: 3 # Disconnect after 3 failed
Error Handlingβ
Connection Errorsβ
try {
await $.ssh('user@host')`command`;
} catch (error) {
if (error.code === 'ECONNREFUSED') {
console.error('SSH service not running');
} else if (error.code === 'ETIMEDOUT') {
console.error('Connection timeout');
} else if (error.code === 'EAUTH') {
console.error('Authentication failed');
}
}
Automatic Retryβ
// Configure retry behavior
await $.ssh({
host: 'unreliable.example.com',
user: 'admin',
retries: 3,
retryDelay: 2000, // 2 seconds between retries
retryOn: ['ECONNRESET', 'ETIMEDOUT'] // Retry on specific errors
})`command`;
Security Best Practicesβ
Credential Managementβ
- Never hardcode credentials
- Use SSH keys over passwords
- Store keys securely
- Use SSH agent when possible
- Rotate keys regularly
Host Verificationβ
targets:
secure:
type: ssh
host: server.example.com
strictHostKeyChecking: true # Verify host key
knownHostsFile: ~/.ssh/known_hosts
Secure Configurationβ
// Secure SSH configuration
await $.ssh({
host: 'secure.example.com',
user: 'admin',
privateKey: process.env.SSH_KEY, // From environment
passphrase: process.env.SSH_PASSPHRASE,
strictHostKeyChecking: true,
algorithms: {
cipher: ['aes256-gcm', 'aes128-gcm'],
kex: ['ecdh-sha2-nistp256', 'ecdh-sha2-nistp384'],
serverHostKey: ['ssh-rsa', 'ssh-ed25519']
}
})`command`;
Troubleshootingβ
Debug Modeβ
// Enable SSH debug output
process.env.DEBUG = 'ssh2,xec:ssh:*';
await $.ssh('user@host').debug()`command`;
Common Issuesβ
- Connection Refused: Check SSH service, firewall, port
- Authentication Failed: Verify credentials, key permissions
- Timeout: Check network, increase timeout values
- Host Key Verification: Update known_hosts file
Related Documentationβ
- Connection Configuration - Detailed connection setup
- Authentication - Authentication methods
- Tunneling - Port forwarding and tunnels
- Batch Operations - Multi-host execution
- SSH Adapter API - API reference