Files
coolify/app/Livewire/Server/ValidateAndInstall.php
T
Andras BacsaiandClaude Opus 5.5 2ea1afb811 fix(server): explain why the Docker Engine version check failed
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>
2026-10-24 00:32:14 +02:00

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');
}
}