Add RCON server management support, adds Windows support

This commit is contained in:
timvisee 2021-11-09 01:12:46 +01:00
parent ba2100f015
commit d109b2bf32
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
14 changed files with 774 additions and 35 deletions

499
Cargo.lock generated
View File

@ -23,6 +23,123 @@ version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
[[package]]
name = "async-channel"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6"
dependencies = [
"async-channel",
"async-executor",
"async-io",
"async-mutex",
"blocking",
"futures-lite",
"num_cpus",
"once_cell",
]
[[package]]
name = "async-io"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
dependencies = [
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
"socket2",
"waker-fn",
"winapi",
]
[[package]]
name = "async-lock"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b"
dependencies = [
"event-listener",
]
[[package]]
name = "async-mutex"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
dependencies = [
"event-listener",
]
[[package]]
name = "async-std"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952"
dependencies = [
"async-channel",
"async-global-executor",
"async-io",
"async-lock",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"num_cpus",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
[[package]]
name = "atomic-waker"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atty"
version = "0.2.14"
@ -52,6 +169,26 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blocking"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
dependencies = [
"async-channel",
"async-task",
"atomic-waker",
"fastrand",
"futures-lite",
"once_cell",
]
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -64,6 +201,18 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cache-padded"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
[[package]]
name = "cc"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -121,6 +270,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
"cache-padded",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -130,6 +288,26 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "darling"
version = "0.12.4"
@ -215,6 +393,35 @@ dependencies = [
"termcolor",
]
[[package]]
name = "err-derive"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc7f65832b62ed38939f98966824eb6294911c3629b0e9a262bfb80836d9686"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"rustversion",
"syn",
"synstructure",
]
[[package]]
name = "event-listener"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "fastrand"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e"
dependencies = [
"instant",
]
[[package]]
name = "flate2"
version = "1.0.22"
@ -275,6 +482,21 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
[[package]]
name = "futures-lite"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-sink"
version = "0.3.17"
@ -301,6 +523,30 @@ dependencies = [
"pin-utils",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gloo-timers"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
@ -350,12 +596,39 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "js-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -377,6 +650,8 @@ dependencies = [
"log",
"minecraft-protocol",
"pretty_env_logger",
"rand 0.8.4",
"rcon",
"serde",
"thiserror",
"tokio",
@ -402,6 +677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
"value-bag",
]
[[package]]
@ -510,6 +786,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "pin-project-lite"
version = "0.2.7"
@ -522,6 +804,25 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polling"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
@ -588,9 +889,9 @@ checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.7",
"libc",
"rand_chacha",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
@ -599,6 +900,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
@ -609,6 +922,16 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.3.1"
@ -624,6 +947,15 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
@ -633,6 +965,15 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
@ -686,6 +1027,17 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rcon"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465a6f903164a399084787547a026b83e7937bc576d8acdbd9e41ebf5de90a85"
dependencies = [
"async-std",
"bytes",
"err-derive",
]
[[package]]
name = "rdrand"
version = "0.4.0"
@ -712,6 +1064,12 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
@ -758,6 +1116,22 @@ dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "socket2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -775,6 +1149,18 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
@ -885,16 +1271,123 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
dependencies = [
"rand",
"rand 0.6.5",
"serde",
]
[[package]]
name = "value-bag"
version = "1.0.0-alpha.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f"
dependencies = [
"ctor",
"version_check",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "web-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
dependencies = [
"cc",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -18,6 +18,10 @@ exclude = [
]
edition = "2021"
[features]
default = ["rcon"]
rcon = ["rust_rcon", "rand"]
[dependencies]
anyhow = "1.0"
bytes = "1.1"
@ -34,3 +38,7 @@ serde = "1.0"
thiserror = "1.0"
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] }
toml = "0.5"
# Feature: rcon
rust_rcon = { package = "rcon", version = "0.5", optional = true }
rand = { version = "0.8", optional = true }

View File

@ -28,8 +28,9 @@ https://user-images.githubusercontent.com/856222/140804726-ba1a8e59-85d9-413b-82
## Requirements
- Linux, macOS (Windows not yet supported)
- Linux, macOS, Windows
- Minecraft Java Edition 1.6 or above
- On Windows: RCON (automatically managed by default)
Using a modded Minecraft server and client (such as Forge) should work fine.

7
build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
// Must enable rcon on Windows
#[cfg(all(windows, not(feature = "rcon")))]
{
println!("cargo:warning=lazymc: you must enable rcon feature on Windows");
}
}

View File

@ -41,6 +41,20 @@ motd_starting = "§2☻ Server is starting...\n§7⌛ Please wait..."
# Login (kick) message when server is starting.
login_starting = "Server is starting... §c♥§r\n\nThis may take some time.\n\nPlease try to reconnect in a minute."
[rcon]
# Enable sleeping server through RCON.
# Must be enabled on Windows.
enabled = true
# Server RCON port. Must differ from Minecraft server port.
port = 25575
# Server RCON password.
password = ""
# Randomize ingress server RCON password on each start.
randomize_password = true
[advanced]
# Automatically set IP and port in Minecraft server.properties file.
# Automatically update values in Minecraft server.properties file as required.
rewrite_server_properties = true

View File

@ -7,19 +7,111 @@ use crate::config::{self, Config};
use crate::mc::server_properties;
use crate::service;
/// RCON randomized password length.
#[cfg(feature = "rcon")]
const RCON_PASSWORD_LENGTH: usize = 32;
/// Start lazymc.
pub async fn invoke(matches: &ArgMatches) -> Result<(), ()> {
// Load config
let config = Arc::new(config::load(matches));
#[allow(unused_mut)]
let mut config = config::load(matches);
// Prepare RCON if enabled
#[cfg(feature = "rcon")]
prepare_rcon(&mut config);
// Rewrite server server.properties file
rewrite_server_properties(&config);
// Start server service
// TODO: start tokio runtime here?
let config = Arc::new(config);
service::server::service(config).await
}
/// Prepare RCON.
#[cfg(feature = "rcon")]
fn prepare_rcon(config: &mut Config) {
use crate::util::error::{quit_error_msg, ErrorHintsBuilder};
// On Windows, this must be enabled
if cfg!(windows) && !config.rcon.enabled {
quit_error_msg(
"RCON must be enabled on Windows",
ErrorHintsBuilder::default()
.add_info("change 'rcon.enabled' to 'true' in the config file".into())
.build()
.unwrap(),
);
}
// Skip if not enabled
if !config.rcon.enabled {
return;
}
// Must configure RCON password with no randomization
if config.server.address.port() == config.rcon.port {
quit_error_msg(
"RCON port cannot be the same as the server",
ErrorHintsBuilder::default()
.add_info("change 'rcon.port' in the config file".into())
.build()
.unwrap(),
);
}
// Must configure RCON password with no randomization
if config.rcon.password.trim().is_empty() && !config.rcon.randomize_password {
quit_error_msg(
"RCON password can't be empty, or enable randomization",
ErrorHintsBuilder::default()
.add_info("change 'rcon.randomize_password' to 'true' in the config file".into())
.add_info("or change 'rcon.password' in the config file".into())
.build()
.unwrap(),
);
}
// RCON password randomization
if config.rcon.randomize_password {
// Must enable server.properties rewrite
if !config.advanced.rewrite_server_properties {
quit_error_msg(
format!(
"You must enable {} rewrite to use RCON password randomization",
server_properties::FILE
),
ErrorHintsBuilder::default()
.add_info(
"change 'advanced.rewrite_server_properties' to 'true' in the config file"
.into(),
)
.build()
.unwrap(),
);
}
// Randomize password
config.rcon.password = generate_random_password();
}
}
/// Generate secure random password.
#[cfg(feature = "rcon")]
fn generate_random_password() -> String {
use rand::{distributions::Alphanumeric, Rng};
use std::iter;
let mut rng = rand::thread_rng();
iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(RCON_PASSWORD_LENGTH)
.collect()
}
/// Rewrite server server.properties file with correct internal IP and port.
fn rewrite_server_properties(config: &Config) {
// Rewrite must be enabled
@ -37,12 +129,23 @@ fn rewrite_server_properties(config: &Config) {
};
// Build list of changes
let changes = HashMap::from([
#[allow(unused_mut)]
let mut changes = HashMap::from([
("server-ip", config.server.address.ip().to_string()),
("server-port", config.server.address.port().to_string()),
("query.port", config.server.address.port().to_string()),
]);
// Add RCON configuration
#[cfg(feature = "rcon")]
if config.rcon.enabled {
changes.extend([
("rcon.port", config.rcon.port.to_string()),
("rcon.password", config.rcon.password.clone()),
("enable-rcon", "true".into()),
]);
}
// Rewrite file
server_properties::rewrite_dir(dir, changes)
}

View File

@ -69,6 +69,9 @@ pub struct Config {
/// Messages, shown to the user.
pub messages: Messages,
/// RCON configuration.
pub rcon: Rcon,
/// Advanced configuration.
pub advanced: Advanced,
}
@ -132,6 +135,22 @@ pub struct Messages {
pub login_starting: String,
}
/// RCON configuration.
#[derive(Debug, Deserialize)]
pub struct Rcon {
/// Enable sleeping server through RCON.
pub enabled: bool,
/// Server RCON port.
pub port: u16,
/// Server RCON password.
pub password: String,
/// Randomize ingress server RCON password on each start.
pub randomize_password: bool,
}
/// Advanced configuration.
#[derive(Debug, Deserialize)]
pub struct Advanced {

View File

@ -1 +1,3 @@
#[cfg(feature = "rcon")]
pub mod rcon;
pub mod server_properties;

29
src/mc/rcon.rs Normal file
View File

@ -0,0 +1,29 @@
use rust_rcon::{Connection, Error as RconError};
/// An RCON client.
pub struct Rcon {
con: Connection,
}
impl Rcon {
/// Connect to a host.
pub async fn connect(addr: &str, pass: &str) -> Result<Self, ()> {
// Start connection
let con = Connection::builder()
.enable_minecraft_quirks(true)
.connect(addr, pass)
.await
.map_err(|err| {
dbg!(err);
()
})?;
Ok(Self { con })
}
/// Send command over RCON.
pub async fn cmd(&mut self, cmd: &str) -> Result<String, RconError> {
debug!(target: "lazymc::rcon", "Sending RCON: {}", cmd);
self.con.cmd(cmd).await
}
}

View File

@ -77,7 +77,7 @@ pub fn rewrite_file(file: &Path, changes: HashMap<&str, String>) {
match fs::write(file, contents) {
Ok(_) => {
info!(target: "lazymc",
"Rewritten {} file with correct IP and port",
"Rewritten {} file with updated values",
FILE,
);
}

View File

@ -37,7 +37,9 @@ pub async fn monitor_server(config: Arc<Config>, state: Arc<ServerState>) {
// Sleep server when it's bedtime
if state.should_sleep(&config) {
info!(target: "lazymc::montior", "Server has been idle, sleeping...");
state.kill_server();
if !state.kill_server(&config).await {
warn!(target: "lazymc", "Failed to stop server");
}
}
// TODO: use interval instead, for a more reliable polling interval?

View File

@ -61,21 +61,32 @@ impl ServerState {
}
/// Kill any running server.
pub fn kill_server(&self) -> bool {
if let Some(pid) = *self.pid.lock().unwrap() {
debug!(target: "lazymc", "Sending kill signal to server");
crate::os::unix::kill_gracefully(pid);
#[allow(unused_variables)]
pub async fn kill_server(&self, config: &Config) -> bool {
// Ensure we have a running process
let has_process = self.pid.lock().unwrap().is_some();
if !has_process {
return false;
}
// TODO: should we set this?
self.set_online(false);
self.set_keep_online_until(None);
// Try to kill through RCON
#[cfg(feature = "rcon")]
if stop_server_rcon(config, &self).await {
// TODO: set stopping state elsewhere
self.stopping.store(true, Ordering::Relaxed);
return true;
}
// Try to kill through signal
#[cfg(unix)]
if stop_server_signal(&self) {
// TODO: set stopping state elsewhere
self.stopping.store(true, Ordering::Relaxed);
return true;
}
false
}
@ -240,22 +251,62 @@ pub async fn invoke_server_command(
Ok(())
}
/// Gracefully kill process.
fn kill_gracefully(pid: u32) {
#[cfg(unix)]
unsafe {
debug!(target: "lazymc", "Sending SIGTERM signal to {} to kill server", pid);
let result = libc::kill(pid as i32, libc::SIGTERM);
trace!(target: "lazymc", "SIGTERM result: {}", result);
/// Stop server through RCON.
#[cfg(feature = "rcon")]
async fn stop_server_rcon(config: &Config, server: &ServerState) -> bool {
use crate::mc::rcon::Rcon;
// TODO: send sigterm to childs as well?
// TODO: handle error if != 0
// RCON must be enabled
if !config.rcon.enabled {
return false;
}
// TODO: implement for Windows
#[cfg(not(unix))]
{
// TODO: implement this for Windows
unimplemented!();
// RCON address
let mut addr = config.server.address.clone();
addr.set_port(config.rcon.port);
let addr = addr.to_string();
// Create RCON client
let mut rcon = match Rcon::connect(&addr, &config.rcon.password).await {
Ok(rcon) => rcon,
Err(_) => {
error!(target: "lazymc", "failed to create RCON client to sleep server");
return false;
}
};
// Invoke save-all
if let Err(err) = rcon.cmd("save-all").await {
error!(target: "lazymc", "failed to invoke save-all through RCON, ignoring: {}", err);
}
// Invoke stop
if let Err(err) = rcon.cmd("stop").await {
error!(target: "lazymc", "failed to invoke stop through RCON: {}", err);
}
// TODO: should we set this?
server.set_online(false);
server.set_keep_online_until(None);
true
}
/// Stop server by sending SIGTERM signal.
///
/// Only works on Unix.
#[cfg(unix)]
fn stop_server_signal(server: &ServerState) -> bool {
if let Some(pid) = *server.pid.lock().unwrap() {
debug!(target: "lazymc", "Sending kill signal to server");
crate::os::kill_gracefully(pid);
// TODO: should we set this?
server.set_online(false);
server.set_keep_online_until(None);
return true;
}
false
}

View File

@ -39,7 +39,10 @@ pub async fn service(config: Arc<Config>) -> Result<(), ()> {
config.clone(),
server_state.clone(),
));
tokio::spawn(service::signal::service(server_state.clone()));
tokio::spawn(service::signal::service(
config.clone(),
server_state.clone(),
));
// Initiate server start
if config.server.wake_on_start {

View File

@ -1,12 +1,19 @@
use std::sync::Arc;
use crate::config::Config;
use crate::server::ServerState;
/// Signal handler task.
pub async fn service(server_state: Arc<ServerState>) {
pub async fn service(config: Arc<Config>, server_state: Arc<ServerState>) {
loop {
// Wait for SIGTERM/SIGINT signal
tokio::signal::ctrl_c().await.unwrap();
if !server_state.kill_server() {
// Attemp to kill server
let killed = !server_state.kill_server(&config).await;
// If we don't kill the server, quit this process
if !killed {
// TODO: gracefully kill itself instead
std::process::exit(1)
}