diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 85867816c..4b440d529 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -172,7 +172,7 @@ pub struct TslConfig { pub ssl_key_path: Option, } -#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] #[serde(rename_all = "lowercase")] pub enum LogLevel { /// A level lower than all log levels. diff --git a/packages/configuration/src/v1/core.rs b/packages/configuration/src/v1/core.rs new file mode 100644 index 000000000..ed9074194 --- /dev/null +++ b/packages/configuration/src/v1/core.rs @@ -0,0 +1,88 @@ +use std::net::{IpAddr, Ipv4Addr}; + +use serde::{Deserialize, Serialize}; +use torrust_tracker_primitives::{DatabaseDriver, TrackerMode}; + +use crate::{AnnouncePolicy, LogLevel}; + +#[allow(clippy::struct_excessive_bools)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +pub struct Core { + /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, + /// `Debug` and `Trace`. Default is `Info`. + pub log_level: Option, + /// Tracker mode. See [`TrackerMode`] for more information. + pub mode: TrackerMode, + + // Database configuration + /// Database driver. Possible values are: `Sqlite3`, and `MySQL`. + pub db_driver: DatabaseDriver, + /// Database connection string. The format depends on the database driver. + /// For `Sqlite3`, the format is `path/to/database.db`, for example: + /// `./storage/tracker/lib/database/sqlite3.db`. + /// For `Mysql`, the format is `mysql://db_user:db_user_password:port/db_name`, for + /// example: `root:password@localhost:3306/torrust`. + pub db_path: String, + + /// See [`AnnouncePolicy::interval`] + pub announce_interval: u32, + + /// See [`AnnouncePolicy::interval_min`] + pub min_announce_interval: u32, + /// Weather the tracker is behind a reverse proxy or not. + /// If the tracker is behind a reverse proxy, the `X-Forwarded-For` header + /// sent from the proxy will be used to get the client's IP address. + pub on_reverse_proxy: bool, + /// The external IP address of the tracker. If the client is using a + /// loopback IP address, this IP address will be used instead. If the peer + /// is using a loopback IP address, the tracker assumes that the peer is + /// in the same network as the tracker and will use the tracker's IP + /// address instead. + pub external_ip: Option, + /// Weather the tracker should collect statistics about tracker usage. + /// If enabled, the tracker will collect statistics like the number of + /// connections handled, the number of announce requests handled, etc. + /// Refer to the [`Tracker`](https://docs.rs/torrust-tracker) for more + /// information about the collected metrics. + pub tracker_usage_statistics: bool, + /// If enabled the tracker will persist the number of completed downloads. + /// That's how many times a torrent has been downloaded completely. + pub persistent_torrent_completed_stat: bool, + + // Cleanup job configuration + /// Maximum time in seconds that a peer can be inactive before being + /// considered an inactive peer. If a peer is inactive for more than this + /// time, it will be removed from the torrent peer list. + pub max_peer_timeout: u32, + /// Interval in seconds that the cleanup job will run to remove inactive + /// peers from the torrent peer list. + pub inactive_peer_cleanup_interval: u64, + /// If enabled, the tracker will remove torrents that have no peers. + /// The clean up torrent job runs every `inactive_peer_cleanup_interval` + /// seconds and it removes inactive peers. Eventually, the peer list of a + /// torrent could be empty and the torrent will be removed if this option is + /// enabled. + pub remove_peerless_torrents: bool, +} + +impl Default for Core { + fn default() -> Self { + let announce_policy = AnnouncePolicy::default(); + + Self { + log_level: Some(LogLevel::Info), + mode: TrackerMode::Public, + db_driver: DatabaseDriver::Sqlite3, + db_path: String::from("./storage/tracker/lib/database/sqlite3.db"), + announce_interval: announce_policy.interval, + min_announce_interval: announce_policy.interval_min, + max_peer_timeout: 900, + on_reverse_proxy: false, + external_ip: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))), + tracker_usage_statistics: true, + persistent_torrent_completed_stat: false, + inactive_peer_cleanup_interval: 600, + remove_peerless_torrents: true, + } + } +} diff --git a/packages/configuration/src/v1/mod.rs b/packages/configuration/src/v1/mod.rs index 643235c03..52e580ac4 100644 --- a/packages/configuration/src/v1/mod.rs +++ b/packages/configuration/src/v1/mod.rs @@ -230,86 +230,32 @@ //! [health_check_api] //! bind_address = "127.0.0.1:1313" //!``` +pub mod core; pub mod health_check_api; pub mod http_tracker; pub mod tracker_api; pub mod udp_tracker; use std::fs; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::IpAddr; use figment::providers::{Env, Format, Serialized, Toml}; use figment::Figment; use serde::{Deserialize, Serialize}; -use torrust_tracker_primitives::{DatabaseDriver, TrackerMode}; +use self::core::Core; use self::health_check_api::HealthCheckApi; use self::http_tracker::HttpTracker; use self::tracker_api::HttpApi; use self::udp_tracker::UdpTracker; -use crate::{AnnouncePolicy, Error, Info, LogLevel}; +use crate::{Error, Info}; /// Core configuration for the tracker. -#[allow(clippy::struct_excessive_bools)] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] pub struct Configuration { - /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, - /// `Debug` and `Trace`. Default is `Info`. - pub log_level: Option, - /// Tracker mode. See [`TrackerMode`] for more information. - pub mode: TrackerMode, - - // Database configuration - /// Database driver. Possible values are: `Sqlite3`, and `MySQL`. - pub db_driver: DatabaseDriver, - /// Database connection string. The format depends on the database driver. - /// For `Sqlite3`, the format is `path/to/database.db`, for example: - /// `./storage/tracker/lib/database/sqlite3.db`. - /// For `Mysql`, the format is `mysql://db_user:db_user_password:port/db_name`, for - /// example: `root:password@localhost:3306/torrust`. - pub db_path: String, - - /// See [`AnnouncePolicy::interval`] - pub announce_interval: u32, - - /// See [`AnnouncePolicy::interval_min`] - pub min_announce_interval: u32, - /// Weather the tracker is behind a reverse proxy or not. - /// If the tracker is behind a reverse proxy, the `X-Forwarded-For` header - /// sent from the proxy will be used to get the client's IP address. - pub on_reverse_proxy: bool, - /// The external IP address of the tracker. If the client is using a - /// loopback IP address, this IP address will be used instead. If the peer - /// is using a loopback IP address, the tracker assumes that the peer is - /// in the same network as the tracker and will use the tracker's IP - /// address instead. - pub external_ip: Option, - /// Weather the tracker should collect statistics about tracker usage. - /// If enabled, the tracker will collect statistics like the number of - /// connections handled, the number of announce requests handled, etc. - /// Refer to the [`Tracker`](https://docs.rs/torrust-tracker) for more - /// information about the collected metrics. - pub tracker_usage_statistics: bool, - /// If enabled the tracker will persist the number of completed downloads. - /// That's how many times a torrent has been downloaded completely. - pub persistent_torrent_completed_stat: bool, - - // Cleanup job configuration - /// Maximum time in seconds that a peer can be inactive before being - /// considered an inactive peer. If a peer is inactive for more than this - /// time, it will be removed from the torrent peer list. - pub max_peer_timeout: u32, - /// Interval in seconds that the cleanup job will run to remove inactive - /// peers from the torrent peer list. - pub inactive_peer_cleanup_interval: u64, - /// If enabled, the tracker will remove torrents that have no peers. - /// The clean up torrent job runs every `inactive_peer_cleanup_interval` - /// seconds and it removes inactive peers. Eventually, the peer list of a - /// torrent could be empty and the torrent will be removed if this option is - /// enabled. - pub remove_peerless_torrents: bool, - - // Server jobs configuration + /// Core configuration. + #[serde(flatten)] + pub core: Core, /// The list of UDP trackers the tracker is running. Each UDP tracker /// represents a UDP server that the tracker is running and it has its own /// configuration. @@ -326,30 +272,13 @@ pub struct Configuration { impl Default for Configuration { fn default() -> Self { - let announce_policy = AnnouncePolicy::default(); - - let mut configuration = Configuration { - log_level: Some(LogLevel::Info), - mode: TrackerMode::Public, - db_driver: DatabaseDriver::Sqlite3, - db_path: String::from("./storage/tracker/lib/database/sqlite3.db"), - announce_interval: announce_policy.interval, - min_announce_interval: announce_policy.interval_min, - max_peer_timeout: 900, - on_reverse_proxy: false, - external_ip: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))), - tracker_usage_statistics: true, - persistent_torrent_completed_stat: false, - inactive_peer_cleanup_interval: 600, - remove_peerless_torrents: true, - udp_trackers: Vec::new(), - http_trackers: Vec::new(), + Self { + core: Core::default(), + udp_trackers: vec![UdpTracker::default()], + http_trackers: vec![HttpTracker::default()], http_api: HttpApi::default(), health_check_api: HealthCheckApi::default(), - }; - configuration.udp_trackers.push(UdpTracker::default()); - configuration.http_trackers.push(HttpTracker::default()); - configuration + } } } @@ -362,7 +291,7 @@ impl Configuration { /// and `None` otherwise. #[must_use] pub fn get_ext_ip(&self) -> Option { - self.external_ip.as_ref().map(|external_ip| *external_ip) + self.core.external_ip.as_ref().map(|external_ip| *external_ip) } /// Saves the default configuration at the given path. @@ -490,7 +419,7 @@ mod tests { fn configuration_should_contain_the_external_ip() { let configuration = Configuration::default(); - assert_eq!(configuration.external_ip, Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))); + assert_eq!(configuration.core.external_ip, Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))); } #[test] diff --git a/packages/test-helpers/src/configuration.rs b/packages/test-helpers/src/configuration.rs index 0c7cc533a..86ed57b9e 100644 --- a/packages/test-helpers/src/configuration.rs +++ b/packages/test-helpers/src/configuration.rs @@ -27,10 +27,9 @@ pub fn ephemeral() -> Configuration { // todo: disable services that are not needed. // For example: a test for the UDP tracker should disable the API and HTTP tracker. - let mut config = Configuration { - log_level: Some(LogLevel::Off), // Change to `debug` for tests debugging - ..Default::default() - }; + let mut config = Configuration::default(); + + config.core.log_level = Some(LogLevel::Off); // Change to `debug` for tests debugging // Ephemeral socket address for API let api_port = 0u16; @@ -55,7 +54,7 @@ pub fn ephemeral() -> Configuration { let temp_directory = env::temp_dir(); let random_db_id = random::string(16); let temp_file = temp_directory.join(format!("data_{random_db_id}.db")); - temp_file.to_str().unwrap().clone_into(&mut config.db_path); + temp_file.to_str().unwrap().clone_into(&mut config.core.db_path); config } @@ -65,7 +64,7 @@ pub fn ephemeral() -> Configuration { pub fn ephemeral_with_reverse_proxy() -> Configuration { let mut cfg = ephemeral(); - cfg.on_reverse_proxy = true; + cfg.core.on_reverse_proxy = true; cfg } @@ -75,7 +74,7 @@ pub fn ephemeral_with_reverse_proxy() -> Configuration { pub fn ephemeral_without_reverse_proxy() -> Configuration { let mut cfg = ephemeral(); - cfg.on_reverse_proxy = false; + cfg.core.on_reverse_proxy = false; cfg } @@ -85,7 +84,7 @@ pub fn ephemeral_without_reverse_proxy() -> Configuration { pub fn ephemeral_mode_public() -> Configuration { let mut cfg = ephemeral(); - cfg.mode = TrackerMode::Public; + cfg.core.mode = TrackerMode::Public; cfg } @@ -95,7 +94,7 @@ pub fn ephemeral_mode_public() -> Configuration { pub fn ephemeral_mode_private() -> Configuration { let mut cfg = ephemeral(); - cfg.mode = TrackerMode::Private; + cfg.core.mode = TrackerMode::Private; cfg } @@ -105,7 +104,7 @@ pub fn ephemeral_mode_private() -> Configuration { pub fn ephemeral_mode_whitelisted() -> Configuration { let mut cfg = ephemeral(); - cfg.mode = TrackerMode::Listed; + cfg.core.mode = TrackerMode::Listed; cfg } @@ -115,7 +114,7 @@ pub fn ephemeral_mode_whitelisted() -> Configuration { pub fn ephemeral_mode_private_whitelisted() -> Configuration { let mut cfg = ephemeral(); - cfg.mode = TrackerMode::PrivateListed; + cfg.core.mode = TrackerMode::PrivateListed; cfg } @@ -125,7 +124,7 @@ pub fn ephemeral_mode_private_whitelisted() -> Configuration { pub fn ephemeral_with_external_ip(ip: IpAddr) -> Configuration { let mut cfg = ephemeral(); - cfg.external_ip = Some(ip); + cfg.core.external_ip = Some(ip); cfg } diff --git a/src/app.rs b/src/app.rs index 8bdc281a6..fcb01a696 100644 --- a/src/app.rs +++ b/src/app.rs @@ -67,7 +67,7 @@ pub async fn start(config: &Configuration, tracker: Arc) -> Vec) -> Vec 0 { - jobs.push(torrent_cleanup::start_job(config, &tracker)); + if config.core.inactive_peer_cleanup_interval > 0 { + jobs.push(torrent_cleanup::start_job(&config.core, &tracker)); } // Start Health Check API diff --git a/src/bootstrap/jobs/torrent_cleanup.rs b/src/bootstrap/jobs/torrent_cleanup.rs index 300813430..bd3b2e332 100644 --- a/src/bootstrap/jobs/torrent_cleanup.rs +++ b/src/bootstrap/jobs/torrent_cleanup.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use chrono::Utc; use log::info; use tokio::task::JoinHandle; -use torrust_tracker_configuration::Configuration; +use torrust_tracker_configuration::v1::core::Core; use crate::core; @@ -25,7 +25,7 @@ use crate::core; /// /// Refer to [`torrust-tracker-configuration documentation`](https://docs.rs/torrust-tracker-configuration) for more info about that option. #[must_use] -pub fn start_job(config: &Configuration, tracker: &Arc) -> JoinHandle<()> { +pub fn start_job(config: &Core, tracker: &Arc) -> JoinHandle<()> { let weak_tracker = std::sync::Arc::downgrade(tracker); let interval = config.inactive_peer_cleanup_interval; diff --git a/src/bootstrap/logging.rs b/src/bootstrap/logging.rs index b71079b57..5c7e93811 100644 --- a/src/bootstrap/logging.rs +++ b/src/bootstrap/logging.rs @@ -19,7 +19,7 @@ static INIT: Once = Once::new(); /// It redirects the log info to the standard output with the log level defined in the configuration pub fn setup(cfg: &Configuration) { - let level = config_level_or_default(&cfg.log_level); + let level = config_level_or_default(&cfg.core.log_level); if level == log::LevelFilter::Off { return; diff --git a/src/core/mod.rs b/src/core/mod.rs index 83813a863..dbaf27e22 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -544,13 +544,13 @@ impl Tracker { stats_event_sender: Option>, stats_repository: statistics::Repo, ) -> Result { - let database = Arc::new(databases::driver::build(&config.db_driver, &config.db_path)?); + let database = Arc::new(databases::driver::build(&config.core.db_driver, &config.core.db_path)?); - let mode = config.mode; + let mode = config.core.mode; Ok(Tracker { //config, - announce_policy: AnnouncePolicy::new(config.announce_interval, config.min_announce_interval), + announce_policy: AnnouncePolicy::new(config.core.announce_interval, config.core.min_announce_interval), mode, keys: tokio::sync::RwLock::new(std::collections::HashMap::new()), whitelist: tokio::sync::RwLock::new(std::collections::HashSet::new()), @@ -560,11 +560,11 @@ impl Tracker { database, external_ip: config.get_ext_ip(), policy: TrackerPolicy::new( - config.remove_peerless_torrents, - config.max_peer_timeout, - config.persistent_torrent_completed_stat, + config.core.remove_peerless_torrents, + config.core.max_peer_timeout, + config.core.persistent_torrent_completed_stat, ), - on_reverse_proxy: config.on_reverse_proxy, + on_reverse_proxy: config.core.on_reverse_proxy, }) } @@ -1033,7 +1033,7 @@ mod tests { pub fn tracker_persisting_torrents_in_database() -> Tracker { let mut configuration = configuration::ephemeral(); - configuration.persistent_torrent_completed_stat = true; + configuration.core.persistent_torrent_completed_stat = true; tracker_factory(&configuration) } diff --git a/src/core/services/mod.rs b/src/core/services/mod.rs index 76c6a36f6..dec143568 100644 --- a/src/core/services/mod.rs +++ b/src/core/services/mod.rs @@ -21,7 +21,7 @@ use crate::core::Tracker; #[must_use] pub fn tracker_factory(config: &Configuration) -> Tracker { // Initialize statistics - let (stats_event_sender, stats_repository) = statistics::setup::factory(config.tracker_usage_statistics); + let (stats_event_sender, stats_repository) = statistics::setup::factory(config.core.tracker_usage_statistics); // Initialize Torrust tracker match Tracker::new(&Arc::new(config), stats_event_sender, stats_repository) { diff --git a/src/servers/http/v1/services/announce.rs b/src/servers/http/v1/services/announce.rs index 5a0ae40e4..9529f954c 100644 --- a/src/servers/http/v1/services/announce.rs +++ b/src/servers/http/v1/services/announce.rs @@ -145,7 +145,7 @@ mod tests { fn tracker_with_an_ipv6_external_ip(stats_event_sender: Box) -> Tracker { let mut configuration = configuration::ephemeral(); - configuration.external_ip = Some(IpAddr::V6(Ipv6Addr::new( + configuration.core.external_ip = Some(IpAddr::V6(Ipv6Addr::new( 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, ))); diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index d8ca4680c..d6d7a1065 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -426,7 +426,7 @@ mod tests { } pub fn with_external_ip(mut self, external_ip: &str) -> Self { - self.configuration.external_ip = Some(external_ip.to_owned().parse().expect("valid IP address")); + self.configuration.core.external_ip = Some(external_ip.to_owned().parse().expect("valid IP address")); self }