mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 12:50:23 -07:00
Compare commits
79 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d058164aa6 | ||
|
8ed68b1ddf | ||
|
0eec1e0b55 | ||
|
485941cf81 | ||
|
a1008ad2a7 | ||
|
b22d32b951 | ||
|
18f26b00cb | ||
|
86428f4501 | ||
|
c311313ecb | ||
|
6e6d098cf1 | ||
|
efb047114e | ||
|
37fdb9c12a | ||
|
e7a3db19aa | ||
|
ca4753673d | ||
|
0124aa723d | ||
|
afbc54758c | ||
|
e54025f02f | ||
|
023e46fe64 | ||
|
6622962d5d | ||
|
da60287e10 | ||
|
eb5ee7defd | ||
|
fdeb7594c2 | ||
|
5c7e17b0ae | ||
|
be74e053f4 | ||
|
eb2cf1219e | ||
|
72d58f051f | ||
|
6c3129a8b0 | ||
|
cc2061ad7d | ||
|
342a55471c | ||
|
982a604d34 | ||
|
6b463ac8c8 | ||
|
c7caebe6a8 | ||
|
aa1a74682e | ||
|
835ca62c06 | ||
|
b609f86bde | ||
|
4b1857f48d | ||
|
bc7bd908f6 | ||
|
a3fef88eac | ||
|
57117b29f3 | ||
|
5ef7c54ec6 | ||
|
5f13132c57 | ||
|
226215479c | ||
|
f4870c66fa | ||
|
c415420eae | ||
|
c6db4d7c3f | ||
|
39feb0bdc2 | ||
|
35fff7168b | ||
|
e5e5947a16 | ||
|
2e6551b009 | ||
|
2c00dba5e8 | ||
|
0e4d18e9f6 | ||
|
026aa58b5d | ||
|
0ac9a07c93 | ||
|
540137b93e | ||
|
05dfd19d80 | ||
|
f02217abd3 | ||
|
3185ca855c | ||
|
2c43446ed0 | ||
|
f7fe00aa50 | ||
|
f2087792b4 | ||
|
d46f8375c7 | ||
|
2d8173aba8 | ||
|
bd9f81f1f0 | ||
|
b561351a2a | ||
|
d3cb880dd0 | ||
|
716cd48eac | ||
|
c6f860f013 | ||
|
8d4ace60a6 | ||
|
09c6d1d996 | ||
|
dca2f8eb5c | ||
|
1f6665a90f | ||
|
0c77a18d96 | ||
|
0c69752fec | ||
|
6e8ff5b1b3 | ||
|
fba581d4bd | ||
|
d7b601d6e3 | ||
|
4d76058472 | ||
|
5b5a2bf8ae | ||
|
c477e45553 |
135
.gitlab-ci.yml
135
.gitlab-ci.yml
@ -9,18 +9,16 @@ 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
|
||||||
@ -29,54 +27,39 @@ before_script:
|
|||||||
.before_script-windows: &before_script-windows
|
.before_script-windows: &before_script-windows
|
||||||
before_script:
|
before_script:
|
||||||
# Install scoop
|
# Install scoop
|
||||||
- Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
- iex "& {$(irm get.scoop.sh)} -RunAsAdmin"
|
||||||
|
|
||||||
# Install Rust
|
# Install Rust
|
||||||
- scoop install rustup
|
- scoop install rustup gcc
|
||||||
|
- 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:
|
check-stable:
|
||||||
<<: *check-base
|
<<: *check-base
|
||||||
check-macos:
|
check-msrv:
|
||||||
tags:
|
<<: *check-base
|
||||||
- macos
|
variables:
|
||||||
|
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
|
||||||
@ -90,11 +73,12 @@ 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
|
||||||
@ -113,11 +97,12 @@ 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
|
||||||
@ -148,11 +133,12 @@ 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
|
||||||
@ -179,36 +165,14 @@ 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
|
||||||
@ -225,29 +189,28 @@ 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: []
|
||||||
cache: {}
|
# <<: *before_script-windows
|
||||||
<<: *before_script-windows
|
# script:
|
||||||
script:
|
# - cargo test --locked --verbose
|
||||||
- cargo test --locked --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 rcon,lobby --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:
|
||||||
@ -258,16 +221,14 @@ 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(\d+\.)*\d+$/
|
- /^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-]+)*))?$/
|
||||||
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:
|
||||||
@ -284,8 +245,6 @@ 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}
|
||||||
|
|
||||||
@ -294,13 +253,12 @@ 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(\d+\.)*\d+$/
|
- /^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-]+)*))?$/
|
||||||
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:
|
||||||
@ -315,20 +273,18 @@ 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(\d+\.)*\d+$/
|
- /^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-]+)*))?$/
|
||||||
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:
|
||||||
@ -347,5 +303,4 @@ 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
|
||||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@ -1,5 +1,39 @@
|
|||||||
# 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
|
||||||
|
1444
Cargo.lock
generated
1444
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
71
Cargo.toml
71
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lazymc"
|
name = "lazymc"
|
||||||
version = "0.2.5"
|
version = "0.2.11"
|
||||||
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 = [
|
categories = ["command-line-interface", "games"]
|
||||||
"command-line-interface",
|
exclude = ["/.github", "/contrib"]
|
||||||
"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", "async-std"]
|
rcon = ["rust_rcon"]
|
||||||
|
|
||||||
# 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,20 +32,30 @@ lobby = ["md-5", "uuid"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
base64 = "0.13"
|
base64 = "0.22"
|
||||||
bytes = "1.1"
|
bytes = "1.1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "3.0.0-beta.5", default-features = false, features = [ "std", "cargo", "color", "env", "suggestions", "unicode" ]}
|
clap = { version = "4.0.32", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"help",
|
||||||
|
"suggestions",
|
||||||
|
"color",
|
||||||
|
"usage",
|
||||||
|
"cargo",
|
||||||
|
"env",
|
||||||
|
"unicode",
|
||||||
|
] }
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
derive_builder = "0.10"
|
derive_builder = "0.20"
|
||||||
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 = "edfdf87" }
|
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "4f93bb3" }
|
||||||
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.4"
|
pretty_env_logger = "0.5"
|
||||||
proxy-protocol = "0.5"
|
proxy-protocol = "0.5"
|
||||||
quartz_nbt = "0.2"
|
quartz_nbt = "0.2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
@ -53,20 +63,35 @@ 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 = ["rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal", "sync", "fs"] }
|
tokio = { version = "1", default-features = false, features = [
|
||||||
toml = "0.5"
|
"rt-multi-thread",
|
||||||
version-compare = "0.1"
|
"io-util",
|
||||||
|
"net",
|
||||||
|
"macros",
|
||||||
|
"time",
|
||||||
|
"process",
|
||||||
|
"signal",
|
||||||
|
"sync",
|
||||||
|
"fs",
|
||||||
|
] }
|
||||||
|
toml = "0.8"
|
||||||
|
version-compare = "0.2"
|
||||||
|
|
||||||
# Feature: rcon
|
# Feature: rcon
|
||||||
rust_rcon = { package = "rcon", version = "0.5.2", optional = true }
|
rust_rcon = { package = "rcon", version = "0.6", default-features = false, features = ["rt-tokio"], optional = true }
|
||||||
async-std = { version = "1.9.0", default-features = false, optional = true }
|
|
||||||
|
|
||||||
# Feature: lobby
|
# Feature: lobby
|
||||||
md-5 = { version = "0.9", optional = true }
|
md-5 = { version = "0.10", optional = true }
|
||||||
uuid = { version = "0.7", optional = true, features = ["v3"] }
|
uuid = { version = "1.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 = ["winuser", "processthreadsapi", "handleapi", "ntdef", "minwindef"] }
|
winapi = { version = "0.3", features = [
|
||||||
|
"winuser",
|
||||||
|
"processthreadsapi",
|
||||||
|
"handleapi",
|
||||||
|
"ntdef",
|
||||||
|
"minwindef",
|
||||||
|
] }
|
||||||
|
@ -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.7.2+, supports modded (e.g. Forge, FTB)
|
- Supports Minecraft Java Edition 1.20.3+
|
||||||
- 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,6 +55,10 @@ 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._
|
||||||
@ -67,7 +71,8 @@ _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.
|
release][latest-release] page. On macOS you must [compile from
|
||||||
|
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:
|
||||||
|
7
build.rs
Normal file
7
build.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fn main() {
|
||||||
|
// rcon is required on Windows
|
||||||
|
#[cfg(all(windows, not(feature = "rcon")))]
|
||||||
|
{
|
||||||
|
compile_error!("required feature missing on Windows: rcon");
|
||||||
|
}
|
||||||
|
}
|
1
clippy.toml
Normal file
1
clippy.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
msrv = "1.64.0"
|
@ -62,15 +62,18 @@ 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 and unstable.
|
# This is highly experimental, incomplete 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.
|
||||||
#
|
#
|
||||||
# - Only works with offline mode
|
# - Server must be in offline mode
|
||||||
# - Only works with vanilla Minecraft clients, does not work with modded
|
# - Server must use Minecraft version 1.16.3 to 1.17.1 (tested with 1.17.1)
|
||||||
# - Only tested with Minecraft 1.17.1
|
# - Server must use vanilla Minecraft
|
||||||
|
# - 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
|
||||||
|
@ -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.17.1"
|
version = "1.19.3"
|
||||||
protocol = 756
|
protocol = 761
|
||||||
|
|
||||||
# -- snip --
|
# -- snip --
|
||||||
```
|
```
|
||||||
|
@ -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
|
||||||
|
@ -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.17.1"
|
#version = "1.20.3"
|
||||||
#protocol = 756
|
#protocol = 765
|
||||||
|
|
||||||
[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,6 +33,10 @@ 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
|
||||||
|
|
||||||
@ -50,6 +54,9 @@ 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
|
||||||
|
|
||||||
@ -180,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.5"
|
version = "0.2.11"
|
||||||
|
@ -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.value_of("config").unwrap());
|
let mut path = PathBuf::from(matches.get_one::<String>("config").unwrap());
|
||||||
if let Ok(p) = path.canonicalize() {
|
if let Ok(p) = path.canonicalize() {
|
||||||
path = p;
|
path = p;
|
||||||
}
|
}
|
||||||
|
@ -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.value_of("config").unwrap());
|
let mut path = PathBuf::from(matches.get_one::<String>("config").unwrap());
|
||||||
if let Ok(p) = path.canonicalize() {
|
if let Ok(p) = path.canonicalize() {
|
||||||
path = p;
|
path = p;
|
||||||
}
|
}
|
||||||
|
25
src/cli.rs
25
src/cli.rs
@ -1,23 +1,28 @@
|
|||||||
use clap::{App, AppSettings, Arg};
|
use clap::{Arg, Command};
|
||||||
|
|
||||||
/// The clap app for CLI argument parsing.
|
/// The clap app for CLI argument parsing.
|
||||||
pub fn app() -> App<'static> {
|
pub fn app() -> Command {
|
||||||
App::new(crate_name!())
|
Command::new(crate_name!())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.author(crate_authors!())
|
.author(crate_authors!())
|
||||||
.about(crate_description!())
|
.about(crate_description!())
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("start")
|
Command::new("start")
|
||||||
.alias("run")
|
.alias("run")
|
||||||
.about("Start lazymc and server (default)"),
|
.about("Start lazymc and server (default)"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
App::new("config")
|
Command::new("config")
|
||||||
.alias("cfg")
|
.alias("cfg")
|
||||||
.about("Config actions")
|
.about("Config actions")
|
||||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
.arg_required_else_help(true)
|
||||||
.subcommand(App::new("generate").alias("gen").about("Generate config"))
|
.subcommand_required(true)
|
||||||
.subcommand(App::new("test").about("Test config")),
|
.subcommand(
|
||||||
|
Command::new("generate")
|
||||||
|
.alias("gen")
|
||||||
|
.about("Generate config"),
|
||||||
|
)
|
||||||
|
.subcommand(Command::new("test").about("Test config")),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("config")
|
Arg::new("config")
|
||||||
@ -27,7 +32,7 @@ pub fn app() -> App<'static> {
|
|||||||
.global(true)
|
.global(true)
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.default_value(crate::config::CONFIG_FILE)
|
.default_value(crate::config::CONFIG_FILE)
|
||||||
.about("Use config file")
|
.help("Use config file")
|
||||||
.takes_value(true),
|
.num_args(1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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.1";
|
const CONFIG_VERSION: &str = "0.2.8";
|
||||||
|
|
||||||
/// 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.value_of("config").unwrap());
|
let mut path = PathBuf::from(matches.get_one::<String>("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(&path)?;
|
let data = fs::read_to_string(&path)?;
|
||||||
let mut config: Config = toml::from_slice(&data)?;
|
let mut config: Config = toml::from_str(&data).map_err(io::Error::other)?;
|
||||||
|
|
||||||
// Show warning if config version is problematic
|
// Show warning if config version is problematic
|
||||||
match &config.config.version {
|
match &config.config.version {
|
||||||
@ -174,6 +174,11 @@ 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,
|
||||||
@ -198,6 +203,10 @@ 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,
|
||||||
@ -474,19 +483,13 @@ impl Default for Advanced {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Config configuration.
|
/// Config configuration.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Default)]
|
||||||
#[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())
|
||||||
}
|
}
|
||||||
|
@ -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(read) if read == 0 => return Ok(()),
|
Ok(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) => {
|
||||||
|
@ -29,7 +29,7 @@ pub(crate) mod util;
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use clap::App;
|
use clap::Command;
|
||||||
|
|
||||||
// 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: App) -> Result<(), ()> {
|
fn invoke_action(app: Command) -> Result<(), ()> {
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
|
|
||||||
// Config operations
|
// Config operations
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
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.
|
||||||
@ -12,7 +14,11 @@ 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!("{}{}", "data:image/png;base64,", base64::encode(data))
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
"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.
|
||||||
|
@ -7,6 +7,7 @@ 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)]
|
||||||
|
@ -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,
|
con: Connection<TcpStream>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
.connect_stream(stream, pass)
|
.handshake(stream, pass)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Self { con })
|
Ok(Self { con })
|
||||||
|
@ -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,3 +145,37 @@ 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())
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
107
src/mc/whitelist.rs
Normal file
107
src/mc/whitelist.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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())
|
||||||
|
}
|
@ -3,11 +3,10 @@ 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_14_4::status::{
|
use minecraft_protocol::version::v1_20_3::status::{
|
||||||
PingRequest, PingResponse, StatusRequest, StatusResponse,
|
PingRequest, PingResponse, ServerStatus, StatusRequest, StatusResponse,
|
||||||
};
|
};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
@ -57,13 +56,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::montior", "Server has been idle, sleeping...");
|
info!(target: "lazymc::monitor", "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::montior", "Force killing server, took too long to start or stop");
|
error!(target: "lazymc::monitor", "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");
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
#[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)]
|
||||||
unsafe {
|
return unix_signal(pid, Signal::SIGKILL);
|
||||||
return unix::force_kill(pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -22,20 +24,57 @@ 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)]
|
||||||
unsafe {
|
return unix_signal(pid, Signal::SIGTERM);
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
/// 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
|
|
||||||
}
|
|
@ -157,11 +157,7 @@ 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!(
|
format!("{}{}", config.server.address.ip(), forge::STATUS_MAGIC)
|
||||||
"{}{}",
|
|
||||||
config.server.address.ip().to_string(),
|
|
||||||
forge::STATUS_MAGIC,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
config.server.address.ip().to_string()
|
config.server.address.ip().to_string()
|
||||||
};
|
};
|
||||||
|
@ -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.17.1";
|
pub const PROTO_DEFAULT_VERSION: &str = "1.20.3";
|
||||||
|
|
||||||
/// Default minecraft protocol version.
|
/// Default minecraft protocol version.
|
||||||
///
|
///
|
||||||
@ -17,7 +17,7 @@ pub const PROTO_DEFAULT_VERSION: &str = "1.17.1";
|
|||||||
/// 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 = 756;
|
pub const PROTO_DEFAULT_PROTOCOL: u32 = 765;
|
||||||
|
|
||||||
/// Compression threshold to use.
|
/// Compression threshold to use.
|
||||||
// TODO: read this from server.properties instead
|
// TODO: read this from server.properties instead
|
||||||
|
@ -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.fandom.com/wiki/Commands/title#Detail
|
// Defaults: https://minecraft.wiki/w/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.fandom.com/wiki/Commands/title#Detail
|
// Defaults: https://minecraft.wiki/w/Commands/title#Detail
|
||||||
SetTitleTimes {
|
SetTitleTimes {
|
||||||
fade_in: 10,
|
fade_in: 10,
|
||||||
stay: 70,
|
stay: 70,
|
||||||
|
116
src/server.rs
116
src/server.rs
@ -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::data::server_status::ServerStatus;
|
use minecraft_protocol::version::v1_20_3::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,6 +14,7 @@ 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;
|
||||||
|
|
||||||
@ -28,9 +29,11 @@ const SERVER_QUIT_COOLDOWN: Duration = Duration::from_millis(2500);
|
|||||||
#[cfg(feature = "rcon")]
|
#[cfg(feature = "rcon")]
|
||||||
const RCON_COOLDOWN: Duration = Duration::from_secs(15);
|
const RCON_COOLDOWN: Duration = Duration::from_secs(15);
|
||||||
|
|
||||||
/// Exit code when SIGTERM is received on Unix.
|
/// Exit codes that are allowed.
|
||||||
#[cfg(unix)]
|
///
|
||||||
const UNIX_EXIT_SIGTERM: i32 = 130;
|
/// - 143: https://github.com/timvisee/lazymc/issues/26#issuecomment-1435670029
|
||||||
|
/// - 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)]
|
||||||
@ -73,6 +76,9 @@ 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,
|
||||||
@ -213,9 +219,14 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +242,12 @@ 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 {
|
||||||
@ -346,6 +363,18 @@ 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;
|
||||||
@ -355,6 +384,16 @@ 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 {
|
||||||
@ -371,6 +410,7 @@ 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")]
|
||||||
@ -458,8 +498,12 @@ pub async fn invoke_server_cmd(
|
|||||||
debug!(target: "lazymc", "Server process stopped successfully ({})", status);
|
debug!(target: "lazymc", "Server process stopped successfully ({})", status);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
Ok(status)
|
||||||
Ok(status) if status.code() == Some(UNIX_EXIT_SIGTERM) => {
|
if status
|
||||||
|
.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
|
||||||
}
|
}
|
||||||
@ -559,13 +603,11 @@ 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;
|
||||||
@ -575,3 +617,59 @@ 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
|
||||||
|
}
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
172
src/service/file_watcher.rs
Normal file
172
src/service/file_watcher.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
pub mod ban_reload;
|
pub mod file_watcher;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
pub mod probe;
|
pub mod probe;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
@ -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::ban_reload::service(config, server)
|
|| service::file_watcher::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 config.server.drop_banned_ips {
|
if banned && 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;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use minecraft_protocol::data::chat::{Message, Payload};
|
use minecraft_protocol::data::server_status::{OnlinePlayers, ServerVersion};
|
||||||
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_14_4::status::StatusResponse;
|
use minecraft_protocol::version::v1_20_3::status::{ServerStatus, StatusResponse};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
@ -27,6 +26,9 @@ 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";
|
||||||
|
|
||||||
@ -149,12 +151,17 @@ 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(
|
action::kick(&client, &format!("{BAN_MESSAGE_PREFIX}{msg}"), &mut writer)
|
||||||
&client,
|
.await?;
|
||||||
&format!("{}{}", BAN_MESSAGE_PREFIX, msg),
|
break;
|
||||||
&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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,11 +231,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 {
|
||||||
Message::new(Payload::text(match server_state {
|
match server_state {
|
||||||
server::State::Stopped | server::State::Started => &config.motd.sleeping,
|
server::State::Stopped | server::State::Started => config.motd.sleeping.clone(),
|
||||||
server::State::Starting => &config.motd.starting,
|
server::State::Starting => config.motd.starting.clone(),
|
||||||
server::State::Stopping => &config.motd.stopping,
|
server::State::Stopping => config.motd.stopping.clone(),
|
||||||
}))
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -273,15 +280,10 @@ async fn server_favicon(config: &Config) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read icon data
|
// Read icon data
|
||||||
let data = match fs::read(path).await.map_err(|err| {
|
let data = fs::read(path).await.unwrap_or_else(|err| {
|
||||||
error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
|
error!(target: "lazymc::status", "Failed to read favicon from {}, using default: {err}", SERVER_ICON_FILE);
|
||||||
}) {
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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| format!("{}", err))
|
.map(|err| err.to_string())
|
||||||
.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!("{} config generate", bin))
|
highlight(&format!("{bin} config generate"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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!("{} config test -c FILE", bin))
|
highlight(&format!("{bin} config test -c FILE"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.verbose {
|
if self.verbose {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user