1use crate::error::Result;
4use crate::paths::Paths;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct Config {
10 #[serde(default)]
11 pub index: IndexConfig,
12 #[serde(default)]
13 pub install: InstallConfig,
14 #[serde(default)]
15 pub cache: CacheConfig,
16 #[serde(default)]
17 pub analytics: AnalyticsConfig,
18 #[serde(default)]
19 pub security: SecurityConfig,
20 #[serde(default)]
21 pub sync: SyncConfig,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct IndexConfig {
26 #[serde(default = "default_base_url")]
28 pub base_url: String,
29 #[serde(default = "default_true")]
31 pub auto_update: bool,
32 #[serde(default = "default_update_interval")]
34 pub update_interval: u64,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct InstallConfig {
39 #[serde(default = "default_cellar")]
41 pub cellar: String,
42 #[serde(default = "default_prefix")]
44 pub prefix: String,
45 #[serde(default = "default_parallel")]
47 pub parallel_downloads: u32,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct CacheConfig {
52 #[serde(default = "default_max_size")]
54 pub max_size: String,
55 #[serde(default = "default_formula_ttl")]
57 pub formula_ttl: u64,
58 #[serde(default = "default_download_ttl")]
60 pub download_ttl: u64,
61}
62
63#[derive(Debug, Clone, Default, Serialize, Deserialize)]
64pub struct AnalyticsConfig {
65 #[serde(default)]
67 pub enabled: bool,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct SecurityConfig {
72 #[serde(default = "default_require_signature")]
75 pub require_signature: bool,
76 #[serde(default = "default_allow_unsigned")]
79 pub allow_unsigned: bool,
80 #[serde(default = "default_max_signature_age")]
83 pub max_signature_age: u64,
84 #[serde(default)]
87 pub additional_trusted_keys: Vec<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct SyncConfig {
92 #[serde(default = "default_true")]
94 pub sync_on_update: bool,
95}
96
97impl Default for SyncConfig {
98 fn default() -> Self {
99 Self {
100 sync_on_update: true,
101 }
102 }
103}
104
105fn default_base_url() -> String {
107 "https://raw.githubusercontent.com/neul-labs/stout-index/main".to_string()
108}
109
110fn default_true() -> bool {
111 true
112}
113
114fn default_update_interval() -> u64 {
115 1800 }
117
118fn default_cellar() -> String {
119 format!("{}/Cellar", default_prefix())
120}
121
122fn default_prefix() -> String {
123 #[cfg(target_os = "macos")]
125 {
126 #[cfg(target_arch = "aarch64")]
127 return "/opt/homebrew".to_string();
128 #[cfg(not(target_arch = "aarch64"))]
129 return "/usr/local".to_string();
130 }
131
132 #[cfg(target_os = "linux")]
133 {
134 if let Some(home) = dirs::home_dir() {
136 return home
137 .join(".local")
138 .join("stout")
139 .to_string_lossy()
140 .to_string();
141 }
142 "/home/linuxbrew/.linuxbrew".to_string()
143 }
144
145 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
146 {
147 "/opt/homebrew".to_string()
148 }
149}
150
151fn default_parallel() -> u32 {
152 4
153}
154
155fn default_max_size() -> String {
156 "2GB".to_string()
157}
158
159fn default_formula_ttl() -> u64 {
160 86400 }
162
163fn default_download_ttl() -> u64 {
164 604800 }
166
167fn default_require_signature() -> bool {
168 false
170}
171
172fn default_allow_unsigned() -> bool {
173 true
175}
176
177fn default_max_signature_age() -> u64 {
178 604800 }
180
181impl Default for IndexConfig {
182 fn default() -> Self {
183 Self {
184 base_url: default_base_url(),
185 auto_update: default_true(),
186 update_interval: default_update_interval(),
187 }
188 }
189}
190
191impl Default for InstallConfig {
192 fn default() -> Self {
193 Self {
194 cellar: default_cellar(),
195 prefix: default_prefix(),
196 parallel_downloads: default_parallel(),
197 }
198 }
199}
200
201impl Default for CacheConfig {
202 fn default() -> Self {
203 Self {
204 max_size: default_max_size(),
205 formula_ttl: default_formula_ttl(),
206 download_ttl: default_download_ttl(),
207 }
208 }
209}
210
211impl Default for SecurityConfig {
212 fn default() -> Self {
213 Self {
214 require_signature: default_require_signature(),
215 allow_unsigned: default_allow_unsigned(),
216 max_signature_age: default_max_signature_age(),
217 additional_trusted_keys: vec![],
218 }
219 }
220}
221
222impl SecurityConfig {
223 pub fn to_security_policy(&self) -> stout_index::SecurityPolicy {
225 stout_index::SecurityPolicy {
226 require_signature: self.require_signature,
227 max_signature_age: self.max_signature_age,
228 additional_keys: self.additional_trusted_keys.clone(),
229 allow_unsigned: self.allow_unsigned,
230 }
231 }
232}
233
234impl Config {
235 pub fn load(paths: &Paths) -> Result<Self> {
237 let config_path = paths.config_file();
238
239 if config_path.exists() {
240 let contents = std::fs::read_to_string(&config_path)?;
241 let config: Config = toml::from_str(&contents)?;
242 Ok(config)
243 } else {
244 Ok(Self::default())
245 }
246 }
247
248 pub fn save(&self, paths: &Paths) -> Result<()> {
250 let config_path = paths.config_file();
251
252 if let Some(parent) = config_path.parent() {
253 std::fs::create_dir_all(parent)?;
254 }
255
256 let contents = toml::to_string_pretty(self)?;
257 std::fs::write(&config_path, contents)?;
258 Ok(())
259 }
260}