A PHP package to execute commands via SSH
Tags: php, ssh
Published: 2020-02-13 12:08:06

Our team released a new package called [spatie/ssh](https://github.com/spatie/ssh). This package allows you to execute commands via an SSH connection.

<!--more-->

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

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

It will return an instance of [Symfony's `Process`](https://symfony.com/doc/current/components/process.html).

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

```php
$process->isSuccessful();
```

And this is how you can get the output:

```php
$process->getOutput();
```

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

```php
$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](https://github.com/spatie/ssh), 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](https://github.com/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:

```php
/** @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.

```php
/** @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](https://freek.dev/1571-manage-docker-containers-using-php). 

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

## In closing

[spatie/ssh](https://github.com/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](https://spatie.be/open-source/packages).