From 01957f2752b7ea03e413a0884e185f6fec82fa11 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Fri, 21 Nov 2025 09:49:33 +0100
Subject: [PATCH 1/3] feat: implement prerequisite validation and installation
for server setup
---
app/Actions/Server/InstallDocker.php | 31 ----------
app/Actions/Server/InstallPrerequisites.php | 57 +++++++++++++++++++
app/Actions/Server/ValidatePrerequisites.php | 27 +++++++++
app/Actions/Server/ValidateServer.php | 9 +++
app/Jobs/ValidateAndInstallServerJob.php | 31 ++++++++++
app/Livewire/Boarding/Index.php | 15 +++++
app/Livewire/Server/ValidateAndInstall.php | 42 ++++++++++++++
app/Models/Server.php | 12 ++++
.../server/validate-and-install.blade.php | 26 ++++++++-
9 files changed, 218 insertions(+), 32 deletions(-)
create mode 100644 app/Actions/Server/InstallPrerequisites.php
create mode 100644 app/Actions/Server/ValidatePrerequisites.php
diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php
index 92dd7e8c3..36c540950 100644
--- a/app/Actions/Server/InstallDocker.php
+++ b/app/Actions/Server/InstallDocker.php
@@ -59,8 +59,6 @@ public function handle(Server $server)
$command = collect([]);
if (isDev() && $server->id === 0) {
$command = $command->merge([
- "echo 'Installing Prerequisites...'",
- 'sleep 1',
"echo 'Installing Docker Engine...'",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
'sleep 4',
@@ -70,35 +68,6 @@ public function handle(Server $server)
return remote_process($command, $server);
} else {
- if ($supported_os_type->contains('debian')) {
- $command = $command->merge([
- "echo 'Installing Prerequisites...'",
- 'apt-get update -y',
- 'command -v curl >/dev/null || apt install -y curl',
- 'command -v wget >/dev/null || apt install -y wget',
- 'command -v git >/dev/null || apt install -y git',
- 'command -v jq >/dev/null || apt install -y jq',
- ]);
- } elseif ($supported_os_type->contains('rhel')) {
- $command = $command->merge([
- "echo 'Installing Prerequisites...'",
- 'command -v curl >/dev/null || dnf install -y curl',
- 'command -v wget >/dev/null || dnf install -y wget',
- 'command -v git >/dev/null || dnf install -y git',
- 'command -v jq >/dev/null || dnf install -y jq',
- ]);
- } elseif ($supported_os_type->contains('sles')) {
- $command = $command->merge([
- "echo 'Installing Prerequisites...'",
- 'zypper update -y',
- 'command -v curl >/dev/null || zypper install -y curl',
- 'command -v wget >/dev/null || zypper install -y wget',
- 'command -v git >/dev/null || zypper install -y git',
- 'command -v jq >/dev/null || zypper install -y jq',
- ]);
- } else {
- throw new \Exception('Unsupported OS');
- }
$command = $command->merge([
"echo 'Installing Docker Engine...'",
]);
diff --git a/app/Actions/Server/InstallPrerequisites.php b/app/Actions/Server/InstallPrerequisites.php
new file mode 100644
index 000000000..1a7d3bbd9
--- /dev/null
+++ b/app/Actions/Server/InstallPrerequisites.php
@@ -0,0 +1,57 @@
+validateOS();
+ if (! $supported_os_type) {
+ throw new \Exception('Server OS type is not supported for automated installation. Please install prerequisites manually.');
+ }
+
+ $command = collect([]);
+
+ if ($supported_os_type->contains('debian')) {
+ $command = $command->merge([
+ "echo 'Installing Prerequisites...'",
+ 'apt-get update -y',
+ 'command -v curl >/dev/null || apt install -y curl',
+ 'command -v wget >/dev/null || apt install -y wget',
+ 'command -v git >/dev/null || apt install -y git',
+ 'command -v jq >/dev/null || apt install -y jq',
+ ]);
+ } elseif ($supported_os_type->contains('rhel')) {
+ $command = $command->merge([
+ "echo 'Installing Prerequisites...'",
+ 'command -v curl >/dev/null || dnf install -y curl',
+ 'command -v wget >/dev/null || dnf install -y wget',
+ 'command -v git >/dev/null || dnf install -y git',
+ 'command -v jq >/dev/null || dnf install -y jq',
+ ]);
+ } elseif ($supported_os_type->contains('sles')) {
+ $command = $command->merge([
+ "echo 'Installing Prerequisites...'",
+ 'zypper update -y',
+ 'command -v curl >/dev/null || zypper install -y curl',
+ 'command -v wget >/dev/null || zypper install -y wget',
+ 'command -v git >/dev/null || zypper install -y git',
+ 'command -v jq >/dev/null || zypper install -y jq',
+ ]);
+ } else {
+ throw new \Exception('Unsupported OS type for prerequisites installation');
+ }
+
+ $command->push("echo 'Prerequisites installed successfully.'");
+
+ return remote_process($command, $server);
+ }
+}
diff --git a/app/Actions/Server/ValidatePrerequisites.php b/app/Actions/Server/ValidatePrerequisites.php
new file mode 100644
index 000000000..f74727112
--- /dev/null
+++ b/app/Actions/Server/ValidatePrerequisites.php
@@ -0,0 +1,27 @@
+error);
}
+ $prerequisitesInstalled = $server->validatePrerequisites();
+ if (! $prerequisitesInstalled) {
+ $this->error = 'Prerequisites (git, curl, jq) are not installed. Please install them before continuing or use the validation with installation endpoint.';
+ $server->update([
+ 'validation_logs' => $this->error,
+ ]);
+ throw new \Exception($this->error);
+ }
+
$this->docker_installed = $server->validateDockerEngine();
$this->docker_compose_installed = $server->validateDockerCompose();
if (! $this->docker_installed || ! $this->docker_compose_installed) {
diff --git a/app/Jobs/ValidateAndInstallServerJob.php b/app/Jobs/ValidateAndInstallServerJob.php
index 388791f10..a6dcd62f1 100644
--- a/app/Jobs/ValidateAndInstallServerJob.php
+++ b/app/Jobs/ValidateAndInstallServerJob.php
@@ -72,6 +72,37 @@ public function handle(): void
return;
}
+ // Check and install prerequisites
+ $prerequisitesInstalled = $this->server->validatePrerequisites();
+ if (! $prerequisitesInstalled) {
+ if ($this->numberOfTries >= $this->maxTries) {
+ $errorMessage = 'Prerequisites (git, curl, jq) could not be installed after '.$this->maxTries.' attempts. Please install them manually before continuing.';
+ $this->server->update([
+ 'validation_logs' => $errorMessage,
+ 'is_validating' => false,
+ ]);
+ Log::error('ValidateAndInstallServer: Prerequisites installation failed after max tries', [
+ 'server_id' => $this->server->id,
+ 'attempts' => $this->numberOfTries,
+ ]);
+
+ return;
+ }
+
+ Log::info('ValidateAndInstallServer: Installing prerequisites', [
+ 'server_id' => $this->server->id,
+ 'attempt' => $this->numberOfTries + 1,
+ ]);
+
+ // Install prerequisites
+ $this->server->installPrerequisites();
+
+ // Retry validation after installation
+ self::dispatch($this->server, $this->numberOfTries + 1)->delay(now()->addSeconds(30));
+
+ return;
+ }
+
// Check if Docker is installed
$dockerInstalled = $this->server->validateDockerEngine();
$dockerComposeInstalled = $this->server->validateDockerCompose();
diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php
index 9f1eac4d2..dfddd7f68 100644
--- a/app/Livewire/Boarding/Index.php
+++ b/app/Livewire/Boarding/Index.php
@@ -320,6 +320,21 @@ public function validateServer()
return handleError(error: $e, livewire: $this);
}
+ try {
+ // Check prerequisites
+ $prerequisitesInstalled = $this->createdServer->validatePrerequisites();
+ if (! $prerequisitesInstalled) {
+ $this->createdServer->installPrerequisites();
+ // Recheck after installation
+ $prerequisitesInstalled = $this->createdServer->validatePrerequisites();
+ if (! $prerequisitesInstalled) {
+ throw new \Exception('Prerequisites (git, curl, jq) could not be installed. Please install them manually.');
+ }
+ }
+ } catch (\Throwable $e) {
+ return handleError(error: $e, livewire: $this);
+ }
+
try {
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php
index bbd7f3dd9..687eadd48 100644
--- a/app/Livewire/Server/ValidateAndInstall.php
+++ b/app/Livewire/Server/ValidateAndInstall.php
@@ -25,6 +25,8 @@ class ValidateAndInstall extends Component
public $supported_os_type = null;
+ public $prerequisites_installed = null;
+
public $docker_installed = null;
public $docker_compose_installed = null;
@@ -33,12 +35,15 @@ class ValidateAndInstall extends Component
public $error = null;
+ public string $installationStep = 'Prerequisites';
+
public bool $ask = false;
protected $listeners = [
'init',
'validateConnection',
'validateOS',
+ 'validatePrerequisites',
'validateDockerEngine',
'validateDockerVersion',
'refresh' => '$refresh',
@@ -48,6 +53,7 @@ public function init(int $data = 0)
{
$this->uptime = null;
$this->supported_os_type = null;
+ $this->prerequisites_installed = null;
$this->docker_installed = null;
$this->docker_version = null;
$this->docker_compose_installed = null;
@@ -69,6 +75,7 @@ public function retry()
$this->authorize('update', $this->server);
$this->uptime = null;
$this->supported_os_type = null;
+ $this->prerequisites_installed = null;
$this->docker_installed = null;
$this->docker_compose_installed = null;
$this->docker_version = null;
@@ -103,6 +110,40 @@ public function validateOS()
return;
}
+ $this->dispatch('validatePrerequisites');
+ }
+
+ public function validatePrerequisites()
+ {
+ $this->prerequisites_installed = $this->server->validatePrerequisites();
+ if (! $this->prerequisites_installed) {
+ if ($this->install) {
+ if ($this->number_of_tries == $this->max_tries) {
+ $this->error = 'Prerequisites (git, curl, jq) could not be installed. Please install them manually before continuing.';
+ $this->server->update([
+ 'validation_logs' => $this->error,
+ ]);
+
+ return;
+ } else {
+ if ($this->number_of_tries <= $this->max_tries) {
+ $this->installationStep = 'Prerequisites';
+ $activity = $this->server->installPrerequisites();
+ $this->number_of_tries++;
+ $this->dispatch('activityMonitor', $activity->id, 'init', $this->number_of_tries);
+ }
+
+ return;
+ }
+ } else {
+ $this->error = 'Prerequisites (git, curl, jq) are not installed. Please install them before continuing.';
+ $this->server->update([
+ 'validation_logs' => $this->error,
+ ]);
+
+ return;
+ }
+ }
$this->dispatch('validateDockerEngine');
}
@@ -121,6 +162,7 @@ public function validateDockerEngine()
return;
} else {
if ($this->number_of_tries <= $this->max_tries) {
+ $this->installationStep = 'Docker';
$activity = $this->server->installDocker();
$this->number_of_tries++;
$this->dispatch('activityMonitor', $activity->id, 'init', $this->number_of_tries);
diff --git a/app/Models/Server.php b/app/Models/Server.php
index e88af2b15..9210e801b 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -4,7 +4,9 @@
use App\Actions\Proxy\StartProxy;
use App\Actions\Server\InstallDocker;
+use App\Actions\Server\InstallPrerequisites;
use App\Actions\Server\StartSentinel;
+use App\Actions\Server\ValidatePrerequisites;
use App\Enums\ProxyTypes;
use App\Events\ServerReachabilityChanged;
use App\Helpers\SslHelper;
@@ -1184,6 +1186,16 @@ public function installDocker()
return InstallDocker::run($this);
}
+ public function validatePrerequisites(): bool
+ {
+ return ValidatePrerequisites::run($this);
+ }
+
+ public function installPrerequisites()
+ {
+ return InstallPrerequisites::run($this);
+ }
+
public function validateDockerEngine($throwError = false)
{
$dockerBinary = instant_remote_process(['command -v docker'], $this, false, no_sudo: true);
diff --git a/resources/views/livewire/server/validate-and-install.blade.php b/resources/views/livewire/server/validate-and-install.blade.php
index 572da85e8..85ea3105e 100644
--- a/resources/views/livewire/server/validate-and-install.blade.php
+++ b/resources/views/livewire/server/validate-and-install.blade.php
@@ -52,6 +52,30 @@
@endif
@endif
@if ($uptime && $supported_os_type)
+ @if ($prerequisites_installed)
+
Prerequisites are installed:
+ @else
+ @if ($error)
+ Prerequisites are installed:
+ @else
+
+ @endif
+ @endif
+ @endif
+ @if ($uptime && $supported_os_type && $prerequisites_installed)
@if ($docker_installed)
Docker is installed: