From 69fe2d00bc4903030e90bbfcd42bec11c74295e2 Mon Sep 17 00:00:00 2001 From: user0-07161 Date: Tue, 7 Oct 2025 21:12:59 +0200 Subject: [PATCH] feat: config file support --- Cargo.lock | 56 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ examples/example_config.toml | 5 ++++ src/config.rs | 52 +++++++++++++++++++++++++++++++++ src/error_structs.rs | 15 ++++++++++ src/main.rs | 26 +++++++---------- 6 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 examples/example_config.toml create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 46a4171..9b74719 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -617,8 +617,10 @@ dependencies = [ "clap", "console-subscriber", "once_cell", + "serde", "thiserror", "tokio", + "toml", "tracing", "tracing-subscriber", ] @@ -1017,6 +1019,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1175,6 +1186,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap 2.11.4", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + [[package]] name = "tonic" version = "0.12.3" @@ -1513,6 +1563,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + [[package]] name = "zerocopy" version = "0.8.27" diff --git a/Cargo.toml b/Cargo.toml index 466f79a..2b9ff44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ tracing = "0.1.41" tracing-subscriber = "0.3.20" thiserror = "2.0.17" anyhow = "1.0.100" +toml = "0.9.7" +serde = { version = "1.0.228", features = ["derive"] } [features] tokio-console = ["tokio/tracing", "console-subscriber"] diff --git a/examples/example_config.toml b/examples/example_config.toml new file mode 100644 index 0000000..b09e747 --- /dev/null +++ b/examples/example_config.toml @@ -0,0 +1,5 @@ +ip = "0.0.0.0" +port = 6667 +server_hostname = "irc.foo.bar" +network_name = "MyCoolFooNet" # this SHOULDN'T HAVE SPACES! +operators = [] diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1b702cd --- /dev/null +++ b/src/config.rs @@ -0,0 +1,52 @@ +use std::{env::home_dir, fs::read_to_string, path::PathBuf}; + +use crate::error_structs::ConfigReadError; +use serde::Deserialize; + +#[allow(dead_code)] +#[derive(Clone, Debug, Deserialize)] +pub struct ServerInfo { + pub ip: String, + pub port: u64, + pub server_hostname: String, + pub network_name: String, + pub operators: Vec, +} + +fn get_config_path() -> Result { + if cfg!(target_os = "linux") { + if let Some(mut homedir) = home_dir() { + homedir.push(".config"); + homedir.push("irs"); + homedir.push("config.toml"); + + if homedir.exists() { + return Ok(homedir); + } + } + + let dir = PathBuf::from("/etc/irs/config.toml"); + if dir.exists() { + dir + } else { + return Err(ConfigReadError::NoConfigFile); + } + } else { + return Err(ConfigReadError::UnsupportedOS); + }; + + unreachable!() +} + +impl ServerInfo { + pub fn load(path: Option) -> Result { + let path = if let Some(path) = path { + PathBuf::from(path) + } else { + get_config_path()? + }; + let config: ServerInfo = toml::from_str(&read_to_string(path)?)?; + + Ok(config) + } +} diff --git a/src/error_structs.rs b/src/error_structs.rs index 1660520..617ea56 100644 --- a/src/error_structs.rs +++ b/src/error_structs.rs @@ -27,6 +27,21 @@ pub enum CommandExecError { NonexistantCommand, } +#[derive(Error, Debug)] +pub enum ConfigReadError { + #[error("could not find a config file")] + NoConfigFile, + + #[error("unsupported OS")] + UnsupportedOS, + + #[error("std::io error")] + StdIoError(#[from] std::io::Error), + + #[error("toml reading error")] + TomlError(#[from] toml::de::Error), +} + // Conversion impls here impl From for ListenerError { fn from(value: SenderError) -> Self { diff --git a/src/main.rs b/src/main.rs index 64879df..92d7db5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::Error as AnyhowError; +use clap::Parser; use once_cell::sync::Lazy; use tokio::{ io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter}, @@ -21,6 +22,7 @@ use tracing::instrument; use crate::{ channels::Channel, + config::ServerInfo, error_structs::{HandlerError, ListenerError}, login::send_motd, messages::Message, @@ -30,6 +32,7 @@ use crate::{ mod channels; mod commands; +mod config; mod error_structs; mod login; mod messages; @@ -42,14 +45,12 @@ pub static JOINED_CHANNELS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); pub static SENDER: Lazy>>> = Lazy::new(|| Mutex::new(None)); -#[allow(dead_code)] -#[derive(Clone, Debug)] -struct ServerInfo { - ip: String, - port: String, - server_hostname: String, - network_name: String, - operators: Vec, +/// An IRCd written in Rust +#[derive(Parser, Debug)] +struct Args { + /// Path to the config file + #[arg(short, long)] + pub config_path: Option, } #[tokio::main] @@ -57,13 +58,8 @@ async fn main() -> Result<(), AnyhowError> { #[cfg(feature = "tokio-console")] console_subscriber::init(); - let info = ServerInfo { - ip: "0.0.0.0".into(), - port: "6667".into(), - server_hostname: "irc.blah.blah".into(), - network_name: "TeamDunno".into(), - operators: Vec::new(), - }; + let args = Args::parse(); + let info = ServerInfo::load(args.config_path).unwrap(); // TODO: ^ pull these from a config file let listener = TcpListener::bind(SocketAddr::from_str(&format!("{}:{}", info.ip, info.port))?)?;