Add configuration
This commit is contained in:
parent
c0b3677cb0
commit
0c3685c37c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.env
|
.env
|
||||||
|
lazymc.toml
|
||||||
/target
|
/target
|
||||||
|
|
||||||
# Test server
|
# Test server
|
||||||
|
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -216,7 +216,9 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"minecraft-protocol",
|
"minecraft-protocol",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -611,6 +613,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -28,3 +28,6 @@ tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-t
|
|||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
|
|
||||||
|
serde = "1.0"
|
||||||
|
toml = "0.5"
|
||||||
|
@ -1,21 +1,81 @@
|
|||||||
/// Command to start server.
|
use std::fs;
|
||||||
pub const SERVER_CMD: &str = "/home/timvisee/git/lazymc/mcserver/start";
|
use std::io;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Public address for users to connect to.
|
use serde::Deserialize;
|
||||||
pub const ADDRESS_PUBLIC: &str = "127.0.0.1:9090";
|
|
||||||
|
|
||||||
/// Minecraft server address to proxy to.
|
/// Default configuration file location.
|
||||||
pub const ADDRESS_PROXY: &str = "127.0.0.1:9091";
|
const CONFIG_FILE: &str = "lazymc.toml";
|
||||||
|
|
||||||
/// Server description shown when server is starting.
|
/// Configuration.
|
||||||
pub const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping\n§2☻ Join to start it up";
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Public configuration.
|
||||||
|
pub public: Public,
|
||||||
|
|
||||||
/// Server description shown when server is starting.
|
/// Server configuration.
|
||||||
pub const LABEL_SERVER_STARTING: &str = "§2☻ Server is starting...\n§7⌛ Please wait...";
|
pub server: Server,
|
||||||
|
|
||||||
/// Kick message shown when user tries to connect to starting server.
|
/// Time configuration.
|
||||||
pub const LABEL_SERVER_STARTING_MESSAGE: &str =
|
pub time: Time,
|
||||||
"Server is starting... §c♥§r\n\nThis may take some time.\n\nPlease try to reconnect in a minute.";
|
|
||||||
|
|
||||||
/// Idle server sleeping delay in seconds.
|
/// Messages, shown to the user.
|
||||||
pub const SLEEP_DELAY: u64 = 10;
|
pub messages: Messages,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Load configuration form file.
|
||||||
|
pub fn load() -> Result<Self, io::Error> {
|
||||||
|
let data = fs::read(CONFIG_FILE)?;
|
||||||
|
let config = toml::from_slice(&data)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public configuration.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Public {
|
||||||
|
/// Egress address.
|
||||||
|
#[serde(alias = "address_egress")]
|
||||||
|
pub address: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server configuration.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Server {
|
||||||
|
/// Server directory.
|
||||||
|
pub directory: PathBuf,
|
||||||
|
|
||||||
|
/// Start command.
|
||||||
|
pub command: String,
|
||||||
|
|
||||||
|
/// Ingress address.
|
||||||
|
#[serde(alias = "address_ingress")]
|
||||||
|
pub address: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Time configuration.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Time {
|
||||||
|
/// Sleep after number of seconds.
|
||||||
|
pub sleep_after: u32,
|
||||||
|
|
||||||
|
/// Minimum time in seconds to stay online when server is started.
|
||||||
|
// TODO: implement this
|
||||||
|
#[serde(alias = "minimum_online_time")]
|
||||||
|
pub min_online_time: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Messages {
|
||||||
|
/// MOTD when server is sleeping.
|
||||||
|
pub motd_sleeping: String,
|
||||||
|
|
||||||
|
/// MOTD when server is starting.
|
||||||
|
pub motd_starting: String,
|
||||||
|
|
||||||
|
/// Login message when server is starting.
|
||||||
|
pub login_starting: String,
|
||||||
|
}
|
||||||
|
57
src/main.rs
57
src/main.rs
@ -8,6 +8,7 @@ pub(crate) mod server;
|
|||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
@ -30,25 +31,38 @@ use server::ServerState;
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), ()> {
|
async fn main() -> Result<(), ()> {
|
||||||
// Initialize logging
|
// Initialize logging
|
||||||
|
// TODO: set default levels!
|
||||||
let _ = dotenv::dotenv();
|
let _ = dotenv::dotenv();
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
let config = match Config::load() {
|
||||||
|
Ok(config) => Arc::new(config),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to load configuration:");
|
||||||
|
error!("{}", err);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let server_state = Arc::new(ServerState::default());
|
let server_state = Arc::new(ServerState::default());
|
||||||
|
|
||||||
// Listen for new connections
|
// Listen for new connections
|
||||||
// TODO: do not drop error here
|
// TODO: do not drop error here
|
||||||
let listener = TcpListener::bind(ADDRESS_PUBLIC).await.map_err(|err| {
|
let listener = TcpListener::bind(config.public.address)
|
||||||
error!("Failed to start: {}", err);
|
.await
|
||||||
()
|
.map_err(|err| {
|
||||||
})?;
|
error!("Failed to start: {}", err);
|
||||||
|
()
|
||||||
|
})?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Proxying egress {} to ingress {}",
|
"Proxying egress {} to ingress {}",
|
||||||
ADDRESS_PUBLIC, ADDRESS_PROXY,
|
config.public.address, config.server.address,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Spawn server monitor and signal handler
|
// Spawn server monitor and signal handler
|
||||||
tokio::spawn(server_monitor(server_state.clone()));
|
tokio::spawn(server_monitor(config.clone(), server_state.clone()));
|
||||||
tokio::spawn(signal_handler(server_state.clone()));
|
tokio::spawn(signal_handler(server_state.clone()));
|
||||||
|
|
||||||
// Proxy all incomming connections
|
// Proxy all incomming connections
|
||||||
@ -57,18 +71,19 @@ async fn main() -> Result<(), ()> {
|
|||||||
|
|
||||||
if !server_state.online() {
|
if !server_state.online() {
|
||||||
// When server is not online, spawn a status server
|
// When server is not online, spawn a status server
|
||||||
let transfer = serve_status(client, inbound, server_state.clone()).map(|r| {
|
let transfer =
|
||||||
if let Err(err) = r {
|
serve_status(client, inbound, config.clone(), server_state.clone()).map(|r| {
|
||||||
error!("Failed to serve status: {:?}", err);
|
if let Err(err) = r {
|
||||||
}
|
warn!("Failed to serve status: {:?}", err);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tokio::spawn(transfer);
|
tokio::spawn(transfer);
|
||||||
} else {
|
} else {
|
||||||
// When server is online, proxy all
|
// When server is online, proxy all
|
||||||
let transfer = proxy(inbound, ADDRESS_PROXY.to_string()).map(|r| {
|
let transfer = proxy(inbound, config.server.address).map(|r| {
|
||||||
if let Err(err) = r {
|
if let Err(err) = r {
|
||||||
error!("Failed to proxy: {}", err);
|
warn!("Failed to proxy: {}", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,9 +106,8 @@ pub async fn signal_handler(server_state: Arc<ServerState>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Server monitor task.
|
/// Server monitor task.
|
||||||
pub async fn server_monitor(state: Arc<ServerState>) {
|
pub async fn server_monitor(config: Arc<Config>, state: Arc<ServerState>) {
|
||||||
let addr = ADDRESS_PROXY.parse().expect("invalid server IP");
|
monitor::monitor_server(config, state).await
|
||||||
monitor::monitor_server(addr, state).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Proxy the given inbound stream to a target address.
|
/// Proxy the given inbound stream to a target address.
|
||||||
@ -101,6 +115,7 @@ pub async fn server_monitor(state: Arc<ServerState>) {
|
|||||||
async fn serve_status(
|
async fn serve_status(
|
||||||
client: Client,
|
client: Client,
|
||||||
mut inbound: TcpStream,
|
mut inbound: TcpStream,
|
||||||
|
config: Arc<Config>,
|
||||||
server: Arc<ServerState>,
|
server: Arc<ServerState>,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let (mut reader, mut writer) = inbound.split();
|
let (mut reader, mut writer) = inbound.split();
|
||||||
@ -122,7 +137,7 @@ async fn serve_status(
|
|||||||
// Hijack login start
|
// Hijack login start
|
||||||
if client.state() == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_START {
|
if client.state() == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_START {
|
||||||
let packet = LoginDisconnect {
|
let packet = LoginDisconnect {
|
||||||
reason: Message::new(Payload::text(LABEL_SERVER_STARTING_MESSAGE)),
|
reason: Message::new(Payload::text(&config.messages.login_starting)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
@ -137,7 +152,7 @@ async fn serve_status(
|
|||||||
if !server.starting() {
|
if !server.starting() {
|
||||||
server.set_starting(true);
|
server.set_starting(true);
|
||||||
server.update_last_active_time();
|
server.update_last_active_time();
|
||||||
tokio::spawn(server::start(server).map(|_| ()));
|
tokio::spawn(server::start(config, server).map(|_| ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -173,9 +188,9 @@ async fn serve_status(
|
|||||||
|
|
||||||
// Select description
|
// Select description
|
||||||
let description = if server.starting() {
|
let description = if server.starting() {
|
||||||
LABEL_SERVER_STARTING
|
&config.messages.motd_starting
|
||||||
} else {
|
} else {
|
||||||
LABEL_SERVER_SLEEPING
|
&config.messages.motd_sleeping
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build status resposne
|
// Build status resposne
|
||||||
@ -221,7 +236,7 @@ async fn serve_status(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Proxy the inbound stream to a target address.
|
/// Proxy the inbound stream to a target address.
|
||||||
async fn proxy(mut inbound: TcpStream, addr_target: String) -> Result<(), Box<dyn Error>> {
|
async fn proxy(mut inbound: TcpStream, addr_target: SocketAddr) -> Result<(), Box<dyn Error>> {
|
||||||
// Set up connection to server
|
// Set up connection to server
|
||||||
// TODO: on connect fail, ping server and redirect to serve_status if offline
|
// TODO: on connect fail, ping server and redirect to serve_status if offline
|
||||||
let mut outbound = TcpStream::connect(addr_target).await?;
|
let mut outbound = TcpStream::connect(addr_target).await?;
|
||||||
|
@ -13,6 +13,7 @@ use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
use crate::proto::{self, ClientState, RawPacket, PROTO_DEFAULT_PROTOCOL};
|
use crate::proto::{self, ClientState, RawPacket, PROTO_DEFAULT_PROTOCOL};
|
||||||
use crate::server::ServerState;
|
use crate::server::ServerState;
|
||||||
|
|
||||||
@ -23,7 +24,10 @@ const MONITOR_PING_INTERVAL: u64 = 2;
|
|||||||
const STATUS_TIMEOUT: u64 = 8;
|
const STATUS_TIMEOUT: u64 = 8;
|
||||||
|
|
||||||
/// Monitor server.
|
/// Monitor server.
|
||||||
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
pub async fn monitor_server(config: Arc<Config>, state: Arc<ServerState>) {
|
||||||
|
// Server address
|
||||||
|
let addr = config.server.address;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Poll server state and update internal status
|
// Poll server state and update internal status
|
||||||
trace!("Fetching status for {} ... ", addr);
|
trace!("Fetching status for {} ... ", addr);
|
||||||
@ -31,8 +35,8 @@ pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
|||||||
state.update_status(status);
|
state.update_status(status);
|
||||||
|
|
||||||
// Sleep server when it's bedtime
|
// Sleep server when it's bedtime
|
||||||
if state.should_sleep() {
|
if state.should_sleep(&config) {
|
||||||
info!("Server has been idle, initiating sleep...");
|
info!("Server has been idle, sleeping...");
|
||||||
state.kill_server();
|
state.kill_server();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::time::{Duration, Instant};
|
|||||||
use minecraft_protocol::data::server_status::ServerStatus;
|
use minecraft_protocol::data::server_status::ServerStatus;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::config::{SERVER_CMD, SLEEP_DELAY};
|
use crate::config::Config;
|
||||||
|
|
||||||
/// Shared server state.
|
/// Shared server state.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
@ -127,7 +127,7 @@ impl ServerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the server should now sleep.
|
/// Check whether the server should now sleep.
|
||||||
pub fn should_sleep(&self) -> bool {
|
pub fn should_sleep(&self, config: &Config) -> bool {
|
||||||
// TODO: when initating server start, set last active time!
|
// TODO: when initating server start, set last active time!
|
||||||
// TODO: do not initiate sleep when starting?
|
// TODO: do not initiate sleep when starting?
|
||||||
// TODO: do not initiate sleep when already initiated (with timeout)
|
// TODO: do not initiate sleep when already initiated (with timeout)
|
||||||
@ -139,7 +139,7 @@ impl ServerState {
|
|||||||
|
|
||||||
// Last active time must have passed sleep threshold
|
// Last active time must have passed sleep threshold
|
||||||
if let Some(last_idle) = self.last_active.lock().unwrap().as_ref() {
|
if let Some(last_idle) = self.last_active.lock().unwrap().as_ref() {
|
||||||
return last_idle.elapsed() >= Duration::from_secs(SLEEP_DELAY);
|
return last_idle.elapsed() >= Duration::from_secs(config.time.sleep_after as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
@ -147,8 +147,22 @@ impl ServerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start Minecraft server.
|
/// Start Minecraft server.
|
||||||
pub async fn start(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn start(
|
||||||
let mut cmd = Command::new(SERVER_CMD);
|
config: Arc<Config>,
|
||||||
|
state: Arc<ServerState>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// TODO: this doesn't properly handle quotes
|
||||||
|
let args = config
|
||||||
|
.server
|
||||||
|
.command
|
||||||
|
.split_terminator(" ")
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Build command
|
||||||
|
let mut cmd = Command::new(args[0]);
|
||||||
|
cmd.args(args.iter().skip(1));
|
||||||
|
cmd.current_dir(&config.server.directory);
|
||||||
|
cmd.kill_on_drop(true);
|
||||||
|
|
||||||
info!("Starting server...");
|
info!("Starting server...");
|
||||||
let mut child = cmd.spawn()?;
|
let mut child = cmd.spawn()?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user