14 Commits

Author SHA1 Message Date
timvisee
2af20945cc Bump version to 0.1.1 2021-11-14 12:54:34 +01:00
timvisee
ad638b5b3e Update dependencies 2021-11-14 12:54:09 +01:00
timvisee
69c2d580d5 Only disable prevent-proxy-connections if server is on non-loopback IP 2021-11-14 12:18:56 +01:00
timvisee
c6cd08c993 Add setup testing recommendation to README, note repository watch button 2021-11-14 12:14:10 +01:00
timvisee
69812f5b55 Make server sleeping errors a bit more descriptive 2021-11-14 12:09:26 +01:00
timvisee
f172587fd5 Add server quit cooldown period 2021-11-14 12:00:03 +01:00
timvisee
e6021502d9 Add TODO file, minor fixes 2021-11-12 13:12:17 +01:00
timvisee
157905f140 Update minecraft-protocol crate repository 2021-11-12 12:30:19 +01:00
timvisee
72d132ae8b Rewrite enable-status and prevent-proxy-connections in server.properties 2021-11-12 12:24:41 +01:00
timvisee
7a00c2df9e List crbanman/papermc-lazymc Docker image as 3rd-party implementation 2021-11-12 11:46:21 +01:00
Tim Visée
fe3bf63401 Update demo video in README 2021-11-11 23:34:54 +01:00
timvisee
e9e58a766b Improve Windows instructions 2021-11-11 15:15:47 +01:00
timvisee
f9be5c5a0f Update README, improve usage/compile instructions, add Windows guide 2021-11-11 15:05:05 +01:00
Tim Visée
7c7595dcd3 Update demo video in README 2021-11-11 14:38:03 +01:00
11 changed files with 194 additions and 34 deletions

View File

@@ -1,5 +1,18 @@
# Changelog
## 0.1.1 (2021-11-14)
- Make server sleeping errors more descriptive
- Add server quit cooldown period, intended to prevent RCON errors due to RCON
server thread something quitting after main server
- Rewrite `enable-status = true` in `server.properties`
- Rewrite `prevent-proxy-connections = false` in `server.properties` if
Minecraft server has non-loopback address (other public IP)
- Add compile from source instructions to README
- Add Windows instructions to README
- Update dependencies
- Various fixes and improvements
## 0.1.0 (2021-11-11)
- Initial release

10
Cargo.lock generated
View File

@@ -614,7 +614,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazymc"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"anyhow",
"bytes",
@@ -668,7 +668,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "minecraft-protocol"
version = "0.1.0"
source = "git+https://github.com/timvisee/minecraft-protocol?rev=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
dependencies = [
"byteorder",
"minecraft-protocol-derive",
@@ -681,7 +681,7 @@ dependencies = [
[[package]]
name = "minecraft-protocol-derive"
version = "0.0.0"
source = "git+https://github.com/timvisee/minecraft-protocol?rev=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=31041b8#31041b8fe2bc7e512d12476b958c1fe9e9077394"
dependencies = [
"proc-macro2",
"quote",
@@ -1077,9 +1077,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.69"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8"
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
dependencies = [
"itoa",
"ryu",

View File

@@ -1,6 +1,6 @@
[package]
name = "lazymc"
version = "0.1.0"
version = "0.1.1"
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
license = "GPL-3.0"
readme = "README.md"
@@ -31,7 +31,7 @@ derive_builder = "0.10"
dotenv = "0.15"
futures = { version = "0.3", default-features = false }
log = "0.4"
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "31041b8" }
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "31041b8" }
pretty_env_logger = "0.4"
rand = "0.8"
serde = "1.0"

View File

@@ -18,9 +18,8 @@ lazymc functions as proxy between clients and the server. It handles all
incoming status connections until the server is started and then transparently
relays/proxies the rest. All without them noticing.
_Note: this is a prototype and may be incomplete._
https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81cc-5a7ab8b8e86b.mp4
https://user-images.githubusercontent.com/856222/140804726-ba1a8e59-85d9-413b-8229-03be84b55d51.mp4
<details><summary>Click to see screenshots</summary>
<p>
@@ -55,28 +54,27 @@ won't be able to set this up._
## Usage
_Note: these instructions are for Linux & macOS, for Windows look
[here](./docs/usage-windows.md)._
Make sure you meet all [requirements](#requirements).
_Note: Installation options are limited at this moment. Ready-to-go binaries
will be published later. For now we compile and install from source._
Download the appropriate binary for your system from the [latest
release][latest-release] page.
To compile and install you need Rust, install it through `rustup`: https://rustup.rs/
When Rust is installed, compile and install `lazymc` from this git repository:
Place the binary in your Minecraft server directory, rename it if you like.
Open a terminal, go to the directory, and make sure you can invoke it:
```bash
# Compile and install lazymc from source
cargo install -f --git https://github.com/timvisee/lazymc
# Ensure lazymc works
lazymc --help
chmod a+x ./lazymc
./lazymc --help
```
When `lazymc` is available, change into your server directory. Then set up the
[configuration](./res/lazymc.toml) and start it up:
When `lazymc` is set-up, change into your server directory if you haven't
already. Then set up the [configuration](./res/lazymc.toml) and start it up:
```bash
# Change into your server directory
# Change into your server directory (if you haven't already)
cd server
# Generate lazymc configuration
@@ -90,9 +88,62 @@ nano lazymc.toml
lazymc start
```
Everything should now be running. Connect with your Minecraft client to wake
Before you use this in production, please ensure starting and stopping the
server works as expected by connecting to it once. Watch `lazymc`s output while
it starts and stops. If stopping results in errors, fix this first to prevent
corrupting world/user data.
Follow this repository with the _Watch_ button on the top right to be notified of new releases.
Everything should now be ready to go! Connect with your Minecraft client to wake
your server up!
_Note: If a binary for your system isn't provided, please [compile from
source](#compile-from-source)._
_Note: Installation options are limited at this moment. More will be added
later._
[latest-release]: https://github.com/timvisee/lazymc/releases/latest
## Compile from source
Make sure you meet all [requirements](#requirements).
To compile from source you need Rust, install it through `rustup`: https://rustup.rs/
When Rust is installed, compile and install `lazymc` from this git repository
directly:
```bash
# Compile and install lazymc from source
cargo install -f --git https://github.com/timvisee/lazymc
# Ensure lazymc works
lazymc --help
```
Or clone the repository and build it yourself:
```bash
# Clone repository
git clone https://github.com/timvisee/lazymc
cd lazymc
# Compile
cargo build --release
# Run lazymc
./target/release/lazymc --help
```
## Third-party usage & implementations
A list of third-party implementations, projects using `lazymc`, that you might
find useful:
- Docker: [crbanman/papermc-lazymc](https://hub.docker.com/r/crbanman/papermc-lazymc) _(PaperMC with lazymc in Docker)_
## License
This project is released under the GNU GPL-3.0 license.

22
TODO.md Normal file
View File

@@ -0,0 +1,22 @@
# TODO
- Better organize code
- Resolve TODOs in code
- Don't drop errors, handle everywhere where needed (some were dropped while
prototyping to speed up development)
## Nice to have
- Use server whitelist/blacklist
- Console error if server already started on port, not through `lazymc`
- Kick with message if proxy-to-server connection fails for new client.
- Test configuration on start (server dir exists, command not empty)
- Also quit `lazymc` after CTRL+C signal, after server has stopped
- Dynamically increase/decrease server polling interval based on server state
- Server polling through query (`enable-query` in `server.properties`, uses GameSpy4 protocol)
## Experiment
- Lobby method: let players connect with an emulated empty server (like 2b2t's
queue), redirect them when the server started.
- `io_uring` on Linux for efficient proxying (see `tokio-uring`)

48
docs/usage-windows.md Normal file
View File

@@ -0,0 +1,48 @@
## Usage on Windows
Make sure you meet all [requirements](../README.md#requirements).
Download the `lazymc-*-windows.exe` Windows executable for your system from the
[latest release][latest-release] page.
Place the binary in your Minecraft server directory, and rename it to
`lazymc.exe`.
Open a terminal, go to the server directory, and make sure you can execute it:
```bash
.\lazymc --help
```
When `lazymc` is ready, set up the [configuration](./res/lazymc.toml) and start
it up:
```bash
# In your Minecraft server directory:
# Generate lazymc configuration
.\lazymc config generate
# Edit configuration
# Set the correct server address, directory and start command
notepad lazymc.toml
# Start lazymc
.\lazymc start
```
Before you use this in production, please ensure starting and stopping the
server works as expected by connecting to it once. Watch `lazymc`s output while
it starts and stops. If stopping results in errors, fix this first to prevent
corrupting world/user data.
Follow this repository with the _Watch_ button on the top right to be notified of new releases.
Everything should now be ready to go! Connect with your Minecraft client to wake
your server up!
_Note: if you put `lazymc` in `PATH`, or if you
[install](../README.md#compile-from-source) it through Cargo, you can invoke
`lazymc` everywhere directly without the `.\` prefix.
[latest-release]: https://github.com/timvisee/lazymc/releases/latest

View File

@@ -132,9 +132,15 @@ fn rewrite_server_properties(config: &Config) {
let mut changes = HashMap::from([
("server-ip", config.server.address.ip().to_string()),
("server-port", config.server.address.port().to_string()),
("enable-status", "true".into()),
("query.port", config.server.address.port().to_string()),
]);
// If connecting to server over non-loopback address, disable proxy blocking
if !config.server.address.ip().is_loopback() {
changes.extend([("prevent-proxy-connections", "false".into())]);
}
// Add RCON configuration
#[cfg(feature = "rcon")]
if config.rcon.enabled {

View File

@@ -55,9 +55,7 @@ pub async fn monitor_server(config: Arc<Config>, server: Arc<Server>) {
// Sleep server when it's bedtime
if server.should_sleep(&config) {
info!(target: "lazymc::montior", "Server has been idle, sleeping...");
if !server.stop(&config).await {
warn!(target: "lazymc", "Failed to stop server");
}
server.stop(&config).await;
}
// Check whether we should force kill server

View File

@@ -20,7 +20,7 @@ pub unsafe fn kill_gracefully(pid: u32) -> bool {
let result = libc::kill(pid as i32, libc::SIGTERM);
if result != 0 {
trace!(target: "lazymc", "SIGTERM failed: {}", result);
warn!(target: "lazymc", "Sending SIGTERM signal to server failed: {}", result);
}
result == 0

View File

@@ -5,10 +5,15 @@ use std::time::{Duration, Instant};
use futures::FutureExt;
use minecraft_protocol::data::server_status::ServerStatus;
use tokio::process::Command;
use tokio::time;
use crate::config::Config;
use crate::os;
/// Server cooldown after the process quit.
/// Used to give it some more time to quit forgotten threads, such as for RCON.
const SERVER_QUIT_COOLDOWN: Duration = Duration::from_millis(2500);
/// Server state.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum State {
@@ -205,6 +210,7 @@ impl Server {
// We must have a running process
let has_process = self.pid.lock().unwrap().is_some();
if !has_process {
debug!(target: "lazymc", "Tried to stop server, while no PID is known");
return false;
}
@@ -220,6 +226,7 @@ impl Server {
return true;
}
warn!(target: "lazymc", "Failed to stop server, no more suitable stopping method to use");
false
}
@@ -364,8 +371,13 @@ pub async fn invoke_server_cmd(
}
};
// Set state to stopped, update server PID
// Forget server PID
state.pid.lock().unwrap().take();
// Give server a little more time to quit forgotten threads
time::sleep(SERVER_QUIT_COOLDOWN).await;
// Set server state to stopped
state.update_state(State::Stopped, &config);
// Restart on crash
@@ -384,6 +396,7 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
// RCON must be enabled
if !config.rcon.enabled {
trace!(target: "lazymc", "Not using RCON to stop server, disabled in config");
return false;
}
@@ -404,6 +417,7 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
// Invoke stop
if let Err(err) = rcon.cmd("stop").await {
error!(target: "lazymc", "Failed to invoke stop through RCON: {}", err);
return false;
}
// Set server to stopping state
@@ -421,13 +435,21 @@ fn stop_server_signal(config: &Config, server: &Server) -> bool {
// Grab PID
let pid = match *server.pid.lock().unwrap() {
Some(pid) => pid,
None => return false,
None => {
debug!(target: "lazymc", "Could not send stop signal to server process, PID unknown");
return false;
}
};
// Set stopping state, send kill signal
// TODO: revert state on failure
server.update_state(State::Stopping, config);
crate::os::kill_gracefully(pid);
// Send kill signal
if !crate::os::kill_gracefully(pid) {
error!(target: "lazymc", "Failed to send stop signal to server process");
return false;
}
// Update from starting/started to stopping
server.update_state_from(Some(State::Starting), State::Stopping, config);
server.update_state_from(Some(State::Started), State::Stopping, config);
true
}

View File

@@ -177,7 +177,7 @@ pub async fn hold<'a>(
// If hold timeout is reached, kick client
if since.elapsed().as_secs() >= timeout {
warn!(target: "lazymc", "Holding client reached timeout of {}s, disconnecting", timeout);
warn!(target: "lazymc", "Held client reached timeout of {}s, disconnecting", timeout);
kick(&config.messages.login_starting, &mut inbound.split().1).await?;
return Ok(());
}