refractor into smaller modules
This commit is contained in:
22
src/config.rs
Normal file
22
src/config.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
pub listen_addr: String,
|
||||||
|
pub server_addr: String,
|
||||||
|
pub motd_server_addr: String,
|
||||||
|
pub rcon_addr: String,
|
||||||
|
pub rcon_password: String,
|
||||||
|
pub idle_timeout_secs: u64,
|
||||||
|
pub polling_interval_millis: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn load(path: &str) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let content = fs::read_to_string(path)?;
|
||||||
|
let config: Config = toml::from_str(&content)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/main.rs
149
src/main.rs
@@ -1,144 +1,29 @@
|
|||||||
|
mod config;
|
||||||
|
mod motd;
|
||||||
|
mod monitor;
|
||||||
|
mod proxy;
|
||||||
|
mod rcon;
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs;
|
use tokio::main;
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use rcon::Connection;
|
use config::Config;
|
||||||
use serde::Deserialize;
|
|
||||||
use tokio::time::Instant;
|
|
||||||
use tokio::{io::copy_bidirectional, main};
|
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
|
||||||
use valence::network::{
|
|
||||||
async_trait, BroadcastToLan, CleanupFn, ConnectionMode, HandshakeData, ServerListPing,
|
|
||||||
};
|
|
||||||
|
|
||||||
use valence::prelude::*;
|
|
||||||
use valence::MINECRAFT_VERSION;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
listen_addr: String,
|
|
||||||
server_addr: String,
|
|
||||||
motd_server_addr: String,
|
|
||||||
rcon_addr: String,
|
|
||||||
rcon_password: String,
|
|
||||||
idle_timeout_secs: u64,
|
|
||||||
polling_interval_millis: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn load(path: &str) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let content = fs::read_to_string(path)?;
|
|
||||||
let config: Config = toml::from_str(&content)?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MotdCallbacks;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl NetworkCallbacks for MotdCallbacks {
|
|
||||||
async fn server_list_ping(
|
|
||||||
&self,
|
|
||||||
_shared: &SharedNetworkState,
|
|
||||||
_remote_addr: SocketAddr,
|
|
||||||
handshake_data: &HandshakeData,
|
|
||||||
) -> ServerListPing {
|
|
||||||
ServerListPing::Respond {
|
|
||||||
online_players: 0,
|
|
||||||
max_players: 0,
|
|
||||||
player_sample: vec![],
|
|
||||||
description: "Serwer jest ".into_text() +
|
|
||||||
"wyłączony".into_text().color(Color::rgb(250, 50, 50)) + "!\n" +
|
|
||||||
"Dołącz aby uruchomić serwer! ".into_text().color(Color::rgb(255, 150, 230)) +
|
|
||||||
format!("⏲ {}s", 20).into_text().color(Color::rgb(80, 80, 80)),
|
|
||||||
favicon_png: include_bytes!("../assets/icon.png"),
|
|
||||||
version_name: MINECRAFT_VERSION.to_string(),
|
|
||||||
protocol: handshake_data.protocol_version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn broadcast_to_lan(&self, _shared: &SharedNetworkState) -> BroadcastToLan {
|
|
||||||
BroadcastToLan::Enabled("Hello Valence!".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn login(
|
|
||||||
&self,
|
|
||||||
_shared: &SharedNetworkState,
|
|
||||||
_info: &NewClientInfo,
|
|
||||||
) -> Result<CleanupFn, Text> {
|
|
||||||
Err("You are not meant to join this example".color(Color::rgb(250, 30, 21)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_players_online(s: &str) -> Option<u32> {
|
|
||||||
s.split_whitespace()
|
|
||||||
.find_map(|tok| tok.parse::<u32>().ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[main]
|
#[main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let config = Config::load("config.toml")?;
|
let config = Config::load("config.toml")?;
|
||||||
|
|
||||||
App::new()
|
let motd_config = config.clone();
|
||||||
.insert_resource(NetworkSettings {
|
|
||||||
connection_mode: ConnectionMode::Offline,
|
|
||||||
callbacks: MotdCallbacks.into(),
|
|
||||||
address: config.motd_server_addr.parse().unwrap(),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.run();
|
|
||||||
|
|
||||||
println!("Listening on {}", config.listen_addr);
|
|
||||||
println!("Proxying to {}", config.server_addr);
|
|
||||||
println!("Motd server running on {}", config.motd_server_addr);
|
|
||||||
|
|
||||||
let listener = TcpListener::bind(&config.listen_addr).await?;
|
|
||||||
let server_addr = config.server_addr.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut conn = <Connection<TcpStream>>::builder()
|
motd::create_motd_server(&motd_config).run();
|
||||||
.enable_minecraft_quirks(true)
|
|
||||||
.connect(&config.rcon_addr, &config.rcon_password)
|
|
||||||
.await.unwrap();
|
|
||||||
|
|
||||||
let mut idle = false;
|
|
||||||
let mut last_online = Instant::now();
|
|
||||||
let idle_timeout = Duration::from_secs(config.idle_timeout_secs);
|
|
||||||
let polling_interval = Duration::from_millis(config.polling_interval_millis);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let players_cmd_output = conn.cmd("list").await.unwrap();
|
|
||||||
let players_number = parse_players_online(&players_cmd_output).unwrap();
|
|
||||||
|
|
||||||
if players_number > 0 {
|
|
||||||
last_online = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{players_number} {idle}");
|
|
||||||
|
|
||||||
if Instant::now() - last_online > idle_timeout {
|
|
||||||
idle = true;
|
|
||||||
println!("Stopping the server");
|
|
||||||
conn.cmd("stop").await.unwrap();
|
|
||||||
} else {
|
|
||||||
idle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(polling_interval).await;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
while let Ok((mut inbound, _)) = listener.accept().await {
|
let monitor_config = config.clone();
|
||||||
let mut outbound = TcpStream::connect(&server_addr).await?;
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = monitor::run_idle_monitor(monitor_config).await {
|
||||||
|
eprintln!("Idle monitor error: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tokio::spawn(async move {
|
proxy::run_proxy(config.listen_addr, config.server_addr).await
|
||||||
if let Err(e) = copy_bidirectional(&mut inbound, &mut outbound).await {
|
|
||||||
println!("Failed to transfer; error={e}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/monitor.rs
Normal file
43
src/monitor.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::rcon;
|
||||||
|
|
||||||
|
fn parse_players_online(s: &str) -> Option<u32> {
|
||||||
|
s.split_whitespace()
|
||||||
|
.find_map(|tok| tok.parse::<u32>().ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_idle_monitor(config: Config) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut conn = rcon::connect_rcon(&config.rcon_addr, &config.rcon_password).await?;
|
||||||
|
|
||||||
|
let mut idle = false;
|
||||||
|
let mut last_online = Instant::now();
|
||||||
|
let idle_timeout = Duration::from_secs(config.idle_timeout_secs);
|
||||||
|
let polling_interval = Duration::from_millis(config.polling_interval_millis);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let players_cmd_output = conn.cmd("list").await?;
|
||||||
|
let players_number = parse_players_online(&players_cmd_output).unwrap_or(0);
|
||||||
|
|
||||||
|
if players_number > 0 {
|
||||||
|
last_online = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{players_number} {idle}");
|
||||||
|
|
||||||
|
if Instant::now() - last_online > idle_timeout {
|
||||||
|
if !idle {
|
||||||
|
idle = true;
|
||||||
|
println!("Stopping the server");
|
||||||
|
conn.cmd("stop").await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(polling_interval).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/motd.rs
Normal file
62
src/motd.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
use valence::network::{
|
||||||
|
async_trait, BroadcastToLan, CleanupFn, ConnectionMode, HandshakeData, ServerListPing,
|
||||||
|
};
|
||||||
|
use valence::prelude::*;
|
||||||
|
use valence::MINECRAFT_VERSION;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
|
struct MotdCallbacks;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl NetworkCallbacks for MotdCallbacks {
|
||||||
|
async fn server_list_ping(
|
||||||
|
&self,
|
||||||
|
_shared: &SharedNetworkState,
|
||||||
|
_remote_addr: SocketAddr,
|
||||||
|
handshake_data: &HandshakeData,
|
||||||
|
) -> ServerListPing {
|
||||||
|
ServerListPing::Respond {
|
||||||
|
online_players: 0,
|
||||||
|
max_players: 0,
|
||||||
|
player_sample: vec![],
|
||||||
|
description: "Serwer jest ".into_text()
|
||||||
|
+ "wyłączony".into_text().color(Color::rgb(250, 50, 50))
|
||||||
|
+ "!\n"
|
||||||
|
+ "Dołącz aby uruchomić serwer! "
|
||||||
|
.into_text()
|
||||||
|
.color(Color::rgb(255, 150, 230))
|
||||||
|
+ format!("⏲ {}s", 20)
|
||||||
|
.into_text()
|
||||||
|
.color(Color::rgb(80, 80, 80)),
|
||||||
|
favicon_png: include_bytes!("../assets/icon.png"),
|
||||||
|
version_name: MINECRAFT_VERSION.to_string(),
|
||||||
|
protocol: handshake_data.protocol_version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast_to_lan(&self, _shared: &SharedNetworkState) -> BroadcastToLan {
|
||||||
|
BroadcastToLan::Enabled("Hello Valence!".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login(
|
||||||
|
&self,
|
||||||
|
_shared: &SharedNetworkState,
|
||||||
|
_info: &NewClientInfo,
|
||||||
|
) -> Result<CleanupFn, Text> {
|
||||||
|
Err("You are not meant to join this example".color(Color::rgb(250, 30, 21)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_motd_server(config: &Config) -> App {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.insert_resource(NetworkSettings {
|
||||||
|
connection_mode: ConnectionMode::Offline,
|
||||||
|
callbacks: MotdCallbacks.into(),
|
||||||
|
address: config.motd_server_addr.parse().unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.add_plugins(DefaultPlugins);
|
||||||
|
app
|
||||||
|
}
|
||||||
29
src/proxy.rs
Normal file
29
src/proxy.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use tokio::io::copy_bidirectional;
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
pub async fn run_proxy(listen_addr: String, server_addr: String) -> Result<(), Box<dyn Error>> {
|
||||||
|
println!("Listening on {}", listen_addr);
|
||||||
|
println!("Proxying to {}", server_addr);
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(&listen_addr).await?;
|
||||||
|
|
||||||
|
while let Ok((mut inbound, _)) = listener.accept().await {
|
||||||
|
let server_addr = server_addr.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match TcpStream::connect(&server_addr).await {
|
||||||
|
Ok(mut outbound) => {
|
||||||
|
if let Err(e) = copy_bidirectional(&mut inbound, &mut outbound).await {
|
||||||
|
println!("Failed to transfer; error={e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to connect to server; error={e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
37
src/rcon.rs
Normal file
37
src/rcon.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::time::Duration;
|
||||||
|
use rcon::Connection;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
pub async fn connect_rcon(
|
||||||
|
addr: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<Connection<TcpStream>, Box<dyn Error>> {
|
||||||
|
let conn = <Connection<TcpStream>>::builder()
|
||||||
|
.enable_minecraft_quirks(true)
|
||||||
|
.connect(addr, password)
|
||||||
|
.await?;
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_rcon(
|
||||||
|
addr: &str,
|
||||||
|
password: &str,
|
||||||
|
timeout: Duration,
|
||||||
|
retry_interval: Duration,
|
||||||
|
) -> Result<Connection<TcpStream>, Box<dyn Error>> {
|
||||||
|
let start = tokio::time::Instant::now();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if tokio::time::Instant::now() - start > timeout {
|
||||||
|
return Err("RCON connection timeout".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match connect_rcon(addr, password).await {
|
||||||
|
Ok(conn) => return Ok(conn),
|
||||||
|
Err(_) => {
|
||||||
|
tokio::time::sleep(retry_interval).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user