diff --git a/README.md b/README.md index 8c65288c3..697421a2f 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ OpenShell collects anonymous telemetry to help improve the project for developer Disable telemetry at runtime by setting `OPENSHELL_TELEMETRY_ENABLED=false` on the gateway deployment. OpenShell propagates this deployment setting into sandbox supervisor environments so sandbox-side telemetry collection is disabled as well. -You can also compile telemetry out entirely. Telemetry support is a default-on `telemetry` Cargo feature; building `openshell-server` with `--no-default-features` and selecting only the driver features you need produces a gateway that contains no telemetry endpoint, no telemetry HTTP client, and no emission code. The gateway requires at least one compute driver to link; for example, `cargo build --release -p openshell-server --bin openshell-gateway --no-default-features --features driver-kubernetes,driver-docker,driver-podman,driver-vm` builds a telemetry-free gateway with every driver. Build telemetry-free `openshell-sandbox` and `openshell-driver-vm` binaries with a plain `--no-default-features`. With telemetry compiled out, the gateway emits nothing and reports telemetry disabled to the sandboxes it launches. +You can also compile telemetry out entirely. Telemetry support is a default-on `telemetry` Cargo feature; building `openshell-server` with `--no-default-features` produces a gateway that contains no telemetry endpoint, no telemetry HTTP client, and no emission code. Enable the built-in driver features you need, or enable none for an extension-only gateway; for example, `cargo build --release -p openshell-server --bin openshell-gateway --no-default-features --features driver-kubernetes,driver-docker,driver-podman,driver-vm` builds a telemetry-free gateway with every built-in driver. Build telemetry-free `openshell-sandbox` and `openshell-driver-vm` binaries with a plain `--no-default-features`. With telemetry compiled out, the gateway emits nothing and reports telemetry disabled to the sandboxes it launches. Telemetry events are limited to anonymous operational categories and counts, such as sandbox lifecycle outcomes, provider profile buckets, policy decision counts, and aggregate network activity denial categories. OpenShell telemetry does not collect sandbox names or IDs, hostnames, file paths, binary paths, prompts, credentials, provider names, model names, or user content. @@ -269,7 +269,7 @@ cargo build --release -p openshell-server --bin openshell-gateway \ --no-default-features --features driver-kubernetes,telemetry ``` -Any non-empty driver subset is a valid build, including zero built-in drivers (an extension-only gateway that reaches an out-of-tree driver over `[openshell.drivers.].socket_path`). What the gateway needs at *runtime* is a `compute_drivers` entry it can serve: either a built-in this binary was built with, or an extension driver name with a matching socket_path entry. Naming a built-in that this gateway was not built with fails at startup with a message naming both the driver and the Cargo flag that re-enables it. Extension drivers reached via `--compute-driver-socket` work regardless of the feature set. See the [gateway configuration reference](docs/reference/gateway-config.mdx) for details. +Any built-in driver subset is a valid build, including zero built-in drivers (an extension-only gateway that reaches an out-of-tree driver over `[openshell.drivers.].socket_path`). What the gateway needs at *runtime* is a `compute_drivers` entry it can serve: either a built-in this binary was built with, or an extension driver name with a matching socket path entry. Naming a built-in that this gateway was not built with fails at startup with a message naming the unsupported driver. Extension drivers reached via `--compute-driver-socket` work regardless of the feature set. See the [gateway configuration reference](docs/reference/gateway-config.mdx) for details. ## Notice and Disclaimer diff --git a/architecture/compute-runtimes.md b/architecture/compute-runtimes.md index d4a39adc2..077992df6 100644 --- a/architecture/compute-runtimes.md +++ b/architecture/compute-runtimes.md @@ -208,10 +208,10 @@ carries no in-tree driver code and speaks `compute_driver.proto` only to out-of-tree drivers named in `compute_drivers` with a matching `[openshell.drivers.].socket_path`. A driver named in `gateway.toml` that this binary was not built with fails at startup with -a message naming both the driver and the Cargo flag that re-enables it; -auto-detection skips drivers that were compiled out and falls through to -a "no suitable driver" error rather than silently picking a -different backend. Extension drivers reached via +a message naming the unsupported driver. Auto-detection skips detected +drivers that were compiled out and falls through to the next detected +compiled-in backend; if none remain, startup fails with the standard "no +suitable driver" error. Extension drivers reached via `--compute-driver-socket` are always available regardless of driver features — they run out-of-process and the gateway only needs to speak `compute_driver.proto`. diff --git a/crates/openshell-core/src/config.rs b/crates/openshell-core/src/config.rs index 0532fcb23..f3ba89974 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -149,25 +149,32 @@ impl FromStr for ComputeDriverKind { /// Priority order: Kubernetes → Podman → Docker. /// VM is never auto-detected (requires explicit `--drivers vm`). /// -/// Returns the first driver where the environment check passes. -/// Returns `None` if no compatible driver is found. -pub fn detect_driver() -> Option { +/// Returns every driver where the environment check passes, in priority order. +pub fn detect_drivers() -> Vec { + let mut drivers = Vec::new(); + // Kubernetes: check for KUBERNETES_SERVICE_HOST env var (set inside pods) if std::env::var_os("KUBERNETES_SERVICE_HOST").is_some() { - return Some(ComputeDriverKind::Kubernetes); + drivers.push(ComputeDriverKind::Kubernetes); } // Podman: check for a reachable local API socket. if is_podman_available() { - return Some(ComputeDriverKind::Podman); + drivers.push(ComputeDriverKind::Podman); } // Docker: check for a reachable local API socket. if is_docker_available() { - return Some(ComputeDriverKind::Docker); + drivers.push(ComputeDriverKind::Docker); } - None + drivers +} + +/// Returns the first driver where the environment check passes. +/// Returns `None` if no compatible driver is found. +pub fn detect_driver() -> Option { + detect_drivers().into_iter().next() } fn is_podman_available() -> bool { @@ -982,9 +989,9 @@ mod tests { ComputeDriverKind, Config, DEFAULT_SERVICE_ROUTING_DOMAIN, GatewayInterceptorBindingPolicy, GatewayInterceptorConfig, GatewayInterceptorFailurePolicy, GatewayJwtConfig, GatewayProviderProfileSourceConfig, detect_docker_socket_from_candidates, detect_driver, - 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, + detect_drivers, 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 _}; @@ -1401,6 +1408,8 @@ mod tests { let result = detect_driver(); assert_eq!(result, Some(ComputeDriverKind::Kubernetes)); + let detected = detect_drivers(); + assert_eq!(detected.first(), Some(&ComputeDriverKind::Kubernetes)); // Restore the original env var unsafe { diff --git a/crates/openshell-server/src/cli.rs b/crates/openshell-server/src/cli.rs index e99c708e1..baea9d162 100644 --- a/crates/openshell-server/src/cli.rs +++ b/crates/openshell-server/src/cli.rs @@ -757,7 +757,7 @@ fn normalize_compute_driver_socket_args(args: &mut RunArgs, matches: &ArgMatches fn effective_single_driver(args: &RunArgs) -> Option { match args.drivers.as_slice() { - [] => openshell_core::config::detect_driver(), + [] => crate::first_compiled_driver(openshell_core::config::detect_drivers()), [driver] => driver.parse().ok(), _ => None, } diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 2048c3cfa..2304abc08 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -983,7 +983,7 @@ fn resolve_configured_compute_driver( /// /// Third-party drivers reached via `--compute-driver-socket` are handled /// through the remote path and are always available regardless of features. -const fn driver_compiled_in(kind: ComputeDriverKind) -> bool { +pub(crate) const fn driver_compiled_in(kind: ComputeDriverKind) -> bool { match kind { ComputeDriverKind::Kubernetes => cfg!(feature = "driver-kubernetes"), ComputeDriverKind::Docker => cfg!(feature = "driver-docker"), @@ -992,15 +992,19 @@ const fn driver_compiled_in(kind: ComputeDriverKind) -> bool { } } +pub(crate) fn first_compiled_driver( + detected: impl IntoIterator, +) -> Option { + detected.into_iter().find(|kind| driver_compiled_in(*kind)) +} + /// Auto-detect a driver, ignoring any that were compiled out. /// -/// Wraps [`openshell_core::config::detect_driver`]; if the environment's -/// preferred driver is not compiled in, the caller falls back to the -/// standard "no driver configured" error rather than silently starting a -/// different backend. +/// Walks [`openshell_core::config::detect_drivers`] in priority order so +/// a compiled-out driver detected first does not hide a later compiled-in +/// backend from automatic startup. fn filtered_detect_driver() -> Option { - let kind = openshell_core::config::detect_driver()?; - driver_compiled_in(kind).then_some(kind) + first_compiled_driver(openshell_core::config::detect_drivers()) } fn builtin_compute_driver(name: &str) -> Option { @@ -1090,7 +1094,8 @@ mod tests { use super::{ ConfiguredComputeDriver, ConnectionProtocol, MultiplexService, ServerState, TlsAcceptor, allow_plaintext_service_http, classify_initial_bytes, configured_compute_driver, - gateway_listener_addresses, is_benign_tls_handshake_failure, serve_gateway_listener, + first_compiled_driver, gateway_listener_addresses, is_benign_tls_handshake_failure, + serve_gateway_listener, }; use openshell_core::{ ComputeDriverKind, Config, @@ -1661,4 +1666,18 @@ mod tests { } } } + + #[test] + fn first_compiled_driver_skips_detected_drivers_not_in_this_build() { + let detected = [ + ComputeDriverKind::Kubernetes, + ComputeDriverKind::Podman, + ComputeDriverKind::Docker, + ]; + let expected = detected + .into_iter() + .find(|kind| super::driver_compiled_in(*kind)); + + assert_eq!(first_compiled_driver(detected), expected); + } } diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index 1a13a5c45..3855ebfbc 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -488,6 +488,6 @@ cargo build --release -p openshell-server --bin openshell-gateway --no-default-f This build carries no built-in driver code. `compute_drivers` must name an extension driver with a matching `[openshell.drivers.].socket_path`. Configuring a driver in `gateway.toml` that this gateway was not built with fails at startup with a -message naming both the driver and the Cargo flag that re-enables it. +message naming the unsupported driver. Extension drivers reached via `--compute-driver-socket` do not depend on any driver feature and work regardless.