@@ -52,6 +52,10 @@ pub enum ConfigError {
5252 KeyringError ( String ) ,
5353 #[ error( "Failed to lock config file: {0}" ) ]
5454 LockError ( String ) ,
55+ #[ error(
56+ "Global config already initialized with writable path {existing}; cannot use requested writable path {requested}"
57+ ) ]
58+ GlobalConfigPathMismatch { existing : String , requested : String } ,
5559 #[ error( "Secret stored using file-based fallback" ) ]
5660 FallbackToFileStorage ,
5761}
@@ -163,43 +167,7 @@ fn bundled_defaults_path() -> Option<PathBuf> {
163167
164168impl Default for Config {
165169 fn default ( ) -> Self {
166- let config_dir = Paths :: config_dir ( ) ;
167- let user_config_path = config_dir. join ( CONFIG_YAML_NAME ) ;
168-
169- let mut config_paths = vec ! [ system_config_path( ) ] ;
170- if let Some ( defaults) = bundled_defaults_path ( ) {
171- config_paths. insert ( 0 , defaults) ;
172- }
173- config_paths. push ( user_config_path. clone ( ) ) ;
174-
175- let no_secrets_config = Self {
176- config_paths : config_paths. clone ( ) ,
177- secrets : SecretStorage :: File {
178- path : Default :: default ( ) ,
179- } ,
180- guard : Mutex :: new ( ( ) ) ,
181- secrets_cache : Arc :: new ( Mutex :: new ( None ) ) ,
182- } ;
183-
184- let secrets = if env:: var ( "GOOSE_DISABLE_KEYRING" ) . is_ok ( )
185- || no_secrets_config
186- . get_param :: < serde_yaml:: Value > ( "GOOSE_DISABLE_KEYRING" )
187- . is_ok_and ( |v| keyring_disabled_value ( & v) )
188- {
189- SecretStorage :: File {
190- path : config_dir. join ( "secrets.yaml" ) ,
191- }
192- } else {
193- SecretStorage :: Keyring {
194- service : KEYRING_SERVICE . to_string ( ) ,
195- }
196- } ;
197- Self {
198- config_paths,
199- secrets,
200- guard : Mutex :: new ( ( ) ) ,
201- secrets_cache : Arc :: new ( Mutex :: new ( None ) ) ,
202- }
170+ Self :: with_config_dir ( Paths :: config_dir ( ) )
203171 }
204172}
205173
@@ -352,37 +320,77 @@ impl Config {
352320 GLOBAL_CONFIG . get_or_init ( Config :: default)
353321 }
354322
355- /// Initialize the global configuration with a custom config directory.
356- ///
357- /// If the global instance has already been initialized (e.g. by a prior call
358- /// to `global()` or `init_global()`), this returns the existing instance and
359- /// the provided path is ignored. This is safe because Goose runs one ACP
360- /// server per process.
361- pub fn init_global ( config_dir : PathBuf ) -> & ' static Config {
362- GLOBAL_CONFIG . get_or_init ( || {
363- let config_path = config_dir. join ( CONFIG_YAML_NAME ) ;
364-
365- let secrets = if env:: var ( "GOOSE_DISABLE_KEYRING" ) . is_ok ( )
366- || keyring_disabled_in_config ( & config_path)
367- {
368- SecretStorage :: File {
369- path : config_dir. join ( "secrets.yaml" ) ,
370- }
371- } else {
372- SecretStorage :: Keyring {
373- service : KEYRING_SERVICE . to_string ( ) ,
374- }
375- } ;
323+ fn config_paths_for_dir ( config_dir : & Path ) -> Vec < PathBuf > {
324+ let mut config_paths = vec ! [ system_config_path( ) ] ;
325+ if let Some ( defaults) = bundled_defaults_path ( ) {
326+ config_paths. insert ( 0 , defaults) ;
327+ }
328+ config_paths. push ( config_dir. join ( CONFIG_YAML_NAME ) ) ;
329+ config_paths
330+ }
331+
332+ fn with_config_dir ( config_dir : PathBuf ) -> Self {
333+ let config_paths = Self :: config_paths_for_dir ( & config_dir) ;
334+
335+ let no_secrets_config = Self {
336+ config_paths : config_paths. clone ( ) ,
337+ secrets : SecretStorage :: File {
338+ path : Default :: default ( ) ,
339+ } ,
340+ guard : Mutex :: new ( ( ) ) ,
341+ secrets_cache : Arc :: new ( Mutex :: new ( None ) ) ,
342+ } ;
376343
377- Config {
378- config_path,
379- secrets,
380- guard : Mutex :: new ( ( ) ) ,
381- secrets_cache : Arc :: new ( Mutex :: new ( None ) ) ,
344+ let secrets = if env:: var ( "GOOSE_DISABLE_KEYRING" ) . is_ok ( )
345+ || no_secrets_config
346+ . get_param :: < serde_yaml:: Value > ( "GOOSE_DISABLE_KEYRING" )
347+ . is_ok_and ( |v| keyring_disabled_value ( & v) )
348+ {
349+ SecretStorage :: File {
350+ path : config_dir. join ( "secrets.yaml" ) ,
382351 }
352+ } else {
353+ SecretStorage :: Keyring {
354+ service : KEYRING_SERVICE . to_string ( ) ,
355+ }
356+ } ;
357+
358+ Self {
359+ config_paths,
360+ secrets,
361+ guard : Mutex :: new ( ( ) ) ,
362+ secrets_cache : Arc :: new ( Mutex :: new ( None ) ) ,
363+ }
364+ }
365+
366+ fn ensure_writable_path ( & self , requested : & Path ) -> Result < ( ) , ConfigError > {
367+ if self . write_path ( ) == requested {
368+ return Ok ( ( ) ) ;
369+ }
370+
371+ Err ( ConfigError :: GlobalConfigPathMismatch {
372+ existing : self . write_path ( ) . display ( ) . to_string ( ) ,
373+ requested : requested. display ( ) . to_string ( ) ,
383374 } )
384375 }
385376
377+ /// Initialize the global configuration with a custom config directory.
378+ ///
379+ /// If the global instance has already been initialized, this validates that
380+ /// the requested config directory resolves to the same writable config path.
381+ pub fn init_global ( config_dir : PathBuf ) -> Result < & ' static Config , ConfigError > {
382+ let requested = config_dir. join ( CONFIG_YAML_NAME ) ;
383+
384+ if let Some ( config) = GLOBAL_CONFIG . get ( ) {
385+ config. ensure_writable_path ( & requested) ?;
386+ return Ok ( config) ;
387+ }
388+
389+ let config = GLOBAL_CONFIG . get_or_init ( || Self :: with_config_dir ( config_dir) ) ;
390+ config. ensure_writable_path ( & requested) ?;
391+ Ok ( config)
392+ }
393+
386394 /// Create a new configuration instance with custom paths
387395 ///
388396 /// This is primarily useful for testing or for applications that need
0 commit comments