diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index 3e868d6..6721108 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -1818,8 +1818,7 @@ async def start_instance( instance_port, ) tunnel_info = start_cloudflared_tunnel( - container_name=instance.container_name or instance.name, - port=instance_port, + host_port=instance.port, ) instance.tunnel_id = tunnel_info["pid"] instance.public_url = tunnel_info["url"] @@ -2026,15 +2025,12 @@ async def restart_instance( "error": f"Tool type '{tool_type.name if tool_type else 'unknown'}' has no port configured", } - instance_port = tool_type.default_port - # Only create tunnel for web-enabled tools if tool_type.interface_type == "web": # Create new temporary tunnel try: tunnel_info = start_cloudflared_tunnel( - container_name=instance.container_name or instance.name, - port=instance_port, + host_port=instance.port or 0, ) instance.tunnel_id = tunnel_info["pid"] instance.public_url = tunnel_info["url"] @@ -2267,16 +2263,9 @@ async def recreate_tunnel_endpoint( "message": "Tunnel is already healthy", } - # Get tool type for default port - tool_type = await session.get(ToolType, instance.tool_type_id) - instance_port = ( - tool_type.default_port if tool_type and tool_type.default_port else 8080 - ) - try: tunnel_info = recreate_tunnel( - container_name=instance.container_name or instance.name, - port=instance_port, + host_port=instance.port or 0, old_pid=instance.tunnel_id, ) instance.tunnel_id = tunnel_info["pid"] diff --git a/apps/api/src/services/docker.py b/apps/api/src/services/docker.py index d179d92..625a6a9 100644 --- a/apps/api/src/services/docker.py +++ b/apps/api/src/services/docker.py @@ -385,16 +385,15 @@ def find_free_port(start: int = 10000, end: int = 20000) -> int: def start_cloudflared_tunnel( - container_name: str, port: int, timeout: int = 30 + host_port: int, timeout: int = 30 ) -> dict[str, str]: - """Start a temporary Cloudflare tunnel for a container. + """Start a temporary Cloudflare tunnel to localhost. Uses 'cloudflared tunnel --url' to create a temporary tunnel with a random trycloudflare.com URL. Args: - container_name: Name of the Docker container to tunnel to - port: Port number the container listens on + host_port: Host-mapped port number (e.g. from find_free_port) timeout: Maximum seconds to wait for tunnel URL Returns: @@ -405,8 +404,8 @@ def start_cloudflared_tunnel( logger = logging.getLogger(__name__) - # First verify the container is accessible - logger.info("Checking connectivity to %s:%d...", container_name, port) + # First verify the container is accessible via the host-mapped port + logger.info("Checking connectivity to localhost:%d...", host_port) for attempt in range(10): check = subprocess.run( [ @@ -416,7 +415,7 @@ def start_cloudflared_tunnel( "/dev/null", "-w", "%{http_code}", - f"http://{container_name}:{port}", + f"http://localhost:{host_port}", ], capture_output=True, text=True, @@ -430,13 +429,13 @@ def start_cloudflared_tunnel( time.sleep(1) else: logger.warning( - "Container %s:%d not responding to curl checks", container_name, port + "localhost:%d not responding to curl checks", host_port ) # Run cloudflared in background, capture output - logger.info("Starting cloudflared tunnel to http://%s:%d", container_name, port) + logger.info("Starting cloudflared tunnel to http://localhost:%d", host_port) proc = subprocess.Popen( - ["cloudflared", "tunnel", "--url", f"http://{container_name}:{port}"], + ["cloudflared", "tunnel", "--url", f"http://localhost:{host_port}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, @@ -491,15 +490,14 @@ def stop_cloudflared_tunnel(pid: str) -> None: def recreate_tunnel( - container_name: str, port: int, old_pid: str | None = None + host_port: int, old_pid: str | None = None ) -> dict[str, str]: """Recreate a temporary Cloudflare tunnel. Stops the old tunnel (if pid provided) and starts a new one. Args: - container_name: Name of the Docker container to tunnel to - port: Port number the container listens on + host_port: Host-mapped port number (e.g. from find_free_port) old_pid: Optional PID of the old tunnel process to stop Returns: @@ -508,7 +506,7 @@ def recreate_tunnel( if old_pid: stop_cloudflared_tunnel(old_pid) - return start_cloudflared_tunnel(container_name, port) + return start_cloudflared_tunnel(host_port) def check_tunnel_health(url: str, timeout: int = 10) -> dict[str, Any]: diff --git a/apps/api/src/services/lifecycle_hooks.py b/apps/api/src/services/lifecycle_hooks.py index 17c07e8..ddc4530 100644 --- a/apps/api/src/services/lifecycle_hooks.py +++ b/apps/api/src/services/lifecycle_hooks.py @@ -120,9 +120,9 @@ async def publish_lifecycle_event( # Create notification for instance owner (fire-and-forget) # Skip intermediate "starting" notifications — only notify on terminal states # (failed or successful attempts) - _is_starting_intermediate = event_type == "instance.started" and ( - status or instance.status - ) == "starting" + _is_starting_intermediate = ( + event_type == "instance.started" and (status or instance.status) == "starting" + ) if _is_starting_intermediate: return