torrust_tracker_configuration/
lib.rs

1//! Configuration data structures for [Torrust Tracker](https://docs.rs/torrust-tracker).
2//!
3//! This module contains the configuration data structures for the
4//! Torrust Tracker, which is a `BitTorrent` tracker server.
5//!
6//! The current version for configuration is [`v2`].
7pub mod v2_0_0;
8pub mod validator;
9
10use std::collections::HashMap;
11use std::env;
12use std::sync::Arc;
13use std::time::Duration;
14
15use camino::Utf8PathBuf;
16use derive_more::{Constructor, Display};
17use serde::{Deserialize, Serialize};
18use serde_with::serde_as;
19use thiserror::Error;
20use torrust_tracker_located_error::{DynError, LocatedError};
21
22/// The maximum number of returned peers for a torrent.
23pub const TORRENT_PEERS_LIMIT: usize = 74;
24
25/// Default timeout for sending and receiving packets. And waiting for sockets
26/// to be readable and writable.
27pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
28
29// Environment variables
30
31/// The whole `tracker.toml` file content. It has priority over the config file.
32/// Even if the file is not on the default path.
33const ENV_VAR_CONFIG_TOML: &str = "TORRUST_TRACKER_CONFIG_TOML";
34
35/// The `tracker.toml` file location.
36pub const ENV_VAR_CONFIG_TOML_PATH: &str = "TORRUST_TRACKER_CONFIG_TOML_PATH";
37
38pub type Configuration = v2_0_0::Configuration;
39pub type Core = v2_0_0::core::Core;
40pub type HealthCheckApi = v2_0_0::health_check_api::HealthCheckApi;
41pub type HttpApi = v2_0_0::tracker_api::HttpApi;
42pub type HttpTracker = v2_0_0::http_tracker::HttpTracker;
43pub type UdpTracker = v2_0_0::udp_tracker::UdpTracker;
44pub type Database = v2_0_0::database::Database;
45pub type Driver = v2_0_0::database::Driver;
46pub type Threshold = v2_0_0::logging::Threshold;
47
48pub type AccessTokens = HashMap<String, String>;
49
50pub const LATEST_VERSION: &str = "2.0.0";
51
52/// Info about the configuration specification.
53#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)]
54#[display("Metadata(app: {app}, purpose: {purpose}, schema_version: {schema_version})")]
55pub struct Metadata {
56    /// The application this configuration is valid for.
57    #[serde(default = "Metadata::default_app")]
58    app: App,
59
60    /// The purpose of this parsed file.
61    #[serde(default = "Metadata::default_purpose")]
62    purpose: Purpose,
63
64    /// The schema version for the configuration.
65    #[serde(default = "Metadata::default_schema_version")]
66    #[serde(flatten)]
67    schema_version: Version,
68}
69
70impl Default for Metadata {
71    fn default() -> Self {
72        Self {
73            app: Self::default_app(),
74            purpose: Self::default_purpose(),
75            schema_version: Self::default_schema_version(),
76        }
77    }
78}
79
80impl Metadata {
81    fn default_app() -> App {
82        App::TorrustTracker
83    }
84
85    fn default_purpose() -> Purpose {
86        Purpose::Configuration
87    }
88
89    fn default_schema_version() -> Version {
90        Version::latest()
91    }
92}
93
94#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)]
95#[serde(rename_all = "kebab-case")]
96pub enum App {
97    TorrustTracker,
98}
99
100#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)]
101#[serde(rename_all = "lowercase")]
102pub enum Purpose {
103    Configuration,
104}
105
106/// The configuration version.
107#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Display, Clone)]
108#[serde(rename_all = "lowercase")]
109pub struct Version {
110    #[serde(default = "Version::default_semver")]
111    schema_version: String,
112}
113
114impl Default for Version {
115    fn default() -> Self {
116        Self {
117            schema_version: Self::default_semver(),
118        }
119    }
120}
121
122impl Version {
123    fn new(semver: &str) -> Self {
124        Self {
125            schema_version: semver.to_owned(),
126        }
127    }
128
129    fn latest() -> Self {
130        Self {
131            schema_version: LATEST_VERSION.to_string(),
132        }
133    }
134
135    fn default_semver() -> String {
136        LATEST_VERSION.to_string()
137    }
138}
139
140#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Constructor)]
141pub struct TrackerPolicy {
142    // Cleanup job configuration
143    /// Maximum time in seconds that a peer can be inactive before being
144    /// considered an inactive peer. If a peer is inactive for more than this
145    /// time, it will be removed from the torrent peer list.
146    #[serde(default = "TrackerPolicy::default_max_peer_timeout")]
147    pub max_peer_timeout: u32,
148
149    /// If enabled the tracker will persist the number of completed downloads.
150    /// That's how many times a torrent has been downloaded completely.
151    #[serde(default = "TrackerPolicy::default_persistent_torrent_completed_stat")]
152    pub persistent_torrent_completed_stat: bool,
153
154    /// If enabled, the tracker will remove torrents that have no peers.
155    /// The clean up torrent job runs every `inactive_peer_cleanup_interval`
156    /// seconds and it removes inactive peers. Eventually, the peer list of a
157    /// torrent could be empty and the torrent will be removed if this option is
158    /// enabled.
159    #[serde(default = "TrackerPolicy::default_remove_peerless_torrents")]
160    pub remove_peerless_torrents: bool,
161}
162
163impl Default for TrackerPolicy {
164    fn default() -> Self {
165        Self {
166            max_peer_timeout: Self::default_max_peer_timeout(),
167            persistent_torrent_completed_stat: Self::default_persistent_torrent_completed_stat(),
168            remove_peerless_torrents: Self::default_remove_peerless_torrents(),
169        }
170    }
171}
172
173impl TrackerPolicy {
174    fn default_max_peer_timeout() -> u32 {
175        900
176    }
177
178    fn default_persistent_torrent_completed_stat() -> bool {
179        false
180    }
181
182    fn default_remove_peerless_torrents() -> bool {
183        true
184    }
185}
186
187/// Information required for loading config
188#[derive(Debug, Default, Clone)]
189pub struct Info {
190    config_toml: Option<String>,
191    config_toml_path: String,
192}
193
194impl Info {
195    /// Build Configuration Info
196    ///
197    /// # Errors
198    ///
199    /// Will return `Err` if unable to obtain a configuration.
200    ///
201    #[allow(clippy::needless_pass_by_value)]
202    pub fn new(default_config_toml_path: String) -> Result<Self, Error> {
203        let env_var_config_toml = ENV_VAR_CONFIG_TOML.to_string();
204        let env_var_config_toml_path = ENV_VAR_CONFIG_TOML_PATH.to_string();
205
206        let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) {
207            println!("Loading extra configuration from environment variable:\n {config_toml}");
208            Some(config_toml)
209        } else {
210            None
211        };
212
213        let config_toml_path = if let Ok(config_toml_path) = env::var(env_var_config_toml_path) {
214            println!("Loading extra configuration from file: `{config_toml_path}` ...");
215            config_toml_path
216        } else {
217            println!("Loading extra configuration from default configuration file: `{default_config_toml_path}` ...");
218            default_config_toml_path
219        };
220
221        Ok(Self {
222            config_toml,
223            config_toml_path,
224        })
225    }
226}
227
228/// Announce policy
229#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, Constructor)]
230pub struct AnnouncePolicy {
231    /// Interval in seconds that the client should wait between sending regular
232    /// announce requests to the tracker.
233    ///
234    /// It's a **recommended** wait time between announcements.
235    ///
236    /// This is the standard amount of time that clients should wait between
237    /// sending consecutive announcements to the tracker. This value is set by
238    /// the tracker and is typically provided in the tracker's response to a
239    /// client's initial request. It serves as a guideline for clients to know
240    /// how often they should contact the tracker for updates on the peer list,
241    /// while ensuring that the tracker is not overwhelmed with requests.
242    #[serde(default = "AnnouncePolicy::default_interval")]
243    pub interval: u32,
244
245    /// Minimum announce interval. Clients must not reannounce more frequently
246    /// than this.
247    ///
248    /// It establishes the shortest allowed wait time.
249    ///
250    /// This is an optional parameter in the protocol that the tracker may
251    /// provide in its response. It sets a lower limit on the frequency at which
252    /// clients are allowed to send announcements. Clients should respect this
253    /// value to prevent sending too many requests in a short period, which
254    /// could lead to excessive load on the tracker or even getting banned by
255    /// the tracker for not adhering to the rules.
256    #[serde(default = "AnnouncePolicy::default_interval_min")]
257    pub interval_min: u32,
258}
259
260impl Default for AnnouncePolicy {
261    fn default() -> Self {
262        Self {
263            interval: Self::default_interval(),
264            interval_min: Self::default_interval_min(),
265        }
266    }
267}
268
269impl AnnouncePolicy {
270    fn default_interval() -> u32 {
271        120
272    }
273
274    fn default_interval_min() -> u32 {
275        120
276    }
277}
278
279/// Errors that can occur when loading the configuration.
280#[derive(Error, Debug)]
281pub enum Error {
282    /// Unable to load the configuration from the environment variable.
283    /// This error only occurs if there is no configuration file and the
284    /// `TORRUST_TRACKER_CONFIG_TOML` environment variable is not set.
285    #[error("Unable to load from Environmental Variable: {source}")]
286    UnableToLoadFromEnvironmentVariable {
287        source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
288    },
289
290    #[error("Unable to load from Config File: {source}")]
291    UnableToLoadFromConfigFile {
292        source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
293    },
294
295    /// Unable to load the configuration from the configuration file.
296    #[error("Failed processing the configuration: {source}")]
297    ConfigError {
298        source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
299    },
300
301    #[error("The error for errors that can never happen.")]
302    Infallible,
303
304    #[error("Unsupported configuration version: {version}")]
305    UnsupportedVersion { version: Version },
306
307    #[error("Missing mandatory configuration option. Option path: {path}")]
308    MissingMandatoryOption { path: String },
309}
310
311impl From<figment::Error> for Error {
312    #[track_caller]
313    fn from(err: figment::Error) -> Self {
314        Self::ConfigError {
315            source: (Arc::new(err) as DynError).into(),
316        }
317    }
318}
319
320#[serde_as]
321#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)]
322pub struct TslConfig {
323    /// Path to the SSL certificate file.
324    #[serde(default = "TslConfig::default_ssl_cert_path")]
325    pub ssl_cert_path: Utf8PathBuf,
326
327    /// Path to the SSL key file.
328    #[serde(default = "TslConfig::default_ssl_key_path")]
329    pub ssl_key_path: Utf8PathBuf,
330}
331
332impl TslConfig {
333    #[allow(clippy::unnecessary_wraps)]
334    fn default_ssl_cert_path() -> Utf8PathBuf {
335        Utf8PathBuf::new()
336    }
337
338    #[allow(clippy::unnecessary_wraps)]
339    fn default_ssl_key_path() -> Utf8PathBuf {
340        Utf8PathBuf::new()
341    }
342}