@@ -15,6 +15,7 @@ use hyper::{Method, Request, Response};
1515use hyper_util:: rt:: { TokioExecutor , TokioIo } ;
1616use hyper_util:: server:: conn:: auto:: Builder as AutoBuilder ;
1717use orion_conf:: { ToStructError , UvsConfFrom } ;
18+ use orion_variate:: { EnvDict , EnvEvaluable } ;
1819use rustls:: pki_types:: pem:: PemObject ;
1920use rustls:: pki_types:: { CertificateDer , PrivateKeyDer } ;
2021use rustls:: ServerConfig ;
@@ -125,6 +126,13 @@ struct HostConfigFile {
125126 admin_api : AdminApiConfigFile ,
126127}
127128
129+ impl EnvEvaluable < HostConfigFile > for HostConfigFile {
130+ fn env_eval ( mut self , dict : & EnvDict ) -> HostConfigFile {
131+ self . admin_api = self . admin_api . env_eval ( dict) ;
132+ self
133+ }
134+ }
135+
128136#[ derive( Debug , Deserialize , Clone ) ]
129137struct AdminApiConfigFile {
130138 #[ serde( default ) ]
@@ -154,6 +162,15 @@ impl Default for AdminApiConfigFile {
154162 }
155163}
156164
165+ impl EnvEvaluable < AdminApiConfigFile > for AdminApiConfigFile {
166+ fn env_eval ( mut self , dict : & EnvDict ) -> AdminApiConfigFile {
167+ self . bind = self . bind . env_eval ( dict) ;
168+ self . tls = self . tls . env_eval ( dict) ;
169+ self . auth = self . auth . env_eval ( dict) ;
170+ self
171+ }
172+ }
173+
157174#[ derive( Debug , Deserialize , Clone , Default ) ]
158175struct AdminTlsConfigFile {
159176 #[ serde( default ) ]
@@ -164,6 +181,14 @@ struct AdminTlsConfigFile {
164181 key_file : String ,
165182}
166183
184+ impl EnvEvaluable < AdminTlsConfigFile > for AdminTlsConfigFile {
185+ fn env_eval ( mut self , dict : & EnvDict ) -> AdminTlsConfigFile {
186+ self . cert_file = self . cert_file . env_eval ( dict) ;
187+ self . key_file = self . key_file . env_eval ( dict) ;
188+ self
189+ }
190+ }
191+
167192#[ derive( Debug , Deserialize , Clone ) ]
168193struct AdminAuthConfigFile {
169194 #[ serde( default = "default_auth_mode" ) ]
@@ -181,6 +206,14 @@ impl Default for AdminAuthConfigFile {
181206 }
182207}
183208
209+ impl EnvEvaluable < AdminAuthConfigFile > for AdminAuthConfigFile {
210+ fn env_eval ( mut self , dict : & EnvDict ) -> AdminAuthConfigFile {
211+ self . mode = self . mode . env_eval ( dict) ;
212+ self . token_file = self . token_file . env_eval ( dict) ;
213+ self
214+ }
215+ }
216+
184217struct ResolvedAdminApiConfig {
185218 bind : SocketAddr ,
186219 request_timeout : Duration ,
@@ -200,8 +233,10 @@ fn load_config(work_root: &Path) -> RunResult<Option<ResolvedAdminApiConfig>> {
200233 let conf_path = work_root. join ( ADMIN_CONF_PATH ) ;
201234 let raw = fs:: read_to_string ( & conf_path)
202235 . map_err ( |e| conf_err ( format ! ( "read {} failed: {}" , conf_path. display( ) , e) ) ) ?;
236+ let dict = crate :: load_sec_dict ( ) . unwrap_or_else ( |_| EnvDict :: new ( ) ) ;
203237 let parsed: HostConfigFile = toml:: from_str ( & raw )
204238 . map_err ( |e| conf_err ( format ! ( "parse {} failed: {}" , conf_path. display( ) , e) ) ) ?;
239+ let parsed = parsed. env_eval ( & dict) ;
205240 if !parsed. admin_api . enabled {
206241 return Ok ( None ) ;
207242 }
@@ -273,8 +308,10 @@ pub fn resolve_client_profile(work_root: &Path) -> RunResult<Option<AdminApiClie
273308 let conf_path = work_root. join ( ADMIN_CONF_PATH ) ;
274309 let raw = fs:: read_to_string ( & conf_path)
275310 . map_err ( |e| conf_err ( format ! ( "read {} failed: {}" , conf_path. display( ) , e) ) ) ?;
311+ let dict = crate :: load_sec_dict ( ) . unwrap_or_else ( |_| EnvDict :: new ( ) ) ;
276312 let parsed: HostConfigFile = toml:: from_str ( & raw )
277313 . map_err ( |e| conf_err ( format ! ( "parse {} failed: {}" , conf_path. display( ) , e) ) ) ?;
314+ let parsed = parsed. env_eval ( & dict) ;
278315 if !parsed. admin_api . enabled {
279316 return Ok ( None ) ;
280317 }
@@ -1351,7 +1388,7 @@ fn default_wait() -> bool {
13511388mod tests {
13521389 use super :: * ;
13531390 use std:: os:: unix:: fs:: PermissionsExt ;
1354- use std:: sync:: OnceLock ;
1391+ use std:: sync:: { Mutex , OnceLock } ;
13551392
13561393 use reqwest:: Client ;
13571394 use tempfile:: tempdir;
@@ -1436,12 +1473,23 @@ token_file = "{token_file}"
14361473 }
14371474
14381475 fn shared_control_handle ( ) -> RuntimeControlHandle {
1476+ fn shared_control_work_root ( ) -> & ' static PathBuf {
1477+ static WORK_ROOT : OnceLock < PathBuf > = OnceLock :: new ( ) ;
1478+ WORK_ROOT . get_or_init ( || {
1479+ let root = std:: env:: temp_dir ( ) . join ( "warp-parse-admin-api-tests" ) ;
1480+ let conf_dir = root. join ( "conf" ) ;
1481+ fs:: create_dir_all ( & conf_dir) . expect ( "create shared control conf dir" ) ;
1482+ fs:: write ( conf_dir. join ( "wparse.toml" ) , BASE_TEST_WPARSE_CONF )
1483+ . expect ( "write shared control config" ) ;
1484+ root
1485+ } )
1486+ }
1487+
14391488 static HANDLE : OnceLock < RuntimeControlHandle > = OnceLock :: new ( ) ;
14401489 HANDLE
14411490 . get_or_init ( || {
1442- let work_root = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "tests" ) ;
14431491 let args = ParseArgs {
1444- work_root : Some ( work_root . to_string_lossy ( ) . to_string ( ) ) ,
1492+ work_root : Some ( shared_control_work_root ( ) . to_string_lossy ( ) . to_string ( ) ) ,
14451493 ..Default :: default ( )
14461494 } ;
14471495 WpApp :: try_from ( args, orion_variate:: EnvDict :: default ( ) )
@@ -1451,6 +1499,38 @@ token_file = "{token_file}"
14511499 . clone ( )
14521500 }
14531501
1502+ fn home_lock ( ) -> & ' static Mutex < ( ) > {
1503+ static LOCK : OnceLock < Mutex < ( ) > > = OnceLock :: new ( ) ;
1504+ LOCK . get_or_init ( || Mutex :: new ( ( ) ) )
1505+ }
1506+
1507+ struct HomeOverride {
1508+ original : Option < std:: ffi:: OsString > ,
1509+ }
1510+
1511+ impl HomeOverride {
1512+ fn new ( home : & Path ) -> Self {
1513+ let original = std:: env:: var_os ( "HOME" ) ;
1514+ unsafe {
1515+ std:: env:: set_var ( "HOME" , home) ;
1516+ }
1517+ Self { original }
1518+ }
1519+ }
1520+
1521+ impl Drop for HomeOverride {
1522+ fn drop ( & mut self ) {
1523+ match & self . original {
1524+ Some ( home) => unsafe {
1525+ std:: env:: set_var ( "HOME" , home) ;
1526+ } ,
1527+ None => unsafe {
1528+ std:: env:: remove_var ( "HOME" ) ;
1529+ } ,
1530+ }
1531+ }
1532+ }
1533+
14541534 #[ tokio:: test]
14551535 async fn admin_api_requires_safe_token_permissions ( ) {
14561536 let temp = tempdir ( ) . expect ( "tempdir" ) ;
@@ -1467,6 +1547,31 @@ token_file = "{token_file}"
14671547 ) ;
14681548 }
14691549
1550+ #[ test]
1551+ fn admin_api_expands_token_file_env_in_daemon_path ( ) {
1552+ let _guard = home_lock ( ) . lock ( ) . expect ( "lock HOME override" ) ;
1553+ let temp = tempdir ( ) . expect ( "tempdir" ) ;
1554+ let home = temp. path ( ) . join ( "fake-home" ) ;
1555+ fs:: create_dir_all ( & home) . expect ( "create fake home" ) ;
1556+ let _home = HomeOverride :: new ( & home) ;
1557+
1558+ write_test_work_root (
1559+ temp. path ( ) ,
1560+ "127.0.0.1:0" ,
1561+ "${HOME}/.warp_parse/admin_api.token" ,
1562+ ) ;
1563+ write_token ( temp. path ( ) , "fake-home/.warp_parse/admin_api.token" , 0o600 ) ;
1564+
1565+ let loaded = load_config ( temp. path ( ) )
1566+ . expect ( "load admin api config with env token path" )
1567+ . expect ( "enabled" ) ;
1568+ assert_eq ! (
1569+ loaded. bind,
1570+ "127.0.0.1:0" . parse( ) . expect( "parse socket addr" )
1571+ ) ;
1572+ assert_eq ! ( loaded. bearer_token, "test-token" ) ;
1573+ }
1574+
14701575 #[ tokio:: test]
14711576 async fn admin_api_status_requires_bearer_and_reports_runtime_state ( ) {
14721577 let temp = tempdir ( ) . expect ( "tempdir" ) ;
0 commit comments