doum_cli/system/
config.rs1use crate::system::error::{DoumError, DoumResult};
2use crate::system::paths::get_config_path;
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::PathBuf;
6
7#[cfg(unix)]
8use std::os::unix::fs::PermissionsExt;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Config {
13 pub llm: LLMConfig,
14 pub context: ContextConfig,
15 pub logging: LoggingConfig,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct LLMConfig {
21 pub provider: String,
22 pub model: String,
23 pub timeout: u64,
24 pub max_retries: u32,
25 pub use_thinking: bool,
26 pub use_web_search: bool,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ContextConfig {
32 pub max_lines: usize,
33 pub max_size_kb: usize,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct LoggingConfig {
39 pub enabled: bool,
40 pub level: String,
41}
42
43fn ensure_config() -> DoumResult<PathBuf> {
45 let config_path = get_config_path()?;
46
47 if let Some(parent) = config_path.parent()
48 && !parent.exists()
49 {
50 fs::create_dir_all(parent)
51 .map_err(|e| DoumError::Config(format!("설정 디렉터리 생성 실패: {}", e)))?;
52
53 #[cfg(unix)]
55 {
56 let metadata = fs::metadata(parent)
57 .map_err(|e| DoumError::Config(format!("디렉터리 메타데이터 읽기 실패: {}", e)))?;
58 let mut permissions = metadata.permissions();
59 permissions.set_mode(0o700);
60 fs::set_permissions(parent, permissions)
61 .map_err(|e| DoumError::Config(format!("디렉터리 권한 설정 실패: {}", e)))?;
62 }
63 }
64
65 Ok(config_path)
66}
67
68pub fn load_config() -> DoumResult<Config> {
70 let config_path = ensure_config()?;
71
72 if config_path.exists() {
73 validate_config(&config_path)?;
75
76 let content = fs::read_to_string(&config_path)
78 .map_err(|e| DoumError::Config(format!("설정 파일 읽기 실패: {}", e)))?;
79
80 let config: Config = toml::from_str(&content)
82 .map_err(|e| DoumError::Config(format!("설정 파일 파싱 실패: {}", e)))?;
83
84 Ok(config)
85 } else {
86 let config = load_default_config()?;
88 save_config(&config)?;
89 Ok(config)
90 }
91}
92
93pub fn load_default_config() -> DoumResult<Config> {
95 Ok(Config {
96 llm: LLMConfig {
97 provider: "openai".to_string(),
98 model: "gpt-5".to_string(),
99 timeout: 30,
100 max_retries: 3,
101 use_thinking: false,
102 use_web_search: true,
103 },
104 context: ContextConfig {
105 max_lines: 100,
106 max_size_kb: 50,
107 },
108 logging: LoggingConfig {
109 enabled: false,
110 level: "info".to_string(),
111 },
112 })
113}
114
115pub fn save_config(config: &Config) -> DoumResult<()> {
117 let config_path = ensure_config()?;
118
119 let content = toml::to_string_pretty(config)
121 .map_err(|e| DoumError::Config(format!("설정 직렬화 실패: {}", e)))?;
122
123 fs::write(&config_path, content)
125 .map_err(|e| DoumError::Config(format!("설정 파일 쓰기 실패: {}", e)))?;
126
127 #[cfg(windows)]
129 {
130 }
133
134 #[cfg(unix)]
136 {
137 let metadata = fs::metadata(&config_path)
138 .map_err(|e| DoumError::Config(format!("파일 메타데이터 읽기 실패: {}", e)))?;
139 let mut permissions = metadata.permissions();
140 permissions.set_mode(0o600);
141 fs::set_permissions(&config_path, permissions)
142 .map_err(|e| DoumError::Config(format!("파일 권한 설정 실패: {}", e)))?;
143 }
144
145 Ok(())
146}
147
148fn validate_config(path: &PathBuf) -> DoumResult<()> {
150 #[cfg(windows)]
151 {
152 let _ = path; }
156
157 #[cfg(unix)]
158 {
159 let metadata = fs::metadata(path)
160 .map_err(|e| DoumError::Config(format!("파일 메타데이터 읽기 실패: {}", e)))?;
161 let permissions = metadata.permissions();
162 let mode = permissions.mode() & 0o777;
163
164 if mode != 0o600 && mode != 0o400 {
166 return Err(DoumError::InvalidConfig(format!(
167 "설정 파일 권한이 안전하지 않습니다 (현재: {:o}, 필요: 600 또는 400). \
168 다음 명령으로 수정하세요: chmod 600 {}",
169 mode,
170 path.display()
171 )));
172 }
173 }
174
175 Ok(())
176}