diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index 2f2e2096b..bfc65d8d2 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -63,7 +63,16 @@ public function handle(Server $server, bool $async = true, bool $force = false, 'docker compose pull', 'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then', " echo 'Stopping and removing existing coolify-proxy.'", - ' docker rm -f coolify-proxy || true', + ' 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', "echo 'Starting coolify-proxy.'", diff --git a/app/Actions/Proxy/StopProxy.php b/app/Actions/Proxy/StopProxy.php index a11754cd0..8f1b8af1c 100644 --- a/app/Actions/Proxy/StopProxy.php +++ b/app/Actions/Proxy/StopProxy.php @@ -24,8 +24,15 @@ public function handle(Server $server, bool $forceStop = true, int $timeout = 30 } instant_remote_process(command: [ - "docker stop --time=$timeout $containerName", - "docker rm -f $containerName", + "docker stop --time=$timeout $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', ], server: $server, throwError: false); $server->proxy->force_stop = $forceStop; diff --git a/tests/Unit/StartProxyTest.php b/tests/Unit/StartProxyTest.php new file mode 100644 index 000000000..7b6589d60 --- /dev/null +++ b/tests/Unit/StartProxyTest.php @@ -0,0 +1,87 @@ +/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', + "echo 'Starting coolify-proxy.'", + 'docker compose up -d --wait --remove-orphans', + "echo 'Successfully started coolify-proxy.'", + ]); + + $commandsString = $commands->implode("\n"); + + // Verify the cleanup sequence includes all required components + expect($commandsString)->toContain('docker stop coolify-proxy 2>/dev/null || true') + ->and($commandsString)->toContain('docker rm -f coolify-proxy 2>/dev/null || true') + ->and($commandsString)->toContain('for i in {1..10}; do') + ->and($commandsString)->toContain('if ! docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then') + ->and($commandsString)->toContain('break') + ->and($commandsString)->toContain('sleep 1') + ->and($commandsString)->toContain('docker compose up -d --wait --remove-orphans'); + + // Verify the order: cleanup must come before compose up + $stopPosition = strpos($commandsString, 'docker stop coolify-proxy'); + $waitLoopPosition = strpos($commandsString, 'for i in {1..10}'); + $composeUpPosition = strpos($commandsString, 'docker compose up -d'); + + expect($stopPosition)->toBeLessThan($waitLoopPosition) + ->and($waitLoopPosition)->toBeLessThan($composeUpPosition); +}); + +it('includes error suppression in container cleanup commands', function () { + // Test that cleanup commands suppress errors to prevent failures + // when the container doesn't exist + + $cleanupCommands = [ + ' docker stop coolify-proxy 2>/dev/null || true', + ' docker rm -f coolify-proxy 2>/dev/null || true', + ]; + + foreach ($cleanupCommands as $command) { + expect($command)->toContain('2>/dev/null || true'); + } +}); + +it('waits up to 10 seconds for container removal', function () { + // Verify the wait loop has correct bounds + + $waitLoop = [ + ' 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', + ]; + + $loopString = implode("\n", $waitLoop); + + // Verify loop iterates 10 times + expect($loopString)->toContain('{1..10}') + ->and($loopString)->toContain('sleep 1') + ->and($loopString)->toContain('break'); // Early exit when container is gone +}); diff --git a/tests/Unit/StopProxyTest.php b/tests/Unit/StopProxyTest.php new file mode 100644 index 000000000..62151e1d1 --- /dev/null +++ b/tests/Unit/StopProxyTest.php @@ -0,0 +1,69 @@ +/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', + ' sleep 1', + 'done', + ]; + + $commandsString = implode("\n", $commands); + + // Verify the stop sequence includes all required components + expect($commandsString)->toContain('docker stop --time=30 coolify-proxy') + ->and($commandsString)->toContain('docker rm -f coolify-proxy') + ->and($commandsString)->toContain('for i in {1..10}; do') + ->and($commandsString)->toContain('if ! docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"') + ->and($commandsString)->toContain('break') + ->and($commandsString)->toContain('sleep 1'); + + // Verify order: stop before remove, and wait loop after remove + $stopPosition = strpos($commandsString, 'docker stop'); + $removePosition = strpos($commandsString, 'docker rm -f'); + $waitLoopPosition = strpos($commandsString, 'for i in {1..10}'); + + expect($stopPosition)->toBeLessThan($removePosition) + ->and($removePosition)->toBeLessThan($waitLoopPosition); +}); + +it('includes error suppression in stop proxy commands', function () { + // Test that stop/remove commands suppress errors gracefully + + $commands = [ + 'docker stop --time=30 coolify-proxy 2>/dev/null || true', + 'docker rm -f coolify-proxy 2>/dev/null || true', + ]; + + foreach ($commands as $command) { + expect($command)->toContain('2>/dev/null || true'); + } +}); + +it('uses configurable timeout for docker stop', function () { + // Verify that stop command includes the timeout parameter + + $timeout = 30; + $stopCommand = "docker stop --time=$timeout coolify-proxy 2>/dev/null || true"; + + expect($stopCommand)->toContain('--time=30'); +}); + +it('waits for swarm service container removal correctly', function () { + // Test that the container name pattern matches swarm naming + + $containerName = 'coolify-proxy_traefik'; + $checkCommand = " if ! docker ps -a --format \"{{.Names}}\" | grep -q \"^$containerName$\"; then"; + + expect($checkCommand)->toContain('coolify-proxy_traefik'); +});