refactor(packaging): rely on gateway runtime defaults (#1415)

* fix(packaging): use gateway TOML config in packages

* refactor(packaging): rely on gateway runtime defaults
This commit is contained in:
Drew Newberry
2026-05-18 14:13:19 -07:00
committed by GitHub
parent 7f16d60ef1
commit f257ed0193
35 changed files with 762 additions and 707 deletions
+4 -4
View File
@@ -675,8 +675,8 @@ fn is_loopback_gateway_endpoint(endpoint: &str) -> bool {
/// would serve this endpoint.
///
/// Loopback endpoints (`localhost`, `127.0.0.1`, `::1`) resolve to the
/// `"openshell"` gateway name, matching the convention used by
/// `init-pki.sh` and the TLS cert resolver in `tls.rs`.
/// `"openshell"` gateway name, matching the convention used by local
/// `openshell-gateway generate-certs` and the TLS cert resolver in `tls.rs`.
fn mtls_certs_exist_for_endpoint(name: &str, endpoint: &str) -> bool {
let cert_name = if is_loopback_gateway_endpoint(endpoint) {
"openshell"
@@ -901,7 +901,7 @@ pub async fn gateway_add(
// Derive a gateway name from the hostname when none is provided.
// Loopback endpoints use the canonical "openshell" name, matching the
// convention in init-pki.sh and default_tls_dir.
// convention in local cert generation and default_tls_dir.
let derived_name;
let name = if let Some(n) = name {
n
@@ -7240,7 +7240,7 @@ mod tests {
});
// Loopback endpoints derive the canonical "openshell" gateway
// name, matching init-pki.sh and default_tls_dir conventions.
// name, matching local cert generation and default_tls_dir conventions.
let metadata = load_gateway_metadata("openshell").expect("load stored gateway");
assert_eq!(metadata.auth_mode.as_deref(), Some("plaintext"));
assert!(!metadata.is_remote);
+4 -4
View File
@@ -22,7 +22,7 @@ use std::str::FromStr;
pub const DEFAULT_SSH_PORT: u16 = 2222;
/// Default gateway server port.
pub const DEFAULT_SERVER_PORT: u16 = 8080;
pub const DEFAULT_SERVER_PORT: u16 = 17670;
/// Default container stop timeout in seconds (SIGTERM → SIGKILL).
pub const DEFAULT_STOP_TIMEOUT_SECS: u32 = 10;
@@ -34,7 +34,7 @@ pub const DEFAULT_DOCKER_NETWORK_NAME: &str = "openshell-docker";
pub const DEFAULT_SERVICE_ROUTING_DOMAIN: &str = "openshell.localhost";
/// Default OCI image for the openshell-sandbox supervisor binary.
pub const DEFAULT_SUPERVISOR_IMAGE: &str = "openshell/supervisor:latest";
pub const DEFAULT_SUPERVISOR_IMAGE: &str = "ghcr.io/nvidia/openshell/supervisor:latest";
/// CDI device identifier for requesting all NVIDIA GPUs.
pub const CDI_GPU_DEVICE_ALL: &str = "nvidia.com/gpu=all";
@@ -451,7 +451,7 @@ impl Default for ServiceRoutingConfig {
}
fn default_bind_address() -> SocketAddr {
"127.0.0.1:8080".parse().expect("valid default address")
"127.0.0.1:17670".parse().expect("valid default address")
}
fn default_service_routing_domains() -> Vec<String> {
@@ -557,7 +557,7 @@ mod tests {
#[test]
fn config_defaults_to_loopback_bind_address() {
let expected: SocketAddr = "127.0.0.1:8080".parse().expect("valid address");
let expected: SocketAddr = "127.0.0.1:17670".parse().expect("valid address");
assert_eq!(Config::new(None).bind_address, expected);
}
+27
View File
@@ -29,6 +29,24 @@ pub fn openshell_config_dir() -> Result<PathBuf> {
Ok(xdg_config_dir()?.join("openshell"))
}
/// Resolve the XDG state base directory.
///
/// Returns `$XDG_STATE_HOME` if set, otherwise `$HOME/.local/state`.
pub fn xdg_state_dir() -> Result<PathBuf> {
if let Ok(path) = std::env::var("XDG_STATE_HOME") {
return Ok(PathBuf::from(path));
}
let home = std::env::var("HOME")
.into_diagnostic()
.wrap_err("HOME is not set")?;
Ok(PathBuf::from(home).join(".local").join("state"))
}
/// The top-level `OpenShell` state directory: `$XDG_STATE_HOME/openshell/`.
pub fn openshell_state_dir() -> Result<PathBuf> {
Ok(xdg_state_dir()?.join("openshell"))
}
/// Resolve the XDG data base directory.
///
/// Returns `$XDG_DATA_HOME` if set, otherwise `$HOME/.local/share`.
@@ -130,6 +148,15 @@ mod tests {
);
}
#[test]
fn openshell_state_dir_appends_openshell() {
let dir = openshell_state_dir().unwrap();
assert!(
dir.ends_with("openshell"),
"expected path ending with 'openshell', got: {dir:?}"
);
}
#[cfg(unix)]
#[test]
fn create_dir_restricted_sets_0o700() {
+18 -8
View File
@@ -252,12 +252,6 @@ impl DockerComputeDriver {
docker_config: &DockerComputeConfig,
supervisor_readiness: Arc<dyn SupervisorReadiness>,
) -> CoreResult<Self> {
if docker_config.grpc_endpoint.trim().is_empty() {
return Err(Error::config(
"grpc_endpoint is required when using the docker compute driver",
));
}
let docker = Docker::connect_with_local_defaults()
.map_err(|err| Error::execution(format!("failed to create Docker client: {err}")))?;
let version = docker.version().await.map_err(|err| {
@@ -281,14 +275,24 @@ impl DockerComputeDriver {
let host_gateway_ip = parse_optional_host_gateway_ip(&docker_config.host_gateway_ip)?;
let gateway_route =
docker_gateway_route(&info, bridge_gateway_ip, gateway_port, host_gateway_ip);
let mut docker_config = docker_config.clone();
if docker_config.grpc_endpoint.trim().is_empty() {
let scheme = if docker_guest_tls_configured(&docker_config) {
"https"
} else {
"http"
};
docker_config.grpc_endpoint =
format!("{scheme}://{HOST_OPENSHELL_INTERNAL}:{gateway_port}");
}
let grpc_endpoint = docker_container_openshell_endpoint(
&docker_config.grpc_endpoint,
HOST_OPENSHELL_INTERNAL,
gateway_port,
);
let daemon_arch = normalize_docker_arch(version.arch.as_deref().unwrap_or_default());
let supervisor_bin = resolve_supervisor_bin(&docker, docker_config, &daemon_arch).await?;
let guest_tls = docker_guest_tls_paths(docker_config)?;
let supervisor_bin = resolve_supervisor_bin(&docker, &docker_config, &daemon_arch).await?;
let guest_tls = docker_guest_tls_paths(&docker_config)?;
let driver = Self {
docker: Arc::new(docker),
@@ -2009,6 +2013,12 @@ pub(crate) fn validate_linux_elf_binary(path: &Path) -> CoreResult<()> {
Ok(())
}
fn docker_guest_tls_configured(docker_config: &DockerComputeConfig) -> bool {
docker_config.guest_tls_ca.is_some()
&& docker_config.guest_tls_cert.is_some()
&& docker_config.guest_tls_key.is_some()
}
pub(crate) fn docker_guest_tls_paths(
docker_config: &DockerComputeConfig,
) -> CoreResult<Option<DockerGuestTlsPaths>> {
+5 -5
View File
@@ -74,7 +74,7 @@ fn container_visible_endpoint_rewrites_loopback_hosts() {
HOST_OPENSHELL_INTERNAL,
DEFAULT_SERVER_PORT,
),
"https://host.openshell.internal:8080/"
"https://host.openshell.internal:17670/"
);
assert_eq!(
docker_container_openshell_endpoint(
@@ -82,7 +82,7 @@ fn container_visible_endpoint_rewrites_loopback_hosts() {
HOST_OPENSHELL_INTERNAL,
DEFAULT_SERVER_PORT,
),
"http://host.openshell.internal:8080/"
"http://host.openshell.internal:17670/"
);
assert_eq!(
docker_container_openshell_endpoint(
@@ -90,7 +90,7 @@ fn container_visible_endpoint_rewrites_loopback_hosts() {
HOST_OPENSHELL_INTERNAL,
DEFAULT_SERVER_PORT,
),
"https://host.openshell.internal:8080/"
"https://host.openshell.internal:17670/"
);
}
@@ -273,7 +273,7 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() {
assert_eq!(
route,
DockerGatewayRoute::Bridge {
bind_address: "172.18.0.1:8080".parse().unwrap(),
bind_address: "172.18.0.1:17670".parse().unwrap(),
host_alias_ip: IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)),
}
);
@@ -303,7 +303,7 @@ fn docker_gateway_route_prefers_configured_host_gateway_ip() {
assert_eq!(
route,
DockerGatewayRoute::Bridge {
bind_address: "172.20.0.4:8080".parse().unwrap(),
bind_address: "172.20.0.4:17670".parse().unwrap(),
host_alias_ip: IpAddr::V4(Ipv4Addr::new(172, 20, 0, 4)),
}
);
+6 -7
View File
@@ -357,9 +357,9 @@ Supervisor proxy in container netns
The Podman driver auto-detects the callback endpoint scheme based on whether
TLS client certificates are configured. When the RPM's auto-generated PKI is in
place, the endpoint is `https://host.containers.internal:8080` and the
place, the endpoint is `https://host.containers.internal:17670` and the
supervisor connects with mTLS. Without TLS configuration, it falls back to
`http://host.containers.internal:8080`.
`http://host.containers.internal:<gateway-port>`.
```text
Supervisor in container netns
@@ -382,10 +382,9 @@ Gateway
9. Same gRPC channel reused for RelayStream calls
```
The gateway binds to `0.0.0.0` by default in the RPM packaging. mTLS prevents
unauthenticated access even though the gateway is reachable from the network.
Client certificates are auto-generated by `init-pki.sh` on first start and
bind-mounted into sandbox containers by the Podman driver.
The gateway binds to `127.0.0.1:17670` by default in the RPM packaging. Client
certificates are auto-generated by `openshell-gateway generate-certs` on first
start and bind-mounted into sandbox containers by the Podman driver.
## Differences from the Kubernetes Driver
@@ -412,7 +411,7 @@ published ports, or the supervisor relay.
| Port | Component | Purpose |
|---|---|---|
| `8080` | Gateway | gRPC and HTTP multiplexed default server port. |
| `17670` | Gateway | Default local gRPC and HTTP multiplexed server port. |
| `2222` | Sandbox | Container port mapping default for the SSH compatibility port. |
| `3128` | Sandbox proxy | HTTP CONNECT proxy inside the sandbox network model. |
| `0` | Host | Ephemeral host port requested for the container SSH compatibility port. |
+7 -7
View File
@@ -120,10 +120,10 @@ connection back to the gateway. On SELinux systems, the bind mounts include
Podman's shared relabel option so the container process can read the files.
The RPM packaging auto-generates a self-signed PKI on first start via
`init-pki.sh`. Client certs are placed in the CLI auto-discovery directory
(`~/.config/openshell/gateways/openshell/mtls/`) so the CLI connects with mTLS
without manual configuration. See `deploy/rpm/CONFIGURATION.md` for the full
RPM configuration reference.
`openshell-gateway generate-certs`. Client certs are placed in the CLI
auto-discovery directory (`~/.config/openshell/gateways/openshell/mtls/`) so
the CLI connects with mTLS without manual configuration. See
`deploy/rpm/CONFIGURATION.md` for the full RPM configuration reference.
## Network Model
@@ -134,7 +134,7 @@ the supervisor for sandbox process isolation.
```mermaid
graph TB
subgraph Host
GW["Gateway Server<br/>127.0.0.1:8080"]
GW["Gateway Server<br/>127.0.0.1:17670"]
PS["Podman Socket"]
end
@@ -289,11 +289,11 @@ Podman resources after out-of-band container removal or label drift.
| `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. |
| `OPENSHELL_GATEWAY_PORT` | `--gateway-port` | `8080` | Gateway port used for endpoint auto-detection by the standalone binary. |
| `OPENSHELL_GATEWAY_PORT` | `--gateway-port` | `17670` | Gateway port used for endpoint auto-detection by the standalone binary. |
| `OPENSHELL_NETWORK_NAME` | `--network-name` | `openshell` | Podman bridge network name. |
| `OPENSHELL_SANDBOX_SSH_SOCKET_PATH` | `--sandbox-ssh-socket-path` | `/run/openshell/ssh.sock` | Supervisor Unix socket path in `PodmanComputeConfig`. |
| `OPENSHELL_STOP_TIMEOUT` | `--stop-timeout` | `10` | Container stop timeout in seconds. |
| `OPENSHELL_SUPERVISOR_IMAGE` | `--supervisor-image` | `openshell/supervisor:latest` through the gateway, required standalone | OCI image containing the supervisor binary. |
| `OPENSHELL_SUPERVISOR_IMAGE` | `--supervisor-image` | `ghcr.io/nvidia/openshell/supervisor:latest` through the gateway, required standalone | OCI image containing the supervisor binary. |
| `OPENSHELL_PODMAN_TLS_CA` | `--podman-tls-ca` | unset | Host path to the CA certificate mounted for sandbox mTLS. |
| `OPENSHELL_PODMAN_TLS_CERT` | `--podman-tls-cert` | unset | Host path to the client certificate mounted for sandbox mTLS. |
| `OPENSHELL_PODMAN_TLS_KEY` | `--podman-tls-key` | unset | Host path to the client private key mounted for sandbox mTLS. |
@@ -1036,7 +1036,7 @@ mod tests {
let vol = &image_volumes[0];
assert_eq!(
vol["source"].as_str(),
Some("openshell/supervisor:latest"),
Some("ghcr.io/nvidia/openshell/supervisor:latest"),
"image volume source should be the supervisor image"
);
assert_eq!(
+3 -3
View File
@@ -8,9 +8,9 @@
//! - **Kubernetes mode** (default): create two `kubernetes.io/tls` Secrets
//! in the supplied namespace. Used by the Helm pre-install hook. Requires
//! `--namespace`, `--server-secret-name`, `--client-secret-name`.
//! - **Local mode** (`--output-dir <DIR>`): write PEMs to a filesystem layout
//! used by the RPM systemd unit's `ExecStartPre`. Also copies client
//! materials to
//! - **Local mode** (`--output-dir <DIR>`): write PEMs to the local package
//! filesystem layout. Used by systemd units' `ExecStartPre`. Also copies
//! client materials to
//! `$XDG_CONFIG_HOME/openshell/gateways/openshell/mtls/` so the local CLI
//! picks them up automatically.
//!
+198 -20
View File
@@ -16,6 +16,7 @@ use tracing_subscriber::EnvFilter;
use crate::certgen;
use crate::compute::{DockerComputeConfig, VmComputeConfig};
use crate::config_file::{self, ConfigFile, GatewayFileSection};
use crate::defaults::{self, LocalTlsPaths};
use crate::{run_server, tracing_bus::TracingLogBus};
/// `OpenShell` gateway process - gRPC and HTTP server with protocol multiplexing.
@@ -87,9 +88,9 @@ struct RunArgs {
/// Database URL for persistence.
///
/// Required when running the gateway. Validated at the call site rather
/// than as a clap-level requirement so the `generate-certs` subcommand
/// (which does not need a database) can run without it.
/// When unset, the gateway stores state under the `XDG` state
/// directory. Kept as an Option at the clap layer so the `generate-certs`
/// subcommand can run without gateway runtime defaults.
#[arg(long, env = "OPENSHELL_DB_URL")]
db_url: Option<String>,
@@ -201,10 +202,11 @@ pub async fn run_cli() -> Result<()> {
}
async fn run_from_args(mut args: RunArgs, matches: ArgMatches) -> Result<()> {
// Load TOML file when --config / OPENSHELL_GATEWAY_CONFIG is set.
// File values are applied below for any argument that is still at its
// built-in default — CLI flags and OPENSHELL_* env vars always win.
let file: Option<ConfigFile> = if let Some(path) = args.config.clone() {
// Load TOML when explicitly requested, or from the default XDG location
// when that file exists. Missing default config is not an error: runtime
// defaults and OPENSHELL_* env vars are enough for package-managed starts.
let config_path = resolve_config_path(&args)?;
let file: Option<ConfigFile> = if let Some(path) = config_path {
Some(config_file::load(&path).map_err(|e| miette::miette!("{e}"))?)
} else {
None
@@ -213,6 +215,8 @@ async fn run_from_args(mut args: RunArgs, matches: ArgMatches) -> Result<()> {
merge_file_into_args(&mut args, &file.openshell.gateway, &matches);
}
let local_tls = apply_runtime_defaults(&mut args)?;
let tracing_log_bus = TracingLogBus::new();
tracing_log_bus.install_subscriber(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level)),
@@ -251,7 +255,7 @@ async fn run_from_args(mut args: RunArgs, matches: ArgMatches) -> Result<()> {
let db_url = args
.db_url
.clone()
.ok_or_else(|| miette::miette!("--db-url is required (or set OPENSHELL_DB_URL)"))?;
.expect("runtime defaults populate db_url");
let mut config = openshell_core::Config::new(tls)
.with_bind_address(bind)
@@ -332,8 +336,13 @@ async fn run_from_args(mut args: RunArgs, matches: ArgMatches) -> Result<()> {
});
}
let vm_config = build_vm_config(file.as_ref())?;
let docker_config = build_docker_config(file.as_ref())?;
let vm_config = build_vm_config(
file.as_ref(),
local_tls.as_ref(),
args.disable_tls,
args.port,
)?;
let docker_config = build_docker_config(file.as_ref(), local_tls.as_ref())?;
if args.disable_tls {
warn!("TLS disabled — listening on plaintext HTTP");
@@ -372,6 +381,40 @@ fn parse_compute_driver(value: &str) -> std::result::Result<ComputeDriverKind, S
value.parse()
}
fn resolve_config_path(args: &RunArgs) -> Result<Option<PathBuf>> {
if let Some(path) = args.config.clone() {
return Ok(Some(path));
}
let default_path = defaults::default_gateway_config_path()?;
Ok(default_path.is_file().then_some(default_path))
}
fn apply_runtime_defaults(args: &mut RunArgs) -> Result<Option<LocalTlsPaths>> {
let local_tls = if args.disable_tls {
None
} else {
defaults::complete_local_tls_paths()?
};
if args.db_url.is_none() {
args.db_url = Some(defaults::default_database_url()?);
}
if !args.disable_tls
&& args.tls_cert.is_none()
&& args.tls_key.is_none()
&& args.tls_client_ca.is_none()
&& let Some(paths) = &local_tls
{
args.tls_cert = Some(paths.server_cert.clone());
args.tls_key = Some(paths.server_key.clone());
args.tls_client_ca = Some(paths.ca.clone());
}
Ok(local_tls)
}
/// Returns `true` when an argument's value came from clap's built-in default
/// (or was never supplied at all). When the predicate is `true`, the loader
/// is free to replace the value with one read from the TOML config file.
@@ -500,7 +543,12 @@ fn merge_file_into_args(args: &mut RunArgs, file: &GatewayFileSection, matches:
/// Build [`VmComputeConfig`] from the `[openshell.drivers.vm]` table
/// inherited from `[openshell.gateway]`.
fn build_vm_config(file: Option<&ConfigFile>) -> Result<VmComputeConfig> {
fn build_vm_config(
file: Option<&ConfigFile>,
local_tls: Option<&LocalTlsPaths>,
disable_tls: bool,
gateway_port: u16,
) -> Result<VmComputeConfig> {
let mut cfg = if let Some(file) = file {
let merged = config_file::driver_table(
ComputeDriverKind::Vm,
@@ -517,23 +565,61 @@ fn build_vm_config(file: Option<&ConfigFile>) -> Result<VmComputeConfig> {
if cfg.state_dir.as_os_str().is_empty() {
cfg.state_dir = VmComputeConfig::default_state_dir();
}
if cfg.grpc_endpoint.trim().is_empty() && (disable_tls || local_tls.is_some()) {
let scheme = if disable_tls { "http" } else { "https" };
cfg.grpc_endpoint = format!("{scheme}://127.0.0.1:{gateway_port}");
}
apply_guest_tls_defaults(
&mut cfg.guest_tls_ca,
&mut cfg.guest_tls_cert,
&mut cfg.guest_tls_key,
local_tls,
);
Ok(cfg)
}
/// Build [`DockerComputeConfig`] using the same inheritance pattern as
/// [`build_vm_config`].
fn build_docker_config(file: Option<&ConfigFile>) -> Result<DockerComputeConfig> {
if let Some(file) = file {
fn build_docker_config(
file: Option<&ConfigFile>,
local_tls: Option<&LocalTlsPaths>,
) -> Result<DockerComputeConfig> {
let mut cfg = if let Some(file) = file {
let merged = config_file::driver_table(
ComputeDriverKind::Docker,
&file.openshell.gateway,
file.openshell.drivers.get("docker"),
);
return merged
merged
.try_into::<DockerComputeConfig>()
.map_err(|e| miette::miette!("invalid [openshell.drivers.docker] table: {e}"));
.map_err(|e| miette::miette!("invalid [openshell.drivers.docker] table: {e}"))?
} else {
DockerComputeConfig::default()
};
apply_guest_tls_defaults(
&mut cfg.guest_tls_ca,
&mut cfg.guest_tls_cert,
&mut cfg.guest_tls_key,
local_tls,
);
Ok(cfg)
}
fn apply_guest_tls_defaults(
ca: &mut Option<PathBuf>,
cert: &mut Option<PathBuf>,
key: &mut Option<PathBuf>,
local_tls: Option<&LocalTlsPaths>,
) {
if ca.is_none()
&& cert.is_none()
&& key.is_none()
&& let Some(paths) = local_tls
{
*ca = Some(paths.ca.clone());
*cert = Some(paths.client_cert.clone());
*key = Some(paths.client_key.clone());
}
Ok(DockerComputeConfig::default())
}
#[cfg(test)]
@@ -782,11 +868,10 @@ mod tests {
}
#[test]
fn bare_invocation_with_no_db_url_errors_at_runtime_not_parse_time() {
fn bare_invocation_with_no_db_url_parses_for_runtime_defaults() {
// db_url is Option<String> at the clap level so subcommand parsing
// does not require it. The Run path validates it inside
// run_from_args. This test asserts the parse step succeeds with no
// --db-url, mirroring what the runtime check sees.
// does not require it. The Run path fills a default URL from XDG
// state when neither CLI nor env supplied one.
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
@@ -819,6 +904,99 @@ mod tests {
toml::from_str(toml).expect("valid TOML in test fixture")
}
#[test]
fn default_config_path_is_loaded_only_when_present() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let tmp = tempfile::tempdir().unwrap();
let _g1 = EnvVarGuard::remove("OPENSHELL_GATEWAY_CONFIG");
let _g2 = EnvVarGuard::set("XDG_CONFIG_HOME", tmp.path().to_str().unwrap());
let (args, _) = parse_with_args(&["openshell-gateway"]);
assert_eq!(super::resolve_config_path(&args).unwrap(), None);
let config = tmp.path().join("openshell").join("gateway.toml");
std::fs::create_dir_all(config.parent().unwrap()).unwrap();
std::fs::write(&config, "[openshell]\nversion = 1\n").unwrap();
assert_eq!(super::resolve_config_path(&args).unwrap(), Some(config));
}
#[test]
fn explicit_config_path_is_returned_even_when_missing() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _g = EnvVarGuard::remove("OPENSHELL_GATEWAY_CONFIG");
let (args, _) = parse_with_args(&["openshell-gateway", "--config", "/tmp/missing.toml"]);
assert_eq!(
super::resolve_config_path(&args).unwrap(),
Some(std::path::PathBuf::from("/tmp/missing.toml"))
);
}
#[test]
fn runtime_defaults_populate_database_url_from_xdg_state() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let tmp = tempfile::tempdir().unwrap();
let _g1 = EnvVarGuard::remove("OPENSHELL_DB_URL");
let _g2 = EnvVarGuard::set("XDG_STATE_HOME", tmp.path().to_str().unwrap());
let (mut args, _) = parse_with_args(&["openshell-gateway", "--disable-tls"]);
let local_tls = super::apply_runtime_defaults(&mut args).unwrap();
let expected = format!(
"sqlite:{}",
tmp.path().join("openshell/gateway/openshell.db").display()
);
assert!(local_tls.is_none());
assert_eq!(args.db_url.as_deref(), Some(expected.as_str()));
assert!(tmp.path().join("openshell/gateway").is_dir());
}
#[test]
fn runtime_defaults_use_complete_local_tls_bundle() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let state = tempfile::tempdir().unwrap();
let tls = tempfile::tempdir().unwrap();
let _g1 = EnvVarGuard::remove("OPENSHELL_DB_URL");
let _g2 = EnvVarGuard::remove("OPENSHELL_TLS_CERT");
let _g3 = EnvVarGuard::remove("OPENSHELL_TLS_KEY");
let _g4 = EnvVarGuard::remove("OPENSHELL_TLS_CLIENT_CA");
let _g5 = EnvVarGuard::remove("OPENSHELL_DISABLE_TLS");
let _g6 = EnvVarGuard::set("XDG_STATE_HOME", state.path().to_str().unwrap());
let _g7 = EnvVarGuard::set("OPENSHELL_LOCAL_TLS_DIR", tls.path().to_str().unwrap());
std::fs::create_dir_all(tls.path().join("server")).unwrap();
std::fs::create_dir_all(tls.path().join("client")).unwrap();
for rel in [
"ca.crt",
"server/tls.crt",
"server/tls.key",
"client/tls.crt",
"client/tls.key",
] {
std::fs::write(tls.path().join(rel), "pem").unwrap();
}
let (mut args, _) = parse_with_args(&["openshell-gateway"]);
let local_tls = super::apply_runtime_defaults(&mut args)
.unwrap()
.expect("complete bundle should be returned");
assert_eq!(args.tls_cert, Some(tls.path().join("server/tls.crt")));
assert_eq!(args.tls_key, Some(tls.path().join("server/tls.key")));
assert_eq!(args.tls_client_ca, Some(tls.path().join("ca.crt")));
assert_eq!(local_tls.client_cert, tls.path().join("client/tls.crt"));
}
#[test]
fn file_value_applies_when_cli_uses_default() {
let _lock = ENV_LOCK
+19 -2
View File
@@ -105,7 +105,10 @@ impl VmComputeConfig {
/// Default working directory for VM driver state.
#[must_use]
pub fn default_state_dir() -> PathBuf {
PathBuf::from("target/openshell-vm-driver")
openshell_core::paths::openshell_state_dir().map_or_else(
|_| PathBuf::from("target/openshell-vm-driver"),
|dir| dir.join("vm-driver"),
)
}
/// Default libkrun log level.
@@ -229,7 +232,21 @@ pub fn resolve_compute_driver_bin(vm_config: &VmComputeConfig) -> Result<PathBuf
fn resolve_driver_search_dirs(vm_config: &VmComputeConfig) -> Vec<PathBuf> {
vm_config.driver_dir.clone().map_or_else(
|| VmComputeConfig::default_driver_search_dirs(std::env::var_os("HOME").map(PathBuf::from)),
|| {
let mut dirs = Vec::new();
if let Ok(current_exe) = std::env::current_exe()
&& let Some(prefix) = current_exe.parent().and_then(Path::parent)
{
push_unique_path(&mut dirs, prefix.join("libexec"));
push_unique_path(&mut dirs, prefix.join("libexec").join("openshell"));
}
for dir in VmComputeConfig::default_driver_search_dirs(
std::env::var_os("HOME").map(PathBuf::from),
) {
push_unique_path(&mut dirs, dir);
}
dirs
},
|dir| vec![dir],
)
}
+155
View File
@@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//! Runtime defaults for local gateway installs.
use miette::Result;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalTlsPaths {
pub ca: PathBuf,
pub server_cert: PathBuf,
pub server_key: PathBuf,
pub client_cert: PathBuf,
pub client_key: PathBuf,
}
impl LocalTlsPaths {
fn resolve(dir: &Path) -> Self {
Self {
ca: dir.join("ca.crt"),
server_cert: dir.join("server").join("tls.crt"),
server_key: dir.join("server").join("tls.key"),
client_cert: dir.join("client").join("tls.crt"),
client_key: dir.join("client").join("tls.key"),
}
}
fn files(&self) -> [&Path; 5] {
[
&self.ca,
&self.server_cert,
&self.server_key,
&self.client_cert,
&self.client_key,
]
}
}
pub fn default_gateway_config_path() -> Result<PathBuf> {
Ok(openshell_core::paths::openshell_config_dir()?.join("gateway.toml"))
}
pub fn default_database_url() -> Result<String> {
let path = openshell_core::paths::openshell_state_dir()?
.join("gateway")
.join("openshell.db");
openshell_core::paths::ensure_parent_dir_restricted(&path)?;
Ok(format!("sqlite:{}", path.display()))
}
fn default_local_tls_dir() -> Result<PathBuf> {
if let Some(path) = std::env::var_os("OPENSHELL_LOCAL_TLS_DIR") {
return Ok(PathBuf::from(path));
}
Ok(openshell_core::paths::openshell_state_dir()?.join("tls"))
}
pub fn complete_local_tls_paths() -> Result<Option<LocalTlsPaths>> {
let dir = default_local_tls_dir()?;
let paths = LocalTlsPaths::resolve(&dir);
let present = paths.files().iter().filter(|path| path.is_file()).count();
match present {
0 => Ok(None),
5 => Ok(Some(paths)),
_ => Err(miette::miette!(
"partial local TLS state in {}: expected ca.crt, server/tls.crt, server/tls.key, client/tls.crt, and client/tls.key",
dir.display()
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{LazyLock, Mutex};
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
struct EnvVarGuard {
key: &'static str,
original: Option<String>,
}
impl EnvVarGuard {
#[allow(unsafe_code)]
fn set(key: &'static str, value: &Path) -> Self {
let original = std::env::var(key).ok();
// SAFETY: tests serialize environment mutation with ENV_LOCK.
unsafe { std::env::set_var(key, value) };
Self { key, original }
}
}
impl Drop for EnvVarGuard {
#[allow(unsafe_code)]
fn drop(&mut self) {
match self.original.as_deref() {
// SAFETY: tests serialize environment mutation with ENV_LOCK.
Some(value) => unsafe { std::env::set_var(self.key, value) },
// SAFETY: tests serialize environment mutation with ENV_LOCK.
None => unsafe { std::env::remove_var(self.key) },
}
}
}
#[test]
fn complete_local_tls_paths_returns_none_when_bundle_absent() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let tmp = tempfile::tempdir().unwrap();
let _guard = EnvVarGuard::set("OPENSHELL_LOCAL_TLS_DIR", tmp.path());
assert!(complete_local_tls_paths().unwrap().is_none());
}
#[test]
fn complete_local_tls_paths_rejects_partial_bundle() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let tmp = tempfile::tempdir().unwrap();
let _guard = EnvVarGuard::set("OPENSHELL_LOCAL_TLS_DIR", tmp.path());
std::fs::write(tmp.path().join("ca.crt"), "ca").unwrap();
let err = complete_local_tls_paths().unwrap_err();
assert!(err.to_string().contains("partial local TLS state"));
}
#[test]
fn complete_local_tls_paths_returns_full_bundle() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let tmp = tempfile::tempdir().unwrap();
let _guard = EnvVarGuard::set("OPENSHELL_LOCAL_TLS_DIR", tmp.path());
std::fs::create_dir_all(tmp.path().join("server")).unwrap();
std::fs::create_dir_all(tmp.path().join("client")).unwrap();
for rel in [
"ca.crt",
"server/tls.crt",
"server/tls.key",
"client/tls.crt",
"client/tls.key",
] {
std::fs::write(tmp.path().join(rel), "pem").unwrap();
}
let paths = complete_local_tls_paths().unwrap().unwrap();
assert_eq!(paths.ca, tmp.path().join("ca.crt"));
assert_eq!(paths.server_cert, tmp.path().join("server/tls.crt"));
assert_eq!(paths.client_key, tmp.path().join("client/tls.key"));
}
}
+25
View File
@@ -24,6 +24,7 @@ pub mod certgen;
pub mod cli;
mod compute;
pub mod config_file;
mod defaults;
mod grpc;
mod http;
mod inference;
@@ -622,6 +623,7 @@ async fn build_compute_runtime(
ComputeDriverKind::Podman => {
let mut podman = podman_config_from_file(file)?;
podman.gateway_port = config.bind_address.port();
apply_podman_local_tls_defaults(config, &mut podman)?;
ComputeRuntime::new_podman(
podman,
@@ -674,6 +676,29 @@ fn podman_config_from_file(
.map_err(|e| Error::config(format!("invalid [openshell.drivers.podman] table: {e}")))
}
fn apply_podman_local_tls_defaults(
config: &Config,
podman: &mut openshell_driver_podman::PodmanComputeConfig,
) -> Result<()> {
if config.tls.is_none()
|| podman.guest_tls_ca.is_some()
|| podman.guest_tls_cert.is_some()
|| podman.guest_tls_key.is_some()
{
return Ok(());
}
let Some(paths) = defaults::complete_local_tls_paths()
.map_err(|e| Error::config(format!("failed to resolve local TLS defaults: {e}")))?
else {
return Ok(());
};
podman.guest_tls_ca = Some(paths.ca);
podman.guest_tls_cert = Some(paths.client_cert);
podman.guest_tls_key = Some(paths.client_key);
Ok(())
}
fn configured_compute_driver(config: &Config) -> Result<ComputeDriverKind> {
match config.compute_drivers.as_slice() {
[] => match openshell_core::config::detect_driver() {
-56
View File
@@ -1,56 +0,0 @@
#!/bin/sh
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
set -eu
CONFIG_FILE="${1:?Usage: init-gateway-config.sh <config-file> <pki-dir> <driver-dir> <vm-state-dir>}"
PKI_DIR="${2:?Usage: init-gateway-config.sh <config-file> <pki-dir> <driver-dir> <vm-state-dir>}"
DRIVER_DIR="${3:?Usage: init-gateway-config.sh <config-file> <pki-dir> <driver-dir> <vm-state-dir>}"
VM_STATE_DIR="${4:?Usage: init-gateway-config.sh <config-file> <pki-dir> <driver-dir> <vm-state-dir>}"
if [ -f "$CONFIG_FILE" ]; then
exit 0
fi
mkdir -p "$(dirname "$CONFIG_FILE")" "$VM_STATE_DIR"
port="${OPENSHELL_SERVER_PORT:-17670}"
scheme="https"
if [ "${OPENSHELL_DISABLE_TLS:-false}" = "true" ]; then
scheme="http"
fi
tmp="${CONFIG_FILE}.tmp"
{
cat <<EOF
[openshell]
version = 1
[openshell.gateway]
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
EOF
if [ "$scheme" = "https" ]; then
cat <<EOF
guest_tls_ca = "${PKI_DIR}/ca.crt"
guest_tls_cert = "${PKI_DIR}/client/tls.crt"
guest_tls_key = "${PKI_DIR}/client/tls.key"
EOF
fi
cat <<EOF
[openshell.drivers.vm]
state_dir = "${VM_STATE_DIR}"
driver_dir = "${DRIVER_DIR}"
grpc_endpoint = "${scheme}://127.0.0.1:${port}"
[openshell.drivers.docker]
grpc_endpoint = "${scheme}://127.0.0.1:${port}"
EOF
} > "$tmp"
chmod 600 "$tmp"
mv "$tmp" "$CONFIG_FILE"
+1 -10
View File
@@ -6,17 +6,8 @@ After=default.target
[Service]
Type=simple
StateDirectory=openshell/gateway
# %S resolves to $XDG_STATE_HOME for user services.
Environment=OPENSHELL_BIND_ADDRESS=127.0.0.1
Environment=OPENSHELL_SERVER_PORT=17670
Environment=OPENSHELL_TLS_CERT=%S/openshell/tls/server/tls.crt
Environment=OPENSHELL_TLS_KEY=%S/openshell/tls/server/tls.key
Environment=OPENSHELL_TLS_CLIENT_CA=%S/openshell/tls/ca.crt
Environment=OPENSHELL_DB_URL=sqlite:%S/openshell/gateway/openshell.db
Environment=OPENSHELL_GATEWAY_CONFIG=%S/openshell/gateway/config.toml
EnvironmentFile=-%h/.config/openshell/gateway.env
EnvironmentFile=-%E/openshell/gateway.env
ExecStartPre=/usr/bin/openshell-gateway generate-certs --output-dir %S/openshell/tls --server-san host.openshell.internal
ExecStartPre=/usr/libexec/openshell/init-gateway-config.sh %S/openshell/gateway/config.toml %S/openshell/tls /usr/libexec/openshell %S/openshell/vm-driver
ExecStart=/usr/bin/openshell-gateway
Restart=on-failure
RestartSec=5s
+41 -38
View File
@@ -22,12 +22,13 @@ network and filesystem policies to sandboxes, routes inference
requests, and provides the SSH tunnel endpoint for CLI-to-sandbox
connections.
When installed via RPM, the gateway runs as a systemd user service
with the Podman compute driver. Sandboxes are rootless Podman
containers on the host.
When installed via a Linux package, the gateway runs as a systemd user
service. The packaged service starts from built-in defaults and reads
the default gateway TOML path only when that file exists.
The gateway exposes a single port (default 8080) with multiplexed
gRPC and HTTP, secured by mutual TLS (mTLS) by default.
The gateway exposes a single port with multiplexed gRPC and HTTP,
secured by mutual TLS (mTLS) by default unless the TOML config disables
TLS.
# OPTIONS
@@ -36,7 +37,7 @@ gRPC and HTTP, secured by mutual TLS (mTLS) by default.
Environment: **OPENSHELL_BIND_ADDRESS**.
**--port** *PORT*
: Port for the gRPC/HTTP API. Default: **8080**.
: Port for the gRPC/HTTP API. Default: **17670**.
Environment: **OPENSHELL_SERVER_PORT**.
**--health-port** *PORT*
@@ -53,22 +54,26 @@ gRPC and HTTP, secured by mutual TLS (mTLS) by default.
Environment: **OPENSHELL_LOG_LEVEL**.
**--db-url** *URL*
: SQLite database URL for state persistence. Required.
: SQLite database URL for state persistence. When unset, the gateway
stores SQLite state under *~/.local/state/openshell/gateway/*.
Environment: **OPENSHELL_DB_URL**.
**--drivers** *DRIVER*\[,*DRIVER*\]
: Compute driver. Accepts a comma-delimited list. The gateway
currently requires exactly one driver. Options: **podman**,
**docker**, **kubernetes**. Default: **kubernetes**.
**docker**, **kubernetes**, **vm**. When unset, the gateway
auto-detects Kubernetes, then Podman, then Docker. VM is opt-in.
Environment: **OPENSHELL_DRIVERS**.
**--tls-cert** *PATH*
: Path to server TLS certificate file. Required unless
**--disable-tls** is set. Environment: **OPENSHELL_TLS_CERT**.
: Path to server TLS certificate file. Defaults to the local generated
TLS bundle when present. Required unless **--disable-tls** is set.
Environment: **OPENSHELL_TLS_CERT**.
**--tls-key** *PATH*
: Path to server TLS private key file. Required unless
**--disable-tls** is set. Environment: **OPENSHELL_TLS_KEY**.
: Path to server TLS private key file. Defaults to the local generated
TLS bundle when present. Required unless **--disable-tls** is set.
Environment: **OPENSHELL_TLS_KEY**.
**--tls-client-ca** *PATH*
: Path to CA certificate for client certificate verification (mTLS).
@@ -100,7 +105,7 @@ configured in the TOML file passed with **--config**.
# SYSTEMD INTEGRATION
The RPM installs a systemd user unit at
The package installs a systemd user unit at
*/usr/lib/systemd/user/openshell-gateway.service*. Manage the gateway
with standard systemd commands:
@@ -114,15 +119,12 @@ View logs:
journalctl --user -u openshell-gateway
journalctl --user -u openshell-gateway -f
The unit runs two **ExecStartPre** steps on first start:
The unit runs **openshell-gateway generate-certs** as an **ExecStartPre**
step on first start. This generates a self-signed PKI bundle for mTLS
and skips generation when the bundle already exists.
1. **openshell-gateway generate-certs --output-dir** generates a
self-signed PKI bundle for mTLS.
2. **init-gateway-env.sh** generates the environment configuration
file.
Both steps are idempotent and skip generation if their output files
already exist.
The gateway then starts from built-in defaults and reads
*~/.config/openshell/gateway.toml* when that file exists.
To persist the service across logouts:
@@ -130,11 +132,16 @@ To persist the service across logouts:
# CONFIGURATION
The systemd user unit reads configuration from
*~/.config/openshell/gateway.env*. See **openshell-gateway.env**(5)
for the full variable reference.
The systemd user unit launches the gateway with:
To override individual settings without modifying gateway.env:
openshell-gateway
Gateway listener, TLS, database, and compute driver settings have local
defaults. Create *~/.config/openshell/gateway.toml* when you need to
override them. The gateway rejects `database_url` in TOML; set
**OPENSHELL_DB_URL** when you need a different database.
To override individual settings without creating TOML:
systemctl --user edit openshell-gateway
@@ -148,16 +155,13 @@ This creates a drop-in override that persists across package upgrades.
*/usr/lib/systemd/user/openshell-gateway.service*
: Systemd user unit file.
*/usr/libexec/openshell/init-gateway-env.sh*
: Gateway environment file generator.
*~/.config/openshell/gateway.env*
: Gateway environment configuration (generated on first start).
*~/.config/openshell/gateway.toml*
: Optional gateway TOML configuration.
*~/.local/state/openshell/tls/*
: Auto-generated TLS certificates.
*~/.local/state/openshell/gateway.db*
*~/.local/state/openshell/gateway/openshell.db*
: SQLite database for gateway state.
*~/.config/openshell/gateways/openshell/mtls/*
@@ -171,18 +175,17 @@ Start the gateway as a systemd user service:
Check gateway health from the CLI:
openshell gateway add --local https://127.0.0.1:8080
openshell gateway add --local https://127.0.0.1:17670
openshell status
Override the API port via a systemd drop-in:
Override the API port in TOML:
systemctl --user edit openshell-gateway
# Add: [Service]
# Add: Environment=OPENSHELL_SERVER_PORT=9090
$EDITOR ~/.config/openshell/gateway.toml
systemctl --user restart openshell-gateway
# SEE ALSO
**openshell**(1), **openshell-gateway.env**(5), **systemctl**(1),
**journalctl**(1), **loginctl**(1), **podman**(1)
**openshell**(1), **systemctl**(1), **journalctl**(1), **loginctl**(1),
**podman**(1)
Full documentation: *https://docs.nvidia.com/openshell/*
-127
View File
@@ -1,127 +0,0 @@
---
title: OPENSHELL-GATEWAY.ENV
section: 5
header: OpenShell Manual
footer: openshell-gateway
date: 2025
---
# NAME
openshell-gateway.env - OpenShell gateway environment configuration
# DESCRIPTION
The **openshell-gateway.env** file contains environment variables that
configure the OpenShell gateway server when running as a systemd user
service. It is generated automatically on first start by
**init-gateway-env.sh** and is not overwritten on subsequent starts or
package upgrades.
The file uses the standard systemd **EnvironmentFile** format: one
**KEY=VALUE** pair per line. Lines beginning with **#** are comments.
Shell variable expansion is not performed.
# LOCATION
The file is located at:
~/.config/openshell/gateway.env
The systemd user unit reads it via:
EnvironmentFile=-~/.config/openshell/gateway.env
The **-** prefix means the service starts normally if the file does not
exist (the unit has built-in defaults for all required settings).
# VARIABLES
## Gateway
**OPENSHELL_BIND_ADDRESS** (default: 0.0.0.0)
: IP address to bind all listeners to. The RPM default of **0.0.0.0**
exposes the gateway on all network interfaces; mTLS must remain
enabled to prevent unauthenticated access. Set to **127.0.0.1** for
local-only access.
**OPENSHELL_SERVER_PORT** (default: 8080)
: Port for the multiplexed gRPC/HTTP API.
**OPENSHELL_HEALTH_PORT** (default: 0)
: Port for unauthenticated health endpoints (/healthz, /readyz).
Set to a non-zero value to enable a dedicated health listener.
**OPENSHELL_METRICS_PORT** (default: 0)
: Port for Prometheus metrics endpoint (/metrics). Set to a
non-zero value to enable a dedicated metrics listener.
**OPENSHELL_LOG_LEVEL** (default: info)
: Log verbosity: **trace**, **debug**, **info**, **warn**, **error**.
**OPENSHELL_DRIVERS** (default: podman)
: Compute driver for sandbox management. Options: **podman**,
**docker**, **kubernetes**. The RPM unit defaults to **podman**.
**OPENSHELL_DB_URL** (default: sqlite://$XDG_STATE_HOME/openshell/gateway.db)
: SQLite database URL for gateway state persistence.
## TLS
**OPENSHELL_TLS_CERT** (default: auto-generated path)
: Path to server TLS certificate.
**OPENSHELL_TLS_KEY** (default: auto-generated path)
: Path to server TLS private key.
**OPENSHELL_TLS_CLIENT_CA** (default: auto-generated path)
: Path to CA certificate for client certificate verification. When
set without **OPENSHELL_OIDC_ISSUER**, mTLS is required. When both
are set, callers may authenticate via Bearer token or client
certificate.
**OPENSHELL_DISABLE_TLS** (default: unset)
: Set to **true** to disable TLS entirely and listen on plaintext
HTTP. Not recommended for production. When the bind address is
**0.0.0.0** (the RPM default), disabling TLS exposes the API to the
entire network without authentication. Restrict
**OPENSHELL_BIND_ADDRESS** to **127.0.0.1** or place the gateway
behind a TLS-terminating reverse proxy.
**OPENSHELL_SERVER_SAN** (default: unset)
: Comma-separated SANs configured on the gateway server certificate.
Wildcard DNS SANs also enable sandbox service URLs under that
domain.
## Driver Configuration
Compute driver settings are configured in the TOML file referenced by
**OPENSHELL_GATEWAY_CONFIG** or **--config**. This includes sandbox
images, image pull policy, callback endpoints, Podman socket path,
Docker network name, VM state directory, and guest TLS material.
# EXAMPLES
Change the API port to 9090:
OPENSHELL_SERVER_PORT=9090
Enable debug logging:
OPENSHELL_LOG_LEVEL=debug
Use externally-managed TLS certificates:
OPENSHELL_TLS_CERT=/etc/pki/tls/certs/openshell.crt
OPENSHELL_TLS_KEY=/etc/pki/tls/private/openshell.key
OPENSHELL_TLS_CLIENT_CA=/etc/pki/tls/certs/openshell-ca.crt
Disable TLS (behind a reverse proxy):
OPENSHELL_DISABLE_TLS=true
# SEE ALSO
**openshell-gateway**(8), **openshell**(1), **systemd.exec**(5)
Full documentation: *https://docs.nvidia.com/openshell/*
+2 -2
View File
@@ -190,7 +190,7 @@ development task, or behind a cloud reverse proxy.
Register the local RPM gateway and create a sandbox:
openshell gateway add --local https://127.0.0.1:8080
openshell gateway add --local https://127.0.0.1:17670
openshell sandbox create -- claude
List sandboxes and connect to one:
@@ -208,7 +208,7 @@ Check gateway health:
# SEE ALSO
**openshell-gateway**(8), **openshell-gateway.env**(5)
**openshell-gateway**(8)
Full documentation: *https://docs.nvidia.com/openshell/*
+46 -59
View File
@@ -9,15 +9,15 @@ TROUBLESHOOTING.md.
## TLS (mTLS)
The RPM enables mutual TLS by default. The gateway requires a valid
client certificate for all API connections, protecting the API even
though it listens on all interfaces (`0.0.0.0`).
client certificate for all API connections and listens on
`127.0.0.1:17670` by default.
### Auto-generated certificates
On first start, the gateway's `ExecStartPre` runs
`openshell-gateway generate-certs --output-dir <state-dir>/openshell/tls`,
which generates the certificates with `rcgen` (the same routine the CLI
uses for local mTLS bundles):
On first start, the systemd user service runs
`openshell-gateway generate-certs --output-dir ~/.local/state/openshell/tls --server-san host.openshell.internal`
to generate certificates with `rcgen` (the same routine the CLI uses for
local mTLS bundles):
| File | Purpose | Location |
|------|---------|----------|
@@ -51,6 +51,7 @@ Names:
- `openshell.openshell.svc.cluster.local`
- `host.containers.internal`
- `host.docker.internal`
- `host.openshell.internal`
- `127.0.0.1`
To connect from a remote machine, you need externally-managed
@@ -63,13 +64,13 @@ To use certificates from an external CA or cert-manager:
1. Place the server cert, key, and CA cert on the filesystem.
1. Edit `~/.config/openshell/gateway.env` or use
`systemctl --user edit openshell-gateway` to override:
1. Edit `~/.config/openshell/gateway.toml`:
```shell
OPENSHELL_TLS_CERT=/path/to/server/tls.crt
OPENSHELL_TLS_KEY=/path/to/server/tls.key
OPENSHELL_TLS_CLIENT_CA=/path/to/ca.crt
```toml
[openshell.gateway.tls]
cert_path = "/path/to/server/tls.crt"
key_path = "/path/to/server/tls.key"
client_ca_path = "/path/to/ca.crt"
```
1. Place the client cert where the CLI expects it:
@@ -94,21 +95,17 @@ The gateway regenerates the PKI on next start.
### Disabling TLS
> **WARNING:** The RPM gateway binds to all interfaces (`0.0.0.0`) by
> default. With TLS disabled, the gateway API is exposed to the entire
> network with **no authentication**. Any host that can reach the
> gateway port has full access, including the ability to create
> sandboxes, execute arbitrary code, and access configured credentials.
> Only disable TLS when the gateway is behind a TLS-terminating reverse
> proxy that enforces its own authentication. When disabling TLS without
> a reverse proxy, restrict `OPENSHELL_BIND_ADDRESS` to `127.0.0.1`.
> **WARNING:** With TLS disabled, the gateway API has no authentication.
> Keep the bind address on `127.0.0.1`, or place the gateway behind a
> TLS-terminating reverse proxy that enforces its own authentication.
To disable TLS (not recommended for production):
1. Edit `~/.config/openshell/gateway.env`:
1. Edit `~/.config/openshell/gateway.toml`:
```shell
OPENSHELL_DISABLE_TLS=true
```toml
[openshell.gateway]
disable_tls = true
```
1. Remove or comment out the `guest_tls_*` entries in
@@ -144,51 +141,44 @@ configuration is required.
## Configuration reference
Gateway process settings are controlled via environment variables. Driver
implementation settings live in `~/.config/openshell/gateway.toml`, which is
generated on first start and selected through `OPENSHELL_GATEWAY_CONFIG`.
Gateway and driver settings have local runtime defaults. The gateway reads
`~/.config/openshell/gateway.toml` when that file exists. Set
`OPENSHELL_GATEWAY_CONFIG` in the launch environment to use a different file.
Values in `gateway.env` override the unit defaults. Use
`systemctl --user edit openshell-gateway` to add overrides that persist
across package upgrades. Gateway CLI/env values override the gateway section
of the TOML file, while driver tables are read from TOML.
Use `systemctl --user edit openshell-gateway` for service environment
overrides that persist across package upgrades.
### Gateway settings
| Variable | Default | Description |
|----------|---------|-------------|
| `OPENSHELL_BIND_ADDRESS` | `0.0.0.0` | IP address to bind all listeners to. The default exposes the gateway on all interfaces; mTLS must remain enabled to prevent unauthenticated access. Set to `127.0.0.1` for local-only access. |
| `OPENSHELL_SERVER_PORT` | `8080` | Port for the gRPC/HTTP API |
| `OPENSHELL_HEALTH_PORT` | `0` (disabled) | Port for unauthenticated health endpoints (`/healthz`, `/readyz`). Set to a non-zero value to enable. |
| `OPENSHELL_METRICS_PORT` | `0` (disabled) | Port for Prometheus metrics (`/metrics`). Set to a non-zero value to enable. |
| `OPENSHELL_LOG_LEVEL` | `info` | Log level: `trace`, `debug`, `info`, `warn`, `error` |
| `OPENSHELL_DRIVERS` | `podman` | Compute driver (`podman`, `docker`, `kubernetes`, `vm`) |
| `OPENSHELL_DB_URL` | `sqlite://$XDG_STATE_HOME/openshell/gateway.db` | SQLite database URL for state persistence |
| TOML option | Default | Description |
|-------------|---------|-------------|
| `bind_address` | `127.0.0.1:17670` | Address for the gRPC/HTTP API. |
| `compute_drivers` | unset | When unset, the gateway auto-detects Kubernetes, then Podman, then Docker. Set `compute_drivers = ["podman"]` to force Podman. |
| `default_image` | `ghcr.io/nvidia/openshell-community/sandboxes/base:latest` | Default sandbox image. |
| `supervisor_image` | `ghcr.io/nvidia/openshell/supervisor:latest` | Supervisor image mounted into Podman sandboxes. |
| `guest_tls_ca`, `guest_tls_cert`, `guest_tls_key` | auto-generated paths | Client TLS material bind-mounted into sandbox containers. |
| `[openshell.gateway.tls]` paths | auto-generated paths | Server TLS certificate, key, and client CA. |
| `disable_tls` | unset | Set to `true` to disable TLS. |
### TLS settings
| Variable | Default | Description |
|----------|---------|-------------|
| `OPENSHELL_TLS_CERT` | (auto-generated path) | Server TLS certificate |
| `OPENSHELL_TLS_KEY` | (auto-generated path) | Server TLS private key |
| `OPENSHELL_TLS_CLIENT_CA` | (auto-generated path) | CA for client certificate verification; requires mTLS unless OIDC is also configured |
| `OPENSHELL_DISABLE_TLS` | (unset) | Set to `true` to disable TLS |
The database URL is not accepted in TOML. When `OPENSHELL_DB_URL` is unset,
the gateway uses `sqlite:$XDG_STATE_HOME/openshell/gateway/openshell.db`.
### Driver TOML settings
The generated `gateway.toml` contains the RPM's Podman defaults:
Create `~/.config/openshell/gateway.toml` when you need to customize driver
settings:
```toml
[openshell]
version = 1
[openshell.gateway]
compute_drivers = ["podman"]
bind_address = "127.0.0.1:17670"
# Leave unset to auto-detect the compute driver.
# compute_drivers = ["podman"]
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
guest_tls_ca = "/home/user/.local/state/openshell/tls/ca.crt"
guest_tls_cert = "/home/user/.local/state/openshell/tls/client/tls.crt"
guest_tls_key = "/home/user/.local/state/openshell/tls/client/tls.key"
[openshell.drivers.podman]
socket_path = "/run/user/1000/podman/podman.sock"
image_pull_policy = "missing"
network_name = "openshell"
stop_timeout_secs = 10
@@ -249,10 +239,7 @@ For air-gapped environments:
| Gateway binary | `/usr/bin/openshell-gateway` |
| CLI binary | `/usr/bin/openshell` |
| Systemd user unit | `/usr/lib/systemd/user/openshell-gateway.service` |
| PKI bootstrap | `openshell-gateway generate-certs` (run from `ExecStartPre`) |
| Env/config generator script | `/usr/libexec/openshell/init-gateway-env.sh` |
| TLS certificates | `~/.local/state/openshell/tls/` |
| CLI client certs | `~/.config/openshell/gateways/openshell/mtls/` |
| Gateway database | `~/.local/state/openshell/gateway.db` |
| Gateway environment | `~/.config/openshell/gateway.env` |
| Gateway TOML configuration | `~/.config/openshell/gateway.toml` |
| Gateway database | `~/.local/state/openshell/gateway/openshell.db` |
| Optional gateway TOML configuration | `~/.config/openshell/gateway.toml` |
+7 -9
View File
@@ -52,7 +52,8 @@ creation. Ensure the host can reach ghcr.io over HTTPS (port 443).
For air-gapped environments, pre-load images with `podman pull` and
set `image_pull_policy = "never"` in
`~/.config/openshell/gateway.toml`. See CONFIGURATION.md for details.
`~/.config/openshell/gateway.toml`. See CONFIGURATION.md for
details.
## Start the gateway
@@ -63,14 +64,11 @@ systemctl --user enable --now openshell-gateway
On first start, the gateway automatically generates:
- A self-signed PKI bundle (CA, server cert, client cert) for mTLS
- A commented configuration file at `~/.config/openshell/gateway.env`
- A gateway TOML file at `~/.config/openshell/gateway.toml`
> **Note:** The gateway binds to all interfaces (`0.0.0.0`) by default.
> Mutual TLS (mTLS) is enabled automatically on first start, requiring a
> valid client certificate for every connection. Do not disable TLS
> without restricting the bind address to `127.0.0.1`. See
> CONFIGURATION.md for details.
> **Note:** The gateway binds to `127.0.0.1:17670` by default. Mutual
> TLS (mTLS) is enabled automatically on first start, requiring a valid
> client certificate for every connection. See CONFIGURATION.md for
> details.
Verify the service is running:
@@ -83,7 +81,7 @@ systemctl --user status openshell-gateway
The CLI needs to know where the gateway is. Register it:
```shell
openshell gateway add --local https://127.0.0.1:8080
openshell gateway add --local https://127.0.0.1:17670
```
This discovers the pre-provisioned mTLS certificates at
+16 -13
View File
@@ -5,10 +5,11 @@ and upgrade procedures for the RPM deployment.
## CLI compatibility
The RPM installs the gateway as a systemd user service with the Podman
compute driver. The published online docs and some CLI commands assume
a Docker/K3s deployment model. This section clarifies which commands
work, which do not, and what to use instead.
The RPM installs the gateway as a systemd user service. On a standard RPM
install the gateway auto-detects Podman because the package depends on it.
The published online docs and some CLI commands assume a Docker/K3s
deployment model. This section clarifies which commands work, which do not,
and what to use instead.
### Commands that work normally
@@ -67,14 +68,14 @@ Forward the gateway port over SSH and connect via localhost:
```shell
# On the remote CLI machine:
ssh -L 8080:127.0.0.1:8080 user@gateway-host
ssh -L 17670:127.0.0.1:17670 user@gateway-host
# In another terminal on the same machine:
# Copy the client certs from the gateway host first:
scp -r user@gateway-host:~/.config/openshell/gateways/openshell/mtls/ \
~/.config/openshell/gateways/openshell/mtls/
openshell gateway add --local https://127.0.0.1:8080
openshell gateway add --local https://127.0.0.1:17670
openshell status
```
@@ -82,6 +83,9 @@ openshell status
Generate certificates that include the server's hostname or IP in the
SANs. See "Using externally-managed certificates" in CONFIGURATION.md.
Then change `bind_address` in
`~/.config/openshell/gateway.toml` to the interface the remote CLI
can reach, for example `0.0.0.0:17670`, and restart the gateway.
After placing the server and client certs, register from the remote
CLI:
@@ -91,7 +95,7 @@ CLI:
mkdir -p ~/.config/openshell/gateways/openshell/mtls/
cp ca.crt tls.crt tls.key ~/.config/openshell/gateways/openshell/mtls/
openshell gateway add --local https://<gateway-hostname>:8080
openshell gateway add --local https://<gateway-hostname>:17670
```
### Firewall
@@ -99,7 +103,7 @@ openshell gateway add --local https://<gateway-hostname>:8080
For remote access, open the gateway port in firewalld:
```shell
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --add-port=17670/tcp --permanent
sudo firewall-cmd --reload
```
@@ -117,7 +121,7 @@ The CLI cannot find a registered gateway. This happens when the
gateway is running but has not been registered with the CLI.
```shell
openshell gateway add --local https://127.0.0.1:8080
openshell gateway add --local https://127.0.0.1:17670
```
### Gateway fails to start
@@ -216,10 +220,9 @@ systemctl --user restart openshell-gateway
The SQLite database schema is auto-migrated on startup. Running
sandboxes are stopped during the restart.
The `gateway.env` and `gateway.toml` files are not overwritten during
upgrades. The `init-gateway-env.sh` script is idempotent and only generates
missing files on first start. New gateway process options can be added
manually by referencing CONFIGURATION.md or running `openshell-gateway --help`.
Package upgrades do not overwrite `~/.config/openshell/gateway.toml` when you
create one. New gateway process options can be added manually by referencing
CONFIGURATION.md or running `openshell-gateway --help`.
To pick up new container images after an upgrade:
-140
View File
@@ -1,140 +0,0 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# Generate the gateway environment and TOML configuration files on first start.
#
# Called from the systemd ExecStartPre directive to bootstrap the
# gateway configuration. Idempotent: exits immediately if the file
# already exists.
#
# Usage:
# init-gateway-env.sh <env-file>
#
# The generated file contains commented defaults for gateway
# environment variables.
set -euo pipefail
ENV_FILE="${1:?Usage: init-gateway-env.sh <env-file>}"
CONFIG_DIR="$(dirname "${ENV_FILE}")"
CONFIG_FILE="${CONFIG_DIR}/gateway.toml"
STATE_HOME="${XDG_STATE_HOME:-${HOME}/.local/state}"
RUNTIME_HOME="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
write_gateway_config() {
if [ -f "${CONFIG_FILE}" ]; then
return
fi
mkdir -p "${CONFIG_DIR}" "${STATE_HOME}/openshell/vm-driver"
cat > "${CONFIG_FILE}" << EOF
[openshell]
version = 1
[openshell.gateway]
compute_drivers = ["podman"]
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
guest_tls_ca = "${STATE_HOME}/openshell/tls/ca.crt"
guest_tls_cert = "${STATE_HOME}/openshell/tls/client/tls.crt"
guest_tls_key = "${STATE_HOME}/openshell/tls/client/tls.key"
[openshell.drivers.podman]
socket_path = "${RUNTIME_HOME}/podman/podman.sock"
image_pull_policy = "missing"
network_name = "openshell"
stop_timeout_secs = 10
[openshell.drivers.vm]
state_dir = "${STATE_HOME}/openshell/vm-driver"
driver_dir = "/usr/libexec/openshell"
grpc_endpoint = "https://127.0.0.1:8080"
EOF
chmod 600 "${CONFIG_FILE}"
}
ensure_env_points_at_config() {
if grep -q '^OPENSHELL_GATEWAY_CONFIG=' "${ENV_FILE}"; then
return
fi
cat >> "${ENV_FILE}" << EOF
# Gateway TOML configuration. Driver implementation settings live here.
OPENSHELL_GATEWAY_CONFIG=${CONFIG_FILE}
EOF
}
# ── Idempotent: skip if env file already exists ─────────────────────
if [ -f "${ENV_FILE}" ]; then
write_gateway_config
ensure_env_points_at_config
exit 0
fi
# ── Create parent directory ─────────────────────────────────────────
mkdir -p "${CONFIG_DIR}"
write_gateway_config
# ── Write environment file ──────────────────────────────────────────
cat > "${ENV_FILE}" << EOF
# OpenShell Gateway Environment Configuration
# Generated on first start. Edit freely; this file is not overwritten.
#
# Run 'openshell-gateway --help' for the full list of options.
# See /usr/share/doc/openshell-gateway/ for guides.
OPENSHELL_GATEWAY_CONFIG=${CONFIG_FILE}
# ---- Optional (uncomment to override defaults) ----
# Database URL for gateway state persistence.
# Default for the user unit: sqlite://\$XDG_STATE_HOME/openshell/gateway.db
#OPENSHELL_DB_URL=sqlite:///path/to/gateway.db
# Compute driver: podman (default for RPM), docker, kubernetes, vm.
#OPENSHELL_DRIVERS=podman
# Bind address. 0.0.0.0 listens on all interfaces; mTLS prevents
# unauthenticated access.
#OPENSHELL_BIND_ADDRESS=0.0.0.0
# API port (default: 8080).
#OPENSHELL_SERVER_PORT=8080
# Log level: trace, debug, info, warn, error.
#OPENSHELL_LOG_LEVEL=info
# Driver implementation settings, including images, pull policy, Podman
# socket, TLS mounts, and VM paths, live in:
# ${CONFIG_FILE}
# ---- TLS (mTLS enabled by default) ----
# PKI is auto-generated by 'openshell-gateway generate-certs' from the
# unit's ExecStartPre on first start. Client certs are placed in
# ~/.config/openshell/gateways/openshell/mtls/ so the CLI discovers them
# automatically.
#
# To use externally-managed certs, uncomment and edit the paths below.
# To rotate certs, delete ~/.local/state/openshell/tls/ and restart.
# WARNING: Disabling TLS with the default bind address (0.0.0.0) exposes
# the gateway API to the entire network with NO authentication. Only
# disable TLS when behind a TLS-terminating reverse proxy, or restrict
# OPENSHELL_BIND_ADDRESS to 127.0.0.1.
#OPENSHELL_DISABLE_TLS=true
# Server TLS (gateway listens with these certs).
#OPENSHELL_TLS_CERT=\$XDG_STATE_HOME/openshell/tls/server/tls.crt
#OPENSHELL_TLS_KEY=\$XDG_STATE_HOME/openshell/tls/server/tls.key
#OPENSHELL_TLS_CLIENT_CA=\$XDG_STATE_HOME/openshell/tls/ca.crt
# Comma-separated DNS SANs configured on the gateway server certificate.
# Wildcard DNS SANs also enable sandbox service URLs under that domain.
# Example: OPENSHELL_SERVER_SAN=*.apps.example.com
#OPENSHELL_SERVER_SAN=
EOF
chmod 600 "${ENV_FILE}"
echo "Gateway environment generated: ${ENV_FILE}"
+11 -15
View File
@@ -88,7 +88,7 @@ The snap exposes the CLI:
- `openshell`
It also defines a system service running the gateway with the Docker driver.
It also defines a system service with packaged Docker driver settings.
- `openshell.gateway`
@@ -97,10 +97,9 @@ it while sandboxes are active. Restart the service manually when you are ready
to move the gateway to the refreshed snap revision.
`openshell-sandbox` is staged next to `openshell-gateway` as the Docker
supervisor binary. The gateway app starts through a small wrapper that writes
`$SNAP_COMMON/gateway.toml` on first start and points the in-process Docker
driver at `$SNAP/bin/openshell-sandbox`. The service stores its gateway
database under `$SNAP_COMMON`.
supervisor binary. The gateway app starts through a small wrapper that sets
Snap-specific defaults and reads `$SNAP_COMMON/gateway.toml` when that file
exists. The service stores its gateway database under `$SNAP_COMMON`.
## Interfaces
@@ -140,21 +139,18 @@ override is required. The OpenShell snap still requires the Docker snap because
it relies on the `docker:docker-daemon` slot; it does not work with Docker
installed from a Debian package or Docker's upstream packages.
The service runs the gateway with the Docker driver enabled:
The service runs the gateway with Snap-specific environment defaults:
```shell
openshell.gateway \
--drivers docker \
--disable-tls \
--port 17670 \
--db-url "sqlite:$SNAP_COMMON/gateway.db?mode=rwc" \
--config "$SNAP_COMMON/gateway.toml"
OPENSHELL_DISABLE_TLS=true \
OPENSHELL_DB_URL="sqlite:$SNAP_COMMON/gateway.db?mode=rwc" \
openshell.gateway
```
This stores the gateway SQLite database at
`/var/snap/openshell/common/gateway.db`. The generated TOML stores Docker
driver settings such as the supervisor binary path, network name, sandbox
namespace, sandbox image, pull policy, and callback endpoint.
`/var/snap/openshell/common/gateway.db`. Create
`/var/snap/openshell/common/gateway.toml` when you need to override gateway or
Docker driver settings.
## Connect with the OpenShell CLI
+5 -17
View File
@@ -4,24 +4,12 @@
set -eu
CONFIG_FILE="${OPENSHELL_GATEWAY_CONFIG:-${SNAP_COMMON}/gateway.toml}"
CANONICAL_CONFIG_FILE="${SNAP_COMMON}/gateway.toml"
export OPENSHELL_DB_URL="${OPENSHELL_DB_URL:-sqlite:${SNAP_COMMON}/gateway.db?mode=rwc}"
export OPENSHELL_DISABLE_TLS="${OPENSHELL_DISABLE_TLS:-true}"
if [ ! -f "$CONFIG_FILE" ]; then
mkdir -p "$(dirname "$CONFIG_FILE")"
cat > "$CONFIG_FILE" << EOF
[openshell]
version = 1
[openshell.drivers.docker]
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
image_pull_policy = "IfNotPresent"
sandbox_namespace = "docker-snap"
grpc_endpoint = "http://host.openshell.internal:17670"
supervisor_bin = "${SNAP}/bin/openshell-sandbox"
network_name = "openshell-snap"
EOF
chmod 600 "$CONFIG_FILE"
if [ -z "${OPENSHELL_GATEWAY_CONFIG:-}" ] && [ -f "$CANONICAL_CONFIG_FILE" ]; then
exec "${SNAP}/bin/openshell-gateway" --config "$CANONICAL_CONFIG_FILE" "$@"
fi
export OPENSHELL_GATEWAY_CONFIG="$CONFIG_FILE"
exec "${SNAP}/bin/openshell-gateway" "$@"
-6
View File
@@ -35,12 +35,6 @@ apps:
daemon: simple
refresh-mode: endure
environment:
OPENSHELL_BIND_ADDRESS: 127.0.0.1
OPENSHELL_SERVER_PORT: 17670
OPENSHELL_DB_URL: "sqlite:$SNAP_COMMON/gateway.db?mode=rwc"
OPENSHELL_DISABLE_TLS: true
OPENSHELL_DRIVERS: docker
OPENSHELL_GATEWAY_CONFIG: "$SNAP_COMMON/gateway.toml"
XDG_DATA_HOME: "$SNAP_COMMON"
# Used for creating and locating certain sockets.
XDG_RUNTIME_DIR: "$SNAP_COMMON"
+7 -3
View File
@@ -24,7 +24,7 @@ Use `openshell status` to confirm the CLI can reach the gateway.
## Supported Compute Drivers
OpenShell supports several local compute drivers. The installer chooses a default driver for your platform, and the gateway reads the driver choice from its startup configuration. Sandbox commands use the same CLI workflow after the gateway is running.
OpenShell supports several local compute drivers. Package-managed gateways leave the driver unset by default so the gateway can auto-detect an available driver. Set `compute_drivers` in the gateway TOML when you need to pin a specific driver.
| Compute Driver | How It Is Configured | System Requirements |
|---|---|---|
@@ -38,7 +38,9 @@ For detailed driver behavior, refer to [Sandbox Compute Drivers](/reference/sand
On macOS, the install script uses Homebrew. The Homebrew package installs the `openshell` CLI, the gateway binary, and a Homebrew-managed gateway service.
The Homebrew service listens on `https://127.0.0.1:17670` and generates a local mTLS bundle on install. The CLI reads the client bundle from `~/.config/openshell/gateways/openshell/mtls/`.
The Homebrew service listens on `https://127.0.0.1:17670` and generates a local mTLS bundle on install. The gateway starts from built-in defaults and reads `~/.config/openshell/gateway.toml` when that file exists. If that file is absent, the Homebrew service also falls back to a Homebrew prefix config when present, such as `/opt/homebrew/var/openshell/gateway.toml`.
The CLI reads the client bundle from `~/.config/openshell/gateways/openshell/mtls/`.
The installer starts the service for you. Use Homebrew service commands when you need to inspect, restart, or stop the gateway service:
@@ -53,7 +55,9 @@ On Fedora and RHEL, the install script uses RPM packages. The RPM installs the `
On Debian and Ubuntu, the install script uses a Debian package. The Debian package installs the `openshell` CLI, the `openshell-gateway` daemon, VM sandbox support, and a systemd user service.
The Debian user service listens on `https://127.0.0.1:17670` and generates a local mTLS bundle before the gateway starts. The CLI reads the client bundle from `~/.config/openshell/gateways/openshell/mtls/`.
The Linux user service listens on `https://127.0.0.1:17670`, starts from built-in defaults, and generates a local mTLS bundle before the gateway starts. Create `~/.config/openshell/gateway.toml` only when you need to override those defaults.
The CLI reads the client bundle from `~/.config/openshell/gateways/openshell/mtls/`.
The installer starts the service for you. Use systemd user commands when you need to inspect, restart, or stop the gateway service:
+2 -2
View File
@@ -38,7 +38,7 @@ Set these environment variables before starting the gateway:
For local access, the server certificate must be valid for the endpoint the CLI uses. Include `localhost` and `127.0.0.1` in the certificate SANs when users connect to a local gateway through loopback.
Package-managed local gateways on Homebrew and Debian generate this bundle automatically for the `openshell` gateway name and use `https://127.0.0.1:17670` by default.
Package-managed local gateways on Homebrew, Debian, and RPM generate this bundle automatically for the `openshell` gateway name and use `https://127.0.0.1:17670` by default.
When you register a package-managed local gateway with `openshell gateway add https://127.0.0.1:17670 --local --name openshell`, the CLI refreshes its mTLS bundle from the package-managed TLS directory.
On Homebrew, the gateway service also mirrors the Docker sandbox client bundle into `$HOME/.local/state/openshell/homebrew/tls` before startup so Docker Desktop can bind-mount the files into sandbox containers.
@@ -162,7 +162,7 @@ When a gateway is deployed with `server.disableTls=true`, TLS is disabled entire
Register a plaintext gateway with an explicit `http://` endpoint:
```shell
openshell gateway add http://127.0.0.1:8080 --local
openshell gateway add http://127.0.0.1:17670 --local
```
This stores the gateway with `auth_mode = plaintext`, skips mTLS client certificate lookup, and does not open the browser login flow.
+18 -7
View File
@@ -8,7 +8,7 @@ keywords: "Generative AI, Cybersecurity, AI Agents, Sandboxing, Gateway, Configu
position: 5
---
The OpenShell gateway reads its configuration from a TOML file when `--config` or `OPENSHELL_GATEWAY_CONFIG` is set. Gateway process flags and gateway `OPENSHELL_*` environment variables override the file. Compute driver settings live in the driver TOML tables. See [RFC 0003](https://github.com/NVIDIA/OpenShell/blob/main/rfc/0003-gateway-configuration/README.md) for the full schema.
The OpenShell gateway reads its configuration from a TOML file when `--config` or `OPENSHELL_GATEWAY_CONFIG` is set. When neither is set, the gateway reads `$XDG_CONFIG_HOME/openshell/gateway.toml` if that file exists. If no config file exists, the gateway starts from built-in defaults. Gateway process flags and gateway `OPENSHELL_*` environment variables override the file. Compute driver settings live in the driver TOML tables. See [RFC 0003](https://github.com/NVIDIA/OpenShell/blob/main/rfc/0003-gateway-configuration/README.md) for the full schema.
## Source Precedence
@@ -16,7 +16,18 @@ The OpenShell gateway reads its configuration from a TOML file when `--config` o
Gateway CLI flag > gateway OPENSHELL_* env var > TOML file > built-in default
```
`database_url` is env-only. The loader rejects it when it appears in the file.
`database_url` is env-only. The loader rejects it when it appears in the file. When `OPENSHELL_DB_URL` is unset, the gateway stores its SQLite database under `$XDG_STATE_HOME/openshell/gateway/openshell.db`.
## Package-Managed Locations
Package-managed gateways do not require a TOML file. Create one at the package's optional config location when you need to override built-in defaults. Set `OPENSHELL_GATEWAY_CONFIG` in the launch environment to use a different file.
| Package | Optional Gateway TOML location |
|---|---|
| Homebrew | `$XDG_CONFIG_HOME/openshell/gateway.toml` when it exists, otherwise an existing Homebrew prefix config such as `/opt/homebrew/var/openshell/gateway.toml`. |
| Debian/Ubuntu | `$XDG_CONFIG_HOME/openshell/gateway.toml`, usually `~/.config/openshell/gateway.toml` for the systemd user service. |
| Fedora/RHEL RPM | `$XDG_CONFIG_HOME/openshell/gateway.toml`, usually `~/.config/openshell/gateway.toml` for the systemd user service. |
| Snap | `$SNAP_COMMON/gateway.toml`, usually `/var/snap/openshell/common/gateway.toml`. |
## Layout
@@ -138,7 +149,7 @@ Sandboxes run as containers on a local bridge network. The supervisor binary is
version = 1
[openshell.gateway]
bind_address = "127.0.0.1:8080"
bind_address = "127.0.0.1:17670"
log_level = "info"
compute_drivers = ["docker"]
@@ -151,7 +162,7 @@ guest_tls_key = "/etc/openshell/certs/client-key.pem"
default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
image_pull_policy = "IfNotPresent"
sandbox_namespace = "docker-dev"
grpc_endpoint = "https://host.openshell.internal:8080"
grpc_endpoint = "https://host.openshell.internal:17670"
network_name = "openshell-docker"
# Skip the image-pull-and-extract step by pointing at a locally built binary.
supervisor_bin = "/usr/local/libexec/openshell/openshell-sandbox"
@@ -166,7 +177,7 @@ Sandboxes run as Podman containers on a user-mode bridge network. The supervisor
version = 1
[openshell.gateway]
bind_address = "127.0.0.1:8080"
bind_address = "127.0.0.1:17670"
log_level = "info"
compute_drivers = ["podman"]
@@ -193,7 +204,7 @@ Each sandbox runs inside its own libkrun microVM managed by the standalone `open
version = 1
[openshell.gateway]
bind_address = "127.0.0.1:8080"
bind_address = "127.0.0.1:17670"
log_level = "info"
# VM is never auto-detected; an explicit entry here is required.
compute_drivers = ["vm"]
@@ -204,7 +215,7 @@ guest_tls_cert = "/var/lib/openshell/guest-tls/client.pem"
guest_tls_key = "/var/lib/openshell/guest-tls/client-key.pem"
[openshell.drivers.vm]
grpc_endpoint = "https://host.containers.internal:8080"
grpc_endpoint = "https://host.containers.internal:17670"
state_dir = "/var/lib/openshell/vm"
# Where the gateway looks for the openshell-driver-vm subprocess binary.
driver_dir = "/usr/local/libexec/openshell"
+18 -16
View File
@@ -14,21 +14,22 @@ Every compute driver runs the OpenShell supervisor inside the sandbox workload.
## Configure a Compute Driver
Configure the compute driver on the gateway. Current releases accept one driver per gateway:
Configure the compute driver on the gateway. Current releases accept one driver per gateway. Set `compute_drivers` in the gateway TOML file:
```shell
openshell-gateway --drivers docker
```toml
[openshell.gateway]
compute_drivers = ["docker"]
```
You can also set the driver with `OPENSHELL_DRIVERS`. Supported values are `docker`, `podman`, `kubernetes`, and `vm`.
Supported values are `docker`, `podman`, `kubernetes`, and `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`.
When `compute_drivers` is 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 `compute_drivers = ["vm"]` or set `OPENSHELL_DRIVERS=vm` in the launch environment.
Common gateway options:
| Option | Environment variable | Description |
|---|---|---|
| `--drivers <driver>` | `OPENSHELL_DRIVERS` | Select the compute driver. Supported values are `docker`, `podman`, `kubernetes`, and `vm`. |
| Gateway TOML option | Description |
|---|---|
| `compute_drivers = ["<driver>"]` | Select the compute driver. Supported values are `docker`, `podman`, `kubernetes`, and `vm`. |
Set driver-specific values such as sandbox images, callback endpoints, network names, TLS material, and VM sizing in the gateway TOML file. See the [Gateway Configuration File](./gateway-config) reference for the full `[openshell.drivers.<name>]` schema.
@@ -45,7 +46,7 @@ The gateway talks to the Docker daemon to create sandbox containers. Docker is a
For maintainer-level implementation details, refer to the [Docker driver README](https://github.com/NVIDIA/OpenShell/blob/main/crates/openshell-driver-docker/README.md).
Select Docker with `--drivers docker` or `OPENSHELL_DRIVERS=docker`. Configure Docker driver values such as `grpc_endpoint`, `network_name`, `supervisor_bin`, `supervisor_image`, `image_pull_policy`, and `guest_tls_*` in `[openshell.drivers.docker]`.
Select Docker with `compute_drivers = ["docker"]` in `[openshell.gateway]`. Configure Docker driver values such as `grpc_endpoint`, `network_name`, `supervisor_bin`, `supervisor_image`, `image_pull_policy`, and `guest_tls_*` in `[openshell.drivers.docker]`.
For GPU-backed Docker sandboxes, configure Docker CDI before starting the gateway so OpenShell can detect the daemon capability.
@@ -57,7 +58,7 @@ The gateway talks to the Podman API socket. The Podman driver requires Podman 5.
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).
Select Podman with `--drivers podman` or `OPENSHELL_DRIVERS=podman`. Configure Podman driver values such as `socket_path`, `network_name`, `supervisor_image`, `stop_timeout_secs`, `image_pull_policy`, `grpc_endpoint`, and `guest_tls_*` in `[openshell.drivers.podman]`.
Select Podman with `compute_drivers = ["podman"]` in `[openshell.gateway]`. Configure Podman driver values such as `socket_path`, `network_name`, `supervisor_image`, `stop_timeout_secs`, `image_pull_policy`, `grpc_endpoint`, and `guest_tls_*` in `[openshell.drivers.podman]`.
## MicroVM Driver
@@ -77,15 +78,16 @@ For maintainer-level implementation details, refer to the [VM driver README](htt
The VM driver is opt-in. Release packages can install `openshell-driver-vm`, but the gateway does not select it unless you configure the driver explicitly.
For a one-off gateway process, pass `--drivers vm`:
Enable VM by setting `compute_drivers = ["vm"]` in the gateway TOML file:
```shell
openshell-gateway --drivers vm
```toml
[openshell.gateway]
compute_drivers = ["vm"]
```
For a service, set `OPENSHELL_DRIVERS=vm` in the service environment file and restart the service. Homebrew creates `$(brew --prefix)/var/openshell/gateway.env` with a commented `OPENSHELL_DRIVERS=vm` entry. Debian and RPM user services read `~/.config/openshell/gateway.env`.
For a launch-time override, set `OPENSHELL_DRIVERS=vm` in the gateway environment and restart the service.
Select VM with `--drivers vm` or `OPENSHELL_DRIVERS=vm`. Configure VM driver values such as `grpc_endpoint`, `driver_dir`, `state_dir`, `default_image`, `bootstrap_image`, `vcpus`, `mem_mib`, `overlay_disk_mib`, `krun_log_level`, and `guest_tls_*` in `[openshell.drivers.vm]`. The VM `state_dir` stores overlay disks, console logs, runtime state, image-rootfs cache, and the private `run/compute-driver.sock` socket.
Configure VM driver values such as `grpc_endpoint`, `driver_dir`, `state_dir`, `default_image`, `bootstrap_image`, `vcpus`, `mem_mib`, `overlay_disk_mib`, `krun_log_level`, and `guest_tls_*` in `[openshell.drivers.vm]`. The VM `state_dir` stores overlay disks, console logs, runtime state, image-rootfs cache, and the private `run/compute-driver.sock` socket.
The gateway starts `openshell-driver-vm` over a private Unix socket and passes its process ID so the driver can reject unexpected local clients. The driver's standalone TCP listener is disabled unless `--allow-unauthenticated-tcp` is set for local development.
@@ -107,7 +109,7 @@ For maintainer-level implementation details, refer to the [Kubernetes driver REA
| Gateway configuration | Helm value | Description |
|---|---|---|
| `compute_drivers = ["kubernetes"]` or `--drivers kubernetes` | Not applicable | Select the Kubernetes compute driver. |
| `compute_drivers = ["kubernetes"]` | Not applicable | Select the Kubernetes compute driver. |
| `[openshell.drivers.kubernetes].namespace` | `server.sandboxNamespace` | Set the namespace for sandbox resources. The Helm chart defaults to the release namespace when left empty. |
| `default_image` | `server.sandboxImage` | Set the default sandbox image. |
| `image_pull_policy` | `server.sandboxImagePullPolicy` | Set the Kubernetes image pull policy for sandbox pods. |
+14 -45
View File
@@ -53,7 +53,7 @@ BuildRequires: pandoc
BuildRequires: python3-devel
# Runtime: container runtime for package-managed gateway sandboxes.
# Podman is preferred; Docker is also supported via --container-runtime flag.
# The gateway auto-detects Podman when the package-managed service starts.
Recommends: podman
%description
@@ -71,9 +71,9 @@ Requires: %{name} = %{version}-%{release}
%description gateway
OpenShell gateway server providing the control-plane API for sandbox
lifecycle management. This package configures the gateway to use the
Podman compute driver, pulling sandbox and supervisor images from
ghcr.io/nvidia/openshell.
lifecycle management. This package installs Podman-oriented defaults in
gateway TOML while leaving compute driver selection to gateway auto-detection
or explicit operator configuration.
# --- Python SDK sub-package ---
%package -n python3-%{name}
@@ -119,7 +119,6 @@ cargo build --release --bin openshell --bin openshell-gateway
# Build man pages from markdown
pandoc -s -t man deploy/man/openshell.1.md -o openshell.1
pandoc -s -t man deploy/man/openshell-gateway.8.md -o openshell-gateway.8
pandoc -s -t man deploy/man/openshell-gateway.env.5.md -o openshell-gateway.env.5
%install
# --- CLI binary ---
@@ -128,50 +127,31 @@ install -Dpm 0755 target/release/%{name} %{buildroot}%{_bindir}/%{name}
# --- Gateway binary ---
install -Dpm 0755 target/release/%{name}-gateway %{buildroot}%{_bindir}/%{name}-gateway
# --- Gateway systemd user unit (rootless Podman) ---
# --- Gateway systemd user unit ---
# Installed to the systemd user unit directory so any user can run:
# systemctl --user enable --now openshell-gateway.service
# Podman socket activation provides the container API.
install -d %{buildroot}%{_userunitdir}
cat > %{buildroot}%{_userunitdir}/%{name}-gateway.service << 'EOF'
[Unit]
Description=OpenShell Gateway (user)
Documentation=https://github.com/NVIDIA/OpenShell
After=podman.socket
Requires=podman.socket
Wants=podman.socket
[Service]
Type=exec
# Self-contained defaults for rootless operation with mTLS.
#
# PKI and gateway.env are auto-generated on first start. Client certs
# are placed in ~/.config/openshell/gateways/openshell/mtls/ so the
# CLI discovers them automatically.
# PKI is auto-generated on first start. Client certs are placed in
# ~/.config/openshell/gateways/openshell/mtls/ so the CLI discovers them
# automatically. Gateway runtime defaults are used unless a TOML config
# exists in the default user config location or OPENSHELL_GATEWAY_CONFIG is set.
# See /usr/share/doc/openshell-gateway/ for details.
# Auto-generate PKI on first start. Idempotent: skips when all six PEMs are
# already in place. %%S expands to $XDG_STATE_HOME (~/.local/state) in user
# units.
ExecStartPre=/usr/bin/openshell-gateway generate-certs --output-dir %%S/openshell/tls
# Auto-generate PKI on first start if not present.
# %%S expands to $XDG_STATE_HOME (~/.local/state) in user units.
ExecStartPre=/usr/bin/openshell-gateway generate-certs --output-dir %%S/openshell/tls --server-san host.openshell.internal
# Auto-generate gateway.env (commented config reference) on first
# start if not present.
# %%E expands to $XDG_CONFIG_HOME (~/.config) in user units.
ExecStartPre=%{_libexecdir}/openshell/init-gateway-env.sh %%E/openshell/gateway.env
# Optional OPENSHELL_* overrides.
EnvironmentFile=-%%E/openshell/gateway.env
Environment=OPENSHELL_BIND_ADDRESS=0.0.0.0
Environment=OPENSHELL_DRIVERS=podman
Environment=OPENSHELL_DB_URL=sqlite://%%S/openshell/gateway.db
Environment=OPENSHELL_SUPERVISOR_IMAGE=ghcr.io/nvidia/openshell/supervisor:%{image_tag}
Environment=OPENSHELL_SANDBOX_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest
# mTLS: auto-generated certs in the state directory.
Environment=OPENSHELL_TLS_CERT=%%S/openshell/tls/server/tls.crt
Environment=OPENSHELL_TLS_KEY=%%S/openshell/tls/server/tls.key
Environment=OPENSHELL_TLS_CLIENT_CA=%%S/openshell/tls/ca.crt
# Podman driver: client certs bind-mounted into sandbox containers.
Environment=OPENSHELL_PODMAN_TLS_CA=%%S/openshell/tls/ca.crt
Environment=OPENSHELL_PODMAN_TLS_CERT=%%S/openshell/tls/client/tls.crt
Environment=OPENSHELL_PODMAN_TLS_KEY=%%S/openshell/tls/client/tls.key
ExecStart=/usr/bin/openshell-gateway
StateDirectory=openshell
Restart=on-failure
@@ -187,14 +167,6 @@ RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
WantedBy=default.target
EOF
# --- Gateway env generator ---
install -d %{buildroot}%{_libexecdir}/%{name}
install -pm 0755 deploy/rpm/init-gateway-env.sh %{buildroot}%{_libexecdir}/%{name}/init-gateway-env.sh
# Patch commented image defaults to match the build type (dev or latest).
# The source file uses :latest as a generic reference; the installed copy
# reflects what this RPM actually expects from the registry.
sed -i 's|supervisor:latest|supervisor:%{image_tag}|' %{buildroot}%{_libexecdir}/%{name}/init-gateway-env.sh
# --- Gateway documentation ---
install -d %{buildroot}%{_docdir}/%{name}-gateway
install -pm 0644 deploy/rpm/QUICKSTART.md %{buildroot}%{_docdir}/%{name}-gateway/QUICKSTART.md
@@ -204,7 +176,6 @@ install -pm 0644 deploy/rpm/TROUBLESHOOTING.md %{buildroot}%{_docdir}/%{name}-ga
# --- Man pages ---
install -Dpm 0644 openshell.1 %{buildroot}%{_mandir}/man1/openshell.1
install -Dpm 0644 openshell-gateway.8 %{buildroot}%{_mandir}/man8/openshell-gateway.8
install -Dpm 0644 openshell-gateway.env.5 %{buildroot}%{_mandir}/man5/openshell-gateway.env.5
# --- Python SDK ---
# Install Python SDK modules (test files are intentionally excluded)
@@ -275,9 +246,7 @@ PYTHONPATH=%{buildroot}%{python3_sitelib} %{python3} -c "from importlib.metadata
%doc %{_docdir}/%{name}-gateway/TROUBLESHOOTING.md
%{_bindir}/%{name}-gateway
%{_userunitdir}/%{name}-gateway.service
%{_libexecdir}/%{name}/init-gateway-env.sh
%{_mandir}/man8/openshell-gateway.8*
%{_mandir}/man5/openshell-gateway.env.5*
%files -n python3-%{name}
%license LICENSE
+82 -9
View File
@@ -51,20 +51,93 @@ def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_without_defaul
"v0.0.10/openshell-driver-vm-aarch64-apple-darwin.tar.gz"
) in formula
assert 'sha256 "' + "b" * 64 + '"' in formula
assert "OPENSHELL_DRIVERS:" not in formula
assert "#OPENSHELL_DRIVERS=vm" in formula
assert 'OPENSHELL_GATEWAY_CONFIG: "#{var}/openshell/gateway.toml"' in formula
assert 'driver_dir = "#{opt_libexec}"' in formula
assert 'supervisor_image = "ghcr.io/nvidia/openshell/supervisor:0.0.10"' in formula
assert "OPENSHELL_DRIVERS: " not in formula
assert 'OPENSHELL_GATEWAY_CONFIG: "#{var}/openshell/gateway.toml"' not in formula
assert "init-gateway-config.sh" not in formula
assert 'bind_address = "127.0.0.1:17670"' not in formula
assert '# compute_drivers = ["vm"]' not in formula
assert 'run opt_libexec/"openshell-gateway-homebrew-service"' in formula
assert 'xdg_config_home="${XDG_CONFIG_HOME:-${HOME}/.config}"' in formula
assert 'xdg_gateway_config="${xdg_config_home}/openshell/gateway.toml"' in formula
assert 'prefix_gateway_config="#{var}/openshell/gateway.toml"' in formula
assert (
'docker_tls_dir="${OPENSHELL_DOCKER_TLS_DIR:-${HOME}/.local/state/openshell/homebrew/tls}"'
'if [ -z "${OPENSHELL_GATEWAY_CONFIG:-}" ] && [ ! -f "${xdg_gateway_config}" ] && [ -f "${prefix_gateway_config}" ]; then'
) in formula
assert 'guest_tls_ca = "${docker_tls_dir}/ca.crt"' in formula
assert 'gateway_env="#{var}/openshell/gateway.env"' in formula
assert '. "${gateway_env}"' in formula
assert (
'exec "#{opt_bin}/openshell-gateway" --config "${prefix_gateway_config}"'
in formula
)
assert 'exec "#{opt_bin}/openshell-gateway"' in formula
assert "--db-url" not in formula
assert 'docker_tls_dir="${HOME}/.local/state/openshell/homebrew/tls"' in formula
assert (
'export OPENSHELL_LOCAL_TLS_DIR="${OPENSHELL_LOCAL_TLS_DIR:-${docker_tls_dir}}"'
in formula
)
assert '/usr/bin/install -m 0600 "#{var}/openshell/tls/server/tls.key"' in formula
assert "OPENSHELL_CONFIG_" not in formula
assert "OPENSHELL_DOCKER_TLS_DIR" not in formula
assert 'xdg_gateway_env="${xdg_config_home}/openshell/gateway.env"' in formula
assert 'prefix_gateway_env="#{var}/openshell/gateway.env"' in formula
assert '. "${xdg_gateway_env}"' in formula
assert '. "${prefix_gateway_env}"' in formula
assert 'gateway_env = var/"openshell/gateway.env"' not in formula
assert "#OPENSHELL_GATEWAY_CONFIG=#{var}/openshell/gateway.toml" not in formula
assert "environment_variables(" not in formula
assert " OPENSHELL_BIND_ADDRESS:" not in formula
assert " OPENSHELL_SERVER_PORT:" not in formula
assert " OPENSHELL_TLS_CERT:" not in formula
assert "OPENSHELL_DRIVER_DIR:" not in formula
assert "OPENSHELL_DOCKER_SUPERVISOR_IMAGE:" not in formula
assert 'OPENSHELL_DOCKER_TLS_CA: "#{var}/openshell/tls/ca.crt"' not in formula
assert "entitlements.atomic_write" in formula
assert "brew services restart openshell" in formula
def test_snap_wrapper_uses_optional_gateway_config_without_generating_toml() -> None:
repo_root = Path(__file__).resolve().parents[2]
wrapper = (repo_root / "deploy/snap/bin/openshell-gateway-wrapper").read_text(
encoding="utf-8"
)
assert "init-gateway-config.sh" not in wrapper
assert (
'export OPENSHELL_DB_URL="${OPENSHELL_DB_URL:-sqlite:${SNAP_COMMON}/gateway.db?mode=rwc}"'
in wrapper
)
assert 'export OPENSHELL_DISABLE_TLS="${OPENSHELL_DISABLE_TLS:-true}"' in wrapper
assert (
'exec "${SNAP}/bin/openshell-gateway" --config "$CANONICAL_CONFIG_FILE" "$@"'
in wrapper
)
assert 'exec "${SNAP}/bin/openshell-gateway" "$@"' in wrapper
def test_rpm_spec_uses_gateway_defaults_without_config_helper() -> None:
repo_root = Path(__file__).resolve().parents[2]
spec = (repo_root / "openshell.spec").read_text(encoding="utf-8")
assert "init-gateway-config.sh" not in spec
assert "init-pki.sh" not in spec
assert "openshell-gateway generate-certs --output-dir %%S/openshell/tls" in spec
assert "EnvironmentFile=-%%E/openshell/gateway.env" in spec
assert "Environment=OPENSHELL_DRIVERS" not in spec
assert "Environment=OPENSHELL_BIND_ADDRESS" not in spec
assert "Environment=OPENSHELL_PODMAN_TLS_CA" not in spec
assert "ExecStart=/usr/bin/openshell-gateway" in spec
assert "--config" not in spec
assert "--db-url" not in spec
def test_deb_user_service_uses_gateway_defaults_without_config_helper() -> None:
repo_root = Path(__file__).resolve().parents[2]
unit = (repo_root / "deploy/deb/openshell-gateway.service").read_text(
encoding="utf-8"
)
assert "EnvironmentFile=-%E/openshell/gateway.env" in unit
assert "openshell-gateway generate-certs --output-dir %S/openshell/tls" in unit
assert "init-gateway-config.sh" not in unit
assert "ExecStart=/usr/bin/openshell-gateway" in unit
assert "--config" not in unit
assert "--db-url" not in unit
+1 -1
View File
@@ -65,7 +65,7 @@ version = 1 # optional; reserved for future schema migratio
# ──────────────────────────────────────────────────────────────────────────────
[openshell.gateway]
# Listener
bind_address = "127.0.0.1:8080" # default: 127.0.0.1:8080 (loopback)
bind_address = "127.0.0.1:17670" # default: 127.0.0.1:17670 (loopback)
health_bind_address = "0.0.0.0:8081" # optional; omit to disable
metrics_bind_address = "0.0.0.0:9090" # optional; omit to disable
extra_bind_addresses = [] # additional listeners (driver callbacks, etc.)
-6
View File
@@ -41,12 +41,6 @@ apps:
daemon: simple
refresh-mode: endure
environment:
OPENSHELL_BIND_ADDRESS: 127.0.0.1
OPENSHELL_SERVER_PORT: 17670
OPENSHELL_DB_URL: "sqlite:$SNAP_COMMON/gateway.db?mode=rwc"
OPENSHELL_DISABLE_TLS: "true"
OPENSHELL_DRIVERS: docker
OPENSHELL_GATEWAY_CONFIG: "$SNAP_COMMON/gateway.toml"
XDG_DATA_HOME: "$SNAP_COMMON"
XDG_RUNTIME_DIR: "$SNAP_COMMON"
plugs:
-2
View File
@@ -115,8 +115,6 @@ stage_binary "$OPENSHELL_DRIVER_VM_BINARY" "$pkgroot/usr/libexec/openshell/opens
# Per-user systemd unit. Each user enables it via `systemctl --user`.
install -D -m 0644 "$src_dir/openshell-gateway.service" \
"$pkgroot/usr/lib/systemd/user/openshell-gateway.service"
install -D -m 0755 "$src_dir/init-gateway-config.sh" \
"$pkgroot/usr/libexec/openshell/init-gateway-config.sh"
# ---------------------------------------------------------------------------
# DEBIAN/ control directory
+19 -63
View File
@@ -224,11 +224,6 @@ def _asset_url(release_tag: str, filename: str) -> str:
return f"{GITHUB_RELEASE_DOWNLOADS}/{release_tag}/{filename}"
def _homebrew_supervisor_image(release_tag: str) -> str:
image_tag = "dev" if release_tag == "dev" else release_tag.removeprefix("v")
return f"ghcr.io/nvidia/openshell/supervisor:{image_tag}"
def render_homebrew_formula(
*,
release_tag: str,
@@ -240,7 +235,6 @@ def render_homebrew_formula(
raise ValueError(f"release tag contains unsupported characters: {release_tag}")
version = release_tag.removeprefix("v")
docker_supervisor_image = _homebrew_supervisor_image(release_tag)
return f"""# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
@@ -289,50 +283,37 @@ class Openshell < Formula
exit 1
fi
gateway_env="#{{var}}/openshell/gateway.env"
if [ -f "${{gateway_env}}" ]; then
xdg_config_home="${{XDG_CONFIG_HOME:-${{HOME}}/.config}}"
xdg_gateway_env="${{xdg_config_home}}/openshell/gateway.env"
prefix_gateway_env="#{{var}}/openshell/gateway.env"
if [ -f "${{xdg_gateway_env}}" ]; then
set -a
. "${{gateway_env}}"
. "${{xdg_gateway_env}}"
set +a
elif [ -f "${{prefix_gateway_env}}" ]; then
set -a
. "${{prefix_gateway_env}}"
set +a
fi
docker_tls_dir="${{OPENSHELL_DOCKER_TLS_DIR:-${{HOME}}/.local/state/openshell/homebrew/tls}}"
docker_tls_dir="${{HOME}}/.local/state/openshell/homebrew/tls"
mkdir -p "${{docker_tls_dir}}/server"
mkdir -p "${{docker_tls_dir}}/client"
chmod 700 "${{docker_tls_dir}}" "${{docker_tls_dir}}/client"
chmod 700 "${{docker_tls_dir}}" "${{docker_tls_dir}}/server" "${{docker_tls_dir}}/client"
/usr/bin/install -m 0644 "#{{var}}/openshell/tls/ca.crt" "${{docker_tls_dir}}/ca.crt"
/usr/bin/install -m 0644 "#{{var}}/openshell/tls/server/tls.crt" "${{docker_tls_dir}}/server/tls.crt"
/usr/bin/install -m 0600 "#{{var}}/openshell/tls/server/tls.key" "${{docker_tls_dir}}/server/tls.key"
/usr/bin/install -m 0644 "#{{var}}/openshell/tls/client/tls.crt" "${{docker_tls_dir}}/client/tls.crt"
/usr/bin/install -m 0600 "#{{var}}/openshell/tls/client/tls.key" "${{docker_tls_dir}}/client/tls.key"
export OPENSHELL_LOCAL_TLS_DIR="${{OPENSHELL_LOCAL_TLS_DIR:-${{docker_tls_dir}}}}"
gateway_config="${{OPENSHELL_GATEWAY_CONFIG:-#{{var}}/openshell/gateway.toml}}"
if [ ! -f "${{gateway_config}}" ]; then
mkdir -p "$(dirname "${{gateway_config}}")" "#{{var}}/openshell/vm-driver"
cat > "${{gateway_config}}" <<EOF
[openshell]
version = 1
xdg_gateway_config="${{xdg_config_home}}/openshell/gateway.toml"
prefix_gateway_config="#{{var}}/openshell/gateway.toml"
[openshell.gateway]
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
guest_tls_ca = "#{{var}}/openshell/tls/ca.crt"
guest_tls_cert = "#{{var}}/openshell/tls/client/tls.crt"
guest_tls_key = "#{{var}}/openshell/tls/client/tls.key"
[openshell.drivers.vm]
state_dir = "#{{var}}/openshell/vm-driver"
driver_dir = "#{{opt_libexec}}"
grpc_endpoint = "https://127.0.0.1:{LOCAL_GATEWAY_PORT}"
[openshell.drivers.docker]
grpc_endpoint = "https://127.0.0.1:{LOCAL_GATEWAY_PORT}"
supervisor_image = "{docker_supervisor_image}"
guest_tls_ca = "${{docker_tls_dir}}/ca.crt"
guest_tls_cert = "${{docker_tls_dir}}/client/tls.crt"
guest_tls_key = "${{docker_tls_dir}}/client/tls.key"
EOF
chmod 0600 "${{gateway_config}}"
if [ -z "${{OPENSHELL_GATEWAY_CONFIG:-}}" ] && [ ! -f "${{xdg_gateway_config}}" ] && [ -f "${{prefix_gateway_config}}" ]; then
exec "#{{opt_bin}}/openshell-gateway" --config "${{prefix_gateway_config}}"
fi
export OPENSHELL_GATEWAY_CONFIG="${{gateway_config}}"
exec "#{{opt_bin}}/openshell-gateway"
SH
chmod 0755, libexec/"openshell-gateway-homebrew-service"
@@ -344,22 +325,6 @@ EOF
(var/"log/openshell").mkpath
system bin/"openshell-gateway", "generate-certs", "--output-dir", var/"openshell/tls", "--server-san", "host.openshell.internal"
gateway_env = var/"openshell/gateway.env"
unless gateway_env.exist?
gateway_env.atomic_write <<~ENV
# OpenShell Gateway Environment Configuration
# Edit freely; this file is not overwritten.
#
# Uncomment to force the VM compute driver. Leaving this unset keeps
# normal Podman/Docker/Kubernetes auto-detection.
#OPENSHELL_DRIVERS=vm
# Driver implementation settings live in gateway.toml.
#OPENSHELL_GATEWAY_CONFIG=#{{var}}/openshell/gateway.toml
ENV
chmod 0600, gateway_env
end
entitlements = var/"openshell/openshell-driver-vm.entitlements.plist"
entitlements.atomic_write <<~XML
<?xml version="1.0" encoding="UTF-8"?>
@@ -377,15 +342,6 @@ EOF
service do
run opt_libexec/"openshell-gateway-homebrew-service"
environment_variables(
OPENSHELL_BIND_ADDRESS: "127.0.0.1",
OPENSHELL_SERVER_PORT: "{LOCAL_GATEWAY_PORT}",
OPENSHELL_TLS_CERT: "#{{var}}/openshell/tls/server/tls.crt",
OPENSHELL_TLS_KEY: "#{{var}}/openshell/tls/server/tls.key",
OPENSHELL_TLS_CLIENT_CA: "#{{var}}/openshell/tls/ca.crt",
OPENSHELL_DB_URL: "sqlite:#{{var}}/openshell/gateway/openshell.db",
OPENSHELL_GATEWAY_CONFIG: "#{{var}}/openshell/gateway.toml",
)
keep_alive successful_exit: false
log_path var/"log/openshell/openshell-gateway.out.log"
error_log_path var/"log/openshell/openshell-gateway.err.log"