193 lines
5.5 KiB
Rust
193 lines
5.5 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct Config {
|
|
pub content: ContentConfig,
|
|
pub crypto: CryptoConfig,
|
|
pub telegram: TelegramConfig,
|
|
pub groups: GroupsConfig,
|
|
pub storage: StorageConfig,
|
|
pub upload_limits: UploadLimits,
|
|
pub server: ServerConfig,
|
|
pub rate_limiting: RateLimitConfig,
|
|
pub logging: LoggingConfig,
|
|
pub frontend: FrontendConfig,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct ContentConfig {
|
|
pub keep_content: bool,
|
|
pub share_mode: ShareMode,
|
|
pub default_allow_download: bool,
|
|
#[serde(default)]
|
|
pub default_max_views: Option<u64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum ShareMode {
|
|
B,
|
|
W,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct CryptoConfig {
|
|
pub aes_master_key_source: KeySource,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum KeySource {
|
|
File { path: PathBuf },
|
|
Env { var: String },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct TelegramConfig {
|
|
pub bot_token: String,
|
|
pub api_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct GroupsConfig {
|
|
pub admin_group_ids: Vec<i64>,
|
|
pub review_group_ids: Vec<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct StorageConfig {
|
|
pub paths: StoragePaths,
|
|
pub chunk_size_bytes: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct StoragePaths {
|
|
pub media: PathBuf,
|
|
pub documents: PathBuf,
|
|
pub text: PathBuf,
|
|
pub temp: PathBuf,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct UploadLimits {
|
|
pub max_batch_size: usize,
|
|
pub max_file_size_bytes: u64,
|
|
pub max_total_batch_bytes: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct ServerConfig {
|
|
pub base_url: String,
|
|
pub bind_address: String,
|
|
pub port: u16,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct RateLimitConfig {
|
|
pub requests_per_minute: u32,
|
|
pub burst: u32,
|
|
pub password_attempts_per_minute: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct LoggingConfig {
|
|
pub level: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub struct FrontendConfig {
|
|
pub behavior_toggles: HashMap<String, bool>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn load() -> Result<Self, cgcx_core::CgcxError> {
|
|
let s = config::Config::builder()
|
|
.add_source(config::File::with_name("config/default"))
|
|
.add_source(config::File::with_name("config/local").required(false))
|
|
.add_source(
|
|
config::Environment::with_prefix("CGCX")
|
|
.separator("__")
|
|
.try_parsing(true)
|
|
.list_separator(","),
|
|
)
|
|
.build()
|
|
.map_err(|e| cgcx_core::CgcxError::Config(e.to_string()))?;
|
|
let cfg: Config = s.try_deserialize()
|
|
.map_err(|e| cgcx_core::CgcxError::Config(e.to_string()))?;
|
|
cfg.validate()?;
|
|
Ok(cfg)
|
|
}
|
|
|
|
pub fn validate(&self) -> Result<(), cgcx_core::CgcxError> {
|
|
let chunk = self.storage.chunk_size_bytes;
|
|
const MIN: usize = 8 * 1024 * 1024;
|
|
const MAX: usize = 256 * 1024 * 1024;
|
|
if chunk < MIN || chunk > MAX {
|
|
return Err(cgcx_core::CgcxError::Config(format!(
|
|
"chunk_size_bytes must be between {} and {}, got {}",
|
|
MIN, MAX, chunk
|
|
)));
|
|
}
|
|
|
|
if self.telegram.bot_token.is_empty() || self.telegram.bot_token == "BOT_TOKEN_PLACEHOLDER" {
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"telegram.bot_token must be set to a valid bot token".into()
|
|
));
|
|
}
|
|
|
|
if self.server.port == 0 {
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"server.port must be > 0".into()
|
|
));
|
|
}
|
|
|
|
if self.server.base_url.is_empty() {
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"server.base_url must be set".into()
|
|
));
|
|
}
|
|
|
|
if self.upload_limits.max_batch_size == 0
|
|
|| self.upload_limits.max_file_size_bytes == 0
|
|
|| self.upload_limits.max_total_batch_bytes == 0
|
|
{
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"upload_limits must all be > 0".into()
|
|
));
|
|
}
|
|
|
|
if self.rate_limiting.requests_per_minute == 0
|
|
|| self.rate_limiting.burst == 0
|
|
|| self.rate_limiting.password_attempts_per_minute == 0
|
|
{
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"rate_limiting values must all be > 0".into()
|
|
));
|
|
}
|
|
|
|
if self.logging.level.is_empty() {
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"logging.level must be set".into()
|
|
));
|
|
}
|
|
|
|
match &self.crypto.aes_master_key_source {
|
|
KeySource::File { path } if path.as_os_str().is_empty() => {
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"crypto.aes_master_key_source.file path must not be empty".into()
|
|
));
|
|
}
|
|
KeySource::Env { var } if var.is_empty() => {
|
|
return Err(cgcx_core::CgcxError::Config(
|
|
"crypto.aes_master_key_source.env var must not be empty".into()
|
|
));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|