mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-14 16:52:45 -04:00
When "docker version --format json" did not return clean JSON (for example, text from a shell startup file), validation stored 0.0.0 and said that Docker was older than version 24. Validation now stores no version and tells the user to run the command on the server. When the version is too old, the message shows the detected version. Closes #11089 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
342 lines
12 KiB
PHP
342 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Server;
|
|
|
|
use App\Actions\Proxy\CheckProxy;
|
|
use App\Actions\Proxy\StartProxy;
|
|
use App\Events\ServerValidated;
|
|
use App\Jobs\CheckAndStartSentinelJob;
|
|
use App\Models\Server;
|
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
use Livewire\Attributes\Locked;
|
|
use Livewire\Component;
|
|
|
|
class ValidateAndInstall extends Component
|
|
{
|
|
use AuthorizesRequests;
|
|
|
|
public Server $server;
|
|
|
|
public int $number_of_tries = 0;
|
|
|
|
public int $max_tries = 3;
|
|
|
|
public bool $install = true;
|
|
|
|
#[Locked]
|
|
public $uptime = null;
|
|
|
|
public $supported_os_type = null;
|
|
|
|
public $prerequisites_installed = null;
|
|
|
|
public $docker_installed = null;
|
|
|
|
public $docker_compose_installed = null;
|
|
|
|
public $docker_version = null;
|
|
|
|
public $error = null;
|
|
|
|
public string $installationStep = 'Prerequisites';
|
|
|
|
public bool $ask = false;
|
|
|
|
public bool $isInstalling = false;
|
|
|
|
protected $listeners = [
|
|
'init',
|
|
'validateConnection',
|
|
'validateOS',
|
|
'validatePrerequisites',
|
|
'validateDockerEngine',
|
|
'validateDockerVersion',
|
|
'refresh' => '$refresh',
|
|
];
|
|
|
|
public function init(int $data = 0)
|
|
{
|
|
$this->authorize('update', $this->server);
|
|
|
|
if (! $this->server->canBeValidated()) {
|
|
$this->error = 'This server was transferred to another Coolify instance and cannot be revalidated here.';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
'is_validating' => false,
|
|
]);
|
|
$this->dispatch(
|
|
'error',
|
|
'Cannot revalidate',
|
|
$this->error
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->isInstalling = false;
|
|
$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;
|
|
$this->error = null;
|
|
$this->number_of_tries = $data;
|
|
if (! $this->ask) {
|
|
$this->dispatch('validateConnection');
|
|
}
|
|
}
|
|
|
|
public function startValidatingAfterAsking()
|
|
{
|
|
$this->ask = false;
|
|
$this->init();
|
|
}
|
|
|
|
public function retry()
|
|
{
|
|
try {
|
|
$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;
|
|
$this->error = null;
|
|
$this->number_of_tries = 0;
|
|
$this->init();
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function validateConnection()
|
|
{
|
|
try {
|
|
$this->authorize('update', $this->server);
|
|
Server::throttleValidation($this->server->team_id, $this->server->ip);
|
|
if ($this->server->vultr_instance_id) {
|
|
$status = $this->server->refreshVultrState();
|
|
$this->server->refresh();
|
|
|
|
if (in_array($status, ['stopped', 'suspended', 'deleted'], true)) {
|
|
$this->error = $status === 'deleted'
|
|
? 'Vultr instance is deleted or no longer accessible. Relink this server before validating.'
|
|
: 'Vultr instance is '.($status ?? 'not running').'. Power it on before validating.';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($this->server->digitalocean_droplet_id) {
|
|
$status = $this->server->refreshDigitalOceanState();
|
|
$this->server->refresh();
|
|
|
|
if (in_array($status, ['off', 'archive', 'deleted'], true)) {
|
|
$this->error = $status === 'deleted'
|
|
? 'DigitalOcean droplet is deleted or no longer accessible. Relink this server before validating.'
|
|
: 'DigitalOcean droplet is '.($status ?? 'not running').'. Power it on before validating.';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($this->server->hostinger_virtual_machine_id) {
|
|
$status = $this->server->refreshHostingerState();
|
|
$this->server->refresh();
|
|
|
|
if (in_array($status, ['stopping', 'stopped', 'suspending', 'suspended', 'destroying', 'destroyed', 'error'], true)) {
|
|
$this->error = 'Hostinger VPS is '.($status ?? 'not running').'. Power it on before validating.';
|
|
$this->server->update(['validation_logs' => $this->error]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
|
|
if (! $this->uptime) {
|
|
$sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
|
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$sanitizedError.'</div>';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
$this->dispatch('validateOS');
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function validateOS()
|
|
{
|
|
$this->authorize('update', $this->server);
|
|
|
|
if (! $this->uptime) {
|
|
return;
|
|
}
|
|
|
|
$this->supported_os_type = $this->server->validateOS();
|
|
if (! $this->supported_os_type) {
|
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
$this->dispatch('validatePrerequisites');
|
|
}
|
|
|
|
public function validatePrerequisites()
|
|
{
|
|
$this->authorize('update', $this->server);
|
|
|
|
if (! $this->uptime) {
|
|
return;
|
|
}
|
|
|
|
$validationResult = $this->server->validatePrerequisites();
|
|
$this->prerequisites_installed = $validationResult['success'];
|
|
if (! $validationResult['success']) {
|
|
if ($this->install) {
|
|
if ($this->number_of_tries == $this->max_tries) {
|
|
$missingCommands = implode(', ', $validationResult['missing']);
|
|
$this->error = "Prerequisites ({$missingCommands}) 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->isInstalling = true;
|
|
$this->number_of_tries++;
|
|
$this->dispatch('activityMonitor', $activity->id, 'init', $this->number_of_tries, "{$this->installationStep} Installation Logs");
|
|
}
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
$missingCommands = implode(', ', $validationResult['missing']);
|
|
$this->error = "Prerequisites ({$missingCommands}) are not installed. Please install them before continuing.";
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
$this->dispatch('validateDockerEngine');
|
|
}
|
|
|
|
public function validateDockerEngine()
|
|
{
|
|
$this->authorize('update', $this->server);
|
|
|
|
if (! $this->uptime) {
|
|
return;
|
|
}
|
|
|
|
$this->docker_installed = $this->server->validateDockerEngine();
|
|
$this->docker_compose_installed = $this->server->validateDockerCompose();
|
|
if (! $this->docker_installed || ! $this->docker_compose_installed) {
|
|
if ($this->install) {
|
|
if ($this->number_of_tries == $this->max_tries) {
|
|
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
} else {
|
|
if ($this->number_of_tries <= $this->max_tries) {
|
|
$this->installationStep = 'Docker';
|
|
$activity = $this->server->installDocker();
|
|
$this->isInstalling = true;
|
|
$this->number_of_tries++;
|
|
$this->dispatch('activityMonitor', $activity->id, 'init', $this->number_of_tries, "{$this->installationStep} Installation Logs");
|
|
}
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
$this->dispatch('validateDockerVersion');
|
|
}
|
|
|
|
public function validateDockerVersion()
|
|
{
|
|
$this->authorize('update', $this->server);
|
|
|
|
if (! $this->uptime) {
|
|
return;
|
|
}
|
|
|
|
if ($this->server->isSwarm()) {
|
|
$swarmInstalled = $this->server->validateDockerSwarm();
|
|
if ($swarmInstalled) {
|
|
$this->dispatch('success', 'Docker Swarm is initiated.');
|
|
}
|
|
} else {
|
|
$this->docker_version = $this->server->validateDockerEngineVersion();
|
|
if ($this->docker_version) {
|
|
// Mark validation as complete
|
|
$this->server->update(['is_validating' => false]);
|
|
|
|
// Auto-fetch server details now that validation passed
|
|
$this->server->gatherServerMetadata();
|
|
|
|
$this->dispatch('refreshServerShow');
|
|
$this->dispatch('refreshBoardingIndex');
|
|
ServerValidated::dispatch($this->server->team_id, $this->server->uuid);
|
|
if ($this->server->isSentinelEnabled()) {
|
|
CheckAndStartSentinelJob::dispatch($this->server);
|
|
}
|
|
$this->dispatch('success', 'Server validated, proxy is starting in a moment.');
|
|
$proxyShouldRun = CheckProxy::run($this->server, true);
|
|
if (! $proxyShouldRun) {
|
|
return;
|
|
}
|
|
// Ensure networks exist BEFORE dispatching async proxy startup
|
|
// This prevents race condition where proxy tries to start before networks are created
|
|
instant_remote_process(ensureProxyNetworksExist($this->server)->toArray(), $this->server, false);
|
|
StartProxy::dispatch($this->server);
|
|
} else {
|
|
$this->error = $this->server->dockerEngineVersionError();
|
|
$this->server->update([
|
|
'validation_logs' => $this->error,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($this->server->isBuildServer()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.server.validate-and-install');
|
|
}
|
|
}
|