mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
An upgrade pulls new images and recreates the Coolify containers. A full disk during an upgrade can leave Coolify broken. - Measure free space on the Docker root dir and /data/coolify of localhost; require 5 GB. An unknown value does not block. - Manual upgrade: show the free and required space, with Run Docker cleanup, Check again, and a confirmed Upgrade anyway override. - Automatic upgrade: skip it and notify the team (disk usage channels, at most once per 24 hours per server). - upgrade.sh: stop before any change when the disk is low. A new 5th argument skips the check (used by the override). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.0 KiB
PHP
64 lines
2.0 KiB
PHP
<?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;
|
|
}
|
|
}
|