diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index a26e7daaa..b5ebd92b2 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -30,7 +30,6 @@ public function handle($manual_update = false) if (! $this->server) { return; } - CleanupDocker::dispatch($this->server, false, false); // Fetch fresh version from CDN instead of using cache try { @@ -117,17 +116,12 @@ public function handle($manual_update = false) private function update() { - $helperImage = config('constants.coolify.helper_image'); - $latest_version = getHelperVersion(); - instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false); - - $image = config('constants.coolify.registry_url').'/coollabsio/coolify:'.$this->latestVersion; - instant_remote_process(["docker pull -q $image"], $this->server, false); - + $latestHelperImageVersion = getHelperVersion(); $upgradeScriptUrl = config('constants.coolify.upgrade_script_url'); + remote_process([ "curl -fsSL {$upgradeScriptUrl} -o /data/coolify/source/upgrade.sh", - "bash /data/coolify/source/upgrade.sh $this->latestVersion", + "bash /data/coolify/source/upgrade.sh $this->latestVersion $latestHelperImageVersion", ], $this->server); } } diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index f13baa7a7..36bee2a23 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -4,6 +4,7 @@ use App\Actions\Server\UpdateCoolify; use App\Models\InstanceSettings; +use App\Models\Server; use Livewire\Component; class Upgrade extends Component @@ -14,12 +15,20 @@ class Upgrade extends Component public string $latestVersion = ''; + public string $currentVersion = ''; + protected $listeners = ['updateAvailable' => 'checkUpdate']; + public function mount() + { + $this->currentVersion = config('constants.coolify.version'); + } + public function checkUpdate() { try { $this->latestVersion = get_latest_version_of_coolify(); + $this->currentVersion = config('constants.coolify.version'); $this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false); if (isDev()) { $this->isUpgradeAvailable = true; @@ -41,4 +50,71 @@ public function upgrade() return handleError($e, $this); } } + + public function getUpgradeStatus(): array + { + // Only root team members can view upgrade status + if (auth()->user()?->currentTeam()?->id !== 0) { + return ['status' => 'none']; + } + + $server = Server::find(0); + if (! $server) { + return ['status' => 'none']; + } + + $statusFile = '/data/coolify/source/.upgrade-status'; + + try { + $content = instant_remote_process( + ["cat {$statusFile} 2>/dev/null || echo ''"], + $server, + false + ); + $content = trim($content ?? ''); + } catch (\Throwable $e) { + return ['status' => 'none']; + } + + if (empty($content)) { + return ['status' => 'none']; + } + + $parts = explode('|', $content); + if (count($parts) < 3) { + return ['status' => 'none']; + } + + [$step, $message, $timestamp] = $parts; + + // Check if status is stale (older than 10 minutes) + try { + $statusTime = new \DateTime($timestamp); + $now = new \DateTime; + $diffMinutes = ($now->getTimestamp() - $statusTime->getTimestamp()) / 60; + + if ($diffMinutes > 10) { + return ['status' => 'none']; + } + } catch (\Throwable $e) { + return ['status' => 'none']; + } + + if ($step === 'error') { + return [ + 'status' => 'error', + 'step' => 0, + 'message' => $message, + ]; + } + + $stepInt = (int) $step; + $status = $stepInt >= 6 ? 'complete' : 'in_progress'; + + return [ + 'status' => $status, + 'step' => $stepInt, + 'message' => $message, + ]; + } } diff --git a/other/nightly/upgrade.sh b/other/nightly/upgrade.sh index bfcd11095..0d3896647 100644 --- a/other/nightly/upgrade.sh +++ b/other/nightly/upgrade.sh @@ -64,9 +64,45 @@ if [ -f /root/.docker/config.json ]; then DOCKER_CONFIG_MOUNT="-v /root/.docker/config.json:/root/.docker/config.json" fi -if [ -f /data/coolify/source/docker-compose.custom.yml ]; then - echo "docker-compose.custom.yml detected." >>"$LOGFILE" - 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 --project-name coolify --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --wait --wait-timeout 60" >>"$LOGFILE" 2>&1 -else - 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 --project-name coolify --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --wait --wait-timeout 60" >>"$LOGFILE" 2>&1 -fi +# Pull all required images before stopping containers +# This ensures we don't take down the system if image pull fails (rate limits, network issues, etc.) +echo "Pulling required Docker images..." >>"$LOGFILE" +docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE}" >>"$LOGFILE" 2>&1 || { echo "Failed to pull Coolify image. Aborting upgrade." >>"$LOGFILE"; exit 1; } +docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}" >>"$LOGFILE" 2>&1 || { echo "Failed to pull Coolify helper image. Aborting upgrade." >>"$LOGFILE"; exit 1; } +docker pull postgres:15-alpine >>"$LOGFILE" 2>&1 || { echo "Failed to pull PostgreSQL image. Aborting upgrade." >>"$LOGFILE"; exit 1; } +docker pull redis:7-alpine >>"$LOGFILE" 2>&1 || { echo "Failed to pull Redis image. Aborting upgrade." >>"$LOGFILE"; exit 1; } +# Pull realtime image - version is hardcoded in docker-compose.prod.yml, extract it or use a known version +docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.10" >>"$LOGFILE" 2>&1 || { echo "Failed to pull Coolify realtime image. Aborting upgrade." >>"$LOGFILE"; exit 1; } +echo "All images pulled successfully." >>"$LOGFILE" + +# Stop and remove existing Coolify containers to prevent conflicts +# This handles both old installations (project "source") and new ones (project "coolify") +# Use nohup to ensure the script continues even if SSH connection is lost +echo "Starting container restart sequence (detached)..." >>"$LOGFILE" + +nohup bash -c " + LOGFILE='$LOGFILE' + DOCKER_CONFIG_MOUNT='$DOCKER_CONFIG_MOUNT' + REGISTRY_URL='$REGISTRY_URL' + LATEST_HELPER_VERSION='$LATEST_HELPER_VERSION' + LATEST_IMAGE='$LATEST_IMAGE' + + # Stop and remove containers + echo 'Stopping existing Coolify containers...' >>\"\$LOGFILE\" + for container in coolify coolify-db coolify-redis coolify-realtime; do + if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then + docker stop \"\$container\" >>\"\$LOGFILE\" 2>&1 || true + docker rm \"\$container\" >>\"\$LOGFILE\" 2>&1 || true + echo \" - Removed container: \$container\" >>\"\$LOGFILE\" + fi + done + + # Start new containers + if [ -f /data/coolify/source/docker-compose.custom.yml ]; then + echo 'docker-compose.custom.yml detected.' >>\"\$LOGFILE\" + 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 --project-name coolify --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1 + else + 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 --project-name coolify --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1 + fi + echo 'Upgrade completed.' >>\"\$LOGFILE\" +" >>"$LOGFILE" 2>&1 & diff --git a/resources/views/components/upgrade-progress.blade.php b/resources/views/components/upgrade-progress.blade.php new file mode 100644 index 000000000..d65273cce --- /dev/null +++ b/resources/views/components/upgrade-progress.blade.php @@ -0,0 +1,152 @@ +@props(['step' => 0]) + +{{-- + Step Mapping (Backend → UI): + Backend steps 1-2 (config download, env update) → UI Step 1: Preparing + Backend step 3 (pulling images) → UI Step 2: Helper + UI Step 3: Image + Backend steps 4-5 (stop/start containers) → UI Step 4: Restart + Backend step 6 (complete) → mapped in JS mapStepToUI() in upgrade.blade.php + + The currentStep variable is inherited from parent Alpine component (upgradeModal). +--}} +