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

View File

@@ -1,6 +1,6 @@
[package]
name = "lazymc"
version = "0.2.3"
version = "0.2.4"
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
license = "GPL-3.0"
readme = "README.md"
@@ -28,7 +28,7 @@ rcon = ["rust_rcon", "async-std"]
# Lobby support
# 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]
anyhow = "1.0"
@@ -57,9 +57,10 @@ version-compare = "0.1"
# Feature: rcon
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
md-5 = { version = "0.9", optional = true }
named-binary-tag = { version = "0.6", optional = true }
quartz_nbt = { version = "0.2", optional = true }
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
- 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:
- Hold: hold clients when server starts, relay when ready, without them noticing
- 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
- Automatically manages `server.properties` (host, port and RCON settings)
- 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
- Lockout mode

View File

@@ -170,4 +170,4 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
[config]
# lazymc version this configuration is for.
# 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 crate::config::{self, Config};
use crate::config::{self, Config, Server as ConfigServer};
use crate::mc::server_properties;
use crate::proto;
use crate::service;
@@ -120,7 +120,7 @@ fn rewrite_server_properties(config: &Config) {
}
// Ensure server directory is set, it must exist
let dir = match &config.server.directory {
let dir = match ConfigServer::server_directory(config) {
Some(dir) => dir,
None => {
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::io;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use clap::ArgMatches;
use serde::Deserialize;
@@ -63,6 +63,12 @@ pub fn load(matches: &ArgMatches) -> Config {
/// Configuration.
#[derive(Debug, Deserialize)]
pub struct Config {
/// Configuration path if known.
///
/// Should be used as base directory for filesystem operations.
#[serde(skip)]
pub path: Option<PathBuf>,
/// Public configuration.
#[serde(default)]
pub public: Public,
@@ -101,9 +107,9 @@ pub struct Config {
impl Config {
/// Load configuration from file.
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let data = fs::read(path)?;
let config: Config = toml::from_slice(&data)?;
pub fn load(path: PathBuf) -> Result<Self, io::Error> {
let data = fs::read(&path)?;
let mut config: Config = toml::from_slice(&data)?;
// Show warning if config version is problematic
match &config.config.version {
@@ -118,6 +124,7 @@ impl Config {
Ok(true) => {}
},
}
config.path.replace(path);
Ok(config)
}
@@ -152,8 +159,10 @@ impl Default for Public {
#[derive(Debug, Deserialize)]
pub struct Server {
/// Server directory.
///
/// Private because you should use `Server::server_directory()` instead.
#[serde(default = "option_pathbuf_dot")]
pub directory: Option<PathBuf>,
directory: Option<PathBuf>,
/// Start command.
pub command: String,
@@ -194,6 +203,19 @@ pub struct Server {
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.
#[derive(Debug, Deserialize)]
#[serde(default)]

View File

@@ -20,10 +20,9 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream;
use tokio::select;
use tokio::time;
use uuid::Uuid;
use crate::config::*;
use crate::mc;
use crate::mc::{self, uuid};
use crate::net;
use crate::proto;
use crate::proto::client::{Client, ClientInfo, ClientState};
@@ -198,10 +197,7 @@ async fn respond_login_success(
) -> Result<(), ()> {
packet::write_packet(
LoginSuccess {
uuid: Uuid::new_v3(
&Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"),
login_start.name.as_bytes(),
),
uuid: uuid::offline_player_uuid(&login_start.name),
username: login_start.name.clone(),
},
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 favicon;
#[cfg(feature = "rcon")]
pub mod rcon;
pub mod server_properties;
#[cfg(feature = "lobby")]
pub mod uuid;
/// Minecraft ticks per second.
#[allow(unused)]

View File

@@ -11,39 +11,39 @@ const EOL: &str = "\r\n";
/// Try to rewrite changes in server.properties file in dir.
///
/// 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() {
return;
}
// Ensure directory exists
if !dir.is_dir() {
if !dir.as_ref().is_dir() {
warn!(target: "lazymc",
"Not rewriting {} file, configured server directory doesn't exist: {}",
FILE,
dir.to_str().unwrap_or("?")
dir.as_ref().to_str().unwrap_or("?")
);
return;
}
// Rewrite file
rewrite_file(&dir.join(FILE), changes)
rewrite_file(dir.as_ref().join(FILE), changes)
}
/// Try to rewrite changes in server.properties file.
///
/// 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() {
return;
}
// File must exist
if !file.is_file() {
if !file.as_ref().is_file() {
warn!(target: "lazymc",
"Not writing {} file, not found at: {}",
FILE,
file.to_str().unwrap_or("?"),
file.as_ref().to_str().unwrap_or("?"),
);
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,
/// Header for locally initiated connection.
#[allow(unused)]
Local,
/// Header for proxied connection.

View File

@@ -12,7 +12,7 @@ use tokio::sync::Semaphore;
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
use tokio::time;
use crate::config::Config;
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban::{BannedIp, BannedIps};
use crate::os;
@@ -27,6 +27,10 @@ const SERVER_QUIT_COOLDOWN: Duration = Duration::from_millis(2500);
#[cfg(feature = "rcon")]
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.
#[derive(Debug)]
pub struct Server {
@@ -417,7 +421,7 @@ pub async fn invoke_server_cmd(
cmd.kill_on_drop(true);
// Set working directory
if let Some(ref dir) = config.server.directory {
if let Some(ref dir) = ConfigServer::server_directory(&config) {
cmd.current_dir(dir);
}
@@ -443,6 +447,11 @@ pub async fn invoke_server_cmd(
debug!(target: "lazymc", "Server process stopped successfully ({})", status);
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) => {
warn!(target: "lazymc", "Server process stopped with error code ({})", status);
state.state() == State::Started
@@ -499,7 +508,7 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
}
// Create RCON client
let mut rcon = match Rcon::connect_config(&config).await {
let mut rcon = match Rcon::connect_config(config).await {
Ok(rcon) => rcon,
Err(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 crate::config::Config;
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban;
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
let dir = match &config.server.directory {
let dir = match ConfigServer::server_directory(&config) {
Some(dir) => dir,
None => {
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::net::TcpStream;
use crate::config::*;
use crate::config::{Config, Server as ConfigServer};
use crate::join;
use crate::mc::favicon;
use crate::proto::action;
use crate::proto::client::{Client, ClientInfo, ClientState};
use crate::proto::packet::{self, RawPacket};
@@ -98,7 +99,7 @@ pub async fn serve(
// Hijack server status packet
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 mut data = Vec::new();
@@ -196,7 +197,7 @@ pub async fn serve(
}
/// 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 server_state = server.state();
@@ -230,12 +231,16 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
}
};
// Get server favicon
let favicon = if config.motd.from_server && status.is_some() {
status.as_ref().unwrap().favicon.clone()
} else {
favicon(&config).await
};
// Extract favicon from real server status, load from disk, or use default
let mut favicon = None;
if favicon::supports_favicon(client_info) {
if config.motd.from_server && status.is_some() {
favicon = status.as_ref().unwrap().favicon.clone()
}
if favicon.is_none() {
favicon = Some(server_favicon(config).await);
}
}
// Build status resposne
ServerStatus {
@@ -251,31 +256,31 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
}
/// 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
let dir = match config.server.directory.as_ref() {
let dir = match ConfigServer::server_directory(config) {
Some(dir) => dir,
None => return None,
None => return favicon::default_favicon(),
};
// Server icon file, ensure it exists
let path = dir.join(SERVER_ICON_FILE);
if !path.is_file() {
return None;
return favicon::default_favicon();
}
// Read icon data
let data = fs::read(path)
.await
.map_err(|err| {
error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
})
.ok()?;
let data = match fs::read(path).await.map_err(|err| {
error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
}) {
Ok(data) => data,
Err(err) => {
error!(target: "lazymc::status", "Failed to load server icon from disk, using default: {:?}", err);
return favicon::default_favicon();
}
};
// Format and return favicon
Some(format!(
"{}{}",
"data:image/png;base64,",
base64::encode(data)
))
favicon::encode_favicon(&data)
}