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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .agents/skills/helm-dev-environment/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,14 @@ for dependencies still declared in `Chart.yaml`.
| `deploy/kube/manifests/envoy-gateway-openshell.yaml` | GatewayClass and BackendTrafficPolicy for Envoy Gateway (`mise run helm:gateway:apply`) |
| `tasks/scripts/helm-k3s-local.sh` | k3d cluster create/delete/start/stop/status |
| `tasks/scripts/keycloak-k8s-setup.sh` | Keycloak deploy, realm import, and development TLS trust anchor |

## App listener fixture

The opt-in `deploy/helm/openshell/ci/values-app-listener.yaml` enables the
supervisor listener at port 9443 without discovery or a proxy. Provision the
operator TLS Secret and an ingress-client NetworkPolicy first. Follow
`e2e/kubernetes/app-listener/README.md` for direct in-cluster verification,
shared-member selection, and the HA follow-up checks. Keep the fixture disabled
in default profiles. Shared supervisors expose app ports on their Pod IPs
without a shared app Service. Select the member owning the sandbox; automatic
owner selection belongs to the later discovery API.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions crates/openshell-core/src/app_listener.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Driver-independent application listener configuration.

use serde::{Deserialize, Serialize};

/// Explicitly enabled application ingress. Deployment details such as TLS
/// Secret names belong to the driver, not to this common configuration.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct AppListenerConfig {
pub enabled: bool,
pub port: u16,
pub domain: String,
}

impl Default for AppListenerConfig {
fn default() -> Self {
Self {
enabled: false,
port: 8443,
domain: String::new(),
}
}
}

impl AppListenerConfig {
pub fn validate(&self) -> Result<(), String> {
if !self.enabled {
return Ok(());
}
if self.port < 1024 {
return Err("supervisor.app_listener.port must be at least 1024".into());
}
if self.domain.len() > 253
|| !self.domain.split('.').all(|label| {
!label.is_empty()
&& label.len() <= 63
&& !label.starts_with('-')
&& !label.ends_with('-')
&& label
.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
})
{
return Err("supervisor.app_listener.domain must be a lowercase DNS name".into());
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn only_explicit_enablement_requires_a_valid_endpoint() {
assert!(!AppListenerConfig::default().enabled);
assert!(AppListenerConfig::default().validate().is_ok());
let mut config = AppListenerConfig {
enabled: true,
..Default::default()
};
assert!(config.validate().is_err());
config.domain = "services.example.com".into();
assert!(config.validate().is_ok());
config.port = 0;
assert!(config.validate().is_err());
}
}
2 changes: 2 additions & 0 deletions crates/openshell-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ pub struct Config {

/// Browser-facing sandbox service routing configuration.
pub service_routing: ServiceRoutingConfig,
pub supervisor_app_listener: crate::app_listener::AppListenerConfig,
}

/// Browser-facing sandbox service routing configuration.
Expand Down Expand Up @@ -1015,6 +1016,7 @@ impl Config {
grpc_rate_limit_requests: None,
grpc_rate_limit_window_secs: None,
service_routing: ServiceRoutingConfig::default(),
supervisor_app_listener: crate::app_listener::AppListenerConfig::default(),
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/openshell-core/src/grpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,7 @@ pub struct CachedOpenShellClient {
/// Settings poll result returned by [`CachedOpenShellClient::poll_settings`].
#[derive(Clone, Debug)]
pub struct SettingsPollResult {
pub service_routes: Vec<crate::proto::SandboxServiceRoute>,
pub configuration_instance_id: String,
pub configuration_admitted: bool,
pub configuration_error: String,
Expand Down Expand Up @@ -1890,6 +1891,7 @@ pub struct SettingsPollResult {

fn settings_poll_result(inner: crate::proto::GetSandboxConfigResponse) -> SettingsPollResult {
SettingsPollResult {
service_routes: inner.service_routes,
configuration_instance_id: inner.configuration_instance_id,
configuration_admitted: inner.configuration_admitted,
configuration_error: inner.configuration_error,
Expand Down Expand Up @@ -1919,6 +1921,7 @@ fn settings_poll_result(inner: crate::proto::GetSandboxConfigResponse) -> Settin
impl From<crate::proto::SandboxConfigSnapshot> for SettingsPollResult {
fn from(snapshot: crate::proto::SandboxConfigSnapshot) -> Self {
settings_poll_result(crate::proto::GetSandboxConfigResponse {
service_routes: snapshot.service_routes,
policy: snapshot.policy,
version: snapshot.version,
policy_hash: snapshot.policy_hash,
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//! - Build version metadata

pub mod activity;
pub mod app_listener;
pub mod auth;
pub mod config;
pub mod container_paths;
Expand Down
Loading
Loading