doum_cli/system/
config.rs

1use 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/// 전체 설정 구조
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Config {
13    pub llm: LLMConfig,
14    pub context: ContextConfig,
15    pub logging: LoggingConfig,
16}
17
18/// LLM 관련 설정
19#[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/// 컨텍스트 수집 설정
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ContextConfig {
32    pub max_lines: usize,
33    pub max_size_kb: usize,
34}
35
36/// 로깅 설정
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct LoggingConfig {
39    pub enabled: bool,
40    pub level: String,
41}
42
43/// 설정 디렉터리 생성 및 권한 설정
44fn 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        // Unix 시스템에서 디렉터리 권한 설정 (700)
54        #[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
68/// 설정 파일 로드 (없으면 기본값으로 생성)
69pub fn load_config() -> DoumResult<Config> {
70    let config_path = ensure_config()?;
71
72    if config_path.exists() {
73        // 권한 검증
74        validate_config(&config_path)?;
75
76        // 설정 파일 읽기
77        let content = fs::read_to_string(&config_path)
78            .map_err(|e| DoumError::Config(format!("설정 파일 읽기 실패: {}", e)))?;
79
80        // TOML 파싱
81        let config: Config = toml::from_str(&content)
82            .map_err(|e| DoumError::Config(format!("설정 파일 파싱 실패: {}", e)))?;
83
84        Ok(config)
85    } else {
86        // 임베드된 기본 config.toml을 로드하여 저장
87        let config = load_default_config()?;
88        save_config(&config)?;
89        Ok(config)
90    }
91}
92
93/// 임베드된 기본 config.toml 로드
94pub 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
115/// 설정 파일 저장
116pub fn save_config(config: &Config) -> DoumResult<()> {
117    let config_path = ensure_config()?;
118
119    // TOML로 직렬화
120    let content = toml::to_string_pretty(config)
121        .map_err(|e| DoumError::Config(format!("설정 직렬화 실패: {}", e)))?;
122
123    // 파일 쓰기
124    fs::write(&config_path, content)
125        .map_err(|e| DoumError::Config(format!("설정 파일 쓰기 실패: {}", e)))?;
126
127    // Windows에서는 기본 ACL 사용
128    #[cfg(windows)]
129    {
130        // Windows의 경우 기본 ACL이 이미 적절하게 설정되어 있음
131        // 추가 보안이 필요한 경우 winapi를 사용하여 ACL 설정 가능
132    }
133
134    // Unix에서 파일 권한 설정 (600)
135    #[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
148/// 설정 파일 권한 검증
149fn validate_config(path: &PathBuf) -> DoumResult<()> {
150    #[cfg(windows)]
151    {
152        // Windows에서는 기본적으로 안전하다고 가정
153        // 추가 검증이 필요한 경우 구현 가능
154        let _ = path; // unused warning 방지
155    }
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        // 600 또는 400 권한만 허용
165        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}