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"
domain = "127.0.0.1"
port = 8080
[net]
type = "tcp"

View File

@ -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<PathBuf>,
#[command(flatten)]
net: NetConfig,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(long = "data", value_name = "DATA_DIR", global = true)]
data_dir: Option<PathBuf>,
#[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>,
#[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>,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(short, long = "log", value_name = "LOG_LEVEL", global = true)]
log_level: Option<LogLevel>,
}
#[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::<crate::config::Config>() {
Ok(config) => config,
Err(err) => {
eprintln!("{}", err);

View File

@ -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
}

View File

@ -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<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 {
#[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,
}
}
}