diff --git a/app/Jobs/RestartProxyJob.php b/app/Jobs/RestartProxyJob.php index 1a8a026b6..e4bd8d47e 100644 --- a/app/Jobs/RestartProxyJob.php +++ b/app/Jobs/RestartProxyJob.php @@ -2,10 +2,12 @@ namespace App\Jobs; -use App\Actions\Proxy\StartProxy; -use App\Actions\Proxy\StopProxy; +use App\Actions\Proxy\GetProxyConfiguration; +use App\Actions\Proxy\SaveProxyConfiguration; +use App\Enums\ProxyTypes; use App\Events\ProxyStatusChangedUI; use App\Models\Server; +use App\Services\ProxyDashboardCacheService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; @@ -20,13 +22,13 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue public $tries = 1; - public $timeout = 60; + public $timeout = 120; public ?int $activity_id = null; public function middleware(): array { - return [(new WithoutOverlapping('restart-proxy-'.$this->server->uuid))->expireAfter(60)->dontRelease()]; + return [(new WithoutOverlapping('restart-proxy-'.$this->server->uuid))->expireAfter(120)->dontRelease()]; } public function __construct(public Server $server) {} @@ -34,28 +36,26 @@ public function __construct(public Server $server) {} public function handle() { try { - // Set status to restarting and notify UI immediately + // Set status to restarting $this->server->proxy->status = 'restarting'; - $this->server->save(); - ProxyStatusChangedUI::dispatch($this->server->team_id); - - // Stop proxy - StopProxy::run($this->server, restarting: true); - - // Clear force_stop flag $this->server->proxy->force_stop = false; $this->server->save(); - // Start proxy asynchronously - returns Activity immediately - // The ProxyStatusChanged event will be dispatched when the remote process completes, - // which triggers ProxyStatusChangedNotification listener - $activity = StartProxy::run($this->server, force: true, restarting: true); + // Build combined stop + start commands for a single activity + $commands = $this->buildRestartCommands(); - // Dispatch event with activity ID immediately so UI can show logs in real-time - if ($activity && is_object($activity)) { - $this->activity_id = $activity->id; - ProxyStatusChangedUI::dispatch($this->server->team_id, $this->activity_id); - } + // Create activity and dispatch immediately - returns Activity right away + // The remote_process runs asynchronously, so UI gets activity ID instantly + $activity = remote_process( + $commands, + $this->server, + callEventOnFinish: 'ProxyStatusChanged', + callEventData: $this->server->id + ); + + // Store activity ID and notify UI immediately with it + $this->activity_id = $activity->id; + ProxyStatusChangedUI::dispatch($this->server->team_id, $this->activity_id); } catch (\Throwable $e) { // Set error status @@ -65,7 +65,100 @@ public function handle() // Notify UI of error ProxyStatusChangedUI::dispatch($this->server->team_id); + // Clear dashboard cache on error + ProxyDashboardCacheService::clearCache($this->server); + return handleError($e); } } + + /** + * Build combined stop + start commands for proxy restart. + * This creates a single command sequence that shows all logs in one activity. + */ + private function buildRestartCommands(): array + { + $proxyType = $this->server->proxyType(); + $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; + $proxy_path = $this->server->proxyPath(); + $stopTimeout = 30; + + // Get proxy configuration + $configuration = GetProxyConfiguration::run($this->server); + if (! $configuration) { + throw new \Exception('Configuration is not synced'); + } + SaveProxyConfiguration::run($this->server, $configuration); + $docker_compose_yml_base64 = base64_encode($configuration); + $this->server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value(); + $this->server->save(); + + $commands = collect([]); + + // === STOP PHASE === + $commands = $commands->merge([ + "echo '>>> Stopping proxy...'", + "docker stop -t=$stopTimeout $containerName 2>/dev/null || true", + "docker rm -f $containerName 2>/dev/null || true", + '# Wait for container to be fully removed', + 'for i in {1..10}; do', + " if ! docker ps -a --format \"{{.Names}}\" | grep -q \"^$containerName$\"; then", + ' break', + ' fi', + ' sleep 1', + 'done', + "echo '>>> Proxy stopped successfully.'", + ]); + + // === START PHASE === + if ($this->server->isSwarmManager()) { + $commands = $commands->merge([ + "echo '>>> Starting proxy (Swarm mode)...'", + "mkdir -p $proxy_path/dynamic", + "cd $proxy_path", + "echo 'Creating required Docker Compose file.'", + "echo 'Starting coolify-proxy.'", + 'docker stack deploy --detach=true -c docker-compose.yml coolify-proxy', + "echo '>>> Successfully started coolify-proxy.'", + ]); + } else { + if (isDev() && $proxyType === ProxyTypes::CADDY->value) { + $proxy_path = '/data/coolify/proxy/caddy'; + } + $caddyfile = 'import /dynamic/*.caddy'; + $commands = $commands->merge([ + "echo '>>> Starting proxy...'", + "mkdir -p $proxy_path/dynamic", + "cd $proxy_path", + "echo '$caddyfile' > $proxy_path/dynamic/Caddyfile", + "echo 'Creating required Docker Compose file.'", + "echo 'Pulling docker image.'", + 'docker compose pull', + 'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then', + " echo 'Stopping and removing existing coolify-proxy.'", + ' docker stop coolify-proxy 2>/dev/null || true', + ' docker rm -f coolify-proxy 2>/dev/null || true', + ' # Wait for container to be fully removed', + ' for i in {1..10}; do', + ' if ! docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then', + ' break', + ' fi', + ' echo "Waiting for coolify-proxy to be removed... ($i/10)"', + ' sleep 1', + ' done', + " echo 'Successfully stopped and removed existing coolify-proxy.'", + 'fi', + ]); + // Ensure required networks exist BEFORE docker compose up + $commands = $commands->merge(ensureProxyNetworksExist($this->server)); + $commands = $commands->merge([ + "echo 'Starting coolify-proxy.'", + 'docker compose up -d --wait --remove-orphans', + "echo '>>> Successfully started coolify-proxy.'", + ]); + $commands = $commands->merge(connectProxyToNetworks($this->server)); + } + + return $commands->toArray(); + } } diff --git a/tests/Unit/Jobs/RestartProxyJobTest.php b/tests/Unit/Jobs/RestartProxyJobTest.php index 94c738b79..422abd940 100644 --- a/tests/Unit/Jobs/RestartProxyJobTest.php +++ b/tests/Unit/Jobs/RestartProxyJobTest.php @@ -43,7 +43,7 @@ public function test_job_has_correct_configuration() $job = new RestartProxyJob($server); $this->assertEquals(1, $job->tries); - $this->assertEquals(60, $job->timeout); + $this->assertEquals(120, $job->timeout); $this->assertNull($job->activity_id); }