@@ -33,6 +33,12 @@ pub use state::LoaderOverrides;
3333
3434/// On Unix systems, load requirements from this file path, if present.
3535const DEFAULT_REQUIREMENTS_TOML_FILE_UNIX : & str = "/etc/codex/requirements.toml" ;
36+
37+ /// On Unix systems, load default settings from this file path, if present.
38+ /// Note that /etc/codex/ is treated as a "config folder," so subfolders such
39+ /// as skills/ and rules/ will also be honored.
40+ pub const SYSTEM_CONFIG_TOML_FILE_UNIX : & str = "/etc/codex/config.toml" ;
41+
3642const DEFAULT_PROJECT_ROOT_MARKERS : & [ & str ] = & [ ".git" ] ;
3743
3844/// To build up the set of admin-enforced constraints, we build up from multiple
@@ -72,7 +78,7 @@ pub async fn load_config_layers_state(
7278) -> io:: Result < ConfigLayerStack > {
7379 let mut config_requirements_toml = ConfigRequirementsToml :: default ( ) ;
7480
75- // TODO(mbolin ): Support an entry in MDM for config requirements and use it
81+ // TODO(gt ): Support an entry in MDM for config requirements and use it
7682 // with `config_requirements_toml.merge_unset_fields(...)`, if present.
7783
7884 // Honor /etc/codex/requirements.toml.
@@ -95,57 +101,45 @@ pub async fn load_config_layers_state(
95101
96102 let mut layers = Vec :: < ConfigLayerEntry > :: new ( ) ;
97103
98- // TODO(mbolin): Honor managed preferences (macOS only).
99- // TODO(mbolin): Honor /etc/codex/config.toml.
104+ // TODO(gt): Honor managed preferences (macOS only).
105+
106+ // Include an entry for the "system" config folder, loading its config.toml,
107+ // if it exists.
108+ let system_config_toml_file = if cfg ! ( unix) {
109+ Some ( AbsolutePathBuf :: from_absolute_path (
110+ SYSTEM_CONFIG_TOML_FILE_UNIX ,
111+ ) ?)
112+ } else {
113+ // TODO(gt): Determine the path to load on Windows.
114+ None
115+ } ;
116+ if let Some ( system_config_toml_file) = system_config_toml_file {
117+ let system_layer =
118+ load_config_toml_for_required_layer ( & system_config_toml_file, |config_toml| {
119+ ConfigLayerEntry :: new (
120+ ConfigLayerSource :: System {
121+ file : system_config_toml_file. clone ( ) ,
122+ } ,
123+ config_toml,
124+ )
125+ } )
126+ . await ?;
127+ layers. push ( system_layer) ;
128+ }
100129
101130 // Add a layer for $CODEX_HOME/config.toml if it exists. Note if the file
102131 // exists, but is malformed, then this error should be propagated to the
103132 // user.
104133 let user_file = AbsolutePathBuf :: resolve_path_against_base ( CONFIG_TOML_FILE , codex_home) ?;
105- let user_layer = match tokio:: fs:: read_to_string ( & user_file) . await {
106- Ok ( contents) => {
107- let user_config: TomlValue = toml:: from_str ( & contents) . map_err ( |e| {
108- io:: Error :: new (
109- io:: ErrorKind :: InvalidData ,
110- format ! (
111- "Error parsing user config file {}: {e}" ,
112- user_file. as_path( ) . display( ) ,
113- ) ,
114- )
115- } ) ?;
116- let user_config_parent = user_file. as_path ( ) . parent ( ) . ok_or_else ( || {
117- io:: Error :: new (
118- io:: ErrorKind :: InvalidData ,
119- format ! (
120- "User config file {} has no parent directory" ,
121- user_file. as_path( ) . display( )
122- ) ,
123- )
124- } ) ?;
125- let user_config =
126- resolve_relative_paths_in_config_toml ( user_config, user_config_parent) ?;
127- ConfigLayerEntry :: new ( ConfigLayerSource :: User { file : user_file } , user_config)
128- }
129- Err ( e) => {
130- if e. kind ( ) == io:: ErrorKind :: NotFound {
131- // If there is no config.toml file, record an empty entry
132- // for this user layer, as this may still have subfolders
133- // that are significant in the overall ConfigLayerStack.
134- ConfigLayerEntry :: new (
135- ConfigLayerSource :: User { file : user_file } ,
136- TomlValue :: Table ( toml:: map:: Map :: new ( ) ) ,
137- )
138- } else {
139- return Err ( io:: Error :: new (
140- e. kind ( ) ,
141- format ! (
142- "Failed to read user config file {}: {e}" ,
143- user_file. as_path( ) . display( ) ,
144- ) ,
145- ) ) ;
146- }
147- }
148- } ;
134+ let user_layer = load_config_toml_for_required_layer ( & user_file, |config_toml| {
135+ ConfigLayerEntry :: new (
136+ ConfigLayerSource :: User {
137+ file : user_file. clone ( ) ,
138+ } ,
139+ config_toml,
140+ )
141+ } )
142+ . await ?;
149143 layers. push ( user_layer) ;
150144
151145 if let Some ( cwd) = cwd {
@@ -206,6 +200,52 @@ pub async fn load_config_layers_state(
206200 ConfigLayerStack :: new ( layers, config_requirements_toml. try_into ( ) ?)
207201}
208202
203+ /// Attempts to load a config.toml file from `config_toml`.
204+ /// - If the file exists and is valid TOML, passes the parsed `toml::Value` to
205+ /// `create_entry` and returns the resulting layer entry.
206+ /// - If the file does not exist, uses an empty `Table` with `create_entry` and
207+ /// returns the resulting layer entry.
208+ /// - If there is an error reading the file or parsing the TOML, returns an
209+ /// error.
210+ async fn load_config_toml_for_required_layer (
211+ config_toml : impl AsRef < Path > ,
212+ create_entry : impl FnOnce ( TomlValue ) -> ConfigLayerEntry ,
213+ ) -> io:: Result < ConfigLayerEntry > {
214+ let toml_file = config_toml. as_ref ( ) ;
215+ let toml_value = match tokio:: fs:: read_to_string ( toml_file) . await {
216+ Ok ( contents) => {
217+ let config: TomlValue = toml:: from_str ( & contents) . map_err ( |e| {
218+ io:: Error :: new (
219+ io:: ErrorKind :: InvalidData ,
220+ format ! ( "Error parsing config file {}: {e}" , toml_file. display( ) ) ,
221+ )
222+ } ) ?;
223+ let config_parent = toml_file. parent ( ) . ok_or_else ( || {
224+ io:: Error :: new (
225+ io:: ErrorKind :: InvalidData ,
226+ format ! (
227+ "Config file {} has no parent directory" ,
228+ toml_file. display( )
229+ ) ,
230+ )
231+ } ) ?;
232+ resolve_relative_paths_in_config_toml ( config, config_parent)
233+ }
234+ Err ( e) => {
235+ if e. kind ( ) == io:: ErrorKind :: NotFound {
236+ Ok ( TomlValue :: Table ( toml:: map:: Map :: new ( ) ) )
237+ } else {
238+ Err ( io:: Error :: new (
239+ e. kind ( ) ,
240+ format ! ( "Failed to read config file {}: {e}" , toml_file. display( ) ) ,
241+ ) )
242+ }
243+ }
244+ } ?;
245+
246+ Ok ( create_entry ( toml_value) )
247+ }
248+
209249/// If available, apply requirements from `/etc/codex/requirements.toml` to
210250/// `config_requirements_toml` by filling in any unset fields.
211251async fn load_requirements_toml (
0 commit comments