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