Compare commits

..

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

37 changed files with 1161 additions and 1406 deletions

View File

@ -9,16 +9,18 @@ stages:
# Variable defaults # Variable defaults
variables: variables:
RUST_VERSION: stable
TARGET: x86_64-unknown-linux-gnu TARGET: x86_64-unknown-linux-gnu
# Rust build cache configuration
.rust-build-cache: &rust-build-cache
key: "$CI_PIPELINE_ID"
paths:
- target/
# Install build dependencies # Install build dependencies
before_script: before_script:
- apt-get update - apt-get update
- apt-get install -y --no-install-recommends build-essential - apt-get install -y --no-install-recommends build-essential
- |
rustup install $RUST_VERSION
rustup default $RUST_VERSION
- | - |
rustc --version rustc --version
cargo --version cargo --version
@ -27,39 +29,54 @@ before_script:
.before_script-windows: &before_script-windows .before_script-windows: &before_script-windows
before_script: before_script:
# Install scoop # Install scoop
- iex "& {$(irm get.scoop.sh)} -RunAsAdmin" - Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
# Install Rust # Install Rust
- scoop install rustup gcc - scoop install rustup
- rustup install $RUST_VERSION
- rustup default $RUST_VERSION
- rustc --version - rustc --version
- cargo --version - cargo --version
# Install proper Rust target
- rustup target install x86_64-pc-windows-msvc
# Check on stable, beta and nightly # Check on stable, beta and nightly
.check-base: &check-base .check-base: &check-base
stage: check stage: check
cache:
<<: *rust-build-cache
script: script:
- cargo check --verbose - cargo check --verbose
- cargo check --no-default-features --verbose - cargo check --no-default-features --verbose
- cargo check --no-default-features --features rcon --verbose - cargo check --no-default-features --features rcon --verbose
- cargo check --no-default-features --features lobby --verbose - cargo check --no-default-features --features lobby --verbose
check-stable: check:
<<: *check-base <<: *check-base
check-msrv: check-macos:
<<: *check-base tags:
variables: - macos
RUST_VERSION: 1.74.0
only: only:
- master - master
- /^v(\d+\.)*\d+$/
before_script:
- rustup default stable
- |
rustc --version
cargo --version
<<: *check-base
check-windows:
stage: check
tags:
- windows
cache: {}
<<: *before_script-windows
script:
- cargo check --locked --verbose
- cargo check --locked --no-default-features --features rcon --verbose
- cargo check --locked --no-default-features --features rcon,lobby --verbose
# Build using Rust stable on Linux # Build using Rust stable on Linux
build-x86_64-linux-gnu: build-x86_64-linux-gnu:
stage: build stage: build
needs: [] needs: []
cache:
<<: *rust-build-cache
script: script:
- cargo build --target=$TARGET --release --locked --verbose - cargo build --target=$TARGET --release --locked --verbose
- mv target/$TARGET/release/lazymc ./lazymc-$TARGET - mv target/$TARGET/release/lazymc ./lazymc-$TARGET
@ -73,12 +90,11 @@ build-x86_64-linux-gnu:
# Build a static version # Build a static version
build-x86_64-linux-musl: build-x86_64-linux-musl:
stage: build stage: build
only:
- master
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
needs: [] needs: []
variables: variables:
TARGET: x86_64-unknown-linux-musl TARGET: x86_64-unknown-linux-musl
cache:
<<: *rust-build-cache
script: script:
- rustup target add $TARGET - rustup target add $TARGET
- cargo build --target=$TARGET --release --locked --verbose - cargo build --target=$TARGET --release --locked --verbose
@ -97,12 +113,11 @@ build-x86_64-linux-musl:
build-armv7-linux-gnu: build-armv7-linux-gnu:
stage: build stage: build
image: ubuntu image: ubuntu
only:
- master
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
needs: [] needs: []
variables: variables:
TARGET: armv7-unknown-linux-gnueabihf TARGET: armv7-unknown-linux-gnueabihf
cache:
<<: *rust-build-cache
before_script: before_script:
- apt-get update - apt-get update
- apt-get install -y --no-install-recommends build-essential - apt-get install -y --no-install-recommends build-essential
@ -133,12 +148,11 @@ build-armv7-linux-gnu:
build-aarch64-linux-gnu: build-aarch64-linux-gnu:
stage: build stage: build
image: ubuntu image: ubuntu
only:
- master
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
needs: [] needs: []
variables: variables:
TARGET: aarch64-unknown-linux-gnu TARGET: aarch64-unknown-linux-gnu
cache:
<<: *rust-build-cache
before_script: before_script:
- apt-get update - apt-get update
- apt-get install -y --no-install-recommends build-essential - apt-get install -y --no-install-recommends build-essential
@ -165,14 +179,36 @@ build-aarch64-linux-gnu:
- lazymc-$TARGET - lazymc-$TARGET
expire_in: 1 month expire_in: 1 month
# Build using Rust stable on macOS
build-macos:
stage: build
tags:
- macos
only:
- master
- /^v(\d+\.)*\d+$/
needs: []
variables:
TARGET: x86_64-apple-darwin
before_script:
- rustup default stable
- |
rustc --version
cargo --version
script:
- cargo build --target=$TARGET --release --locked --verbose
- mv target/$TARGET/release/lazymc ./lazymc-$TARGET
artifacts:
name: lazymc-x86_64-macos
paths:
- lazymc-$TARGET
expire_in: 1 month
# Build using Rust stable on Windows # Build using Rust stable on Windows
build-x86_64-windows: build-x86_64-windows:
stage: build stage: build
tags: tags:
- windows - windows
only:
- master
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
needs: [] needs: []
variables: variables:
TARGET: x86_64-pc-windows-msvc TARGET: x86_64-pc-windows-msvc
@ -189,28 +225,29 @@ build-x86_64-windows:
# Run the unit tests through Cargo on Linux # Run the unit tests through Cargo on Linux
test-cargo-x86_64-linux-gnu: test-cargo-x86_64-linux-gnu:
stage: test stage: test
only:
- master
needs: [] needs: []
dependencies: [] dependencies: []
cache:
<<: *rust-build-cache
script: script:
- cargo test --locked --verbose - cargo test --locked --verbose
- cargo test --locked --no-default-features --verbose - cargo test --locked --no-default-features --verbose
- cargo test --locked --no-default-features --features rcon --verbose - cargo test --locked --no-default-features --features rcon --verbose
- cargo test --locked --no-default-features --features lobby --verbose - cargo test --locked --no-default-features --features lobby --verbose
# # Run the unit tests through Cargo on Windows # Run the unit tests through Cargo on Windows
# test-cargo-x86_64-windows: test-cargo-x86_64-windows:
# stage: test stage: test
# tags: tags:
# - windows - windows
# needs: [] needs: []
# dependencies: [] dependencies: []
# <<: *before_script-windows cache: {}
# script: <<: *before_script-windows
# - cargo test --locked --verbose script:
# - cargo test --locked --no-default-features --features rcon --verbose - cargo test --locked --verbose
# - cargo test --locked --no-default-features --features rcon,lobby --verbose - cargo test --locked --no-default-features --features rcon --verbose
- cargo test --locked --no-default-features --features rcon,lobby --verbose
# Release binaries on GitLab as generic package # Release binaries on GitLab as generic package
release-gitlab-generic-package: release-gitlab-generic-package:
@ -221,14 +258,16 @@ release-gitlab-generic-package:
- build-x86_64-linux-musl - build-x86_64-linux-musl
- build-armv7-linux-gnu - build-armv7-linux-gnu
- build-aarch64-linux-gnu - build-aarch64-linux-gnu
- build-macos
- build-x86_64-windows - build-x86_64-windows
only: only:
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - /^v(\d+\.)*\d+$/
variables: variables:
LINUX_GNU_BIN: "lazymc-x86_64-unknown-linux-gnu" LINUX_GNU_BIN: "lazymc-x86_64-unknown-linux-gnu"
LINUX_MUSL_BIN: "lazymc-x86_64-unknown-linux-musl" LINUX_MUSL_BIN: "lazymc-x86_64-unknown-linux-musl"
LINUX_ARMV7_GNU_BIN: "lazymc-armv7-unknown-linux-gnueabihf" LINUX_ARMV7_GNU_BIN: "lazymc-armv7-unknown-linux-gnueabihf"
LINUX_AARCH64_GNU_BIN: "lazymc-aarch64-unknown-linux-gnu" LINUX_AARCH64_GNU_BIN: "lazymc-aarch64-unknown-linux-gnu"
MACOS_BIN: "lazymc-x86_64-apple-darwin"
WINDOWS_BIN: "lazymc-x86_64-pc-windows-msvc.exe" WINDOWS_BIN: "lazymc-x86_64-pc-windows-msvc.exe"
before_script: [] before_script: []
script: script:
@ -245,6 +284,8 @@ release-gitlab-generic-package:
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${LINUX_ARMV7_GNU_BIN} ${PACKAGE_REGISTRY_URL}/${LINUX_ARMV7_GNU_BIN} curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${LINUX_ARMV7_GNU_BIN} ${PACKAGE_REGISTRY_URL}/${LINUX_ARMV7_GNU_BIN}
- | - |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${LINUX_AARCH64_GNU_BIN} ${PACKAGE_REGISTRY_URL}/${LINUX_AARCH64_GNU_BIN} curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${LINUX_AARCH64_GNU_BIN} ${PACKAGE_REGISTRY_URL}/${LINUX_AARCH64_GNU_BIN}
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${MACOS_BIN} ${PACKAGE_REGISTRY_URL}/${MACOS_BIN}
- | - |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${WINDOWS_BIN} ${PACKAGE_REGISTRY_URL}/${WINDOWS_BIN} curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${WINDOWS_BIN} ${PACKAGE_REGISTRY_URL}/${WINDOWS_BIN}
@ -253,12 +294,13 @@ release-gitlab-release:
image: registry.gitlab.com/gitlab-org/release-cli image: registry.gitlab.com/gitlab-org/release-cli
stage: release stage: release
only: only:
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - /^v(\d+\.)*\d+$/
variables: variables:
LINUX_GNU_BIN: "lazymc-x86_64-unknown-linux-gnu" LINUX_GNU_BIN: "lazymc-x86_64-unknown-linux-gnu"
LINUX_MUSL_BIN: "lazymc-x86_64-unknown-linux-musl" LINUX_MUSL_BIN: "lazymc-x86_64-unknown-linux-musl"
LINUX_ARMV7_GNU_BIN: "lazymc-armv7-unknown-linux-gnueabihf" LINUX_ARMV7_GNU_BIN: "lazymc-armv7-unknown-linux-gnueabihf"
LINUX_AARCH64_GNU_BIN: "lazymc-aarch64-unknown-linux-gnu" LINUX_AARCH64_GNU_BIN: "lazymc-aarch64-unknown-linux-gnu"
MACOS_BIN: "lazymc-x86_64-apple-darwin"
WINDOWS_BIN: "lazymc-x86_64-pc-windows-msvc.exe" WINDOWS_BIN: "lazymc-x86_64-pc-windows-msvc.exe"
before_script: [] before_script: []
script: script:
@ -273,18 +315,20 @@ release-gitlab-release:
--assets-link "{\"name\":\"${LINUX_MUSL_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_MUSL_BIN}\"}" \ --assets-link "{\"name\":\"${LINUX_MUSL_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_MUSL_BIN}\"}" \
--assets-link "{\"name\":\"${LINUX_ARMV7_GNU_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_ARMV7_GNU_BIN}\"}" \ --assets-link "{\"name\":\"${LINUX_ARMV7_GNU_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_ARMV7_GNU_BIN}\"}" \
--assets-link "{\"name\":\"${LINUX_AARCH64_GNU_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_AARCH64_GNU_BIN}\"}" \ --assets-link "{\"name\":\"${LINUX_AARCH64_GNU_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_AARCH64_GNU_BIN}\"}" \
--assets-link "{\"name\":\"${MACOS_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${MACOS_BIN}\"}" \
--assets-link "{\"name\":\"${WINDOWS_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WINDOWS_BIN}\"}" --assets-link "{\"name\":\"${WINDOWS_BIN}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WINDOWS_BIN}\"}"
# Publish GitHub release # Publish GitHub release
release-github: release-github:
stage: release stage: release
only: only:
- /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - /^v(\d+\.)*\d+$/
dependencies: dependencies:
- build-x86_64-linux-gnu - build-x86_64-linux-gnu
- build-x86_64-linux-musl - build-x86_64-linux-musl
- build-armv7-linux-gnu - build-armv7-linux-gnu
- build-aarch64-linux-gnu - build-aarch64-linux-gnu
- build-macos
- build-x86_64-windows - build-x86_64-windows
before_script: [] before_script: []
script: script:
@ -303,4 +347,5 @@ release-github:
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-x86_64-unknown-linux-musl --name lazymc-$CI_COMMIT_REF_NAME-linux-x64-static - ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-x86_64-unknown-linux-musl --name lazymc-$CI_COMMIT_REF_NAME-linux-x64-static
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-armv7-unknown-linux-gnueabihf --name lazymc-$CI_COMMIT_REF_NAME-linux-armv7 - ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-armv7-unknown-linux-gnueabihf --name lazymc-$CI_COMMIT_REF_NAME-linux-armv7
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-aarch64-unknown-linux-gnu --name lazymc-$CI_COMMIT_REF_NAME-linux-aarch64 - ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-aarch64-unknown-linux-gnu --name lazymc-$CI_COMMIT_REF_NAME-linux-aarch64
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-x86_64-apple-darwin --name lazymc-$CI_COMMIT_REF_NAME-macos
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-x86_64-pc-windows-msvc.exe --name lazymc-$CI_COMMIT_REF_NAME-windows.exe - ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo lazymc --tag "$CI_COMMIT_REF_NAME" --file ./lazymc-x86_64-pc-windows-msvc.exe --name lazymc-$CI_COMMIT_REF_NAME-windows.exe

View File

@ -1,39 +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)
- Fix dropping all connections when `server.drop_banned_ips` was enabled
- Update dependencies
## 0.2.8 (2023-01-30)
- Add `freeze_process` feature on Unix platforms to freeze a sleeping server
rather than shutting it down.
- Update default Minecraft version to 1.19.3
- Remove macOS builds from releases, users can compile from source
- Update dependencies
## 0.2.7 (2021-12-13)
- Update default Minecraft version to 1.18.1
- Update dependencies
## 0.2.6 (2021-11-28)
- Add whitelist support, use server whitelist to prevent unknown users from waking server
- Update dependencies
## 0.2.5 (2021-11-25) ## 0.2.5 (2021-11-25)
- Add support Minecraft 1.16.3 to 1.17.1 with lobby join method - Add support Minecraft 1.16.3 to 1.17.1 with lobby join method

1470
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.5"
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"
@ -8,15 +8,15 @@ homepage = "https://timvisee.com/projects/lazymc"
repository = "https://gitlab.com/timvisee/lazymc" repository = "https://gitlab.com/timvisee/lazymc"
description = "Put your Minecraft server to rest when idle." description = "Put your Minecraft server to rest when idle."
keywords = ["minecraft", "server", "idle", "cli"] keywords = ["minecraft", "server", "idle", "cli"]
categories = ["command-line-interface", "games"] categories = [
exclude = ["/.github", "/contrib"] "command-line-interface",
"games",
]
exclude = [
"/.github",
"/contrib",
]
edition = "2021" edition = "2021"
rust-version = "1.74.0"
[profile.release]
codegen-units = 1
lto = true
strip = true
[features] [features]
default = ["rcon", "lobby"] default = ["rcon", "lobby"]
@ -24,7 +24,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,30 +32,20 @@ lobby = ["md-5", "uuid"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
base64 = "0.22" base64 = "0.13"
bytes = "1.1" bytes = "1.1"
chrono = "0.4" chrono = "0.4"
clap = { version = "4.0.32", default-features = false, features = [ clap = { version = "3.0.0-beta.5", default-features = false, features = [ "std", "cargo", "color", "env", "suggestions", "unicode" ]}
"std",
"help",
"suggestions",
"color",
"usage",
"cargo",
"env",
"unicode",
] }
colored = "2.0" colored = "2.0"
derive_builder = "0.20" derive_builder = "0.10"
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"] }
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"
@ -63,35 +53,20 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
shlex = "1.1" shlex = "1.1"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1", default-features = false, features = [ tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal", "sync", "fs"] }
"rt-multi-thread", toml = "0.5"
"io-util", version-compare = "0.1"
"net",
"macros",
"time",
"process",
"signal",
"sync",
"fs",
] }
toml = "0.8"
version-compare = "0.2"
# 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.9", 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"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = [ winapi = { version = "0.3", features = ["winuser", "processthreadsapi", "handleapi", "ntdef", "minwindef"] }
"winuser",
"processthreadsapi",
"handleapi",
"ntdef",
"minwindef",
] }

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
@ -55,10 +55,6 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
- Minecraft Java Edition 1.6+ - Minecraft Java Edition 1.6+
- On Windows: RCON (automatically managed) - On Windows: RCON (automatically managed)
Build requirements:
- Rust 1.74 (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
won't be able to set this up._ won't be able to set this up._
@ -71,8 +67,7 @@ _Note: these instructions are for Linux & macOS, for Windows look
Make sure you meet all [requirements](#requirements). Make sure you meet all [requirements](#requirements).
Download the appropriate binary for your system from the [latest Download the appropriate binary for your system from the [latest
release][latest-release] page. On macOS you must [compile from release][latest-release] page.
source](#compile-from-source).
Place the binary in your Minecraft server directory, rename it if you like. 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: Open a terminal, go to the directory, and make sure you can invoke it:

View File

@ -1,7 +0,0 @@
fn main() {
// rcon is required on Windows
#[cfg(all(windows, not(feature = "rcon")))]
{
compile_error!("required feature missing on Windows: rcon");
}
}

View File

@ -1 +0,0 @@
msrv = "1.64.0"

View File

@ -62,18 +62,15 @@ Then configure the lobby to your likings:
# The client will be teleported to the real server once it is ready. # The client will be teleported to the real server once it is ready.
# This may keep the client occupied forever if no timeout is set. # This may keep the client occupied forever if no timeout is set.
# Consumes client, not allowing other join methods afterwards. # Consumes client, not allowing other join methods afterwards.
# See: https://git.io/JMIi4
# !!! WARNING !!! # !!! WARNING !!!
# This is highly experimental, incomplete and unstable. # This is highly experimental and unstable.
# This may break the game and crash clients. # This may break the game and crash clients.
# Don't enable this unless you know what you're doing. # Don't enable this unless you know what you're doing.
# #
# - Server must be in offline mode # - Only works with offline mode
# - Server must use Minecraft version 1.16.3 to 1.17.1 (tested with 1.17.1) # - Only works with vanilla Minecraft clients, does not work with modded
# - Server must use vanilla Minecraft # - Only tested with Minecraft 1.17.1
# - May work with Forge, enable in config, depends on used mods, test before use
# - Does not work with other mods, such as FTB
# Maximum time in seconds in the lobby while the server starts. # Maximum time in seconds in the lobby while the server starts.
timeout = 600 timeout = 600

View File

@ -21,8 +21,8 @@ In lazymc you may configure what protocol version to use:
# 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.19.3" version = "1.17.1"
protocol = 761 protocol = 756
# -- snip -- # -- snip --
``` ```

View File

@ -14,7 +14,7 @@ Open a terminal, go to the server directory, and make sure you can execute it:
.\lazymc --help .\lazymc --help
``` ```
When lazymc is ready, set up the [configuration](../res/lazymc.toml) and start it When lazymc is ready, set up the [configuration](./res/lazymc.toml) and start it
up: up:
```bash ```bash

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.17.1"
#protocol = 765 #protocol = 756
[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.
@ -33,10 +33,6 @@ directory = "."
# Warning: if using a bash script read: https://git.io/JMIKH # Warning: if using a bash script read: https://git.io/JMIKH
command = "java -Xmx1G -Xms1G -jar server.jar --nogui" command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
# Freeze the server process instead of restarting it when no players online, making it resume faster.
# Only works on Unix (Linux or MacOS), ignored on Windows
#freeze_process = true
# Immediately wake server when starting lazymc. # Immediately wake server when starting lazymc.
#wake_on_start = false #wake_on_start = false
@ -54,9 +50,6 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
#start_timeout = 300 #start_timeout = 300
#stop_timeout = 150 #stop_timeout = 150
# To wake server, user must be in server whitelist if enabled on server.
#wake_whitelist = true
# Block banned IPs as listed in banned-ips.json in server directory. # Block banned IPs as listed in banned-ips.json in server directory.
#block_banned_ips = true #block_banned_ips = true
@ -187,4 +180,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.5"

View File

@ -9,7 +9,7 @@ use crate::util::error::{quit, quit_error, ErrorHintsBuilder};
/// Invoke config test command. /// Invoke config test command.
pub fn invoke(matches: &ArgMatches) { pub fn invoke(matches: &ArgMatches) {
// Get config path, attempt to canonicalize // Get config path, attempt to canonicalize
let mut path = PathBuf::from(matches.get_one::<String>("config").unwrap()); let mut path = PathBuf::from(matches.value_of("config").unwrap());
if let Ok(p) = path.canonicalize() { if let Ok(p) = path.canonicalize() {
path = p; path = p;
} }

View File

@ -8,7 +8,7 @@ use crate::util::error::{quit_error, quit_error_msg, ErrorHintsBuilder};
/// Invoke config test command. /// Invoke config test command.
pub fn invoke(matches: &ArgMatches) { pub fn invoke(matches: &ArgMatches) {
// Get config path, attempt to canonicalize // Get config path, attempt to canonicalize
let mut path = PathBuf::from(matches.get_one::<String>("config").unwrap()); let mut path = PathBuf::from(matches.value_of("config").unwrap());
if let Ok(p) = path.canonicalize() { if let Ok(p) = path.canonicalize() {
path = p; path = p;
} }

View File

@ -1,28 +1,23 @@
use clap::{Arg, Command}; use clap::{App, AppSettings, Arg};
/// The clap app for CLI argument parsing. /// The clap app for CLI argument parsing.
pub fn app() -> Command { pub fn app() -> App<'static> {
Command::new(crate_name!()) App::new(crate_name!())
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.about(crate_description!()) .about(crate_description!())
.subcommand( .subcommand(
Command::new("start") App::new("start")
.alias("run") .alias("run")
.about("Start lazymc and server (default)"), .about("Start lazymc and server (default)"),
) )
.subcommand( .subcommand(
Command::new("config") App::new("config")
.alias("cfg") .alias("cfg")
.about("Config actions") .about("Config actions")
.arg_required_else_help(true) .setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand_required(true) .subcommand(App::new("generate").alias("gen").about("Generate config"))
.subcommand( .subcommand(App::new("test").about("Test config")),
Command::new("generate")
.alias("gen")
.about("Generate config"),
)
.subcommand(Command::new("test").about("Test config")),
) )
.arg( .arg(
Arg::new("config") Arg::new("config")
@ -32,7 +27,7 @@ pub fn app() -> Command {
.global(true) .global(true)
.value_name("FILE") .value_name("FILE")
.default_value(crate::config::CONFIG_FILE) .default_value(crate::config::CONFIG_FILE)
.help("Use config file") .about("Use config file")
.num_args(1), .takes_value(true),
) )
} }

View File

@ -15,14 +15,14 @@ use crate::util::serde::to_socket_addrs;
pub const CONFIG_FILE: &str = "lazymc.toml"; pub const CONFIG_FILE: &str = "lazymc.toml";
/// Configuration version user should be using, or warning will be shown. /// Configuration version user should be using, or warning will be shown.
const CONFIG_VERSION: &str = "0.2.8"; const CONFIG_VERSION: &str = "0.2.1";
/// Load config from file, based on CLI arguments. /// Load config from file, based on CLI arguments.
/// ///
/// Quits with an error message on failure. /// Quits with an error message on failure.
pub fn load(matches: &ArgMatches) -> Config { pub fn load(matches: &ArgMatches) -> Config {
// Get config path, attempt to canonicalize // Get config path, attempt to canonicalize
let mut path = PathBuf::from(matches.get_one::<String>("config").unwrap()); let mut path = PathBuf::from(matches.value_of("config").unwrap());
if let Ok(p) = path.canonicalize() { if let Ok(p) = path.canonicalize() {
path = p; path = p;
} }
@ -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 {
@ -174,11 +174,6 @@ pub struct Server {
)] )]
pub address: SocketAddr, pub address: SocketAddr,
/// Freeze the server process instead of restarting it when no players online, making it start up faster.
/// Only works on Unix (Linux or MacOS)
#[serde(default = "bool_true")]
pub freeze_process: bool,
/// Immediately wake server when starting lazymc. /// Immediately wake server when starting lazymc.
#[serde(default)] #[serde(default)]
pub wake_on_start: bool, pub wake_on_start: bool,
@ -203,10 +198,6 @@ pub struct Server {
#[serde(default = "u32_150")] #[serde(default = "u32_150")]
pub stop_timeout: u32, pub stop_timeout: u32,
/// To wake server, user must be in server whitelist if enabled on server.
#[serde(default = "bool_true")]
pub wake_whitelist: bool,
/// Block banned IPs as listed in banned-ips.json in server directory. /// Block banned IPs as listed in banned-ips.json in server directory.
#[serde(default = "bool_true")] #[serde(default = "bool_true")]
pub block_banned_ips: bool, pub block_banned_ips: bool,
@ -483,13 +474,19 @@ impl Default for Advanced {
} }
/// Config configuration. /// Config configuration.
#[derive(Debug, Deserialize, Default)] #[derive(Debug, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ConfigConfig { pub struct ConfigConfig {
/// Configuration for lazymc version. /// Configuration for lazymc version.
pub version: Option<String>, pub version: Option<String>,
} }
impl Default for ConfigConfig {
fn default() -> Self {
Self { version: None }
}
}
fn option_pathbuf_dot() -> Option<PathBuf> { fn option_pathbuf_dot() -> Option<PathBuf> {
Some(".".into()) Some(".".into())
} }

View File

@ -42,7 +42,7 @@ const SERVER_JOIN_GAME_TIMEOUT: Duration = Duration::from_secs(20);
/// ///
/// Notchian servers are slow, we must wait a little before sending play packets, because the /// Notchian servers are slow, we must wait a little before sending play packets, because the
/// server needs time to transition the client into this state. /// server needs time to transition the client into this state.
/// See warning at: <https://wiki.vg/Protocol#Login_Success> /// See warning at: https://wiki.vg/Protocol#Login_Success
const SERVER_WARMUP: Duration = Duration::from_secs(1); const SERVER_WARMUP: Duration = Duration::from_secs(1);
/// Serve lobby service for given client connection. /// Serve lobby service for given client connection.
@ -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

@ -29,7 +29,7 @@ pub(crate) mod util;
use std::env; use std::env;
use clap::Command; use clap::App;
// Compile time feature compatability check. // Compile time feature compatability check.
#[cfg(all(windows, not(feature = "rcon")))] #[cfg(all(windows, not(feature = "rcon")))]
@ -63,7 +63,7 @@ fn init_log() {
} }
/// Invoke an action. /// Invoke an action.
fn invoke_action(app: Command) -> Result<(), ()> { fn invoke_action(app: App) -> Result<(), ()> {
let matches = app.get_matches(); let matches = app.get_matches();
// Config operations // Config operations

View File

@ -1,5 +1,3 @@
use base64::Engine;
use crate::proto::client::ClientInfo; use crate::proto::client::ClientInfo;
/// Protocol version since when favicons are supported. /// Protocol version since when favicons are supported.
@ -14,11 +12,7 @@ pub fn default_favicon() -> String {
/// ///
/// This assumes the favicon data to be a valid PNG image. /// This assumes the favicon data to be a valid PNG image.
pub fn encode_favicon(data: &[u8]) -> String { pub fn encode_favicon(data: &[u8]) -> String {
format!( format!("{}{}", "data:image/png;base64,", base64::encode(data))
"{}{}",
"data:image/png;base64,",
base64::engine::general_purpose::STANDARD.encode(data)
)
} }
/// Check whether the status response favicon is supported based on the given client info. /// Check whether the status response favicon is supported based on the given client info.

View File

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

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

@ -114,7 +114,7 @@ fn rewrite_contents(contents: String, mut changes: HashMap<&str, String>) -> Opt
} }
// Try to split property // Try to split property
let (key, value) = match line.split_once('=') { let (key, value) = match line.split_once("=") {
Some(result) => result, Some(result) => result,
None => return line, None => return line,
}; };
@ -122,7 +122,7 @@ fn rewrite_contents(contents: String, mut changes: HashMap<&str, String>) -> Opt
// Take any new value, and update it // Take any new value, and update it
if let Some((_, new)) = changes.remove_entry(key.trim().to_lowercase().as_str()) { if let Some((_, new)) = changes.remove_entry(key.trim().to_lowercase().as_str()) {
if value != new { if value != new {
line = format!("{key}={new}"); line = format!("{}={}", key, new);
changed = true; changed = true;
} }
} }
@ -134,7 +134,7 @@ fn rewrite_contents(contents: String, mut changes: HashMap<&str, String>) -> Opt
// Append any missed changes // Append any missed changes
for (key, value) in changes { for (key, value) in changes {
new_contents += &format!("{EOL}{key}={value}"); new_contents += &format!("{}{}={}", EOL, key, value);
changed = true; changed = true;
} }
@ -145,37 +145,3 @@ fn rewrite_contents(contents: String, mut changes: HashMap<&str, String>) -> Opt
None None
} }
} }
/// Read the given property from the given server.properties file.o
///
/// Returns `None` if file does not contain the property.
pub fn read_property<P: AsRef<Path>>(file: P, property: &str) -> Option<String> {
// File must exist
if !file.as_ref().is_file() {
warn!(target: "lazymc",
"Failed to read property from {} file, it does not exist",
FILE,
);
return None;
}
// Read contents
let contents = match fs::read_to_string(&file) {
Ok(contents) => contents,
Err(err) => {
error!(target: "lazymc",
"Failed to read property from {} file, could not load: {}",
FILE,
err,
);
return None;
}
};
// Find property, return value
contents
.lines()
.filter_map(|line| line.split_once('='))
.find(|(p, _)| p.trim().to_lowercase() == property.to_lowercase())
.map(|(_, v)| v.trim().to_string())
}

View File

@ -11,14 +11,14 @@ fn player_uuid(username: &str) -> Uuid {
/// Get UUID for given offline player username. /// Get UUID for given offline player username.
pub fn offline_player_uuid(username: &str) -> Uuid { pub fn offline_player_uuid(username: &str) -> Uuid {
player_uuid(&format!("{OFFLINE_PLAYER_NAMESPACE}{username}")) player_uuid(&format!("{}{}", OFFLINE_PLAYER_NAMESPACE, username))
} }
/// Java's `UUID.nameUUIDFromBytes` /// Java's `UUID.nameUUIDFromBytes`
/// ///
/// Static factory to retrieve a type 3 (name based) `Uuid` based on the specified byte array. /// Static factory to retrieve a type 3 (name based) `Uuid` based on the specified byte array.
/// ///
/// Ported from: <https://git.io/J1b6A> /// Ported from: https://git.io/J1b6A
fn java_name_uuid_from_bytes(data: &[u8]) -> Uuid { fn java_name_uuid_from_bytes(data: &[u8]) -> Uuid {
let mut hasher = Md5::new(); let mut hasher = Md5::new();
hasher.update(data); hasher.update(data);

View File

@ -1,107 +0,0 @@
use std::error::Error;
use std::fs;
use std::path::Path;
use serde::Deserialize;
/// Whitelist file name.
pub const WHITELIST_FILE: &str = "whitelist.json";
/// OPs file name.
pub const OPS_FILE: &str = "ops.json";
/// Whitelisted users.
///
/// Includes list of OPs, which are also automatically whitelisted.
#[derive(Debug, Default)]
pub struct Whitelist {
/// Whitelisted users.
whitelist: Vec<String>,
/// OPd users.
ops: Vec<String>,
}
impl Whitelist {
/// Check whether a user is whitelisted.
pub fn is_whitelisted(&self, username: &str) -> bool {
self.whitelist.iter().any(|u| u == username) || self.ops.iter().any(|u| u == username)
}
}
/// A whitelist user.
#[derive(Debug, Deserialize, Clone)]
pub struct WhitelistUser {
/// Whitelisted username.
#[serde(rename = "name", alias = "username")]
pub username: String,
/// Whitelisted UUID.
pub uuid: Option<String>,
}
/// An OP user.
#[derive(Debug, Deserialize, Clone)]
pub struct OpUser {
/// OP username.
#[serde(rename = "name", alias = "username")]
pub username: String,
/// OP UUID.
pub uuid: Option<String>,
/// OP level.
pub level: Option<u32>,
/// Whether OP can bypass player limit.
#[serde(rename = "bypassesPlayerLimit")]
pub byapsses_player_limit: Option<bool>,
}
/// Load whitelist from directory.
pub fn load_dir(path: &Path) -> Result<Whitelist, Box<dyn Error>> {
let whitelist_file = path.join(WHITELIST_FILE);
let ops_file = path.join(OPS_FILE);
// Load whitelist users
let whitelist = if whitelist_file.is_file() {
load_whitelist(&whitelist_file)?
} else {
vec![]
};
// Load OPd users
let ops = if ops_file.is_file() {
load_ops(&ops_file)?
} else {
vec![]
};
debug!(target: "lazymc", "Loaded {} whitelist and {} OP users", whitelist.len(), ops.len());
Ok(Whitelist { whitelist, ops })
}
/// Load whitelist from file.
fn load_whitelist(path: &Path) -> Result<Vec<String>, Box<dyn Error>> {
// Load file contents
let contents = fs::read_to_string(path)?;
// Parse contents
let users: Vec<WhitelistUser> = serde_json::from_str(&contents)?;
// Pluck usernames
Ok(users.into_iter().map(|user| user.username).collect())
}
/// Load OPs from file.
fn load_ops(path: &Path) -> Result<Vec<String>, Box<dyn Error>> {
// Load file contents
let contents = fs::read_to_string(path)?;
// Parse contents
let users: Vec<OpUser> = serde_json::from_str(&contents)?;
// Pluck usernames
Ok(users.into_iter().map(|user| user.username).collect())
}

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

@ -1,19 +1,17 @@
#[cfg(unix)]
pub mod unix;
#[cfg(windows)] #[cfg(windows)]
pub mod windows; pub mod windows;
#[cfg(unix)]
use nix::{
sys::signal::{self, Signal},
unistd::Pid,
};
/// Force kill process. /// Force kill process.
/// ///
/// Results in undefined behavior if PID is invalid. /// Results in undefined behavior if PID is invalid.
#[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); unsafe {
return unix::force_kill(pid);
}
#[cfg(windows)] #[cfg(windows)]
unsafe { unsafe {
@ -24,57 +22,20 @@ pub fn force_kill(pid: u32) -> bool {
} }
/// Gracefully kill process. /// Gracefully kill process.
///
/// Results in undefined behavior if PID is invalid. /// Results in undefined behavior if PID is invalid.
/// ///
/// # Panics /// # Panics
///
/// Panics on platforms other than Unix. /// Panics on platforms other than Unix.
#[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); unsafe {
return unix::kill_gracefully(pid);
}
unimplemented!( unimplemented!(
"gracefully killing Minecraft server process not implemented on non-Unix platforms" "gracefully killing Minecraft server process not implemented on non-Unix platforms"
); );
} }
/// Freeze process.
/// Results in undefined behavior if PID is invaild.
///
/// # Panics
/// Panics on platforms other than Unix.
#[allow(unreachable_code)]
pub fn freeze(pid: u32) -> bool {
#[cfg(unix)]
return unix_signal(pid, Signal::SIGSTOP);
unimplemented!(
"freezing the Minecraft server process is not implemented on non-Unix platforms"
);
}
/// Unfreeze process.
/// Results in undefined behavior if PID is invaild.
///
/// # Panics
/// Panics on platforms other than Unix.
#[allow(unreachable_code)]
pub fn unfreeze(pid: u32) -> bool {
#[cfg(unix)]
return unix_signal(pid, Signal::SIGCONT);
unimplemented!(
"unfreezing the Minecraft server process is not implemented on non-Unix platforms"
);
}
#[cfg(unix)]
pub fn unix_signal(pid: u32, signal: Signal) -> bool {
match signal::kill(Pid::from_raw(pid as i32), signal) {
Ok(()) => true,
Err(err) => {
warn!(target: "lazymc", "Sending {signal} signal to server failed: {err}");
false
}
}
}

27
src/os/unix.rs Normal file
View File

@ -0,0 +1,27 @@
/// Force kill process on Unix by sending SIGKILL.
///
/// This is unsafe because the PID isn't checked.
pub unsafe fn force_kill(pid: u32) -> bool {
debug!(target: "lazymc", "Sending SIGKILL signal to {} to kill server", pid);
let result = libc::kill(pid as i32, libc::SIGKILL);
if result != 0 {
trace!(target: "lazymc", "SIGKILL failed: {}", result);
}
result == 0
}
/// Gracefully kill process on Unix by sending SIGTERM.
///
/// This is unsafe because the PID isn't checked.
pub unsafe fn kill_gracefully(pid: u32) -> bool {
debug!(target: "lazymc", "Sending SIGTERM signal to {} to kill server", pid);
let result = libc::kill(pid as i32, libc::SIGTERM);
if result != 0 {
warn!(target: "lazymc", "Sending SIGTERM signal to server failed: {}", result);
}
result == 0
}

View File

@ -157,7 +157,11 @@ async fn connect_to_server_no_timeout(
// Select server address to use, add magic if Forge // Select server address to use, add magic if Forge
let server_addr = if config.server.forge { let server_addr = if config.server.forge {
format!("{}{}", config.server.address.ip(), forge::STATUS_MAGIC) format!(
"{}{}",
config.server.address.ip().to_string(),
forge::STATUS_MAGIC,
)
} else { } else {
config.server.address.ip().to_string() config.server.address.ip().to_string()
}; };

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.17.1";
/// 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 = 756;
/// 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")]
@ -14,7 +14,6 @@ use tokio::time;
use crate::config::{Config, Server as ConfigServer}; use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban::{BannedIp, BannedIps}; use crate::mc::ban::{BannedIp, BannedIps};
use crate::mc::whitelist::Whitelist;
use crate::os; use crate::os;
use crate::proto::packets::play::join_game::JoinGameData; use crate::proto::packets::play::join_game::JoinGameData;
@ -29,11 +28,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)]
@ -76,9 +73,6 @@ pub struct Server {
/// List of banned IPs. /// List of banned IPs.
banned_ips: RwLock<BannedIps>, banned_ips: RwLock<BannedIps>,
/// Whitelist if enabled.
whitelist: RwLock<Option<Whitelist>>,
/// Lock for exclusive RCON operations. /// Lock for exclusive RCON operations.
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
rcon_lock: Semaphore, rcon_lock: Semaphore,
@ -219,14 +213,9 @@ impl Server {
None => info!(target: "lazymc", "Starting server..."), None => info!(target: "lazymc", "Starting server..."),
} }
// Unfreeze server if it is frozen
#[cfg(unix)]
if config.server.freeze_process && unfreeze_server_signal(&config, &server).await {
return true;
}
// Spawn server in new task // Spawn server in new task
Self::spawn_server_task(config, server); Self::spawn_server_task(config, server);
true true
} }
@ -242,12 +231,6 @@ impl Server {
/// This will attempt to stop the server with all available methods. /// This will attempt to stop the server with all available methods.
#[allow(unused_variables)] #[allow(unused_variables)]
pub async fn stop(&self, config: &Config) -> bool { pub async fn stop(&self, config: &Config) -> bool {
// Try to freeze through signal
#[cfg(unix)]
if config.server.freeze_process && freeze_server_signal(config, self).await {
return true;
}
// Try to stop through RCON if started // Try to stop through RCON if started
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
if self.state() == State::Started && stop_server_rcon(config, self).await { if self.state() == State::Started && stop_server_rcon(config, self).await {
@ -363,18 +346,6 @@ impl Server {
futures::executor::block_on(async { self.is_banned_ip(ip).await }) futures::executor::block_on(async { self.is_banned_ip(ip).await })
} }
/// Check whether the given username is whitelisted.
///
/// Returns `true` if no whitelist is currently used.
pub async fn is_whitelisted(&self, username: &str) -> bool {
self.whitelist
.read()
.await
.as_ref()
.map(|w| w.is_whitelisted(username))
.unwrap_or(true)
}
/// Update the list of banned IPs. /// Update the list of banned IPs.
pub async fn set_banned_ips(&self, ips: BannedIps) { pub async fn set_banned_ips(&self, ips: BannedIps) {
*self.banned_ips.write().await = ips; *self.banned_ips.write().await = ips;
@ -384,16 +355,6 @@ impl Server {
pub fn set_banned_ips_blocking(&self, ips: BannedIps) { pub fn set_banned_ips_blocking(&self, ips: BannedIps) {
futures::executor::block_on(async { self.set_banned_ips(ips).await }) futures::executor::block_on(async { self.set_banned_ips(ips).await })
} }
/// Update the whitelist.
pub async fn set_whitelist(&self, whitelist: Option<Whitelist>) {
*self.whitelist.write().await = whitelist;
}
/// Update the whitelist.
pub fn set_whitelist_blocking(&self, whitelist: Option<Whitelist>) {
futures::executor::block_on(async { self.set_whitelist(whitelist).await })
}
} }
impl Default for Server { impl Default for Server {
@ -410,7 +371,6 @@ impl Default for Server {
keep_online_until: Default::default(), keep_online_until: Default::default(),
kill_at: Default::default(), kill_at: Default::default(),
banned_ips: Default::default(), banned_ips: Default::default(),
whitelist: Default::default(),
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
rcon_lock: Semaphore::new(1), rcon_lock: Semaphore::new(1),
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
@ -498,12 +458,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
} }
@ -603,11 +559,13 @@ async fn stop_server_signal(config: &Config, server: &Server) -> bool {
} }
}; };
// Send kill signal
if !crate::os::kill_gracefully(pid) { if !crate::os::kill_gracefully(pid) {
error!(target: "lazymc", "Failed to send stop signal to server process"); error!(target: "lazymc", "Failed to send stop signal to server process");
return false; return false;
} }
// Update from starting/started to stopping
server server
.update_state_from(Some(State::Starting), State::Stopping, config) .update_state_from(Some(State::Starting), State::Stopping, config)
.await; .await;
@ -617,59 +575,3 @@ async fn stop_server_signal(config: &Config, server: &Server) -> bool {
true true
} }
/// Freeze server by sending SIGSTOP signal.
///
/// Only available on Unix.
#[cfg(unix)]
async fn freeze_server_signal(config: &Config, server: &Server) -> bool {
// Grab PID
let pid = match *server.pid.lock().await {
Some(pid) => pid,
None => {
debug!(target: "lazymc", "Could not send freeze signal to server process, PID unknown");
return false;
}
};
if !os::freeze(pid) {
error!(target: "lazymc", "Failed to send freeze signal to server process.");
}
server
.update_state_from(Some(State::Starting), State::Stopped, config)
.await;
server
.update_state_from(Some(State::Started), State::Stopped, config)
.await;
true
}
/// Unfreeze server by sending SIGCONT signal.
///
/// Only available on Unix.
#[cfg(unix)]
async fn unfreeze_server_signal(config: &Config, server: &Server) -> bool {
// Grab PID
let pid = match *server.pid.lock().await {
Some(pid) => pid,
None => {
debug!(target: "lazymc", "Could not send unfreeze signal to server process, PID unknown");
return false;
}
};
if !os::unfreeze(pid) {
error!(target: "lazymc", "Failed to send unfreeze signal to server process.");
}
server
.update_state_from(Some(State::Stopping), State::Starting, config)
.await;
server
.update_state_from(Some(State::Stopped), State::Starting, config)
.await;
true
}

119
src/service/ban_reload.rs Normal file
View File

@ -0,0 +1,119 @@
use std::path::Path;
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban;
use crate::server::Server;
/// File debounce time.
const WATCH_DEBOUNCE: Duration = Duration::from_secs(2);
/// Service to reload banned IPs when its file changes.
pub fn service(config: Arc<Config>, server: Arc<Server>) {
// TODO: check what happens when file doesn't exist at first?
// Ensure we need to reload banned IPs
if !config.server.block_banned_ips && !config.server.drop_banned_ips {
return;
}
// Ensure server directory is set, it must exist
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);
return;
}
};
// Determine file path, ensure it exists
let path = dir.join(crate::mc::ban::FILE);
if !path.is_file() {
warn!(target: "lazymc", "Not blocking banned IPs, {} file does not exist", ban::FILE);
return;
}
// Load banned IPs once
match ban::load(&path) {
Ok(ips) => server.set_banned_ips_blocking(ips),
Err(err) => {
error!(target: "lazymc", "Failed to load banned IPs from {}: {}", ban::FILE, err);
}
}
// Show warning if 127.0.0.1 is banned
if server.is_banned_ip_blocking(&("127.0.0.1".parse().unwrap())) {
warn!(target: "lazymc", "Local address 127.0.0.1 IP banned, probably not what you want");
warn!(target: "lazymc", "Use '/pardon-ip 127.0.0.1' on the server to unban");
}
// Keep watching
while watch(&server, &path) {}
}
/// Watch the given file.
fn watch(server: &Server, path: &Path) -> bool {
// The file must exist
if !path.is_file() {
warn!(target: "lazymc", "File {} does not exist, not watching changes", ban::FILE);
return false;
}
// Create watcher for banned IPs file
let (tx, rx) = channel();
let mut watcher =
watcher(tx, WATCH_DEBOUNCE).expect("failed to create watcher for banned-ips.json");
if let Err(err) = watcher.watch(path, RecursiveMode::NonRecursive) {
error!(target: "lazymc", "An error occured while creating watcher for {}: {}", ban::FILE, err);
return true;
}
loop {
// Take next event
let event = rx.recv().unwrap();
// Decide whether to reload and rewatch
let (reload, rewatch) = match event {
// Reload on write
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::Write(_) => (true, false),
// Reload and rewatch on rename/remove
DebouncedEvent::NoticeRemove(_)
| DebouncedEvent::Remove(_)
| DebouncedEvent::Rename(_, _)
| DebouncedEvent::Rescan
| DebouncedEvent::Create(_) => {
trace!(target: "lazymc", "File banned-ips.json removed, trying to rewatch after 1 second");
thread::sleep(WATCH_DEBOUNCE);
(true, true)
}
// Ignore chmod changes
DebouncedEvent::Chmod(_) => (false, false),
// Rewatch on error
DebouncedEvent::Error(_, _) => (false, true),
};
// Reload banned IPs
if reload {
debug!(target: "lazymc", "Reloading list of banned IPs...");
match ban::load(path) {
Ok(ips) => server.set_banned_ips_blocking(ips),
Err(err) => {
error!(target: "lazymc", "Failed reload list of banned IPs from {}: {}", ban::FILE, err);
}
}
}
// Rewatch
if rewatch {
return true;
}
}
}

View File

@ -1,172 +0,0 @@
use std::path::Path;
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::time::Duration;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban::{self, BannedIps};
use crate::mc::{server_properties, whitelist};
use crate::server::Server;
/// File watcher debounce time.
const WATCH_DEBOUNCE: Duration = Duration::from_secs(2);
/// Service to watch server file changes.
pub fn service(config: Arc<Config>, server: Arc<Server>) {
// Ensure server directory is set, it must exist
let dir = match ConfigServer::server_directory(&config) {
Some(dir) if dir.is_dir() => dir,
_ => {
warn!(target: "lazymc", "Server directory doesn't exist, can't watch file changes to reload whitelist and banned IPs");
return;
}
};
// Keep watching
#[allow(clippy::blocks_in_conditions)]
while {
// Update all files once
reload_bans(&config, &server, &dir.join(ban::FILE));
reload_whitelist(&config, &server, &dir);
// Watch for changes, update accordingly
watch_server(&config, &server, &dir)
} {}
}
/// Watch server directory.
///
/// Returns `true` if we should watch again.
#[must_use]
fn watch_server(config: &Config, server: &Server, dir: &Path) -> bool {
// Directory must exist
if !dir.is_dir() {
error!(target: "lazymc", "Server directory does not exist at {} anymore, not watching changes", dir.display());
return false;
}
// Create watcher for directory
let (tx, rx) = channel();
let mut watcher =
watcher(tx, WATCH_DEBOUNCE).expect("failed to create watcher for banned-ips.json");
if let Err(err) = watcher.watch(dir, RecursiveMode::NonRecursive) {
error!(target: "lazymc", "An error occured while creating watcher for server files: {}", err);
return true;
}
// Handle change events
loop {
match rx.recv().unwrap() {
// Handle file updates
DebouncedEvent::Create(ref path)
| DebouncedEvent::Write(ref path)
| DebouncedEvent::Remove(ref path) => {
update(config, server, dir, path);
}
// Handle file updates on both paths for rename
DebouncedEvent::Rename(ref before_path, ref after_path) => {
update(config, server, dir, before_path);
update(config, server, dir, after_path);
}
// Ignore write/remove notices, will receive write/remove event later
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {}
// Ignore chmod changes
DebouncedEvent::Chmod(_) => {}
// Rewatch on rescan
DebouncedEvent::Rescan => {
debug!(target: "lazymc", "Rescanning server directory files due to file watching problem");
return true;
}
// Rewatch on error
DebouncedEvent::Error(err, _) => {
error!(target: "lazymc", "Error occurred while watching server directory for file changes: {}", err);
return true;
}
}
}
}
/// Process a file change on the given path.
///
/// Should be called both when created, changed or removed.
fn update(config: &Config, server: &Server, dir: &Path, path: &Path) {
// Update bans
if path.ends_with(ban::FILE) {
reload_bans(config, server, path);
}
// Update whitelist
if path.ends_with(whitelist::WHITELIST_FILE)
|| path.ends_with(whitelist::OPS_FILE)
|| path.ends_with(server_properties::FILE)
{
reload_whitelist(config, server, dir);
}
}
/// Reload banned IPs.
fn reload_bans(config: &Config, server: &Server, path: &Path) {
// Bans must be enabled
if !config.server.block_banned_ips && !config.server.drop_banned_ips {
return;
}
trace!(target: "lazymc", "Reloading banned IPs...");
// File must exist, clear file otherwise
if !path.is_file() {
debug!(target: "lazymc", "No banned IPs, {} does not exist", ban::FILE);
// warn!(target: "lazymc", "Not blocking banned IPs, {} file does not exist", ban::FILE);
server.set_banned_ips_blocking(BannedIps::default());
return;
}
// Load and update banned IPs
match ban::load(path) {
Ok(ips) => server.set_banned_ips_blocking(ips),
Err(err) => {
debug!(target: "lazymc", "Failed load banned IPs from {}, ignoring: {}", ban::FILE, err);
}
}
// Show warning if 127.0.0.1 is banned
if server.is_banned_ip_blocking(&("127.0.0.1".parse().unwrap())) {
warn!(target: "lazymc", "Local address 127.0.0.1 IP banned, probably not what you want");
warn!(target: "lazymc", "Use '/pardon-ip 127.0.0.1' on the server to unban");
}
}
/// Reload whitelisted users.
fn reload_whitelist(config: &Config, server: &Server, dir: &Path) {
// Whitelist must be enabled
if !config.server.wake_whitelist {
return;
}
// Must be enabled in server.properties
let enabled = server_properties::read_property(dir.join(server_properties::FILE), "white-list")
.map(|v| v.trim() == "true")
.unwrap_or(false);
if !enabled {
server.set_whitelist_blocking(None);
debug!(target: "lazymc", "Not using whitelist, not enabled in {}", server_properties::FILE);
return;
}
trace!(target: "lazymc", "Reloading whitelisted users...");
// Load and update whitelisted users
match whitelist::load_dir(dir) {
Ok(whitelist) => server.set_whitelist_blocking(Some(whitelist)),
Err(err) => {
debug!(target: "lazymc", "Failed load whitelist from {}, ignoring: {}", dir.display(), err);
}
}
}

View File

@ -1,4 +1,4 @@
pub mod file_watcher; pub mod ban_reload;
pub mod monitor; pub mod monitor;
pub mod probe; pub mod probe;
pub mod server; pub mod server;

View File

@ -59,7 +59,7 @@ pub async fn service(config: Arc<Config>) -> Result<(), ()> {
tokio::spawn(service::probe::service(config.clone(), server.clone())); tokio::spawn(service::probe::service(config.clone(), server.clone()));
tokio::task::spawn_blocking({ tokio::task::spawn_blocking({
let (config, server) = (config.clone(), server.clone()); let (config, server) = (config.clone(), server.clone());
|| service::file_watcher::service(config, server) || service::ban_reload::service(config, server)
}); });
// Route all incomming connections // Route all incomming connections
@ -84,7 +84,7 @@ fn route(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) {
// Check ban state, just drop connection if enabled // Check ban state, just drop connection if enabled
let banned = server.is_banned_ip_blocking(&peer.ip()); let banned = server.is_banned_ip_blocking(&peer.ip());
if banned && config.server.drop_banned_ips { if config.server.drop_banned_ips {
info!(target: "lazymc", "Connection from banned IP {}, dropping", peer.ip()); info!(target: "lazymc", "Connection from banned IP {}, dropping", peer.ip());
return; return;
} }

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;
@ -26,9 +27,6 @@ const BAN_MESSAGE_PREFIX: &str = "Your IP address is banned from this server.\nR
/// Default ban reason if unknown. /// Default ban reason if unknown.
const DEFAULT_BAN_REASON: &str = "Banned by an operator."; const DEFAULT_BAN_REASON: &str = "Banned by an operator.";
/// The not-whitelisted kick message.
const WHITELIST_MESSAGE: &str = "You are not white-listed on this server!";
/// Server icon file path. /// Server icon file path.
const SERVER_ICON_FILE: &str = "server-icon.png"; const SERVER_ICON_FILE: &str = "server-icon.png";
@ -151,17 +149,12 @@ pub async fn serve(
info!(target: "lazymc", "Login from banned IP {}, disconnecting", client.peer.ip()); info!(target: "lazymc", "Login from banned IP {}, disconnecting", client.peer.ip());
DEFAULT_BAN_REASON.to_string() DEFAULT_BAN_REASON.to_string()
}; };
action::kick(&client, &format!("{BAN_MESSAGE_PREFIX}{msg}"), &mut writer) action::kick(
.await?; &client,
break; &format!("{}{}", BAN_MESSAGE_PREFIX, msg),
} &mut writer,
} )
.await?;
// Kick if client is not whitelisted to wake server
if let Some(ref username) = username {
if !server.is_whitelisted(username).await {
info!(target: "lazymc", "User '{}' tried to wake server but is not whitelisted, disconnecting", username);
action::kick(&client, WHITELIST_MESSAGE, &mut writer).await?;
break; break;
} }
} }
@ -231,11 +224,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 +273,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)
} }

View File

@ -10,7 +10,7 @@ use crate::util::error::{quit_error, ErrorHints};
/// excluding the `:` suffix. /// excluding the `:` suffix.
pub fn prompt(msg: &str) -> String { pub fn prompt(msg: &str) -> String {
// Show the prompt // Show the prompt
eprint!("{msg}: "); eprint!("{}: ", msg);
let _ = stderr().flush(); let _ = stderr().flush();
// Get the input // Get the input
@ -49,7 +49,7 @@ pub fn prompt_yes(msg: &str, def: Option<bool>) -> bool {
); );
// Get the user input // Get the user input
let answer = prompt(&format!("{msg} {options}")); let answer = prompt(&format!("{} {}", msg, options));
// Assume the default if the answer is empty // Assume the default if the answer is empty
if answer.is_empty() { if answer.is_empty() {

View File

@ -16,7 +16,7 @@ pub fn print_error(err: anyhow::Error) {
// Report each printable error, count them // Report each printable error, count them
let count = err let count = err
.chain() .chain()
.map(|err| err.to_string()) .map(|err| format!("{}", err))
.filter(|err| !err.is_empty()) .filter(|err| !err.is_empty())
.enumerate() .enumerate()
.map(|(i, err)| { .map(|(i, err)| {
@ -126,7 +126,7 @@ impl ErrorHints {
if self.config_generate { if self.config_generate {
eprintln!( eprintln!(
"Use '{}' to generate a new config file", "Use '{}' to generate a new config file",
highlight(&format!("{bin} config generate")) highlight(&format!("{} config generate", bin))
); );
} }
if self.config { if self.config {
@ -138,7 +138,7 @@ impl ErrorHints {
if self.config_test { if self.config_test {
eprintln!( eprintln!(
"Use '{}' to test a config file", "Use '{}' to test a config file",
highlight(&format!("{bin} config test -c FILE")) highlight(&format!("{} config test -c FILE", bin))
); );
} }
if self.verbose { if self.verbose {