From 483fe1e6493da1d924b123bc9fd9ed007eae2203 Mon Sep 17 00:00:00 2001 From: Neshura Date: Mon, 8 Apr 2024 22:12:22 +0200 Subject: [PATCH] implemented split user/system config with unified service at the top due to port conflicts --- Cargo.toml | 4 +- src/main.rs | 147 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 111 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 980dea9..044fe66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,5 +24,7 @@ systemd-units = { enable = false } [dependencies] actix-web = "4" +confy = "0.6" log = "0.4" -systemd-journal-logger = "2" \ No newline at end of file +systemd-journal-logger = "2" +serde = { version = "1.0.197", features = ["derive"] } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 395bf51..84eb4f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,12 @@ -use std::fmt::{Display, Formatter}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::error::Error; +use std::fmt::{Display, format, Formatter}; +use std::fs; +use std::net::{IpAddr, Ipv6Addr}; +use std::path::Path; use actix_web::{web, App, HttpResponse, HttpServer, get, Responder, HttpRequest}; use log::{LevelFilter}; use systemd_journal_logger::{connected_to_journal, JournalLog}; +use serde::{Deserialize, Serialize}; macro_rules! info { @@ -32,7 +36,7 @@ macro_rules! error { }; } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] enum Protocol { Http, Https @@ -47,64 +51,129 @@ impl Display for Protocol { } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] struct DomainLinkConfig { domain: String, protocol: Protocol, target: String, } -#[derive(Clone)] -struct Config { - redirects: Vec, +#[derive(Clone, Serialize, Deserialize, Default)] +struct UserConfig { + domain_configs: Vec +} + +#[derive(Clone, Serialize, Deserialize)] +struct SystemConfig { ports: Vec, addresses: Vec, } +impl Default for SystemConfig { + fn default() -> Self { + Self { + ports: vec![8000], + addresses: vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))], + } + } +} + +#[derive(Clone)] +struct Config { + user: UserConfig, + system: SystemConfig, +} + +impl Config { + pub fn load() -> Result> { + // get list of home directories + // query every home directory for a config file (just attempt a load, an empty config is perfectly fine) + // merge all configs into one + let mut user_config = UserConfig::default(); + for entry in fs::read_dir("/home").expect("home directory is expected") { + let entry = entry.expect("home directory is expected to have at least one directory"); + let path = entry.path(); + if path.is_dir() { + let config_path = format!("{}/.config/{}/config.toml", path.display().to_string().as_str(), env!("CARGO_PKG_NAME")); + let mut path_config: UserConfig = match confy::load_path(config_path) { + Ok(data) => data, + Err(e) => { + match &e { + confy::ConfyError::GeneralLoadError(os_error) => { + if os_error.raw_os_error() == Some(13) { + let msg = format!("Missing read permissions for {}, skipping", path.display().to_string().as_str()); + warn!(msg); + UserConfig::default() + } + else { + error!(e); + return Err(Box::new(e)); + } + }, + _ => { + error!(e); + return Err(Box::new(e)); + } + } + } + }; + user_config.domain_configs.append(&mut path_config.domain_configs) + } + } + + let etc_path = format!("/etc/{}", env!("CARGO_PKG_NAME")); + let usr_path = format!("/usr/local/share/{}", env!("CARGO_PKG_NAME")); + + let system_config_paths = vec![ + Path::new(&etc_path), + Path::new(&usr_path), + ]; + + let mut system_path= system_config_paths[1]; + + for path in system_config_paths { + if path.exists() { + system_path = path; + } + }; + + let path = format!("{}/config.toml", system_path.display().to_string().as_str()); + match confy::load_path(path) { + Ok(data) => { + Ok(Config { + user: user_config, + system: data, + }) + }, + Err(e) => { + error!(e); + Err(Box::new(e)) + } + } + } +} + #[actix_web::main] async fn main() -> std::io::Result<()> { JournalLog::new().expect("Systemd-Logger crate error").install().expect("Systemd-Logger crate error"); log::set_max_level(LevelFilter::Info); - let config = Config { - redirects: vec![ - DomainLinkConfig { - domain: "neshura.me".to_owned(), - protocol: Protocol::Https, - target: "neshweb.net".to_owned(), - }, - DomainLinkConfig { - domain: "lemmy.neshura.me".to_owned(), - protocol: Protocol::Https, - target: "bookwormstory.social/u/neshura".to_owned() - }, - DomainLinkConfig { - domain: "test2.neshura.me".to_owned(), - protocol: Protocol::Https, - target: "neshweb.net".to_owned() - } - ], - ports: vec![8080, 8090], - addresses: vec![ - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - IpAddr::V4(Ipv4Addr::new(192, 168, 178, 11)) - ], - }; - - let msg = "This Build is not intended for production use!"; - info!("Test Info"); - warn!(msg); - error!(msg); + let config = Config::load().expect("Error while loading or generating the config"); + + let loaded_redirects_msg = format!("Loaded {} redirects from user config", config.user.domain_configs.len()); + info!(loaded_redirects_msg); let mut server = HttpServer::new(move || { App::new() - .app_data(web::Data::new(config.redirects.clone())) + .app_data(web::Data::new(config.user.domain_configs.clone())) .service(handle) .service(dry_handle) }); - for address in config.addresses.iter() { - for port in config.ports.iter() { + for address in config.system.addresses.iter() { + for port in config.system.ports.iter() { + let msg = format!("Listening on {address}:{port}"); + info!(msg); server = server.bind((*address, *port))? } }