DEV Community

Freek Van der Herten
Freek Van der Herten

Posted on • Originally published at freek.dev

A PHP package to execute commands via SSH

Our team released a new package called spatie/ssh. This package allows you to execute commands via an SSH connection.

With the package installed, you can execute an SSH command like this:

$process = Ssh::create('user', 'example.com')->execute('your favorite command');

It will return an instance of Symfony's Process.

Here's how to check if your command ran ok:

$process->isSuccessful();

And this is how you can get the output:

$process->getOutput();

To run multiple commands, pass an array to the execute method.

$process = Ssh::create('user', 'example.com')->execute([
   'first command',
   'second command',
]);

The package has a few extra options, head over to the readme on GitHub, to learn more.

Why we built this

I'm currently building a package called spatie/laravel-backup-server. It will be the spiritual successor of spatie/laravel-backup. Laravel Backup is usually installed into the Laravel app, and it will copy that app to some other storage. Laravel Backup Server will take a different approach: it will SSH into each server that needs to be backed up and will copy all the files on to itself.

The package will also be able to, before and after the actual copying of files starts, execute some commands.

Let's take a look at how that is implemented. In laravel-backup-server a Source is a model that represents something that needs to be backed up.

On that model we have an executeSshCommands methods that looks like this:

/** @test */
public function executeSshCommands(array $commands): Process
{
    $ssh = new Ssh($this->ssh_user, $this->host);

    if ($this->ssh_port) {
        $ssh->usePort($this->ssh_port);
    }

    if ($this->ssh_private_key_file) {
        $ssh->usePrivateKey($this->ssh_private_key_file);
    }

    return $ssh->execute($commands);
}

In that method, we get some of the attributes of the model and use them to connect to a host. Inside the backup procure that
executeSshCommands is getting called.

Here's how we test that commands in the pre_backup_commands do actually succeed (there's a separate test for post_backup_comands. In the test, we create a new file using the touch command, which will be performed via SSH. Later in the test, we assert that the created file is part of the backup.

/** @test */
public function it_can_perform_a_pre_backup_command()
{
    $this->container->addFiles(__DIR__ . '/stubs/serverContent/testServer', '/src');

    $this->source->update(['pre_backup_commands' => ['cd /src', 'touch newfile.txt']]);

    $this->artisan('backup-server:backup')->assertExitCode(0);

    $this->assertTrue($this->source->backups()->first()->has('src/newfile.txt'));

    $this->assertEquals(Backup::STATUS_COMPLETED, $this->source->backups()->first()->status);
}

$container in the test above is a local docker container, which we've set up to have a target to ssh into, you can read more on that in this blog post.

Here's another test that proves the backup fails should any of the commands in the pre_backup_commands fails.

In closing

spatie/ssh is probably the easiest way to perform a quick SSH command. It doesn't have a lot of features, but that's ok. It's intended as something very lightweight. This isn't the first package our team has built. Here's a list with all the stuff we released previously.

Discussion (0)