coolify/app/Jobs/RestartProxyJob.php
Andras Bacsai 36da7174d5 Combine stop+start into single activity for real-time logs
Instead of calling StopProxy::run() (synchronous) then StartProxy::run()
(async), now we build a single command sequence that includes both stop
and start phases. This creates one Activity immediately via remote_process(),
so the UI receives the activity ID right away and can show logs in real-time
from the very beginning of the restart operation.

Key changes:
- Removed dependency on StopProxy and StartProxy actions
- Build combined command sequence inline in buildRestartCommands()
- Use remote_process() directly which returns Activity immediately
- Increased timeout from 60s to 120s to accommodate full restart
- Activity ID dispatched to UI within milliseconds of job starting

Flow is now:
1. Job starts → sets "restarting" status
2. Commands built synchronously (fast, no SSH)
3. remote_process() creates Activity and dispatches CoolifyTask job
4. Activity ID sent to UI immediately via WebSocket
5. UI opens activity monitor with real-time streaming logs
6. Logs show "Stopping proxy..." then "Starting proxy..." as they happen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:21:26 +01:00

164 lines
6.4 KiB
PHP

<?php
namespace App\Jobs;
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;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 120;
public ?int $activity_id = null;
public function middleware(): array
{
return [(new WithoutOverlapping('restart-proxy-'.$this->server->uuid))->expireAfter(120)->dontRelease()];
}
public function __construct(public Server $server) {}
public function handle()
{
try {
// 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();
// 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
$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(): 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();
}
}