mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-04 08:28:19 +08:00
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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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.
|
||||
//!
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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/*
|
||||
@@ -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
@@ -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` |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user