From 919e81821de5469d76c7e97f4617fee1ef926cdd Mon Sep 17 00:00:00 2001 From: mesutoezdil Date: Thu, 16 Jul 2026 21:22:32 +0200 Subject: [PATCH 1/3] fix(driver-podman): resolve Podman socket via auto-detection Align Podman socket selection with the existing Docker model: explicit config wins, otherwise probe openshell-core for a responsive socket. This also fixes the original HOME-unset panic, since resolution no longer hardcodes a per-OS default path. - add detect_podman_socket() in openshell-core, mirroring detect_docker_socket - PodmanComputeConfig.socket_path is now Option, no default - remove default_socket_path() (podman driver) and podman_socket_path() (vm driver), both replaced by the shared detector - update server env override and CLI for the new Option type - add tests: responsive-candidate detection in openshell-core, and config-error (not panic) when no socket is configured or reachable --- crates/openshell-core/src/config.rs | 47 ++++++++++- crates/openshell-driver-podman/src/config.rs | 84 ++----------------- .../openshell-driver-podman/src/container.rs | 2 +- crates/openshell-driver-podman/src/driver.rs | 83 ++++++++++++++++-- crates/openshell-driver-podman/src/grpc.rs | 2 +- crates/openshell-driver-podman/src/main.rs | 6 +- crates/openshell-driver-vm/src/driver.rs | 26 +----- .../src/compute/driver_config.rs | 2 +- 8 files changed, 132 insertions(+), 120 deletions(-) diff --git a/crates/openshell-core/src/config.rs b/crates/openshell-core/src/config.rs index c9f8a86b21..b200eded64 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -171,9 +171,19 @@ pub fn detect_driver() -> Option { } fn is_podman_available() -> bool { - podman_socket_candidates() + detect_podman_socket().is_some() +} + +/// Return the first responsive Podman API socket, or `None` if none respond. +pub fn detect_podman_socket() -> Option { + detect_podman_socket_from_candidates(&podman_socket_candidates()) +} + +fn detect_podman_socket_from_candidates(candidates: &[PathBuf]) -> Option { + candidates .iter() - .any(|path| podman_socket_responds(path)) + .find(|path| podman_socket_responds(path)) + .cloned() } fn podman_socket_candidates() -> Vec { @@ -972,8 +982,9 @@ mod tests { ComputeDriverKind, Config, DEFAULT_SERVICE_ROUTING_DOMAIN, GatewayInterceptorBindingPolicy, GatewayInterceptorConfig, GatewayInterceptorFailurePolicy, GatewayJwtConfig, GatewayProviderProfileSourceConfig, detect_docker_socket_from_candidates, detect_driver, - docker_host_unix_socket_path, docker_socket_responds, is_unix_socket, - normalize_compute_driver_name, podman_socket_candidates_from_env, podman_socket_responds, + detect_podman_socket_from_candidates, docker_host_unix_socket_path, docker_socket_responds, + is_unix_socket, normalize_compute_driver_name, podman_socket_candidates_from_env, + podman_socket_responds, }; #[cfg(unix)] use std::io::{Read as _, Write as _}; @@ -1349,6 +1360,34 @@ mod tests { ))); } + #[cfg(unix)] + #[test] + fn podman_socket_detection_returns_the_responsive_candidate() { + let temp_dir = tempfile::tempdir().expect("create temp dir"); + let inactive_path = temp_dir.path().join("inactive.sock"); + let inactive_listener = UnixListener::bind(&inactive_path).expect("bind inactive socket"); + drop(inactive_listener); + + let responsive_path = temp_dir.path().join("responsive.sock"); + let listener = UnixListener::bind(&responsive_path).expect("bind responsive socket"); + let handle = std::thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept podman probe"); + let mut request = [0_u8; 128]; + let _ = stream.read(&mut request).expect("read podman probe"); + stream + .write_all( + b"HTTP/1.1 200 OK\r\nLibpod-Api-Version: 5.8.2\r\nContent-Length: 2\r\n\r\nOK", + ) + .expect("write podman ping response"); + }); + + assert_eq!( + detect_podman_socket_from_candidates(&[inactive_path, responsive_path.clone(),]), + Some(responsive_path) + ); + handle.join().expect("probe server exits"); + } + #[test] #[allow(unsafe_code)] // std::env::set_var/remove_var require unsafe in Rust 2024 fn detect_driver_prefers_kubernetes_when_k8s_env_is_set() { diff --git a/crates/openshell-driver-podman/src/config.rs b/crates/openshell-driver-podman/src/config.rs index def8e5f3dc..1c5efa0a89 100644 --- a/crates/openshell-driver-podman/src/config.rs +++ b/crates/openshell-driver-podman/src/config.rs @@ -70,9 +70,11 @@ impl FromStr for ImagePullPolicy { #[serde(default, deny_unknown_fields)] pub struct PodmanComputeConfig { /// Path to the Podman API Unix socket. - /// Default: `$XDG_RUNTIME_DIR/podman/podman.sock` (Linux), - /// `$HOME/.local/share/containers/podman/machine/podman.sock` (macOS). - pub socket_path: PathBuf, + /// + /// `None` means auto-detect: the driver probes + /// [`openshell_core::config::detect_podman_socket`] for the first + /// responsive candidate when it starts. + pub socket_path: Option, /// Default OCI image for sandboxes. pub default_image: String, /// Image pull policy for sandbox images. @@ -215,38 +217,12 @@ impl PodmanComputeConfig { String::new() } } - - /// Resolve the default socket path from the environment. - /// - /// - **macOS**: `$HOME/.local/share/containers/podman/machine/podman.sock` - /// (the symlink created by `podman machine` pointing to the VM API socket). - /// - **Linux**: `$XDG_RUNTIME_DIR/podman/podman.sock` when set (by - /// `pam_systemd`/logind), otherwise `/run/user/{uid}/podman/podman.sock` - /// using the real UID via `getuid()`. - #[must_use] - pub fn default_socket_path() -> PathBuf { - #[cfg(target_os = "macos")] - { - let home = std::env::var("HOME").expect("HOME must be set on macOS"); - PathBuf::from(home).join(".local/share/containers/podman/machine/podman.sock") - } - #[cfg(target_os = "linux")] - { - std::env::var("XDG_RUNTIME_DIR").map_or_else( - |_| { - let uid = rustix::process::getuid().as_raw(); - PathBuf::from(format!("/run/user/{uid}/podman/podman.sock")) - }, - |xdg| PathBuf::from(xdg).join("podman/podman.sock"), - ) - } - } } impl Default for PodmanComputeConfig { fn default() -> Self { Self { - socket_path: Self::default_socket_path(), + socket_path: None, default_image: openshell_core::image::default_sandbox_image(), image_pull_policy: ImagePullPolicy::default(), grpc_endpoint: String::new(), @@ -296,39 +272,6 @@ impl std::fmt::Debug for PodmanComputeConfig { mod tests { use super::*; - /// Serialises env-mutating tests so that parallel test threads cannot - /// observe each other's changes to `XDG_RUNTIME_DIR`. - static ENV_LOCK: std::sync::LazyLock> = - std::sync::LazyLock::new(|| std::sync::Mutex::new(())); - - #[test] - #[cfg(target_os = "linux")] - fn default_socket_path_respects_xdg_runtime_dir() { - let _guard = ENV_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - temp_env::with_vars([("XDG_RUNTIME_DIR", Some("/tmp/test-xdg"))], || { - let path = PodmanComputeConfig::default_socket_path(); - assert_eq!(path, PathBuf::from("/tmp/test-xdg/podman/podman.sock")); - }); - } - - #[test] - #[cfg(target_os = "linux")] - fn default_socket_path_falls_back_to_uid() { - let _guard = ENV_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - temp_env::with_vars([("XDG_RUNTIME_DIR", None::<&str>)], || { - let path = PodmanComputeConfig::default_socket_path(); - let uid = rustix::process::getuid().as_raw(); - assert_eq!( - path, - PathBuf::from(format!("/run/user/{uid}/podman/podman.sock")) - ); - }); - } - #[test] fn default_config_sets_health_check_interval() { let cfg = PodmanComputeConfig::default(); @@ -382,21 +325,6 @@ mod tests { assert!(err.to_string().contains("sandbox_pids_limit")); } - #[test] - #[cfg(target_os = "macos")] - fn default_socket_path_uses_podman_machine_on_macos() { - let _guard = ENV_LOCK - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - temp_env::with_vars([("HOME", Some("/Users/testuser"))], || { - let path = PodmanComputeConfig::default_socket_path(); - assert_eq!( - path, - PathBuf::from("/Users/testuser/.local/share/containers/podman/machine/podman.sock") - ); - }); - } - // ── TLS config validation ───────────────────────────────────────── #[test] diff --git a/crates/openshell-driver-podman/src/container.rs b/crates/openshell-driver-podman/src/container.rs index e4a0b79189..874b5859ce 100644 --- a/crates/openshell-driver-podman/src/container.rs +++ b/crates/openshell-driver-podman/src/container.rs @@ -1808,7 +1808,7 @@ mod tests { fn test_config() -> PodmanComputeConfig { PodmanComputeConfig { - socket_path: std::path::PathBuf::from("/tmp/test.sock"), + socket_path: Some(std::path::PathBuf::from("/tmp/test.sock")), default_image: "test-image:latest".to_string(), grpc_endpoint: "http://localhost:50051".to_string(), host_gateway_ip: String::new(), diff --git a/crates/openshell-driver-podman/src/driver.rs b/crates/openshell-driver-podman/src/driver.rs index cf639fa265..b392f4107b 100644 --- a/crates/openshell-driver-podman/src/driver.rs +++ b/crates/openshell-driver-podman/src/driver.rs @@ -163,16 +163,32 @@ impl PodmanComputeDriver { const MAX_PING_RETRIES: u32 = 5; const PING_RETRY_DELAY: Duration = Duration::from_secs(2); - if !config.socket_path.exists() { + // Explicit configuration wins; otherwise probe for a responsive socket. + // Unlike Docker, Podman has no single well-known default path, so + // there is no further fallback if neither resolves. + let socket_path = config + .socket_path + .clone() + .or_else(openshell_core::config::detect_podman_socket) + .ok_or_else(|| { + PodmanApiError::InvalidInput( + "no responsive Podman API socket found; set OPENSHELL_PODMAN_SOCKET \ + or configure socket_path" + .to_string(), + ) + })?; + config.socket_path = Some(socket_path.clone()); + + if !socket_path.exists() { if cfg!(target_os = "macos") { warn!( - path = %config.socket_path.display(), + path = %socket_path.display(), "Podman socket not found; is podman machine running? \ Try `podman machine start` or set OPENSHELL_PODMAN_SOCKET to override." ); } else { warn!( - path = %config.socket_path.display(), + path = %socket_path.display(), "Podman socket not found; is the Podman service running? \ Set OPENSHELL_PODMAN_SOCKET or XDG_RUNTIME_DIR to override." ); @@ -186,7 +202,7 @@ impl PodmanComputeDriver { config.validate_runtime_limits()?; config.validate_host_gateway_ip()?; - let client = PodmanClient::new(config.socket_path.clone()); + let client = PodmanClient::new(socket_path); // Verify connectivity, retrying briefly to tolerate transient socket // unavailability (e.g. podman.socket restarting after a package @@ -774,7 +790,7 @@ impl PodmanComputeDriver { gpu_inventory: CdiGpuInventory, allow_all_default_gpu: bool, ) -> Self { - let client = PodmanClient::new(config.socket_path.clone()); + let client = PodmanClient::new(config.socket_path.clone().unwrap_or_default()); let refresh_inventory = gpu_inventory.clone(); Self { client, @@ -850,6 +866,59 @@ mod tests { use std::fs; use std::path::PathBuf; + // ── socket resolution ─────────────────────────────────────────────── + + /// Restores env vars on drop, even if the test body panics. + struct EnvVarGuard(Vec<(&'static str, Option)>); + + impl EnvVarGuard { + #[allow(unsafe_code)] // std::env::set_var requires unsafe in Rust 2024 + fn set(vars: &[(&'static str, &str)]) -> Self { + let saved = vars + .iter() + .map(|(name, value)| { + let previous = std::env::var(name).ok(); + unsafe { std::env::set_var(name, value) }; + (*name, previous) + }) + .collect(); + Self(saved) + } + } + + impl Drop for EnvVarGuard { + #[allow(unsafe_code)] // std::env::set_var/remove_var require unsafe in Rust 2024 + fn drop(&mut self) { + for (name, previous) in &self.0 { + unsafe { + match previous { + Some(value) => std::env::set_var(name, value), + None => std::env::remove_var(name), + } + } + } + } + } + + #[tokio::test] + async fn new_returns_config_error_when_no_socket_is_configured_or_detected() { + let _guard = EnvVarGuard::set(&[ + ("OPENSHELL_PODMAN_SOCKET", "/nonexistent/podman.sock"), + ("XDG_RUNTIME_DIR", "/nonexistent/xdg"), + ("HOME", "/nonexistent/home"), + ]); + + let config = PodmanComputeConfig { + socket_path: None, + ..PodmanComputeConfig::default() + }; + let err = PodmanComputeDriver::new(config) + .await + .expect_err("no socket is configured or reachable"); + + assert!(err.to_string().contains("no responsive Podman API socket")); + } + fn cdi_devices_config(device_ids: &[&str]) -> prost_types::Struct { prost_types::Struct { fields: std::iter::once(( @@ -1245,7 +1314,7 @@ mod tests { fn test_driver(socket_path: PathBuf) -> PodmanComputeDriver { let config = PodmanComputeConfig { - socket_path, + socket_path: Some(socket_path), stop_timeout_secs: 10, ..PodmanComputeConfig::default() }; @@ -1453,7 +1522,7 @@ mod tests { )], ); let config = PodmanComputeConfig { - socket_path: socket_path.clone(), + socket_path: Some(socket_path.clone()), enable_bind_mounts: true, ..PodmanComputeConfig::default() }; diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index 4840ee2810..ecbfacdbff 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -179,7 +179,7 @@ mod tests { fn test_service(socket_path: PathBuf) -> ComputeDriverService { let config = PodmanComputeConfig { - socket_path, + socket_path: Some(socket_path), stop_timeout_secs: 10, ..PodmanComputeConfig::default() }; diff --git a/crates/openshell-driver-podman/src/main.rs b/crates/openshell-driver-podman/src/main.rs index c57aff4277..831d338a30 100644 --- a/crates/openshell-driver-podman/src/main.rs +++ b/crates/openshell-driver-podman/src/main.rs @@ -114,12 +114,8 @@ async fn main() -> Result<()> { ) .init(); - let socket_path = args - .podman_socket - .unwrap_or_else(PodmanComputeConfig::default_socket_path); - let driver = PodmanComputeDriver::new(PodmanComputeConfig { - socket_path, + socket_path: args.podman_socket, default_image: args.sandbox_image.unwrap_or_default(), image_pull_policy: args.sandbox_image_pull_policy, grpc_endpoint: args.grpc_endpoint.unwrap_or_default(), diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index 90f5467927..e83cd76a33 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -3270,10 +3270,9 @@ async fn connect_local_container_engine() -> Option { return Some(docker); } - let podman_socket = podman_socket_path(); - if podman_socket.exists() - && let Ok(docker) = - Docker::connect_with_unix(podman_socket.to_str()?, 120, bollard::API_DEFAULT_VERSION) + let podman_socket = openshell_core::config::detect_podman_socket()?; + if let Ok(docker) = + Docker::connect_with_unix(podman_socket.to_str()?, 120, bollard::API_DEFAULT_VERSION) && docker.ping().await.is_ok() { info!( @@ -3286,25 +3285,6 @@ async fn connect_local_container_engine() -> Option { None } -/// Podman user socket path for the current platform. -fn podman_socket_path() -> PathBuf { - #[cfg(target_os = "macos")] - { - let home = std::env::var("HOME").unwrap_or_default(); - PathBuf::from(home).join(".local/share/containers/podman/machine/podman.sock") - } - #[cfg(target_os = "linux")] - { - std::env::var("XDG_RUNTIME_DIR").map_or_else( - |_| { - let uid = nix::unistd::getuid(); - PathBuf::from(format!("/run/user/{uid}/podman/podman.sock")) - }, - |xdg| PathBuf::from(xdg).join("podman/podman.sock"), - ) - } -} - fn is_openshell_local_build_image_ref(image_ref: &str) -> bool { image_ref.starts_with("openshell/sandbox-from:") } diff --git a/crates/openshell-server/src/compute/driver_config.rs b/crates/openshell-server/src/compute/driver_config.rs index fa44cd3a38..59fed439bc 100644 --- a/crates/openshell-server/src/compute/driver_config.rs +++ b/crates/openshell-server/src/compute/driver_config.rs @@ -195,7 +195,7 @@ fn apply_vm_runtime_defaults(cfg: &mut VmComputeConfig, context: DriverStartupCo fn apply_podman_env_overrides(podman: &mut PodmanComputeConfig) { if let Ok(p) = std::env::var("OPENSHELL_PODMAN_SOCKET") { - podman.socket_path = PathBuf::from(p); + podman.socket_path = Some(PathBuf::from(p)); } if let Ok(ip) = std::env::var("OPENSHELL_PODMAN_HOST_GATEWAY_IP") { podman.host_gateway_ip = ip; From 39d82658e67a1715678a7b9f17975467879b9f48 Mon Sep 17 00:00:00 2001 From: mesutoezdil Date: Fri, 17 Jul 2026 17:32:21 +0200 Subject: [PATCH 2/3] fix(driver-podman): address review feedback on socket resolution Extract socket resolution into resolve_socket_path, taking the detector as a parameter so tests do not depend on real env vars or the host's actual Podman state. Replace the flaky env-mutating test with three deterministic cases: explicit wins, detected is used when absent, and neither source errors. Fix the socket_path doc comment to describe it from a config user's point of view, matching DockerComputeConfig's docstring. Drop a comment that only made sense next to the Docker driver code. Update the Podman README and gateway docs: they described a fixed per-OS default path that no longer exists, replace with the actual probe-then-fail behavior. --- crates/openshell-driver-podman/README.md | 2 +- crates/openshell-driver-podman/src/config.rs | 7 +- crates/openshell-driver-podman/src/driver.rs | 103 ++++++++----------- docs/reference/gateway-config.mdx | 2 + docs/reference/sandbox-compute-drivers.mdx | 2 +- 5 files changed, 51 insertions(+), 65 deletions(-) diff --git a/crates/openshell-driver-podman/README.md b/crates/openshell-driver-podman/README.md index c6a619f3d1..ab6b053f5e 100644 --- a/crates/openshell-driver-podman/README.md +++ b/crates/openshell-driver-podman/README.md @@ -331,7 +331,7 @@ Podman resources after out-of-band container removal or label drift. | Environment Variable | CLI Flag | Default | Description | |---|---|---|---| -| `OPENSHELL_PODMAN_SOCKET` | `--podman-socket` | `$XDG_RUNTIME_DIR/podman/podman.sock` on Linux, `$HOME/.local/share/containers/podman/machine/podman.sock` on macOS | Podman API Unix socket path. | +| `OPENSHELL_PODMAN_SOCKET` | `--podman-socket` | Auto-detected if unset: probes `$XDG_RUNTIME_DIR/podman/podman.sock` (Linux), the macOS Podman machine socket under `$HOME`, and this variable itself, using the first one that responds. Fails to start if none respond. | Podman API Unix socket path. | | `OPENSHELL_SANDBOX_IMAGE` | `--sandbox-image` | From gateway config | Default OCI image for sandboxes. | | `OPENSHELL_SANDBOX_IMAGE_PULL_POLICY` | `--sandbox-image-pull-policy` | `missing` | Pull policy: `always`, `missing`, `never`, or `newer`. | | `OPENSHELL_GRPC_ENDPOINT` | `--grpc-endpoint` | Auto-detected via `host.containers.internal` | Gateway gRPC endpoint for sandbox callbacks. | diff --git a/crates/openshell-driver-podman/src/config.rs b/crates/openshell-driver-podman/src/config.rs index 1c5efa0a89..e9bd8619dc 100644 --- a/crates/openshell-driver-podman/src/config.rs +++ b/crates/openshell-driver-podman/src/config.rs @@ -69,11 +69,8 @@ impl FromStr for ImagePullPolicy { #[derive(Clone, serde::Serialize, serde::Deserialize)] #[serde(default, deny_unknown_fields)] pub struct PodmanComputeConfig { - /// Path to the Podman API Unix socket. - /// - /// `None` means auto-detect: the driver probes - /// [`openshell_core::config::detect_podman_socket`] for the first - /// responsive candidate when it starts. + /// Podman API Unix socket. When unset, use the socket selected by + /// gateway auto-detection. pub socket_path: Option, /// Default OCI image for sandboxes. pub default_image: String, diff --git a/crates/openshell-driver-podman/src/driver.rs b/crates/openshell-driver-podman/src/driver.rs index bc6815b1c0..6aab1ecd62 100644 --- a/crates/openshell-driver-podman/src/driver.rs +++ b/crates/openshell-driver-podman/src/driver.rs @@ -19,7 +19,7 @@ use openshell_core::gpu::{ use openshell_core::proto::compute::v1::{ DriverSandbox, GetCapabilitiesResponse, GpuResourceRequirements, }; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; use tracing::{info, warn}; @@ -157,26 +157,36 @@ fn podman_gpu_selection_error(err: CdiGpuSelectionError) -> ComputeDriverError { ComputeDriverError::Precondition(err.to_string()) } +/// Resolve the socket to connect to: explicit configuration wins, otherwise +/// fall back to `detect`. Returns an error if neither resolves. +/// +/// Takes `detect` as a parameter (rather than calling +/// [`openshell_core::config::detect_podman_socket`] directly) so tests can +/// exercise the precedence deterministically, without touching real +/// environment variables or the filesystem. +fn resolve_socket_path( + configured: Option, + detect: impl FnOnce() -> Option, +) -> Result { + configured.or_else(detect).ok_or_else(|| { + PodmanApiError::InvalidInput( + "no responsive Podman API socket found; set OPENSHELL_PODMAN_SOCKET \ + or configure socket_path" + .to_string(), + ) + }) +} + impl PodmanComputeDriver { /// Create a new driver, verifying the Podman socket is reachable. pub async fn new(mut config: PodmanComputeConfig) -> Result { const MAX_PING_RETRIES: u32 = 5; const PING_RETRY_DELAY: Duration = Duration::from_secs(2); - // Explicit configuration wins; otherwise probe for a responsive socket. - // Unlike Docker, Podman has no single well-known default path, so - // there is no further fallback if neither resolves. - let socket_path = config - .socket_path - .clone() - .or_else(openshell_core::config::detect_podman_socket) - .ok_or_else(|| { - PodmanApiError::InvalidInput( - "no responsive Podman API socket found; set OPENSHELL_PODMAN_SOCKET \ - or configure socket_path" - .to_string(), - ) - })?; + let socket_path = resolve_socket_path( + config.socket_path.clone(), + openshell_core::config::detect_podman_socket, + )?; config.socket_path = Some(socket_path.clone()); if !socket_path.exists() { @@ -867,54 +877,31 @@ mod tests { use std::path::PathBuf; // ── socket resolution ─────────────────────────────────────────────── + // + // These test resolve_socket_path directly with an injected detector, so + // they are deterministic regardless of the host's real environment + // variables or whether a Podman socket happens to be running. - /// Restores env vars on drop, even if the test body panics. - struct EnvVarGuard(Vec<(&'static str, Option)>); - - impl EnvVarGuard { - #[allow(unsafe_code)] // std::env::set_var requires unsafe in Rust 2024 - fn set(vars: &[(&'static str, &str)]) -> Self { - let saved = vars - .iter() - .map(|(name, value)| { - let previous = std::env::var(name).ok(); - unsafe { std::env::set_var(name, value) }; - (*name, previous) - }) - .collect(); - Self(saved) - } - } + #[test] + fn resolve_socket_path_prefers_explicit_configuration() { + let path = resolve_socket_path(Some(PathBuf::from("/explicit.sock")), || { + Some(PathBuf::from("/detected.sock")) + }) + .unwrap(); - impl Drop for EnvVarGuard { - #[allow(unsafe_code)] // std::env::set_var/remove_var require unsafe in Rust 2024 - fn drop(&mut self) { - for (name, previous) in &self.0 { - unsafe { - match previous { - Some(value) => std::env::set_var(name, value), - None => std::env::remove_var(name), - } - } - } - } + assert_eq!(path, PathBuf::from("/explicit.sock")); } - #[tokio::test] - async fn new_returns_config_error_when_no_socket_is_configured_or_detected() { - let _guard = EnvVarGuard::set(&[ - ("OPENSHELL_PODMAN_SOCKET", "/nonexistent/podman.sock"), - ("XDG_RUNTIME_DIR", "/nonexistent/xdg"), - ("HOME", "/nonexistent/home"), - ]); + #[test] + fn resolve_socket_path_uses_detected_socket_when_unconfigured() { + let path = resolve_socket_path(None, || Some(PathBuf::from("/detected.sock"))).unwrap(); - let config = PodmanComputeConfig { - socket_path: None, - ..PodmanComputeConfig::default() - }; - let err = PodmanComputeDriver::new(config) - .await - .expect_err("no socket is configured or reachable"); + assert_eq!(path, PathBuf::from("/detected.sock")); + } + + #[test] + fn resolve_socket_path_errors_when_neither_source_resolves() { + let err = resolve_socket_path(None, || None).unwrap_err(); assert!(err.to_string().contains("no responsive Podman API socket")); } diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index 3dde7643d5..56f5d15348 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -362,6 +362,8 @@ compute_drivers = ["podman"] [openshell.drivers.podman] # Rootless socket path. For root Podman use /run/podman/podman.sock. +# Omit to auto-detect: the driver probes for a responsive Podman socket and +# fails to start if none respond. socket_path = "/run/user/1000/podman/podman.sock" default_image = "ghcr.io/nvidia/openshell/sandbox:latest" image_pull_policy = "missing" # always | missing | never | newer diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index a0167ae72a..280849b5f6 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -176,7 +176,7 @@ checks do not make host bind mounts safe. [Podman](https://podman.io/)-backed sandboxes run as rootless containers on the gateway host. Use Podman for Linux workstation workflows that avoid a rootful Docker daemon. -The gateway talks to the Podman API socket. The Podman driver requires Podman 5.x, cgroups v2, rootless networking, and an active Podman user socket. +The gateway talks to the Podman API socket. The Podman driver requires Podman 5.x, cgroups v2, rootless networking, and an active Podman user socket. When `socket_path` is not set, the driver probes for a responsive Podman socket and fails to start if none respond. For maintainer-level implementation details, refer to the [Podman driver README](https://github.com/NVIDIA/OpenShell/blob/main/crates/openshell-driver-podman/README.md) and [Podman networking notes](https://github.com/NVIDIA/OpenShell/blob/main/crates/openshell-driver-podman/NETWORKING.md). From 7e1ef869f236114f9237d03868f908fbc210fbd7 Mon Sep 17 00:00:00 2001 From: mesutoezdil Date: Fri, 17 Jul 2026 18:08:19 +0200 Subject: [PATCH 3/3] docs(driver-podman): simplify socket default description Previous wording was self-contradictory (says auto-detect on unset, then lists the same var as a probed candidate) and omitted the Linux /run/user/uid/podman/podman.sock candidate. --- crates/openshell-driver-podman/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/openshell-driver-podman/README.md b/crates/openshell-driver-podman/README.md index ab6b053f5e..6a58cdcd23 100644 --- a/crates/openshell-driver-podman/README.md +++ b/crates/openshell-driver-podman/README.md @@ -331,7 +331,7 @@ Podman resources after out-of-band container removal or label drift. | Environment Variable | CLI Flag | Default | Description | |---|---|---|---| -| `OPENSHELL_PODMAN_SOCKET` | `--podman-socket` | Auto-detected if unset: probes `$XDG_RUNTIME_DIR/podman/podman.sock` (Linux), the macOS Podman machine socket under `$HOME`, and this variable itself, using the first one that responds. Fails to start if none respond. | Podman API Unix socket path. | +| `OPENSHELL_PODMAN_SOCKET` | `--podman-socket` | Probes known local Podman API sockets and uses the first responsive socket. Fails to start if none respond. | Podman API Unix socket path. | | `OPENSHELL_SANDBOX_IMAGE` | `--sandbox-image` | From gateway config | Default OCI image for sandboxes. | | `OPENSHELL_SANDBOX_IMAGE_PULL_POLICY` | `--sandbox-image-pull-policy` | `missing` | Pull policy: `always`, `missing`, `never`, or `newer`. | | `OPENSHELL_GRPC_ENDPOINT` | `--grpc-endpoint` | Auto-detected via `host.containers.internal` | Gateway gRPC endpoint for sandbox callbacks. |