vito/app/Helpers/SSH.php
2023-08-04 18:28:04 +02:00

184 lines
4.5 KiB
PHP
Executable File

<?php
namespace App\Helpers;
use App\Contracts\SSHCommand;
use App\Exceptions\SSHAuthenticationError;
use App\Models\Server;
use App\Models\ServerLog;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use phpseclib3\Crypt\Common\PrivateKey;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Net\SFTP;
use phpseclib3\Net\SSH2;
use Throwable;
class SSH
{
public Server $server;
public ?ServerLog $log;
protected SSH2|SFTP|null $connection;
protected ?string $user;
protected ?string $asUser;
protected string $publicKey;
protected PrivateKey $privateKey;
public function init(Server $server, string $asUser = null): self
{
$this->connection = null;
$this->log = null;
$this->asUser = null;
$this->server = $server->refresh();
$this->user = $server->ssh_user;
if ($asUser && $asUser != $server->ssh_user) {
$this->user = $asUser;
$this->asUser = $asUser;
}
$this->privateKey = PublicKeyLoader::loadPrivateKey(
file_get_contents($this->server->sshKey()['private_key_path'])
);
return $this;
}
public function setLog(string $logType, $siteId = null): void
{
$this->log = $this->server->logs()->create([
'site_id' => $siteId,
'name' => $this->server->id.'-'.strtotime('now').'-'.$logType.'.log',
'type' => $logType,
'disk' => config('core.logs_disk'),
]);
}
/**
* @throws Throwable
*/
public function connect(bool $sftp = false): void
{
try {
if ($sftp) {
$this->connection = new SFTP($this->server->ip, $this->server->port);
} else {
$this->connection = new SSH2($this->server->ip, $this->server->port);
}
$login = $this->connection->login($this->user, $this->privateKey);
if (! $login) {
throw new SSHAuthenticationError("Error authenticating");
}
Log::info("Login status", [
'status' => $login
]);
} catch (Throwable $e) {
Log::error("Error connecting", [
"msg" => $e->getMessage()
]);
throw $e;
}
}
/**
* @throws Throwable
*/
public function exec(string|array|SSHCommand $commands, string $log = '', int $siteId = null): string
{
if ($log) {
$this->setLog($log, $siteId);
} else {
$this->log = null;
}
if (! $this->connection) {
$this->connect();
}
if (! is_array($commands)) {
$commands = [$commands];
}
$result = '';
foreach ($commands as $command) {
$result .= $this->executeCommand($command);
}
return $result;
}
/**
* @throws Throwable
*/
public function upload(string $local, string $remote): void
{
$this->log = null;
Log::info("Starting to upload");
if (! $this->connection) {
$this->connect(true);
}
Log::info("Uploading");
$uploaded = $this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
Log::info("Upload finished", [
'status' => $uploaded
]);
}
/**
* @throws Exception
*/
protected function executeCommand(string|SSHCommand $command): string
{
if ($command instanceof SSHCommand) {
$commandContent = $command->content($this->server->os);
} else {
$commandContent = $command;
}
Log::info("command", [
"asUser" => $this->asUser,
"content" => $commandContent
]);
if ($this->asUser) {
$commandContent = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($commandContent).'"';
}
Log::info("Running command", [
"cmd" => $commandContent
]);
$output = $this->connection->exec($commandContent);
Log::info("Command executed");
$this->log?->write($output);
if (Str::contains($output, 'VITO_SSH_ERROR')) {
throw new Exception('SSH command failed with an error');
}
return $output;
}
/**
* @throws Exception
*/
public function disconnect(): void
{
if ($this->connection) {
$this->connection->disconnect();
$this->connection = null;
}
}
}