Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 85 additions & 5 deletions crates/openshell-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use std::net::SocketAddr;
use std::path::PathBuf;
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;

Expand Down Expand Up @@ -108,8 +110,8 @@ pub fn detect_driver() -> Option<ComputeDriverKind> {
return Some(ComputeDriverKind::Podman);
}

// Docker: check if docker binary is available
if is_binary_available("docker") {
// Docker: check if the CLI is available or a local Docker socket exists.
if is_docker_available() {
return Some(ComputeDriverKind::Docker);
}

Expand All @@ -124,6 +126,55 @@ fn is_binary_available(name: &str) -> bool {
.is_ok_and(|output| output.status.success())
}

fn is_docker_available() -> bool {
is_binary_available("docker") || docker_socket_available()
}

fn docker_socket_available() -> bool {
docker_socket_candidates()
.iter()
.any(|path| is_unix_socket(path))
}

fn docker_socket_candidates() -> Vec<PathBuf> {
let mut candidates = Vec::new();

if let Ok(host) = std::env::var("DOCKER_HOST")
&& let Some(path) = docker_host_unix_socket_path(&host)
{
candidates.push(path);
}

candidates.push(PathBuf::from("/var/run/docker.sock"));

if let Some(home) = std::env::var_os("HOME") {
candidates.push(PathBuf::from(home).join(".docker/run/docker.sock"));
}

if let Some(runtime_dir) = std::env::var_os("XDG_RUNTIME_DIR") {
candidates.push(PathBuf::from(runtime_dir).join("docker.sock"));
}

candidates
}

fn docker_host_unix_socket_path(host: &str) -> Option<PathBuf> {
let path = host.trim().strip_prefix("unix://")?;
(!path.is_empty()).then(|| PathBuf::from(path))
}

#[cfg(unix)]
fn is_unix_socket(path: &Path) -> bool {
path.metadata()
.is_ok_and(|metadata| metadata.file_type().is_socket())
}

#[cfg(not(unix))]
fn is_unix_socket(path: &Path) -> bool {
let _ = path;
false
}

/// Server configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
Expand Down Expand Up @@ -563,8 +614,13 @@ const fn default_ssh_session_ttl_secs() -> u64 {

#[cfg(test)]
mod tests {
use super::{ComputeDriverKind, Config, detect_driver};
use super::{
ComputeDriverKind, Config, detect_driver, docker_host_unix_socket_path, is_unix_socket,
};
use std::net::SocketAddr;
#[cfg(unix)]
use std::os::unix::net::UnixListener;
use std::path::PathBuf;

#[test]
fn compute_driver_kind_parses_supported_values() {
Expand Down Expand Up @@ -614,12 +670,36 @@ mod tests {
#[test]
fn detect_driver_returns_none_without_k8s_env_or_binaries() {
// When KUBERNETES_SERVICE_HOST is not set and no docker/podman binaries
// are available, detect_driver should return None.
// or Docker socket are available, detect_driver should return None.
// This test may pass or fail depending on the test environment,
// but it documents the expected behavior.
let _ = detect_driver(); // Returns Some or None based on environment
}

#[test]
fn docker_host_unix_socket_path_parses_unix_hosts() {
assert_eq!(
docker_host_unix_socket_path("unix:///var/run/docker.sock"),
Some(PathBuf::from("/var/run/docker.sock"))
);
assert_eq!(docker_host_unix_socket_path("tcp://127.0.0.1:2375"), None);
assert_eq!(docker_host_unix_socket_path("unix://"), None);
}

#[cfg(unix)]
#[test]
fn is_unix_socket_detects_socket_files() {
let temp_dir = tempfile::tempdir().expect("create temp dir");
let socket_path = temp_dir.path().join("docker.sock");
let _listener = UnixListener::bind(&socket_path).expect("bind unix socket");

assert!(is_unix_socket(&socket_path));

let regular_file = temp_dir.path().join("not-a-socket");
std::fs::write(&regular_file, b"not a socket").expect("write regular file");
assert!(!is_unix_socket(&regular_file));
}

#[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() {
Expand Down
4 changes: 2 additions & 2 deletions crates/openshell-server/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ struct Args {
/// `kubernetes,podman`. The configuration format is future-proofed for
/// multiple drivers, but the gateway currently requires exactly one.
/// When unset, the gateway auto-detects the driver based on the runtime
/// environment (Kubernetes → Podman → Docker). VM is never auto-detected
/// and requires explicit configuration.
/// environment (Kubernetes → Podman → Docker CLI or socket). VM is never
/// auto-detected and requires explicit configuration.
#[arg(
long,
alias = "driver",
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/sandbox-compute-drivers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ openshell-gateway --drivers docker

You can also set the driver with `OPENSHELL_DRIVERS`. Supported values are `docker`, `podman`, `kubernetes`, and `vm`.

When `--drivers` and `OPENSHELL_DRIVERS` are unset, the gateway auto-detects Kubernetes, then Podman, then Docker. The VM driver is never auto-detected; configure it explicitly with `--drivers vm`.
When `--drivers` and `OPENSHELL_DRIVERS` are unset, the gateway auto-detects Kubernetes, then Podman, then Docker by CLI availability or a local Unix socket. The VM driver is never auto-detected; configure it explicitly with `--drivers vm`.

Common gateway options:

Expand Down
24 changes: 20 additions & 4 deletions install-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,26 @@ find_deb_asset() {
_arch="$2"

awk -v arch="$_arch" '
$2 ~ "^\\*?openshell_.*_" arch "\\.deb$" {
sub("^\\*", "", $2)
print $2
exit
{
name = $2
sub("^\\*", "", name)

if (name == "openshell-dev-" arch ".deb") {
selected = name
found = 1
exit
}

if (fallback == "" && name ~ "^openshell_.*_" arch "\\.deb$") {
fallback = name
}
}
END {
if (found) {
print selected
} else if (fallback != "") {
print fallback
}
}
' "$_checksums"
}
Expand Down
Loading