From 514e48c6ad104d26f202c4e140cd7e63770ee144 Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Wed, 4 Dec 2024 20:44:31 -0700 Subject: [PATCH] Move server config to subsection of config file --- src/config.rs | 160 +++++++++++++------------------------------ src/main.rs | 2 +- src/server/config.rs | 107 +++++++++++++++++++++++++++++ src/server/mod.rs | 18 ++--- 4 files changed, 167 insertions(+), 120 deletions(-) create mode 100644 src/server/config.rs diff --git a/src/config.rs b/src/config.rs index f0af1f0..b728e95 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,9 @@ use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; use std::{fs::File, path::Path, path::PathBuf}; use tracing::{error, trace, warn}; +use std::ffi::OsStr; + +use crate::server::config::{ServerConfig, ServerArgs, DEFAULT_SERVER_ARGS}; /// The globally-accessible static instance of Config. /// On program startup, Config::load() should be called to initialize it. @@ -13,12 +16,11 @@ pub static CONFIG: OnceCell = OnceCell::new(); /// On program startup, Args::load() should be called to initialize it. pub static ARGS: OnceCell = OnceCell::new(); static DEFAULT_ARGS: Lazy = Lazy::new(Args::default); -static DEFAULT_SERVER_ARGS: Lazy = Lazy::new(ServerArgs::default); /// Helper function to read a file from a `Path` /// and return its bytes as a `Vec`. #[tracing::instrument] -fn read_file(path: &Path) -> std::io::Result> { +pub fn read_file(path: &Path) -> std::io::Result> { trace!("{:?}", path); let mut data = vec![]; let mut file = File::open(path)?; @@ -26,45 +28,14 @@ fn read_file(path: &Path) -> std::io::Result> { Ok(data) } -/// The main server configuration struct. -#[derive(Debug, Deserialize, Serialize)] +/// The global configuration. +#[derive(Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] #[serde(default)] pub struct Config { - pub port: u16, - pub max_players: usize, - pub motd: String, - pub server_icon: PathBuf, - #[serde(skip)] - pub server_icon_bytes: Vec, - #[serde(skip)] - pub protocol_version: i32, - #[serde(skip)] - pub game_version: String, - #[serde(skip)] - pub server_version: String, - pub server_threads: Option, -} -impl Default for Config { - fn default() -> Self { - let server_version = format!( - "composition {} ({} {})", - env!("CARGO_PKG_VERSION"), - &env!("GIT_HASH")[0..9], - &env!("GIT_DATE")[0..10] - ); - Config { - port: 25565, - max_players: 20, - motd: "Hello world!".to_owned(), - server_icon: PathBuf::from("server-icon.png"), - server_icon_bytes: include_bytes!("./server-icon.png").to_vec(), - protocol_version: 762, - game_version: "1.19.4".to_owned(), - server_version, - server_threads: None, - } - } + #[serde(rename = "composition")] + pub global: GlobalConfig, + pub server: ServerConfig, } impl Config { pub fn instance() -> &'static Self { @@ -73,9 +44,8 @@ impl Config { None => Self::load(), } } - #[tracing::instrument] - pub fn load() -> &'static Self { - trace!("Config::load()"); + fn load() -> &'static Self { + trace!("GlobalConfig::load()"); let args = Args::instance(); let mut config = Config::default(); let config_path = Path::new(&args.config_file); @@ -100,26 +70,12 @@ impl Config { } // Load the server icon - config.server_icon = args + config.server.server_icon = args .server .as_ref() .map(|s| s.server_icon.clone()) .unwrap_or(DEFAULT_SERVER_ARGS.server_icon.clone()); - let server_icon_path = Path::new(&config.server_icon); - - if server_icon_path.exists() { - if let Ok(server_icon_bytes) = read_file(server_icon_path) { - config.server_icon_bytes = server_icon_bytes; - } else { - warn!("Could not read server icon file, using default"); - } - } else { - warn!( - "Server icon file does not exist, creating {}", - server_icon_path.to_str().unwrap_or("") - ); - config.write_server_icon(server_icon_path); - } + config.server.load_icon(); CONFIG.set(config).expect("could not set CONFIG"); Self::instance() @@ -143,21 +99,39 @@ impl Config { error!("Could not write configuration file"); std::process::exit(1); } - #[tracing::instrument] - fn write_server_icon(&self, path: &Path) { - trace!("Config.write_server_icon()"); - if let Ok(mut file) = File::options() - .write(true) - .create(true) - .truncate(true) - .open(path) - { - if file.write_all(&self.server_icon_bytes).is_ok() { - return; - } +} + +/// The global configuration. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct GlobalConfig { + #[serde(skip)] + pub version: String, + #[serde(skip)] + pub protocol_version: i32, + #[serde(skip)] + pub game_version: String, + pub threads: Option, +} +impl Default for GlobalConfig { + fn default() -> Self { + GlobalConfig { + version: format!( + "composition {} ({} {})", + env!("CARGO_PKG_VERSION"), + &env!("GIT_HASH")[0..9], + &env!("GIT_DATE")[0..10] + ), + protocol_version: 762, + game_version: "1.19.4".to_owned(), + threads: None, } - error!("Could not write server icon file"); - std::process::exit(1); + } +} +impl GlobalConfig { + pub fn instance() -> &'static Self { + &Config::instance().global } } @@ -176,7 +150,7 @@ pub struct Args { pub log_level: Option, pub log_dir: PathBuf, pub subcommand: Subcommand, - server: Option, + pub server: Option, } impl Default for Args { fn default() -> Self { @@ -201,7 +175,6 @@ impl Args { Self::instance() } fn command() -> clap::Command { - use std::ffi::OsStr; clap::Command::new("composition") .about(env!("CARGO_PKG_DESCRIPTION")) .disable_version_flag(true) @@ -249,17 +222,7 @@ impl Args { .value_hint(clap::ValueHint::DirPath) .default_value(OsStr::new(&DEFAULT_ARGS.log_dir)), ) - .subcommand( - clap::Command::new("server") - .about("Run composition in server mode") - .arg( - Arg::new("server-icon") - .long("server-icon") - .help("Server icon file path") - .value_hint(clap::ValueHint::FilePath) - .default_value(OsStr::new(&DEFAULT_SERVER_ARGS.server_icon)), - ), - ) + .subcommand(ServerArgs::command()) } fn parse() -> Self { let mut args = Self::default(); @@ -281,7 +244,7 @@ impl Args { } if m.get_flag("version") { - println!("{}", Config::default().server_version); + println!("{}", GlobalConfig::default().version); if m.get_flag("verbose") { println!("release: {}", env!("CARGO_PKG_VERSION")); println!("commit-hash: {}", env!("GIT_HASH")); @@ -296,11 +259,7 @@ impl Args { match m.subcommand() { Some(("server", m)) => { args.subcommand = Subcommand::Server; - let mut server_args = ServerArgs::default(); - server_args.server_icon = m - .get_one::("server-icon") - .map_or(server_args.server_icon, PathBuf::from); - args.server = Some(server_args); + args.server = Some(ServerArgs::parse(m.clone())) } None => { let _ = Self::command().print_help(); @@ -312,24 +271,3 @@ impl Args { args } } - -#[derive(Debug)] -pub struct ServerArgs { - server_icon: PathBuf, -} -impl Default for ServerArgs { - fn default() -> Self { - let config = Config::default(); - ServerArgs { - server_icon: config.server_icon, - } - } -} -impl ServerArgs { - pub fn instance() -> Option<&'static Self> { - Args::instance().server.as_ref() - } - pub fn load() -> Option<&'static Self> { - Args::load().server.as_ref() - } -} diff --git a/src/main.rs b/src/main.rs index 4dcb78c..a50eb98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ pub fn main() { // Load the config. let config = composition::config::Config::instance(); - match config.server_threads { + match config.global.threads { Some(1) => { warn!("Running on only one thread"); tokio::runtime::Builder::new_current_thread() diff --git a/src/server/config.rs b/src/server/config.rs new file mode 100644 index 0000000..adc0cb6 --- /dev/null +++ b/src/server/config.rs @@ -0,0 +1,107 @@ +use clap::Arg; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::{fs::File, path::Path, path::PathBuf}; +use tracing::{error, trace, warn}; +use crate::config::{Config, Args, read_file}; +use std::ffi::OsStr; + +pub static DEFAULT_SERVER_ARGS: Lazy = Lazy::new(ServerArgs::default); + +/// The main server configuration struct. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct ServerConfig { + pub port: u16, + pub max_players: usize, + pub motd: String, + pub server_icon: PathBuf, + #[serde(skip)] + pub server_icon_bytes: Vec, +} +impl Default for ServerConfig { + fn default() -> Self { + ServerConfig { + port: 25565, + max_players: 20, + motd: "Hello world!".to_owned(), + server_icon: PathBuf::from("server-icon.png"), + server_icon_bytes: include_bytes!("../server-icon.png").to_vec(), + } + } +} +impl ServerConfig { + pub fn instance() -> &'static Self { + &Config::instance().server + } + pub fn load_icon(&mut self) { + let server_icon_path = Path::new(&self.server_icon); + + if server_icon_path.exists() { + if let Ok(server_icon_bytes) = read_file(server_icon_path) { + self.server_icon_bytes = server_icon_bytes; + } else { + warn!("Could not read server icon file, using default"); + } + } else { + warn!( + "Server icon file does not exist, creating {}", + server_icon_path.to_str().unwrap_or("") + ); + self.write_server_icon(server_icon_path); + } + } + pub fn write_server_icon(&self, path: &Path) { + trace!("ServerConfig.write_server_icon()"); + if let Ok(mut file) = File::options() + .write(true) + .create(true) + .truncate(true) + .open(path) + { + if file.write_all(&self.server_icon_bytes).is_ok() { + return; + } + } + error!("Could not write server icon file"); + std::process::exit(1); + } +} + +#[derive(Debug)] +pub struct ServerArgs { + pub server_icon: PathBuf, +} +impl Default for ServerArgs { + fn default() -> Self { + let config = Config::default(); + ServerArgs { + server_icon: config.server.server_icon, + } + } +} +impl ServerArgs { + pub fn instance() -> Option<&'static Self> { + Args::instance().server.as_ref() + } + pub fn command() -> clap::Command { + clap::Command::new("server") + .about("Run composition in server mode") + .arg( + Arg::new("server-icon") + .long("server-icon") + .help("Server icon file path") + .value_hint(clap::ValueHint::FilePath) + .default_value(OsStr::new(&DEFAULT_SERVER_ARGS.server_icon)), + ) + } + pub fn parse(m: clap::ArgMatches) -> Self { + let mut server_args = ServerArgs::default(); + server_args.server_icon = m + .get_one::("server-icon") + .map_or(server_args.server_icon, PathBuf::from); + server_args + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 85bbd47..28b0332 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,6 +2,8 @@ pub mod error; /// Network operations. pub mod net; +/// Server-specific configuration. +pub mod config; use crate::config::Config; use crate::protocol::ClientState; @@ -23,9 +25,9 @@ impl Server { /// Start the server. #[tracing::instrument] pub async fn run() { - let config = crate::config::Config::instance(); - info!("Starting {} on port {}", config.server_version, config.port); - let (mut server, running) = Self::new(format!("0.0.0.0:{}", Config::instance().port)).await; + let config = Config::instance(); + info!("Starting {} on port {}", config.global.version, config.server.port); + let (mut server, running) = Self::new(format!("0.0.0.0:{}", config.server.port)).await; info!( "Done! Start took {:?}", crate::START_TIME.get().unwrap().elapsed() @@ -242,18 +244,18 @@ impl Server { client.queue_packet(CS00StatusResponse { response: serde_json::json!({ "version": { - "name": config.game_version, - "protocol": config.protocol_version + "name": config.global.game_version, + "protocol": config.global.protocol_version }, "players": { - "max": config.max_players, + "max": config.server.max_players, "online": online_players, "sample": [] }, "description": { - "text": config.motd + "text": config.server.motd }, - "favicon": format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD_NO_PAD.encode(&config.server_icon_bytes)), + "favicon": format!("data:image/png;base64,{}", base64::engine::general_purpose::STANDARD_NO_PAD.encode(&config.server.server_icon_bytes)), "enforcesSecureChat": true }), });