Compare commits

...

9 commits

18 changed files with 617 additions and 191 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@
flamegraph*.svg flamegraph*.svg
perf.data* perf.data*
dhat.out.* dhat.out.*
settings.yaml

90
Cargo.lock generated
View file

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "actix-codec" name = "actix-codec"
version = "0.4.0" version = "0.4.0"
@ -563,13 +565,14 @@ dependencies = [
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.14" version = "0.99.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320" checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version 0.3.3",
"syn", "syn",
] ]
@ -600,6 +603,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]] [[package]]
name = "ed25519" name = "ed25519"
version = "1.1.1" version = "1.1.1"
@ -1105,6 +1114,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "local-channel" name = "local-channel"
version = "0.1.2" version = "0.1.2"
@ -1139,6 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"serde",
] ]
[[package]] [[package]]
@ -1176,6 +1192,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serde_yaml",
"simple_logger", "simple_logger",
"sodiumoxide", "sodiumoxide",
"sqlx", "sqlx",
@ -1361,6 +1378,15 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.0.7" version = "1.0.7"
@ -1628,6 +1654,15 @@ dependencies = [
"semver 0.9.0", "semver 0.9.0",
] ]
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver 0.11.0",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -1693,7 +1728,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [ dependencies = [
"semver-parser", "semver-parser 0.7.0",
]
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser 0.10.2",
] ]
[[package]] [[package]]
@ -1708,6 +1752,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.126" version = "1.0.126"
@ -1762,6 +1815,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_yaml"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
dependencies = [
"dtoa",
"linked-hash-map",
"serde",
"yaml-rust",
]
[[package]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.9.6" version = "0.9.6"
@ -2055,9 +2120,9 @@ dependencies = [
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.19.0" version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41c72bb3368a39bb153f226d8dd426ee33d54bc31f44037c078391b4a0b8dc04" checksum = "a68b8aff80646f09ec11e59b56091a5278ee527d5baeca938f1a5dbd9a15a859"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"core-foundation-sys", "core-foundation-sys",
@ -2292,6 +2357,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.5" version = "0.3.5"
@ -2569,6 +2640,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.7.0+zstd.1.4.9" version = "0.7.0+zstd.1.4.9"

View file

@ -26,7 +26,7 @@ ctrlc = "3"
dotenv = "0.15" dotenv = "0.15"
futures = "0.3" futures = "0.3"
once_cell = "1" once_cell = "1"
log = "0.4" log = { version = "0.4", features = [ "serde" ] }
lfu_cache = "1" lfu_cache = "1"
lru = "0.6" lru = "0.6"
parking_lot = "0.11" parking_lot = "0.11"
@ -36,6 +36,7 @@ rustls = "0.19"
serde = "1" serde = "1"
serde_json = "1" serde_json = "1"
serde_repr = "0.1" serde_repr = "0.1"
serde_yaml = "0.8"
simple_logger = "1" simple_logger = "1"
sodiumoxide = "0.2" sodiumoxide = "0.2"
sqlx = { version = "0.5", features = [ "runtime-actix-rustls", "sqlite", "time", "chrono", "macros" ] } sqlx = { version = "0.5", features = [ "runtime-actix-rustls", "sqlite", "time", "chrono", "macros" ] }

View file

@ -1,10 +1,25 @@
use std::error::Error; use std::error::Error;
use std::process::Command;
use vergen::{vergen, Config, ShaKind}; use vergen::{vergen, Config, ShaKind};
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
// Initialize vergen stuff
let mut config = Config::default(); let mut config = Config::default();
*config.git_mut().sha_kind_mut() = ShaKind::Short; *config.git_mut().sha_kind_mut() = ShaKind::Short;
vergen(config)?; vergen(config)?;
// Initialize SQL stuff
let project_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
Command::new("mkdir")
.args(["cache", "--parents"])
.current_dir(&project_root)
.output()?;
Command::new("sqlite3")
.args(["cache/metadata.sqlite", include_str!("db_queries/init.sql")])
.current_dir(&project_root)
.output()?;
Ok(()) Ok(())
} }

14
docs/ciphers.md Normal file
View file

@ -0,0 +1,14 @@
# Ciphers
This client relies on rustls, which only supports a subset of TLS ciphers.
Specifically, only TLS 1.2 ECDSA GCM ciphers as well as all TLS 1.3 ciphers are
supported. This means that clients that only support older, more insecure
ciphers may not be able to connect to this client.
In practice, this means this client's failure rate may be higher than expected.
This is okay, and within specifications.
## Why even bother?
Well, Australia has officially banned hentai... so I gotta make sure my mates
over there won't get in trouble if I'm connecting to them.

14
docs/unstable_options.md Normal file
View file

@ -0,0 +1,14 @@
# Unstable Options
Unstable options are options that are either experimental, dangerous, for
development only, or a mix of the three. The following table describes each
option. Generally speaking, you should never need to enable these unless you
know what you're doing.
| Option | Experimental? | Dangerous? | For development? |
| -------------------------- | ------------- | ---------- | ---------------- |
| `override-upstream` | | | Yes |
| `use-lfu` | Yes | | |
| `disable-token-validation` | | Yes | Yes |
| `offline-mode` | | | Yes |
| `disable-tls` | | Yes | Yes |

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
# This script needs to be run once in order for compile time macros to not
# complain about a missing DB
# We can trust that our program will initialize the db at runtime the same way
# as it pulls from the same file for initialization
mkdir cache
sqlite3 cache/metadata.sqlite < db_queries/init.sql

57
settings.sample.yaml Normal file
View file

@ -0,0 +1,57 @@
---
# ⢸⣿⣿⣿⣿⠃⠄⢀⣴⡾⠃⠄⠄⠄⠄⠄⠈⠺⠟⠛⠛⠛⠛⠻⢿⣿⣿⣿⣿⣶⣤⡀⠄
# ⢸⣿⣿⣿⡟⢀⣴⣿⡿⠁⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣸⣿⣿⣿⣿⣿⣿⣿⣷
# ⢸⣿⣿⠟⣴⣿⡿⡟⡼⢹⣷⢲⡶⣖⣾⣶⢄⠄⠄⠄⠄⠄⢀⣼⣿⢿⣿⣿⣿⣿⣿⣿⣿
# ⢸⣿⢫⣾⣿⡟⣾⡸⢠⡿⢳⡿⠍⣼⣿⢏⣿⣷⢄⡀⠄⢠⣾⢻⣿⣸⣿⣿⣿⣿⣿⣿⣿
# ⡿⣡⣿⣿⡟⡼⡁⠁⣰⠂⡾⠉⢨⣿⠃⣿⡿⠍⣾⣟⢤⣿⢇⣿⢇⣿⣿⢿⣿⣿⣿⣿⣿
# ⣱⣿⣿⡟⡐⣰⣧⡷⣿⣴⣧⣤⣼⣯⢸⡿⠁⣰⠟⢀⣼⠏⣲⠏⢸⣿⡟⣿⣿⣿⣿⣿⣿
# ⣿⣿⡟⠁⠄⠟⣁⠄⢡⣿⣿⣿⣿⣿⣿⣦⣼⢟⢀⡼⠃⡹⠃⡀⢸⡿⢸⣿⣿⣿⣿⣿⡟
# ⣿⣿⠃⠄⢀⣾⠋⠓⢰⣿⣿⣿⣿⣿⣿⠿⣿⣿⣾⣅⢔⣕⡇⡇⡼⢁⣿⣿⣿⣿⣿⣿⢣
# ⣿⡟⠄⠄⣾⣇⠷⣢⣿⣿⣿⣿⣿⣿⣿⣭⣀⡈⠙⢿⣿⣿⡇⡧⢁⣾⣿⣿⣿⣿⣿⢏⣾
# ⣿⡇⠄⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⢻⠇⠄⠄⢿⣿⡇⢡⣾⣿⣿⣿⣿⣿⣏⣼⣿
# ⣿⣷⢰⣿⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⢰⣧⣀⡄⢀⠘⡿⣰⣿⣿⣿⣿⣿⣿⠟⣼⣿⣿
# ⢹⣿⢸⣿⣿⠟⠻⢿⣿⣿⣿⣿⣿⣿⣿⣶⣭⣉⣤⣿⢈⣼⣿⣿⣿⣿⣿⣿⠏⣾⣹⣿⣿
# ⢸⠇⡜⣿⡟⠄⠄⠄⠈⠙⣿⣿⣿⣿⣿⣿⣿⣿⠟⣱⣻⣿⣿⣿⣿⣿⠟⠁⢳⠃⣿⣿⣿
# ⠄⣰⡗⠹⣿⣄⠄⠄⠄⢀⣿⣿⣿⣿⣿⣿⠟⣅⣥⣿⣿⣿⣿⠿⠋⠄⠄⣾⡌⢠⣿⡿⠃
# ⠜⠋⢠⣷⢻⣿⣿⣶⣾⣿⣿⣿⣿⠿⣛⣥⣾⣿⠿⠟⠛⠉⠄⠄
#
# 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,
# but just know the two are **NOT** the same.
max_cache_size_in_mebibytes: 0
server_settings:
# 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 max appeal.
# port: 443
# This controls the value the server receives for your upload speed.
external_max_kilobits_per_second: 0
#
# Advanced settings
#
# 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
# 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
# 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: ~

9
src/cache/disk.rs vendored
View file

@ -6,7 +6,6 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use bytes::Bytes;
use futures::StreamExt; use futures::StreamExt;
use log::{error, warn, LevelFilter}; use log::{error, warn, LevelFilter};
use sqlx::sqlite::SqliteConnectOptions; use sqlx::sqlite::SqliteConnectOptions;
@ -15,6 +14,8 @@ use tokio::fs::remove_file;
use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio_stream::wrappers::ReceiverStream; use tokio_stream::wrappers::ReceiverStream;
use crate::units::Bytes;
use super::{ use super::{
BoxedImageStream, Cache, CacheError, CacheKey, CacheStream, CallbackCache, ImageMetadata, BoxedImageStream, Cache, CacheError, CacheKey, CacheStream, CallbackCache, ImageMetadata,
}; };
@ -34,7 +35,7 @@ impl DiskCache {
/// Constructs a new low memory cache at the provided path and capacity. /// Constructs a new low memory cache at the provided path and capacity.
/// This internally spawns a task that will wait for filesystem /// This internally spawns a task that will wait for filesystem
/// notifications when a file has been written. /// notifications when a file has been written.
pub async fn new(disk_max_size: u64, disk_path: PathBuf) -> Arc<Self> { pub async fn new(disk_max_size: Bytes, disk_path: PathBuf) -> Arc<Self> {
let (db_tx, db_rx) = channel(128); let (db_tx, db_rx) = channel(128);
let db_pool = { let db_pool = {
let db_url = format!("sqlite:{}/metadata.sqlite", disk_path.to_string_lossy()); let db_url = format!("sqlite:{}/metadata.sqlite", disk_path.to_string_lossy());
@ -75,7 +76,7 @@ impl DiskCache {
Arc::clone(&new_self), Arc::clone(&new_self),
db_rx, db_rx,
db_pool, db_pool,
disk_max_size / 20 * 19, disk_max_size.get() as u64 / 20 * 19,
)); ));
new_self new_self
@ -237,7 +238,7 @@ impl CallbackCache for DiskCache {
key: CacheKey, key: CacheKey,
image: BoxedImageStream, image: BoxedImageStream,
metadata: ImageMetadata, metadata: ImageMetadata,
on_complete: Sender<(CacheKey, Bytes, ImageMetadata, u64)>, on_complete: Sender<(CacheKey, bytes::Bytes, ImageMetadata, u64)>,
) -> Result<CacheStream, CacheError> { ) -> Result<CacheStream, CacheError> {
let channel = self.db_update_channel_sender.clone(); let channel = self.db_update_channel_sender.clone();

6
src/cache/fs.rs vendored
View file

@ -111,21 +111,21 @@ pub(super) async fn read_file(
return None; 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 header
} else { } else {
warn!("Found file, but encrypted header was invalid. Assuming corrupted!"); warn!("Found file, but encrypted header was invalid. Assuming corrupted!");
return None; 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 stream
} else { } else {
warn!("Failed to init secret stream with key and header. Assuming corrupted!"); warn!("Failed to init secret stream with key and header. Assuming corrupted!");
return None; return None;
}; };
maybe_header = Some(header); maybe_header = Some(file_header);
reader = Some(Box::pin(EncryptedDiskReader::new(file, secret_stream))); reader = Some(Box::pin(EncryptedDiskReader::new(file, secret_stream)));
} }

6
src/cache/mem.rs vendored
View file

@ -86,7 +86,7 @@ where
MemoryCacheImpl: 'static + InternalMemoryCache, MemoryCacheImpl: 'static + InternalMemoryCache,
ColdCache: 'static + Cache, ColdCache: 'static + Cache,
{ {
pub async fn new(inner: ColdCache, max_mem_size: u64) -> Arc<Self> { pub async fn new(inner: ColdCache, max_mem_size: crate::units::Bytes) -> Arc<Self> {
let (tx, mut rx) = channel(100); let (tx, mut rx) = channel(100);
let new_self = Arc::new(Self { let new_self = Arc::new(Self {
inner, inner,
@ -98,7 +98,7 @@ where
let new_self_0 = Arc::clone(&new_self); let new_self_0 = Arc::clone(&new_self);
tokio::spawn(async move { tokio::spawn(async move {
let new_self = new_self_0; let new_self = new_self_0;
let max_mem_size = max_mem_size / 20 * 19; let max_mem_size = max_mem_size.get() / 20 * 19;
while let Some((key, bytes, metadata, size)) = rx.recv().await { while let Some((key, bytes, metadata, size)) = rx.recv().await {
// Add to memory cache // Add to memory cache
// We can add first because we constrain our memory usage to 95% // We can add first because we constrain our memory usage to 95%
@ -112,7 +112,7 @@ where
.push(key, (bytes, metadata, size)); .push(key, (bytes, metadata, size));
// Pop if too large // Pop if too large
while new_self.cur_mem_size.load(Ordering::Acquire) >= max_mem_size { while new_self.cur_mem_size.load(Ordering::Acquire) >= max_mem_size as u64 {
let popped = new_self let popped = new_self
.mem_cache .mem_cache
.lock() .lock()

View file

@ -1,59 +1,275 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::num::{NonZeroU16, NonZeroU64}; use std::fs::{File, OpenOptions};
use std::path::PathBuf; use std::hint::unreachable_unchecked;
use std::io::{ErrorKind, Write};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::num::NonZeroU16;
use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::AtomicBool; use std::sync::atomic::{AtomicBool, Ordering};
use clap::{crate_authors, crate_description, crate_version, Clap}; use clap::{crate_authors, crate_description, crate_version, Clap};
use log::LevelFilter;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use url::Url; use url::Url;
use crate::units::{KilobitsPerSecond, Mebibytes, Port};
// Validate tokens is an atomic because it's faster than locking on rwlock. // Validate tokens is an atomic because it's faster than locking on rwlock.
pub static VALIDATE_TOKENS: AtomicBool = AtomicBool::new(false); pub static VALIDATE_TOKENS: AtomicBool = AtomicBool::new(false);
// We use an atomic here because it's better for us to not pass the config
// everywhere.
pub static SEND_SERVER_VERSION: AtomicBool = AtomicBool::new(false);
pub static OFFLINE_MODE: AtomicBool = AtomicBool::new(false); pub static OFFLINE_MODE: AtomicBool = AtomicBool::new(false);
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("No config found. One has been created for you to modify.")]
NotInitialized,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Parse(#[from] serde_yaml::Error),
}
pub fn load_config() -> Result<Config, ConfigError> {
// Load cli args first
let cli_args: CliArgs = CliArgs::parse();
// Load yaml file next
let config_file: Result<YamlArgs, _> = {
let config_path = cli_args
.config_path
.as_deref()
.unwrap_or_else(|| Path::new("./settings.yaml"));
match File::open(config_path) {
Ok(file) => serde_yaml::from_reader(file),
Err(e) if e.kind() == ErrorKind::NotFound => {
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(config_path)
.unwrap();
let default_config = include_str!("../settings.sample.yaml");
file.write_all(default_config.as_bytes()).unwrap();
return Err(ConfigError::NotInitialized);
}
Err(e) => return Err(e.into()),
}
};
// generate config
let config = Config::from_cli_and_file(cli_args, config_file?);
// initialize globals
OFFLINE_MODE.store(
config
.unstable_options
.contains(&UnstableOptions::OfflineMode),
Ordering::Release,
);
Ok(config)
}
#[derive(Debug)]
/// Represents a fully parsed config, from a variety of sources.
pub struct Config {
pub cache_type: CacheType,
pub cache_path: PathBuf,
pub shutdown_timeout: NonZeroU16,
pub log_level: LevelFilter,
pub client_secret: ClientSecret,
pub port: Port,
pub bind_address: SocketAddr,
pub external_address: Option<SocketAddr>,
pub ephemeral_disk_encryption: bool,
pub network_speed: KilobitsPerSecond,
pub disk_quota: Mebibytes,
pub memory_quota: Mebibytes,
pub unstable_options: Vec<UnstableOptions>,
pub override_upstream: Option<Url>,
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_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() },
};
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: if let Ok(v) = std::env::var("CLIENT_SECRET") {
ClientSecret(v)
} else {
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,
}
}
}
#[derive(Deserialize)]
struct YamlArgs {
// Naming is legacy
max_cache_size_in_mebibytes: Mebibytes,
server_settings: YamlServerSettings,
// This implementation custom options
extended_options: Option<YamlExtendedOptions>,
}
// Naming is legacy
#[derive(Deserialize)]
struct YamlServerSettings {
secret: ClientSecret,
#[serde(default)]
port: Port,
external_max_kilobits_per_second: KilobitsPerSecond,
external_port: Option<Port>,
graceful_shutdown_wait_seconds: Option<NonZeroU16>,
hostname: Option<IpAddr>,
external_ip: Option<IpAddr>,
}
// this intentionally does not implement display
#[derive(Deserialize, Serialize, Clone)]
pub struct ClientSecret(String);
impl std::fmt::Debug for ClientSecret {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "[client secret]")
}
}
#[derive(Deserialize, Default)]
struct YamlExtendedOptions {
memory_quota: Option<Mebibytes>,
cache_type: Option<CacheType>,
ephemeral_disk_encryption: Option<bool>,
enable_metrics: Option<bool>,
logging_level: Option<LevelFilter>,
cache_path: Option<PathBuf>,
}
#[derive(Deserialize, Copy, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum CacheType {
OnDisk,
Lru,
Lfu,
}
impl FromStr for CacheType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"on_disk" => Ok(Self::OnDisk),
"lru" => Ok(Self::Lru),
"lfu" => Ok(Self::Lfu),
_ => Err(format!("Unknown option: {}", s)),
}
}
}
impl Default for CacheType {
fn default() -> Self {
Self::OnDisk
}
}
#[derive(Clap, Clone)] #[derive(Clap, Clone)]
#[clap(version = crate_version!(), author = crate_authors!(), about = crate_description!())] #[clap(version = crate_version!(), author = crate_authors!(), about = crate_description!())]
pub struct CliArgs { struct CliArgs {
/// The port to listen on. /// The port to listen on.
#[clap(short, long, default_value = "42069", env = "PORT")] #[clap(short, long)]
pub port: NonZeroU16, pub port: Option<Port>,
/// How large, in bytes, the in-memory cache should be. Note that this does /// How large, in mebibytes, the in-memory cache should be. Note that this
/// not include runtime memory usage. /// does not include runtime memory usage.
#[clap(long, env = "MEM_CACHE_QUOTA_BYTES", conflicts_with = "low-memory")] #[clap(long)]
pub memory_quota: Option<NonZeroU64>, pub memory_quota: Option<Mebibytes>,
/// How large, in bytes, the on-disk cache should be. Note that actual /// How large, in mebibytes, the on-disk cache should be. Note that actual
/// values may be larger for metadata information. /// values may be larger for metadata information.
#[clap(long, env = "DISK_CACHE_QUOTA_BYTES")] #[clap(long)]
pub disk_quota: u64, pub disk_quota: Option<Mebibytes>,
/// Sets the location of the disk cache. /// Sets the location of the disk cache.
#[clap(long, default_value = "./cache", env = "DISK_CACHE_PATH")] #[clap(long)]
pub cache_path: PathBuf, pub cache_path: Option<PathBuf>,
/// The network speed to advertise to Mangadex@Home control server. /// The network speed to advertise to Mangadex@Home control server.
#[clap(long, env = "MAX_NETWORK_SPEED")] #[clap(long)]
pub network_speed: NonZeroU64, pub network_speed: Option<KilobitsPerSecond>,
/// Whether or not to provide the Server HTTP header to clients. This is
/// useful for debugging, but is generally not recommended for security
/// reasons.
#[clap(long, env = "ENABLE_SERVER_STRING", takes_value = false)]
pub enable_server_string: bool,
/// Changes the caching behavior to avoid buffering images in memory, and
/// instead use the filesystem as the buffer backing. This is useful for
/// clients in low (< 1GB) RAM environments.
#[clap(
short,
long,
conflicts_with("memory-quota"),
env = "LOW_MEMORY_MODE",
takes_value = false
)]
pub low_memory: bool,
/// Changes verbosity. Default verbosity is INFO, while increasing counts of /// Changes verbosity. Default verbosity is INFO, while increasing counts of
/// verbose flags increases the verbosity to DEBUG and TRACE, respectively. /// 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, pub verbose: usize,
/// Changes verbosity. Default verbosity is INFO, while increasing counts of /// Changes verbosity. Default verbosity is INFO, while increasing counts of
/// quiet flags decreases the verbosity to WARN, ERROR, and no logs, /// quiet flags decreases the verbosity to WARN, ERROR, and no logs,
@ -68,7 +284,11 @@ pub struct CliArgs {
/// encrypted with a key generated at runtime. There are implications to /// encrypted with a key generated at runtime. There are implications to
/// performance, privacy, and usability with this flag enabled. /// performance, privacy, and usability with this flag enabled.
#[clap(short, long)] #[clap(short, long)]
pub ephemeral_disk_encryption: bool, pub ephemeral_disk_encryption: Option<bool>,
#[clap(short, long)]
pub config_path: Option<PathBuf>,
#[clap(short = 't', long)]
pub cache_type: Option<CacheType>,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -77,10 +297,6 @@ pub enum UnstableOptions {
/// you know what you're dealing with. /// you know what you're dealing with.
OverrideUpstream, OverrideUpstream,
/// Use an LFU implementation for the in-memory cache instead of the default
/// LRU implementation.
UseLfu,
/// Disables token validation. Don't use this unless you know the /// Disables token validation. Don't use this unless you know the
/// ramifications of this command. /// ramifications of this command.
DisableTokenValidation, DisableTokenValidation,
@ -98,7 +314,6 @@ impl FromStr for UnstableOptions {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"override-upstream" => Ok(Self::OverrideUpstream), "override-upstream" => Ok(Self::OverrideUpstream),
"use-lfu" => Ok(Self::UseLfu),
"disable-token-validation" => Ok(Self::DisableTokenValidation), "disable-token-validation" => Ok(Self::DisableTokenValidation),
"offline-mode" => Ok(Self::OfflineMode), "offline-mode" => Ok(Self::OfflineMode),
"disable-tls" => Ok(Self::DisableTls), "disable-tls" => Ok(Self::DisableTls),
@ -111,7 +326,6 @@ impl Display for UnstableOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::OverrideUpstream => write!(f, "override-upstream"), Self::OverrideUpstream => write!(f, "override-upstream"),
Self::UseLfu => write!(f, "use-lfu"),
Self::DisableTokenValidation => write!(f, "disable-token-validation"), Self::DisableTokenValidation => write!(f, "disable-token-validation"),
Self::OfflineMode => write!(f, "offline-mode"), Self::OfflineMode => write!(f, "offline-mode"),
Self::DisableTls => write!(f, "disable-tls"), Self::DisableTls => write!(f, "disable-tls"),

View file

@ -2,12 +2,10 @@
// We're end users, so these is ok // We're end users, so these is ok
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
use std::env::{self, VarError}; use std::env::VarError;
use std::error::Error; use std::error::Error;
use std::fmt::Display; use std::fmt::Display;
use std::hint::unreachable_unchecked; use std::num::ParseIntError;
use std::num::{NonZeroU64, ParseIntError};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -16,9 +14,8 @@ use actix_web::rt::{spawn, time, System};
use actix_web::web::{self, Data}; use actix_web::web::{self, Data};
use actix_web::{App, HttpResponse, HttpServer}; use actix_web::{App, HttpResponse, HttpServer};
use cache::{Cache, DiskCache}; use cache::{Cache, DiskCache};
use clap::Clap; use config::Config;
use config::CliArgs; use log::{debug, error, info, warn};
use log::{debug, error, info, warn, LevelFilter};
use parking_lot::RwLock; use parking_lot::RwLock;
use rustls::{NoClientAuth, ServerConfig}; use rustls::{NoClientAuth, ServerConfig};
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
@ -29,7 +26,7 @@ use thiserror::Error;
use crate::cache::mem::{Lfu, Lru}; use crate::cache::mem::{Lfu, Lru};
use crate::cache::{MemoryCache, ENCRYPTION_KEY}; use crate::cache::{MemoryCache, ENCRYPTION_KEY};
use crate::config::{UnstableOptions, OFFLINE_MODE}; use crate::config::{CacheType, UnstableOptions, OFFLINE_MODE};
use crate::state::DynamicServerCert; use crate::state::DynamicServerCert;
mod cache; mod cache;
@ -39,13 +36,9 @@ mod ping;
mod routes; mod routes;
mod state; mod state;
mod stop; mod stop;
mod units;
#[macro_export] const CLIENT_API_VERSION: usize = 31;
macro_rules! client_api_version {
() => {
"31"
};
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
enum ServerError { enum ServerError {
@ -65,77 +58,58 @@ async fn main() -> Result<(), Box<dyn Error>> {
// Config loading // Config loading
// //
let cli_args = CliArgs::parse(); let config = match config::load_config() {
let port = cli_args.port; Ok(c) => c,
let memory_max_size = cli_args Err(e) => {
.memory_quota eprintln!("{}", e);
.map(NonZeroU64::get) return Err(Box::new(e) as Box<_>);
.unwrap_or_default(); }
let disk_quota = cli_args.disk_quota; };
let cache_path = cli_args.cache_path.clone();
let low_mem_mode = cli_args.low_memory; let memory_quota = config.memory_quota;
let use_lfu = cli_args.unstable_options.contains(&UnstableOptions::UseLfu); let disk_quota = config.disk_quota;
let disable_tls = cli_args let cache_type = config.cache_type;
let cache_path = config.cache_path.clone();
let disable_tls = config
.unstable_options .unstable_options
.contains(&UnstableOptions::DisableTls); .contains(&UnstableOptions::DisableTls);
OFFLINE_MODE.store( let bind_address = config.bind_address;
cli_args
.unstable_options
.contains(&UnstableOptions::OfflineMode),
Ordering::Release,
);
// //
// Logging and warnings // Logging and warnings
// //
let log_level = match (cli_args.quiet, cli_args.verbose) { SimpleLogger::new().with_level(config.log_level).init()?;
(n, _) if n > 2 => LevelFilter::Off,
(2, _) => LevelFilter::Error,
(1, _) => LevelFilter::Warn,
(0, 0) => LevelFilter::Info,
(_, 1) => LevelFilter::Debug,
(_, n) if n > 1 => LevelFilter::Trace,
// compiler can't figure it out
_ => unsafe { unreachable_unchecked() },
};
SimpleLogger::new().with_level(log_level).init()?; if let Err(e) = print_preamble_and_warnings(&config) {
if let Err(e) = print_preamble_and_warnings(&cli_args) {
error!("{}", e); error!("{}", e);
return Err(e); return Err(e);
} }
let client_secret = if let Ok(v) = env::var("CLIENT_SECRET") { debug!("{:?}", &config);
v
} else {
error!("Client secret not found in ENV. Please set CLIENT_SECRET.");
process::exit(1);
};
let client_secret_1 = client_secret.clone();
if cli_args.ephemeral_disk_encryption { let client_secret = config.client_secret.clone();
let client_secret_1 = config.client_secret.clone();
if config.ephemeral_disk_encryption {
info!("Running with at-rest encryption!"); info!("Running with at-rest encryption!");
ENCRYPTION_KEY.set(gen_key()).unwrap(); ENCRYPTION_KEY.set(gen_key()).unwrap();
} }
metrics::init(); if config.enable_metrics {
metrics::init();
}
// HTTP Server init // HTTP Server init
let server = if OFFLINE_MODE.load(Ordering::Acquire) { let server = if OFFLINE_MODE.load(Ordering::Acquire) {
ServerState::init_offline() ServerState::init_offline()
} else { } else {
ServerState::init(&client_secret, &cli_args).await? ServerState::init(&client_secret, &config).await?
}; };
let data_0 = Arc::new(RwLockServerState(RwLock::new(server))); let data_0 = Arc::new(RwLockServerState(RwLock::new(server)));
let data_1 = Arc::clone(&data_0); let data_1 = Arc::clone(&data_0);
// What's nice is that Rustls only supports TLS 1.2 and 1.3.
let mut tls_config = ServerConfig::new(NoClientAuth::new());
tls_config.cert_resolver = Arc::new(DynamicServerCert);
// //
// At this point, the server is ready to start, and starts the necessary // At this point, the server is ready to start, and starts the necessary
// threads. // threads.
@ -171,18 +145,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
loop { loop {
interval.tick().await; interval.tick().await;
debug!("Sending ping!"); debug!("Sending ping!");
ping::update_server_state(&client_secret_1, &cli_args, &mut data).await; ping::update_server_state(&client_secret_1, &config, &mut data).await;
} }
}); });
} }
let cache = DiskCache::new(disk_quota, cache_path.clone()).await; let memory_max_size = memory_quota.into();
let cache: Arc<dyn Cache> = if low_mem_mode { let cache = DiskCache::new(disk_quota.into(), cache_path.clone()).await;
cache let cache: Arc<dyn Cache> = match cache_type {
} else if use_lfu { CacheType::OnDisk => cache,
MemoryCache::<Lfu, _>::new(cache, memory_max_size).await CacheType::Lru => MemoryCache::<Lfu, _>::new(cache, memory_max_size).await,
} else { CacheType::Lfu => MemoryCache::<Lru, _>::new(cache, memory_max_size).await,
MemoryCache::<Lru, _>::new(cache, memory_max_size).await
}; };
let cache_0 = Arc::clone(&cache); let cache_0 = Arc::clone(&cache);
@ -209,12 +182,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
.shutdown_timeout(60); .shutdown_timeout(60);
if disable_tls { if disable_tls {
server.bind(format!("0.0.0.0:{}", port))?.run().await?; server.bind(bind_address)?.run().await?;
} else { } else {
server // Rustls only supports TLS 1.2 and 1.3.
.bind_rustls(format!("0.0.0.0:{}", port), tls_config)? let tls_config = {
.run() let mut tls_config = ServerConfig::new(NoClientAuth::new());
.await?; tls_config.cert_resolver = Arc::new(DynamicServerCert);
tls_config
};
server.bind_rustls(bind_address, tls_config)?.run().await?;
} }
// Waiting for us to finish sending stop message // Waiting for us to finish sending stop message
@ -246,7 +223,7 @@ impl Display for InvalidCombination {
impl Error for InvalidCombination {} impl Error for InvalidCombination {}
fn print_preamble_and_warnings(args: &CliArgs) -> Result<(), Box<dyn Error>> { fn print_preamble_and_warnings(args: &Config) -> Result<(), Box<dyn Error>> {
println!(concat!( println!(concat!(
env!("CARGO_PKG_NAME"), env!("CARGO_PKG_NAME"),
" ", " ",

View file

@ -1,4 +1,3 @@
use std::num::{NonZeroU16, NonZeroU64};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::{io::BufReader, sync::Arc}; use std::{io::BufReader, sync::Arc};
@ -12,35 +11,34 @@ use serde_repr::Deserialize_repr;
use sodiumoxide::crypto::box_::PrecomputedKey; use sodiumoxide::crypto::box_::PrecomputedKey;
use url::Url; use url::Url;
use crate::config::{CliArgs, VALIDATE_TOKENS}; use crate::config::{ClientSecret, Config, UnstableOptions, VALIDATE_TOKENS};
use crate::state::{ use crate::state::{
RwLockServerState, PREVIOUSLY_COMPROMISED, PREVIOUSLY_PAUSED, TLS_CERTS, RwLockServerState, PREVIOUSLY_COMPROMISED, PREVIOUSLY_PAUSED, TLS_CERTS,
TLS_PREVIOUSLY_CREATED, TLS_SIGNING_KEY, TLS_PREVIOUSLY_CREATED, TLS_SIGNING_KEY,
}; };
use crate::{client_api_version, config::UnstableOptions}; use crate::units::{BytesPerSecond, Mebibytes, Port};
use crate::CLIENT_API_VERSION;
pub const CONTROL_CENTER_PING_URL: &str = "https://api.mangadex.network/ping"; pub const CONTROL_CENTER_PING_URL: &str = "https://api.mangadex.network/ping";
#[derive(Serialize, Debug)] #[derive(Serialize)]
pub struct Request<'a> { pub struct Request<'a> {
secret: &'a str, secret: &'a ClientSecret,
port: NonZeroU16, port: Port,
disk_space: u64, disk_space: Mebibytes,
network_speed: NonZeroU64, network_speed: BytesPerSecond,
build_version: u64, build_version: usize,
tls_created_at: Option<String>, tls_created_at: Option<String>,
} }
impl<'a> Request<'a> { impl<'a> Request<'a> {
fn from_config_and_state(secret: &'a str, config: &CliArgs) -> Self { fn from_config_and_state(secret: &'a ClientSecret, config: &Config) -> Self {
Self { Self {
secret, secret,
port: config.port, port: config.port,
disk_space: config.disk_quota, disk_space: config.disk_quota,
network_speed: config.network_speed, network_speed: config.network_speed.into(),
build_version: client_api_version!() build_version: CLIENT_API_VERSION,
.parse()
.expect("to parse the build version"),
tls_created_at: TLS_PREVIOUSLY_CREATED tls_created_at: TLS_PREVIOUSLY_CREATED
.get() .get()
.map(|v| v.load().as_ref().clone()), .map(|v| v.load().as_ref().clone()),
@ -48,17 +46,14 @@ impl<'a> Request<'a> {
} }
} }
#[allow(clippy::fallible_impl_from)] impl<'a> From<(&'a ClientSecret, &Config)> for Request<'a> {
impl<'a> From<(&'a str, &CliArgs)> for Request<'a> { fn from((secret, config): (&'a ClientSecret, &Config)) -> Self {
fn from((secret, config): (&'a str, &CliArgs)) -> Self {
Self { Self {
secret, secret,
port: config.port, port: config.port,
disk_space: config.disk_quota, disk_space: config.disk_quota,
network_speed: config.network_speed, network_speed: config.network_speed.into(),
build_version: client_api_version!() build_version: CLIENT_API_VERSION,
.parse()
.expect("to parse the build version"),
tls_created_at: None, tls_created_at: None,
} }
} }
@ -166,7 +161,11 @@ impl std::fmt::Debug for Tls {
} }
} }
pub async fn update_server_state(secret: &str, cli: &CliArgs, data: &mut Arc<RwLockServerState>) { pub async fn update_server_state(
secret: &ClientSecret,
cli: &Config,
data: &mut Arc<RwLockServerState>,
) {
let req = Request::from_config_and_state(secret, cli); let req = Request::from_config_and_state(secret, cli);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let resp = client.post(CONTROL_CENTER_PING_URL).json(&req).send().await; let resp = client.post(CONTROL_CENTER_PING_URL).json(&req).send().await;

View file

@ -22,15 +22,14 @@ use sodiumoxide::crypto::box_::{open_precomputed, Nonce, PrecomputedKey, NONCEBY
use thiserror::Error; use thiserror::Error;
use crate::cache::{Cache, CacheKey, ImageMetadata, UpstreamError}; use crate::cache::{Cache, CacheKey, ImageMetadata, UpstreamError};
use crate::client_api_version; use crate::config::{OFFLINE_MODE, VALIDATE_TOKENS};
use crate::config::{OFFLINE_MODE, SEND_SERVER_VERSION, VALIDATE_TOKENS};
use crate::metrics::{ use crate::metrics::{
CACHE_HIT_COUNTER, CACHE_MISS_COUNTER, REQUESTS_DATA_COUNTER, REQUESTS_DATA_SAVER_COUNTER, CACHE_HIT_COUNTER, CACHE_MISS_COUNTER, REQUESTS_DATA_COUNTER, REQUESTS_DATA_SAVER_COUNTER,
REQUESTS_OTHER_COUNTER, REQUESTS_TOTAL_COUNTER, REQUESTS_OTHER_COUNTER, REQUESTS_TOTAL_COUNTER,
}; };
use crate::state::RwLockServerState; use crate::state::RwLockServerState;
pub const BASE64_CONFIG: base64::Config = base64::Config::new(base64::CharacterSet::UrlSafe, false); const BASE64_CONFIG: base64::Config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| { static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
Client::builder() Client::builder()
@ -41,15 +40,6 @@ static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
.expect("Client initialization to work") .expect("Client initialization to work")
}); });
const SERVER_ID_STRING: &str = concat!(
env!("CARGO_CRATE_NAME"),
" ",
env!("CARGO_PKG_VERSION"),
" (",
client_api_version!(),
") - Conforming to spec revision b82043289",
);
enum ServerResponse { enum ServerResponse {
TokenValidationError(TokenValidationError), TokenValidationError(TokenValidationError),
HttpResponse(HttpResponse), HttpResponse(HttpResponse),
@ -226,10 +216,6 @@ fn push_headers(builder: &mut HttpResponseBuilder) -> &mut HttpResponseBuilder {
.insert_header((CACHE_CONTROL, "public, max-age=1209600")) .insert_header((CACHE_CONTROL, "public, max-age=1209600"))
.insert_header(("Timing-Allow-Origin", "https://mangadex.org")); .insert_header(("Timing-Allow-Origin", "https://mangadex.org"));
if SEND_SERVER_VERSION.load(Ordering::Acquire) {
builder.insert_header(("Server", SERVER_ID_STRING));
}
builder builder
} }

View file

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use crate::config::{CliArgs, UnstableOptions, OFFLINE_MODE, SEND_SERVER_VERSION, VALIDATE_TOKENS}; use crate::config::{ClientSecret, Config, UnstableOptions, OFFLINE_MODE, VALIDATE_TOKENS};
use crate::ping::{Request, Response, CONTROL_CENTER_PING_URL}; use crate::ping::{Request, Response, CONTROL_CENTER_PING_URL};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use log::{error, info, warn}; use log::{error, info, warn};
@ -45,18 +45,13 @@ pub enum ServerInitError {
} }
impl ServerState { impl ServerState {
pub async fn init(secret: &str, config: &CliArgs) -> Result<Self, ServerInitError> { pub async fn init(secret: &ClientSecret, config: &Config) -> Result<Self, ServerInitError> {
let resp = reqwest::Client::new() let resp = reqwest::Client::new()
.post(CONTROL_CENTER_PING_URL) .post(CONTROL_CENTER_PING_URL)
.json(&Request::from((secret, config))) .json(&Request::from((secret, config)))
.send() .send()
.await; .await;
if config.enable_server_string {
warn!("Client will send Server header in responses. This is not recommended!");
SEND_SERVER_VERSION.store(true, Ordering::Release);
}
match resp { match resp {
Ok(resp) => match resp.json::<Response>().await { Ok(resp) => match resp.json::<Response>().await {
Ok(Response::Ok(mut resp)) => { Ok(Response::Ok(mut resp)) => {

View file

@ -2,14 +2,16 @@ use log::{info, warn};
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::Serialize; use serde::Serialize;
use crate::config::ClientSecret;
const CONTROL_CENTER_STOP_URL: &str = "https://api.mangadex.network/ping"; const CONTROL_CENTER_STOP_URL: &str = "https://api.mangadex.network/ping";
#[derive(Serialize)] #[derive(Serialize)]
struct StopRequest<'a> { struct StopRequest<'a> {
secret: &'a str, secret: &'a ClientSecret,
} }
pub async fn send_stop(secret: &str) { pub async fn send_stop(secret: &ClientSecret) {
let request = StopRequest { secret }; let request = StopRequest { secret };
let client = reqwest::Client::new(); let client = reqwest::Client::new();
match client match client

80
src/units.rs Normal file
View file

@ -0,0 +1,80 @@
use std::fmt::Display;
use std::num::{NonZeroU16, NonZeroU64, ParseIntError};
use std::str::FromStr;
use serde::{Deserialize, Serialize};
/// Wrapper type for a port number.
#[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 = <NonZeroU16 as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NonZeroU16::from_str(s).map(Self)
}
}
impl Display for Port {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Copy, Clone, Serialize, Deserialize, Default, Debug, Hash, Eq, PartialEq)]
pub struct Mebibytes(usize);
impl FromStr for Mebibytes {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<usize>().map(Self)
}
}
pub struct Bytes(usize);
impl Bytes {
pub const fn get(&self) -> usize {
self.0
}
}
impl From<Mebibytes> for Bytes {
fn from(mib: Mebibytes) -> Self {
Self(mib.0 << 20)
}
}
#[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<Self, Self::Err> {
s.parse::<NonZeroU64>().map(Self)
}
}
#[derive(Copy, Clone, Serialize, Debug, Hash, Eq, PartialEq)]
pub struct BytesPerSecond(NonZeroU64);
impl From<KilobitsPerSecond> for BytesPerSecond {
fn from(kbps: KilobitsPerSecond) -> Self {
Self(unsafe { NonZeroU64::new_unchecked(kbps.0.get() * 125) })
}
}