Compare commits

..

No commits in common. "master" and "v0.2.9" have entirely different histories.

16 changed files with 990 additions and 637 deletions

View File

@ -7,6 +7,11 @@ stages:
- pre-release - pre-release
- release - release
default:
tags:
- linux
- timvisee-linux
# Variable defaults # Variable defaults
variables: variables:
RUST_VERSION: stable RUST_VERSION: stable
@ -52,7 +57,7 @@ check-stable:
check-msrv: check-msrv:
<<: *check-base <<: *check-base
variables: variables:
RUST_VERSION: 1.74.0 RUST_VERSION: 1.64.0
only: only:
- master - master

View File

@ -1,16 +1,5 @@
# Changelog # Changelog
## 0.2.11 (2024-03-16)
- Add support for Minecraft 1.20.3 and 1.20.4
- Improve error handling of parsing server favicon
- Fix typo in log message
- Update dependencies
## 0.2.10 (2023-02-20)
- Do not report an error when server exits with status code 143
## 0.2.9 (2023-02-14) ## 0.2.9 (2023-02-14)
- Fix dropping all connections when `server.drop_banned_ips` was enabled - Fix dropping all connections when `server.drop_banned_ips` was enabled

1477
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lazymc" name = "lazymc"
version = "0.2.11" version = "0.2.9"
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"
@ -11,7 +11,6 @@ keywords = ["minecraft", "server", "idle", "cli"]
categories = ["command-line-interface", "games"] categories = ["command-line-interface", "games"]
exclude = ["/.github", "/contrib"] exclude = ["/.github", "/contrib"]
edition = "2021" edition = "2021"
rust-version = "1.74.0"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
@ -24,7 +23,7 @@ default = ["rcon", "lobby"]
# RCON support # RCON support
# Allow use of RCON to manage (stop) server. # Allow use of RCON to manage (stop) server.
# Required on Windows. # Required on Windows.
rcon = ["rust_rcon"] 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.
@ -32,7 +31,7 @@ lobby = ["md-5", "uuid"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
base64 = "0.22" base64 = "0.21"
bytes = "1.1" bytes = "1.1"
chrono = "0.4" chrono = "0.4"
clap = { version = "4.0.32", default-features = false, features = [ clap = { version = "4.0.32", default-features = false, features = [
@ -46,16 +45,16 @@ clap = { version = "4.0.32", default-features = false, features = [
"unicode", "unicode",
] } ] }
colored = "2.0" colored = "2.0"
derive_builder = "0.20" derive_builder = "0.12"
dotenv = "0.15" dotenv = "0.15"
flate2 = { version = "1.0", default-features = false, features = ["default"] } flate2 = { version = "1.0", default-features = false, features = ["default"] }
futures = { version = "0.3", default-features = false, features = ["executor"] } futures = { version = "0.3", default-features = false, features = ["executor"] }
log = "0.4" log = "0.4"
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "4f93bb3" } minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "edfdf87" }
named-binary-tag = "0.6" named-binary-tag = "0.6"
nix = { version = "0.28", features = ["process", "signal"] } nix = "0.26"
notify = "4.0" notify = "4.0"
pretty_env_logger = "0.5" pretty_env_logger = "0.4"
proxy-protocol = "0.5" proxy-protocol = "0.5"
quartz_nbt = "0.2" quartz_nbt = "0.2"
rand = "0.8" rand = "0.8"
@ -74,15 +73,16 @@ tokio = { version = "1", default-features = false, features = [
"sync", "sync",
"fs", "fs",
] } ] }
toml = "0.8" toml = "0.5"
version-compare = "0.2" version-compare = "0.1"
# Feature: rcon # Feature: rcon
rust_rcon = { package = "rcon", version = "0.6", default-features = false, features = ["rt-tokio"], optional = true } rust_rcon = { package = "rcon", version = "0.5.2", optional = true }
async-std = { version = "1.9.0", default-features = false, optional = true }
# Feature: lobby # Feature: lobby
md-5 = { version = "0.10", optional = true } md-5 = { version = "0.10", optional = true }
uuid = { version = "1.7", optional = true, features = ["v3"] } uuid = { version = "0.7", optional = true, features = ["v3"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"

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.20.3+ - 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
@ -57,7 +57,7 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
Build requirements: Build requirements:
- Rust 1.74 (MSRV) - Rust 1.64 (MSRV)
_Note: You must have access to the system to run the `lazymc` binary. If you're _Note: You must have access to the system to run the `lazymc` binary. If you're
using a Minecraft shared hosting provider with a custom dashboard, you likely using a Minecraft shared hosting provider with a custom dashboard, you likely

View File

@ -18,8 +18,8 @@
# Server version & protocol hint. # Server version & protocol hint.
# Sent to clients until actual server version is known. # Sent to clients until actual server version is known.
# See: https://git.io/J1Fvx # See: https://git.io/J1Fvx
#version = "1.20.3" #version = "1.19.3"
#protocol = 765 #protocol = 761
[server] [server]
# Server address. Internal IP and port of server started by lazymc to proxy to. # Server address. Internal IP and port of server started by lazymc to proxy to.
@ -187,4 +187,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.11" version = "0.2.9"

View File

@ -108,8 +108,8 @@ pub struct Config {
impl Config { impl Config {
/// Load configuration from file. /// Load configuration from file.
pub fn load(path: PathBuf) -> Result<Self, io::Error> { pub fn load(path: PathBuf) -> Result<Self, io::Error> {
let data = fs::read_to_string(&path)?; let data = fs::read(&path)?;
let mut config: Config = toml::from_str(&data).map_err(io::Error::other)?; 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 {

View File

@ -652,7 +652,7 @@ async fn drain_stream(reader: &mut ReadHalf<'_>) -> Result<(), ()> {
let mut drain_buf = [0; 8 * 1024]; let mut drain_buf = [0; 8 * 1024];
loop { loop {
match reader.try_read(&mut drain_buf) { match reader.try_read(&mut drain_buf) {
Ok(0) => return Ok(()), Ok(read) if read == 0 => return Ok(()),
Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(()), Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(()),
Ok(_) => continue, Ok(_) => continue,
Err(err) => { Err(err) => {

View File

@ -1,8 +1,8 @@
use std::time::Duration; use std::time::Duration;
use async_std::net::TcpStream;
use async_std::prelude::*;
use rust_rcon::{Connection, Error as RconError}; use rust_rcon::{Connection, Error as RconError};
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::time; use tokio::time;
use crate::config::Config; use crate::config::Config;
@ -17,7 +17,7 @@ const QUIRK_RCON_GRACE_TIME: Duration = Duration::from_millis(200);
/// An RCON client. /// An RCON client.
pub struct Rcon { pub struct Rcon {
con: Connection<TcpStream>, con: Connection,
} }
impl Rcon { impl Rcon {
@ -39,7 +39,7 @@ impl Rcon {
// Start connection // Start connection
let con = Connection::builder() let con = Connection::builder()
.enable_minecraft_quirks(true) .enable_minecraft_quirks(true)
.handshake(stream, pass) .connect_stream(stream, pass)
.await?; .await?;
Ok(Self { con }) Ok(Self { con })

View File

@ -3,10 +3,11 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use bytes::BytesMut; use bytes::BytesMut;
use minecraft_protocol::data::server_status::ServerStatus;
use minecraft_protocol::decoder::Decoder; use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake; use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_20_3::status::{ use minecraft_protocol::version::v1_14_4::status::{
PingRequest, PingResponse, ServerStatus, StatusRequest, StatusResponse, PingRequest, PingResponse, StatusRequest, StatusResponse,
}; };
use rand::Rng; use rand::Rng;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
@ -56,13 +57,13 @@ pub async fn monitor_server(config: Arc<Config>, server: Arc<Server>) {
// Sleep server when it's bedtime // Sleep server when it's bedtime
if server.should_sleep(&config).await { if server.should_sleep(&config).await {
info!(target: "lazymc::monitor", "Server has been idle, sleeping..."); info!(target: "lazymc::montior", "Server has been idle, sleeping...");
server.stop(&config).await; server.stop(&config).await;
} }
// Check whether we should force kill server // Check whether we should force kill server
if server.should_kill().await { if server.should_kill().await {
error!(target: "lazymc::monitor", "Force killing server, took too long to start or stop"); error!(target: "lazymc::montior", "Force killing server, took too long to start or stop");
if !server.force_kill().await { if !server.force_kill().await {
warn!(target: "lazymc", "Failed to force kill server"); warn!(target: "lazymc", "Failed to force kill server");
} }

View File

@ -2,10 +2,7 @@
pub mod windows; pub mod windows;
#[cfg(unix)] #[cfg(unix)]
use nix::{ use nix::{sys::signal, unistd::Pid};
sys::signal::{self, Signal},
unistd::Pid,
};
/// Force kill process. /// Force kill process.
/// ///
@ -13,7 +10,7 @@ use nix::{
#[allow(unreachable_code)] #[allow(unreachable_code)]
pub fn force_kill(pid: u32) -> bool { pub fn force_kill(pid: u32) -> bool {
#[cfg(unix)] #[cfg(unix)]
return unix_signal(pid, Signal::SIGKILL); return unix_signal(pid, signal::SIGKILL);
#[cfg(windows)] #[cfg(windows)]
unsafe { unsafe {
@ -31,7 +28,7 @@ pub fn force_kill(pid: u32) -> bool {
#[allow(unreachable_code, dead_code, unused_variables)] #[allow(unreachable_code, dead_code, unused_variables)]
pub fn kill_gracefully(pid: u32) -> bool { pub fn kill_gracefully(pid: u32) -> bool {
#[cfg(unix)] #[cfg(unix)]
return unix_signal(pid, Signal::SIGTERM); return unix_signal(pid, signal::SIGTERM);
unimplemented!( unimplemented!(
"gracefully killing Minecraft server process not implemented on non-Unix platforms" "gracefully killing Minecraft server process not implemented on non-Unix platforms"
@ -46,7 +43,7 @@ pub fn kill_gracefully(pid: u32) -> bool {
#[allow(unreachable_code)] #[allow(unreachable_code)]
pub fn freeze(pid: u32) -> bool { pub fn freeze(pid: u32) -> bool {
#[cfg(unix)] #[cfg(unix)]
return unix_signal(pid, Signal::SIGSTOP); return unix_signal(pid, signal::SIGSTOP);
unimplemented!( unimplemented!(
"freezing the Minecraft server process is not implemented on non-Unix platforms" "freezing the Minecraft server process is not implemented on non-Unix platforms"
@ -61,7 +58,7 @@ pub fn freeze(pid: u32) -> bool {
#[allow(unreachable_code)] #[allow(unreachable_code)]
pub fn unfreeze(pid: u32) -> bool { pub fn unfreeze(pid: u32) -> bool {
#[cfg(unix)] #[cfg(unix)]
return unix_signal(pid, Signal::SIGCONT); return unix_signal(pid, signal::SIGCONT);
unimplemented!( unimplemented!(
"unfreezing the Minecraft server process is not implemented on non-Unix platforms" "unfreezing the Minecraft server process is not implemented on non-Unix platforms"
@ -69,12 +66,12 @@ pub fn unfreeze(pid: u32) -> bool {
} }
#[cfg(unix)] #[cfg(unix)]
pub fn unix_signal(pid: u32, signal: Signal) -> bool { pub fn unix_signal(pid: u32, signal: signal::Signal) -> bool {
match signal::kill(Pid::from_raw(pid as i32), signal) { return match signal::kill(Pid::from_raw(pid as i32), signal) {
Ok(()) => true, Ok(()) => true,
Err(err) => { Err(err) => {
warn!(target: "lazymc", "Sending {signal} signal to server failed: {err}"); warn!(target: "lazymc", "Sending {signal} signal to server failed: {err}");
false false
} }
} };
} }

View File

@ -9,7 +9,7 @@ pub mod packets;
/// in the configuration. /// in the configuration.
/// ///
/// Should be kept up-to-date with latest supported Minecraft version by lazymc. /// Should be kept up-to-date with latest supported Minecraft version by lazymc.
pub const PROTO_DEFAULT_VERSION: &str = "1.20.3"; pub const PROTO_DEFAULT_VERSION: &str = "1.19.3";
/// Default minecraft protocol version. /// Default minecraft protocol version.
/// ///
@ -17,7 +17,7 @@ pub const PROTO_DEFAULT_VERSION: &str = "1.20.3";
/// in the configuration. /// in the configuration.
/// ///
/// Should be kept up-to-date with latest supported Minecraft version by lazymc. /// Should be kept up-to-date with latest supported Minecraft version by lazymc.
pub const PROTO_DEFAULT_PROTOCOL: u32 = 765; pub const PROTO_DEFAULT_PROTOCOL: u32 = 761;
/// Compression threshold to use. /// Compression threshold to use.
// TODO: read this from server.properties instead // TODO: read this from server.properties instead

View File

@ -70,7 +70,7 @@ async fn send_v1_16_3(
packet::write_packet( packet::write_packet(
Title { Title {
action: if title.is_empty() && subtitle.is_empty() { action: if title.is_empty() && subtitle.is_empty() {
// Defaults: https://minecraft.wiki/w/Commands/title#Detail // Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
TitleAction::SetTimesAndDisplay { TitleAction::SetTimesAndDisplay {
fade_in: 10, fade_in: 10,
stay: 70, stay: 70,
@ -121,7 +121,7 @@ async fn send_v1_17(
// Set title times // Set title times
packet::write_packet( packet::write_packet(
if title.is_empty() && subtitle.is_empty() { if title.is_empty() && subtitle.is_empty() {
// Defaults: https://minecraft.wiki/w/Commands/title#Detail // Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
SetTitleTimes { SetTitleTimes {
fade_in: 10, fade_in: 10,
stay: 70, stay: 70,

View File

@ -4,7 +4,7 @@ use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use futures::FutureExt; use futures::FutureExt;
use minecraft_protocol::version::v1_20_3::status::ServerStatus; use minecraft_protocol::data::server_status::ServerStatus;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::watch; use tokio::sync::watch;
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
@ -29,11 +29,9 @@ 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 codes that are allowed. /// Exit code when SIGTERM is received on Unix.
/// #[cfg(unix)]
/// - 143: https://github.com/timvisee/lazymc/issues/26#issuecomment-1435670029 const UNIX_EXIT_SIGTERM: i32 = 130;
/// - 130: https://unix.stackexchange.com/q/386836/61092
const ALLOWED_EXIT_CODES: [i32; 2] = [130, 143];
/// Shared server state. /// Shared server state.
#[derive(Debug)] #[derive(Debug)]
@ -498,12 +496,8 @@ pub async fn invoke_server_cmd(
debug!(target: "lazymc", "Server process stopped successfully ({})", status); debug!(target: "lazymc", "Server process stopped successfully ({})", status);
false false
} }
Ok(status) #[cfg(unix)]
if status Ok(status) if status.code() == Some(UNIX_EXIT_SIGTERM) => {
.code()
.map(|ref code| ALLOWED_EXIT_CODES.contains(code))
.unwrap_or(false) =>
{
debug!(target: "lazymc", "Server process stopped successfully by SIGTERM ({})", status); debug!(target: "lazymc", "Server process stopped successfully by SIGTERM ({})", status);
false false
} }

View File

@ -25,7 +25,7 @@ pub fn service(config: Arc<Config>, server: Arc<Server>) {
}; };
// Keep watching // Keep watching
#[allow(clippy::blocks_in_conditions)] #[allow(clippy::blocks_in_if_conditions)]
while { while {
// Update all files once // Update all files once
reload_bans(&config, &server, &dir.join(ban::FILE)); reload_bans(&config, &server, &dir.join(ban::FILE));

View File

@ -1,12 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use bytes::BytesMut; use bytes::BytesMut;
use minecraft_protocol::data::server_status::{OnlinePlayers, ServerVersion}; use minecraft_protocol::data::chat::{Message, Payload};
use minecraft_protocol::data::server_status::*;
use minecraft_protocol::decoder::Decoder; use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::encoder::Encoder; use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake; use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_14_4::login::LoginStart; use minecraft_protocol::version::v1_14_4::login::LoginStart;
use minecraft_protocol::version::v1_20_3::status::{ServerStatus, StatusResponse}; use minecraft_protocol::version::v1_14_4::status::StatusResponse;
use tokio::fs; use tokio::fs;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream; use tokio::net::TcpStream;
@ -231,11 +232,11 @@ async fn server_status(client_info: &ClientInfo, config: &Config, server: &Serve
if config.motd.from_server && status.is_some() { if config.motd.from_server && status.is_some() {
status.as_ref().unwrap().description.clone() status.as_ref().unwrap().description.clone()
} else { } else {
match server_state { Message::new(Payload::text(match server_state {
server::State::Stopped | server::State::Started => config.motd.sleeping.clone(), server::State::Stopped | server::State::Started => &config.motd.sleeping,
server::State::Starting => config.motd.starting.clone(), server::State::Starting => &config.motd.starting,
server::State::Stopping => config.motd.stopping.clone(), server::State::Stopping => &config.motd.stopping,
} }))
} }
}; };
@ -280,10 +281,15 @@ async fn server_favicon(config: &Config) -> String {
} }
// Read icon data // Read icon data
let data = fs::read(path).await.unwrap_or_else(|err| { let data = match fs::read(path).await.map_err(|err| {
error!(target: "lazymc::status", "Failed to read favicon from {}, using default: {err}", SERVER_ICON_FILE); error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
favicon::default_favicon().into_bytes() }) {
}); Ok(data) => data,
Err(err) => {
error!(target: "lazymc::status", "Failed to load server icon from disk, using default: {:?}", err);
return favicon::default_favicon();
}
};
favicon::encode_favicon(&data) favicon::encode_favicon(&data)
} }