fix(upgrade): wait for new version before showing success

Expose the running Coolify version on /api/health and only treat an upgrade as complete once that version meets the target. Parse upgrade status in a shared service, start the update after the Livewire response, and keep polling when the instance is still on the old version.
This commit is contained in:
Andras Bacsai committed 2026-08-13 10:46:06 +02:00
1 parent 882e25e072
commit 6481ffffcf
6 files changed
+317 -46

No files matched your search

+9 -2
View File
@@ -292,13 +292,20 @@ class OtherController extends Controller
#[OA\Get(
summary: 'Healthcheck',
description: 'Healthcheck endpoint.',
description: 'Healthcheck endpoint. Includes the running Coolify version in the X-Coolify-Version header.',
path: '/health',
operationId: 'healthcheck',
responses: [
new OA\Response(
response: 200,
description: 'Healthcheck endpoint.',
headers: [
new OA\Header(
header: 'X-Coolify-Version',
description: 'Currently running Coolify version.',
schema: new OA\Schema(type: 'string', example: '4.3.1'),
),
],
content: new OA\MediaType(
mediaType: 'text/html',
schema: new OA\Schema(type: 'string'),
@@ -316,6 +323,6 @@ class OtherController extends Controller
)]
public function healthcheck(Request $request)
{
return 'OK';
return response('OK')->header('X-Coolify-Version', (string) config('constants.coolify.version'));
}
}
+13 -41
View File
@@ -5,6 +5,7 @@ namespace App\Livewire;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Services\CoolifyUpgradeStatus;
use Livewire\Component;
class Upgrade extends Component
@@ -69,7 +70,13 @@ class Upgrade extends Component
return;
}
$this->updateInProgress = true;
UpdateCoolify::run(manual_update: true);
dispatch(function () {
try {
UpdateCoolify::run(manual_update: true);
} catch (\Throwable $e) {
report($e);
}
})->afterResponse();
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -100,45 +107,10 @@ class Upgrade extends Component
return ['status' => 'none'];
}
if (empty($content)) {
return ['status' => 'none'];
}
$parts = explode('|', $content);
if (count($parts) < 3) {
return ['status' => 'none'];
}
[$step, $message, $timestamp] = $parts;
// Check if status is stale (older than 10 minutes)
try {
$statusTime = new \DateTime($timestamp);
$now = new \DateTime;
$diffMinutes = ($now->getTimestamp() - $statusTime->getTimestamp()) / 60;
if ($diffMinutes > 10) {
return ['status' => 'none'];
}
} catch (\Throwable $e) {
return ['status' => 'none'];
}
if ($step === 'error') {
return [
'status' => 'error',
'step' => 0,
'message' => $message,
];
}
$stepInt = (int) $step;
$status = $stepInt >= 6 ? 'complete' : 'in_progress';
return [
'status' => $status,
'step' => $stepInt,
'message' => $message,
];
return CoolifyUpgradeStatus::fromFile(
content: $content,
runningVersion: $this->currentVersion !== '' ? $this->currentVersion : (string) config('constants.coolify.version'),
targetVersion: $this->latestVersion !== '' ? $this->latestVersion : get_latest_version_of_coolify(),
);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace App\Services;
use DateTimeInterface;
class CoolifyUpgradeStatus
{
public const STALE_AFTER_MINUTES = 10;
/**
* @return array{status: string, step?: int, message?: string, running_version: string, target_version: string}
*/
public static function fromFile(
string $content,
string $runningVersion,
string $targetVersion,
?DateTimeInterface $now = null,
int $staleAfterMinutes = self::STALE_AFTER_MINUTES,
): array {
$base = [
'running_version' => $runningVersion,
'target_version' => $targetVersion,
];
$content = trim($content);
if ($content === '') {
return ['status' => 'none', ...$base];
}
$parts = explode('|', $content);
if (count($parts) < 3) {
return ['status' => 'none', ...$base];
}
[$step, $message, $timestamp] = $parts;
try {
$statusTime = new \DateTime($timestamp);
$now = $now ?? new \DateTime;
$diffMinutes = ($now->getTimestamp() - $statusTime->getTimestamp()) / 60;
if ($diffMinutes > $staleAfterMinutes) {
return ['status' => 'none', ...$base];
}
} catch (\Throwable) {
return ['status' => 'none', ...$base];
}
if ($step === 'error') {
return [
'status' => 'error',
'step' => 0,
'message' => $message,
...$base,
];
}
$stepInt = (int) $step;
if ($stepInt >= 6 && ! self::hasReachedTargetVersion($runningVersion, $targetVersion)) {
return [
'status' => 'in_progress',
'step' => $stepInt,
'message' => "Waiting for Coolify {$targetVersion} to come online...",
...$base,
];
}
$status = $stepInt >= 6 ? 'complete' : 'in_progress';
return [
'status' => $status,
'step' => $stepInt,
'message' => $message,
...$base,
];
}
public static function hasReachedTargetVersion(string $runningVersion, string $targetVersion): bool
{
if ($runningVersion === '' || $targetVersion === '') {
return false;
}
return version_compare($runningVersion, $targetVersion, '>=');
}
}