diff --git a/src/config.rs b/src/config.rs index 05336a5..ae176b2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,9 @@ -use std::num::{NonZeroU16, NonZeroU64}; -use std::path::PathBuf; -use std::sync::atomic::AtomicBool; +use std::{fmt::Display, path::PathBuf}; +use std::{fmt::Formatter, sync::atomic::AtomicBool}; +use std::{ + num::{NonZeroU16, NonZeroU64}, + str::FromStr, +}; use clap::{crate_authors, crate_description, crate_version, Clap}; use url::Url; @@ -43,7 +46,6 @@ pub struct CliArgs { short, long, conflicts_with("memory-quota"), - conflicts_with("use-lfu"), env = "LOW_MEMORY_MODE", takes_value = false )] @@ -57,16 +59,46 @@ pub struct CliArgs { /// respectively. #[clap(short, long, parse(from_occurrences), conflicts_with = "verbose")] pub quiet: usize, - /// Overrides the upstream URL to fetch images from. Don't use this unless - /// you know what you're dealing with. + #[clap(short = 'Z', long)] + pub unstable_options: Vec, #[clap(long)] pub override_upstream: Option, - /// Disables token validation. Don't use this unless you know the - /// ramifications of this command. - #[clap(long)] - pub disable_token_validation: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UnstableOptions { + /// Overrides the upstream URL to fetch images from. Don't use this unless + /// you know what you're dealing with. + OverrideUpstream, + /// Use an LFU implementation for the in-memory cache instead of the default /// LRU implementation. - #[clap(short = 'F', long)] - pub use_lfu: bool, + UseLfu, + + /// Disables token validation. Don't use this unless you know the + /// ramifications of this command. + DisableTokenValidation, +} + +impl FromStr for UnstableOptions { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "override-upstream" => Ok(Self::OverrideUpstream), + "use-lfu" => Ok(Self::UseLfu), + "disable-token-validation" => Ok(Self::DisableTokenValidation), + _ => Err("Unknown unstable option"), + } + } +} + +impl Display for UnstableOptions { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::OverrideUpstream => write!(f, "override-upstream"), + Self::UseLfu => write!(f, "use-lfu"), + Self::DisableTokenValidation => write!(f, "disable-token-validation"), + } + } } diff --git a/src/main.rs b/src/main.rs index 0b63a12..b4921c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,16 @@ // We're end users, so these is ok #![allow(clippy::module_name_repetitions)] -use std::env::{self, VarError}; -use std::hint::unreachable_unchecked; use std::num::ParseIntError; use std::process; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; +use std::{ + env::{self, VarError}, + fmt::Display, +}; +use std::{error::Error, hint::unreachable_unchecked}; use actix_web::rt::{spawn, time, System}; use actix_web::web::{self, Data}; @@ -24,8 +27,11 @@ use state::{RwLockServerState, ServerState}; use stop::send_stop; use thiserror::Error; -use crate::cache::{MemoryLfuCache, MemoryLruCache}; use crate::state::DynamicServerCert; +use crate::{ + cache::{MemoryLfuCache, MemoryLruCache}, + config::UnstableOptions, +}; mod cache; mod config; @@ -50,7 +56,7 @@ enum ServerError { } #[actix_web::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> { // It's ok to fail early here, it would imply we have a invalid config. dotenv::dotenv().ok(); let cli_args = CliArgs::parse(); @@ -60,7 +66,7 @@ async fn main() -> Result<(), Box> { let disk_quota = cli_args.disk_quota; let cache_path = cli_args.cache_path.clone(); let low_mem_mode = cli_args.low_memory; - let use_lfu = cli_args.use_lfu; + let use_lfu = cli_args.unstable_options.contains(&UnstableOptions::UseLfu); let log_level = match (cli_args.quiet, cli_args.verbose) { (n, _) if n > 2 => LevelFilter::Off, @@ -74,7 +80,10 @@ async fn main() -> Result<(), Box> { SimpleLogger::new().with_level(log_level).init()?; - print_preamble_and_warnings(); + if let Err(e) = print_preamble_and_warnings(&cli_args) { + error!("{}", e); + return Err(e); + } let client_secret = if let Ok(v) = env::var("CLIENT_SECRET") { v @@ -163,7 +172,28 @@ async fn main() -> Result<(), Box> { Ok(()) } -fn print_preamble_and_warnings() { +#[derive(Debug)] +enum InvalidCombination { + MissingUnstableOption(&'static str, UnstableOptions), +} + +impl Display for InvalidCombination { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InvalidCombination::MissingUnstableOption(opt, arg) => { + write!( + f, + "The option '{}' requires the unstable option '-Z {}'", + opt, arg + ) + } + } + } +} + +impl Error for InvalidCombination {} + +fn print_preamble_and_warnings(args: &CliArgs) -> Result<(), Box> { println!(concat!( env!("CARGO_PKG_NAME"), " ", @@ -186,4 +216,21 @@ fn print_preamble_and_warnings() { env!("CARGO_PKG_NAME"), ". If not, see .\n" )); + + if !args.unstable_options.is_empty() { + warn!("Unstable options are enabled. These options should not be used in production!"); + } + + if args.override_upstream.is_some() + && !args + .unstable_options + .contains(&UnstableOptions::OverrideUpstream) + { + Err(Box::new(InvalidCombination::MissingUnstableOption( + "override-upstream", + UnstableOptions::OverrideUpstream, + ))) + } else { + Ok(()) + } } diff --git a/src/ping.rs b/src/ping.rs index 32f6fdf..c9461b9 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -11,12 +11,12 @@ use serde::{Deserialize, Serialize}; use sodiumoxide::crypto::box_::PrecomputedKey; use url::Url; -use crate::client_api_version; use crate::config::{CliArgs, VALIDATE_TOKENS}; use crate::state::{ RwLockServerState, PREVIOUSLY_COMPROMISED, PREVIOUSLY_PAUSED, TLS_CERTS, TLS_PREVIOUSLY_CREATED, TLS_SIGNING_KEY, }; +use crate::{client_api_version, config::UnstableOptions}; pub const CONTROL_CENTER_PING_URL: &str = "https://api.mangadex.network/ping"; @@ -170,7 +170,9 @@ pub async fn update_server_state(secret: &str, cli: &CliArgs, data: &mut Arc { - warn!("Got malformed response: {}", e); + error!("Got malformed response: {}. Is MangaDex@Home down?", e); Err(ServerInitError::MalformedResponse(e)) } }, Err(e) => match e { e if e.is_timeout() => { - error!("Response timed out to control server. Is MangaDex down?"); + error!("Response timed out to control server. Is MangaDex@Home down?"); Err(ServerInitError::Timeout(e)) } e => { - warn!("Failed to send request: {}", e); + error!("Failed to send request: {}", e); Err(ServerInitError::SendFailure(e)) } },