doum_cli/system/
config.rs

1use crate::llm::Provider;
2use crate::system::paths::get_config_path;
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::PathBuf;
7
8#[cfg(unix)]
9use std::os::unix::fs::PermissionsExt;
10
11/// Entire application configuration
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Config {
14    pub llm: LLMConfig,
15    pub context: ContextConfig,
16    pub logging: LoggingConfig,
17}
18
19/// Configuration for LLM API
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct LLMConfig {
22    pub provider: String,
23    pub model: String,
24    pub timeout: u64,
25    pub max_retries: u32,
26    pub use_thinking: bool,
27    pub use_web_search: bool,
28}
29
30/// Context management settings
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ContextConfig {
33    pub max_lines: usize,
34    pub max_size_kb: usize,
35}
36
37/// Configuration for logging
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct LoggingConfig {
40    pub enabled: bool,
41    pub level: String,
42}
43
44/// Ensure configuration directory and return config file path
45fn ensure_config() -> Result<PathBuf> {
46    let config_path = get_config_path()?;
47
48    if let Some(parent) = config_path.parent()
49        && !parent.exists()
50    {
51        fs::create_dir_all(parent).context("Failed to create config directory")?;
52
53        // Set directory permissions to 700 on Unix
54        #[cfg(unix)]
55        {
56            let metadata = fs::metadata(parent).context("Failed to read directory metadata")?;
57            let mut permissions = metadata.permissions();
58            permissions.set_mode(0o700);
59            fs::set_permissions(parent, permissions)
60                .context("Failed to set directory permissions")?;
61        }
62    }
63
64    Ok(config_path)
65}
66
67/// load configuration from file or create default
68pub fn load_config() -> Result<Config> {
69    let config_path = ensure_config()?;
70
71    if config_path.exists() {
72        // Read and parse existing config file
73        let content = fs::read_to_string(&config_path).context("Failed to read config file")?;
74        let config: Config = toml::from_str(&content).context("Failed to parse config file")?;
75        Ok(config)
76    } else {
77        // If config file doesn't exist, create default
78        let config = load_default_config()?;
79        save_config(&config)?;
80        Ok(config)
81    }
82}
83
84/// Load default configuration
85pub fn load_default_config() -> Result<Config> {
86    Ok(Config {
87        llm: LLMConfig {
88            provider: Provider::OpenAI.as_str().to_string(),
89            model: "gpt-5".to_string(),
90            timeout: 30,
91            max_retries: 3,
92            use_thinking: false,
93            use_web_search: true,
94        },
95        context: ContextConfig {
96            max_lines: 100,
97            max_size_kb: 50,
98        },
99        logging: LoggingConfig {
100            enabled: false,
101            level: "info".to_string(),
102        },
103    })
104}
105
106/// Save configuration to file with secure permissions
107pub fn save_config(config: &Config) -> Result<()> {
108    // Get config path and write file
109    let config_path = ensure_config()?;
110    let content = toml::to_string_pretty(config).context("Failed to serialize config")?;
111    fs::write(&config_path, content).context("Failed to write config file")?;
112
113    // if Windows, set ACLs for the user only
114    #[cfg(windows)]
115    {
116        // In Windows, basic file permissions are usually sufficient
117        // Additional ACL settings can be implemented if needed
118    }
119
120    // if Unix, set file permissions to 600
121    #[cfg(unix)]
122    {
123        let metadata = fs::metadata(&config_path).context("File metadata read failed")?;
124        let mut permissions = metadata.permissions();
125        permissions.set_mode(0o600);
126        fs::set_permissions(&config_path, permissions).context("Failed to set file permissions")?;
127    }
128
129    Ok(())
130}