refactor(server): lay groundwork for unix socket configuration

main
Jef Roosens 2025-04-16 10:12:55 +02:00
parent 7abce21aee
commit dca4d2d1ec
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
4 changed files with 88 additions and 64 deletions

View File

@ -1,3 +1,6 @@
data_dir = "./data" data_dir = "./data"
domain = "127.0.0.1" domain = "127.0.0.1"
port = 8080 port = 8080
[net]
type = "tcp"

View File

@ -4,7 +4,7 @@ mod serve;
use std::path::PathBuf; use std::path::PathBuf;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand, ValueEnum};
use figment::{ use figment::{
providers::{Env, Format, Serialized, Toml}, providers::{Env, Format, Serialized, Toml},
Figment, Figment,
@ -24,7 +24,7 @@ pub struct Cli {
pub cmd: Command, pub cmd: Command,
} }
#[derive(Serialize, Args, Clone)] #[derive(Serialize, Args, Clone, Debug)]
pub struct ClapConfig { pub struct ClapConfig {
#[arg( #[arg(
short, short,
@ -35,21 +35,37 @@ pub struct ClapConfig {
)] )]
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
#[command(flatten)]
net: NetConfig,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[arg(long = "data", value_name = "DATA_DIR", global = true)] #[arg(long = "data", value_name = "DATA_DIR", global = true)]
data_dir: Option<PathBuf>, data_dir: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")] #[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<LogLevel>,
}
#[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<NetConfigType>,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(long, value_name = "DOMAIN", global = true)]
domain: Option<String>, domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[arg(short, long, value_name = "PORT", global = true)] #[arg(long, value_name = "PORT", global = true)]
port: Option<u16>, port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(short, long = "log", value_name = "LOG_LEVEL", global = true)]
log_level: Option<LogLevel>,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -65,17 +81,18 @@ pub enum Command {
impl Cli { impl Cli {
pub fn run(&self) -> u8 { 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 { if let Some(config_path) = &self.config.config_file {
figment = figment.merge(Toml::file(config_path)); figment = figment.merge(Toml::file(config_path));
} }
let config: crate::config::Config = match figment let config = figment
.merge(Env::prefixed("OTTER_")) .merge(Env::prefixed("OTTER_"))
.merge(Serialized::defaults(self.config.clone())) .merge(Serialized::defaults(self.config.clone()));
.extract()
{ let config = match config.extract::<crate::config::Config>() {
Ok(config) => config, Ok(config) => config,
Err(err) => { Err(err) => {
eprintln!("{}", err); eprintln!("{}", err);

View File

@ -1,6 +1,6 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use crate::server; use crate::{config::NetConfig, server};
pub fn serve(config: &crate::config::Config) -> u8 { pub fn serve(config: &crate::config::Config) -> u8 {
tracing_subscriber::fmt() tracing_subscriber::fmt()
@ -27,13 +27,12 @@ pub fn serve(config: &crate::config::Config) -> u8 {
.build() .build()
.unwrap(); .unwrap();
let address = format!("{}:{}", config.domain, config.port); // Spawn the session cleanup background task
tracing::info!("Starting server on {address}");
let session_removal_duration = Duration::from_secs(config.session_cleanup_interval); let session_removal_duration = Duration::from_secs(config.session_cleanup_interval);
rt.block_on(async { tracing::info!("Starting session cleanup background task");
tokio::task::spawn(async move {
rt.spawn(async move {
let mut interval = tokio::time::interval(session_removal_duration); let mut interval = tokio::time::interval(session_removal_duration);
loop { loop {
@ -52,11 +51,21 @@ pub fn serve(config: &crate::config::Config) -> u8 {
} }
}); });
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(); let listener = tokio::net::TcpListener::bind(address).await.unwrap();
axum::serve(listener, app.into_make_service()) axum::serve(listener, app.into_make_service())
.await .await
.unwrap() .unwrap()
}); });
}
}
0 0
} }

View File

@ -3,7 +3,7 @@ use std::path::PathBuf;
use clap::ValueEnum; use clap::ValueEnum;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone, ValueEnum, Copy)] #[derive(Deserialize, Serialize, Clone, ValueEnum, Copy, Debug)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum LogLevel { pub enum LogLevel {
Debug, Debug,
@ -23,37 +23,32 @@ impl From<LogLevel> 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 { pub struct Config {
#[serde(default = "default_data_dir")] pub net: NetConfig,
pub data_dir: PathBuf, 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, pub session_cleanup_interval: u64,
#[serde(default = "default_log_level")]
pub log_level: LogLevel, pub log_level: LogLevel,
} }
fn default_data_dir() -> PathBuf { impl Default for Config {
PathBuf::from("./data") fn default() -> Self {
} Self {
net: NetConfig::Tcp {
fn default_domain() -> String { domain: "127.0.0.1".to_string(),
"127.0.0.1".to_string() port: 8080,
} },
data_dir: "./data".into(),
fn default_port() -> u16 { // Once per day
8080 session_cleanup_interval: 60 * 60 * 24,
} log_level: LogLevel::Warn,
}
fn default_session_cleanup_interval() -> u64 { }
// Default is once a day
60 * 60 * 24
}
fn default_log_level() -> LogLevel {
LogLevel::Warn
} }