feat(release): streamline versions.json on the CDN

- Every channel has a block of its own, every version after this one reads only these blocks
- Every stable sync also fills the coolify and traefik keys older instances read
- An instance keeps a copy of versions.json and reads upstream.json of the image until it has one
This commit is contained in:
peaklabs-dev authored and GitHub committed 2026-11-02 00:07:20 +01:00
1 parent d759126149
commit 0e30950793
24 files changed
+196 -309

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
Open source & free forever, backed by our [philosophy](https://coolify.io/philosophy).
![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest%20released%20version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge
![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest%20released%20version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=stable.coolify.version&style=for-the-badge
)
[Website](https://coolify.io) · [Documentation](https://coolify.io/docs) · [Cloud](https://app.coolify.io) · [Discord](https://coollabs.io/discord) · [Community](https://github.com/coollabsio/coolify/discussions)
+3 -4
View File
@@ -160,7 +160,7 @@ The pull request workflow builds every pull request and publishes the image of a
## CDN
`scripts/sync-cdn.sh` syncs a channel of `coollabsio/coollabs-cdn` from the commit of the version it syncs: the install and upgrade scripts, the compose files, the environment file, the service templates, the GitHub releases and `versions.json`.
`scripts/sync-cdn.sh` syncs a channel of `coollabsio/coollabs-cdn` from the commit of the version it syncs: the install and upgrade scripts, the compose files, the environment file, the service templates, the GitHub releases and the block of the channel in `/coolify/versions.json`.
| Channel | Path | Synced by |
| --------- | ------------------- | --------------------------------------------------------- |
@@ -170,9 +170,8 @@ The pull request workflow builds every pull request and publishes the image of a
- The preview channel takes betas and release candidates, alphas reach the nightly channel only
- `CDN_TOKEN` in the environment `release` is a fine-grained token for `coollabsio/coollabs-cdn` alone, with read and write access to contents and pull requests
- `versions.json` of a channel names its Coolify version and the helper, Sentinel and Traefik versions of `upstream.json`
- `/coolify/release.json` lists the Coolify version of every channel, each sync sets the version of its channel
- An instance on the preview or nightly channel takes the newest version of its channel and the more stable ones from `/coolify/release.json`
- `/coolify/versions.json` has a `stable`, `preview` and `nightly` block, each with the Coolify version of its channel and the helper, Sentinel and Traefik versions of the `upstream.json` it was synced from. An instance takes the newest version of its channel and the more stable ones
- Every stable sync also sets the `coolify` and `traefik` keys, which older instances read, to the stable versions, so older instances upgrade to a version that reads the blocks
Cloudflare keeps a file of the CDN for up to 10 minutes. To clear the cache sooner, for example once the pull request of the stable channel is merged and deployed, set `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ZONE_ID` in `.env` or the environment, with a token that can purge the cache of the zone:
+3 -1
View File
@@ -52,7 +52,7 @@ class UpdateCoolify
$latest = fetch_latest_versions(tries: $manual_update ? 1 : 3);
if ($latest !== null) {
$this->latestVersion = data_get($latest, 'versions.coolify.v4.version');
$this->latestVersion = data_get($latest, 'versions.coolify.version');
$this->upgradeScriptUrl = $latest['upgrade_script_url'];
} else {
// Fallback to cache if CDN unavailable
@@ -71,6 +71,7 @@ class UpdateCoolify
}
$this->latestVersion = $cacheVersion;
$this->upgradeScriptUrl = data_get(get_versions_data(), 'upgrade_script_url');
Log::warning('Failed to fetch fresh version from CDN (unsuccessful response), using validated cache', [
'version' => $cacheVersion,
]);
@@ -92,6 +93,7 @@ class UpdateCoolify
}
$this->latestVersion = $cacheVersion;
$this->upgradeScriptUrl = data_get(get_versions_data(), 'upgrade_script_url');
Log::warning('Failed to fetch fresh version from CDN, using validated cache', [
'error' => $e->getMessage(),
'version' => $cacheVersion,
@@ -132,7 +132,7 @@ class SentinelController extends Controller
Cache::put(Server::sentinelReportedVersionCacheKey($server->id), $runningVersion, now()->addHours(2));
$latestVersion = data_get(get_versions_data(), 'coolify.sentinel.version');
$latestVersion = data_get(get_versions_data(), 'sentinel.version');
if (blank($latestVersion) || version_compare($runningVersion, $latestVersion, '>=')) {
return;
}
+3 -3
View File
@@ -17,7 +17,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
/** A manual check runs in the web request, so it does not wait for CDN retries. */
public int $cdnTries = 3;
public function __construct(int $cdnTries = 3, public bool $channelChanged = false)
public function __construct(int $cdnTries = 3)
{
$this->cdnTries = $cdnTries;
}
@@ -31,14 +31,14 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
if (isDev() || isCloud()) {
return false;
}
$versions = refresh_versions_file($this->cdnTries, $this->channelChanged);
$versions = refresh_versions_file($this->cdnTries);
if ($versions === null) {
return false;
}
CheckTraefikVersionJob::dispatch();
$latestVersion = data_get($versions, 'coolify.v4.version');
$latestVersion = data_get($versions, 'coolify.version');
$newVersionAvailable = instanceSettings()->update_channel !== 'custom'
&& version_compare($latestVersion, config('constants.coolify.version'), '>');
instanceSettings()->update(['new_version_available' => $newVersionAvailable]);
+2 -1
View File
@@ -83,7 +83,8 @@ class Updates extends Component
$changedFields = auditChangedFields($this->settings);
$this->settings->save();
if ($channelChanged) {
(new CheckForUpdatesJob(cdnTries: 1, channelChanged: true))->handle();
invalidate_versions_cache();
(new CheckForUpdatesJob(cdnTries: 1))->handle();
$this->dispatch('updateAvailable');
}
if ($changedFields !== []) {
+2 -2
View File
@@ -715,14 +715,14 @@ function get_route_parameters(): array
function get_latest_sentinel_version(): string
{
// Read the local versions.json, which CheckForUpdatesJob refreshes every hour. Do not ask the CDN for each server.
return data_get(get_versions_data(), 'coolify.sentinel.version') ?? '0.0.0';
return data_get(get_versions_data(), 'sentinel.version') ?? '0.0.0';
}
function get_latest_version_of_coolify(): string
{
try {
$versions = get_versions_data();
return data_get($versions, 'coolify.v4.version', '0.0.0');
return data_get($versions, 'coolify.version', '0.0.0');
} catch (Throwable $e) {
return '0.0.0';
+56 -62
View File
@@ -9,24 +9,30 @@ use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Get cached versions data from versions.json.
* Get the versions of the channel this instance follows: its Coolify version and the helper, Sentinel and Traefik versions released with it.
*
* This function provides a centralized, cached access point for all
* version data in the application. Data is cached in Redis for 1 hour
* and shared across all servers in the cluster.
* version data in the application. It reads the copy of versions.json of the CDN
* that refresh_versions_file() keeps on this node, or the upstream.json of the image until then.
* Data is cached in Redis for 1 hour and shared across all servers in the cluster.
*
* @return array|null The versions data array, or null if file doesn't exist
* @return array{coolify: array{version: string}, helper?: array{version: string}, sentinel?: array{version: string}, traefik?: array<string, string>}
*/
function get_versions_data(): ?array
function get_versions_data(): array
{
return Cache::remember('coolify:versions:all', 3600, function () {
$versionsPath = File::exists(storage_path('app/versions.json')) ? storage_path('app/versions.json') : base_path('versions.json');
return Cache::remember('coolify:versions', 3600, function () {
$versionsPath = storage_path('app/versions.json');
if (! File::exists($versionsPath)) {
return null;
return ['coolify' => ['version' => config('constants.coolify.version')]] + json_decode(File::get(base_path('upstream.json')), true);
}
$channels = json_decode(File::get($versionsPath), true);
$channel = newest_update_channel($channels);
$versions = $channels[$channel] + ['upgrade_script_url' => upgrade_script_url($channel)];
if (version_compare(config('constants.coolify.version'), $versions['coolify']['version'], '>')) {
$versions['coolify']['version'] = config('constants.coolify.version');
}
return json_decode(File::get($versionsPath), true);
return $versions;
});
}
@@ -54,7 +60,7 @@ function get_traefik_versions(): ?array
*/
function invalidate_versions_cache(): void
{
Cache::forget('coolify:versions:all');
Cache::forget('coolify:versions');
}
/**
@@ -89,41 +95,41 @@ function cdn_retry_delay_ms(int $attempt, Throwable $exception): int
}
/**
* @return array{versions: array, upgrade_script_url: string}|null
* Fetch versions.json from the CDN and take the block of the newest Coolify version this instance takes.
* Only the stable, preview and nightly keys are read, the coolify and traefik keys are for older instances.
*
* @return array{channels: array, versions: array{coolify: array{version: string}}, upgrade_script_url: string}|null
*/
function fetch_latest_versions(int $tries = 3): ?array
{
$settings = instanceSettings();
$cdnUrl = dirname(config('constants.coolify.versions_url'));
$channel = 'stable';
if ($settings->update_channel !== 'stable' || $settings->previous_update_channel !== null) {
$response = cdn_http($tries)->get("{$cdnUrl}/release.json");
$release = $response->json();
if ($response->failed() || ! is_array($release)) {
return null;
}
$channel = newest_update_channel(array_map(fn ($channel) => data_get($channel, 'coolify'), $release));
}
$channelUrl = $channel === 'stable' ? null : "{$cdnUrl}/{$channel}";
$response = cdn_http($tries)->get($channelUrl ? "{$channelUrl}/versions.json" : config('constants.coolify.versions_url'));
$versions = $response->json();
if ($response->failed() || ! is_string(data_get($versions, 'coolify.v4.version'))) {
$response = cdn_http($tries)->get(config('constants.coolify.versions_url'));
$channels = $response->json();
if ($response->failed() || ! is_array($channels)) {
return null;
}
$channel = newest_update_channel($channels);
return [
'versions' => $versions,
'upgrade_script_url' => $channelUrl ? "{$channelUrl}/upgrade.sh" : config('constants.coolify.upgrade_script_url'),
'channels' => $channels,
'versions' => $channels[$channel],
'upgrade_script_url' => upgrade_script_url($channel),
];
}
/**
* The upgrade script of an update channel on the CDN.
*/
function upgrade_script_url(string $channel): string
{
return $channel === 'stable' ? config('constants.coolify.upgrade_script_url') : dirname(config('constants.coolify.versions_url'))."/{$channel}/upgrade.sh";
}
/**
* The channel of the newest Coolify version this instance takes: its update channel and the more stable ones.
* After a switch to a more stable channel, also the channel it left, below the stable release of the running version,
* so an instance on a nightly of 4.6.0 gets the nightlies of 4.6.0 until 4.6.0 is released.
*
* @param array<string, mixed> $versions The Coolify version of each channel
* @param array<string, mixed> $versions The versions.json of the CDN
*/
function newest_update_channel(array $versions): string
{
@@ -134,8 +140,8 @@ function newest_update_channel(array $versions): string
$release = preg_replace('/^(\d+\.\d+\.\d+).*/', '$1', config('constants.coolify.version'));
$newest = 'stable';
foreach ($channels as $rank => $channel) {
$version = $versions[$channel] ?? null;
if (! is_string($version) || version_compare($version, (string) ($versions[$newest] ?? ''), '<=')) {
$version = data_get($versions, "{$channel}.coolify.version");
if (! is_string($version) || version_compare($version, (string) data_get($versions, "{$newest}.coolify.version"), '<=')) {
continue;
}
if ($rank <= $follows || ($rank <= $followsUntilRelease && version_compare($version, $release, '<'))) {
@@ -147,62 +153,50 @@ function newest_update_channel(array $versions): string
}
/**
* Fetch versions.json from the CDN and replace the local file of this node with it in one step.
* The Coolify version never goes below the running version, or below the cached one while the channel stays.
* When the CDN fails, the local file stays as it is. After a channel change it is removed instead,
* so the versions of the image apply until the next check and not the version of the old channel.
* Fetch versions.json from the CDN and replace the copy of it on this node in one step.
* The copy stays when the CDN serves an older Coolify version for this instance than the copy has, or when the CDN fails.
*
* @return array|null The written versions data, or null if the CDN fetch failed
* @return array|null The versions of the channel of this instance, or null if the CDN fetch failed
*/
function refresh_versions_file(int $tries = 3, bool $channelChanged = false): ?array
function refresh_versions_file(int $tries = 3): ?array
{
$versionsPath = storage_path('app/versions.json');
$versions = fetch_latest_versions($tries)['versions'] ?? null;
if ($versions === null) {
$latest = fetch_latest_versions($tries);
if ($latest === null) {
Log::warning('Could not fetch versions.json from the CDN, using the local file');
if ($channelChanged) {
File::delete($versionsPath);
invalidate_versions_cache();
}
return null;
}
$latestVersion = data_get($versions, 'coolify.v4.version');
$latestVersion = data_get($latest, 'versions.coolify.version');
$currentVersion = config('constants.coolify.version');
$existingCoolifyVersion = null;
if (! $channelChanged && File::exists($versionsPath)) {
$existingVersions = json_decode(File::get($versionsPath), true);
$existingCoolifyVersion = data_get($existingVersions, 'coolify.v4.version');
if (File::exists($versionsPath)) {
$existingChannels = json_decode(File::get($versionsPath), true);
$existingCoolifyVersion = data_get($existingChannels, newest_update_channel($existingChannels).'.coolify.version');
}
$bestVersion = $latestVersion;
if ($existingCoolifyVersion && version_compare($existingCoolifyVersion, $bestVersion, '>')) {
if ($existingCoolifyVersion && version_compare($existingCoolifyVersion, $latestVersion, '>')) {
Log::warning('CDN served older Coolify version than cache', [
'cdn_version' => $latestVersion,
'cached_version' => $existingCoolifyVersion,
'current_version' => $currentVersion,
]);
$bestVersion = $existingCoolifyVersion;
return get_versions_data();
}
if (version_compare($bestVersion, $currentVersion, '<')) {
if (version_compare($latestVersion, $currentVersion, '<')) {
Log::warning('Version downgrade prevented in CheckForUpdatesJob', [
'cdn_version' => $latestVersion,
'cached_version' => $existingCoolifyVersion,
'current_version' => $currentVersion,
'attempted_best' => $bestVersion,
'using' => $currentVersion,
]);
$bestVersion = $currentVersion;
}
data_set($versions, 'coolify.v4.version', $bestVersion);
File::replace($versionsPath, json_encode($versions, JSON_PRETTY_PRINT));
File::replace($versionsPath, json_encode($latest['channels'], JSON_PRETTY_PRINT));
invalidate_versions_cache();
$versions = get_versions_data();
update_helper_version($versions);
return $versions;
@@ -213,7 +207,7 @@ function refresh_versions_file(int $tries = 3, bool $channelChanged = false): ?a
*/
function update_helper_version(?array $versions): void
{
$latestVersion = data_get($versions, 'coolify.helper.version');
$latestVersion = data_get($versions, 'helper.version');
$settings = instanceSettings();
if ($latestVersion && version_compare($latestVersion, (string) $settings->helper_version, '>')) {
$settings->update(['helper_version' => $latestVersion]);
-2
View File
@@ -204,8 +204,6 @@ COPY --chown=www-data:www-data artisan artisan
COPY --chown=www-data:www-data openapi.yaml ./openapi.yaml
COPY --chown=www-data:www-data changelogs/ ./changelogs/
COPY --chown=www-data:www-data release.json upstream.json ./
RUN jq --arg version "$COOLIFY_VERSION" '{coolify: ({v4: {$version}} + del(.traefik)), traefik}' upstream.json > versions.json \
&& chown www-data:www-data versions.json
RUN composer dump-autoload
+5 -3
View File
@@ -16,6 +16,7 @@ set -e # Exit immediately if a command exits with a non-zero status
#set -u # Treat unset variables as an error and exit
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
CDN="https://cdn.coollabs.io/coolify"
CHANNEL="stable"
DATE=$(date +"%Y%m%d-%H%M%S")
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
@@ -518,9 +519,10 @@ if [ "$OS_TYPE" = 'amzn' ]; then
fi
# Fetch versions.json once and parse all values from it
VERSIONS_JSON=$(curl -L --silent $CDN/versions.json)
LATEST_VERSION=$(echo "$VERSIONS_JSON" | sed -n '/"v4"/,/}/s/.*"version": *"\([^"]*\)".*/\1/p' | head -n 1)
LATEST_HELPER_VERSION=$(echo "$VERSIONS_JSON" | sed -n '/"helper"/,/}/s/.*"version": *"\([^"]*\)".*/\1/p' | head -n 1)
VERSIONS_JSON=$(curl -L --silent https://cdn.coollabs.io/coolify/versions.json)
CHANNEL_VERSIONS=$(echo "$VERSIONS_JSON" | sed -n "/^ \"$CHANNEL\": {/,/^ }/p")
LATEST_VERSION=$(echo "$CHANNEL_VERSIONS" | sed -n '/"coolify"/,/}/s/.*"version": *"\([^"]*\)".*/\1/p' | head -n 1)
LATEST_HELPER_VERSION=$(echo "$CHANNEL_VERSIONS" | sed -n '/"helper"/,/}/s/.*"version": *"\([^"]*\)".*/\1/p' | head -n 1)
if [ -z "$LATEST_HELPER_VERSION" ]; then
LATEST_HELPER_VERSION=latest
+20 -19
View File
@@ -6,7 +6,7 @@ CHANNEL="${1:-}"
VERSION="${2:-}"
CDN_REPOSITORY=coollabsio/coollabs-cdn
CDN_URL=https://cdn.coollabs.io
FILES=versions.json,releases.json,docker-compose.yml,docker-compose.prod.yml,.env.production,install.sh,upgrade.sh,upgrade-postgres.sh,patch.sh,service-templates-latest.json
FILES=releases.json,docker-compose.yml,docker-compose.prod.yml,.env.production,install.sh,upgrade.sh,upgrade-postgres.sh,patch.sh,service-templates-latest.json
WORK_DIR="$(mktemp -d)"
BOLD=$'\033[1m' BLUE=$'\033[34m' GREEN=$'\033[32m' RED=$'\033[31m' RESET=$'\033[0m'
@@ -29,10 +29,9 @@ main() {
return
fi
select_channel
write_versions
fetch_releases
clone_cdn
write_release
write_versions
commit_channel
publish_channel
}
@@ -50,11 +49,6 @@ select_channel() {
info "syncing $channel_url to Coolify $BOLD$VERSION$RESET"
}
write_versions() {
jq --indent 4 --arg version "$VERSION" '{coolify: ({v4: {$version}} + del(.traefik)), traefik}' upstream.json > "$WORK_DIR/versions.json"
ok "versions.json names Coolify $VERSION and the helper, Sentinel and Traefik versions of upstream.json"
}
fetch_releases() {
gh api "repos/coollabsio/coolify/releases?per_page=30" | jq . > "$WORK_DIR/releases.json"
ok "releases.json lists the $(jq length "$WORK_DIR/releases.json") newest GitHub releases"
@@ -65,13 +59,17 @@ clone_cdn() {
ok "cloned $CDN_REPOSITORY"
}
write_release() {
local release="$WORK_DIR/cdn/json/coolify/release.json"
[ -f "$release" ] || echo '{}' > "$release"
jq --tab --arg channel "$channel" --arg version "$VERSION" '.[$channel].coolify = $version | {stable, preview, nightly} | with_entries(select(.value))' \
"$release" > "$WORK_DIR/release.json"
cp "$WORK_DIR/release.json" "$release"
ok "release.json sets the $channel version $BOLD$VERSION$RESET"
write_versions() {
local versions="$WORK_DIR/cdn/json/coolify/versions.json"
[ -f "$versions" ] || echo '{}' > "$versions"
jq --indent 4 --arg channel "$channel" --arg version "$VERSION" --slurpfile upstream upstream.json '
.[$channel] = ({coolify: {$version}} + $upstream[0])
| if $channel == "stable" then .coolify = ({v4: {$version}} + ($upstream[0] | del(.traefik))) | .traefik = $upstream[0].traefik else . end
| {stable, preview, nightly, coolify, traefik} | with_entries(select(.value))
' "$versions" > "$WORK_DIR/versions.json"
cp "$WORK_DIR/versions.json" "$versions"
ok "versions.json sets the $channel version $BOLD$VERSION$RESET with the helper, Sentinel and Traefik versions of upstream.json"
[ "$channel" != stable ] || ok "versions.json sets the coolify and traefik keys of older instances to the same versions"
}
commit_channel() {
@@ -80,7 +78,10 @@ commit_channel() {
for name in ${FILES//,/ }; do
channel_file "$name" > "$WORK_DIR/cdn/$channel_path/$name"
done
cdn add "$channel_path" json/coolify/release.json
if [ "$channel" != stable ]; then
cdn rm --quiet --ignore-unmatch "$channel_path/versions.json"
fi
cdn add "$channel_path" json/coolify/versions.json
if cdn diff --cached --quiet; then
ok "$channel_path on $CDN_REPOSITORY is up to date"
exit 0
@@ -98,7 +99,7 @@ publish_channel() {
ok "pushed the $channel channel to main of $CDN_REPOSITORY"
return
fi
previous="$(cdn show HEAD~1:json/coolify/versions.json 2> /dev/null | jq -r '.coolify.v4.version // empty' || true)"
previous="$(cdn show HEAD~1:json/coolify/versions.json 2> /dev/null | jq -r '.stable.coolify.version // .coolify.v4.version // empty' || true)"
[ "$previous" = "$VERSION" ] || from="${previous:+from Coolify $previous to }"
branch="sync/stable-$(date -u +%Y%m%d%H%M%S)"
cdn push --quiet origin "HEAD:refs/heads/$branch"
@@ -124,9 +125,9 @@ purge_cache() {
channel_file() {
case "$1" in
versions.json | releases.json) cat "$WORK_DIR/$1" ;;
releases.json) cat "$WORK_DIR/$1" ;;
service-templates-latest.json) cat "templates/$1" ;;
*.sh) sed "s|^CDN=\"$CDN_URL/coolify\"$|CDN=\"$channel_url\"|" "scripts/$1" ;;
*.sh) sed -e "s|^CDN=\"$CDN_URL/coolify\"$|CDN=\"$channel_url\"|" -e "s|^CHANNEL=\"stable\"$|CHANNEL=\"$channel\"|" "scripts/$1" ;;
*) cat "$1" ;;
esac
}
+6 -6
View File
@@ -5,22 +5,21 @@ use App\Jobs\CheckTraefikVersionJob;
use App\Models\InstanceSettings;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
afterEach(function () {
@unlink(storage_path('app/versions.json'));
});
it('checks installed Traefik versions after refreshing available versions', function () {
Bus::fake();
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.10']],
'traefik' => ['v3.7' => '3.7.13'],
'stable' => ['coolify' => ['version' => '4.0.10'], 'traefik' => ['v3.7' => '3.7.13']],
]),
]);
File::shouldReceive('exists')->andReturn(false);
File::shouldReceive('replace')->once();
InstanceSettings::forceCreate(['id' => 0]);
config([
@@ -32,4 +31,5 @@ it('checks installed Traefik versions after refreshing available versions', func
(new CheckForUpdatesJob)->handle();
Bus::assertDispatched(CheckTraefikVersionJob::class);
expect(get_traefik_versions())->toBe(['v3.7' => '3.7.13']);
});
+8 -5
View File
@@ -12,6 +12,10 @@ use Illuminate\Support\Sleep;
uses(RefreshDatabase::class);
afterEach(function () {
@unlink(storage_path('app/versions.json'));
});
beforeEach(function () {
InstanceSettings::forceCreate(['id' => 0]);
@@ -24,7 +28,7 @@ beforeEach(function () {
it('uses a newer helper version from the local versions file without asking the CDN', function () {
Http::preventStrayRequests();
Cache::put('coolify:versions:all', ['coolify' => ['helper' => ['version' => '1.0.18']]], 3600);
Cache::put('coolify:versions', ['coolify' => ['version' => '4.0.0'], 'helper' => ['version' => '1.0.18']], 3600);
(new CheckHelperImageJob)->handle();
@@ -33,18 +37,17 @@ it('uses a newer helper version from the local versions file without asking the
});
it('stores the helper version when the update check refreshes versions.json from the CDN', function () {
File::partialMock()->shouldReceive('replace')->once()->with(storage_path('app/versions.json'), Mockery::type('string'));
config(['constants.coolify.version' => '4.0.0']);
Http::preventStrayRequests();
Http::fake([
'https://cdn.example.com/coolify/versions.json' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.0'], 'helper' => ['version' => '1.0.19']],
'stable' => ['coolify' => ['version' => '4.0.0'], 'helper' => ['version' => '1.0.25']],
]),
]);
refresh_versions_file();
expect(InstanceSettings::findOrFail(0)->helper_version)->toBe('1.0.19');
expect(InstanceSettings::findOrFail(0)->helper_version)->toBe('1.0.25');
});
it('does not use a stored helper version older than the bundled version', function () {
@@ -58,7 +61,7 @@ it('keeps the stored helper version and does not notify when the CDN is unavaila
InstanceSettings::findOrFail(0)->update(['helper_version' => '1.0.17']);
Notification::fake();
Sleep::fake();
File::partialMock()->shouldNotReceive('put');
File::partialMock()->shouldNotReceive('replace');
Http::preventStrayRequests();
Http::fake([
'https://cdn.example.com/coolify/versions.json' => Http::response('error code: 1200', 503, ['Retry-After' => '60']),
+1 -1
View File
@@ -163,7 +163,7 @@ test('resend settings save is audited without the api key', function () {
test('manual instance upgrade is audited', function () {
Bus::fake();
config()->set('constants.coolify.version', '4.0.0');
Cache::put('coolify:versions:all', ['coolify' => ['v4' => ['version' => '4.0.1']]], 3600);
Cache::put('coolify:versions', ['coolify' => ['version' => '4.0.1']], 3600);
Livewire::test(Upgrade::class)->call('upgrade');
+1 -1
View File
@@ -14,7 +14,7 @@ beforeEach(function () {
Queue::fake();
Cache::flush();
Cache::put('coolify:versions:all', ['coolify' => ['sentinel' => ['version' => '1.0.2']]], 3600);
Cache::put('coolify:versions', ['sentinel' => ['version' => '1.0.2']], 3600);
$user = User::factory()->create();
$this->server = Server::factory()->create(['team_id' => $user->teams()->first()->id]);
@@ -22,7 +22,7 @@ beforeEach(function () {
config()->set('constants.ssh.max_retries', 1);
config()->set('constants.ssh.mux_enabled', false);
Http::preventStrayRequests();
Cache::put('coolify:versions:all', ['coolify' => ['sentinel' => ['version' => '1.0.0']]], 3600);
Cache::put('coolify:versions', ['sentinel' => ['version' => '1.0.0']], 3600);
$user = User::factory()->create();
$team = $user->teams()->first();
@@ -110,7 +110,7 @@ it('does not restart a Sentinel whose health check is still starting', function
it('restarts a running Sentinel that stopped pushing, at most once per hour', function () {
Cache::flush();
Cache::put('coolify:versions:all', ['coolify' => ['sentinel' => ['version' => '1.0.0']]], 3600);
Cache::put('coolify:versions', ['sentinel' => ['version' => '1.0.0']], 3600);
$this->server->forceFill(['sentinel_updated_at' => now()->subMinutes(15), 'sentinel_waiting_since' => null])->save();
fakeRemoteProcesses([
'*docker ps*' => Process::result(output: 'running Up 2 hours (healthy)'),
@@ -124,7 +124,7 @@ it('restarts a running Sentinel that stopped pushing, at most once per hour', fu
it('does not restart a Sentinel that started recently and has not pushed yet', function () {
Cache::flush();
Cache::put('coolify:versions:all', ['coolify' => ['sentinel' => ['version' => '1.0.0']]], 3600);
Cache::put('coolify:versions', ['sentinel' => ['version' => '1.0.0']], 3600);
$this->server->forceFill(['sentinel_updated_at' => now()->subDays(5), 'sentinel_waiting_since' => now()->subMinutes(3)])->save();
fakeRemoteProcesses([
'*docker ps*' => Process::result(output: 'running Up 3 minutes (healthy)'),
@@ -15,6 +15,10 @@ use Livewire\Livewire;
uses(RefreshDatabase::class);
afterEach(function () {
@unlink(storage_path('app/versions.json'));
});
beforeEach(function () {
Queue::fake([CheckTraefikVersionJob::class]);
config()->set('constants.coolify.version', '4.4.6');
@@ -41,8 +45,7 @@ it('reports a failed CDN request instead of "No new version available"', functio
});
it('reports a new version when the CDN has a newer version', function () {
Http::fake(['*' => Http::response(['coolify' => ['v4' => ['version' => '4.4.7']]])]);
File::partialMock()->shouldReceive('replace')->with(storage_path('app/versions.json'), Mockery::any())->once();
Http::fake(['*' => Http::response(['stable' => ['coolify' => ['version' => '4.4.7']]])]);
Livewire::test(Updates::class)
->call('checkManually')
+6 -6
View File
@@ -17,7 +17,7 @@ use Livewire\Livewire;
uses(RefreshDatabase::class);
it('shows patch and minor upgrade warnings on the first proxy render', function () {
Cache::put('coolify:versions:all', [
Cache::put('coolify:versions', [
'traefik' => [
'v3.7' => '3.7.13',
'v3.6' => '3.6.25',
@@ -50,7 +50,7 @@ it('shows patch and minor upgrade warnings on the first proxy render', function
});
it('shows a warning from the saved image before version detection finishes', function () {
Cache::put('coolify:versions:all', [
Cache::put('coolify:versions', [
'traefik' => ['v3.7' => '3.7.13', 'v3.6' => '3.6.25'],
]);
@@ -96,7 +96,7 @@ it('does not treat an unapplied image as the running Traefik version', function
});
it('ignores stale minor upgrade information for the detected Traefik version', function () {
Cache::put('coolify:versions:all', [
Cache::put('coolify:versions', [
'traefik' => [
'v3.7' => '3.7.8',
'v3.6' => '3.6.23',
@@ -121,7 +121,7 @@ it('ignores stale minor upgrade information for the detected Traefik version', f
});
it('does not offer the Traefik branch already configured on a running proxy', function () {
Cache::put('coolify:versions:all', [
Cache::put('coolify:versions', [
'traefik' => [
'v3.7' => '3.7.8',
'v3.6' => '3.6.23',
@@ -154,7 +154,7 @@ YAML;
});
it('still offers a newer Traefik branch than the configured image', function () {
Cache::put('coolify:versions:all', [
Cache::put('coolify:versions', [
'traefik' => [
'v3.7' => '3.7.8',
'v3.6' => '3.6.23',
@@ -321,7 +321,7 @@ it('clears Traefik version state when the proxy changes', function () {
it('cleans stale Traefik version state while selecting servers to check', function () {
Bus::fake();
Cache::put('coolify:versions:all', [
Cache::put('coolify:versions', [
'traefik' => ['v3.7' => '3.7.8'],
]);
+7 -13
View File
@@ -17,12 +17,10 @@ it('initializes latest version during mount from cached versions data', function
Cache::shouldReceive('remember')
->once()
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
->with('coolify:versions', 3600, Mockery::type(Closure::class))
->andReturn([
'coolify' => [
'v4' => [
'version' => '4.0.0-beta.999',
],
'version' => '4.0.0-beta.999',
],
]);
@@ -42,7 +40,7 @@ it('falls back to 0.0.0 during mount when cached versions data is unavailable',
Cache::shouldReceive('remember')
->once()
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
->with('coolify:versions', 3600, Mockery::type(Closure::class))
->andReturn(null);
Livewire::test(Upgrade::class)
@@ -58,12 +56,10 @@ it('clears stale upgrade availability when current version already matches lates
Cache::shouldReceive('remember')
->once()
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
->with('coolify:versions', 3600, Mockery::type(Closure::class))
->andReturn([
'coolify' => [
'v4' => [
'version' => '4.0.0-beta.999',
],
'version' => '4.0.0-beta.999',
],
]);
@@ -83,12 +79,10 @@ it('clears stale upgrade availability when current version is newer than cached
Cache::shouldReceive('remember')
->once()
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
->with('coolify:versions', 3600, Mockery::type(Closure::class))
->andReturn([
'coolify' => [
'v4' => [
'version' => '4.0.0-beta.999',
],
'version' => '4.0.0-beta.999',
],
]);
+24 -119
View File
@@ -23,146 +23,51 @@ beforeEach(function () {
});
afterEach(function () {
@unlink(storage_path('app/versions.json'));
Mockery::close();
});
it('uses max of CDN and cache versions', function () {
// CDN has older version
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.0']],
'traefik' => ['v3.5' => '3.5.6'],
], 200),
]);
// Cache has newer version
File::shouldReceive('exists')
->with(storage_path('app/versions.json'))
->andReturn(true);
File::shouldReceive('get')
->with(storage_path('app/versions.json'))
->andReturn(json_encode(['coolify' => ['v4' => ['version' => '4.0.10']]]));
File::shouldReceive('replace')
->once()
->with(storage_path('app/versions.json'), Mockery::on(function ($json) {
$data = json_decode($json, true);
// Should use cached version (4.0.10), not CDN version (4.0.0)
return $data['coolify']['v4']['version'] === '4.0.10';
}));
Cache::shouldReceive('forget')->once();
it('keeps the local versions.json when the CDN serves an older Coolify version', function () {
Http::fake(['*' => Http::response(['stable' => ['coolify' => ['version' => '4.0.0'], 'traefik' => ['v3.5' => '3.5.6']]], 200)]);
File::put(storage_path('app/versions.json'), json_encode(['stable' => ['coolify' => ['version' => '4.0.10']]]));
config(['constants.coolify.version' => '4.0.5']);
// Mock instanceSettings function
$this->app->instance('App\Models\InstanceSettings', function () {
return $this->settings;
});
$job = new CheckForUpdatesJob;
$job->handle();
});
it('never downgrades from current running version', function () {
// CDN has older version
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.0']],
'traefik' => ['v3.5' => '3.5.6'],
], 200),
]);
// Cache also has older version
File::shouldReceive('exists')
->with(storage_path('app/versions.json'))
->andReturn(true);
File::shouldReceive('get')
->with(storage_path('app/versions.json'))
->andReturn(json_encode(['coolify' => ['v4' => ['version' => '4.0.5']]]));
File::shouldReceive('replace')
->once()
->with(storage_path('app/versions.json'), Mockery::on(function ($json) {
$data = json_decode($json, true);
// Should use running version (4.0.10), not CDN (4.0.0) or cache (4.0.5)
return $data['coolify']['v4']['version'] === '4.0.10';
}));
Cache::shouldReceive('forget')->once();
// Running version is newest
config(['constants.coolify.version' => '4.0.10']);
Log::shouldReceive('warning')
->once()
->with('CDN served older Coolify version than cache', Mockery::type('array'));
expect(refresh_versions_file())->toMatchArray(['coolify' => ['version' => '4.0.10']])
->and(json_decode(File::get(storage_path('app/versions.json')), true))->toBe(['stable' => ['coolify' => ['version' => '4.0.10']]]);
});
it('never offers a version below the running version', function () {
Http::fake(['*' => Http::response(['stable' => ['coolify' => ['version' => '4.0.0']]], 200)]);
config(['constants.coolify.version' => '4.0.10']);
Log::shouldReceive('warning')
->once()
->with('Version downgrade prevented in CheckForUpdatesJob', Mockery::type('array'));
$this->app->instance('App\Models\InstanceSettings', function () {
return $this->settings;
});
$job = new CheckForUpdatesJob;
$job->handle();
expect(data_get(refresh_versions_file(), 'coolify.version'))->toBe('4.0.10');
});
it('preserves other component versions when preventing Coolify downgrade', function () {
// CDN has older Coolify but newer Traefik
it('keeps the CDN versions of the other components when it keeps the running Coolify version', function () {
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.0']],
'traefik' => ['v3.6' => '3.6.2'],
'sentinel' => ['version' => '1.0.5'],
'stable' => [
'coolify' => ['version' => '4.0.0'],
'traefik' => ['v3.6' => '3.6.2'],
'sentinel' => ['version' => '1.0.5'],
],
], 200),
]);
File::shouldReceive('exists')->andReturn(true);
File::shouldReceive('get')
->andReturn(json_encode([
'coolify' => ['v4' => ['version' => '4.0.5']],
'traefik' => ['v3.5' => '3.5.6'],
]));
File::shouldReceive('replace')
->once()
->with(storage_path('app/versions.json'), Mockery::on(function ($json) {
$data = json_decode($json, true);
// Coolify should use running version
expect($data['coolify']['v4']['version'])->toBe('4.0.10');
// Traefik should use CDN version (newer)
expect($data['traefik']['v3.6'])->toBe('3.6.2');
// Sentinel should use CDN version
expect($data['sentinel']['version'])->toBe('1.0.5');
return true;
}));
Cache::shouldReceive('forget')->once();
config(['constants.coolify.version' => '4.0.10']);
Log::shouldReceive('warning')
->once()
->with('CDN served older Coolify version than cache', Mockery::type('array'));
Log::shouldReceive('warning')
->once()
->with('Version downgrade prevented in CheckForUpdatesJob', Mockery::type('array'));
$this->app->instance('App\Models\InstanceSettings', function () {
return $this->settings;
});
$job = new CheckForUpdatesJob;
$job->handle();
expect(refresh_versions_file())->toMatchArray([
'coolify' => ['version' => '4.0.10'],
'traefik' => ['v3.6' => '3.6.2'],
'sentinel' => ['version' => '1.0.5'],
]);
});
it('keeps the local versions.json when the CDN fetch fails', function () {
+19 -9
View File
@@ -1,13 +1,23 @@
<?php
it('reads the Coolify and helper versions by key from the versions.json of the CDN in install.sh', function () {
$versions = shell_exec("jq --indent 4 --arg version 4.6.0-beta.1 '{coolify: ({v4: {\$version}} + del(.traefik)), traefik}' ".escapeshellarg(dirname(__DIR__, 2).'/upstream.json'));
$parse = implode("\n", preg_grep('/^LATEST_(HELPER_)?VERSION=\$\(echo "\$VERSIONS_JSON"/', file(dirname(__DIR__, 2).'/scripts/install.sh')));
$output = shell_exec('VERSIONS_JSON='.escapeshellarg($versions).' bash -c '.escapeshellarg($parse."\necho \"\$LATEST_VERSION \$LATEST_HELPER_VERSION\""));
$helper = json_decode(file_get_contents(dirname(__DIR__, 2).'/upstream.json'), true)['helper']['version'];
it('reads the Coolify and helper versions of its channel from versions.json in install.sh', function (string $channel, string $version) {
$root = dirname(__DIR__, 2);
$upstream = json_decode(file_get_contents("{$root}/upstream.json"), true);
$versions = json_encode([
'stable' => ['coolify' => ['version' => '4.5.1']] + $upstream,
'preview' => ['coolify' => ['version' => '4.6.0-beta.1']] + $upstream,
'nightly' => ['coolify' => ['version' => '4.6.0-beta.1.20261027.1']] + $upstream,
'coolify' => ['v4' => ['version' => '4.5.1']],
], JSON_PRETTY_PRINT);
$parse = implode("\n", preg_grep('/^(CHANNEL_VERSIONS|LATEST_(HELPER_)?VERSION)=\$\(echo "\$(VERSIONS_JSON|CHANNEL_VERSIONS)"/', file("{$root}/scripts/install.sh")));
$output = shell_exec('CHANNEL='.escapeshellarg($channel).' VERSIONS_JSON='.escapeshellarg($versions).' bash -c '.escapeshellarg($parse."\necho \"\$LATEST_VERSION \$LATEST_HELPER_VERSION\""));
expect(trim($output))->toBe("4.6.0-beta.1 {$helper}");
});
expect(trim($output))->toBe("{$version} {$upstream['helper']['version']}");
})->with([
'stable' => ['stable', '4.5.1'],
'preview' => ['preview', '4.6.0-beta.1'],
'nightly' => ['nightly', '4.6.0-beta.1.20261027.1'],
]);
it('builds the development helper image from the coolify-helper repository', function (string $file) {
expect(file_get_contents(dirname(__DIR__, 2)."/{$file}"))->toContain('https://github.com/coollabsio/coolify-helper.git#main:src');
@@ -44,13 +54,13 @@ it('guides issue authors to the correct contribution branch', function () {
->toContain('Small fixes should target `main`; larger changes should target `next`');
});
it('writes versions.json in the production image from upstream.json so version checks work right after an upgrade', function () {
it('ships release.json and upstream.json in the production image so version checks work right after an upgrade', function () {
$dockerfile = file_get_contents(dirname(__DIR__, 2).'/docker/production/Dockerfile');
$dockerignore = file_get_contents(dirname(__DIR__, 2).'/.dockerignore');
expect($dockerfile)
->toContain('COPY --chown=www-data:www-data release.json upstream.json ./')
->toContain('upstream.json > versions.json')
->not->toContain('versions.json')
->and($dockerignore)
->not->toMatch('/^\/?(release|upstream)\.json$/m');
});
+11 -36
View File
@@ -10,7 +10,6 @@ use App\Models\User;
use App\Services\CoolifyUpgradeStatus;
use App\Support\RemoteProcessCommand;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Once;
@@ -37,7 +36,7 @@ beforeEach(function () {
});
afterEach(function () {
File::delete(storage_path('app/versions.json'));
@unlink(storage_path('app/versions.json'));
});
function updateChannelTestUse(string $channel, ?string $customTag = null, ?string $preview = '4.6.0-beta.2', string $nightly = '4.6.0-beta.1.20261027.3'): void
@@ -45,14 +44,11 @@ function updateChannelTestUse(string $channel, ?string $customTag = null, ?strin
InstanceSettings::findOrFail(0)->update(['update_channel' => $channel, 'update_custom_tag' => $customTag]);
Once::flush();
Http::fake([
'cdn.example.com/coolify/release.json' => Http::response(array_filter([
'stable' => ['coolify' => '4.5.1'],
'preview' => $preview ? ['coolify' => $preview] : null,
'nightly' => ['coolify' => $nightly],
'cdn.example.com/coolify/versions.json' => Http::response(array_filter([
'stable' => ['coolify' => ['version' => '4.5.1']],
'preview' => $preview ? ['coolify' => ['version' => $preview]] : null,
'nightly' => ['coolify' => ['version' => $nightly]],
])),
'cdn.example.com/coolify/versions.json' => Http::response(['coolify' => ['v4' => ['version' => '4.5.1']]]),
'cdn.example.com/coolify/preview/versions.json' => Http::response(['coolify' => ['v4' => ['version' => $preview]]]),
'cdn.example.com/coolify/nightly/versions.json' => Http::response(['coolify' => ['v4' => ['version' => $nightly]]]),
]);
}
@@ -60,7 +56,7 @@ it('follows the newest version of the channel and the more stable ones', functio
updateChannelTestUse($channel);
expect(fetch_latest_versions())->toMatchArray([
'versions' => ['coolify' => ['v4' => ['version' => $version]]],
'versions' => ['coolify' => ['version' => $version]],
'upgrade_script_url' => $upgradeScriptUrl,
]);
})->with([
@@ -77,16 +73,16 @@ it('asks only the stable channel of the CDN on the stable channel', function ()
Http::assertSentCount(1);
});
it('skips a channel that release.json does not list yet', function () {
it('skips a channel that versions.json does not list yet', function () {
updateChannelTestUse('nightly', preview: null, nightly: '4.6.0-alpha.0.20261027.1');
expect(fetch_latest_versions(tries: 1)['upgrade_script_url'])->toBe('https://cdn.example.com/coolify/nightly/upgrade.sh');
});
it('checks nothing when release.json does not answer', function () {
it('checks nothing when versions.json does not answer', function () {
InstanceSettings::findOrFail(0)->update(['update_channel' => 'nightly']);
Once::flush();
Http::fake(['cdn.example.com/coolify/release.json' => Http::response('', 404)]);
Http::fake(['cdn.example.com/coolify/versions.json' => Http::response('', 404)]);
expect(fetch_latest_versions(tries: 1))->toBeNull();
});
@@ -168,12 +164,10 @@ it('keeps the nightlies of the running version after a switch to stable until it
InstanceSettings::findOrFail(0)->update(['update_channel' => 'stable', 'previous_update_channel' => 'nightly']);
Once::flush();
Http::fake([
'cdn.example.com/coolify/release.json' => Http::response(['stable' => ['coolify' => $stable], 'nightly' => ['coolify' => $nightly]]),
'cdn.example.com/coolify/versions.json' => Http::response(['coolify' => ['v4' => ['version' => $stable]]]),
'cdn.example.com/coolify/nightly/versions.json' => Http::response(['coolify' => ['v4' => ['version' => $nightly]]]),
'cdn.example.com/coolify/versions.json' => Http::response(['stable' => ['coolify' => ['version' => $stable]], 'nightly' => ['coolify' => ['version' => $nightly]]]),
]);
expect(data_get(fetch_latest_versions(), 'versions.coolify.v4.version'))->toBe($version);
expect(data_get(fetch_latest_versions(), 'versions.coolify.version'))->toBe($version);
})->with([
'a newer nightly of 4.6.0' => ['4.5.1', '4.6.0-beta.2.20261028.1', '4.6.0-beta.2.20261028.1'],
'the stable release of 4.6.0' => ['4.6.0', '4.7.0-alpha.0.20261101.1', '4.6.0'],
@@ -208,22 +202,3 @@ it('follows the channel it left after a switch to a more stable channel', functi
'stable to nightly' => ['stable', 'nightly', null],
'nightly to a custom image' => ['nightly', 'custom', null],
]);
it('replaces the versions of the old channel after a channel change', function () {
config(['constants.coolify.version' => '4.5.1']);
File::put(storage_path('app/versions.json'), json_encode(['coolify' => ['v4' => ['version' => '4.6.0-beta.2.20261027.1']]]));
updateChannelTestUse('stable');
refresh_versions_file(tries: 1, channelChanged: true);
expect(data_get(json_decode(File::get(storage_path('app/versions.json')), true), 'coolify.v4.version'))->toBe('4.5.1');
});
it('falls back to the versions of the image when the CDN fails after a channel change', function () {
File::put(storage_path('app/versions.json'), json_encode(['coolify' => ['v4' => ['version' => '4.6.0-beta.2.20261027.1']]]));
Http::fake(['*' => Http::response('', 500)]);
refresh_versions_file(tries: 1, channelChanged: true);
expect(File::exists(storage_path('app/versions.json')))->toBeFalse();
});
+8 -8
View File
@@ -99,7 +99,7 @@ function updateCoolifyTestPrepareUpgrade(): void
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.10']],
'stable' => ['coolify' => ['version' => '4.0.10']],
], 200),
]);
}
@@ -123,7 +123,7 @@ it('validates cache against running version before fallback', function () {
// Mock cache returning older version
Cache::shouldReceive('remember')
->andReturn(['coolify' => ['v4' => ['version' => '4.0.5']]]);
->andReturn(['coolify' => ['version' => '4.0.5']]);
config(['constants.coolify.version' => '4.0.10']);
@@ -150,7 +150,7 @@ it('uses validated cache when CDN fails and cache is newer', function () {
// Cache has newer version than current
Cache::shouldReceive('remember')
->andReturn(['coolify' => ['v4' => ['version' => '4.0.10']]]);
->andReturn(['coolify' => ['version' => '4.0.10']]);
config(['constants.coolify.version' => '4.0.5']);
@@ -183,7 +183,7 @@ it('passes the saved registry URL to the upgrade script command', function () {
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.10']],
'stable' => ['coolify' => ['version' => '4.0.10']],
], 200),
]);
@@ -212,7 +212,7 @@ it('falls back to docker io for the upgrade script command when no registry is s
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.10']],
'stable' => ['coolify' => ['version' => '4.0.10']],
], 200),
]);
@@ -364,7 +364,7 @@ it('prevents downgrade even with manual update', function () {
// CDN returns older version
Http::fake([
'*' => Http::response([
'coolify' => ['v4' => ['version' => '4.0.0']],
'stable' => ['coolify' => ['version' => '4.0.0']],
], 200),
]);
@@ -396,7 +396,7 @@ it('does not start a second upgrade while a recent upgrade status is in progress
'constants.ssh.mux_enabled' => false,
]);
updateCoolifyTestCreateRootServerAndSettings();
Http::fake(['*' => Http::response(['coolify' => ['v4' => ['version' => '4.0.10']]], 200)]);
Http::fake(['*' => Http::response(['stable' => ['coolify' => ['version' => '4.0.10']]], 200)]);
$action = updateCoolifyTestActionWithStatus('3|Pulling Docker images|'.now()->subMinutes(2)->toIso8601String());
expect(fn () => $action->handle(manual_update: true))
@@ -414,7 +414,7 @@ it('starts an upgrade when the previous upgrade status is older than the lock ex
'constants.ssh.mux_enabled' => false,
]);
updateCoolifyTestCreateRootServerAndSettings();
Http::fake(['*' => Http::response(['coolify' => ['v4' => ['version' => '4.0.10']]], 200)]);
Http::fake(['*' => Http::response(['stable' => ['coolify' => ['version' => '4.0.10']]], 200)]);
updateCoolifyTestActionWithStatus('3|Pulling Docker images|'.now()->subMinutes(16)->toIso8601String())
->handle(manual_update: true);
+1 -1
View File
@@ -8,7 +8,7 @@ uses(RefreshDatabase::class);
it('keeps the update badge inside the sidebar brand area', function (string $version) {
config()->set('constants.coolify.version', $version);
seedBrowserResourceStack(['instanceSettings' => ['new_version_available' => true]]);
Cache::put('coolify:versions:all', ['coolify' => ['v4' => ['version' => '9.9.9']]], 3600);
Cache::put('coolify:versions', ['coolify' => ['version' => '9.9.9']], 3600);
$page = visit('/login')
->fill('email', 'test@example.com')