diff --git a/settings.sample.yaml b/settings.sample.yaml index f181a0d..b73314d 100644 --- a/settings.sample.yaml +++ b/settings.sample.yaml @@ -18,6 +18,8 @@ # MangaDex@Home configuration file # We are pleased to have you here # May fate stay the night with you! +# +# Default values are commented out. # The size in mebibytes of the cache # You can use megabytes instead in a pinch, @@ -25,37 +27,31 @@ max_cache_size_in_mebibytes: 0 server_settings: - # The client secret - # Keep this secret at all costs :P - secret: ina_is_the_cutest + # The client secret. Keep this secret at all costs :P + secret: suichan wa kyou mo kawaii! - # The port for the webserver to listen on - # 443 is recommended for maximum appeal - port: 443 + # The port for the webserver to listen on. 443 is recommended for max appeal. + # port: 443 - # This controls the value the server receives - # for your upload speed + # This controls the value the server receives for your upload speed. external_max_kilobits_per_second: 0 # - # Stuff that you probably don't need to change + # Advanced settings # - # The external port to broadcast to the backend - # Keep this at 0 unless you know what you're doing - # 0 means broadcast the same value as `port` - external_port: 0 + # The external hostname to listen on. Keep this at 0.0.0.0 unless you know + # what you're doing. + # hostname: 0.0.0.0 - # How long to wait for the graceful shutdown (Ctrl-C or SIGINT) - # This is rounded to a multiple of 5 seconds - graceful_shutdown_wait_seconds: 60 + # The external port to broadcast to the backend. Keep this at 0 unless you + # know what you're doing. 0 means broadcast the same value as `port`. + # external_port: 0 - # The external hostname to listen on - # Keep this at 0.0.0.0 unless you know what you're doing - hostname: 0.0.0.0 + # How long to wait at most for the graceful shutdown (Ctrl-C or SIGINT). + # graceful_shutdown_wait_seconds: 60 - # The external ip to broadcast to the webserver - # The default of null means the backend will infer it - # from where it was sent from, which may fail in the - # presence of multiple IPs - external_ip: ~ + # The external ip to broadcast to the webserver. The default of null (~) means + # the backend will infer it from where it was sent from, which may fail in the + # presence of multiple IPs. + # external_ip: ~ diff --git a/src/cache/fs.rs b/src/cache/fs.rs index 7e125f4..70aef66 100644 --- a/src/cache/fs.rs +++ b/src/cache/fs.rs @@ -111,21 +111,21 @@ pub(super) async fn read_file( return None; } - let header = if let Some(header) = Header::from_slice(&header_bytes) { + let file_header = if let Some(header) = Header::from_slice(&header_bytes) { header } else { warn!("Found file, but encrypted header was invalid. Assuming corrupted!"); return None; }; - let secret_stream = if let Ok(stream) = SecretStream::init_pull(&header, key) { + let secret_stream = if let Ok(stream) = SecretStream::init_pull(&file_header, key) { stream } else { warn!("Failed to init secret stream with key and header. Assuming corrupted!"); return None; }; - maybe_header = Some(header); + maybe_header = Some(file_header); reader = Some(Box::pin(EncryptedDiskReader::new(file, secret_stream))); } diff --git a/src/config.rs b/src/config.rs index 50c8964..c94ad6e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,8 +2,8 @@ use std::fmt::{Display, Formatter}; use std::fs::{File, OpenOptions}; use std::hint::unreachable_unchecked; use std::io::{ErrorKind, Write}; -use std::net::{IpAddr, SocketAddr}; -use std::num::{NonZeroU16, NonZeroU64}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::num::NonZeroU16; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; @@ -64,7 +64,7 @@ pub fn load_config() -> Result { Ok(config) } -/// Represents a fully parsed config file. +/// Represents a fully parsed config, from a variety of sources. pub struct Config { pub cache_type: CacheType, pub cache_path: PathBuf, @@ -75,27 +75,93 @@ pub struct Config { pub bind_address: SocketAddr, pub external_address: Option, pub ephemeral_disk_encryption: bool, - pub unstable_options: Vec, pub network_speed: KilobitsPerSecond, pub disk_quota: Mebibytes, pub memory_quota: Mebibytes, + pub unstable_options: Vec, pub override_upstream: Option, + pub enable_metrics: bool, } impl Config { fn from_cli_and_file(cli_args: CliArgs, file_args: YamlArgs) -> Self { + let file_extended_options = file_args.extended_options.unwrap_or_default(); + let log_level = match (cli_args.quiet, cli_args.verbose) { (n, _) if n > 2 => LevelFilter::Off, (2, _) => LevelFilter::Error, (1, _) => LevelFilter::Warn, // Use log level from file if no flags were provided to CLI - (0, 0) => file_args.extended_options.logging_level, + (0, 0) => file_extended_options + .logging_level + .unwrap_or(LevelFilter::Info), (_, 1) => LevelFilter::Debug, (_, n) if n > 1 => LevelFilter::Trace, // compiler can't figure it out _ => unsafe { unreachable_unchecked() }, }; - todo!() + + let bind_port = cli_args + .port + .unwrap_or(file_args.server_settings.port) + .get(); + + // This needs to be outside because rust isn't smart enough yet to + // realize a disjointed borrow of a moved value is ok. This will be + // fixed in Rust 2021. + let external_port = file_args + .server_settings + .external_port + .map_or(bind_port, Port::get); + + Self { + cache_type: cli_args + .cache_type + .or(file_extended_options.cache_type) + .unwrap_or_default(), + cache_path: cli_args + .cache_path + .or(file_extended_options.cache_path) + .unwrap_or_else(|| PathBuf::from_str("./cache").unwrap()), + shutdown_timeout: file_args + .server_settings + .graceful_shutdown_wait_seconds + .unwrap_or(unsafe { NonZeroU16::new_unchecked(60) }), + log_level, + // secret should never be in CLI + client_secret: file_args.server_settings.secret, + port: cli_args.port.unwrap_or(file_args.server_settings.port), + bind_address: SocketAddr::new( + file_args + .server_settings + .hostname + .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))), + bind_port, + ), + external_address: file_args + .server_settings + .external_ip + .map(|ip_addr| SocketAddr::new(ip_addr, external_port)), + ephemeral_disk_encryption: cli_args + .ephemeral_disk_encryption + .or(file_extended_options.ephemeral_disk_encryption) + .unwrap_or_default(), + network_speed: cli_args + .network_speed + .unwrap_or(file_args.server_settings.external_max_kilobits_per_second), + disk_quota: cli_args + .disk_quota + .unwrap_or(file_args.max_cache_size_in_mebibytes), + memory_quota: cli_args + .memory_quota + .or(file_extended_options.memory_quota) + .unwrap_or_default(), + enable_metrics: file_extended_options.enable_metrics.unwrap_or_default(), + + // Unstable options (and related) should never be in yaml config + unstable_options: cli_args.unstable_options, + override_upstream: cli_args.override_upstream, + } } } @@ -105,13 +171,14 @@ struct YamlArgs { max_cache_size_in_mebibytes: Mebibytes, server_settings: YamlServerSettings, // This implementation custom options - extended_options: YamlExtendedOptions, + extended_options: Option, } // Naming is legacy #[derive(Deserialize)] struct YamlServerSettings { secret: ClientSecret, + #[serde(default)] port: Port, external_max_kilobits_per_second: KilobitsPerSecond, external_port: Option, @@ -124,21 +191,14 @@ struct YamlServerSettings { #[derive(Deserialize, Serialize)] pub struct ClientSecret(String); -#[derive(Deserialize)] +#[derive(Deserialize, Default)] struct YamlExtendedOptions { - memory_quota: Option, - #[serde(default)] - cache_type: CacheType, - #[serde(default)] - ephemeral_disk_encryption: bool, - #[serde(default)] - enable_metrics: bool, - #[serde(default = "default_logging_level")] - logging_level: LevelFilter, -} - -const fn default_logging_level() -> LevelFilter { - LevelFilter::Info + memory_quota: Option, + cache_type: Option, + ephemeral_disk_encryption: Option, + enable_metrics: Option, + logging_level: Option, + cache_path: Option, } #[derive(Deserialize, Copy, Clone)] @@ -172,25 +232,25 @@ impl Default for CacheType { #[clap(version = crate_version!(), author = crate_authors!(), about = crate_description!())] struct CliArgs { /// The port to listen on. - #[clap(short, long, default_value = "42069")] - pub port: Port, - /// How large, in bytes, the in-memory cache should be. Note that this does - /// not include runtime memory usage. + #[clap(short, long)] + pub port: Option, + /// How large, in mebibytes, the in-memory cache should be. Note that this + /// does not include runtime memory usage. #[clap(long)] - pub memory_quota: Option, - /// How large, in bytes, the on-disk cache should be. Note that actual + pub memory_quota: Option, + /// How large, in mebibytes, the on-disk cache should be. Note that actual /// values may be larger for metadata information. #[clap(long)] - pub disk_quota: u64, + pub disk_quota: Option, /// Sets the location of the disk cache. - #[clap(long, default_value = "./cache")] - pub cache_path: PathBuf, + #[clap(long)] + pub cache_path: Option, /// The network speed to advertise to Mangadex@Home control server. #[clap(long)] - pub network_speed: NonZeroU64, + pub network_speed: Option, /// Changes verbosity. Default verbosity is INFO, while increasing counts of /// verbose flags increases the verbosity to DEBUG and TRACE, respectively. - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, parse(from_occurrences), conflicts_with = "quiet")] pub verbose: usize, /// Changes verbosity. Default verbosity is INFO, while increasing counts of /// quiet flags decreases the verbosity to WARN, ERROR, and no logs, @@ -205,11 +265,11 @@ struct CliArgs { /// encrypted with a key generated at runtime. There are implications to /// performance, privacy, and usability with this flag enabled. #[clap(short, long)] - pub ephemeral_disk_encryption: bool, + pub ephemeral_disk_encryption: Option, #[clap(short, long)] pub config_path: Option, - #[clap(default_value = "on_disk")] - pub cache_type: CacheType, + #[clap(short, long)] + pub cache_type: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/src/main.rs b/src/main.rs index 7ee532b..0ca0bbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,7 +93,9 @@ async fn main() -> Result<(), Box> { ENCRYPTION_KEY.set(gen_key()).unwrap(); } - metrics::init(); + if config.enable_metrics { + metrics::init(); + } // HTTP Server init diff --git a/src/units.rs b/src/units.rs index 0ea9df1..213dc08 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,5 +1,5 @@ use std::fmt::Display; -use std::num::{NonZeroU16, NonZeroU64}; +use std::num::{NonZeroU16, NonZeroU64, ParseIntError}; use std::str::FromStr; use serde::{Deserialize, Serialize}; @@ -8,6 +8,18 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct Port(NonZeroU16); +impl Port { + pub const fn get(self) -> u16 { + self.0.get() + } +} + +impl Default for Port { + fn default() -> Self { + Self(unsafe { NonZeroU16::new_unchecked(443) }) + } +} + impl FromStr for Port { type Err = ::Err; @@ -25,9 +37,11 @@ impl Display for Port { #[derive(Copy, Clone, Serialize, Deserialize, Default, Debug, Hash, Eq, PartialEq)] pub struct Mebibytes(usize); -impl Mebibytes { - pub const fn get(self) -> usize { - self.0 +impl FromStr for Mebibytes { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + s.parse::().map(Self) } } @@ -48,6 +62,14 @@ impl From for Bytes { #[derive(Copy, Clone, Deserialize, Debug, Hash, Eq, PartialEq)] pub struct KilobitsPerSecond(NonZeroU64); +impl FromStr for KilobitsPerSecond { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + s.parse::().map(Self) + } +} + #[derive(Copy, Clone, Serialize, Debug, Hash, Eq, PartialEq)] pub struct BytesPerSecond(NonZeroU64);