2019-08-19 12:39:23 +00:00
#![ deny(unsafe_code) ]
2020-09-27 16:20:40 +00:00
#![ deny(clippy::clone_on_ref_ptr) ]
2020-09-20 02:40:26 +00:00
#![ feature(bool_to_option) ]
2019-08-19 12:39:23 +00:00
2020-10-10 06:10:04 +00:00
mod admin ;
2021-03-13 06:48:30 +00:00
/// `server-cli` interface commands not to be confused with the commands sent
/// from the client to the server
mod cmd ;
2020-10-05 08:35:24 +00:00
mod settings ;
2020-10-03 19:10:34 +00:00
mod shutdown_coordinator ;
2020-08-31 08:41:11 +00:00
mod tui_runner ;
mod tuilog ;
2021-03-28 23:40:53 +00:00
use crate ::{
cmd ::Message , shutdown_coordinator ::ShutdownCoordinator , tui_runner ::Tui , tuilog ::TuiLog ,
} ;
2020-10-10 06:10:04 +00:00
use clap ::{ App , Arg , SubCommand } ;
2021-03-08 22:40:02 +00:00
use common ::clock ::Clock ;
use common_base ::span ;
2021-03-15 22:50:26 +00:00
use core ::sync ::atomic ::{ AtomicUsize , Ordering } ;
2021-04-13 22:05:47 +00:00
use server ::{
persistence ::{ DatabaseSettings , SqlLogMode } ,
Event , Input , Server ,
} ;
2020-10-03 19:10:34 +00:00
use std ::{
io ,
sync ::{ atomic ::AtomicBool , mpsc , Arc } ,
time ::Duration ,
} ;
2021-03-21 17:33:39 +00:00
use tracing ::{ info , trace } ;
2020-07-27 16:26:34 +00:00
2021-03-28 23:40:53 +00:00
lazy_static ::lazy_static! {
pub static ref LOG : TuiLog < 'static > = TuiLog ::default ( ) ;
}
2019-03-19 19:53:35 +00:00
const TPS : u64 = 30 ;
2020-07-27 16:26:34 +00:00
2020-12-10 11:50:48 +00:00
#[ allow(clippy::unnecessary_wraps) ]
2020-07-27 16:26:34 +00:00
fn main ( ) -> io ::Result < ( ) > {
2020-07-28 13:47:01 +00:00
let matches = App ::new ( " Veloren server cli " )
2020-09-16 10:50:55 +00:00
. version ( common ::util ::DISPLAY_VERSION_LONG . as_str ( ) )
2020-07-28 13:47:01 +00:00
. author ( " The veloren devs <https://gitlab.com/veloren/veloren> " )
2020-07-28 15:02:36 +00:00
. about ( " The veloren server cli provides an easy to use interface to start a veloren server " )
2020-09-20 04:51:20 +00:00
. args ( & [
2020-07-28 13:47:01 +00:00
Arg ::with_name ( " basic " )
. short ( " b " )
. long ( " basic " )
2020-10-10 06:10:04 +00:00
. help ( " Disables the tui " ) ,
2021-03-25 23:22:46 +00:00
Arg ::with_name ( " non-interactive " )
. short ( " n " )
. long ( " non-interactive " )
. help ( " doesn't listen on STDIN. Useful if you want to send the server in background, and your kernels terminal driver will send SIGTTIN to it otherwise. (https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Redirections). and you dont want to use `stty -tostop` or `nohub` or `tmux` or `screen` or `<<< \" \\ 004 \" ` to the programm. This implies `-b` " ) ,
2020-09-20 04:51:20 +00:00
Arg ::with_name ( " no-auth " )
. long ( " no-auth " )
. help ( " Runs without auth enabled " ) ,
2021-04-13 22:05:47 +00:00
Arg ::with_name ( " sql-log-mode " )
. long ( " sql-log-mode " )
. help ( " Enables SQL logging, valid values are \" trace \" and \" profile \" " )
. possible_values ( & [ " trace " , " profile " ] )
. takes_value ( true )
2020-09-20 04:51:20 +00:00
] )
2020-10-10 06:10:04 +00:00
. subcommand (
SubCommand ::with_name ( " admin " )
. about ( " Add or remove admins " )
. subcommands ( vec! [
SubCommand ::with_name ( " add " ) . about ( " Adds an admin " ) . arg (
Arg ::with_name ( " username " )
. help ( " Name of the admin to add " )
. required ( true ) ,
) ,
SubCommand ::with_name ( " remove " )
. about ( " Removes an admin " )
. arg (
Arg ::with_name ( " username " )
. help ( " Name of the admin to remove " )
. required ( true ) ,
) ,
] ) ,
)
2020-07-28 13:47:01 +00:00
. get_matches ( ) ;
2020-10-10 06:10:04 +00:00
let basic = matches . is_present ( " basic " )
// Default to basic with these subcommands
| | matches
. subcommand_name ( )
. filter ( | name | [ " admin " ] . contains ( name ) )
. is_some ( ) ;
2021-03-25 23:22:46 +00:00
let noninteractive = matches . is_present ( " non-interactive " ) ;
2020-09-20 04:51:20 +00:00
let no_auth = matches . is_present ( " no-auth " ) ;
2020-10-03 19:10:34 +00:00
2021-04-13 22:05:47 +00:00
let sql_log_mode = match matches . value_of ( " sql-log-mode " ) {
Some ( " trace " ) = > SqlLogMode ::Trace ,
Some ( " profile " ) = > SqlLogMode ::Profile ,
_ = > SqlLogMode ::Disabled ,
} ;
2021-03-25 23:22:46 +00:00
// noninteractive implies basic
let basic = basic | | noninteractive ;
2020-10-03 19:10:34 +00:00
let sigusr1_signal = Arc ::new ( AtomicBool ::new ( false ) ) ;
#[ cfg(any(target_os = " linux " , target_os = " macos " )) ]
2021-03-09 19:12:57 +00:00
let _ = signal_hook ::flag ::register ( signal_hook ::consts ::SIGUSR1 , Arc ::clone ( & sigusr1_signal ) ) ;
2020-10-03 19:10:34 +00:00
2021-03-28 23:40:53 +00:00
let ( _guards , _guards2 ) = if basic {
( vec! [ ] , common_frontend ::init_stdout ( None ) )
} else {
( common_frontend ::init ( None , | | LOG . clone ( ) ) , vec! [ ] )
} ;
2019-01-30 12:11:34 +00:00
2020-10-05 08:35:24 +00:00
// Load settings
let settings = settings ::Settings ::load ( ) ;
2020-10-05 07:41:58 +00:00
// Determine folder to save server data in
2020-10-06 02:59:47 +00:00
let server_data_dir = {
2021-03-08 22:40:02 +00:00
let mut path = common_base ::userdata_dir_workspace! ( ) ;
2020-10-25 20:19:39 +00:00
info! ( " Using userdata folder at {} " , path . display ( ) ) ;
2020-10-05 07:41:58 +00:00
path . push ( server ::DEFAULT_DATA_DIR_NAME ) ;
path
2020-10-06 02:59:47 +00:00
} ;
2020-10-05 07:41:58 +00:00
2021-03-11 00:08:12 +00:00
let runtime = Arc ::new (
tokio ::runtime ::Builder ::new_multi_thread ( )
. enable_all ( )
2021-03-15 22:50:26 +00:00
. thread_name_fn ( | | {
static ATOMIC_ID : AtomicUsize = AtomicUsize ::new ( 0 ) ;
let id = ATOMIC_ID . fetch_add ( 1 , Ordering ::SeqCst ) ;
format! ( " tokio-server- {} " , id )
} )
2021-03-11 00:08:12 +00:00
. build ( )
. unwrap ( ) ,
) ;
2020-10-05 08:35:24 +00:00
// Load server settings
2020-10-06 02:59:47 +00:00
let mut server_settings = server ::Settings ::load ( & server_data_dir ) ;
2020-10-10 06:10:04 +00:00
let mut editable_settings = server ::EditableSettings ::load ( & server_data_dir ) ;
2021-04-13 22:05:47 +00:00
// Relative to data_dir
const PERSISTENCE_DB_DIR : & str = " saves " ;
let database_settings = DatabaseSettings {
db_dir : server_data_dir . join ( PERSISTENCE_DB_DIR ) ,
sql_log_mode ,
} ;
2020-10-10 07:01:30 +00:00
#[ allow(clippy::single_match) ] // Note: remove this when there are more subcommands
2020-10-10 06:10:04 +00:00
match matches . subcommand ( ) {
( " admin " , Some ( sub_m ) ) = > {
admin ::admin_subcommand (
2021-03-11 00:08:12 +00:00
runtime ,
2020-10-10 06:10:04 +00:00
sub_m ,
& server_settings ,
& mut editable_settings ,
& server_data_dir ,
) ;
return Ok ( ( ) ) ;
} ,
_ = > { } ,
}
// Panic hook to ensure that console mode is set back correctly if in non-basic
// mode
if ! basic {
let hook = std ::panic ::take_hook ( ) ;
std ::panic ::set_hook ( Box ::new ( move | info | {
Tui ::shutdown ( basic ) ;
hook ( info ) ;
} ) ) ;
}
2021-03-25 23:22:46 +00:00
let tui = ( ! basic | | ! noninteractive ) . then ( | | Tui ::run ( basic ) ) ;
2020-10-10 06:10:04 +00:00
info! ( " Starting server... " ) ;
2020-10-05 07:41:58 +00:00
2020-09-20 04:51:20 +00:00
if no_auth {
2020-10-05 08:35:24 +00:00
server_settings . auth_server_address = None ;
2020-09-20 04:51:20 +00:00
}
2020-10-05 07:41:58 +00:00
2020-10-05 08:35:24 +00:00
let server_port = & server_settings . gameserver_address . port ( ) ;
let metrics_port = & server_settings . metrics_address . port ( ) ;
2020-07-28 10:02:48 +00:00
// Create server
2021-01-15 13:04:32 +00:00
let mut server = Server ::new (
server_settings ,
editable_settings ,
2021-04-13 22:05:47 +00:00
database_settings ,
2021-01-15 13:04:32 +00:00
& server_data_dir ,
runtime ,
)
. expect ( " Failed to create server instance! " ) ;
2020-07-28 10:02:48 +00:00
2020-10-03 19:10:34 +00:00
info! (
? server_port ,
? metrics_port ,
" Server is ready to accept connections. "
) ;
let mut shutdown_coordinator = ShutdownCoordinator ::new ( Arc ::clone ( & sigusr1_signal ) ) ;
2020-07-28 10:02:48 +00:00
2020-10-10 06:10:04 +00:00
// Set up an fps clock
2020-11-12 02:47:22 +00:00
let mut clock = Clock ::new ( Duration ::from_secs_f64 ( 1.0 / TPS as f64 ) ) ;
2020-10-10 06:10:04 +00:00
// Wait for a tick so we don't start with a zero dt
2021-03-21 17:33:39 +00:00
let mut tick_no = 0 u64 ;
2020-07-28 10:02:48 +00:00
loop {
2021-03-21 17:33:39 +00:00
tick_no + = 1 ;
2020-11-22 08:50:25 +00:00
span! ( guard , " work " ) ;
2020-10-03 19:10:34 +00:00
// Terminate the server if instructed to do so by the shutdown coordinator
2020-10-05 08:35:24 +00:00
if shutdown_coordinator . check ( & mut server , & settings ) {
2020-10-03 19:10:34 +00:00
break ;
}
2020-07-28 10:02:48 +00:00
let events = server
2020-11-10 12:30:01 +00:00
. tick ( Input ::default ( ) , clock . dt ( ) )
2020-07-28 10:02:48 +00:00
. expect ( " Failed to tick server " ) ;
for event in events {
match event {
Event ::ClientConnected { entity : _ } = > info! ( " Client connected! " ) ,
Event ::ClientDisconnected { entity : _ } = > info! ( " Client disconnected! " ) ,
Event ::Chat { entity : _ , msg } = > info! ( " [Client] {} " , msg ) ,
}
}
// Clean up the server after a tick.
server . cleanup ( ) ;
2021-03-21 17:33:39 +00:00
if tick_no . rem_euclid ( 1000 ) = = 0 {
trace! ( ? tick_no , " keepalive " )
}
2020-09-20 02:40:26 +00:00
if let Some ( tui ) = tui . as_ref ( ) {
match tui . msg_r . try_recv ( ) {
Ok ( msg ) = > match msg {
Message ::AbortShutdown = > shutdown_coordinator . abort_shutdown ( & mut server ) ,
Message ::Shutdown { grace_period } = > {
// TODO: The TUI parser doesn't support quoted strings so it is not
// currently possible to provide a shutdown reason
// from the console.
let message = " The server is shutting down " . to_owned ( ) ;
shutdown_coordinator . initiate_shutdown ( & mut server , grace_period , message ) ;
} ,
Message ::Quit = > {
info! ( " Closing the server " ) ;
break ;
} ,
2020-10-10 06:10:04 +00:00
Message ::AddAdmin ( username ) = > {
server . add_admin ( & username ) ;
} ,
Message ::RemoveAdmin ( username ) = > {
server . remove_admin ( & username ) ;
} ,
2021-03-13 06:48:30 +00:00
Message ::LoadArea ( view_distance ) = > {
2021-04-15 18:07:46 +00:00
#[ cfg(feature = " worldgen " ) ]
2021-03-13 06:48:30 +00:00
server . create_centered_persister ( view_distance ) ;
} ,
2021-04-13 22:05:47 +00:00
Message ::SetSqlLogMode ( sql_log_mode ) = > {
server . set_sql_log_mode ( sql_log_mode ) ;
} ,
Message ::DisconnectAllClients = > {
server . disconnect_all_clients ( ) ;
} ,
2020-10-03 19:10:34 +00:00
} ,
2020-09-20 02:40:26 +00:00
Err ( mpsc ::TryRecvError ::Empty ) | Err ( mpsc ::TryRecvError ::Disconnected ) = > { } ,
}
}
2020-07-28 10:02:48 +00:00
2020-11-22 08:50:25 +00:00
drop ( guard ) ;
2020-07-28 10:02:48 +00:00
// Wait for the next tick.
2020-11-10 12:30:01 +00:00
clock . tick ( ) ;
2020-11-22 08:50:25 +00:00
#[ cfg(feature = " tracy " ) ]
2021-03-11 09:25:59 +00:00
common_base ::tracy_client ::finish_continuous_frame! ( ) ;
2020-07-28 10:02:48 +00:00
}
Ok ( ( ) )
}