Group editable server settings into one struct that is stored in the ecs, don't expose DataDir, use Uuid in the whitelist instead of usernames, replace Banlist record tuple with new type with named fields, remove commented code

This commit is contained in:
Imbris 2020-10-05 22:59:47 -04:00
parent a3ee5a4006
commit 83fb26c4f9
8 changed files with 181 additions and 140 deletions

View File

@ -14,7 +14,7 @@ use crate::{
};
use clap::{App, Arg};
use common::clock::Clock;
use server::{DataDir, Event, Input, Server, ServerSettings};
use server::{Event, Input, Server};
#[cfg(any(target_os = "linux", target_os = "macos"))]
use signal_hook::SIGUSR1;
use std::{
@ -78,14 +78,15 @@ fn main() -> io::Result<()> {
let mut clock = Clock::start();
// Determine folder to save server data in
let server_data_dir = DataDir::from({
let server_data_dir = {
let mut path = common::userdata_dir_workspace!();
path.push(server::DEFAULT_DATA_DIR_NAME);
path
});
};
// Load server settings
let mut server_settings = ServerSettings::load(server_data_dir.as_ref());
let mut server_settings = server::Settings::load(&server_data_dir);
let editable_settings = server::EditableSettings::load(&server_data_dir);
if no_auth {
server_settings.auth_server_address = None;
@ -94,8 +95,8 @@ fn main() -> io::Result<()> {
let server_port = &server_settings.gameserver_address.port();
let metrics_port = &server_settings.metrics_address.port();
// Create server
let mut server =
Server::new(server_settings, server_data_dir).expect("Failed to create server instance!");
let mut server = Server::new(server_settings, editable_settings, &server_data_dir)
.expect("Failed to create server instance!");
info!(
?server_port,

View File

@ -2,7 +2,11 @@
//! To implement a new command, add an instance of `ChatCommand` to
//! `CHAT_COMMANDS` and provide a handler function.
use crate::{client::Client, settings::EditableSetting, Server, StateExt};
use crate::{
client::Client,
settings::{BanRecord, EditableSetting},
Server, StateExt,
};
use chrono::{NaiveTime, Timelike};
use common::{
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
@ -266,7 +270,7 @@ fn handle_motd(
) {
server.notify_client(
client,
ChatType::CommandError.server_msg((**server.server_description()).clone()),
ChatType::CommandError.server_msg((*server.editable_settings().server_description).clone()),
);
}
@ -281,7 +285,8 @@ fn handle_set_motd(
match scan_fmt!(&args, &action.arg_fmt(), String) {
Ok(msg) => {
server
.server_description_mut()
.editable_settings_mut()
.server_description
.edit(data_dir.as_ref(), |d| **d = msg.clone());
server.notify_client(
client,
@ -290,7 +295,8 @@ fn handle_set_motd(
},
Err(_) => {
server
.server_description_mut()
.editable_settings_mut()
.server_description
.edit(data_dir.as_ref(), |d| d.clear());
server.notify_client(
client,
@ -1826,25 +1832,48 @@ fn handle_whitelist(
action: &ChatCommand,
) {
if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) {
let lookup_uuid = || {
server
.state
.ecs()
.read_resource::<LoginProvider>()
.username_to_uuid(&username)
.map_err(|_| {
server.notify_client(
client,
ChatType::CommandError.server_msg(format!(
"Unable to determine UUID for username \"{}\"",
&username
)),
)
})
.ok()
};
if whitelist_action.eq_ignore_ascii_case("add") {
server
.whitelist_mut()
.edit(server.data_dir().as_ref(), |w| w.push(username.clone()));
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!("\"{}\" added to whitelist", username)),
);
if let Some(uuid) = lookup_uuid() {
server
.editable_settings_mut()
.whitelist
.edit(server.data_dir().as_ref(), |w| w.push(uuid));
server.notify_client(
client,
ChatType::CommandInfo
.server_msg(format!("\"{}\" added to whitelist", username)),
);
}
} else if whitelist_action.eq_ignore_ascii_case("remove") {
server
.whitelist_mut()
.edit(server.data_dir().as_ref(), |w| {
w.retain(|x| !x.eq_ignore_ascii_case(&username.clone()))
});
server.notify_client(
client,
ChatType::CommandInfo
.server_msg(format!("\"{}\" removed from whitelist", username)),
);
if let Some(uuid) = lookup_uuid() {
server
.editable_settings_mut()
.whitelist
.edit(server.data_dir().as_ref(), |w| w.retain(|x| x != &uuid));
server.notify_client(
client,
ChatType::CommandInfo
.server_msg(format!("\"{}\" removed from whitelist", username)),
);
}
} else {
server.notify_client(
client,
@ -1930,16 +1959,22 @@ fn handle_ban(
.username_to_uuid(&target_alias);
if let Ok(uuid) = uuid_result {
if server.banlist().contains_key(&uuid) {
if server.editable_settings().banlist.contains_key(&uuid) {
server.notify_client(
client,
ChatType::CommandError
.server_msg(format!("{} is already on the banlist", target_alias)),
)
} else {
server.banlist_mut().edit(server.data_dir().as_ref(), |b| {
b.insert(uuid, (target_alias.clone(), reason.clone()));
});
server
.editable_settings_mut()
.banlist
.edit(server.data_dir().as_ref(), |b| {
b.insert(uuid, BanRecord {
username_when_banned: target_alias.clone(),
reason: reason.clone(),
});
});
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!(
@ -1990,9 +2025,12 @@ fn handle_unban(
.username_to_uuid(&username);
if let Ok(uuid) = uuid_result {
server.banlist_mut().edit(server.data_dir().as_ref(), |b| {
b.remove(&uuid);
});
server
.editable_settings_mut()
.banlist
.edit(server.data_dir().as_ref(), |b| {
b.remove(&uuid);
});
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!("{} was successfully unbanned", username)),

View File

@ -8,9 +8,6 @@ pub const DEFAULT_DATA_DIR_NAME: &str = "server";
pub struct DataDir {
pub path: PathBuf,
}
impl<T: Into<PathBuf>> From<T> for DataDir {
fn from(t: T) -> Self { Self { path: t.into() } }
}
impl AsRef<Path> for DataDir {
fn as_ref(&self) -> &Path { &self.path }
}

View File

@ -23,11 +23,11 @@ pub mod sys;
// Reexports
pub use crate::{
data_dir::{DataDir, DEFAULT_DATA_DIR_NAME},
data_dir::DEFAULT_DATA_DIR_NAME,
error::Error,
events::Event,
input::Input,
settings::ServerSettings,
settings::{EditableSettings, Settings},
};
use crate::{
@ -35,8 +35,8 @@ use crate::{
chunk_generator::ChunkGenerator,
client::{Client, RegionSubscription},
cmd::ChatCommandExt,
data_dir::DataDir,
login_provider::LoginProvider,
settings::{Banlist, EditableSetting, ServerDescription, Whitelist},
state_ext::StateExt,
sys::sentinel::{DeletedEntities, TrackedComps},
};
@ -110,11 +110,15 @@ impl Server {
/// Create a new `Server`
#[allow(clippy::expect_fun_call)] // TODO: Pending review in #587
#[allow(clippy::needless_update)] // TODO: Pending review in #587
pub fn new(settings: ServerSettings, data_dir: DataDir) -> Result<Self, Error> {
info!("Server is data dir is: {}", data_dir.path.display());
pub fn new(
settings: Settings,
editable_settings: EditableSettings,
data_dir: &std::path::Path,
) -> Result<Self, Error> {
info!("Server is data dir is: {}", data_dir.display());
// persistence_db_dir is relative to data_dir
let persistence_db_dir = data_dir.path.join(&settings.persistence_db_dir);
let persistence_db_dir = data_dir.join(&settings.persistence_db_dir);
// Run pending DB migrations (if any)
debug!("Running DB migrations...");
@ -129,12 +133,10 @@ impl Server {
let mut state = State::default();
state.ecs_mut().insert(settings.clone());
state.ecs_mut().insert(Whitelist::load(&data_dir.path));
state.ecs_mut().insert(Banlist::load(&data_dir.path));
state
.ecs_mut()
.insert(ServerDescription::load(&data_dir.path));
state.ecs_mut().insert(data_dir);
state.ecs_mut().insert(editable_settings);
state.ecs_mut().insert(DataDir {
path: data_dir.to_owned(),
});
state.ecs_mut().insert(EventBus::<ServerEvent>::default());
state
.ecs_mut()
@ -358,11 +360,11 @@ impl Server {
}
pub fn get_server_info(&self) -> ServerInfo {
let settings = self.state.ecs().fetch::<ServerSettings>();
let server_description = self.state.ecs().fetch::<ServerDescription>();
let settings = self.state.ecs().fetch::<Settings>();
let editable_settings = self.state.ecs().fetch::<EditableSettings>();
ServerInfo {
name: settings.server_name.clone(),
description: (**server_description).clone(),
description: (&*editable_settings.server_description).clone(),
git_hash: common::util::GIT_HASH.to_string(),
git_date: common::util::GIT_DATE.to_string(),
auth_provider: settings.auth_server_address.clone(),
@ -375,38 +377,23 @@ impl Server {
}
/// Get a reference to the server's settings
pub fn settings(&self) -> impl Deref<Target = ServerSettings> + '_ {
self.state.ecs().fetch::<ServerSettings>()
pub fn settings(&self) -> impl Deref<Target = Settings> + '_ {
self.state.ecs().fetch::<Settings>()
}
/// Get a mutable reference to the server's settings
pub fn settings_mut(&self) -> impl DerefMut<Target = ServerSettings> + '_ {
self.state.ecs().fetch_mut::<ServerSettings>()
pub fn settings_mut(&self) -> impl DerefMut<Target = Settings> + '_ {
self.state.ecs().fetch_mut::<Settings>()
}
/// Get a mutable reference to the server's whitelist
pub fn whitelist_mut(&self) -> impl DerefMut<Target = Whitelist> + '_ {
self.state.ecs().fetch_mut::<Whitelist>()
/// Get a mutable reference to the server's editable settings
pub fn editable_settings_mut(&self) -> impl DerefMut<Target = EditableSettings> + '_ {
self.state.ecs().fetch_mut::<EditableSettings>()
}
/// Get a reference to the server's banlist
pub fn banlist(&self) -> impl Deref<Target = Banlist> + '_ {
self.state.ecs().fetch::<Banlist>()
}
/// Get a mutable reference to the server's banlist
pub fn banlist_mut(&self) -> impl DerefMut<Target = Banlist> + '_ {
self.state.ecs().fetch_mut::<Banlist>()
}
/// Get a reference to the server's description
pub fn server_description(&self) -> impl Deref<Target = ServerDescription> + '_ {
self.state.ecs().fetch::<ServerDescription>()
}
/// Get a mutable reference to the server's description
pub fn server_description_mut(&self) -> impl DerefMut<Target = ServerDescription> + '_ {
self.state.ecs().fetch_mut::<ServerDescription>()
/// Get a reference to the server's editable settings
pub fn editable_settings(&self) -> impl Deref<Target = EditableSettings> + '_ {
self.state.ecs().fetch::<EditableSettings>()
}
/// Get path to the directory that the server info into

View File

@ -1,3 +1,4 @@
use crate::settings::BanRecord;
use authc::{AuthClient, AuthClientError, AuthToken, Uuid};
use common::msg::RegisterError;
use hashbrown::HashMap;
@ -52,8 +53,8 @@ impl LoginProvider {
pub fn try_login(
&mut self,
username_or_token: &str,
whitelist: &[String],
banlist: &HashMap<Uuid, (String, String)>,
whitelist: &[Uuid],
banlist: &HashMap<Uuid, BanRecord>,
) -> Result<(String, Uuid), RegisterError> {
self
// resolve user information
@ -63,12 +64,12 @@ impl LoginProvider {
// user cannot join if they are listed on the banlist
if let Some(ban_record) = banlist.get(&uuid) {
// Pull reason string out of ban record and send a copy of it
return Err(RegisterError::Banned(ban_record.1.clone()));
return Err(RegisterError::Banned(ban_record.reason.clone()));
}
// user can only join if he is admin, the whitelist is empty (everyone can join)
// or his name is in the whitelist
if !whitelist.is_empty() && !whitelist.contains(&username) {
if !whitelist.is_empty() && !whitelist.contains(&uuid) {
return Err(RegisterError::NotOnWhitelist);
}

View File

@ -17,8 +17,7 @@ use tracing::{error, warn};
use world::sim::FileOpts;
const DEFAULT_WORLD_SEED: u32 = 59686;
//const CONFIG_DIR_ENV: &'static str = "VELOREN_SERVER_CONFIG";
const /*DEFAULT_*/CONFIG_DIR: &str = "server_config";
const CONFIG_DIR: &str = "server_config";
const SETTINGS_FILENAME: &str = "settings.ron";
const WHITELIST_FILENAME: &str = "whitelist.ron";
const BANLIST_FILENAME: &str = "banlist.ron";
@ -26,7 +25,7 @@ const SERVER_DESCRIPTION_FILENAME: &str = "description.ron";
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ServerSettings {
pub struct Settings {
pub gameserver_address: SocketAddr,
pub metrics_address: SocketAddr,
pub auth_server_address: Option<String>,
@ -47,7 +46,7 @@ pub struct ServerSettings {
pub client_timeout: Duration,
}
impl Default for ServerSettings {
impl Default for Settings {
fn default() -> Self {
Self {
gameserver_address: SocketAddr::from(([0; 4], 14004)),
@ -68,7 +67,7 @@ impl Default for ServerSettings {
}
}
impl ServerSettings {
impl Settings {
/// path: Directory that contains the server config directory
pub fn load(path: &Path) -> Self {
let path = Self::get_settings_path(path);
@ -135,7 +134,6 @@ impl ServerSettings {
DEFAULT_WORLD_SEED
},
server_name: "Singleplayer".to_owned(),
//server_description: "Who needs friends anyway?".to_owned(),
max_players: 100,
start_time: 9.0 * 3600.0,
admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want
@ -155,28 +153,51 @@ impl ServerSettings {
fn with_config_dir(path: &Path) -> PathBuf {
let mut path = PathBuf::from(path);
//if let Some(path) = std::env::var_os(CONFIG_DIR_ENV) {
// let config_dir = PathBuf::from(path);
// if config_dir.exists() {
// return config_dir;
// }
// warn!(?path, "VELROREN_SERVER_CONFIG points to invalid path.");
//}
path.push(/* DEFAULT_ */ CONFIG_DIR);
//PathBuf::from(DEFAULT_CONFIG_DIR)
path.push(CONFIG_DIR);
path
}
#[derive(Deserialize, Serialize)]
pub struct BanRecord {
pub username_when_banned: String,
pub reason: String,
}
#[derive(Deserialize, Serialize, Default)]
#[serde(transparent)]
pub struct Whitelist(Vec<String>);
pub struct Whitelist(Vec<Uuid>);
#[derive(Deserialize, Serialize, Default)]
#[serde(transparent)]
pub struct Banlist(HashMap<Uuid, (String, String)>);
pub struct Banlist(HashMap<Uuid, BanRecord>);
#[derive(Deserialize, Serialize)]
#[serde(transparent)]
pub struct ServerDescription(String);
/// Combines all the editable settings into one struct that is stored in the ecs
pub struct EditableSettings {
pub whitelist: Whitelist,
pub banlist: Banlist,
pub server_description: ServerDescription,
}
impl EditableSettings {
pub fn load(data_dir: &Path) -> Self {
Self {
whitelist: Whitelist::load(data_dir),
banlist: Banlist::load(data_dir),
server_description: ServerDescription::load(data_dir),
}
}
pub fn singleplayer(data_dir: &Path) -> Self {
let load = Self::load(data_dir);
Self {
server_description: ServerDescription("Who needs friends anyway?".into()),
..load
}
}
}
impl Default for ServerDescription {
fn default() -> Self { Self("This is the best Veloren server".into()) }
}
@ -194,7 +215,7 @@ impl EditableSetting for ServerDescription {
}
impl Deref for Whitelist {
type Target = Vec<String>;
type Target = Vec<Uuid>;
fn deref(&self) -> &Self::Target { &self.0 }
}
@ -204,7 +225,7 @@ impl DerefMut for Whitelist {
}
impl Deref for Banlist {
type Target = HashMap<Uuid, (String, String)>;
type Target = HashMap<Uuid, BanRecord>;
fn deref(&self) -> &Self::Target { &self.0 }
}

View File

@ -6,8 +6,7 @@ use crate::{
login_provider::LoginProvider,
metrics::{NetworkRequestMetrics, PlayerMetrics},
persistence::character_loader::CharacterLoader,
settings::{Banlist, ServerDescription, Whitelist},
ServerSettings,
EditableSettings, Settings,
};
use common::{
comp::{
@ -65,11 +64,9 @@ impl Sys {
orientations: &mut WriteStorage<'_, Ori>,
players: &mut WriteStorage<'_, Player>,
controllers: &mut WriteStorage<'_, Controller>,
settings: &Read<'_, ServerSettings>,
settings: &Read<'_, Settings>,
alias_validator: &ReadExpect<'_, AliasValidator>,
whitelist: &Whitelist,
banlist: &Banlist,
server_description: &ServerDescription,
editable_settings: &EditableSettings,
) -> Result<(), crate::error::Error> {
loop {
let msg = client.recv().await?;
@ -100,14 +97,17 @@ impl Sys {
view_distance,
token_or_username,
} => {
let (username, uuid) =
match login_provider.try_login(&token_or_username, &whitelist, &banlist) {
Err(err) => {
client.error_state(RequestStateError::RegisterDenied(err));
break Ok(());
},
Ok((username, uuid)) => (username, uuid),
};
let (username, uuid) = match login_provider.try_login(
&token_or_username,
&*editable_settings.whitelist,
&*editable_settings.banlist,
) {
Err(err) => {
client.error_state(RequestStateError::RegisterDenied(err));
break Ok(());
},
Ok((username, uuid)) => (username, uuid),
};
let vd =
view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd)));
@ -207,11 +207,10 @@ impl Sys {
});
// Give the player a welcome message
if !server_description.is_empty() {
client.notify(
ChatType::CommandInfo
.server_msg(String::from(&**server_description)),
);
if !editable_settings.server_description.is_empty() {
client.notify(ChatType::CommandInfo.server_msg(String::from(
&*editable_settings.server_description,
)));
}
// Only send login message if it wasn't already
@ -449,13 +448,9 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Player>,
WriteStorage<'a, Client>,
WriteStorage<'a, Controller>,
Read<'a, ServerSettings>,
Read<'a, Settings>,
ReadExpect<'a, AliasValidator>,
(
ReadExpect<'a, Whitelist>,
ReadExpect<'a, Banlist>,
ReadExpect<'a, ServerDescription>,
),
ReadExpect<'a, EditableSettings>,
);
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
@ -489,7 +484,7 @@ impl<'a> System<'a> for Sys {
mut controllers,
settings,
alias_validator,
(whitelist, banlist, server_description),
editable_settings,
): Self::SystemData,
) {
span!(_guard, "run", "message::Sys::run");
@ -550,9 +545,7 @@ impl<'a> System<'a> for Sys {
&mut controllers,
&settings,
&alias_validator,
&whitelist,
&banlist,
&server_description,
&editable_settings,
);
select!(
_ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()),

View File

@ -1,7 +1,7 @@
use client::Client;
use common::clock::Clock;
use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError};
use server::{DataDir, Error as ServerError, Event, Input, Server, ServerSettings};
use server::{Error as ServerError, Event, Input, Server};
use std::{
sync::{
atomic::{AtomicBool, Ordering},
@ -29,18 +29,19 @@ pub struct Singleplayer {
}
impl Singleplayer {
pub fn new(client: Option<&Client>) -> (Self, ServerSettings) {
pub fn new(client: Option<&Client>) -> (Self, server::Settings) {
let (sender, receiver) = unbounded();
// Determine folder to save server data in
let server_data_dir = DataDir::from({
let server_data_dir = {
let mut path = common::userdata_dir_workspace!();
path.push("singleplayer");
path
});
};
// Create server
let settings = ServerSettings::singleplayer(server_data_dir.as_ref());
let settings = server::Settings::singleplayer(&server_data_dir);
let editable_settings = server::EditableSettings::singleplayer(&server_data_dir);
let thread_pool = client.map(|c| c.thread_pool().clone());
let settings2 = settings.clone();
@ -52,13 +53,15 @@ impl Singleplayer {
let thread = thread::spawn(move || {
let mut server = None;
if let Err(e) = result_sender.send(match Server::new(settings2, server_data_dir) {
Ok(s) => {
server = Some(s);
Ok(())
if let Err(e) = result_sender.send(
match Server::new(settings2, editable_settings, &server_data_dir) {
Ok(s) => {
server = Some(s);
Ok(())
},
Err(e) => Err(e),
},
Err(e) => Err(e),
}) {
) {
warn!(
?e,
"Failed to send singleplayer server initialization result. Most likely the \