From a47a03cd0b6df153c00a3ea906527b535c9fd1d0 Mon Sep 17 00:00:00 2001 From: Jeff Wainwright <1074042+yowainwright@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:44:32 -0700 Subject: [PATCH] fix: use random port for desktop/standalone builds to avoid port conflicts --- Dockerfile.fix | 17 +++++++++++ Dockerfile.master | 20 +++++++++++++ comfy/cli_args.py | 11 ++++++- server.py | 5 ++++ test_port_conflict.py | 36 +++++++++++++++++++++++ tests-unit/comfy_test/folder_path_test.py | 25 ++++++++++++++++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.fix create mode 100644 Dockerfile.master create mode 100644 test_port_conflict.py diff --git a/Dockerfile.fix b/Dockerfile.fix new file mode 100644 index 000000000..b5d49767c --- /dev/null +++ b/Dockerfile.fix @@ -0,0 +1,17 @@ +# Dockerfile.fix - Shows the fix from the fix-electron-port branch +FROM python:3.11-slim + +WORKDIR /app + +# Copy ComfyUI from current directory (fix-electron-port branch with our changes) +COPY . /app/ + +# Install dependencies +RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu && \ + pip install -r requirements.txt + +# FIX: With our changes, --windows-standalone-build automatically uses port 0 +# No EXPOSE needed - port is dynamically assigned + +# The fix makes --windows-standalone-build use port 0 automatically +CMD ["python", "main.py", "--listen", "0.0.0.0", "--windows-standalone-build", "--cpu"] \ No newline at end of file diff --git a/Dockerfile.master b/Dockerfile.master new file mode 100644 index 000000000..c2f4ed99c --- /dev/null +++ b/Dockerfile.master @@ -0,0 +1,20 @@ +# Dockerfile.master - Shows the issue from master branch +FROM python:3.11-slim + +WORKDIR /app + +# Clone ComfyUI from master branch (without the fix) +RUN apt-get update && apt-get install -y git && \ + git clone https://github.com/comfyanonymous/ComfyUI.git /app && \ + git checkout master + +# Install dependencies +RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu && \ + pip install -r requirements.txt + +# ISSUE: Desktop app expects port 8000, but that conflicts with Docker +# Running with --windows-standalone-build doesn't help on master +EXPOSE 8188 + +# This will use port 8188 even with --windows-standalone-build (no fix yet) +CMD ["python", "main.py", "--port", "8188", "--listen", "0.0.0.0", "--windows-standalone-build", "--cpu"] \ No newline at end of file diff --git a/comfy/cli_args.py b/comfy/cli_args.py index de3e85c08..01e80d117 100644 --- a/comfy/cli_args.py +++ b/comfy/cli_args.py @@ -1,6 +1,8 @@ import argparse import enum import os +import socket +import sys import comfy.options @@ -36,7 +38,7 @@ class EnumAction(argparse.Action): parser = argparse.ArgumentParser() parser.add_argument("--listen", type=str, default="127.0.0.1", metavar="IP", nargs="?", const="0.0.0.0,::", help="Specify the IP address to listen on (default: 127.0.0.1). You can give a list of ip addresses by separating them with a comma like: 127.2.2.2,127.3.3.3 If --listen is provided without an argument, it defaults to 0.0.0.0,:: (listens on all ipv4 and ipv6)") -parser.add_argument("--port", type=int, default=8188, help="Set the listen port.") +parser.add_argument("--port", type=int, default=8188, help="Set the listen port. Use 0 for a random available port (useful for desktop apps).") parser.add_argument("--tls-keyfile", type=str, help="Path to TLS (SSL) key file. Enables TLS, makes app accessible at https://... requires --tls-certfile to function") parser.add_argument("--tls-certfile", type=str, help="Path to TLS (SSL) certificate file. Enables TLS, makes app accessible at https://... requires --tls-keyfile to function") parser.add_argument("--enable-cors-header", type=str, default=None, metavar="ORIGIN", nargs="?", const="*", help="Enable CORS (Cross-Origin Resource Sharing) with optional origin or allow all with default '*'.") @@ -220,6 +222,13 @@ else: if args.windows_standalone_build: args.auto_launch = True + if args.port != 0: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.bind(('', args.port)) + sock.close() + except OSError: + args.port = 0 if args.disable_auto_launch: args.auto_launch = False diff --git a/server.py b/server.py index 8f9c88ebf..dbd4a6a3f 100644 --- a/server.py +++ b/server.py @@ -943,6 +943,11 @@ class PromptServer(): port = addr[1] site = web.TCPSite(runner, address, port, ssl_context=ssl_ctx) await site.start() + + if port == 0 and site._server and site._server.sockets: + port = site._server.sockets[0].getsockname()[1] + if verbose: + logging.info("Random port assigned: {}".format(port)) if not hasattr(self, 'address'): self.address = address #TODO: remove this diff --git a/test_port_conflict.py b/test_port_conflict.py new file mode 100644 index 000000000..e6a6da7aa --- /dev/null +++ b/test_port_conflict.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import socket +import sys +import os + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +print("Testing port conflict detection...") + +sock_test = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +try: + sock_test.bind(('', 8188)) + print("✓ Port 8188 is available") + sock_test.close() +except OSError: + print("✗ Port 8188 is already in use") + +print("\nSimulating standalone build behavior...") +port = 8188 +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +try: + sock.bind(('', port)) + sock.close() + print(f"✓ Would use port {port}") +except OSError: + print(f"✗ Port {port} is in use, switching to random port (0)") + port = 0 + +print(f"\nFinal port selection: {port}") + +if port == 0: + test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + test_sock.bind(('', 0)) + actual_port = test_sock.getsockname()[1] + print(f"Random port assigned: {actual_port}") + test_sock.close() \ No newline at end of file diff --git a/tests-unit/comfy_test/folder_path_test.py b/tests-unit/comfy_test/folder_path_test.py index 775e15c36..623a8bca6 100644 --- a/tests-unit/comfy_test/folder_path_test.py +++ b/tests-unit/comfy_test/folder_path_test.py @@ -160,3 +160,28 @@ def test_base_path_change_clears_old(set_base_dir): for name in ["controlnet", "diffusion_models", "text_encoders"]: assert len(folder_paths.get_folder_paths(name)) == 2 + + +def test_windows_standalone_random_port(): + """Test that --windows-standalone-build uses port 0 when port is in use""" + import socket + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(('', 8188)) + try: + with patch.object(sys, 'argv', ["main.py", "--windows-standalone-build"]): + reload(comfy.cli_args) + assert comfy.cli_args.args.port == 0 + assert comfy.cli_args.args.windows_standalone_build + finally: + sock.close() + + with patch.object(sys, 'argv', ["main.py", "--windows-standalone-build", "--port", "9999"]): + reload(comfy.cli_args) + assert comfy.cli_args.args.port == 9999 or comfy.cli_args.args.port == 0 + assert comfy.cli_args.args.windows_standalone_build + + with patch.object(sys, 'argv', ["main.py"]): + reload(comfy.cli_args) + assert comfy.cli_args.args.port == 8188 + assert not comfy.cli_args.args.windows_standalone_build