1use std::path::Path;
3use std::{env, fs};
4
5use config::{Config, ConfigError, File, FileFormat};
6use log::warn;
7use serde::{Deserialize, Serialize};
8use tokio::sync::RwLock;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Website {
13 pub name: String,
15}
16
17impl Default for Website {
18 fn default() -> Self {
19 Self {
20 name: "Torrust".to_string(),
21 }
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum TrackerMode {
29 Public,
32 Private,
34 Whitelisted,
36 PrivateWhitelisted,
38}
39
40impl Default for TrackerMode {
41 fn default() -> Self {
42 Self::Public
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Tracker {
49 pub url: String,
51 pub mode: TrackerMode,
55 pub api_url: String,
57 pub token: String,
59 pub token_valid_seconds: u64,
61}
62
63impl Default for Tracker {
64 fn default() -> Self {
65 Self {
66 url: "udp://localhost:6969".to_string(),
67 mode: TrackerMode::default(),
68 api_url: "http://localhost:1212".to_string(),
69 token: "MyAccessToken".to_string(),
70 token_valid_seconds: 7_257_600,
71 }
72 }
73}
74
75pub const FREE_PORT: u16 = 0;
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Network {
83 pub port: u16,
85 pub base_url: Option<String>,
88}
89
90impl Default for Network {
91 fn default() -> Self {
92 Self {
93 port: 3001,
94 base_url: None,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum EmailOnSignup {
102 Required,
104 Optional,
106 None, }
109
110impl Default for EmailOnSignup {
111 fn default() -> Self {
112 Self::Optional
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct Auth {
119 pub email_on_signup: EmailOnSignup,
121 pub min_password_length: usize,
123 pub max_password_length: usize,
125 pub secret_key: String,
127}
128
129impl Default for Auth {
130 fn default() -> Self {
131 Self {
132 email_on_signup: EmailOnSignup::default(),
133 min_password_length: 6,
134 max_password_length: 64,
135 secret_key: "MaxVerstappenWC2021".to_string(),
136 }
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct Database {
143 pub connect_url: String,
145}
146
147impl Default for Database {
148 fn default() -> Self {
149 Self {
150 connect_url: "sqlite://data.db?mode=rwc".to_string(),
151 }
152 }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct Mail {
158 pub email_verification_enabled: bool,
160 pub from: String,
162 pub reply_to: String,
164 pub username: String,
166 pub password: String,
168 pub server: String,
170 pub port: u16,
172}
173
174impl Default for Mail {
175 fn default() -> Self {
176 Self {
177 email_verification_enabled: false,
178 from: "example@email.com".to_string(),
179 reply_to: "noreply@email.com".to_string(),
180 username: String::default(),
181 password: String::default(),
182 server: String::default(),
183 port: 25,
184 }
185 }
186}
187
188#[allow(clippy::module_name_repetitions)]
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct ImageCache {
198 pub max_request_timeout_ms: u64,
200 pub capacity: usize,
202 pub entry_size_limit: usize,
204 pub user_quota_period_seconds: u64,
207 pub user_quota_bytes: usize,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct Api {
215 pub default_torrent_page_size: u8,
217 pub max_torrent_page_size: u8,
219}
220
221impl Default for Api {
222 fn default() -> Self {
223 Self {
224 default_torrent_page_size: 10,
225 max_torrent_page_size: 30,
226 }
227 }
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct TrackerStatisticsImporter {
233 pub torrent_info_update_interval: u64,
235}
236
237impl Default for TrackerStatisticsImporter {
238 fn default() -> Self {
239 Self {
240 torrent_info_update_interval: 3600,
241 }
242 }
243}
244
245impl Default for ImageCache {
246 fn default() -> Self {
247 Self {
248 max_request_timeout_ms: 1000,
249 capacity: 128_000_000,
250 entry_size_limit: 4_000_000,
251 user_quota_period_seconds: 3600,
252 user_quota_bytes: 64_000_000,
253 }
254 }
255}
256
257#[derive(Debug, Default, Clone, Serialize, Deserialize)]
259pub struct TorrustBackend {
260 pub log_level: Option<String>,
263 pub website: Website,
265 pub tracker: Tracker,
267 pub net: Network,
269 pub auth: Auth,
271 pub database: Database,
273 pub mail: Mail,
275 pub image_cache: ImageCache,
277 pub api: Api,
279 pub tracker_statistics_importer: TrackerStatisticsImporter,
281}
282
283#[derive(Debug)]
285pub struct Configuration {
286 pub settings: RwLock<TorrustBackend>,
288 pub config_path: Option<String>,
291}
292
293impl Default for Configuration {
294 fn default() -> Configuration {
295 Configuration {
296 settings: RwLock::new(TorrustBackend::default()),
297 config_path: None,
298 }
299 }
300}
301
302impl Configuration {
303 pub async fn load_from_file(config_path: &str) -> Result<Configuration, ConfigError> {
310 let config_builder = Config::builder();
311
312 #[allow(unused_assignments)]
313 let mut config = Config::default();
314
315 if Path::new(config_path).exists() {
316 config = config_builder.add_source(File::with_name(config_path)).build()?;
317 } else {
318 warn!("No config file found. Creating default config file ...");
319
320 let config = Configuration::default();
321 let _ = config.save_to_file(config_path).await;
322
323 return Err(ConfigError::Message(format!(
324 "No config file found. Created default config file in {config_path}. Edit the file and start the application."
325 )));
326 }
327
328 let torrust_config: TorrustBackend = match config.try_deserialize() {
329 Ok(data) => Ok(data),
330 Err(e) => Err(ConfigError::Message(format!("Errors while processing config: {e}."))),
331 }?;
332
333 Ok(Configuration {
334 settings: RwLock::new(torrust_config),
335 config_path: Some(config_path.to_string()),
336 })
337 }
338
339 pub fn load_from_env_var(config_env_var_name: &str) -> Result<Configuration, ConfigError> {
347 match env::var(config_env_var_name) {
348 Ok(config_toml) => {
349 let config_builder = Config::builder()
350 .add_source(File::from_str(&config_toml, FileFormat::Toml))
351 .build()?;
352 let torrust_config: TorrustBackend = config_builder.try_deserialize()?;
353 Ok(Configuration {
354 settings: RwLock::new(torrust_config),
355 config_path: None,
356 })
357 }
358 Err(_) => Err(ConfigError::Message(
359 "Unable to load configuration from the configuration environment variable.".to_string(),
360 )),
361 }
362 }
363
364 pub async fn save_to_file(&self, config_path: &str) {
370 let settings = self.settings.read().await;
371
372 let toml_string = toml::to_string(&*settings).expect("Could not encode TOML value");
373
374 drop(settings);
375
376 fs::write(config_path, toml_string).expect("Could not write to file!");
377 }
378
379 pub async fn get_all(&self) -> TorrustBackend {
380 let settings_lock = self.settings.read().await;
381
382 settings_lock.clone()
383 }
384
385 pub async fn get_public(&self) -> ConfigurationPublic {
386 let settings_lock = self.settings.read().await;
387
388 ConfigurationPublic {
389 website_name: settings_lock.website.name.clone(),
390 tracker_url: settings_lock.tracker.url.clone(),
391 tracker_mode: settings_lock.tracker.mode.clone(),
392 email_on_signup: settings_lock.auth.email_on_signup.clone(),
393 }
394 }
395
396 pub async fn get_site_name(&self) -> String {
397 let settings_lock = self.settings.read().await;
398
399 settings_lock.website.name.clone()
400 }
401
402 pub async fn get_api_base_url(&self) -> Option<String> {
403 let settings_lock = self.settings.read().await;
404
405 settings_lock.net.base_url.clone()
406 }
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct ConfigurationPublic {
413 website_name: String,
414 tracker_url: String,
415 tracker_mode: TrackerMode,
416 email_on_signup: EmailOnSignup,
417}