A pipeline-based SSH runner for Laravel that executes commands on remote servers with support for action composition, failure strategies, automatic rollback, and execution logging.
This package provides a fluent API for building SSH command pipelines using the Spatie SSH library under the hood.
You can install the package via Composer:
composer require serversinc/ssh-runnerPublish the configuration file:
php artisan vendor:publish --provider="Serversinc\SshRunner\SshRunnerServiceProvider" --tag="ssh-runner-config"Publish the migrations:
php artisan vendor:publish --provider="Serversinc\SshRunner\SshRunnerServiceProvider" --tag="ssh-runner-migrations"Run the migrations to create the logging tables:
php artisan migrateYour server model must implement the SshServer contract:
use Serversinc\SshRunner\Contracts\SshServer;
class Server extends Model implements SshServer
{
public function getSshHost(): string
{
return $this->ip_address;
}
public function getSshPort(): int
{
return $this->ssh_port ?? 22;
}
public function getSshUser(): string
{
return $this->ssh_user;
}
public function getSshKeyPath(): ?string
{
return $this->ssh_key_path;
}
public function getSshKeyContents(): ?string
{
return $this->ssh_key_contents;
}
}Actions are reusable, testable units of work:
use Serversinc\SshRunner\Actions\BaseSshAction;
use Serversinc\SshRunner\Contracts\SshServer;
use Serversinc\SshRunner\Results\ActionResult;
use Spatie\Ssh\Ssh;
class InstallPackage extends BaseSshAction
{
public function __construct(private string $packageName)
{
}
public function handle(SshServer $server, Ssh $ssh): ActionResult
{
return $this->run($ssh, ["apt-get install -y {$this->packageName}"]);
}
public function undo(SshServer $server, Ssh $ssh): void
{
// Called automatically on rollback
$ssh->execute(["apt-get remove -y {$this->packageName}"]);
}
}There are several ways to execute pipelines:
use SshRunner;
use Serversinc\SshRunner\Enums\FailureStrategy;
$result = SshRunner::pipeline($server)
->run(new UpdatePackageList)
->run(new InstallPackage('nginx'))
->run(new InstallPackage('nginx'))
->run(new RestartService('nginx'))
->execute();
if ($result->success) {
echo "Pipeline completed in {$result->duration()} seconds";
} else {
foreach ($result->failedActions() as $action) {
echo "Failed: {$action->action}\n";
echo "Error: {$action->errorOutput}\n";
}
}use Serversinc\SshRunner\SshConnection;
$connection = SshConnection::for($server);
$result = $connection->pipeline()
->run(new UpdatePackageList)
->run(new InstallPackage('nginx'))
->execute();use Serversinc\SshRunner\SshRunner;
$result = SshRunner::pipeline($server)
->run(new UpdatePackageList)
->run(new InstallPackage('nginx'))
->execute();Control what happens when an action fails:
use Serversinc\SshRunner\Enums\FailureStrategy;
// STOP (default) - Stop execution on first failure
$pipeline->onFailure(FailureStrategy::STOP);
// CONTINUE - Keep executing remaining actions
$pipeline->onFailure(FailureStrategy::CONTINUE);
// ROLLBACK - Undo completed actions in reverse order
$pipeline->onFailure(FailureStrategy::ROLLBACK)
->run(new CreateDatabase)
->run(new CreateUser) // If this fails, CreateDatabase->undo() is called
->execute();All pipeline runs are automatically logged to the database:
use Serversinc\SshRunner\Models\SshPipelineLog;
// Get all runs for a server
$runs = SshPipelineLog::where('server_id', $server->id)->get();
// Check if a specific run failed
$run = SshPipelineLog::find(1);
if ($run->failed()) {
foreach ($run->actionLogs as $log) {
echo "{$log->action}: {$log->exit_code}\n";
}
}Execute a single action without the pipeline:
// Using the Facade
$result = SshRunner::run($server, new UpdatePackageList);
// Or using SshConnection
$connection = SshConnection::for($server);
$result = $connection->execute(new UpdatePackageList);
if ($result->success) {
echo $result->output;
} else {
echo $result->errorOutput;
}use Serversinc\SshRunner\SshConnection;
use Serversinc\SshRunner\SshPipeline;
$connection = SshConnection::for($server);
// Execute multiple pipelines on the same connection
$result1 = $connection->pipeline()
->run(new Action1())
->execute();
$result2 = $connection->pipeline()
->run(new Action2())
->execute();use Serversinc\SshRunner\SshRunner;
// Create a connection
$connection = SshRunner::connect($server);
// Create a pipeline directly
$pipeline = SshRunner::pipeline($server);
// Execute a single action
$result = SshRunner::run($server, new SomeAction());composer testPlease see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
If you discover any security-related issues, please use the issue tracker and mark it as a security concern.
The MIT License (MIT). Please see License File for more information.