15 Commits

Author SHA1 Message Date
timvisee
0ba46caf5c Bump version to 0.2.4 2021-11-24 14:05:27 +01:00
timvisee
6b23490919 Resolve clippy warnings 2021-11-24 14:02:08 +01:00
timvisee
9e08ed6cda Update dependencies 2021-11-24 13:57:19 +01:00
timvisee
3271db1cb3 Resolve compile warning 2021-11-24 13:55:23 +01:00
timvisee
cf0e3ef15b Make server directory relative to configuration file directory 2021-11-24 13:53:38 +01:00
timvisee
ee21eb45fd Bump minimum supported Minecraft version to 1.7.2 2021-11-24 13:28:06 +01:00
timvisee
aebb5563e0 Only send status response favicon to client versions that support it 2021-11-24 13:27:42 +01:00
timvisee
20fb6ee715 Always include favicon in status response, fall back to default icon
See https://github.com/timvisee/lazymc/issues/11#issuecomment-977814539
2021-11-24 13:20:49 +01:00
timvisee
ea2dbc905c Add Minecraft default server icon, extracted from Minecraft 1.17.1 2021-11-24 13:02:41 +01:00
timvisee
eb66265670 Handle SIGTERM exit code as successful 2021-11-23 13:00:10 +01:00
timvisee
df101ce53b Fix typo in Cargo.toml key 2021-11-22 20:38:30 +01:00
timvisee
8f2ce9b4b8 Fix compilation error without lobby feature 2021-11-22 20:36:46 +01:00
timvisee
20902e6a94 Derive correct UUID for offline players in lobby logic (2/2) 2021-11-22 20:20:08 +01:00
timvisee
3e933f7566 Derive correct UUID for offline players in lobby logic 2021-11-22 20:14:29 +01:00
timvisee
46fa594065 Update features in README 2021-11-22 19:00:36 +01:00
18 changed files with 233 additions and 76 deletions

View File

@@ -1,5 +1,14 @@
# Changelog # Changelog
## 0.2.4 (2021-11-24)
- Fix status response issues with missing server icon, fall back to default icon
- Fix incorrect UUID for players in lobby logic
- Make server directory relative to configuration file path
- Assume SIGTERM exit code for server process to be successful on Unix
- Update features in README
- Update dependencies
## 0.2.3 (2021-11-22) ## 0.2.3 (2021-11-22)
- Add support for `PROXY` header to notify Minecraft server of real client IP - Add support for `PROXY` header to notify Minecraft server of real client IP

91
Cargo.lock generated
View File

@@ -175,6 +175,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "blocking" name = "blocking"
version = "1.1.0" version = "1.1.0"
@@ -298,9 +307,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.2.1" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
@@ -391,6 +400,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "doc-comment" name = "doc-comment"
version = "0.3.3" version = "0.3.3"
@@ -518,9 +536,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -533,9 +551,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@@ -543,15 +561,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@@ -560,9 +578,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
@@ -581,23 +599,22 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
dependencies = [ dependencies = [
"autocfg 1.0.1",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
@@ -609,6 +626,16 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.3" version = "0.2.3"
@@ -759,7 +786,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "lazymc" name = "lazymc"
version = "0.2.3" version = "0.2.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std", "async-std",
@@ -774,6 +801,7 @@ dependencies = [
"futures", "futures",
"libc", "libc",
"log", "log",
"md-5",
"minecraft-protocol", "minecraft-protocol",
"named-binary-tag", "named-binary-tag",
"notify", "notify",
@@ -815,6 +843,17 @@ dependencies = [
"value-bag", "value-bag",
] ]
[[package]]
name = "md-5"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
]
[[package]] [[package]]
name = "md5" name = "md5"
version = "0.6.1" version = "0.6.1"
@@ -1009,6 +1048,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "4.2.0" version = "4.2.0"
@@ -1559,6 +1604,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "typenum"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.6.0" version = "2.6.0"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "lazymc" name = "lazymc"
version = "0.2.3" version = "0.2.4"
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"] authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
license = "GPL-3.0" license = "GPL-3.0"
readme = "README.md" readme = "README.md"
@@ -28,7 +28,7 @@ rcon = ["rust_rcon", "async-std"]
# Lobby support # Lobby support
# Add lobby join method, keeps client in fake lobby world until server is ready. # Add lobby join method, keeps client in fake lobby world until server is ready.
lobby = ["named-binary-tag", "quartz_nbt", "uuid"] lobby = ["md-5", "named-binary-tag", "quartz_nbt", "uuid"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
@@ -57,9 +57,10 @@ version-compare = "0.1"
# Feature: rcon # Feature: rcon
rust_rcon = { package = "rcon", version = "0.5.2", optional = true } rust_rcon = { package = "rcon", version = "0.5.2", optional = true }
async-std = { version = "1.9.0", deafult-features = false, optional = true } async-std = { version = "1.9.0", default-features = false, optional = true }
# Feature: lobby # Feature: lobby
md-5 = { version = "0.9", optional = true }
named-binary-tag = { version = "0.6", optional = true } named-binary-tag = { version = "0.6", optional = true }
quartz_nbt = { version = "0.2", optional = true } quartz_nbt = { version = "0.2", optional = true }
uuid = { version = "0.7", optional = true, features = ["v3"] } uuid = { version = "0.7", optional = true, features = ["v3"] }

View File

@@ -35,7 +35,7 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
## Features ## Features
- Very efficient, lightweight & low-profile (~3KB RAM) - Very efficient, lightweight & low-profile (~3KB RAM)
- Supports Minecraft Java Edition 1.6+, supports modded (e.g. Forge, FTB) - Supports Minecraft Java Edition 1.7.2+, supports modded (e.g. Forge, FTB)
- Configure joining client occupation methods: - Configure joining client occupation methods:
- Hold: hold clients when server starts, relay when ready, without them noticing - Hold: hold clients when server starts, relay when ready, without them noticing
- Kick: kick clients when server starts, with a starting message - Kick: kick clients when server starts, with a starting message
@@ -44,7 +44,8 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
- Customizable MOTD and login messages - Customizable MOTD and login messages
- Automatically manages `server.properties` (host, port and RCON settings) - Automatically manages `server.properties` (host, port and RCON settings)
- Automatically block banned IPs from server within `lazymc` - Automatically block banned IPs from server within `lazymc`
- Graceful server sleep/shutdown through RCON (with `SIGTERM` fallback on Linux/Unix) - Graceful server sleep/shutdown through RCON or `SIGTERM`
- Real client IP on Minecraft server with `PROXY` header ([usage](./docs/proxy-ip.md))
- Restart server on crash - Restart server on crash
- Lockout mode - Lockout mode

View File

@@ -170,4 +170,4 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
[config] [config]
# lazymc version this configuration is for. # lazymc version this configuration is for.
# Don't change unless you know what you're doing. # Don't change unless you know what you're doing.
version = "0.2.3" version = "0.2.4"

BIN
res/unknown_server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use clap::ArgMatches; use clap::ArgMatches;
use crate::config::{self, Config}; use crate::config::{self, Config, Server as ConfigServer};
use crate::mc::server_properties; use crate::mc::server_properties;
use crate::proto; use crate::proto;
use crate::service; use crate::service;
@@ -120,7 +120,7 @@ fn rewrite_server_properties(config: &Config) {
} }
// Ensure server directory is set, it must exist // Ensure server directory is set, it must exist
let dir = match &config.server.directory { let dir = match ConfigServer::server_directory(config) {
Some(dir) => dir, Some(dir) => dir,
None => { None => {
warn!(target: "lazymc", "Not rewriting {} file, server directory not configured (server.directory)", server_properties::FILE); warn!(target: "lazymc", "Not rewriting {} file, server directory not configured (server.directory)", server_properties::FILE);

View File

@@ -1,7 +1,7 @@
use std::fs; use std::fs;
use std::io; use std::io;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use clap::ArgMatches; use clap::ArgMatches;
use serde::Deserialize; use serde::Deserialize;
@@ -63,6 +63,12 @@ pub fn load(matches: &ArgMatches) -> Config {
/// Configuration. /// Configuration.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Config { pub struct Config {
/// Configuration path if known.
///
/// Should be used as base directory for filesystem operations.
#[serde(skip)]
pub path: Option<PathBuf>,
/// Public configuration. /// Public configuration.
#[serde(default)] #[serde(default)]
pub public: Public, pub public: Public,
@@ -101,9 +107,9 @@ pub struct Config {
impl Config { impl Config {
/// Load configuration from file. /// Load configuration from file.
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> { pub fn load(path: PathBuf) -> Result<Self, io::Error> {
let data = fs::read(path)?; let data = fs::read(&path)?;
let config: Config = toml::from_slice(&data)?; let mut config: Config = toml::from_slice(&data)?;
// Show warning if config version is problematic // Show warning if config version is problematic
match &config.config.version { match &config.config.version {
@@ -118,6 +124,7 @@ impl Config {
Ok(true) => {} Ok(true) => {}
}, },
} }
config.path.replace(path);
Ok(config) Ok(config)
} }
@@ -152,8 +159,10 @@ impl Default for Public {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Server { pub struct Server {
/// Server directory. /// Server directory.
///
/// Private because you should use `Server::server_directory()` instead.
#[serde(default = "option_pathbuf_dot")] #[serde(default = "option_pathbuf_dot")]
pub directory: Option<PathBuf>, directory: Option<PathBuf>,
/// Start command. /// Start command.
pub command: String, pub command: String,
@@ -194,6 +203,19 @@ pub struct Server {
pub send_proxy_v2: bool, pub send_proxy_v2: bool,
} }
impl Server {
/// Get the server directory.
///
/// This does not check whether it exists.
pub fn server_directory(config: &Config) -> Option<PathBuf> {
// Get directory, relative to config directory if known
match config.path.as_ref().and_then(|p| p.parent()) {
Some(config_dir) => Some(config_dir.join(config.server.directory.as_ref()?)),
None => config.server.directory.clone(),
}
}
}
/// Time configuration. /// Time configuration.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(default)] #[serde(default)]

View File

@@ -20,10 +20,9 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::select; use tokio::select;
use tokio::time; use tokio::time;
use uuid::Uuid;
use crate::config::*; use crate::config::*;
use crate::mc; use crate::mc::{self, uuid};
use crate::net; use crate::net;
use crate::proto; use crate::proto;
use crate::proto::client::{Client, ClientInfo, ClientState}; use crate::proto::client::{Client, ClientInfo, ClientState};
@@ -198,10 +197,7 @@ async fn respond_login_success(
) -> Result<(), ()> { ) -> Result<(), ()> {
packet::write_packet( packet::write_packet(
LoginSuccess { LoginSuccess {
uuid: Uuid::new_v3( uuid: uuid::offline_player_uuid(&login_start.name),
&Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"),
login_start.name.as_bytes(),
),
username: login_start.name.clone(), username: login_start.name.clone(),
}, },
client, client,

26
src/mc/favicon.rs Normal file
View File

@@ -0,0 +1,26 @@
use crate::proto::client::ClientInfo;
/// Protocol version since when favicons are supported.
const FAVICON_PROTOCOL_VERSION: i32 = 4;
/// Get default server status favicon.
pub fn default_favicon() -> String {
encode_favicon(include_bytes!("../../res/unknown_server_optimized.png"))
}
/// Encode favicon bytes to a string Minecraft can read.
///
/// This assumes the favicon data to be a valid PNG image.
pub fn encode_favicon(data: &[u8]) -> String {
format!("{}{}", "data:image/png;base64,", base64::encode(data))
}
/// Check whether the status response favicon is supported based on the given client info.
///
/// Defaults to `true` if unsure.
pub fn supports_favicon(client_info: &ClientInfo) -> bool {
client_info
.protocol_version
.map(|p| p >= FAVICON_PROTOCOL_VERSION)
.unwrap_or(true)
}

View File

@@ -1,7 +1,10 @@
pub mod ban; pub mod ban;
pub mod favicon;
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
pub mod rcon; pub mod rcon;
pub mod server_properties; pub mod server_properties;
#[cfg(feature = "lobby")]
pub mod uuid;
/// Minecraft ticks per second. /// Minecraft ticks per second.
#[allow(unused)] #[allow(unused)]

View File

@@ -11,39 +11,39 @@ const EOL: &str = "\r\n";
/// Try to rewrite changes in server.properties file in dir. /// Try to rewrite changes in server.properties file in dir.
/// ///
/// Prints an error and stops on failure. /// Prints an error and stops on failure.
pub fn rewrite_dir(dir: &Path, changes: HashMap<&str, String>) { pub fn rewrite_dir<P: AsRef<Path>>(dir: P, changes: HashMap<&str, String>) {
if changes.is_empty() { if changes.is_empty() {
return; return;
} }
// Ensure directory exists // Ensure directory exists
if !dir.is_dir() { if !dir.as_ref().is_dir() {
warn!(target: "lazymc", warn!(target: "lazymc",
"Not rewriting {} file, configured server directory doesn't exist: {}", "Not rewriting {} file, configured server directory doesn't exist: {}",
FILE, FILE,
dir.to_str().unwrap_or("?") dir.as_ref().to_str().unwrap_or("?")
); );
return; return;
} }
// Rewrite file // Rewrite file
rewrite_file(&dir.join(FILE), changes) rewrite_file(dir.as_ref().join(FILE), changes)
} }
/// Try to rewrite changes in server.properties file. /// Try to rewrite changes in server.properties file.
/// ///
/// Prints an error and stops on failure. /// Prints an error and stops on failure.
pub fn rewrite_file(file: &Path, changes: HashMap<&str, String>) { pub fn rewrite_file<P: AsRef<Path>>(file: P, changes: HashMap<&str, String>) {
if changes.is_empty() { if changes.is_empty() {
return; return;
} }
// File must exist // File must exist
if !file.is_file() { if !file.as_ref().is_file() {
warn!(target: "lazymc", warn!(target: "lazymc",
"Not writing {} file, not found at: {}", "Not writing {} file, not found at: {}",
FILE, FILE,
file.to_str().unwrap_or("?"), file.as_ref().to_str().unwrap_or("?"),
); );
return; return;
} }

33
src/mc/uuid.rs Normal file
View File

@@ -0,0 +1,33 @@
use md5::{Digest, Md5};
use uuid::Uuid;
/// Offline player namespace.
const OFFLINE_PLAYER_NAMESPACE: &str = "OfflinePlayer:";
/// Get UUID for given player username.
pub fn player_uuid(username: &str) -> Uuid {
java_name_uuid_from_bytes(username.as_bytes())
}
/// Get UUID for given offline player username.
pub fn offline_player_uuid(username: &str) -> Uuid {
player_uuid(&format!("{}{}", OFFLINE_PLAYER_NAMESPACE, username))
}
/// Java's `UUID.nameUUIDFromBytes`
///
/// Static factory to retrieve a type 3 (name based) `Uuid` based on the specified byte array.
///
/// Ported from: https://git.io/J1b6A
fn java_name_uuid_from_bytes(data: &[u8]) -> Uuid {
let mut hasher = Md5::new();
hasher.update(data);
let mut md5: [u8; 16] = hasher.finalize().into();
md5[6] &= 0x0f; // clear version
md5[6] |= 0x30; // set to version 3
md5[8] &= 0x3f; // clear variant
md5[8] |= 0x80; // set to IETF variant
Uuid::from_bytes(md5)
}

View File

@@ -100,6 +100,7 @@ pub enum ProxyHeader {
None, None,
/// Header for locally initiated connection. /// Header for locally initiated connection.
#[allow(unused)]
Local, Local,
/// Header for proxied connection. /// Header for proxied connection.

View File

@@ -12,7 +12,7 @@ use tokio::sync::Semaphore;
use tokio::sync::{Mutex, RwLock, RwLockReadGuard}; use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
use tokio::time; use tokio::time;
use crate::config::Config; use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban::{BannedIp, BannedIps}; use crate::mc::ban::{BannedIp, BannedIps};
use crate::os; use crate::os;
@@ -27,6 +27,10 @@ const SERVER_QUIT_COOLDOWN: Duration = Duration::from_millis(2500);
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
const RCON_COOLDOWN: Duration = Duration::from_secs(15); const RCON_COOLDOWN: Duration = Duration::from_secs(15);
/// Exit code when SIGTERM is received on Unix.
#[cfg(unix)]
const UNIX_EXIT_SIGTERM: i32 = 130;
/// Shared server state. /// Shared server state.
#[derive(Debug)] #[derive(Debug)]
pub struct Server { pub struct Server {
@@ -417,7 +421,7 @@ pub async fn invoke_server_cmd(
cmd.kill_on_drop(true); cmd.kill_on_drop(true);
// Set working directory // Set working directory
if let Some(ref dir) = config.server.directory { if let Some(ref dir) = ConfigServer::server_directory(&config) {
cmd.current_dir(dir); cmd.current_dir(dir);
} }
@@ -443,6 +447,11 @@ pub async fn invoke_server_cmd(
debug!(target: "lazymc", "Server process stopped successfully ({})", status); debug!(target: "lazymc", "Server process stopped successfully ({})", status);
false false
} }
#[cfg(unix)]
Ok(status) if status.code() == Some(UNIX_EXIT_SIGTERM) => {
debug!(target: "lazymc", "Server process stopped successfully by SIGTERM ({})", status);
false
}
Ok(status) => { Ok(status) => {
warn!(target: "lazymc", "Server process stopped with error code ({})", status); warn!(target: "lazymc", "Server process stopped with error code ({})", status);
state.state() == State::Started state.state() == State::Started
@@ -499,7 +508,7 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
} }
// Create RCON client // Create RCON client
let mut rcon = match Rcon::connect_config(&config).await { let mut rcon = match Rcon::connect_config(config).await {
Ok(rcon) => rcon, Ok(rcon) => rcon,
Err(err) => { Err(err) => {
error!(target: "lazymc", "Failed to RCON server to sleep: {}", err); error!(target: "lazymc", "Failed to RCON server to sleep: {}", err);

View File

@@ -6,7 +6,7 @@ use std::time::Duration;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use crate::config::Config; use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban; use crate::mc::ban;
use crate::server::Server; use crate::server::Server;
@@ -23,7 +23,7 @@ pub fn service(config: Arc<Config>, server: Arc<Server>) {
} }
// Ensure server directory is set, it must exist // Ensure server directory is set, it must exist
let dir = match &config.server.directory { let dir = match ConfigServer::server_directory(&config) {
Some(dir) => dir, Some(dir) => dir,
None => { None => {
warn!(target: "lazymc", "Not blocking banned IPs, server directory not configured, unable to find {} file", ban::FILE); warn!(target: "lazymc", "Not blocking banned IPs, server directory not configured, unable to find {} file", ban::FILE);

View File

@@ -12,8 +12,9 @@ use tokio::fs;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use crate::config::*; use crate::config::{Config, Server as ConfigServer};
use crate::join; use crate::join;
use crate::mc::favicon;
use crate::proto::action; use crate::proto::action;
use crate::proto::client::{Client, ClientInfo, ClientState}; use crate::proto::client::{Client, ClientInfo, ClientState};
use crate::proto::packet::{self, RawPacket}; use crate::proto::packet::{self, RawPacket};
@@ -98,7 +99,7 @@ pub async fn serve(
// Hijack server status packet // Hijack server status packet
if client_state == ClientState::Status && packet.id == packets::status::SERVER_STATUS { if client_state == ClientState::Status && packet.id == packets::status::SERVER_STATUS {
let server_status = server_status(&config, &server).await; let server_status = server_status(&client_info, &config, &server).await;
let packet = StatusResponse { server_status }; let packet = StatusResponse { server_status };
let mut data = Vec::new(); let mut data = Vec::new();
@@ -196,7 +197,7 @@ pub async fn serve(
} }
/// Build server status object to respond to client with. /// Build server status object to respond to client with.
async fn server_status(config: &Config, server: &Server) -> ServerStatus { async fn server_status(client_info: &ClientInfo, config: &Config, server: &Server) -> ServerStatus {
let status = server.status().await; let status = server.status().await;
let server_state = server.state(); let server_state = server.state();
@@ -230,12 +231,16 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
} }
}; };
// Get server favicon // Extract favicon from real server status, load from disk, or use default
let favicon = if config.motd.from_server && status.is_some() { let mut favicon = None;
status.as_ref().unwrap().favicon.clone() if favicon::supports_favicon(client_info) {
} else { if config.motd.from_server && status.is_some() {
favicon(&config).await favicon = status.as_ref().unwrap().favicon.clone()
}; }
if favicon.is_none() {
favicon = Some(server_favicon(config).await);
}
}
// Build status resposne // Build status resposne
ServerStatus { ServerStatus {
@@ -251,31 +256,31 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
} }
/// Get server status favicon. /// Get server status favicon.
async fn favicon(config: &Config) -> Option<String> { ///
/// This always returns a favicon, returning the default one if none is set.
async fn server_favicon(config: &Config) -> String {
// Get server dir // Get server dir
let dir = match config.server.directory.as_ref() { let dir = match ConfigServer::server_directory(config) {
Some(dir) => dir, Some(dir) => dir,
None => return None, None => return favicon::default_favicon(),
}; };
// Server icon file, ensure it exists // Server icon file, ensure it exists
let path = dir.join(SERVER_ICON_FILE); let path = dir.join(SERVER_ICON_FILE);
if !path.is_file() { if !path.is_file() {
return None; return favicon::default_favicon();
} }
// Read icon data // Read icon data
let data = fs::read(path) let data = match fs::read(path).await.map_err(|err| {
.await error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
.map_err(|err| { }) {
error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err); Ok(data) => data,
}) Err(err) => {
.ok()?; error!(target: "lazymc::status", "Failed to load server icon from disk, using default: {:?}", err);
return favicon::default_favicon();
}
};
// Format and return favicon favicon::encode_favicon(&data)
Some(format!(
"{}{}",
"data:image/png;base64,",
base64::encode(data)
))
} }