coolify/app/Actions/Proxy/StartProxy.php
Andras Bacsai 246e3cd8a2 fix: resolve Docker validation race conditions and sudo prefix bug
- Fix sudo prefix bug: Use word boundary matching to prevent 'do' keyword from matching 'docker' commands
- Add ensureProxyNetworksExist() helper to create networks before docker compose up
- Ensure networks exist synchronously before dispatching async proxy startup to prevent race conditions
- Update comprehensive unit tests for sudo parsing (50 tests passing)

This resolves issues where Docker commands failed to execute with sudo on non-root servers and where proxy networks were not created before the proxy container started.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 09:04:42 +01:00

101 lines
4.1 KiB
PHP

<?php
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Events\ProxyStatusChanged;
use App\Events\ProxyStatusChangedUI;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Spatie\Activitylog\Models\Activity;
class StartProxy
{
use AsAction;
public function handle(Server $server, bool $async = true, bool $force = false, bool $restarting = false): string|Activity
{
$proxyType = $server->proxyType();
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
return 'OK';
}
$server->proxy->set('status', 'starting');
$server->save();
$server->refresh();
if (! $restarting) {
ProxyStatusChangedUI::dispatch($server->team_id);
}
$commands = collect([]);
$proxy_path = $server->proxyPath();
$configuration = GetProxyConfiguration::run($server);
if (! $configuration) {
throw new \Exception('Configuration is not synced');
}
SaveProxyConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
$server->save();
if ($server->isSwarmManager()) {
$commands = $commands->merge([
"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()) {
if ($proxyType === ProxyTypes::CADDY->value) {
$proxy_path = '/data/coolify/proxy/caddy';
}
}
$caddyfile = 'import /dynamic/*.caddy';
$commands = $commands->merge([
"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 (networks are declared as external)
$commands = $commands->merge(ensureProxyNetworksExist($server));
$commands = $commands->merge([
"echo 'Starting coolify-proxy.'",
'docker compose up -d --wait --remove-orphans',
"echo 'Successfully started coolify-proxy.'",
]);
$commands = $commands->merge(connectProxyToNetworks($server));
}
if ($async) {
return remote_process($commands, $server, callEventOnFinish: 'ProxyStatusChanged', callEventData: $server->id);
} else {
instant_remote_process($commands, $server);
$server->proxy->set('type', $proxyType);
$server->save();
ProxyStatusChanged::dispatch($server->id);
return 'OK';
}
}
}