diff --git a/app/Facades/SSH.php b/app/Facades/SSH.php index b5ef0b6..66eecd3 100644 --- a/app/Facades/SSH.php +++ b/app/Facades/SSH.php @@ -12,7 +12,7 @@ * @method static init(Server $server, string $asUser = null) * @method static setLog(string $logType, int $siteId = null) * @method static connect() - * @method static string exec(string $command, string $log = '', int $siteId = null) + * @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false) * @method static string assertExecuted(array|string $commands) * @method static string assertExecutedContains(string $command) * @method static disconnect() diff --git a/app/Helpers/SSH.php b/app/Helpers/SSH.php index d2d91b4..3536f3e 100755 --- a/app/Helpers/SSH.php +++ b/app/Helpers/SSH.php @@ -96,7 +96,7 @@ public function connect(bool $sftp = false): void * @throws SSHCommandError * @throws SSHConnectionError */ - public function exec(string|array $commands, string $log = '', ?int $siteId = null): string + public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string { if ($log) { $this->setLog($log, $siteId); @@ -112,18 +112,34 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu throw new SSHConnectionError($e->getMessage()); } - if (! is_array($commands)) { - $commands = [$commands]; - } - try { - $result = ''; - foreach ($commands as $command) { - $result .= $this->executeCommand($command); + if ($this->asUser) { + $command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"'; } - return $result; + $this->connection->setTimeout(0); + if ($stream) { + $this->connection->exec($command, function ($output) { + $this->log?->write($output); + echo $output; + ob_flush(); + flush(); + }); + + return ''; + } else { + $output = $this->connection->exec($command); + + $this->log?->write($output); + + if (Str::contains($output, 'VITO_SSH_ERROR')) { + throw new Exception('SSH command failed with an error'); + } + + return $output; + } } catch (Throwable $e) { + throw $e; throw new SSHCommandError($e->getMessage()); } } @@ -141,28 +157,6 @@ public function upload(string $local, string $remote): void $this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE); } - /** - * @throws Exception - */ - protected function executeCommand(string $command): string - { - if ($this->asUser) { - $command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"'; - } - - $this->connection->setTimeout(0); - - $output = $this->connection->exec($command); - - $this->log?->write($output); - - if (Str::contains($output, 'VITO_SSH_ERROR')) { - throw new Exception('SSH command failed with an error'); - } - - return $output; - } - /** * @throws Exception */ diff --git a/app/Http/Controllers/ConsoleController.php b/app/Http/Controllers/ConsoleController.php new file mode 100644 index 0000000..51639f1 --- /dev/null +++ b/app/Http/Controllers/ConsoleController.php @@ -0,0 +1,43 @@ + $server, + ]); + } + + public function run(Server $server, Request $request) + { + $this->validate($request, [ + 'user' => [ + 'required', + Rule::in(['root', $server->ssh_user]), + ], + 'command' => 'required|string', + ]); + + return response()->stream( + function () use ($server, $request) { + $ssh = $server->ssh($request->user); + $log = 'console-'.time(); + $ssh->exec(command: $request->command, log: $log, stream: true); + }, + 200, + [ + 'Cache-Control' => 'no-cache', + 'X-Accel-Buffering' => 'no', + 'Content-Type' => 'text/event-stream', + ] + ); + } +} diff --git a/app/Http/Middleware/HandleSSHErrors.php b/app/Http/Middleware/HandleSSHErrors.php index 02b97b8..03df15a 100644 --- a/app/Http/Middleware/HandleSSHErrors.php +++ b/app/Http/Middleware/HandleSSHErrors.php @@ -7,13 +7,14 @@ use App\Facades\Toast; use Closure; use Illuminate\Http\Request; +use Illuminate\Http\Response; class HandleSSHErrors { public function handle(Request $request, Closure $next) { $res = $next($request); - if ($res->exception) { + if ($res instanceof Response && $res->exception) { if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) { Toast::error($res->exception->getMessage()); diff --git a/app/Support/Testing/SSHFake.php b/app/Support/Testing/SSHFake.php index def89d1..f17417a 100644 --- a/app/Support/Testing/SSHFake.php +++ b/app/Support/Testing/SSHFake.php @@ -34,7 +34,7 @@ public function connect(bool $sftp = false): void } } - public function exec(string|array $commands, string $log = '', ?int $siteId = null): string + public function exec(string|array $commands, string $log = '', ?int $siteId = null, ?bool $stream = false): string { if ($log) { $this->setLog($log, $siteId); diff --git a/resources/views/components/console-view.blade.php b/resources/views/components/console-view.blade.php index ab36b91..539e4d7 100644 --- a/resources/views/components/console-view.blade.php +++ b/resources/views/components/console-view.blade.php @@ -1,5 +1,5 @@