fix(upgrade): remove disk space checks that block Coolify updates

This commit is contained in:
Andras Bacsai
2026-10-09 20:03:49 +02:00
parent 4d55a93d48
commit 5246427b03
14 changed files with 21 additions and 620 deletions
+7 -52
View File
@@ -3,8 +3,6 @@
namespace App\Actions\Server;
use App\Models\Server;
use App\Notifications\Server\UpgradeSkippedLowDiskSpace;
use App\Services\CoolifyUpgradeDiskSpace;
use App\Services\CoolifyUpgradeStatus;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@@ -21,7 +19,7 @@ class UpdateCoolify
public ?string $currentVersion = null;
public function handle($manual_update = false, bool $skipDiskSpaceCheck = false)
public function handle($manual_update = false)
{
if (isDev()) {
Sleep::for(10)->seconds();
@@ -112,16 +110,12 @@ class UpdateCoolify
);
}
if (! $skipDiskSpaceCheck && ! $this->ensureEnoughDiskSpace($manual_update)) {
return;
}
$this->update($skipDiskSpaceCheck);
$this->update();
$settings->new_version_available = false;
$settings->save();
}
private function update(bool $skipDiskSpaceCheck)
private function update()
{
$this->ensureNoUpgradeIsRunning();
@@ -129,54 +123,15 @@ class UpdateCoolify
$upgradeScriptUrl = config('constants.coolify.upgrade_script_url');
$registryUrl = coolifyRegistryUrl();
$upgradeCommand = 'bash /data/coolify/source/upgrade.sh '.
escapeshellarg($this->latestVersion).' '.
escapeshellarg($latestHelperImageVersion).' '.
escapeshellarg($registryUrl);
if ($skipDiskSpaceCheck) {
// Arguments 4 and 5: do not skip the backup, skip the disk space check.
$upgradeCommand .= " 'false' 'true'";
}
remote_process([
"curl -fsSL {$upgradeScriptUrl} -o /data/coolify/source/upgrade.sh",
$upgradeCommand,
'bash /data/coolify/source/upgrade.sh '.
escapeshellarg($this->latestVersion).' '.
escapeshellarg($latestHelperImageVersion).' '.
escapeshellarg($registryUrl),
], $this->server);
}
/**
* Automatic updates are skipped and the team is notified. Manual updates fail with an error.
*/
private function ensureEnoughDiskSpace(bool $manualUpdate): bool
{
$availableGb = $this->availableDiskSpaceGb();
if (! CoolifyUpgradeDiskSpace::isLow($availableGb)) {
return true;
}
Log::warning('Upgrade skipped because of low disk space', [
'target_version' => $this->latestVersion,
'available_gb' => $availableGb,
'required_gb' => CoolifyUpgradeDiskSpace::REQUIRED_GB,
'manual_update' => $manualUpdate,
]);
if ($manualUpdate) {
throw new \Exception(
"Not enough free disk space to upgrade: {$availableGb} GB free, ".CoolifyUpgradeDiskSpace::REQUIRED_GB.' GB required.'
);
}
$this->server->team?->notify(new UpgradeSkippedLowDiskSpace($this->server, $this->latestVersion, $availableGb));
return false;
}
protected function availableDiskSpaceGb(): ?float
{
return app(CoolifyUpgradeDiskSpace::class)->availableGb($this->server);
}
private function ensureNoUpgradeIsRunning(): void
{
$status = $this->readUpgradeStatus();
+4 -60
View File
@@ -3,10 +3,8 @@
namespace App\Livewire;
use App\Actions\Server\UpdateCoolify;
use App\Jobs\DockerCleanupJob;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Services\CoolifyUpgradeDiskSpace;
use App\Services\CoolifyUpgradeStatus;
use Livewire\Component;
@@ -64,30 +62,19 @@ class Upgrade extends Component
$this->isUpgradeAvailable = $hasNewerVersion && $newVersionAvailable;
}
/**
* Start the upgrade. Without $skipDiskSpaceCheck, the upgrade does not start when the disk is almost full.
*
* @return array{status: 'started'|'low_disk_space', available_gb?: float|null, required_gb?: int}|null
*/
public function upgrade(bool $skipDiskSpaceCheck = false)
public function upgrade()
{
try {
if (! isInstanceAdmin()) {
abort(403);
}
if ($this->updateInProgress) {
return null;
}
if (! $skipDiskSpaceCheck) {
$diskSpace = $this->checkDiskSpace();
if ($diskSpace['status'] === 'low_disk_space') {
return $diskSpace;
}
return;
}
$this->updateInProgress = true;
dispatch(function () use ($skipDiskSpaceCheck) {
dispatch(function () {
try {
UpdateCoolify::run(manual_update: true, skipDiskSpaceCheck: $skipDiskSpaceCheck);
UpdateCoolify::run(manual_update: true);
} catch (\Throwable $e) {
report($e);
}
@@ -98,50 +85,7 @@ class Upgrade extends Component
'resource' => 'instance',
'from_version' => config('constants.coolify.version'),
'to_version' => get_latest_version_of_coolify(),
'skip_disk_space_check' => $skipDiskSpaceCheck,
]);
return ['status' => 'started'];
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
/**
* @return array{status: 'low_disk_space'|'ok', available_gb: float|null, required_gb: int}
*/
public function checkDiskSpace(): array
{
if (! isInstanceAdmin()) {
abort(403);
}
$server = Server::find(0);
$availableGb = $server ? app(CoolifyUpgradeDiskSpace::class)->availableGb($server) : null;
return [
'status' => CoolifyUpgradeDiskSpace::isLow($availableGb) ? 'low_disk_space' : 'ok',
'available_gb' => $availableGb,
'required_gb' => CoolifyUpgradeDiskSpace::REQUIRED_GB,
];
}
public function runDockerCleanup()
{
try {
if (! isInstanceAdmin()) {
abort(403);
}
$server = Server::findOrFail(0);
DockerCleanupJob::dispatch($server, true);
auditLog('ui.server.docker_cleanup_started', [
'team_id' => $server->team_id,
'server_uuid' => $server->uuid,
'server_name' => $server->name,
'delete_unused_volumes' => false,
'delete_unused_networks' => false,
]);
$this->dispatch('success', 'Docker cleanup started. When it is done, click "Check again".');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -1,121 +0,0 @@
<?php
namespace App\Notifications\Server;
use App\Contracts\ThrottledNotification;
use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use App\Services\CoolifyUpgradeDiskSpace;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Messages\MailMessage;
class UpgradeSkippedLowDiskSpace extends CustomEmailNotification implements ThrottledNotification
{
public int $requiredGb = CoolifyUpgradeDiskSpace::REQUIRED_GB;
public function __construct(public Server $server, public string $version, public float $availableGb)
{
$this->onQueue('high');
}
public function via(object $notifiable): array
{
return $notifiable->getEnabledChannels('server_disk_usage');
}
public function throttleSubject(): ?Model
{
return $this->server;
}
public function throttleIntervalMinutes(): int
{
return 24 * 60;
}
private function cleanupUrl(): string
{
return base_url().'/server/'.$this->server->uuid.'/docker-cleanup';
}
public function toMail(): MailMessage
{
$mail = new MailMessage;
$mail->subject("Coolify: Automatic update to {$this->version} skipped, not enough free disk space");
$mail->view('emails.upgrade-skipped-low-disk-space', [
'version' => $this->version,
'available_gb' => $this->availableGb,
'required_gb' => $this->requiredGb,
'cleanup_url' => $this->cleanupUrl(),
]);
return $mail;
}
public function toDiscord(): DiscordMessage
{
$message = new DiscordMessage(
title: ':warning: Automatic update skipped',
description: "Coolify did not update to {$this->version} because the server does not have enough free disk space.",
color: DiscordMessage::warningColor(),
isCritical: true,
);
$message->addField('Free', "{$this->availableGb} GB", true);
$message->addField('Required', "{$this->requiredGb} GB", true);
$message->addField('What to do?', '[Docker cleanup]('.$this->cleanupUrl().')', true);
return $message;
}
public function toTelegram(): array
{
return [
'message' => "Coolify: Automatic update to {$this->version} skipped.\nFree disk space: {$this->availableGb} GB. Required: {$this->requiredGb} GB.\nFree up disk space (for example, run a Docker cleanup): {$this->cleanupUrl()}",
];
}
public function toPushover(): PushoverMessage
{
return new PushoverMessage(
title: 'Automatic update skipped',
level: 'warning',
message: "Coolify did not update to {$this->version} because the server does not have enough free disk space.<br/><br/><b>Free:</b> {$this->availableGb} GB.<br/><b>Required:</b> {$this->requiredGb} GB.",
buttons: [
'Docker cleanup' => $this->cleanupUrl(),
],
);
}
public function toSlack(): SlackMessage
{
$description = "Coolify did not update to {$this->version} because the server does not have enough free disk space.\n";
$description .= "Free: {$this->availableGb} GB\n";
$description .= "Required: {$this->requiredGb} GB\n\n";
$description .= 'Free up disk space (for example, run a Docker cleanup): '.$this->cleanupUrl();
return new SlackMessage(
title: 'Automatic update skipped',
description: $description,
color: SlackMessage::warningColor()
);
}
public function toWebhook(): array
{
return [
'success' => false,
'message' => 'Automatic update skipped, not enough free disk space',
'event' => 'upgrade_skipped_low_disk_space',
'version' => $this->version,
'server_name' => $this->server->name,
'server_uuid' => $this->server->uuid,
'available_gb' => $this->availableGb,
'required_gb' => $this->requiredGb,
'url' => $this->cleanupUrl(),
];
}
}
-63
View File
@@ -1,63 +0,0 @@
<?php
namespace App\Services;
use App\Models\Server;
/**
* An upgrade pulls new images and recreates the Coolify containers. When the disk becomes full
* during an upgrade, Coolify can stop working (for example, an empty config cache file).
*/
class CoolifyUpgradeDiskSpace
{
public const REQUIRED_GB = 5;
public const UPGRADE_PATHS = ['/data/coolify'];
public const DEFAULT_DOCKER_ROOT_DIR = '/var/lib/docker';
/**
* Free space in GB on the Docker data directory and /data/coolify. The smaller value is returned.
* Returns null when the free space cannot be measured, so an unknown value never blocks an upgrade.
*/
public function availableGb(Server $server): ?float
{
$dockerRootDir = trim((string) instant_remote_process(
["docker info --format '{{.DockerRootDir}}' 2>/dev/null || true"],
$server,
false
));
$paths = collect([...self::UPGRADE_PATHS, $dockerRootDir ?: self::DEFAULT_DOCKER_ROOT_DIR])
->map(fn (string $path) => escapeshellarg($path))
->implode(' ');
$output = instant_remote_process(["df -Pk {$paths} 2>/dev/null || true"], $server, false);
return self::parseDfOutput((string) $output);
}
/**
* Read the smallest "Available" column (in 1K blocks) from `df -Pk` output and return it in GB.
*/
public static function parseDfOutput(string $output): ?float
{
$availableKb = collect(preg_split('/\R/', trim($output)))
->skip(1)
->map(fn (string $line) => preg_split('/\s+/', trim($line))[3] ?? null)
->filter(fn (?string $value) => $value !== null && ctype_digit($value))
->map(fn (string $value) => (int) $value)
->min();
if ($availableKb === null) {
return null;
}
return round($availableKb / 1024 / 1024, 1);
}
public static function isLow(?float $availableGb): bool
{
return $availableGb !== null && $availableGb < self::REQUIRED_GB;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ $sshCommandTimeout = (int) env('SSH_COMMAND_TIMEOUT', 3600);
return [
'coolify' => [
'version' => env('COOLIFY_VERSION') ?: '4.4.4',
'version' => env('COOLIFY_VERSION') ?: '4.4.5',
'helper_version' => '1.0.17',
'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'),
+1
View File
@@ -25,6 +25,7 @@ services:
- ./databases:/var/www/html/storage/app/databases
- ./services:/var/www/html/storage/app/services
- ./backups:/var/www/html/storage/app/backups
- ./images:/var/www/html/storage/app/images
env_file:
- .env
environment:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"coolify": {
"v4": {
"version": "4.4.4"
"version": "4.4.5"
},
"nightly": {
"version": "4.5-rc.1"
@@ -1,5 +0,0 @@
<x-emails.layout>
Coolify did not update to {{ $version }} because the server does not have enough free disk space ({{ $available_gb }} GB free, {{ $required_gb }} GB required).
Free up disk space, for example with a [Docker cleanup]({{ $cleanup_url }}). The next automatic update will try again.
</x-emails.layout>
+6 -90
View File
@@ -137,53 +137,8 @@
</div>
</template>
{{-- Low Disk Space View --}}
<template x-if="!showProgress && lowDiskSpace">
<div class="flex flex-col gap-4">
<x-callout type="danger" title="Not enough free disk space">
<p>
The server has <span class="font-semibold" x-text="availableGb + ' GB'"></span> free.
The upgrade needs at least <span class="font-semibold" x-text="requiredGb + ' GB'"></span>.
If the disk becomes full during the upgrade, Coolify can stop working.
</p>
</x-callout>
<p class="text-[12px] leading-5" style="color: var(--coollabs-subtle)">
Run a Docker cleanup to remove unused images, containers, and build cache, then check again.
</p>
<template x-if="confirmOverride">
<x-callout type="warning" title="Upgrade anyway?">
<p>Only continue if you are sure that the upgrade has enough disk space.</p>
</x-callout>
</template>
<div
class="flex flex-wrap items-center justify-end gap-2 border-t border-neutral-200 pt-4 dark:border-white/[0.08]">
<template x-if="!confirmOverride">
<x-forms.button @click="confirmOverride = true" type="button">
Upgrade anyway
</x-forms.button>
</template>
<template x-if="confirmOverride">
<x-forms.button @click="confirmed(true)" type="button"
x-bind:disabled="checkingDiskSpace">
Yes, upgrade anyway
</x-forms.button>
</template>
<x-forms.button @click="$wire.runDockerCleanup()" type="button">
Run Docker cleanup
</x-forms.button>
<x-forms.button @click="checkDiskSpaceAgain()" isHighlighted type="button"
x-bind:disabled="checkingDiskSpace">
<span x-text="checkingDiskSpace ? 'Checking…' : 'Check again'"></span>
</x-forms.button>
</div>
</div>
</template>
{{-- Confirmation View --}}
<template x-if="!showProgress && !lowDiskSpace">
<template x-if="!showProgress">
<div class="flex flex-col gap-4">
<x-callout type="warning" title="Caution">
<p>Any deployments running during the update process will fail.</p>
@@ -205,9 +160,8 @@
Simulate
</x-forms.button>
</template>
<x-forms.button @click="confirmed()" isHighlighted type="button"
x-bind:disabled="checkingDiskSpace">
<span x-text="checkingDiskSpace ? 'Checking disk space…' : 'Upgrade now'"></span>
<x-forms.button @click="confirmed" isHighlighted type="button">
Upgrade now
</x-forms.button>
</div>
</div>
@@ -248,11 +202,6 @@
instanceWentDown: false,
devMode: config.devMode || false,
simulationInterval: null,
lowDiskSpace: false,
availableGb: null,
requiredGb: null,
confirmOverride: false,
checkingDiskSpace: false,
simulateUpgrade() {
if (!this.devMode) return;
@@ -285,30 +234,13 @@
}, 2000);
},
async confirmed(skipDiskSpaceCheck = false) {
if (this.checkingDiskSpace) return;
this.checkingDiskSpace = true;
let result;
try {
// Trigger server-side upgrade script via Livewire. It does not start when the disk is almost full.
result = await this.$wire.upgrade(skipDiskSpaceCheck);
} finally {
this.checkingDiskSpace = false;
}
if (result?.status === 'low_disk_space') {
this.lowDiskSpace = true;
this.availableGb = result.available_gb;
this.requiredGb = result.required_gb;
return;
}
if (result?.status !== 'started') return;
this.lowDiskSpace = false;
this.confirmOverride = false;
confirmed() {
this.showProgress = true;
this.currentStep = 1;
this.currentStatus = 'Starting upgrade...';
this.startTimer();
// Trigger server-side upgrade script via Livewire
this.$wire.$call('upgrade');
// Start client-side status polling
this.upgrade();
// Prevent accidental navigation during upgrade
@@ -319,22 +251,6 @@
window.addEventListener('beforeunload', this.beforeUnloadHandler);
},
async checkDiskSpaceAgain() {
if (this.checkingDiskSpace) return;
this.checkingDiskSpace = true;
try {
const result = await this.$wire.checkDiskSpace();
this.availableGb = result.available_gb;
this.requiredGb = result.required_gb;
this.lowDiskSpace = result.status === 'low_disk_space';
if (!this.lowDiskSpace) {
this.confirmOverride = false;
}
} finally {
this.checkingDiskSpace = false;
}
},
startTimer() {
this.startTime = Date.now();
this.elapsedInterval = setInterval(() => {
-20
View File
@@ -13,8 +13,6 @@ else
REGISTRY_URL="docker.io"
fi
SKIP_BACKUP=${4:-false}
SKIP_DISK_SPACE_CHECK=${5:-false}
REQUIRED_DISK_SPACE_GB=5
STATUS_FILE="/data/coolify/source/.upgrade-status"
DATE=$(date +%Y-%m-%d-%H-%M-%S)
@@ -55,24 +53,6 @@ echo "Helper Version: ${LATEST_HELPER_VERSION}" >>"$LOGFILE"
echo "Registry URL: ${REGISTRY_URL}" >>"$LOGFILE"
echo "============================================================" >>"$LOGFILE"
# Stop before anything changes when the disk is almost full.
# A full disk during the upgrade can leave Coolify broken.
if [ "$SKIP_DISK_SPACE_CHECK" != "true" ]; then
DOCKER_ROOT_DIR=$(docker info --format '{{.DockerRootDir}}' 2>/dev/null)
AVAILABLE_KB=$(df -Pk /data/coolify "${DOCKER_ROOT_DIR:-/var/lib/docker}" 2>/dev/null | awk 'NR>1 && $4 ~ /^[0-9]+$/ {print $4}' | sort -n | head -n1)
if [ -n "$AVAILABLE_KB" ] && [ "$AVAILABLE_KB" -lt $((REQUIRED_DISK_SPACE_GB * 1024 * 1024)) ]; then
AVAILABLE_GB=$(awk -v kb="$AVAILABLE_KB" 'BEGIN {printf "%.1f", kb / 1024 / 1024}')
log "ERROR: Not enough free disk space: ${AVAILABLE_GB} GB free, ${REQUIRED_DISK_SPACE_GB} GB required"
write_status "error" "Not enough free disk space: ${AVAILABLE_GB} GB free, ${REQUIRED_DISK_SPACE_GB} GB required. Free up disk space and try again."
echo " ERROR: Not enough free disk space: ${AVAILABLE_GB} GB free, ${REQUIRED_DISK_SPACE_GB} GB required."
echo " Free up disk space (for example: docker system prune) and try again."
exit 1
fi
log "Disk space check passed (${AVAILABLE_KB:-unknown} KB free)"
else
log "Disk space check skipped"
fi
log_section "Step 1/6: Downloading configuration files"
write_status "1" "Downloading configuration files"
echo "1/6 Downloading latest configuration files..."
-85
View File
@@ -1,15 +1,9 @@
<?php
use App\Jobs\DockerCleanupJob;
use App\Livewire\Upgrade;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use App\Services\CoolifyUpgradeDiskSpace;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
uses(RefreshDatabase::class);
@@ -104,82 +98,3 @@ it('clears stale upgrade availability when current version is newer than cached
expect((bool) InstanceSettings::findOrFail(0)->new_version_available)->toBeFalse();
});
function upgradeComponentTestLoginToRootTeam(string $role, ?float $availableGb = null): void
{
config(['constants.coolify.version' => '4.0.0-beta.998']);
Cache::put('coolify:versions:all', ['coolify' => ['v4' => ['version' => '4.0.0-beta.999']]], 3600);
InstanceSettings::forceCreate(['id' => 0, 'new_version_available' => true]);
$rootTeam = Team::factory()->create(['id' => 0]);
Server::forceCreate([
'id' => 0,
'name' => 'localhost',
'ip' => '127.0.0.1',
'user' => 'root',
'team_id' => 0,
'private_key_id' => 1,
]);
$user = User::factory()->create();
$rootTeam->members()->attach($user->id, ['role' => $role]);
test()->actingAs($user);
session(['currentTeam' => ['id' => 0]]);
test()->mock(CoolifyUpgradeDiskSpace::class)
->shouldReceive('availableGb')
->andReturn($availableGb);
}
it('does not start the upgrade when the disk is almost full', function () {
upgradeComponentTestLoginToRootTeam('admin', 3.2);
Livewire::test(Upgrade::class)
->call('upgrade')
->assertReturned(['status' => 'low_disk_space', 'available_gb' => 3.2, 'required_gb' => 5])
->assertSet('updateInProgress', false);
});
it('starts the upgrade when the disk has enough free space', function () {
upgradeComponentTestLoginToRootTeam('admin', 42.0);
Livewire::test(Upgrade::class)
->call('upgrade')
->assertReturned(['status' => 'started'])
->assertSet('updateInProgress', true);
});
it('starts the upgrade on a low disk when the admin confirms the override', function () {
upgradeComponentTestLoginToRootTeam('admin', 3.2);
Livewire::test(Upgrade::class)
->call('upgrade', true)
->assertReturned(['status' => 'started'])
->assertSet('updateInProgress', true);
});
it('starts a docker cleanup on the localhost server for an instance admin', function () {
Queue::fake();
upgradeComponentTestLoginToRootTeam('admin', 3.2);
Livewire::test(Upgrade::class)
->call('runDockerCleanup')
->assertDispatched('success');
Queue::assertPushed(DockerCleanupJob::class, fn (DockerCleanupJob $job) => $job->server->id === 0 && $job->manualCleanup);
});
it('does not let a root team member check disk space, clean up docker, or upgrade', function () {
Queue::fake();
upgradeComponentTestLoginToRootTeam('member', 3.2);
Livewire::test(Upgrade::class)
->call('checkDiskSpace')
->assertForbidden();
Livewire::test(Upgrade::class)
->call('runDockerCleanup')
->call('upgrade', true)
->assertSet('updateInProgress', false);
Queue::assertNotPushed(DockerCleanupJob::class);
});
@@ -1,30 +0,0 @@
<?php
use App\Services\CoolifyUpgradeDiskSpace;
it('returns the smallest free space of all paths in GB', function () {
$output = <<<'DF'
Filesystem 1024-blocks Used Available Capacity Mounted on
/dev/sda1 41152736 20000000 20971520 50% /
/dev/sdb1 104857600 90000000 4718592 95% /var/lib/docker
DF;
expect(CoolifyUpgradeDiskSpace::parseDfOutput($output))->toBe(4.5);
});
it('returns null when the df output has no free space values', function (string $output) {
expect(CoolifyUpgradeDiskSpace::parseDfOutput($output))->toBeNull();
})->with([
'empty' => '',
'header only' => 'Filesystem 1024-blocks Used Available Capacity Mounted on',
'error' => "df: /data/coolify: No such file or directory\nPermission denied",
]);
it('treats only a known value below the minimum as low', function (?float $availableGb, bool $isLow) {
expect(CoolifyUpgradeDiskSpace::isLow($availableGb))->toBe($isLow);
})->with([
'unknown' => [null, false],
'below minimum' => [4.9, true],
'at minimum' => [5.0, false],
'above minimum' => [120.3, false],
]);
-91
View File
@@ -6,13 +6,11 @@ use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use App\Notifications\Server\UpgradeSkippedLowDiskSpace;
use App\Support\RemoteProcessCommand;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Once;
@@ -55,48 +53,6 @@ function updateCoolifyTestActionWithStatus(string $status): UpdateCoolify
};
}
function updateCoolifyTestActionWithDiskSpace(?float $availableGb): UpdateCoolify
{
return new class($availableGb) extends UpdateCoolify
{
public function __construct(private ?float $availableGb) {}
protected function readUpgradeStatus(): ?string
{
return '';
}
protected function availableDiskSpaceGb(): ?float
{
return $this->availableGb;
}
};
}
function updateCoolifyTestPrepareUpgrade(): void
{
Queue::fake();
config([
'app.env' => 'testing',
'constants.coolify.version' => '4.0.9',
'constants.coolify.helper_version' => '1.0.14',
'constants.coolify.upgrade_script_url' => 'https://cdn.example.com/upgrade.sh',
'constants.ssh.mux_enabled' => false,
]);
updateCoolifyTestCreateRootServerAndSettings([
'is_auto_update_enabled' => true,
'new_version_available' => true,
'docker_registry_url' => 'docker.io',
]);
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.10']],
], 200),
]);
}
afterEach(function () {
Mockery::close();
});
@@ -407,50 +363,3 @@ it('starts an upgrade when the previous upgrade status is older than the lock ex
expect(RemoteProcessCommand::read(Activity::query()->latest('id')->first()))
->toContain("bash /data/coolify/source/upgrade.sh '4.0.10'");
});
it('skips an automatic update and notifies the root team when the disk is almost full', function () {
updateCoolifyTestPrepareUpgrade();
Notification::fake();
Team::findOrFail(0)->discordNotificationSettings()->update(['discord_enabled' => true, 'server_disk_usage_discord_notifications' => true]);
updateCoolifyTestActionWithDiskSpace(3.2)->handle();
expect(Activity::query()->count())->toBe(0)
->and((bool) InstanceSettings::findOrFail(0)->new_version_available)->toBeTrue();
Notification::assertSentTo(
Team::findOrFail(0),
UpgradeSkippedLowDiskSpace::class,
fn (UpgradeSkippedLowDiskSpace $notification) => $notification->version === '4.0.10'
&& $notification->availableGb === 3.2
&& str_contains((string) $notification->toMail()->render(), '3.2 GB free'),
);
});
it('fails a manual update when the disk is almost full', function () {
updateCoolifyTestPrepareUpgrade();
Notification::fake();
expect(fn () => updateCoolifyTestActionWithDiskSpace(3.2)->handle(manual_update: true))
->toThrow(Exception::class, 'Not enough free disk space to upgrade: 3.2 GB free, 5 GB required.');
expect(Activity::query()->count())->toBe(0);
Notification::assertNothingSent();
});
it('upgrades when the free disk space cannot be measured', function () {
updateCoolifyTestPrepareUpgrade();
updateCoolifyTestActionWithDiskSpace(null)->handle();
expect(RemoteProcessCommand::read(Activity::query()->latest('id')->first()))
->toEndWith("bash /data/coolify/source/upgrade.sh '4.0.10' '1.0.14' 'docker.io'");
});
it('upgrades and tells the upgrade script to skip its disk space check when the user overrides it', function () {
updateCoolifyTestPrepareUpgrade();
updateCoolifyTestActionWithDiskSpace(3.2)->handle(manual_update: true, skipDiskSpaceCheck: true);
expect(RemoteProcessCommand::read(Activity::query()->latest('id')->first()))
->toEndWith("bash /data/coolify/source/upgrade.sh '4.0.10' '1.0.14' 'docker.io' 'false' 'true'");
});
+1 -1
View File
@@ -1,7 +1,7 @@
{
"coolify": {
"v4": {
"version": "4.4.4"
"version": "4.4.5"
},
"nightly": {
"version": "4.5-rc.1"