Index

SSH

MMO\core\server · 1 symbol · generated 30 Sep 2026

class

SSH

SSH.php:18

A remote shell over SSH (wraps the ssh2 PECL extension).

class MMO\core\server\SSH implements MMO\core\interfaces\iRemoteShell
Namespace
MMO\core\server
Implements
iRemoteShell
# Installation sudo apt install php-ssh2
$ssh = new SSH('user', 'example.com', 22, 'password'); $ssh->exec('ls -la', '/tmp'); // connects lazily on first use $ssh->send('/local/file.txt', '/remote/file.txt'); $job = $ssh->execBackground('long-import.sh'); // doesn't wait: @see SSHJob $ssh->disconnect();

Index

failOnStderr
fingerprintServer identity fingerprint
handle
host
jobsFolder
password
port
privateKey
publicKey
useAgent
user
checkFingerprint()Verify the server's host key against $fingerprint, and refuse to go any further if it doesn't match -- or if it cannot be checked at all.
connect()Open the SSH session and authenticate, per $useAgent/$publicKey/$password/$fingerprint.
debugName()
disconnect()
exec($cmd, $cwd, $exitCode)Run a command remotely and wait for it to finish.
execBackground($cmd, $cwd, $env, $id)Run a command remotely *without waiting for it*, detached from this session: it survives the end of the command, the end of the SSH connection and the end of this PHP process, and its output, exit …
execSafe($cmd, $error, $cwd, $exitCode)Run a command remotely and wait for it to finish, never throwing on a non-zero exit code or stderr output (exec for that).
expectedFingerprint()
getJob($id)Bind back to a job started earlier (by anybody, including another process), by id.
getJobs()Every job still present under $jobsFolder on the remote host, finished ones included (a job's folder stays until somebody calls SSHJob::clean).
isConnected()
lastError()
onDebug($message, $language, $always_display)ssh2_connect()'s 'debug' callback -- logs every SSH protocol debug message via debug.
onDisconnect($reason, $message, $language)ssh2_connect()'s 'disconnect' callback -- logs the disconnect and clears $handle so isConnected reports false.
openExec($cmd, $cwd)Run a command remotely, returning a live stream to its stdout/stderr (SSHStream) -- unlike exec /
openShell($type, $env, $width, $height, $sizeType)Create a stream to shell
quote($value) staticQuote a value for a *remote POSIX* shell: single quotes, always, with any embedded single quote closed, escaped and reopened -- the only form a POSIX shell interprets nothing inside of.
receive($remoteFile, $localFile)Receive ssh file
send($localFile, $remoteFile, $mode)Send file over ssh
__construct($user, $host, $port, $password, $fingerprint, $publicKey, $privateKey)
__destruct()Destructor. Disconnects if still connected.

Properties11

failOnStderr

bool $failOnStderr = false
Type
bool

fingerprint

Server identity fingerprint

?string $fingerprint = null
Type
null | string

handle

protected $handle = null

jobsFolder

string $jobsFolder = '/tmp/.mmo-jobs'
Type
string

port

int $port
Type
int

privateKey

?string $privateKey = null
Type
null | string

publicKey

?string $publicKey = null
Type
null | string

useAgent

bool $useAgent = false
Type
bool

Methods21

checkFingerprint()

SSH.php:167

Verify the server's host key against $fingerprint, and refuse to go any further if it doesn't match -- or if it cannot be checked at all.

protected function checkFingerprint(): voidNOTE: this used to `strcmp()` the given fingerprint against `ssh2_fingerprint()`'s output, which is UPPERCASE hex with no separators. Every tool prints the thing in another shape (`ssh-keygen -E md5` gives lowercase, colon-separated), so a *correct* fingerprint in any of those shapes was rejected -- and, worse, the whole check was skipped when `ssh2_fingerprint()` returned nothing (its result was one of the `&&` conditions guarding the throw), i.e. it failed open exactly when it couldn't verify anything.
returnsvoid
throwsif $fingerprint isn't an MD5 or SHA1 hex digest (expectedFingerprint).
throwsif the fingerprint cannot be read, or doesn't match.

connect()

SSH.php:107

Open the SSH session and authenticate, per $useAgent/$publicKey/$password/$fingerprint.

function connect(): voidReconnects (disconnecting first) if already connected. Every other method that needs a live connection calls this automatically on demand, so calling it directly is only needed to force authentication up front. Leaves nothing behind on failure: the session is closed again, and [`isConnected`](https://mmoframework.fr/documentation/mmo.core/php/server/SSH.md#ssh-isconnected) reports false, so a caller that catches the exception and retries starts from a clean slate.
returnsvoid
throwsif the connection, the host key verification, or the authentication fails.
throwsif there is no usable way to log in, or the key files are unreadable.

debugName()

SSH.php:79
function debugName(): string
returnsstringe.g. 'SSH://user@example.com:22'.

disconnect()

SSH.php:225
function disconnect(): bool
returnsbooltrue if a connection was actually closed; false if there wasn't one to close.

exec($cmd, $cwd, $exitCode)

SSH.php:318

Run a command remotely and wait for it to finish.

function exec(string $cmd, ?string $cwd = null, ?int &$exitCode = null): string
See
Shell::exec (exit code), which is what makes the shared iShell contract mean the same thing locally and remotely.
$cmdstring
$cwd = nullnull | stringthe remote working directory to run it from.
$exitCode = nullnull | int
returnsstringthe command's stdout output.
throwsif the command failed: a non-zero exit code, or -- only when the remote host reported no exit code at all, or when $failOnStderr is on -- any stderr output. NOTE: this used to key off stderr alone, which both threw on the progress output of every normal git/tar/ curl invocation and let a command that exited 3 silently look like a success. It now matches

execBackground($cmd, $cwd, $env, $id)

SSH.php:349

Run a command remotely *without waiting for it*, detached from this session: it survives the end of the command, the end of the SSH connection and the end of this PHP process, and its output, exit code and liveness can be read back at any time -- from another request or another machine -- through its

function execBackground(string $cmd, ?string $cwd = null, ?array $env = null, ?string $id = null): MMO\core\server\SSHJob
See
SSHJob::$id., openExec is the other non-blocking option, and the opposite trade-off: a live stream, but the command dies with the channel.
$cmdstringthe command -- written to a remote file verbatim and run with sh, so no escaping is needed and anything a shell accepts works (pipes, &&, quotes, several lines).
$cwd = nullnull | stringthe remote working directory to run it from.
$env = nullnull | arrayenvironment variables to export first, as name => value.
$id = nullnull | stringthe job id, defaults to a generated one (SSHJob::NewId).
returnsSSHJob
throwsif the remote host didn't confirm the launch.
$job = $ssh->execBackground('./deploy.sh 2>&1', '/var/www'); while ($job->isRunning()) {echo $job->getIncrementalOutput(); sleep(2);} echo "exit code: " . $job->getExitCode();

execSafe($cmd, $error, $cwd, $exitCode)

SSH.php:297

Run a command remotely and wait for it to finish, never throwing on a non-zero exit code or stderr output (exec for that).

function execSafe( string $cmd, ?string &$error = null, ?string $cwd = null, ?int &$exitCode = null ): string
$cmdstring
$error = nullnull | string
$cwd = nullnull | stringthe remote working directory to run it from.
$exitCode = nullnull | int
returnsstringthe command's stdout output.

expectedFingerprint()

SSH.php:190
protected function expectedFingerprint(): string
See
$fingerprint in the shape ssh2_fingerprint() returns one: UPPERCASE hex digits, no separators. Takes it as the tools print it -- MD5:ab:cd:... (ssh-keygen -l -E md5), ab:cd:..., ABCD... -- so a leading MD5: or SHA1:, colons, blanks and case are all ignored. NOTE: the label used to be read as digits too: the D5 of MD5: made an MD5 pasted as printed 34 digits long, and it was refused by a message telling to get it from ssh-keygen -E md5.
returnsstring32 hex digits (an MD5) or 40 (a SHA1).
throwsif it is anything else -- a base64 fingerprint included: the SHA256:... ssh-keygen -l prints by default (the ssh2 extension computes no SHA256) or the SHA1:... of -E sha1.
'MD5:14:5e:b2:3b:2b:57:44:bc:ba:ac:cb:f4:fa:59:3c:0d' => '145EB23B2B5744BCBAACCBF4FA593C0D'

getJob($id)

SSH.php:360

Bind back to a job started earlier (by anybody, including another process), by id.

function getJob(string $id): MMO\core\server\SSHJobCosts nothing on its own: the first method that needs the job's state goes and reads it ([`SSHJob::refresh`](https://mmoframework.fr/documentation/mmo.core/php/server/SSHJob.md#sshjob-refresh)), and reports [`cJobState::MISSING`](https://mmoframework.fr/documentation/mmo.core/php/server/cJobState.md#cjobstate-missing) if there is no such job on the host.
$idstring
returnsSSHJob

getJobs()

SSH.php:369

Every job still present under $jobsFolder on the remote host, finished ones included (a job's folder stays until somebody calls SSHJob::clean).

function getJobs(): array
returnsarraykeyed by id, oldest first (SSHJob::NewId ids sort chronologically).

isConnected()

SSH.php:220
function isConnected(): bool
returnsboolwhether there is a live session. NOTE: checks the handle is still a valid resource (a disconnected ssh2 session isn't), not merely that connect once ran -- but it cannot tell that the *server* dropped the connection: nothing notices that until the next read or write fails.

lastError()

SSH.php:210
protected function lastError(): string
returnsstringthe message of the last PHP warning (the ssh2 functions only report their real reason that way), or 'unknown reason' if there was none.

onDebug($message, $language, $always_display)

SSH.php:237

ssh2_connect()'s 'debug' callback -- logs every SSH protocol debug message via debug.

function onDebug(string $message, string $language, bool $always_display): void
$messagestring
$languagestring
$always_displaybool
returnsvoid

onDisconnect($reason, $message, $language)

SSH.php:242

ssh2_connect()'s 'disconnect' callback -- logs the disconnect and clears $handle so isConnected reports false.

function onDisconnect(int $reason, string $message, string $language): void
$reasonint
$messagestring
$languagestring
returnsvoid

openExec($cmd, $cwd)

SSH.php:276

Run a command remotely, returning a live stream to its stdout/stderr (SSHStream) -- unlike exec /

function openExec(string $cmd, ?string $cwd = null): MMO\core\server\SSHStream
See
execSafe, doesn't wait for it to finish. WARNING: "doesn't wait" only goes as far as this SSH session: the command runs *inside* the channel, so closing the stream, disconnecting, or just reaching the end of the script kills it. Use execBackground for something that has to outlive the connection.
$cmdstring
$cwd = nullnull | stringthe remote working directory to run it from -- quoted for the remote shell (quote) and prefixed as cd <cwd> && ..., since ssh2_exec takes one command string and has no working-directory argument of its own (unlike Process).
returnsSSHStream
throwsif the command couldn't be started.

openShell($type, $env, $width, $height, $sizeType)

SSH.php:256

Create a stream to shell

function openShell( string $type = 'xterm', ?array $env = null, int $width = 120, int $height = 25, int $sizeType = MMO\core\server\SSH2_TERM_UNIT_CHARS ): MMO\core\server\SSHStream
$type = 'xterm'stringterminal type
$env = nullnull | arrayenvironment variables
$width = 120intterminal width
$height = 25intterminal height
$sizeType = MMO\core\server\SSH2_TERM_UNIT_CHARSint
returnsSSHStream

quote($value)

staticSSH.php:94

Quote a value for a *remote POSIX* shell: single quotes, always, with any embedded single quote closed, escaped and reopened -- the only form a POSIX shell interprets nothing inside of.

static function quote(string $value): stringNOTE: deliberately not [`escape`](https://mmoframework.fr/documentation/mmo.core/php/shell.md#escape): that one quotes for the shell of the machine PHP runs on (`IS_WINDOWS`), which says nothing about the remote one, and it returns a value that already starts and ends with a single quote untouched -- which is exactly what an injected `'/tmp' && evil && cd '/tmp'` looks like.
$valuestring
returnsstring
SSH::quote("it's") === "'it'\\''s'"

receive($remoteFile, $localFile)

SSH.php:386

Receive ssh file

function receive(string $remoteFile, string $localFile): void
$remoteFilestring
$localFilestring
returnsvoid

send($localFile, $remoteFile, $mode)

SSH.php:400

Send file over ssh

function send(string $localFile, string $remoteFile, int $mode = 420): void
$localFilestring
$remoteFilestring
$mode = 420int
returnsvoid

__construct($user, $host, $port, $password, $fingerprint, $publicKey, $privateKey)

SSH.php:62
function __construct( string $user, string $host, int $port = 22, ?string $password = null, ?string $fingerprint = null, ?string $publicKey = null, ?string $privateKey = null )
$userstring
$hoststring
$port = 22int
$password = nullnull | stringused for password auth (connect), or to decrypt $privateKey if it's encrypted.
$fingerprint = nullnull | stringif set, connect verifies the server's host key matches this before authenticating -- an MD5 (32 hex characters) or SHA1 (40) digest, the only two the ssh2 extension can compute, in hex. A leading MD5:/SHA1:, colons and case are ignored, so MD5:ab:cd:... as ssh-keygen -l -E md5 prints it works, and so do ab:cd:..., ABCD... and abcd...; base64 does not.
$publicKey = nullnull | stringpath to a public key file -- if set, connect authenticates via key pair instead of $password.
$privateKey = nullnull | stringpath to the matching private key file (required together with $publicKey).
throwsif the ssh2 extension isn't installed.

__destruct()

SSH.php:74

Destructor. Disconnects if still connected.

function __destruct()