lasthourcloud/app/Actions/RemoteProcess/RunRemoteProcess.php
2023-03-29 20:33:29 +02:00

134 lines
3.8 KiB
PHP

<?php
namespace App\Actions\RemoteProcess;
use App\Enums\ActivityTypes;
use App\Enums\ProcessStatus;
use Illuminate\Process\ProcessResult;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use Spatie\Activitylog\Models\Activity;
class RunRemoteProcess
{
public Activity $activity;
protected $timeStart;
protected $currentTime;
protected $lastWriteAt = 0;
protected $throttleIntervalMS = 500;
protected string $stdOutIncremental = '';
protected string $stdErrIncremental = '';
/**
* Create a new job instance.
*/
public function __construct(Activity $activity)
{
if ($activity->getExtraProperty('type') !== ActivityTypes::REMOTE_PROCESS->value && $activity->getExtraProperty('type') !== ActivityTypes::DEPLOYMENT->value) {
throw new \RuntimeException('Incompatible Activity to run a remote command.');
}
$this->activity = $activity;
}
public function __invoke(): ProcessResult
{
$this->timeStart = hrtime(true);
$processResult = Process::run($this->getCommand(), $this->handleOutput(...));
$status = match ($processResult->exitCode()) {
0 => ProcessStatus::FINISHED,
default => ProcessStatus::ERROR,
};
$this->activity->properties = $this->activity->properties->merge([
'exitCode' => $processResult->exitCode(),
'stdout' => $processResult->output(),
'stderr' => $processResult->errorOutput(),
'status' => $status,
]);
$this->activity->save();
return $processResult;
}
protected function getCommand(): string
{
$user = $this->activity->getExtraProperty('user');
$server_ip = $this->activity->getExtraProperty('server_ip');
$private_key_location = $this->activity->getExtraProperty('private_key_location');
$port = $this->activity->getExtraProperty('port');
$command = $this->activity->getExtraProperty('command');
$delimiter = 'EOF-COOLIFY-SSH';
Storage::disk('local')->makeDirectory('.ssh');
$ssh_command = "ssh "
. "-i {$private_key_location} "
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
. '-o PasswordAuthentication=no '
. '-o RequestTTY=no '
. '-o LogLevel=ERROR '
. '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/.ssh/ssh_mux_%h_%p_%r '
. "-p {$port} "
. "{$user}@{$server_ip} "
. " 'bash -se' << \\$delimiter" . PHP_EOL
. $command . PHP_EOL
. $delimiter;
return $ssh_command;
}
protected function handleOutput(string $type, string $output)
{
$this->currentTime = $this->elapsedTime();
if ($type === 'out') {
$this->stdOutIncremental .= $output;
} else {
$this->stdErrIncremental .= $output;
}
$this->activity->description .= $output;
if ($this->isAfterLastThrottle()) {
// Let's write to database.
DB::transaction(function () {
$this->activity->save();
$this->lastWriteAt = $this->currentTime;
});
}
}
/**
* Determines if it's time to write again to database.
*
* @return bool
*/
protected function isAfterLastThrottle()
{
// If DB was never written, then we immediately decide we have to write.
if ($this->lastWriteAt === 0) {
return true;
}
return ($this->currentTime - $this->throttleIntervalMS) > $this->lastWriteAt;
}
protected function elapsedTime(): int
{
$timeMs = (hrtime(true) - $this->timeStart) / 1_000_000;
return intval($timeMs);
}
}