Move server config to subsection of config file

This commit is contained in:
Garen Tyler 2024-12-04 20:44:31 -07:00
parent e72f19a814
commit 514e48c6ad
Signed by: garentyler
SSH Key Fingerprint: SHA256:G4ke7blZMdpWPbkescyZ7IQYE4JAtwpI85YoJdq+S7U
4 changed files with 167 additions and 120 deletions

View File

@ -4,6 +4,9 @@ use serde::{Deserialize, Serialize};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::{fs::File, path::Path, path::PathBuf}; use std::{fs::File, path::Path, path::PathBuf};
use tracing::{error, trace, warn}; 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. /// The globally-accessible static instance of Config.
/// On program startup, Config::load() should be called to initialize it. /// On program startup, Config::load() should be called to initialize it.
@ -13,12 +16,11 @@ pub static CONFIG: OnceCell<Config> = OnceCell::new();
/// On program startup, Args::load() should be called to initialize it. /// On program startup, Args::load() should be called to initialize it.
pub static ARGS: OnceCell<Args> = OnceCell::new(); pub static ARGS: OnceCell<Args> = OnceCell::new();
static DEFAULT_ARGS: Lazy<Args> = Lazy::new(Args::default); static DEFAULT_ARGS: Lazy<Args> = Lazy::new(Args::default);
static DEFAULT_SERVER_ARGS: Lazy<ServerArgs> = Lazy::new(ServerArgs::default);
/// Helper function to read a file from a `Path` /// Helper function to read a file from a `Path`
/// and return its bytes as a `Vec<u8>`. /// and return its bytes as a `Vec<u8>`.
#[tracing::instrument] #[tracing::instrument]
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> { pub fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
trace!("{:?}", path); trace!("{:?}", path);
let mut data = vec![]; let mut data = vec![];
let mut file = File::open(path)?; let mut file = File::open(path)?;
@ -26,45 +28,14 @@ fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
Ok(data) Ok(data)
} }
/// The main server configuration struct. /// The global configuration.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
pub port: u16, #[serde(rename = "composition")]
pub max_players: usize, pub global: GlobalConfig,
pub motd: String, pub server: ServerConfig,
pub server_icon: PathBuf,
#[serde(skip)]
pub server_icon_bytes: Vec<u8>,
#[serde(skip)]
pub protocol_version: i32,
#[serde(skip)]
pub game_version: String,
#[serde(skip)]
pub server_version: String,
pub server_threads: Option<usize>,
}
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,
}
}
} }
impl Config { impl Config {
pub fn instance() -> &'static Self { pub fn instance() -> &'static Self {
@ -73,9 +44,8 @@ impl Config {
None => Self::load(), None => Self::load(),
} }
} }
#[tracing::instrument] fn load() -> &'static Self {
pub fn load() -> &'static Self { trace!("GlobalConfig::load()");
trace!("Config::load()");
let args = Args::instance(); let args = Args::instance();
let mut config = Config::default(); let mut config = Config::default();
let config_path = Path::new(&args.config_file); let config_path = Path::new(&args.config_file);
@ -100,26 +70,12 @@ impl Config {
} }
// Load the server icon // Load the server icon
config.server_icon = args config.server.server_icon = args
.server .server
.as_ref() .as_ref()
.map(|s| s.server_icon.clone()) .map(|s| s.server_icon.clone())
.unwrap_or(DEFAULT_SERVER_ARGS.server_icon.clone()); .unwrap_or(DEFAULT_SERVER_ARGS.server_icon.clone());
let server_icon_path = Path::new(&config.server_icon); config.server.load_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.set(config).expect("could not set CONFIG"); CONFIG.set(config).expect("could not set CONFIG");
Self::instance() Self::instance()
@ -143,21 +99,39 @@ impl Config {
error!("Could not write configuration file"); error!("Could not write configuration file");
std::process::exit(1); std::process::exit(1);
} }
#[tracing::instrument] }
fn write_server_icon(&self, path: &Path) {
trace!("Config.write_server_icon()"); /// The global configuration.
if let Ok(mut file) = File::options() #[derive(Debug, Deserialize, Serialize)]
.write(true) #[serde(rename_all = "kebab-case")]
.create(true) #[serde(default)]
.truncate(true) pub struct GlobalConfig {
.open(path) #[serde(skip)]
{ pub version: String,
if file.write_all(&self.server_icon_bytes).is_ok() { #[serde(skip)]
return; pub protocol_version: i32,
} #[serde(skip)]
pub game_version: String,
pub threads: Option<usize>,
}
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<tracing::Level>, pub log_level: Option<tracing::Level>,
pub log_dir: PathBuf, pub log_dir: PathBuf,
pub subcommand: Subcommand, pub subcommand: Subcommand,
server: Option<ServerArgs>, pub server: Option<ServerArgs>,
} }
impl Default for Args { impl Default for Args {
fn default() -> Self { fn default() -> Self {
@ -201,7 +175,6 @@ impl Args {
Self::instance() Self::instance()
} }
fn command() -> clap::Command { fn command() -> clap::Command {
use std::ffi::OsStr;
clap::Command::new("composition") clap::Command::new("composition")
.about(env!("CARGO_PKG_DESCRIPTION")) .about(env!("CARGO_PKG_DESCRIPTION"))
.disable_version_flag(true) .disable_version_flag(true)
@ -249,17 +222,7 @@ impl Args {
.value_hint(clap::ValueHint::DirPath) .value_hint(clap::ValueHint::DirPath)
.default_value(OsStr::new(&DEFAULT_ARGS.log_dir)), .default_value(OsStr::new(&DEFAULT_ARGS.log_dir)),
) )
.subcommand( .subcommand(ServerArgs::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)),
),
)
} }
fn parse() -> Self { fn parse() -> Self {
let mut args = Self::default(); let mut args = Self::default();
@ -281,7 +244,7 @@ impl Args {
} }
if m.get_flag("version") { if m.get_flag("version") {
println!("{}", Config::default().server_version); println!("{}", GlobalConfig::default().version);
if m.get_flag("verbose") { if m.get_flag("verbose") {
println!("release: {}", env!("CARGO_PKG_VERSION")); println!("release: {}", env!("CARGO_PKG_VERSION"));
println!("commit-hash: {}", env!("GIT_HASH")); println!("commit-hash: {}", env!("GIT_HASH"));
@ -296,11 +259,7 @@ impl Args {
match m.subcommand() { match m.subcommand() {
Some(("server", m)) => { Some(("server", m)) => {
args.subcommand = Subcommand::Server; args.subcommand = Subcommand::Server;
let mut server_args = ServerArgs::default(); args.server = Some(ServerArgs::parse(m.clone()))
server_args.server_icon = m
.get_one::<String>("server-icon")
.map_or(server_args.server_icon, PathBuf::from);
args.server = Some(server_args);
} }
None => { None => {
let _ = Self::command().print_help(); let _ = Self::command().print_help();
@ -312,24 +271,3 @@ impl Args {
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()
}
}

View File

@ -38,7 +38,7 @@ pub fn main() {
// Load the config. // Load the config.
let config = composition::config::Config::instance(); let config = composition::config::Config::instance();
match config.server_threads { match config.global.threads {
Some(1) => { Some(1) => {
warn!("Running on only one thread"); warn!("Running on only one thread");
tokio::runtime::Builder::new_current_thread() tokio::runtime::Builder::new_current_thread()

107
src/server/config.rs Normal file
View File

@ -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<ServerArgs> = 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<u8>,
}
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::<String>("server-icon")
.map_or(server_args.server_icon, PathBuf::from);
server_args
}
}

View File

@ -2,6 +2,8 @@
pub mod error; pub mod error;
/// Network operations. /// Network operations.
pub mod net; pub mod net;
/// Server-specific configuration.
pub mod config;
use crate::config::Config; use crate::config::Config;
use crate::protocol::ClientState; use crate::protocol::ClientState;
@ -23,9 +25,9 @@ impl Server {
/// Start the server. /// Start the server.
#[tracing::instrument] #[tracing::instrument]
pub async fn run() { pub async fn run() {
let config = crate::config::Config::instance(); let config = Config::instance();
info!("Starting {} on port {}", config.server_version, config.port); info!("Starting {} on port {}", config.global.version, config.server.port);
let (mut server, running) = Self::new(format!("0.0.0.0:{}", Config::instance().port)).await; let (mut server, running) = Self::new(format!("0.0.0.0:{}", config.server.port)).await;
info!( info!(
"Done! Start took {:?}", "Done! Start took {:?}",
crate::START_TIME.get().unwrap().elapsed() crate::START_TIME.get().unwrap().elapsed()
@ -242,18 +244,18 @@ impl Server {
client.queue_packet(CS00StatusResponse { client.queue_packet(CS00StatusResponse {
response: serde_json::json!({ response: serde_json::json!({
"version": { "version": {
"name": config.game_version, "name": config.global.game_version,
"protocol": config.protocol_version "protocol": config.global.protocol_version
}, },
"players": { "players": {
"max": config.max_players, "max": config.server.max_players,
"online": online_players, "online": online_players,
"sample": [] "sample": []
}, },
"description": { "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 "enforcesSecureChat": true
}), }),
}); });