From dca4d2d1ecdcd826adbf91a52c405072057a375c Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Wed, 16 Apr 2025 10:12:55 +0200 Subject: [PATCH] refactor(server): lay groundwork for unix socket configuration --- otter.toml | 3 +++ server/src/cli/mod.rs | 43 ++++++++++++++++++++++---------- server/src/cli/serve.rs | 55 ++++++++++++++++++++++++----------------- server/src/config.rs | 51 +++++++++++++++++--------------------- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/otter.toml b/otter.toml index 1f93f85..168a1f4 100644 --- a/otter.toml +++ b/otter.toml @@ -1,3 +1,6 @@ data_dir = "./data" domain = "127.0.0.1" port = 8080 + +[net] +type = "tcp" diff --git a/server/src/cli/mod.rs b/server/src/cli/mod.rs index fbd3dac..fbb8414 100644 --- a/server/src/cli/mod.rs +++ b/server/src/cli/mod.rs @@ -4,7 +4,7 @@ mod serve; use std::path::PathBuf; -use clap::{Args, Parser, Subcommand}; +use clap::{Args, Parser, Subcommand, ValueEnum}; use figment::{ providers::{Env, Format, Serialized, Toml}, Figment, @@ -24,7 +24,7 @@ pub struct Cli { pub cmd: Command, } -#[derive(Serialize, Args, Clone)] +#[derive(Serialize, Args, Clone, Debug)] pub struct ClapConfig { #[arg( short, @@ -35,21 +35,37 @@ pub struct ClapConfig { )] config_file: Option, + #[command(flatten)] + net: NetConfig, + #[serde(skip_serializing_if = "Option::is_none")] #[arg(long = "data", value_name = "DATA_DIR", global = true)] data_dir: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[arg(short, long, value_name = "DOMAIN", global = true)] + #[arg(short, long = "log", value_name = "LOG_LEVEL", global = true)] + log_level: Option, +} + +#[derive(ValueEnum, Serialize, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum NetConfigType { + Tcp, +} + +#[derive(Serialize, Clone, Args, Debug)] +pub struct NetConfig { + #[serde(skip_serializing_if = "Option::is_none")] + #[arg(long = "net", value_name = "NET", global = true)] + r#type: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[arg(long, value_name = "DOMAIN", global = true)] domain: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[arg(short, long, value_name = "PORT", global = true)] + #[arg(long, value_name = "PORT", global = true)] port: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - #[arg(short, long = "log", value_name = "LOG_LEVEL", global = true)] - log_level: Option, } #[derive(Subcommand)] @@ -65,17 +81,18 @@ pub enum Command { impl Cli { pub fn run(&self) -> u8 { - let mut figment = Figment::new(); + let mut figment = + Figment::new().merge(Serialized::defaults(crate::config::Config::default())); if let Some(config_path) = &self.config.config_file { figment = figment.merge(Toml::file(config_path)); } - let config: crate::config::Config = match figment + let config = figment .merge(Env::prefixed("OTTER_")) - .merge(Serialized::defaults(self.config.clone())) - .extract() - { + .merge(Serialized::defaults(self.config.clone())); + + let config = match config.extract::() { Ok(config) => config, Err(err) => { eprintln!("{}", err); diff --git a/server/src/cli/serve.rs b/server/src/cli/serve.rs index b9c0e27..974938b 100644 --- a/server/src/cli/serve.rs +++ b/server/src/cli/serve.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, time::Duration}; -use crate::server; +use crate::{config::NetConfig, server}; pub fn serve(config: &crate::config::Config) -> u8 { tracing_subscriber::fmt() @@ -27,36 +27,45 @@ pub fn serve(config: &crate::config::Config) -> u8 { .build() .unwrap(); - let address = format!("{}:{}", config.domain, config.port); - tracing::info!("Starting server on {address}"); - + // Spawn the session cleanup background task let session_removal_duration = Duration::from_secs(config.session_cleanup_interval); - rt.block_on(async { - tokio::task::spawn(async move { - let mut interval = tokio::time::interval(session_removal_duration); + tracing::info!("Starting session cleanup background task"); - loop { - interval.tick().await; + rt.spawn(async move { + let mut interval = tokio::time::interval(session_removal_duration); - tracing::info!("Performing session cleanup"); + loop { + interval.tick().await; - match ctx.store.remove_old_sessions() { - Ok(n) => { - tracing::info!("Removed {} old sessions", n); - } - Err(err) => { - tracing::error!("Error occured during session cleanup: {}", err); - } + tracing::info!("Performing session cleanup"); + + match ctx.store.remove_old_sessions() { + Ok(n) => { + tracing::info!("Removed {} old sessions", n); + } + Err(err) => { + tracing::error!("Error occured during session cleanup: {}", err); } } - }); - - let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - axum::serve(listener, app.into_make_service()) - .await - .unwrap() + } }); + tracing::info!("Initializing server"); + + match &config.net { + NetConfig::Tcp { domain, port } => { + let address = format!("{}:{}", domain, port); + tracing::info!("Listening on TCP address {address}"); + + rt.block_on(async { + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap() + }); + } + } + 0 } diff --git a/server/src/config.rs b/server/src/config.rs index c015933..02af0ab 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use clap::ValueEnum; use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize, Clone, ValueEnum, Copy)] +#[derive(Deserialize, Serialize, Clone, ValueEnum, Copy, Debug)] #[serde(rename_all = "lowercase")] pub enum LogLevel { Debug, @@ -23,37 +23,32 @@ impl From for tracing::Level { } } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[serde(tag = "type")] +pub enum NetConfig { + Tcp { domain: String, port: u16 }, +} + +#[derive(Serialize, Deserialize)] pub struct Config { - #[serde(default = "default_data_dir")] + pub net: NetConfig, pub data_dir: PathBuf, - #[serde(default = "default_domain")] - pub domain: String, - #[serde(default = "default_port")] - pub port: u16, - #[serde(default = "default_session_cleanup_interval")] pub session_cleanup_interval: u64, - #[serde(default = "default_log_level")] pub log_level: LogLevel, } -fn default_data_dir() -> PathBuf { - PathBuf::from("./data") -} - -fn default_domain() -> String { - "127.0.0.1".to_string() -} - -fn default_port() -> u16 { - 8080 -} - -fn default_session_cleanup_interval() -> u64 { - // Default is once a day - 60 * 60 * 24 -} - -fn default_log_level() -> LogLevel { - LogLevel::Warn +impl Default for Config { + fn default() -> Self { + Self { + net: NetConfig::Tcp { + domain: "127.0.0.1".to_string(), + port: 8080, + }, + data_dir: "./data".into(), + // Once per day + session_cleanup_interval: 60 * 60 * 24, + log_level: LogLevel::Warn, + } + } }