MonkeysLegion SSH
A fluent, developer-friendly PHP library for SSH operations. Wraps the native ext-ssh2 extension with a modern API, full type safety (PHPStan level 9), and comprehensive testing utilities.
Installation
composer require monkeyscloud/monkeyslegion-ssh
Quick Start
One-off Command Execution
use MonkeysLegion\SSH\Facades\SSH;
$result = SSH::runtime()
->to('127.0.0.1')
->port(22)
->as('forge')
->withPassword('secret')
->timeout(10)
->connect()
->execute('uname -a');
echo $result->output; // Command output
echo $result->exitCode; // Exit code (0 = success)
Using a Connection Manager
For multi-server workflows, define connection profiles once and reuse them:
use MonkeysLegion\SSH\Facades\SSH;
// Define your servers
SSH::configure([
'default' => 'production',
'connections' => [
'production' => [
'host' => '192.168.1.10',
'port' => 22,
'username' => 'deploy',
'auth' => 'password',
'password' => 'secure-password',
'timeout' => 30,
],
'staging' => [
'host' => '192.168.1.20',
'port' => 22,
'username' => 'deploy',
'auth' => 'key',
'private_key' => '/home/user/.ssh/id_rsa',
'passphrase' => 'key-passphrase', // optional
'timeout' => 30,
],
],
]);
// Use the default connection
$result = SSH::execute('ls -la /var/www');
// Switch to staging
SSH::useDefaultConnection('staging');
$result = SSH::execute('ls -la /var/www');
// Or run commands on a specific connection
$result = SSH::connection('production')->execute('whoami');
Register Connections Dynamically
SSH::register('hotfix-server', [
'host' => '192.168.1.30',
'username' => 'ops',
'auth' => 'password',
'password' => 'ops-password',
'timeout' => 15,
]);
$result = SSH::connection('hotfix-server')->execute('systemctl status app');
API Reference
Connection Methods
All methods below operate on a connection obtained via SSH::connection() or SSH::runtime().
connect() → void
Establishes the SSH connection. This must be called before executing commands.
✅ Establishes connection: Yes
❌ Closes connection on return: No — the connection remains open for subsequent operations
When to use: Explicitly called at the end of a fluent builder chain
$connection = SSH::runtime()
->to('192.168.1.10')
->as('deploy')
->withPassword('secret')
->connect(); // Connection is now open
execute(string $command) → CommandResult
Runs a single command on the open connection.
❌ Establishes connection: No — requires an already-open connection
❌ Closes connection on return: No — connection stays open for more commands
Returns:
CommandResultwithoutput,error, andexitCodeproperties
$result = SSH::execute('whoami');
echo $result->output; // 'deploy'
echo $result->exitCode; // 0
echo $result->error; // '' (empty if no error)
isConnected() → bool
Checks if the connection is currently open.
❌ Establishes connection: No
❌ Closes connection on return: No
Returns:
trueif connected,falseotherwise
if (SSH::isConnected()) {
echo "Connected!";
}
disconnect() → void
Closes the SSH connection and releases resources.
❌ Establishes connection: No
✅ Closes connection on return: Yes — connection is terminated
When to use: Clean up after you're done with the connection
SSH::execute('ls');
SSH::disconnect(); // Connection closed
pipeline(...$steps) → PipelineResult
Executes multiple commands in sequence with state sharing. If one command fails (non-zero exit code), the pipeline can stop immediately or continue based on configuration.
❌ Establishes connection: No — requires an already-open connection
❌ Closes connection on return: No — connection stays open
Returns:
PipelineResultwith execution history and metrics
$result = SSH::pipeline(
'cd /var/www',
'git pull',
'composer install',
'php artisan migrate'
);
if ($result->halted) {
echo "Pipeline stopped at: " . $result->failedAt;
}
foreach ($result->results as $cmd => $output) {
echo "$cmd: $output\n";
}
sftp() → SFTPClient
Opens an SFTP channel for file operations (upload, download, mkdir, chmod, delete). Returns an SFTPClient instance with transfer metrics.
❌ Establishes connection: No — reuses the existing SSH connection
❌ Closes connection on return: No — SFTP session stays open
Returns:
SFTPClientwith transfer and directory methods
$sftp = SSH::sftp();
// Upload a file
$sftp->upload('/local/file.txt', '/remote/file.txt');
// Download a file
$sftp->download('/remote/backup.sql', '/local/backup.sql');
// Create directories
$sftp->mkdir('/var/www/uploads');
// Set permissions (octal)
$sftp->chmod('/var/www/uploads', 0755);
// Delete a file
$sftp->delete('/var/www/temp.log');
// Access metrics
echo $sftp->metrics->uploadedBytes;
echo $sftp->metrics->downloadedBytes;
echo $sftp->metrics->operationCount;
Facade Methods
Static methods on the SSH class for global configuration and connection management.
SSH::configure(array $config) → void
Sets up the connection manager with profiles. Wipes any existing connections.
Config keys:
default(string): Which profile to use by defaultconnections(array): Profile name → config array mapping
SSH::configure([
'default' => 'prod',
'connections' => [
'prod' => ['host' => '192.168.1.10', 'username' => 'deploy', ...],
],
]);
SSH::register(string $name, array $profile) → void
Registers a new connection profile without resetting existing ones.
SSH::register('backup-server', [
'host' => '192.168.1.50',
'username' => 'backup',
'auth' => 'key',
'private_key' => '/home/user/.ssh/backup_key',
]);
SSH::useDefaultConnection(string $name) → void
Switches the default connection. Affects all static SSH::execute() calls.
SSH::useDefaultConnection('staging');
SSH::execute('whoami'); // Runs on staging
SSH::connection(?string $name = null) → SSHConnection
Retrieves a connection by name. If $name is null, returns the default connection.
$prod = SSH::connection('production');
$result = $prod->execute('whoami');
SSH::forgetConnection(?string $name = null) → void
Closes and removes a cached connection. If $name is null, clears all cached connections.
SSH::forgetConnection('staging'); // Closes staging
SSH::forgetConnection(); // Closes all
SSH::runtime() → ConnectionBuilder
Returns a fluent builder for one-off connections without profiles.
$result = SSH::runtime()
->to('example.com')
->as('user')
->withPassword('pass')
->connect()
->execute('whoami');
Testing Utilities
The library provides socket-free fakes for deterministic testing.
SSH::fake(array $responses = []) → SSHFakeRegistry
Enables fake mode. Subsequent commands don't touch the network; responses are stubbed.
SSH::fake([
'whoami' => new CommandResult('forge', '', 0),
'pwd' => new CommandResult('/home/forge', '', 0),
]);
$result = SSH::execute('whoami');
assert($result->output === 'forge');
SSH::fakeCommand(string $command, CommandResult $result) → void
Adds or overrides a command stub while in fake mode.
SSH::fake();
SSH::fakeCommand('git status', new CommandResult('On branch main', '', 0));
SSH::fakeDefault(CommandResult $result) → void
Sets a fallback response for unmapped commands in fake mode.
SSH::fake();
SSH::fakeDefault(new CommandResult('', 'command not found', 127));
$unknownCmd = SSH::execute('unknown-cmd');
assert($unknownCmd->exitCode === 127);
SSH::assertExecuted(string $command) → void
Verifies a command was executed while in fake mode. Throws if not found.
SSH::fake();
SSH::fakeCommand('backup', new CommandResult('OK', '', 0));
SSH::execute('backup');
SSH::assertExecuted('backup'); // Passes
SSH::assertExecuted('other'); // Throws RuntimeException
Exception Factories
Quick constructors for common SSH errors.
use MonkeysLegion\SSH\Exceptions\ConnectionRefusedException;
use MonkeysLegion\SSH\Exceptions\AuthenticationFailedException;
// Connection refused
throw ConnectionRefusedException::forHost('192.168.1.10', 22);
// Auth failures
throw AuthenticationFailedException::password('deploy');
throw AuthenticationFailedException::publicKey('deploy', '/home/.ssh/key');
Configuration
Create a config file at config/ssh.php:
return [
'default' => 'production',
'connections' => [
'production' => [
'host' => env('SSH_HOST', '127.0.0.1'),
'port' => env('SSH_PORT', 22),
'username' => env('SSH_USERNAME', 'forge'),
'auth' => env('SSH_AUTH', 'password'), // 'password' or 'key'
'password' => env('SSH_PASSWORD', ''),
'private_key' => env('SSH_PRIVATE_KEY', ''),
'passphrase' => env('SSH_PASSPHRASE', ''),
'timeout' => env('SSH_TIMEOUT', 30),
],
],
];
Then load it:
SSH::configure(require __DIR__ . '/config/ssh.php');
Testing
See TESTING.md for comprehensive test documentation, including Docker-backed integration testing, PHPStan level 9 analysis, and fake mode usage.
Related Packages
Attribute-driven object↔JSON/XML serializer for MonkeysLegion: property hooks, readonly DTOs, normalizer pipeline, naming strategies, PHPStan Level 9.
Standalone encryption for MonkeysLegion v2 — AES-GCM, XChaCha20, HMAC, key rotation, envelope encryption, PHP 8.4 hooks
ORM-agnostic offset + cursor pagination — PHP 8.4 property hooks, RFC 8288 Link headers, JSON:API links, configurable envelopes