feat(registry): add configurable docker registry url (#9017)
This commit is contained in:
parent
d32d7cf3d2
commit
cf6f5a2678
27 changed files with 465 additions and 91 deletions
|
|
@ -15,4 +15,4 @@ ROOT_USERNAME=
|
||||||
ROOT_USER_EMAIL=
|
ROOT_USER_EMAIL=
|
||||||
ROOT_USER_PASSWORD=
|
ROOT_USER_PASSWORD=
|
||||||
|
|
||||||
REGISTRY_URL=ghcr.io
|
REGISTRY_URL=docker.io
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $
|
||||||
$realtimeImageWithoutPrefixVersion = "coollabsio/coolify-realtime:$realtimeImageVersion";
|
$realtimeImageWithoutPrefixVersion = "coollabsio/coolify-realtime:$realtimeImageVersion";
|
||||||
|
|
||||||
$helperImageVersion = getHelperVersion();
|
$helperImageVersion = getHelperVersion();
|
||||||
$helperImage = config('constants.coolify.helper_image');
|
$helperImage = coolifyHelperImage();
|
||||||
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
$helperImageWithoutPrefix = 'coollabsio/coolify-helper';
|
$helperImageWithoutPrefix = 'coollabsio/coolify-helper';
|
||||||
$helperImageWithoutPrefixVersion = "coollabsio/coolify-helper:$helperImageVersion";
|
$helperImageWithoutPrefixVersion = "coollabsio/coolify-helper:$helperImageVersion";
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public function handle(Server $server, bool $restart = false, ?string $latestVer
|
||||||
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
||||||
$debug = data_get($server, 'settings.is_sentinel_debug_enabled');
|
$debug = data_get($server, 'settings.is_sentinel_debug_enabled');
|
||||||
$mountDir = '/data/coolify/sentinel';
|
$mountDir = '/data/coolify/sentinel';
|
||||||
$image = config('constants.coolify.registry_url').'/coollabsio/sentinel:'.$version;
|
$image = coolifyRegistryUrl().'/coollabsio/sentinel:'.$version;
|
||||||
if (! $endpoint) {
|
if (! $endpoint) {
|
||||||
throw new \RuntimeException('You should set FQDN in Instance Settings.');
|
throw new \RuntimeException('You should set FQDN in Instance Settings.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,10 +118,14 @@ private function update()
|
||||||
{
|
{
|
||||||
$latestHelperImageVersion = getHelperVersion();
|
$latestHelperImageVersion = getHelperVersion();
|
||||||
$upgradeScriptUrl = config('constants.coolify.upgrade_script_url');
|
$upgradeScriptUrl = config('constants.coolify.upgrade_script_url');
|
||||||
|
$registryUrl = coolifyRegistryUrl();
|
||||||
|
|
||||||
remote_process([
|
remote_process([
|
||||||
"curl -fsSL {$upgradeScriptUrl} -o /data/coolify/source/upgrade.sh",
|
"curl -fsSL {$upgradeScriptUrl} -o /data/coolify/source/upgrade.sh",
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion $latestHelperImageVersion",
|
'bash /data/coolify/source/upgrade.sh '.
|
||||||
|
escapeshellarg($this->latestVersion).' '.
|
||||||
|
escapeshellarg($latestHelperImageVersion).' '.
|
||||||
|
escapeshellarg($registryUrl),
|
||||||
], $this->server);
|
], $this->server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2124,7 +2124,7 @@ private function create_workdir()
|
||||||
private function prepare_builder_image(bool $firstTry = true)
|
private function prepare_builder_image(bool $firstTry = true)
|
||||||
{
|
{
|
||||||
$this->checkForCancellation();
|
$this->checkForCancellation();
|
||||||
$helperImage = config('constants.coolify.helper_image');
|
$helperImage = coolifyHelperImage();
|
||||||
$helperImage = "{$helperImage}:".getHelperVersion();
|
$helperImage = "{$helperImage}:".getHelperVersion();
|
||||||
// Get user home directory
|
// Get user home directory
|
||||||
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public function handle(): void
|
||||||
'active_deployment_uuids' => $activeDeployments,
|
'active_deployment_uuids' => $activeDeployments,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("'.config('constants.coolify.registry_url').'/coollabsio/coolify-helper")))\''], $this->server, false);
|
$containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("'.coolifyRegistryUrl().'/coollabsio/coolify-helper")))\''], $this->server, false);
|
||||||
$helperContainers = collect(json_decode($containers));
|
$helperContainers = collect(json_decode($containers));
|
||||||
|
|
||||||
if ($helperContainers->count() > 0) {
|
if ($helperContainers->count() > 0) {
|
||||||
|
|
|
||||||
|
|
@ -739,7 +739,7 @@ private function upload_to_s3(): void
|
||||||
|
|
||||||
private function getFullImageName(): string
|
private function getFullImageName(): string
|
||||||
{
|
{
|
||||||
$helperImage = config('constants.coolify.helper_image');
|
$helperImage = coolifyHelperImage();
|
||||||
$latestVersion = getHelperVersion();
|
$latestVersion = getHelperVersion();
|
||||||
|
|
||||||
return "{$helperImage}:{$latestVersion}";
|
return "{$helperImage}:{$latestVersion}";
|
||||||
|
|
|
||||||
|
|
@ -680,7 +680,7 @@ public function restoreFromS3(string $password = ''): bool|string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get helper image
|
// Get helper image
|
||||||
$helperImage = config('constants.coolify.helper_image');
|
$helperImage = coolifyHelperImage();
|
||||||
$latestVersion = getHelperVersion();
|
$latestVersion = getHelperVersion();
|
||||||
$fullImageName = "{$helperImage}:{$latestVersion}";
|
$fullImageName = "{$helperImage}:{$latestVersion}";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,6 @@ class Index extends Component
|
||||||
|
|
||||||
public bool $forceSaveDomains = false;
|
public bool $forceSaveDomains = false;
|
||||||
|
|
||||||
public $buildActivityId = null;
|
|
||||||
|
|
||||||
protected array $messages = [
|
protected array $messages = [
|
||||||
'fqdn.url' => 'Invalid instance URL.',
|
'fqdn.url' => 'Invalid instance URL.',
|
||||||
'fqdn.max' => 'URL must not exceed 255 characters.',
|
'fqdn.max' => 'URL must not exceed 255 characters.',
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
|
|
@ -26,6 +28,9 @@ class Updates extends Component
|
||||||
#[Validate('boolean')]
|
#[Validate('boolean')]
|
||||||
public bool $is_auto_update_enabled;
|
public bool $is_auto_update_enabled;
|
||||||
|
|
||||||
|
#[Validate('required|string|in:docker.io,ghcr.io')]
|
||||||
|
public string $docker_registry_url;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (! isInstanceAdmin()) {
|
if (! isInstanceAdmin()) {
|
||||||
|
|
@ -39,6 +44,7 @@ public function mount()
|
||||||
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
||||||
$this->update_check_frequency = $this->settings->update_check_frequency;
|
$this->update_check_frequency = $this->settings->update_check_frequency;
|
||||||
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
||||||
|
$this->docker_registry_url = $this->settings->docker_registry_url ?: 'docker.io';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
|
|
@ -50,16 +56,51 @@ public function instantSave()
|
||||||
'auto_update_frequency' => ['required', 'string'],
|
'auto_update_frequency' => ['required', 'string'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
$validated = $this->validate([
|
||||||
|
'docker_registry_url' => ['required', 'string', 'in:docker.io,ghcr.io'],
|
||||||
|
]);
|
||||||
$this->settings->auto_update_frequency = $this->auto_update_frequency;
|
$this->settings->auto_update_frequency = $this->auto_update_frequency;
|
||||||
$this->settings->update_check_frequency = $this->update_check_frequency;
|
$this->settings->update_check_frequency = $this->update_check_frequency;
|
||||||
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
||||||
|
$this->settings->docker_registry_url = $validated['docker_registry_url'];
|
||||||
|
$this->syncRegistryUrlToEnv($validated['docker_registry_url']);
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->dispatch('success', 'Settings updated!');
|
$this->dispatch('success', 'Settings updated!');
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function syncRegistryUrlToEnv(string $registryUrl): void
|
||||||
|
{
|
||||||
|
if (! $this->server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
instant_remote_process([
|
||||||
|
$this->registryEnvSyncCommand($registryUrl),
|
||||||
|
], $this->server);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Failed to sync REGISTRY_URL to .env', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
throw new \RuntimeException('Failed to sync REGISTRY_URL to .env. Settings were not saved.', previous: $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registryEnvSyncCommand(string $registryUrl): string
|
||||||
|
{
|
||||||
|
$envFile = '/data/coolify/source/.env';
|
||||||
|
$sedExpression = escapeshellarg("s|^REGISTRY_URL=.*|REGISTRY_URL={$registryUrl}|");
|
||||||
|
$registryLine = escapeshellarg("REGISTRY_URL={$registryUrl}");
|
||||||
|
|
||||||
|
return "if grep -q '^REGISTRY_URL=' {$envFile}; then sed -i {$sedExpression} {$envFile}; else printf '%s\\n' {$registryLine} >> {$envFile}; fi";
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
@ -89,6 +130,8 @@ public function submit()
|
||||||
if ($this->server) {
|
if ($this->server) {
|
||||||
$this->server->setupDynamicProxyConfiguration();
|
$this->server->setupDynamicProxyConfiguration();
|
||||||
}
|
}
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3749,6 +3749,27 @@ function redirectRoute(Component $component, string $name, array $parameters = [
|
||||||
return $component->redirectRoute($name, $parameters, navigate: $navigate);
|
return $component->redirectRoute($name, $parameters, navigate: $navigate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function coolifyRegistryUrl(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return instanceSettings()->docker_registry_url ?: 'docker.io';
|
||||||
|
} catch (Throwable) {
|
||||||
|
return config('constants.coolify.registry_url', 'docker.io');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function coolifyHelperImage(): string
|
||||||
|
{
|
||||||
|
$configuredHelperImage = config('constants.coolify.helper_image');
|
||||||
|
$configuredDefaultHelperImage = config('constants.coolify.registry_url', 'docker.io').'/coollabsio/coolify-helper';
|
||||||
|
|
||||||
|
if ($configuredHelperImage !== $configuredDefaultHelperImage) {
|
||||||
|
return $configuredHelperImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coolifyRegistryUrl().'/coollabsio/coolify-helper';
|
||||||
|
}
|
||||||
|
|
||||||
function getHelperVersion(): string
|
function getHelperVersion(): string
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
'autoupdate' => env('AUTOUPDATE'),
|
'autoupdate' => env('AUTOUPDATE'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
'registry_url' => env('REGISTRY_URL', 'ghcr.io'),
|
'registry_url' => env('REGISTRY_URL', 'docker.io'),
|
||||||
'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-helper'),
|
'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'docker.io').'/coollabsio/coolify-helper'),
|
||||||
'realtime_image' => env('REALTIME_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-realtime'),
|
'realtime_image' => env('REALTIME_IMAGE', env('REGISTRY_URL', 'docker.io').'/coollabsio/coolify-realtime'),
|
||||||
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
||||||
'cdn_url' => env('CDN_URL', 'https://cdn.coollabs.io'),
|
'cdn_url' => env('CDN_URL', 'https://cdn.coollabs.io'),
|
||||||
'versions_url' => env('VERSIONS_URL', env('CDN_URL', 'https://cdn.coollabs.io').'/coolify/versions.json'),
|
'versions_url' => env('VERSIONS_URL', env('CDN_URL', 'https://cdn.coollabs.io').'/coolify/versions.json'),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->string('docker_registry_url')->default('docker.io')->after('is_auto_update_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('docker_registry_url');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
image: "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
image: "${REGISTRY_URL:-docker.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: /data/coolify/source/.env
|
source: /data/coolify/source/.env
|
||||||
|
|
@ -60,7 +60,7 @@ services:
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.16'
|
image: '${REGISTRY_URL:-docker.io}/coollabsio/coolify-realtime:1.0.16'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
- "6002:6002"
|
- "6002:6002"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
services:
|
services:
|
||||||
coolify-testing-host:
|
coolify-testing-host:
|
||||||
init: true
|
init: true
|
||||||
image: "ghcr.io/coollabsio/coolify-testing-host:latest"
|
image: "docker.io/coollabsio/coolify-testing-host:latest"
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify-testing-host
|
container_name: coolify-testing-host
|
||||||
volumes:
|
volumes:
|
||||||
- //var/run/docker.sock://var/run/docker.sock
|
- //var/run/docker.sock://var/run/docker.sock
|
||||||
- ./:/data/coolify
|
- ./:/data/coolify
|
||||||
coolify:
|
coolify:
|
||||||
image: "ghcr.io/coollabsio/coolify:latest"
|
image: "docker.io/coollabsio/coolify:latest"
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify
|
container_name: coolify
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@ ROOT_USERNAME=
|
||||||
ROOT_USER_EMAIL=
|
ROOT_USER_EMAIL=
|
||||||
ROOT_USER_PASSWORD=
|
ROOT_USER_PASSWORD=
|
||||||
|
|
||||||
REGISTRY_URL=ghcr.io
|
REGISTRY_URL=docker.io
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
image: "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
image: "${REGISTRY_URL:-docker.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: /data/coolify/source/.env
|
source: /data/coolify/source/.env
|
||||||
|
|
@ -60,7 +60,7 @@ services:
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.16'
|
image: '${REGISTRY_URL:-docker.io}/coollabsio/coolify-realtime:1.0.16'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
- "6002:6002"
|
- "6002:6002"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
services:
|
services:
|
||||||
coolify-testing-host:
|
coolify-testing-host:
|
||||||
init: true
|
init: true
|
||||||
image: "ghcr.io/coollabsio/coolify-testing-host:latest"
|
image: "docker.io/coollabsio/coolify-testing-host:latest"
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify-testing-host
|
container_name: coolify-testing-host
|
||||||
volumes:
|
volumes:
|
||||||
- //var/run/docker.sock://var/run/docker.sock
|
- //var/run/docker.sock://var/run/docker.sock
|
||||||
- ./:/data/coolify
|
- ./:/data/coolify
|
||||||
coolify:
|
coolify:
|
||||||
image: "ghcr.io/coollabsio/coolify:latest"
|
image: "docker.io/coollabsio/coolify:latest"
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify
|
container_name: coolify
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
## DOCKER_ADDRESS_POOL_SIZE - Custom Docker address pool size (default: 24)
|
## DOCKER_ADDRESS_POOL_SIZE - Custom Docker address pool size (default: 24)
|
||||||
## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration (default: false)
|
## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration (default: false)
|
||||||
## AUTOUPDATE - Set to "false" to disable auto-updates
|
## AUTOUPDATE - Set to "false" to disable auto-updates
|
||||||
## REGISTRY_URL - Custom registry URL for Docker images (default: ghcr.io)
|
## REGISTRY_URL - Custom registry URL for Docker images (default: docker.io)
|
||||||
|
|
||||||
set -e # Exit immediately if a command exits with a non-zero status
|
set -e # Exit immediately if a command exits with a non-zero status
|
||||||
## $1 could be empty, so we need to disable this check
|
## $1 could be empty, so we need to disable this check
|
||||||
|
|
@ -50,7 +50,7 @@ else
|
||||||
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2)
|
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2)
|
||||||
echo "Using registry URL from .env: $REGISTRY_URL"
|
echo "Using registry URL from .env: $REGISTRY_URL"
|
||||||
else
|
else
|
||||||
REGISTRY_URL="ghcr.io"
|
REGISTRY_URL="docker.io"
|
||||||
echo "Using default registry URL: $REGISTRY_URL"
|
echo "Using default registry URL: $REGISTRY_URL"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -920,9 +920,9 @@ echo -e " - Please wait."
|
||||||
getAJoke
|
getAJoke
|
||||||
|
|
||||||
if [[ $- == *x* ]]; then
|
if [[ $- == *x* ]]; then
|
||||||
bash -x /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" "true"
|
bash -x /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-docker.io}" "true"
|
||||||
else
|
else
|
||||||
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" "true"
|
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-docker.io}" "true"
|
||||||
fi
|
fi
|
||||||
echo " - Coolify installed successfully."
|
echo " - Coolify installed successfully."
|
||||||
echo " - Waiting for Coolify to be ready..."
|
echo " - Waiting for Coolify to be ready..."
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,15 @@
|
||||||
CDN="https://cdn.coollabs.io/coolify-nightly"
|
CDN="https://cdn.coollabs.io/coolify-nightly"
|
||||||
LATEST_IMAGE=${1:-latest}
|
LATEST_IMAGE=${1:-latest}
|
||||||
LATEST_HELPER_VERSION=${2:-latest}
|
LATEST_HELPER_VERSION=${2:-latest}
|
||||||
REGISTRY_URL=${3:-ghcr.io}
|
|
||||||
SKIP_BACKUP=${4:-false}
|
|
||||||
ENV_FILE="/data/coolify/source/.env"
|
ENV_FILE="/data/coolify/source/.env"
|
||||||
|
if [ -n "${3+x}" ]; then
|
||||||
|
REGISTRY_URL="$3"
|
||||||
|
elif [ -f "$ENV_FILE" ] && grep -q "^REGISTRY_URL=" "$ENV_FILE"; then
|
||||||
|
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2- | head -n1)
|
||||||
|
else
|
||||||
|
REGISTRY_URL="docker.io"
|
||||||
|
fi
|
||||||
|
SKIP_BACKUP=${4:-false}
|
||||||
STATUS_FILE="/data/coolify/source/.upgrade-status"
|
STATUS_FILE="/data/coolify/source/.upgrade-status"
|
||||||
|
|
||||||
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
||||||
|
|
@ -80,7 +86,7 @@ fi
|
||||||
|
|
||||||
# Get all unique images from docker compose config
|
# Get all unique images from docker compose config
|
||||||
# LATEST_IMAGE env var is needed for image substitution in compose files
|
# LATEST_IMAGE env var is needed for image substitution in compose files
|
||||||
IMAGES=$(LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images 2>/dev/null | sort -u)
|
IMAGES=$(REGISTRY_URL=${REGISTRY_URL} LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images 2>/dev/null | sort -u)
|
||||||
|
|
||||||
if [ -z "$IMAGES" ]; then
|
if [ -z "$IMAGES" ]; then
|
||||||
log "ERROR: Failed to extract images from docker-compose files"
|
log "ERROR: Failed to extract images from docker-compose files"
|
||||||
|
|
@ -127,7 +133,21 @@ update_env_var() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_env_var() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
|
||||||
|
if grep -q "^${key}=" "$ENV_FILE"; then
|
||||||
|
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||||
|
log "Updated ${key}"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
|
||||||
|
log "Added ${key}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
log "Checking environment variables..."
|
log "Checking environment variables..."
|
||||||
|
set_env_var "REGISTRY_URL" "$REGISTRY_URL"
|
||||||
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
||||||
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
||||||
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
||||||
|
|
@ -164,7 +184,7 @@ echo "3/6 Pulling Docker images..."
|
||||||
echo " This may take a few minutes depending on your connection."
|
echo " This may take a few minutes depending on your connection."
|
||||||
|
|
||||||
# Also pull the helper image (not in compose files but needed for upgrade)
|
# Also pull the helper image (not in compose files but needed for upgrade)
|
||||||
HELPER_IMAGE="${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
|
HELPER_IMAGE="${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
|
||||||
echo " - Pulling $HELPER_IMAGE..."
|
echo " - Pulling $HELPER_IMAGE..."
|
||||||
log "Pulling image: $HELPER_IMAGE"
|
log "Pulling image: $HELPER_IMAGE"
|
||||||
if docker pull "$HELPER_IMAGE" >>"$LOGFILE" 2>&1; then
|
if docker pull "$HELPER_IMAGE" >>"$LOGFILE" 2>&1; then
|
||||||
|
|
@ -256,7 +276,7 @@ nohup bash -c "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log 'Running docker compose up...'
|
log 'Running docker compose up...'
|
||||||
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env \${COMPOSE_FILES} up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env \${COMPOSE_FILES} up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
||||||
log 'Docker compose up completed'
|
log 'Docker compose up completed'
|
||||||
|
|
||||||
# Final log entry
|
# Final log entry
|
||||||
|
|
|
||||||
|
|
@ -81,12 +81,6 @@ class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-co
|
||||||
placeholder="2001:db8::1" autocomplete="new-password" />
|
placeholder="2001:db8::1" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($buildActivityId)
|
|
||||||
<div class="w-full mt-4">
|
|
||||||
<livewire:activity-monitor header="Building Helper Image" :activityId="$buildActivityId"
|
|
||||||
:fullHeight="false" />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if(isDev())
|
@if(isDev())
|
||||||
<x-forms.input canGate="update" :canResource="$settings" id="dev_helper_version" label="Dev Helper Version (Development Only)"
|
<x-forms.input canGate="update" :canResource="$settings" id="dev_helper_version" label="Dev Helper Version (Development Only)"
|
||||||
helper="Override the default coolify-helper image version. Leave empty to use the default version from config ({{ config('constants.coolify.helper_version') }}). Examples: 1.0.11, latest, dev"
|
helper="Override the default coolify-helper image version. Leave empty to use the default version from config ({{ config('constants.coolify.helper_version') }}). Examples: 1.0.11, latest, dev"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,15 @@
|
||||||
<x-forms.input required label="Frequency (cron expression)" disabled placeholder="disabled"
|
<x-forms.input required label="Frequency (cron expression)" disabled placeholder="disabled"
|
||||||
helper="Frequency (cron expression) (automatically update coolify).<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every day at 00:00" />
|
helper="Frequency (cron expression) (automatically update coolify).<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every day at 00:00" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<h4 class="pt-4">Docker Registry</h4>
|
||||||
|
<div class="md:w-96">
|
||||||
|
<x-forms.select id="docker_registry_url" label="Docker Registry"
|
||||||
|
helper="The Docker registry used to pull Coolify images during updates.<br>Switch to Docker Hub if you experience rate limiting with GitHub Container Registry.">
|
||||||
|
<option value="docker.io">Docker Hub (docker.io)</option>
|
||||||
|
<option value="ghcr.io">GitHub Container Registry (ghcr.io)</option>
|
||||||
|
</x-forms.select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
## DOCKER_ADDRESS_POOL_SIZE - Custom Docker address pool size (default: 24)
|
## DOCKER_ADDRESS_POOL_SIZE - Custom Docker address pool size (default: 24)
|
||||||
## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration (default: false)
|
## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration (default: false)
|
||||||
## AUTOUPDATE - Set to "false" to disable auto-updates
|
## AUTOUPDATE - Set to "false" to disable auto-updates
|
||||||
## REGISTRY_URL - Custom registry URL for Docker images (default: ghcr.io)
|
## REGISTRY_URL - Custom registry URL for Docker images (default: docker.io)
|
||||||
|
|
||||||
set -e # Exit immediately if a command exits with a non-zero status
|
set -e # Exit immediately if a command exits with a non-zero status
|
||||||
## $1 could be empty, so we need to disable this check
|
## $1 could be empty, so we need to disable this check
|
||||||
|
|
@ -50,7 +50,7 @@ else
|
||||||
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2)
|
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2)
|
||||||
echo "Using registry URL from .env: $REGISTRY_URL"
|
echo "Using registry URL from .env: $REGISTRY_URL"
|
||||||
else
|
else
|
||||||
REGISTRY_URL="ghcr.io"
|
REGISTRY_URL="docker.io"
|
||||||
echo "Using default registry URL: $REGISTRY_URL"
|
echo "Using default registry URL: $REGISTRY_URL"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -920,9 +920,9 @@ echo -e " - Please wait."
|
||||||
getAJoke
|
getAJoke
|
||||||
|
|
||||||
if [[ $- == *x* ]]; then
|
if [[ $- == *x* ]]; then
|
||||||
bash -x /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" "true"
|
bash -x /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-docker.io}" "true"
|
||||||
else
|
else
|
||||||
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" "true"
|
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-docker.io}" "true"
|
||||||
fi
|
fi
|
||||||
echo " - Coolify installed successfully."
|
echo " - Coolify installed successfully."
|
||||||
echo " - Waiting for Coolify to be ready..."
|
echo " - Waiting for Coolify to be ready..."
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,15 @@
|
||||||
CDN="https://cdn.coollabs.io/coolify"
|
CDN="https://cdn.coollabs.io/coolify"
|
||||||
LATEST_IMAGE=${1:-latest}
|
LATEST_IMAGE=${1:-latest}
|
||||||
LATEST_HELPER_VERSION=${2:-latest}
|
LATEST_HELPER_VERSION=${2:-latest}
|
||||||
REGISTRY_URL=${3:-ghcr.io}
|
|
||||||
SKIP_BACKUP=${4:-false}
|
|
||||||
ENV_FILE="/data/coolify/source/.env"
|
ENV_FILE="/data/coolify/source/.env"
|
||||||
|
if [ -n "${3+x}" ]; then
|
||||||
|
REGISTRY_URL="$3"
|
||||||
|
elif [ -f "$ENV_FILE" ] && grep -q "^REGISTRY_URL=" "$ENV_FILE"; then
|
||||||
|
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2- | head -n1)
|
||||||
|
else
|
||||||
|
REGISTRY_URL="docker.io"
|
||||||
|
fi
|
||||||
|
SKIP_BACKUP=${4:-false}
|
||||||
STATUS_FILE="/data/coolify/source/.upgrade-status"
|
STATUS_FILE="/data/coolify/source/.upgrade-status"
|
||||||
|
|
||||||
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
||||||
|
|
@ -80,7 +86,7 @@ fi
|
||||||
|
|
||||||
# Get all unique images from docker compose config
|
# Get all unique images from docker compose config
|
||||||
# LATEST_IMAGE env var is needed for image substitution in compose files
|
# LATEST_IMAGE env var is needed for image substitution in compose files
|
||||||
IMAGES=$(LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images 2>/dev/null | sort -u)
|
IMAGES=$(REGISTRY_URL=${REGISTRY_URL} LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images 2>/dev/null | sort -u)
|
||||||
|
|
||||||
if [ -z "$IMAGES" ]; then
|
if [ -z "$IMAGES" ]; then
|
||||||
log "ERROR: Failed to extract images from docker-compose files"
|
log "ERROR: Failed to extract images from docker-compose files"
|
||||||
|
|
@ -127,7 +133,21 @@ update_env_var() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_env_var() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
|
||||||
|
if grep -q "^${key}=" "$ENV_FILE"; then
|
||||||
|
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||||
|
log "Updated ${key}"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
|
||||||
|
log "Added ${key}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
log "Checking environment variables..."
|
log "Checking environment variables..."
|
||||||
|
set_env_var "REGISTRY_URL" "$REGISTRY_URL"
|
||||||
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
||||||
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
||||||
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
||||||
|
|
@ -173,7 +193,7 @@ echo "3/6 Pulling Docker images..."
|
||||||
echo " This may take a few minutes depending on your connection."
|
echo " This may take a few minutes depending on your connection."
|
||||||
|
|
||||||
# Also pull the helper image (not in compose files but needed for upgrade)
|
# Also pull the helper image (not in compose files but needed for upgrade)
|
||||||
HELPER_IMAGE="${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
|
HELPER_IMAGE="${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
|
||||||
echo " - Pulling $HELPER_IMAGE..."
|
echo " - Pulling $HELPER_IMAGE..."
|
||||||
log "Pulling image: $HELPER_IMAGE"
|
log "Pulling image: $HELPER_IMAGE"
|
||||||
if docker pull "$HELPER_IMAGE" >>"$LOGFILE" 2>&1; then
|
if docker pull "$HELPER_IMAGE" >>"$LOGFILE" 2>&1; then
|
||||||
|
|
@ -265,7 +285,7 @@ nohup bash -c "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log 'Running docker compose up...'
|
log 'Running docker compose up...'
|
||||||
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env \${COMPOSE_FILES} up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env \${COMPOSE_FILES} up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
||||||
log 'Docker compose up completed'
|
log 'Docker compose up completed'
|
||||||
|
|
||||||
# Final log entry
|
# Final log entry
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
test('instance admin can access settings updates page', function () {
|
test('instance admin can access settings updates page', function () {
|
||||||
$rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]);
|
$rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]);
|
||||||
Server::factory()->create(['id' => 0, 'team_id' => $rootTeam->id]);
|
Server::factory()->create(['id' => 0, 'team_id' => $rootTeam->id]);
|
||||||
InstanceSettings::create(['id' => 0]);
|
InstanceSettings::forceCreate(['id' => 0]);
|
||||||
Once::flush();
|
Once::flush();
|
||||||
|
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
@ -39,3 +39,24 @@
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertNoRedirect();
|
->assertNoRedirect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('instance admin cannot save an invalid docker registry url', function () {
|
||||||
|
config()->set('constants.coolify.self_hosted', false);
|
||||||
|
|
||||||
|
$rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]);
|
||||||
|
$settings = InstanceSettings::forceCreate(['id' => 0]);
|
||||||
|
Once::flush();
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$rootTeam->members()->attach($user->id, ['role' => 'admin']);
|
||||||
|
|
||||||
|
$this->actingAs($user);
|
||||||
|
session(['currentTeam' => ['id' => $rootTeam->id]]);
|
||||||
|
|
||||||
|
Livewire::test(Updates::class)
|
||||||
|
->set('docker_registry_url', 'docker.io; touch /tmp/pwned')
|
||||||
|
->call('instantSave')
|
||||||
|
->assertHasErrors(['docker_registry_url' => 'in']);
|
||||||
|
|
||||||
|
expect($settings->fresh()->docker_registry_url)->toBe('docker.io');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,43 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Actions\Server\UpdateCoolify;
|
use App\Actions\Server\UpdateCoolify;
|
||||||
|
use App\Livewire\Settings\Updates;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Illuminate\Support\Once;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
beforeEach(function () {
|
uses(TestCase::class, RefreshDatabase::class);
|
||||||
// Mock Server
|
|
||||||
$this->mockServer = Mockery::mock(Server::class)->makePartial();
|
|
||||||
$this->mockServer->id = 0;
|
|
||||||
|
|
||||||
// Mock InstanceSettings
|
function updateCoolifyTestCreateRootServerAndSettings(array $settings = []): void
|
||||||
$this->settings = Mockery::mock(InstanceSettings::class);
|
{
|
||||||
$this->settings->is_auto_update_enabled = true;
|
Team::factory()->create(['id' => 0]);
|
||||||
$this->settings->shouldReceive('save')->andReturn(true);
|
Server::forceCreate([
|
||||||
});
|
'id' => 0,
|
||||||
|
'name' => 'localhost',
|
||||||
|
'ip' => '127.0.0.1',
|
||||||
|
'user' => 'root',
|
||||||
|
'team_id' => 0,
|
||||||
|
'private_key_id' => 1,
|
||||||
|
]);
|
||||||
|
InstanceSettings::forceCreate(array_merge([
|
||||||
|
'id' => 0,
|
||||||
|
'is_auto_update_enabled' => true,
|
||||||
|
'auto_update_frequency' => '0 0 * * *',
|
||||||
|
'update_check_frequency' => '0 * * * *',
|
||||||
|
], $settings));
|
||||||
|
Once::flush();
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
Mockery::close();
|
Mockery::close();
|
||||||
|
|
@ -26,15 +48,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
it('validates cache against running version before fallback', function () {
|
it('validates cache against running version before fallback', function () {
|
||||||
// Mock Server::find to return our mock server
|
updateCoolifyTestCreateRootServerAndSettings();
|
||||||
Server::shouldReceive('find')
|
|
||||||
->with(0)
|
|
||||||
->andReturn($this->mockServer);
|
|
||||||
|
|
||||||
// Mock instanceSettings
|
|
||||||
$this->app->instance('App\Models\InstanceSettings', function () {
|
|
||||||
return $this->settings;
|
|
||||||
});
|
|
||||||
|
|
||||||
// CDN fails
|
// CDN fails
|
||||||
Http::fake(['*' => Http::response(null, 500)]);
|
Http::fake(['*' => Http::response(null, 500)]);
|
||||||
|
|
@ -51,7 +65,7 @@
|
||||||
try {
|
try {
|
||||||
$action->handle(manual_update: false);
|
$action->handle(manual_update: false);
|
||||||
expect(false)->toBeTrue('Expected exception was not thrown');
|
expect(false)->toBeTrue('Expected exception was not thrown');
|
||||||
} catch (\Exception $e) {
|
} catch (Exception $e) {
|
||||||
expect($e->getMessage())->toContain('cache version');
|
expect($e->getMessage())->toContain('cache version');
|
||||||
expect($e->getMessage())->toContain('4.0.5');
|
expect($e->getMessage())->toContain('4.0.5');
|
||||||
expect($e->getMessage())->toContain('4.0.10');
|
expect($e->getMessage())->toContain('4.0.10');
|
||||||
|
|
@ -59,15 +73,9 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses validated cache when CDN fails and cache is newer', function () {
|
it('uses validated cache when CDN fails and cache is newer', function () {
|
||||||
// Mock Server::find
|
updateCoolifyTestCreateRootServerAndSettings();
|
||||||
Server::shouldReceive('find')
|
Queue::fake();
|
||||||
->with(0)
|
config(['constants.ssh.mux_enabled' => false]);
|
||||||
->andReturn($this->mockServer);
|
|
||||||
|
|
||||||
// Mock instanceSettings
|
|
||||||
$this->app->instance('App\Models\InstanceSettings', function () {
|
|
||||||
return $this->settings;
|
|
||||||
});
|
|
||||||
|
|
||||||
// CDN fails
|
// CDN fails
|
||||||
Http::fake(['*' => Http::response(null, 500)]);
|
Http::fake(['*' => Http::response(null, 500)]);
|
||||||
|
|
@ -78,12 +86,9 @@
|
||||||
|
|
||||||
config(['constants.coolify.version' => '4.0.5']);
|
config(['constants.coolify.version' => '4.0.5']);
|
||||||
|
|
||||||
// Mock the update method to prevent actual update
|
$action = new UpdateCoolify;
|
||||||
$action = Mockery::mock(UpdateCoolify::class)->makePartial();
|
|
||||||
$action->shouldReceive('update')->once();
|
|
||||||
$action->server = $this->mockServer;
|
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Log::shouldReceive('warning')
|
Log::shouldReceive('warning')
|
||||||
->once()
|
->once()
|
||||||
->with('Failed to fetch fresh version from CDN, using validated cache', Mockery::type('array'));
|
->with('Failed to fetch fresh version from CDN, using validated cache', Mockery::type('array'));
|
||||||
|
|
||||||
|
|
@ -93,16 +98,198 @@
|
||||||
expect($action->latestVersion)->toBe('4.0.10');
|
expect($action->latestVersion)->toBe('4.0.10');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevents downgrade even with manual update', function () {
|
it('passes the saved registry URL to the upgrade script command', function () {
|
||||||
// Mock Server::find
|
Queue::fake();
|
||||||
Server::shouldReceive('find')
|
config([
|
||||||
->with(0)
|
'app.env' => 'testing',
|
||||||
->andReturn($this->mockServer);
|
'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,
|
||||||
|
]);
|
||||||
|
|
||||||
// Mock instanceSettings
|
updateCoolifyTestCreateRootServerAndSettings([
|
||||||
$this->app->instance('App\Models\InstanceSettings', function () {
|
'is_auto_update_enabled' => true,
|
||||||
return $this->settings;
|
'docker_registry_url' => 'ghcr.io',
|
||||||
});
|
]);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'*' => Http::response([
|
||||||
|
'coolify' => ['v4' => ['version' => '4.0.10']],
|
||||||
|
], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
(new UpdateCoolify)->handle();
|
||||||
|
|
||||||
|
expect(Activity::query()->latest('id')->first()?->getExtraProperty('command'))->toBe(
|
||||||
|
"curl -fsSL https://cdn.example.com/upgrade.sh -o /data/coolify/source/upgrade.sh\n".
|
||||||
|
"bash /data/coolify/source/upgrade.sh '4.0.10' '1.0.14' 'ghcr.io'"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to docker io for the upgrade script command when no registry is saved', function () {
|
||||||
|
Queue::fake();
|
||||||
|
config([
|
||||||
|
'app.env' => 'testing',
|
||||||
|
'constants.coolify.version' => '4.0.9',
|
||||||
|
'constants.coolify.helper_version' => '1.0.14',
|
||||||
|
'constants.coolify.registry_url' => 'ghcr.io',
|
||||||
|
'constants.coolify.upgrade_script_url' => 'https://cdn.example.com/upgrade.sh',
|
||||||
|
'constants.ssh.mux_enabled' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings([
|
||||||
|
'is_auto_update_enabled' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'*' => Http::response([
|
||||||
|
'coolify' => ['v4' => ['version' => '4.0.10']],
|
||||||
|
], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
(new UpdateCoolify)->handle();
|
||||||
|
|
||||||
|
expect(Activity::query()->latest('id')->first()?->getExtraProperty('command'))->toBe(
|
||||||
|
"curl -fsSL https://cdn.example.com/upgrade.sh -o /data/coolify/source/upgrade.sh\n".
|
||||||
|
"bash /data/coolify/source/upgrade.sh '4.0.10' '1.0.14' 'docker.io'"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults the registry setting to docker io when no registry is saved', function () {
|
||||||
|
config([
|
||||||
|
'app.env' => 'testing',
|
||||||
|
'constants.coolify.registry_url' => 'ghcr.io',
|
||||||
|
'constants.coolify.self_hosted' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings();
|
||||||
|
|
||||||
|
$rootTeam = Team::findOrFail(0);
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$rootTeam->members()->attach($user->id, ['role' => 'admin']);
|
||||||
|
|
||||||
|
$this->actingAs($user);
|
||||||
|
session(['currentTeam' => ['id' => $rootTeam->id]]);
|
||||||
|
|
||||||
|
Livewire::test(Updates::class)
|
||||||
|
->assertSet('docker_registry_url', 'docker.io');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the database registry for helper images when the configured helper image is default', function () {
|
||||||
|
config([
|
||||||
|
'constants.coolify.registry_url' => 'ghcr.io',
|
||||||
|
'constants.coolify.helper_image' => 'ghcr.io/coollabsio/coolify-helper',
|
||||||
|
]);
|
||||||
|
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings([
|
||||||
|
'docker_registry_url' => 'docker.io',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(coolifyRegistryUrl())->toBe('docker.io')
|
||||||
|
->and(coolifyHelperImage())->toBe('docker.io/coollabsio/coolify-helper');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves an explicit custom helper image override', function () {
|
||||||
|
config([
|
||||||
|
'constants.coolify.registry_url' => 'docker.io',
|
||||||
|
'constants.coolify.helper_image' => 'registry.example.com/custom/helper',
|
||||||
|
]);
|
||||||
|
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings([
|
||||||
|
'docker_registry_url' => 'ghcr.io',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(coolifyHelperImage())->toBe('registry.example.com/custom/helper');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the database registry for sentinel images', function () {
|
||||||
|
$action = file_get_contents(app_path('Actions/Server/StartSentinel.php'));
|
||||||
|
|
||||||
|
expect($action)->toContain("\$image = coolifyRegistryUrl().'/coollabsio/sentinel:'.\$version;");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects invalid registry values and does not sync them', function () {
|
||||||
|
Process::fake();
|
||||||
|
config([
|
||||||
|
'app.env' => 'testing',
|
||||||
|
'constants.coolify.registry_url' => 'docker.io',
|
||||||
|
]);
|
||||||
|
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings([
|
||||||
|
'is_auto_update_enabled' => true,
|
||||||
|
'auto_update_frequency' => '0 0 * * *',
|
||||||
|
'update_check_frequency' => '0 * * * *',
|
||||||
|
'docker_registry_url' => 'docker.io',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rootTeam = Team::findOrFail(0);
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$rootTeam->members()->attach($user->id, ['role' => 'admin']);
|
||||||
|
|
||||||
|
$this->actingAs($user);
|
||||||
|
session(['currentTeam' => ['id' => $rootTeam->id]]);
|
||||||
|
|
||||||
|
Livewire::test(Updates::class)
|
||||||
|
->set('docker_registry_url', 'ghcr.io; touch /tmp/pwned')
|
||||||
|
->call('submit')
|
||||||
|
->assertHasErrors(['docker_registry_url' => ['in']]);
|
||||||
|
|
||||||
|
expect(InstanceSettings::findOrFail(0)->docker_registry_url)->toBe('docker.io');
|
||||||
|
Process::assertDidntRun(fn () => true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not save registry changes when syncing the env file fails', function () {
|
||||||
|
config([
|
||||||
|
'app.env' => 'testing',
|
||||||
|
'constants.coolify.registry_url' => 'docker.io',
|
||||||
|
'constants.coolify.self_hosted' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings([
|
||||||
|
'is_auto_update_enabled' => true,
|
||||||
|
'auto_update_frequency' => '0 0 * * *',
|
||||||
|
'update_check_frequency' => '0 * * * *',
|
||||||
|
'docker_registry_url' => 'docker.io',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rootTeam = Team::findOrFail(0);
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$rootTeam->members()->attach($user->id, ['role' => 'admin']);
|
||||||
|
|
||||||
|
$this->actingAs($user);
|
||||||
|
session(['currentTeam' => ['id' => $rootTeam->id]]);
|
||||||
|
|
||||||
|
$component = new class extends Updates
|
||||||
|
{
|
||||||
|
protected function syncRegistryUrlToEnv(string $registryUrl): void
|
||||||
|
{
|
||||||
|
throw new RuntimeException('sync failed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$component->settings = InstanceSettings::findOrFail(0);
|
||||||
|
$component->auto_update_frequency = '0 0 * * *';
|
||||||
|
$component->update_check_frequency = '0 * * * *';
|
||||||
|
$component->is_auto_update_enabled = true;
|
||||||
|
$component->docker_registry_url = 'ghcr.io';
|
||||||
|
|
||||||
|
$component->instantSave();
|
||||||
|
|
||||||
|
expect(InstanceSettings::findOrFail(0)->docker_registry_url)->toBe('docker.io');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('appends registry url to env file when the key is missing', function () {
|
||||||
|
$component = new Updates;
|
||||||
|
$method = new ReflectionMethod(Updates::class, 'registryEnvSyncCommand');
|
||||||
|
|
||||||
|
expect($method->invoke($component, 'ghcr.io'))
|
||||||
|
->toContain("grep -q '^REGISTRY_URL=' /data/coolify/source/.env")
|
||||||
|
->toContain("sed -i 's|^REGISTRY_URL=.*|REGISTRY_URL=ghcr.io|' /data/coolify/source/.env")
|
||||||
|
->toContain("printf '%s\\n' 'REGISTRY_URL=ghcr.io' >> /data/coolify/source/.env");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents downgrade even with manual update', function () {
|
||||||
|
updateCoolifyTestCreateRootServerAndSettings();
|
||||||
|
|
||||||
// CDN returns older version
|
// CDN returns older version
|
||||||
Http::fake([
|
Http::fake([
|
||||||
|
|
@ -116,7 +303,7 @@
|
||||||
|
|
||||||
$action = new UpdateCoolify;
|
$action = new UpdateCoolify;
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Log::shouldReceive('error')
|
Log::shouldReceive('error')
|
||||||
->once()
|
->once()
|
||||||
->with('Downgrade prevented', Mockery::type('array'));
|
->with('Downgrade prevented', Mockery::type('array'));
|
||||||
|
|
||||||
|
|
@ -124,7 +311,7 @@
|
||||||
try {
|
try {
|
||||||
$action->handle(manual_update: true);
|
$action->handle(manual_update: true);
|
||||||
expect(false)->toBeTrue('Expected exception was not thrown');
|
expect(false)->toBeTrue('Expected exception was not thrown');
|
||||||
} catch (\Exception $e) {
|
} catch (Exception $e) {
|
||||||
expect($e->getMessage())->toContain('Cannot downgrade');
|
expect($e->getMessage())->toContain('Cannot downgrade');
|
||||||
expect($e->getMessage())->toContain('4.0.10');
|
expect($e->getMessage())->toContain('4.0.10');
|
||||||
expect($e->getMessage())->toContain('4.0.0');
|
expect($e->getMessage())->toContain('4.0.0');
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,38 @@ function assertBashSyntaxIsValid(string $path): void
|
||||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
it('uses the selected registry url when extracting upgrade images', function (string $path) {
|
||||||
|
$script = file_get_contents(getcwd().'/'.$path);
|
||||||
|
|
||||||
|
expect($script)->toContain('IMAGES=$(REGISTRY_URL=${REGISTRY_URL} LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images');
|
||||||
|
})->with([
|
||||||
|
'stable upgrade' => 'scripts/upgrade.sh',
|
||||||
|
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('persists the selected registry url during upgrades', function (string $path) {
|
||||||
|
$script = file_get_contents(getcwd().'/'.$path);
|
||||||
|
|
||||||
|
expect($script)->toContain('set_env_var "REGISTRY_URL" "$REGISTRY_URL"');
|
||||||
|
})->with([
|
||||||
|
'stable upgrade' => 'scripts/upgrade.sh',
|
||||||
|
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('uses the existing env registry url when old callers do not pass a registry argument', function (string $path) {
|
||||||
|
$script = file_get_contents(getcwd().'/'.$path);
|
||||||
|
|
||||||
|
expect($script)
|
||||||
|
->toContain('if [ -n "${3+x}" ]; then')
|
||||||
|
->toContain('REGISTRY_URL="$3"')
|
||||||
|
->toContain('elif [ -f "$ENV_FILE" ] && grep -q "^REGISTRY_URL=" "$ENV_FILE"; then')
|
||||||
|
->toContain("REGISTRY_URL=$(grep \"^REGISTRY_URL=\" \"\$ENV_FILE\" | cut -d '=' -f2- | head -n1)")
|
||||||
|
->toContain('REGISTRY_URL="docker.io"');
|
||||||
|
})->with([
|
||||||
|
'stable upgrade' => 'scripts/upgrade.sh',
|
||||||
|
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||||
|
]);
|
||||||
|
|
||||||
it('keeps postgres upgrade compose override in future upgrade compose commands', function (string $path) {
|
it('keeps postgres upgrade compose override in future upgrade compose commands', function (string $path) {
|
||||||
$script = file_get_contents(getcwd().'/'.$path);
|
$script = file_get_contents(getcwd().'/'.$path);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue