From ae5f6286a3486e9186ea27a54bee69503a5421a7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Apr 2023 18:26:43 +0100 Subject: [PATCH] docs: [#275] crate docs for configuration package --- packages/configuration/src/lib.rs | 322 +++++++++++++++++++++++++++++- src/servers/apis/mod.rs | 4 +- src/servers/apis/server.rs | 2 +- src/tracker/mod.rs | 2 +- 4 files changed, 325 insertions(+), 5 deletions(-) diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index d5beca23..6b051b57 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -1,3 +1,231 @@ +//! Configuration data structures for [Torrust Tracker](https://docs.rs/torrust-tracker). +//! +//! This module contains the configuration data structures for the +//! Torrust Tracker, which is a `BitTorrent` tracker server. +//! +//! The configuration is loaded from a [TOML](https://toml.io/en/) file +//! `config.toml` in the project root folder or from an environment variable +//! with the same content as the file. +//! +//! When you run the tracker without a configuration file, a new one will be +//! created with the default values, but the tracker immediately exits. You can +//! then edit the configuration file and run the tracker again. +//! +//! Configuration can not only be loaded from a file, but also from environment +//! variable `TORRUST_TRACKER_CONFIG`. This is useful when running the tracker +//! in a Docker container or environments where you do not have a persistent +//! storage or you cannot inject a configuration file. Refer to +//! [`Torrust Tracker documentation`](https://docs.rs/torrust-tracker) for more +//! information about how to pass configuration to the tracker. +//! +//! # Table of contents +//! +//! - [Sections](#sections) +//! - [Port binding](#port-binding) +//! - [TSL support](#tsl-support) +//! - [Generating self-signed certificates](#generating-self-signed-certificates) +//! - [Default configuration](#default-configuration) +//! +//! ## Sections +//! +//! Each section in the toml structure is mapped to a data structure. For +//! example, the `[http_api]` section (configuration for the tracker HTTP API) +//! is mapped to the [`HttpApi`](HttpApi) structure. +//! +//! > **NOTICE**: some sections are arrays of structures. For example, the +//! > `[[udp_trackers]]` section is an array of [`UdpTracker`](UdpTracker) since +//! > you can have multiple running UDP trackers bound to different ports. +//! +//! Please refer to the documentation of each structure for more information +//! about each section. +//! +//! - [`Core configuration`](crate::Configuration) +//! - [`HTTP API configuration`](crate::HttpApi) +//! - [`HTTP Tracker configuration`](crate::HttpTracker) +//! - [`UDP Tracker configuration`](crate::UdpTracker) +//! +//! ## Port binding +//! +//! For the API, HTTP and UDP trackers you can bind to a random port by using +//! port `0`. For example, if you want to bind to a random port on all +//! interfaces, use `0.0.0.0:0`. The OS will choose a random port but the +//! tracker will not print the port it is listening to when it starts. It just +//! says `Starting Torrust HTTP tracker server on: http://0.0.0.0:0`. It shows +//! the port used in the configuration file, and not the port the +//! tracker is actually listening to. This is a planned feature, see issue +//! [186](https://github.com/torrust/torrust-tracker/issues/186) for more +//! information. +//! +//! ## TSL support +//! +//! For the API and HTTP tracker you can enable TSL by setting `ssl_enabled` to +//! `true` and setting the paths to the certificate and key files. +//! +//! Typically, you will have a directory structure like this: +//! +//! ```text +//! storage/ +//! ├── database +//! │ └── data.db +//! └── ssl_certificates +//! ├── localhost.crt +//! └── localhost.key +//! ``` +//! +//! where you can store all the persistent data. +//! +//! Alternatively, you could setup a reverse proxy like Nginx or Apache to +//! handle the SSL/TLS part and forward the requests to the tracker. If you do +//! that, you should set [`on_reverse_proxy`](crate::Configuration::on_reverse_proxy) +//! to `true` in the configuration file. It's out of scope for this +//! documentation to explain in detail how to setup a reverse proxy, but the +//! configuration file should be something like this: +//! +//! For [NGINX](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/): +//! +//! ```text +//! # HTTPS only (with SSL - force redirect to HTTPS) +//! +//! server { +//! listen 80; +//! server_name tracker.torrust.com; +//! +//! return 301 https://$host$request_uri; +//! } +//! +//! server { +//! listen 443; +//! server_name tracker.torrust.com; +//! +//! ssl_certificate CERT_PATH +//! ssl_certificate_key CERT_KEY_PATH; +//! +//! location / { +//! proxy_set_header X-Forwarded-For $remote_addr; +//! proxy_pass http://127.0.0.1:6969; +//! } +//! } +//! ``` +//! +//! For [Apache](https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html): +//! +//! ```text +//! # HTTPS only (with SSL - force redirect to HTTPS) +//! +//! +//! ServerAdmin webmaster@tracker.torrust.com +//! ServerName tracker.torrust.com +//! +//! +//! RewriteEngine on +//! RewriteCond %{HTTPS} off +//! RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] +//! +//! +//! +//! +//! +//! ServerAdmin webmaster@tracker.torrust.com +//! ServerName tracker.torrust.com +//! +//! +//! Order allow,deny +//! Allow from all +//! +//! +//! ProxyPreserveHost On +//! ProxyRequests Off +//! AllowEncodedSlashes NoDecode +//! +//! ProxyPass / http://localhost:3000/ +//! ProxyPassReverse / http://localhost:3000/ +//! ProxyPassReverse / http://tracker.torrust.com/ +//! +//! RequestHeader set X-Forwarded-Proto "https" +//! RequestHeader set X-Forwarded-Port "443" +//! +//! ErrorLog ${APACHE_LOG_DIR}/tracker.torrust.com-error.log +//! CustomLog ${APACHE_LOG_DIR}/tracker.torrust.com-access.log combined +//! +//! SSLCertificateFile CERT_PATH +//! SSLCertificateKeyFile CERT_KEY_PATH +//! +//! +//! ``` +//! +//! ## Generating self-signed certificates +//! +//! For testing purposes, you can use self-signed certificates. +//! +//! Refer to [Let's Encrypt - Certificates for localhost](https://letsencrypt.org/docs/certificates-for-localhost/) +//! for more information. +//! +//! Running the following command will generate a certificate (`localhost.crt`) +//! and key (`localhost.key`) file in your current directory: +//! +//! ```s +//! openssl req -x509 -out localhost.crt -keyout localhost.key \ +//! -newkey rsa:2048 -nodes -sha256 \ +//! -subj '/CN=localhost' -extensions EXT -config <( \ +//! printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") +//! ``` +//! +//! You can then use the generated files in the configuration file: +//! +//! ```s +//! [[http_trackers]] +//! enabled = true +//! ... +//! ssl_cert_path = "./storage/ssl_certificates/localhost.crt" +//! ssl_key_path = "./storage/ssl_certificates/localhost.key" +//! +//! [http_api] +//! enabled = true +//! ... +//! ssl_cert_path = "./storage/ssl_certificates/localhost.crt" +//! ssl_key_path = "./storage/ssl_certificates/localhost.key" +//! ``` +//! +//! ## Default configuration +//! +//! The default configuration is: +//! +//! ```toml +//! log_level = "info" +//! mode = "public" +//! db_driver = "Sqlite3" +//! db_path = "./storage/database/data.db" +//! announce_interval = 120 +//! min_announce_interval = 120 +//! max_peer_timeout = 900 +//! on_reverse_proxy = false +//! external_ip = "0.0.0.0" +//! tracker_usage_statistics = true +//! persistent_torrent_completed_stat = false +//! inactive_peer_cleanup_interval = 600 +//! remove_peerless_torrents = true +//! +//! [[udp_trackers]] +//! enabled = false +//! bind_address = "0.0.0.0:6969" +//! +//! [[http_trackers]] +//! enabled = false +//! bind_address = "0.0.0.0:7070" +//! ssl_enabled = false +//! ssl_cert_path = "" +//! ssl_key_path = "" +//! +//! [http_api] +//! enabled = true +//! bind_address = "127.0.0.1:1212" +//! ssl_enabled = false +//! ssl_cert_path = "" +//! ssl_key_path = "" +//! +//! [http_api.access_tokens] +//! admin = "MyAccessToken" +//!``` use std::collections::{HashMap, HashSet}; use std::net::IpAddr; use std::panic::Location; @@ -14,38 +242,67 @@ use thiserror::Error; use torrust_tracker_located_error::{Located, LocatedError}; use torrust_tracker_primitives::{DatabaseDriver, TrackerMode}; +/// Configuration for each UDP tracker. #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct UdpTracker { + /// Weather the UDP tracker is enabled or not. pub enabled: bool, + /// The address the tracker will bind to. + /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to + /// listen to all interfaces, use `0.0.0.0`. If you want the operating + /// system to choose a random port, use port `0`. pub bind_address: String, } +/// Configuration for each HTTP tracker. #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct HttpTracker { + /// Weather the HTTP tracker is enabled or not. pub enabled: bool, + /// The address the tracker will bind to. + /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to + /// listen to all interfaces, use `0.0.0.0`. If you want the operating + /// system to choose a random port, use port `0`. pub bind_address: String, + /// Weather the HTTP tracker will use SSL or not. pub ssl_enabled: bool, + /// Path to the SSL certificate file. Only used if `ssl_enabled` is `true`. #[serde_as(as = "NoneAsEmptyString")] pub ssl_cert_path: Option, + /// Path to the SSL key file. Only used if `ssl_enabled` is `true`. #[serde_as(as = "NoneAsEmptyString")] pub ssl_key_path: Option, } +/// Configuration for the HTTP API. #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct HttpApi { + /// Weather the HTTP API is enabled or not. pub enabled: bool, + /// The address the tracker will bind to. + /// The format is `ip:port`, for example `0.0.0.0:6969`. If you want to + /// listen to all interfaces, use `0.0.0.0`. If you want the operating + /// system to choose a random port, use port `0`. pub bind_address: String, + /// Weather the HTTP API will use SSL or not. pub ssl_enabled: bool, + /// Path to the SSL certificate file. Only used if `ssl_enabled` is `true`. #[serde_as(as = "NoneAsEmptyString")] pub ssl_cert_path: Option, + /// Path to the SSL key file. Only used if `ssl_enabled` is `true`. #[serde_as(as = "NoneAsEmptyString")] pub ssl_key_path: Option, + /// Access tokens for the HTTP API. The key is a label identifying the + /// token and the value is the token itself. The token is used to + /// authenticate the user. All tokens are valid for all endpoints and have + /// the all permissions. pub access_tokens: HashMap, } impl HttpApi { + /// Checks if the given token is one of the token in the configuration. #[must_use] pub fn contains_token(&self, token: &str) -> bool { let tokens: HashMap = self.access_tokens.clone(); @@ -54,14 +311,24 @@ impl HttpApi { } } +/// 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`](torrust_tracker_primitives::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/database/data.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, /// Interval in seconds that the client should wait between sending regular @@ -88,35 +355,77 @@ pub struct Configuration { /// could lead to excessive load on the tracker or even getting banned by /// the tracker for not adhering to the rules. 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 + /// 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. pub udp_trackers: Vec, + /// The list of HTTP trackers the tracker is running. Each HTTP tracker + /// represents a HTTP server that the tracker is running and it has its own + /// configuration. pub http_trackers: Vec, + /// The HTTP API configuration. pub http_api: HttpApi, } +/// Errors that can occur when loading the configuration. #[derive(Error, Debug)] pub enum Error { + /// Unable to load the configuration from the environment variable. + /// This error only occurs if there is no configuration file and the + /// `TORRUST_TRACKER_CONFIG` environment variable is not set. #[error("Unable to load from Environmental Variable: {source}")] UnableToLoadFromEnvironmentVariable { source: LocatedError<'static, dyn std::error::Error + Send + Sync>, }, + /// If you run the tracker without providing the configuration (via the + /// `TORRUST_TRACKER_CONFIG` environment variable or configuration file), + /// the tracker will create a default configuration file but it will not + /// load it. It will return this error instead and you have to restart the + /// it. #[error("Default configuration created at: `{path}`, please review and reload tracker, {location}")] CreatedNewConfigHalt { location: &'static Location<'static>, path: String, }, + /// Unable to load the configuration from the configuration file. #[error("Failed processing the configuration: {source}")] ConfigError { source: LocatedError<'static, ConfigError> }, } @@ -176,6 +485,8 @@ impl Default for Configuration { } impl Configuration { + /// Returns the tracker public IP address id defined in the configuration, + /// and `None` otherwise. #[must_use] pub fn get_ext_ip(&self) -> Option { match &self.external_ip { @@ -187,6 +498,8 @@ impl Configuration { } } + /// Loads the configuration from the configuration file. + /// /// # Errors /// /// Will return `Err` if `path` does not exist or has a bad configuration. @@ -214,6 +527,10 @@ impl Configuration { Ok(torrust_config) } + /// Loads the configuration from the environment variable. The whole + /// configuration must be in the environment variable. It contains the same + /// configuration as the configuration file with the same format. + /// /// # Errors /// /// Will return `Err` if the environment variable does not exist or has a bad configuration. @@ -232,10 +549,13 @@ impl Configuration { } } + /// Saves the configuration to the configuration file. + /// /// # Errors /// /// Will return `Err` if `filename` does not exist or the user does not have - /// permission to read it. + /// permission to read it. Will also return `Err` if the configuration is + /// not valid or cannot be encoded to TOML. pub fn save_to_file(&self, path: &str) -> Result<(), Error> { let toml_string = toml::to_string(self).expect("Could not encode TOML value"); fs::write(path, toml_string).expect("Could not write to file!"); diff --git a/src/servers/apis/mod.rs b/src/servers/apis/mod.rs index 203f1d14..eb278bf3 100644 --- a/src/servers/apis/mod.rs +++ b/src/servers/apis/mod.rs @@ -35,7 +35,7 @@ //! admin = "MyAccessToken" //! ``` //! -//! Refer to [torrust-tracker-configuration](https://docs.rs/torrust-tracker-configuration>) +//! Refer to [`torrust-tracker-configuration`](torrust_tracker_configuration) //! for more information about the API configuration. //! //! When you run the tracker with enabled API, you will see the following message: @@ -99,7 +99,7 @@ //! The token label is used to identify the token. All tokens have full access //! to the API. //! -//! Refer to [torrust-tracker-configuration](https://docs.rs/torrust-tracker-configuration>) +//! Refer to [`torrust-tracker-configuration`](torrust_tracker_configuration) //! for more information about the API configuration and to the //! [`auth`](crate::servers::apis::v1::middlewares::auth) middleware for more //! information about the authentication process. diff --git a/src/servers/apis/server.rs b/src/servers/apis/server.rs index 76396cc5..91821aa7 100644 --- a/src/servers/apis/server.rs +++ b/src/servers/apis/server.rs @@ -140,7 +140,7 @@ impl Launcher { /// Starts the API server with graceful shutdown. /// /// If TLS is enabled in the configuration, it will start the server with - /// TLS. See [torrust-tracker-configuration](https://docs.rs/torrust-tracker-configuration>) + /// TLS. See [`torrust-tracker-configuration`](torrust_tracker_configuration) /// for more information about configuration. pub fn start( cfg: &torrust_tracker_configuration::HttpApi, diff --git a/src/tracker/mod.rs b/src/tracker/mod.rs index 03853e1a..2cabd5a8 100644 --- a/src/tracker/mod.rs +++ b/src/tracker/mod.rs @@ -468,7 +468,7 @@ use crate::tracker::databases::Database; /// Typically, the `Tracker` is used by a higher application service that handles /// the network layer. pub struct Tracker { - /// `Tracker` configuration. See + /// `Tracker` configuration. See [`torrust-tracker-configuration`](torrust_tracker_configuration) pub config: Arc, /// A database driver implementation: [`Sqlite3`](crate::tracker::databases::sqlite) /// or [`MySQL`](crate::tracker::databases::mysql)