Compare commits
	
		
			2 Commits 
		
	
	
		
			7abce21aee
			...
			be04d0c7fe
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
									
								
								 | 
						be04d0c7fe | |
| 
							
							
								
									
								
								 | 
						dca4d2d1ec | 
| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,43 @@ 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,
 | 
				
			||||||
 | 
					    Unix,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[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")]
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
    #[arg(short, long = "log", value_name = "LOG_LEVEL", global = true)]
 | 
					    #[serde(rename = "path")]
 | 
				
			||||||
    log_level: Option<LogLevel>,
 | 
					    #[arg(long, value_name = "SOCKET", global = true)]
 | 
				
			||||||
 | 
					    socket: Option<PathBuf>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Subcommand)]
 | 
					#[derive(Subcommand)]
 | 
				
			||||||
| 
						 | 
					@ -65,17 +87,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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,36 +27,63 @@ 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 {
 | 
					 | 
				
			||||||
            let mut interval = tokio::time::interval(session_removal_duration);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            loop {
 | 
					    rt.spawn(async move {
 | 
				
			||||||
                interval.tick().await;
 | 
					        let mut interval = tokio::time::interval(session_removal_duration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                tracing::info!("Performing session cleanup");
 | 
					        loop {
 | 
				
			||||||
 | 
					            interval.tick().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match ctx.store.remove_old_sessions() {
 | 
					            tracing::info!("Performing session cleanup");
 | 
				
			||||||
                    Ok(n) => {
 | 
					
 | 
				
			||||||
                        tracing::info!("Removed {} old sessions", n);
 | 
					            match ctx.store.remove_old_sessions() {
 | 
				
			||||||
                    }
 | 
					                Ok(n) => {
 | 
				
			||||||
                    Err(err) => {
 | 
					                    tracing::info!("Removed {} old sessions", n);
 | 
				
			||||||
                        tracing::error!("Error occured during session cleanup: {}", err);
 | 
					                }
 | 
				
			||||||
                    }
 | 
					                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()
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        NetConfig::Unix { path } => {
 | 
				
			||||||
 | 
					            // Try to remove the socket file first if it exists
 | 
				
			||||||
 | 
					            let _ = std::fs::remove_file(&path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Some(parent) = path.parent() {
 | 
				
			||||||
 | 
					                std::fs::create_dir_all(parent).unwrap();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            tracing::info!("Listening on Unix socket {:?}", path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            rt.block_on(async {
 | 
				
			||||||
 | 
					                let listener = tokio::net::UnixListener::bind(path).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                axum::serve(listener, app.into_make_service())
 | 
				
			||||||
 | 
					                    .await
 | 
				
			||||||
 | 
					                    .unwrap()
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    0
 | 
					    0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,33 @@ 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 },
 | 
				
			||||||
 | 
					    Unix { path: PathBuf },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[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
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue