├── src ├── Config │ ├── HostConfig.php │ └── RemoteConfig.php ├── RemoteServiceProvider.php ├── Exceptions │ └── CouldNotExecuteCommand.php └── Commands │ └── RemoteCommand.php ├── config └── remote.php ├── LICENSE.md ├── composer.json └── README.md /src/Config/HostConfig.php: -------------------------------------------------------------------------------- 1 | name('laravel-remote') 15 | ->hasConfigFile() 16 | ->hasCommand(RemoteCommand::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Exceptions/CouldNotExecuteCommand.php: -------------------------------------------------------------------------------- 1 | 'default', 10 | 11 | /* 12 | * When set to true, A confirmation prompt will be shown before executing the `remote` command. 13 | */ 14 | 'needs_confirmation' => env('REMOTE_NEEDS_CONFIRMATION', false), 15 | 16 | /* 17 | * Here you can define the hosts where the commands should be executed. 18 | */ 19 | 'hosts' => [ 20 | 'default' => [ 21 | 'host' => env('REMOTE_HOST'), 22 | 23 | 'port' => env('REMOTE_PORT', 22), 24 | 25 | 'user' => env('REMOTE_USER'), 26 | 27 | /* 28 | * The package will cd to the given path before executing the given command. 29 | */ 30 | 'path' => env('REMOTE_PATH'), 31 | 32 | /* 33 | * Optional. Path to the php binary on your remote server. 34 | */ 35 | 'phpPath' => env('REMOTE_PHP_PATH', 'php'), 36 | ] 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-remote", 3 | "description": "Execute Artisan commands on remote servers", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-remote" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-remote", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.0", 19 | "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", 20 | "spatie/laravel-package-tools": "^1.9", 21 | "spatie/ssh": "^1.5" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", 25 | "pestphp/pest-plugin-laravel": "^1.3|^2.3|^3.1", 26 | "phpunit/phpunit": "^9.4|^10.5|^11.5.3", 27 | "spatie/laravel-ray": "^1.9", 28 | "spatie/pest-plugin-snapshots": "^1.1|^2.1", 29 | "spatie/phpunit-snapshot-assertions": "^4.2|^5.1", 30 | "spatie/ray": "^1.21" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Spatie\\Remote\\": "src", 35 | "Spatie\\Remote\\Database\\Factories\\": "database/factories" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Spatie\\Remote\\Tests\\": "tests" 41 | } 42 | }, 43 | "scripts": { 44 | "test": "vendor/bin/pest --colors=always", 45 | "test-coverage": "vendor/bin/pest --coverage-html coverage" 46 | }, 47 | "config": { 48 | "sort-packages": true, 49 | "allow-plugins": { 50 | "pestphp/pest-plugin": true 51 | } 52 | }, 53 | "extra": { 54 | "laravel": { 55 | "providers": [ 56 | "Spatie\\Remote\\RemoteServiceProvider" 57 | ] 58 | } 59 | }, 60 | "minimum-stability": "dev", 61 | "prefer-stable": true 62 | } 63 | -------------------------------------------------------------------------------- /src/Commands/RemoteCommand.php: -------------------------------------------------------------------------------- 1 | option('host') ?? config('remote.default_host'); 23 | 24 | $hostConfig = RemoteConfig::getHost($hostConfigName); 25 | 26 | $ssh = Ssh::create($hostConfig->user, $hostConfig->host) 27 | ->onOutput(function ($type, $line) { 28 | $this->displayOutput($type, $line); 29 | }) 30 | ->usePort($hostConfig->port); 31 | 32 | if ($hostConfig->privateKeyPath) { 33 | $ssh->usePrivateKey($hostConfig->privateKeyPath); 34 | } 35 | 36 | $commandsToExecute = $this->getCommandsToExecute($hostConfig); 37 | 38 | if ($this->failsConfirmationPrompt($hostConfig)) { 39 | return $this->failedConfirmationPromptOutput(); 40 | } 41 | 42 | if ($this->option('debug')) { 43 | $this->line($ssh->getExecuteCommand($commandsToExecute)); 44 | 45 | return 0; 46 | } 47 | 48 | $this->output->write("\n"); 49 | $process = $ssh->execute($commandsToExecute); 50 | $this->output->write("\n"); 51 | 52 | return $process->getExitCode(); 53 | } 54 | 55 | protected function getCommandsToExecute(HostConfig $hostConfig): array 56 | { 57 | $command = $this->argument('rawCommand'); 58 | 59 | if (! $this->option('raw')) { 60 | $command = "{$hostConfig->phpPath} artisan {$command} --ansi"; 61 | } 62 | 63 | return [ 64 | "export COLUMNS=". $this->getTerminalWidth(), 65 | "cd {$hostConfig->path}", 66 | $command, 67 | ]; 68 | } 69 | 70 | protected function displayOutput($type, $line): void 71 | { 72 | $lines = explode("\n", $line); 73 | 74 | foreach ($lines as $index => $line) { 75 | if (strlen(trim($line)) === 0) { 76 | continue; 77 | } 78 | 79 | if ($type == Process::OUT) { 80 | $this->output->write($line . PHP_EOL); 81 | 82 | continue; 83 | } 84 | 85 | $this->output->write('' . trim($line) . '' . PHP_EOL); 86 | } 87 | } 88 | 89 | protected function failsConfirmationPrompt(HostConfig $hostConfig): ?bool 90 | { 91 | if (! config('remote.needs_confirmation')) { 92 | return false; 93 | } 94 | 95 | return ! $this->confirm( 96 | "Are you sure you want to execute this command on the following remote server {$hostConfig->host}?" 97 | ); 98 | } 99 | 100 | protected function failedConfirmationPromptOutput(): int 101 | { 102 | $this->error('Remote command aborted'); 103 | 104 | return 0; 105 | } 106 | 107 | protected function getTerminalWidth(): int 108 | { 109 | return is_null(static::$terminalWidthResolver) 110 | ? (new Terminal())->getWidth() 111 | : call_user_func(static::$terminalWidthResolver); 112 | } 113 | 114 | public static function resolveTerminalWidthUsing($resolver) 115 | { 116 | static::$terminalWidthResolver = $resolver; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Execute Artisan commands on remote servers 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-remote.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-remote) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-remote.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-remote) 5 | 6 | This package provides a command to execute Artisan command on a remote server. 7 | 8 | Here's an example that will clear the cache on the remote server. 9 | 10 | ```bash 11 | php artisan remote cache:clear 12 | ``` 13 | 14 | ## Support us 15 | 16 | [](https://spatie.be/github-ad-click/laravel-remote) 17 | 18 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 19 | 20 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 21 | 22 | ## Installation 23 | 24 | You can install the package via composer: 25 | 26 | ```bash 27 | composer require spatie/laravel-remote 28 | ``` 29 | 30 | You can publish the config file with: 31 | 32 | ```bash 33 | php artisan vendor:publish --tag="remote-config" 34 | ``` 35 | 36 | This is the contents of the published config file: 37 | 38 | ```php 39 | return [ 40 | /* 41 | * This host will be used if none is specified 42 | * when executing the `remote` command. 43 | */ 44 | 'default_host' => 'default', 45 | 46 | /* 47 | * When set to true, A confirmation prompt will be shown before executing the `remote` command. 48 | */ 49 | 'needs_confirmation' => env('REMOTE_NEEDS_CONFIRMATION', false), 50 | 51 | /* 52 | * Here you can define the hosts where the commands should be executed. 53 | */ 54 | 'hosts' => [ 55 | 'default' => [ 56 | 'host' => env('REMOTE_HOST'), 57 | 58 | 'port' => env('REMOTE_PORT', 22), 59 | 60 | 'user' => env('REMOTE_USER'), 61 | 62 | /* 63 | * The package will cd to the given path before executing the given command. 64 | */ 65 | 'path' => env('REMOTE_PATH'), 66 | 67 | /* 68 | * Optional. Path to the private key on your computer if your remote server requires it. 69 | */ 70 | 'privateKeyPath' => env('REMOTE_PRIVATE_KEY_PATH'), 71 | 72 | /* 73 | * Optional. Path to the php binary on your remote server. 74 | */ 75 | 'phpPath' => env('REMOTE_PHP_PATH', 'php'), 76 | ] 77 | ], 78 | ]; 79 | ``` 80 | 81 | ## Usage 82 | 83 | To execute a command on the remote server use the `remote` Artisan command. You can pass any artisan command that you would like to execute on the server. 84 | 85 | Here's an example where we clear the cache. 86 | 87 | ```bash 88 | php artisan remote cache:clear 89 | ``` 90 | 91 | ### Executing raw commands 92 | 93 | If you want to execute a bash command, use the `--raw` option. 94 | 95 | Here we will get a list of files on the server. 96 | 97 | ```bash 98 | php artisan remote ls --raw 99 | ``` 100 | 101 | ### Using another host 102 | 103 | You can define hosts in the config file. By default, the `default` host is used. To execute a command on another host use the `--host` option. 104 | 105 | ```bash 106 | php artisan remote cache:clear --host=my-other-host 107 | ``` 108 | 109 | ### Using options in remote commands 110 | 111 | If you need to use flags or options in the command you're trying to execute, you can wrap the entire command in quotes: 112 | 113 | ```bash 114 | php artisan remote --raw "ls -a" 115 | ``` 116 | 117 | ## Testing 118 | 119 | ```bash 120 | composer test 121 | ``` 122 | 123 | ## Changelog 124 | 125 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 126 | 127 | ## Contributing 128 | 129 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 130 | 131 | ## Security Vulnerabilities 132 | 133 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 134 | 135 | ## Credits 136 | 137 | - [Freek Van der Herten](https://github.com/freekmurze) 138 | - [All Contributors](../../contributors) 139 | 140 | ## License 141 | 142 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 143 | --------------------------------------------------------------------------------