Merge pull request #5522 from priard/main
fix: Resolve port 80 in use error for dual-stack network configurations
This commit is contained in:
		
							
								
								
									
										11134
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										11134
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -37,8 +37,12 @@ class CheckProxy
 | 
				
			|||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Determine proxy container name based on environment
 | 
				
			||||||
 | 
					        $proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($server->isSwarm()) {
 | 
					        if ($server->isSwarm()) {
 | 
				
			||||||
            $status = getContainerStatus($server, 'coolify-proxy_traefik');
 | 
					            $status = getContainerStatus($server, $proxyContainerName);
 | 
				
			||||||
            $server->proxy->set('status', $status);
 | 
					            $server->proxy->set('status', $status);
 | 
				
			||||||
            $server->save();
 | 
					            $server->save();
 | 
				
			||||||
            if ($status === 'running') {
 | 
					            if ($status === 'running') {
 | 
				
			||||||
@@ -47,7 +51,7 @@ class CheckProxy
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            $status = getContainerStatus($server, 'coolify-proxy');
 | 
					            $status = getContainerStatus($server, $proxyContainerName);
 | 
				
			||||||
            if ($status === 'running') {
 | 
					            if ($status === 'running') {
 | 
				
			||||||
                $server->proxy->set('status', 'running');
 | 
					                $server->proxy->set('status', 'running');
 | 
				
			||||||
                $server->save();
 | 
					                $server->save();
 | 
				
			||||||
@@ -65,30 +69,8 @@ class CheckProxy
 | 
				
			|||||||
            $portsToCheck = ['80', '443'];
 | 
					            $portsToCheck = ['80', '443'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            foreach ($portsToCheck as $port) {
 | 
					            foreach ($portsToCheck as $port) {
 | 
				
			||||||
                // Try multiple methods to check port availability
 | 
					                // Use the smart port checker that handles dual-stack properly
 | 
				
			||||||
                $commands = [
 | 
					                if ($this->isPortConflict($server, $port, $proxyContainerName)) {
 | 
				
			||||||
                    // Method 1: Check /proc/net/tcp directly (convert port to hex)
 | 
					 | 
				
			||||||
                    "cat /proc/net/tcp | grep -q '00000000:".str_pad(dechex($port), 4, '0', STR_PAD_LEFT)."'",
 | 
					 | 
				
			||||||
                    // Method 2: Use ss command (modern alternative to netstat)
 | 
					 | 
				
			||||||
                    "ss -tuln | grep -q ':$port '",
 | 
					 | 
				
			||||||
                    // Method 3: Use lsof if available
 | 
					 | 
				
			||||||
                    "lsof -i :$port >/dev/null 2>&1",
 | 
					 | 
				
			||||||
                    // Method 4: Use fuser if available
 | 
					 | 
				
			||||||
                    "fuser $port/tcp >/dev/null 2>&1",
 | 
					 | 
				
			||||||
                ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                $portInUse = false;
 | 
					 | 
				
			||||||
                foreach ($commands as $command) {
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        instant_remote_process([$command], $server);
 | 
					 | 
				
			||||||
                        $portInUse = true;
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    } catch (\Throwable $e) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if ($portInUse) {
 | 
					 | 
				
			||||||
                    if ($fromUI) {
 | 
					                    if ($fromUI) {
 | 
				
			||||||
                        throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
 | 
					                        throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
@@ -126,4 +108,144 @@ class CheckProxy
 | 
				
			|||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Smart port checker that handles dual-stack configurations
 | 
				
			||||||
 | 
					     * Returns true only if there's a real port conflict (not just dual-stack)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // First check if our own proxy is using this port (which is fine)
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
 | 
				
			||||||
 | 
					            $containerId = trim(instant_remote_process([$getProxyContainerId], $server));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (! empty($containerId)) {
 | 
				
			||||||
 | 
					                $checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'";
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    instant_remote_process([$checkProxyPort], $server);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Our proxy is using the port, which is fine
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                } catch (\Throwable $e) {
 | 
				
			||||||
 | 
					                    // Our container exists but not using this port
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
 | 
					            // Container not found or error checking, continue with regular checks
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Command sets for different ways to check ports, ordered by preference
 | 
				
			||||||
 | 
					        $commandSets = [
 | 
				
			||||||
 | 
					            // Set 1: Use ss to check listener counts by protocol stack
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                'available' => 'command -v ss >/dev/null 2>&1',
 | 
				
			||||||
 | 
					                'check' => [
 | 
				
			||||||
 | 
					                    // Get listening process details
 | 
				
			||||||
 | 
					                    "ss_output=\$(ss -tuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
 | 
				
			||||||
 | 
					                    // Count IPv4 listeners
 | 
				
			||||||
 | 
					                    "echo \"\$ss_output\" | grep -c 'LISTEN.*:$port'",
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            // Set 2: Use netstat as alternative to ss
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                'available' => 'command -v netstat >/dev/null 2>&1',
 | 
				
			||||||
 | 
					                'check' => [
 | 
				
			||||||
 | 
					                    // Get listening process details
 | 
				
			||||||
 | 
					                    "netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port'",
 | 
				
			||||||
 | 
					                    // Count listeners
 | 
				
			||||||
 | 
					                    "echo \"\$netstat_output\" | grep ':$port' | grep -c 'LISTEN'",
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            // Set 3: Use lsof as last resort
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                'available' => 'command -v lsof >/dev/null 2>&1',
 | 
				
			||||||
 | 
					                'check' => [
 | 
				
			||||||
 | 
					                    // Get process using the port
 | 
				
			||||||
 | 
					                    "lsof -i :$port -P -n | grep 'LISTEN'",
 | 
				
			||||||
 | 
					                    // Count listeners
 | 
				
			||||||
 | 
					                    "lsof -i :$port -P -n | grep 'LISTEN' | wc -l",
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Try each command set until we find one available
 | 
				
			||||||
 | 
					        foreach ($commandSets as $set) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                // Check if the command is available
 | 
				
			||||||
 | 
					                instant_remote_process([$set['available']], $server);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Run the actual check commands
 | 
				
			||||||
 | 
					                $output = instant_remote_process($set['check'], $server, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Parse the output lines
 | 
				
			||||||
 | 
					                $lines = explode("\n", trim($output));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Get the detailed output and listener count
 | 
				
			||||||
 | 
					                $details = trim($lines[0] ?? '');
 | 
				
			||||||
 | 
					                $count = intval(trim($lines[1] ?? '0'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // If no listeners or empty result, port is free
 | 
				
			||||||
 | 
					                if ($count == 0 || empty($details)) {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Try to detect if this is our coolify-proxy
 | 
				
			||||||
 | 
					                if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) {
 | 
				
			||||||
 | 
					                    // It's likely our docker or proxy, which is fine
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6)
 | 
				
			||||||
 | 
					                // If exactly 2 listeners and both have same port, likely dual-stack
 | 
				
			||||||
 | 
					                if ($count <= 2) {
 | 
				
			||||||
 | 
					                    // Check if it looks like a standard dual-stack setup
 | 
				
			||||||
 | 
					                    $isDualStack = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Look for IPv4 and IPv6 in the listing (ss output format)
 | 
				
			||||||
 | 
					                    if (preg_match('/LISTEN.*:'.$port.'\s/', $details) &&
 | 
				
			||||||
 | 
					                        (preg_match('/\*:'.$port.'\s/', $details) ||
 | 
				
			||||||
 | 
					                         preg_match('/:::'.$port.'\s/', $details))) {
 | 
				
			||||||
 | 
					                        $isDualStack = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // For netstat format
 | 
				
			||||||
 | 
					                    if (strpos($details, '0.0.0.0:'.$port) !== false &&
 | 
				
			||||||
 | 
					                        strpos($details, ':::'.$port) !== false) {
 | 
				
			||||||
 | 
					                        $isDualStack = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // For lsof format (IPv4 and IPv6)
 | 
				
			||||||
 | 
					                    if (strpos($details, '*:'.$port) !== false &&
 | 
				
			||||||
 | 
					                        preg_match('/\*:'.$port.'.*IPv4/', $details) &&
 | 
				
			||||||
 | 
					                        preg_match('/\*:'.$port.'.*IPv6/', $details)) {
 | 
				
			||||||
 | 
					                        $isDualStack = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if ($isDualStack) {
 | 
				
			||||||
 | 
					                        return false; // This is just a normal dual-stack setup
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // If we get here, it's likely a real port conflict
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            } catch (\Throwable $e) {
 | 
				
			||||||
 | 
					                // This command set failed, try the next one
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Fallback to simpler check if all above methods fail
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Just try to bind to the port directly to see if it's available
 | 
				
			||||||
 | 
					            $checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'";
 | 
				
			||||||
 | 
					            $result = instant_remote_process([$checkCommand], $server, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return trim($result) === 'in-use';
 | 
				
			||||||
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
 | 
					            // If everything fails, assume the port is free to avoid false positives
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user