From cc71b72da9c69eddf219e5050e85280984427420 Mon Sep 17 00:00:00 2001 From: Carsten Meininger Date: Thu, 28 May 2026 13:15:07 +0200 Subject: [PATCH] fix: improve podman socket detection and connection fallthrough - Probe each socket candidate and fall through on failure instead of raising immediately, so a bad STRIX_RUNTIME_SOCKET or DOCKER_HOST doesn't prevent auto-detection from working. - Add macOS podman machine support via `podman machine inspect` and TMPDIR-based fallback. - Include the underlying docker exception in error messages and debug logs so users can diagnose connection failures. Co-Authored-By: Claude Opus 4.7 --- strix/interface/utils.py | 6 ++-- strix/runtime/backends.py | 76 +++++++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/strix/interface/utils.py b/strix/interface/utils.py index 53f83f19a..a7c1b0d18 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1337,7 +1337,7 @@ def check_docker_connection() -> Any: try: return create_docker_client(backend) - except DockerException: + except DockerException as exc: console = Console() error_text = Text() @@ -1349,6 +1349,7 @@ def check_docker_connection() -> Any: "Please ensure Podman is installed and running, and try running strix again.\n\n", style="white", ) + error_text.append(f"Reason: {exc}\n\n", style="dim") error_text.append( "Tip: set STRIX_RUNTIME_SOCKET to your Podman socket path if auto-detection fails.\n", style="dim", @@ -1358,9 +1359,10 @@ def check_docker_connection() -> Any: error_text.append("\n\n", style="white") error_text.append("Cannot connect to Docker daemon.\n", style="white") error_text.append( - "Please ensure Docker Desktop is installed and running, and try running strix again.\n", + "Please ensure Docker Desktop is installed and running, and try running strix again.\n\n", style="white", ) + error_text.append(f"Reason: {exc}\n", style="dim") panel = Panel( error_text, diff --git a/strix/runtime/backends.py b/strix/runtime/backends.py index 32e0b765f..c3bb385ed 100644 --- a/strix/runtime/backends.py +++ b/strix/runtime/backends.py @@ -35,7 +35,7 @@ def get_host_gateway(backend_name: str) -> str: def create_docker_client(backend_name: str) -> Any: """Create a ``docker.DockerClient`` pointed at the right daemon. - Resolution order: + Resolution order (each step falls through on failure): 1. ``STRIX_RUNTIME_SOCKET`` env var / config (explicit) 2. ``DOCKER_HOST`` env var (standard docker-py mechanism) 3. Per-backend auto-detection (e.g. Podman socket probing) @@ -45,26 +45,46 @@ def create_docker_client(backend_name: str) -> Any: settings = load_settings() socket_path = settings.runtime.socket_path + if socket_path: - logger.debug("Using explicit runtime socket: %s", socket_path) - return docker.DockerClient(base_url=socket_path) + try: + logger.debug("Trying STRIX_RUNTIME_SOCKET: %s", socket_path) + return docker.DockerClient(base_url=socket_path) + except Exception as exc: + logger.debug("STRIX_RUNTIME_SOCKET failed: %s", exc) if os.environ.get("DOCKER_HOST"): - return docker.from_env() + try: + return docker.from_env() + except Exception as exc: + logger.debug("DOCKER_HOST connection failed: %s", exc) if backend_name == "podman": for candidate in _podman_socket_candidates(): path = candidate.replace("unix://", "") if os.path.exists(path): - logger.debug("Auto-detected podman socket: %s", candidate) - return docker.DockerClient(base_url=candidate) + try: + logger.debug("Trying podman socket: %s", candidate) + return docker.DockerClient(base_url=candidate) + except Exception as exc: + logger.debug("Podman socket %s failed: %s", candidate, exc) return docker.from_env() def _podman_socket_candidates() -> list[str]: - """Return Podman socket URI candidates (rootless first, then rootful).""" + """Return Podman socket URI candidates ordered by likelihood. + + Covers Linux rootless, Linux rootful, and macOS ``podman machine`` + (both applehv and libkrun). + """ candidates: list[str] = [] + + # -- macOS podman machine (applehv / libkrun) -- + for entry in _macos_podman_machine_sockets(): + candidates.append(entry) + + # -- Linux rootless -- xdg_runtime = os.environ.get("XDG_RUNTIME_DIR") if xdg_runtime: candidates.append(f"unix://{xdg_runtime}/podman/podman.sock") @@ -73,10 +93,52 @@ def _podman_socket_candidates() -> list[str]: candidates.append(f"unix:///run/user/{os.getuid()}/podman/podman.sock") except (AttributeError, OSError): pass + + # -- Linux rootful -- candidates.append("unix:///run/podman/podman.sock") + + # -- macOS podman machine temp-dir fallback -- + tmpdir = os.environ.get("TMPDIR") + if tmpdir: + candidates.append(f"unix://{tmpdir}podman/podman-machine-default-api.sock") + return candidates +def _macos_podman_machine_sockets() -> list[str]: + """Query ``podman machine inspect`` for the exact socket path (macOS).""" + import subprocess + + try: + proc = subprocess.run( + ["podman", "machine", "inspect"], + capture_output=True, + text=True, + timeout=5, + ) + except (FileNotFoundError, subprocess.TimeoutExpired, OSError): + return [] + + if proc.returncode != 0: + return [] + + try: + import json + + machines = json.loads(proc.stdout) + except json.JSONDecodeError: + return [] + + sockets: list[str] = [] + for m in machines: + conn = m.get("ConnectionInfo", {}) + sock = conn.get("PodmanSocket", {}) + path = sock.get("Path") + if path: + sockets.append(f"unix://{path}") + return sockets + + # -- backend factories --------------------------------------------------