server->uuid))->expireAfter(120)->dontRelease()]; } public function __construct(public Server $server) { $this->onQueue(deployment_queue()); } public function handle() { try { $configuration = GetProxyConfiguration::run($this->server); if (! $configuration) { throw new \Exception('Configuration is not synced'); } ProxyPortParser::fromConfiguration($configuration); // Set status to restarting $this->server->proxy->status = 'restarting'; $this->server->proxy->force_stop = false; $this->server->save(); // Build combined stop + start commands for a single activity $commands = $this->buildRestartCommands($configuration); // 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, queue: deployment_queue(), ); // Store activity ID and notify UI immediately with it $this->activity_id = $activity->id; ProxyStatusChangedUI::dispatch($this->server->team_id, $this->activity_id); } catch (\InvalidArgumentException $e) { return handleError($e); } catch (\Throwable $e) { // Set error status $this->server->proxy->status = 'error'; $this->server->save(); // 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(string $configuration): array { $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; $proxy_path = $this->server->proxyPath(); // Absolute paths: a non-root SSH user may not be able to enter the proxy directory (#4255). $compose_file = rtrim($proxy_path, '/').'/docker-compose.yml'; $stopTimeout = 30; SaveProxyConfiguration::run($this->server, $configuration); $this->server->markProxyConfigurationApplied($configuration); $commands = collect([]); // === STOP PHASE === $commands = $commands->merge([ "echo 'Stopping proxy...'", dockerStopCommand($stopTimeout, $containerName, $this->server).' 2>/dev/null || true', "docker rm -f $containerName 2>/dev/null || true", '# Wait for container to be fully removed', 'for i in {1..15}; do', " if ! docker ps -a --format \"{{.Names}}\" | grep -q \"^$containerName$\"; then", " echo 'Container removed successfully.'", ' break', ' fi', ' echo "Waiting for container to be removed... ($i/15)"', ' sleep 1', ' # Force remove on each iteration in case it got stuck', " docker rm -f $containerName 2>/dev/null || true", 'done', '# Final verification and force cleanup', "if docker ps -a --format \"{{.Names}}\" | grep -q \"^$containerName$\"; then", " echo 'Container still exists after wait, forcing removal...'", " docker rm -f $containerName 2>/dev/null || true", ' sleep 2', 'fi', "echo 'Proxy stopped successfully.'", ]); // === START PHASE === if ($this->server->isSwarmManager()) { $commands = $commands->merge([ "echo 'Starting proxy (Swarm mode)...'", "mkdir -p $proxy_path/dynamic", "echo 'Creating required Docker Compose file.'", "echo 'Starting coolify-proxy.'", "docker stack deploy --detach=true -c $compose_file coolify-proxy", "echo 'Successfully started coolify-proxy.'", ]); } else { $caddyfile = 'import /dynamic/*.caddy'; $commands = $commands->merge([ "echo 'Starting proxy...'", "mkdir -p $proxy_path/dynamic", "echo '$caddyfile' | tee $proxy_path/dynamic/Caddyfile > /dev/null", "echo 'Creating required Docker Compose file.'", "echo 'Pulling docker image.'", "docker compose -f $compose_file pull", ]); // Ensure required networks exist BEFORE docker compose up $commands = $commands->merge(ensureProxyNetworksExist($this->server)); $commands = $commands->merge([ "echo 'Starting coolify-proxy.'", "docker compose -f $compose_file up -d --wait --remove-orphans", "echo 'Successfully started coolify-proxy.'", ]); $commands = $commands->merge(connectProxyToNetworks($this->server)); } return $commands->toArray(); } }