@@ -80,7 +80,8 @@ const DOCKER_NETWORK_DRIVER: &str = "bridge";
8080
8181/// Default image holding the Linux `openshell-sandbox` binary. The gateway
8282/// pulls this image and extracts the binary to a host-side cache when no
83- /// explicit `supervisor_bin` override or local build is available.
83+ /// explicit `supervisor_bin`, configured `supervisor_image`, sibling binary,
84+ /// or local build is available.
8485const DEFAULT_DOCKER_SUPERVISOR_IMAGE_REPO : & str = "ghcr.io/nvidia/openshell/supervisor" ;
8586
8687/// Return the default `ghcr.io/nvidia/openshell/supervisor:<tag>` reference
@@ -156,10 +157,9 @@ pub struct DockerComputeConfig {
156157 /// Optional override for the Linux `openshell-sandbox` binary mounted into containers.
157158 pub supervisor_bin : Option < PathBuf > ,
158159
159- /// Optional override for the image the gateway pulls to extract the
160- /// Linux `openshell-sandbox` binary when no explicit binary path or
161- /// local build is available. Defaults to
162- /// `ghcr.io/nvidia/openshell/supervisor:<gateway-image-tag>`.
160+ /// Optional image used to extract the Linux `openshell-sandbox` binary.
161+ /// Ignored when `supervisor_bin` is set. See `resolve_supervisor_bin` for
162+ /// the full resolution order.
163163 pub supervisor_image : Option < String > ,
164164
165165 /// Host-side CA certificate for Docker sandbox mTLS.
@@ -2978,56 +2978,89 @@ fn normalize_docker_arch(arch: &str) -> String {
29782978 }
29792979}
29802980
2981- pub ( crate ) async fn resolve_supervisor_bin (
2982- docker : & Docker ,
2981+ #[ derive( Debug , Eq , PartialEq ) ]
2982+ enum SupervisorBinSource {
2983+ Binary ( PathBuf ) ,
2984+ Image ( String ) ,
2985+ }
2986+
2987+ fn resolve_supervisor_bin_source (
29832988 docker_config : & DockerComputeConfig ,
2984- daemon_arch : & str ,
2985- ) -> CoreResult < PathBuf > {
2989+ current_exe : Option < & Path > ,
2990+ target_candidates : & [ PathBuf ] ,
2991+ ) -> CoreResult < SupervisorBinSource > {
29862992 // Tier 1: explicit supervisor_bin in [openshell.drivers.docker].
29872993 if let Some ( path) = docker_config. supervisor_bin . clone ( ) {
29882994 let path = canonicalize_existing_file ( & path, "docker supervisor binary" ) ?;
29892995 validate_linux_elf_binary ( & path) ?;
2990- return Ok ( path) ;
2996+ return Ok ( SupervisorBinSource :: Binary ( path) ) ;
2997+ }
2998+
2999+ // Tier 2: explicit supervisor_image in [openshell.drivers.docker].
3000+ // A configured image should be the source of truth even when a local
3001+ // developer build is present under target/.
3002+ if let Some ( image) = docker_config. supervisor_image . clone ( ) {
3003+ return Ok ( SupervisorBinSource :: Image ( image) ) ;
29913004 }
29923005
2993- // Tier 2 : sibling `openshell-sandbox` next to the running gateway
3006+ // Tier 3 : sibling `openshell-sandbox` next to the running gateway
29943007 // (release artifact layout). Linux-only because the sibling must be a
29953008 // Linux ELF to bind-mount into a Linux container.
2996- if cfg ! ( target_os = "linux" ) {
2997- let current_exe = std:: env:: current_exe ( )
2998- . map_err ( |err| Error :: config ( format ! ( "failed to resolve current executable: {err}" ) ) ) ?;
2999- if let Some ( parent) = current_exe. parent ( ) {
3000- let sibling = parent. join ( "openshell-sandbox" ) ;
3001- if sibling. is_file ( ) {
3002- let path = canonicalize_existing_file ( & sibling, "docker supervisor binary" ) ?;
3003- if validate_linux_elf_binary ( & path) . is_ok ( ) {
3004- return Ok ( path) ;
3005- }
3009+ if cfg ! ( target_os = "linux" )
3010+ && let Some ( current_exe) = current_exe
3011+ && let Some ( parent) = current_exe. parent ( )
3012+ {
3013+ let sibling = parent. join ( "openshell-sandbox" ) ;
3014+ if sibling. is_file ( ) {
3015+ let path = canonicalize_existing_file ( & sibling, "docker supervisor binary" ) ?;
3016+ if validate_linux_elf_binary ( & path) . is_ok ( ) {
3017+ return Ok ( SupervisorBinSource :: Binary ( path) ) ;
30063018 }
30073019 }
30083020 }
30093021
3010- // Tier 3: local cargo target build (developer workflow). Preferred
3011- // over a registry pull when available because it matches whatever the
3012- // developer just built.
3013- let target_candidates = linux_supervisor_candidates ( daemon_arch) ;
3014- for candidate in & target_candidates {
3022+ // Tier 4: local cargo target build (developer workflow). Preferred
3023+ // over the default registry image when available because it matches
3024+ // whatever the developer just built.
3025+ for candidate in target_candidates {
30153026 if candidate. is_file ( ) {
30163027 let path = canonicalize_existing_file ( candidate, "docker supervisor binary" ) ?;
30173028 if validate_linux_elf_binary ( & path) . is_ok ( ) {
3018- return Ok ( path) ;
3029+ return Ok ( SupervisorBinSource :: Binary ( path) ) ;
30193030 }
30203031 }
30213032 }
30223033
3023- // Tier 4: pull the supervisor image from a registry and extract the
3024- // binary to a host-side cache keyed by image content digest. This is
3025- // the default path for released gateway binaries.
3026- let image = docker_config
3027- . supervisor_image
3028- . clone ( )
3029- . unwrap_or_else ( default_docker_supervisor_image) ;
3030- extract_supervisor_bin_from_image ( docker, & image) . await
3034+ // Tier 5: pull the release-matched default supervisor image and extract
3035+ // the binary to a host-side cache keyed by image content digest.
3036+ Ok ( SupervisorBinSource :: Image ( default_docker_supervisor_image ( ) ) )
3037+ }
3038+
3039+ pub ( crate ) async fn resolve_supervisor_bin (
3040+ docker : & Docker ,
3041+ docker_config : & DockerComputeConfig ,
3042+ daemon_arch : & str ,
3043+ ) -> CoreResult < PathBuf > {
3044+ let current_exe =
3045+ if cfg ! ( target_os = "linux" )
3046+ && docker_config. supervisor_bin . is_none ( )
3047+ && docker_config. supervisor_image . is_none ( )
3048+ {
3049+ Some ( std:: env:: current_exe ( ) . map_err ( |err| {
3050+ Error :: config ( format ! ( "failed to resolve current executable: {err}" ) )
3051+ } ) ?)
3052+ } else {
3053+ None
3054+ } ;
3055+ let target_candidates = linux_supervisor_candidates ( daemon_arch) ;
3056+
3057+ match resolve_supervisor_bin_source ( docker_config, current_exe. as_deref ( ) , & target_candidates) ?
3058+ {
3059+ SupervisorBinSource :: Binary ( path) => Ok ( path) ,
3060+ SupervisorBinSource :: Image ( image) => {
3061+ extract_supervisor_bin_from_image ( docker, & image) . await
3062+ }
3063+ }
30313064}
30323065
30333066fn linux_supervisor_candidates ( daemon_arch : & str ) -> Vec < PathBuf > {
0 commit comments