Compare commits
15 Commits
e2dd5b6c0b
...
refactor
| Author | SHA1 | Date | |
|---|---|---|---|
| ea16f34c73 | |||
| 6dea63f818 | |||
| 7782e71990 | |||
| 7e7bfbc576 | |||
| e2cbd16dc8 | |||
| 668870b92e | |||
| 6b2757a52f | |||
| f3c7172178 | |||
|
|
a1f72ba842 | ||
| ae52f1113f | |||
| 9f0c253d30 | |||
|
|
454776fcf2 | ||
| 84e79bd179 | |||
| 9c4c23f881 | |||
| 2169e95423 |
345
Cargo.lock
generated
345
Cargo.lock
generated
@@ -2,35 +2,30 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
@@ -38,30 +33,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
@@ -70,38 +67,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "mineguard"
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "mgrewrite"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
@@ -120,6 +125,67 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.7"
|
||||
@@ -129,22 +195,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
@@ -187,32 +237,7 @@ dependencies = [
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -223,12 +248,8 @@ checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hashbrown",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -238,27 +259,84 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -269,66 +347,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
35
Cargo.toml
35
Cargo.toml
@@ -1,29 +1,26 @@
|
||||
[package]
|
||||
name = "mineguard"
|
||||
name = "mgrewrite"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Opinionated Minecraft server supervisor engine built primarily for RustyMine"
|
||||
readme = "README.md"
|
||||
homepage = "https://mineguard.h3cx.dev"
|
||||
repository = "https://github.com/H3ct0r55/MineGuard"
|
||||
license = "MIT"
|
||||
# publish set to false during development
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["core", "events", "mc-vanilla"]
|
||||
# Core runtime requirements for the currently implemented functionality.
|
||||
core = ["dep:thiserror", "dep:tokio", "dep:tokio-stream", "dep:tokio-util"]
|
||||
# Placeholder for upcoming event-driven functionality.
|
||||
default = ["core", "mc-vanilla"]
|
||||
|
||||
core = ["dep:serde", "dep:serde_json", "dep:uuid", "dep:tokio", "dep:tokio-util"]
|
||||
|
||||
version-custom = []
|
||||
|
||||
events = []
|
||||
|
||||
mc-vanilla = []
|
||||
# Add new feature groups here; attach their optional dependencies to the relevant feature list.
|
||||
|
||||
mc-paper = []
|
||||
tokio = ["dep:tokio"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = { version = "2.0.17", optional = true }
|
||||
# Core async runtime and utilities
|
||||
# Add new feature-specific optional dependencies alongside the relevant feature entry above.
|
||||
tokio = { version = "1.48.0", features = ["process", "rt-multi-thread", "macros", "io-std", "io-util"], optional = true }
|
||||
tokio-stream = { version = "0.1.17", features = ["full", "io-util", "signal", "tokio-util"], optional = true }
|
||||
tokio-util = { version = "0.7.17", features = ["full"], optional = true }
|
||||
serde = { version = "1.0.228", features = ["derive"], optional = true }
|
||||
serde_json = {version = "1.0.145", optional = true}
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.48.0", features = ["fs", "io-std", "io-util", "process", "rt-multi-thread"], optional = true }
|
||||
tokio-util ={version = "0.7.17", optional = true}
|
||||
uuid = { version = "1.19.0", features = ["serde", "v4"], optional = true }
|
||||
|
||||
20
README.md
20
README.md
@@ -1,13 +1,21 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="assets/banner-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="assets/banner-light.png">
|
||||
<img src="assets/banner-light.png" alt="Project Banner">
|
||||
</picture>
|
||||
<img src="assets/banner.svg" alt="Project Banner">
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> MineGuard is currently in active development and breaking updates can be merged to the main branch any time
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# MineGuard
|
||||
Rust based Minecraft server lifecycle controller and interface
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<a href="./LICENSE">MIT License</a> • H3cx
|
||||
<a href="./LICENSE">MIT License</a> • h3cx
|
||||
|
||||
Built on coffee, late nights, and a fully up-to-date Arch install (btw).
|
||||
</div>
|
||||
|
||||
7
assets/banner.svg
Normal file
7
assets/banner.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 960 288" version="1.1" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><style>
|
||||
:root { color-scheme: light dark; }
|
||||
#Candi2 { fill: #000000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#Candi2 { fill: #ffffff; }
|
||||
}
|
||||
</style><rect id="Artboard1" x="0" y="0" width="960" height="288" style="fill:none;" /><path id="Candi2" d="M572.408,105.247l-0,50.499c-0,27.084 -58.055,74.003 -92.408,84.254c-34.353,-10.25 -92.408,-57.17 -92.408,-84.254l-0,-107.746l38.689,0l45.705,45.705l45.705,-45.705l54.715,0l-0,43.383l-27.358,0l0,-16.026l-16.026,-0l-57.037,57.037l-57.037,-57.037l0,79.563c0.096,2.885 9.718,14.902 14.778,20.045c14.344,14.58 34.069,28.878 50.272,35.919c16.203,-7.041 35.928,-21.339 50.272,-35.919c5.06,-5.143 14.683,-17.16 14.778,-20.045l0,-22.526l-45.705,0l27.147,-27.147l45.915,0Z" /></svg>
|
||||
|
After Width: | Height: | Size: 941 B |
26
src/config/config.rs
Normal file
26
src/config/config.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::version::{MinecraftType, MinecraftVersion};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerConfig {
|
||||
pub uuid: Uuid,
|
||||
pub core_path: PathBuf,
|
||||
pub jar_path: PathBuf,
|
||||
pub mc_version: MinecraftVersion,
|
||||
pub mc_type: MinecraftType,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
uuid: Uuid::new_v4(),
|
||||
core_path: PathBuf::new(),
|
||||
jar_path: PathBuf::new(),
|
||||
mc_version: MinecraftVersion::Unknown,
|
||||
mc_type: MinecraftType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,2 @@
|
||||
pub mod stream;
|
||||
pub mod config;
|
||||
pub mod version;
|
||||
|
||||
pub use stream::{LogMeta, StreamLine, StreamSource};
|
||||
pub use version::{MinecraftType, MinecraftVersion, Snapshot, Version};
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
use crate::error::ParserError;
|
||||
|
||||
/// Identifies which process stream produced a line of output.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum StreamSource {
|
||||
Stdout,
|
||||
Stderr,
|
||||
#[cfg(feature = "events")]
|
||||
Event,
|
||||
}
|
||||
|
||||
/// Captures a single line of process output along with its origin stream.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StreamLine {
|
||||
line: String,
|
||||
source: StreamSource,
|
||||
}
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
pub struct InstanceEvent {}
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
pub enum Events {}
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
pub struct LogMeta {
|
||||
time: String,
|
||||
thread: String,
|
||||
level: LogLevel,
|
||||
msg: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
pub enum LogLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
impl LogMeta {
|
||||
pub fn new<S: Into<String>>(line: S) -> Result<Option<Self>, ParserError> {
|
||||
let line: String = line.into();
|
||||
let line = line.trim();
|
||||
|
||||
if !line.starts_with('[') {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let time_end = match line.find(']') {
|
||||
Some(i) => i,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let time = line[1..time_end].to_string();
|
||||
|
||||
let meta_start = match line[time_end + 1..].find('[') {
|
||||
Some(j) => time_end + 1 + j,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let msg_sep = match line[meta_start..].find("]: ") {
|
||||
Some(k) => meta_start + k,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let meta = &line[(meta_start + 1)..msg_sep]; // inside the brackets
|
||||
let msg = line[(msg_sep + 3)..].to_string(); // after "]: "
|
||||
|
||||
let mut thread_level = meta.splitn(2, '/');
|
||||
let thread = thread_level
|
||||
.next()
|
||||
.ok_or(ParserError::ParserError)?
|
||||
.to_string();
|
||||
let level_str = thread_level
|
||||
.next()
|
||||
.ok_or(ParserError::ParserError)?
|
||||
.trim_end_matches(']'); // just in case
|
||||
|
||||
let level = match level_str {
|
||||
"INFO" => LogLevel::Info,
|
||||
"WARN" => LogLevel::Warn,
|
||||
"ERROR" => LogLevel::Error,
|
||||
_ => LogLevel::Other,
|
||||
};
|
||||
|
||||
Ok(Some(LogMeta {
|
||||
time,
|
||||
thread,
|
||||
level,
|
||||
msg,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
impl Display for LogMeta {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let line = format!(
|
||||
"Time: {}\nThread: {}\nLevel: {}\nMessage: {}",
|
||||
self.time, self.thread, self.level, self.msg
|
||||
);
|
||||
|
||||
write!(f, "{}", line)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
impl Display for LogLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
LogLevel::Info => write!(f, "INFO"),
|
||||
LogLevel::Warn => write!(f, "WARN"),
|
||||
LogLevel::Error => write!(f, "ERROR"),
|
||||
LogLevel::Other => write!(f, "OTHER"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamLine {
|
||||
pub fn new<S: Into<String>>(line: S, source: StreamSource) -> Self {
|
||||
Self {
|
||||
line: line.into(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
||||
Self {
|
||||
line: line.into(),
|
||||
source: StreamSource::Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stderr<S: Into<String>>(line: S) -> Self {
|
||||
Self {
|
||||
line: line.into(),
|
||||
source: StreamSource::Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn msg(&self) -> String {
|
||||
self.line.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StreamLine {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.line)
|
||||
}
|
||||
}
|
||||
@@ -1,130 +1,23 @@
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::error::VersionError;
|
||||
|
||||
/// Identifies the type of Minecraft distribution supported by the configuration.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MinecraftType {
|
||||
Vanilla,
|
||||
}
|
||||
|
||||
/// Semantic release version parsed from strings like `1.20.4`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
major: u32,
|
||||
minor: u32,
|
||||
patch: u32,
|
||||
}
|
||||
|
||||
/// Snapshot version parsed from strings like `23w13b`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Snapshot {
|
||||
pub year: u32,
|
||||
pub week: u32,
|
||||
pub build: char,
|
||||
}
|
||||
|
||||
/// Enum covering both release and snapshot Minecraft version formats.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MinecraftVersion {
|
||||
Unknown,
|
||||
Release(Version),
|
||||
Snapshot(Snapshot),
|
||||
#[cfg(feature = "version-custom")]
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Snapshot {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}w{:02}{}", self.year, self.week, self.build)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = VersionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut split = s.split('.');
|
||||
|
||||
let major_str = split.next().ok_or(VersionError::MissingMajor)?;
|
||||
let minor_str = split.next().ok_or(VersionError::MissingMinor)?;
|
||||
let patch_str = split.next().ok_or(VersionError::MissingPatch)?;
|
||||
|
||||
if split.next().is_some() {
|
||||
return Err(VersionError::ExtraComponents);
|
||||
}
|
||||
|
||||
let major = major_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| VersionError::IncorrectMajor(major_str.to_string()))?;
|
||||
|
||||
let minor = minor_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| VersionError::IncorrectMinor(minor_str.to_string()))?;
|
||||
|
||||
let patch = patch_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| VersionError::IncorrectPatch(patch_str.to_string()))?;
|
||||
|
||||
Ok(Self {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Snapshot {
|
||||
type Err = VersionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (year_str, rest) = s
|
||||
.split_once('w')
|
||||
.ok_or(VersionError::InvalidSnapshotFormat)?;
|
||||
|
||||
if rest.len() < 3 {
|
||||
return Err(VersionError::InvalidSnapshotFormat);
|
||||
}
|
||||
|
||||
let week_str = &rest[..2];
|
||||
let build_str = &rest[2..];
|
||||
|
||||
let year = year_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| VersionError::IncorrectYear(year_str.to_string()))?;
|
||||
|
||||
let week = week_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| VersionError::IncorrectWeek(week_str.to_string()))?;
|
||||
|
||||
let build = if build_str.len() == 1 {
|
||||
build_str.chars().next().unwrap()
|
||||
} else {
|
||||
return Err(VersionError::IncorrectBuild(build_str.to_string()));
|
||||
};
|
||||
|
||||
Ok(Self { year, week, build })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MinecraftVersion {
|
||||
type Err = VersionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(ver) = Version::from_str(s) {
|
||||
return Ok(MinecraftVersion::Release(ver));
|
||||
}
|
||||
|
||||
if let Ok(snap) = Snapshot::from_str(s) {
|
||||
return Ok(MinecraftVersion::Snapshot(snap));
|
||||
}
|
||||
|
||||
Err(VersionError::UnknownVersionFormat(s.to_string()))
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MinecraftType {
|
||||
Unknown,
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
Vanilla,
|
||||
#[cfg(feature = "mc-paper")]
|
||||
Paper,
|
||||
}
|
||||
|
||||
122
src/error.rs
122
src/error.rs
@@ -1,101 +1,33 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum Error {
|
||||
#[error("Undefined error")]
|
||||
Generic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum VersionError {
|
||||
#[error("Incorrect major version: {0}")]
|
||||
IncorrectMajor(String),
|
||||
|
||||
#[error("Incorrect minor version: {0}")]
|
||||
IncorrectMinor(String),
|
||||
|
||||
#[error("Incorrect patch version: {0}")]
|
||||
IncorrectPatch(String),
|
||||
|
||||
#[error("Incorrect major version: {0}")]
|
||||
IncorrectYear(String),
|
||||
|
||||
#[error("Incorrect minor version: {0}")]
|
||||
IncorrectWeek(String),
|
||||
|
||||
#[error("Incorrect patch version: {0}")]
|
||||
IncorrectBuild(String),
|
||||
|
||||
#[error("Missing major version")]
|
||||
MissingMajor,
|
||||
|
||||
#[error("Missing minor version")]
|
||||
MissingMinor,
|
||||
|
||||
#[error("Missing patch version")]
|
||||
MissingPatch,
|
||||
|
||||
#[error("Invalid snapshot format")]
|
||||
InvalidSnapshotFormat,
|
||||
|
||||
#[error("Too many components")]
|
||||
ExtraComponents,
|
||||
|
||||
#[error("Unrecognized version format: {0}")]
|
||||
UnknownVersionFormat(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum HandleError {
|
||||
#[error("Invalid Minecraft Version: {0}")]
|
||||
InvalidVersion(String),
|
||||
#[error("internal library error, this shouldn't happen")]
|
||||
InternalError,
|
||||
|
||||
#[error("Invalid server root directory: {0}")]
|
||||
InvalidDirectory(String),
|
||||
#[error("failed to start handle, not stopped")]
|
||||
StartFailedNotStopped,
|
||||
|
||||
#[error("Invalid relative JAR path: {0}")]
|
||||
InvalidPathJAR(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum SubscribeError {
|
||||
#[error("No stdout found")]
|
||||
NoStdout,
|
||||
|
||||
#[error("No stderr found")]
|
||||
NoStderr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ServerError {
|
||||
#[error("Server is already running")]
|
||||
AlreadyRunning,
|
||||
|
||||
#[error("Server is not running")]
|
||||
NotRunning,
|
||||
|
||||
#[error("Server crashed early")]
|
||||
EarlyCrash,
|
||||
|
||||
#[error("Failed to run java command")]
|
||||
CommandFailed,
|
||||
|
||||
#[error("Failed to access child stdout pipe")]
|
||||
NoStdoutPipe,
|
||||
|
||||
#[error("Failed to access child stdin pipe")]
|
||||
NoStdinPipe,
|
||||
|
||||
#[error("Failed to access child stderr pipe")]
|
||||
NoStderrPipe,
|
||||
|
||||
#[error("Failed to write to stdin")]
|
||||
StdinWriteFailed,
|
||||
}
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ParserError {
|
||||
#[error("ParserError")]
|
||||
ParserError,
|
||||
#[error("failed to stop handle, not running")]
|
||||
StopFailedNotRunning,
|
||||
|
||||
#[error("failed to start handle, child exists")]
|
||||
StartFailedChildExists,
|
||||
#[error("failed to stop handle, child doesn't exists")]
|
||||
StopFailedChildNotExists,
|
||||
|
||||
#[error("failed to kill handle, child doesn't exists")]
|
||||
KillFailedChildNotExists,
|
||||
|
||||
#[error("failed to kill handle, internal error")]
|
||||
KillFailledInternal,
|
||||
|
||||
#[error("failed to start pumps, child inexistant")]
|
||||
PumpsFailedNoChild,
|
||||
|
||||
#[error("failed to start pumps, no stdout in child")]
|
||||
PumpsFailedNoStdout,
|
||||
|
||||
#[error("failed to start pumps, no stderr in child")]
|
||||
PumpsFailedNoStderr,
|
||||
}
|
||||
|
||||
342
src/instance.rs
342
src/instance.rs
@@ -1,342 +0,0 @@
|
||||
use std::{path::PathBuf, process::Stdio, sync::Arc};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
|
||||
process::{self, Child},
|
||||
sync::{RwLock, broadcast, mpsc},
|
||||
};
|
||||
use tokio_stream::wrappers::BroadcastStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
config::{MinecraftType, MinecraftVersion, StreamLine, StreamSource},
|
||||
error::{HandleError, ServerError, SubscribeError},
|
||||
};
|
||||
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstanceData {
|
||||
pub root_dir: PathBuf,
|
||||
pub jar_path: PathBuf,
|
||||
pub mc_version: MinecraftVersion,
|
||||
pub mc_type: MinecraftType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum InstanceStatus {
|
||||
Starting,
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Crashed,
|
||||
Killing,
|
||||
Killed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceHandle {
|
||||
pub data: InstanceData,
|
||||
pub status: Arc<RwLock<InstanceStatus>>,
|
||||
stdout_tx: broadcast::Sender<StreamLine>,
|
||||
stderr_tx: Option<broadcast::Sender<StreamLine>>,
|
||||
#[cfg(feature = "events")]
|
||||
events_tx: broadcast::Sender<StreamLine>,
|
||||
stdin_tx: mpsc::Sender<String>,
|
||||
stdin_rx: Option<mpsc::Receiver<String>>,
|
||||
child: Option<Arc<RwLock<Child>>>,
|
||||
shutdown: CancellationToken,
|
||||
}
|
||||
|
||||
impl InstanceHandle {
|
||||
pub fn new_with_params(
|
||||
root_dir: &str,
|
||||
jar_path: &str,
|
||||
mc_version: &str,
|
||||
mc_type: MinecraftType,
|
||||
) -> Result<Self, HandleError> {
|
||||
let parsed_version: MinecraftVersion = mc_version
|
||||
.parse()
|
||||
.map_err(|_| HandleError::InvalidVersion(mc_version.to_string()))?;
|
||||
|
||||
let root: PathBuf = root_dir.into();
|
||||
if !root.exists() || !root.is_dir() {
|
||||
return Err(HandleError::InvalidDirectory(root_dir.to_string()));
|
||||
}
|
||||
|
||||
let path: PathBuf = jar_path.into();
|
||||
let conc = root.join(path.clone());
|
||||
if !path.is_relative() || !conc.is_file() {
|
||||
return Err(HandleError::InvalidPathJAR(jar_path.to_string()));
|
||||
}
|
||||
|
||||
let data = InstanceData {
|
||||
root_dir: root,
|
||||
jar_path: path,
|
||||
mc_version: parsed_version,
|
||||
mc_type,
|
||||
};
|
||||
|
||||
let status = InstanceStatus::Stopped;
|
||||
|
||||
let (stdin_tx, stdin_rx) = mpsc::channel(1024);
|
||||
Ok(Self {
|
||||
data,
|
||||
status: Arc::new(RwLock::new(status)),
|
||||
stdout_tx: broadcast::Sender::new(2048),
|
||||
stderr_tx: None,
|
||||
#[cfg(feature = "events")]
|
||||
events_tx: broadcast::Sender::new(2048),
|
||||
stdin_tx,
|
||||
stdin_rx: Some(stdin_rx),
|
||||
child: None,
|
||||
shutdown: CancellationToken::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_command<S: Into<String>>(&self, cmd: S) -> Result<(), ServerError> {
|
||||
let mut command = cmd.into();
|
||||
if !command.ends_with('\n') {
|
||||
command.push('\n');
|
||||
}
|
||||
|
||||
self.stdin_tx
|
||||
.send(command)
|
||||
.await
|
||||
.map_err(|_| ServerError::StdinWriteFailed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> Result<(), ServerError> {
|
||||
self.validate_start_parameters().await?;
|
||||
|
||||
self.transition_status(InstanceStatus::Starting).await;
|
||||
|
||||
let command = self.build_start_command();
|
||||
let child = self.spawn_child_process(command)?;
|
||||
|
||||
self.setup_stream_pumps(child)?;
|
||||
|
||||
self.transition_status(InstanceStatus::Running).await;
|
||||
|
||||
self.setup_parser()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn validate_start_parameters(&self) -> Result<(), ServerError> {
|
||||
if self.child.is_some() {
|
||||
return Err(ServerError::AlreadyRunning);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transition_status(&self, status: InstanceStatus) {
|
||||
let mut guard = self.status.write().await;
|
||||
*guard = status;
|
||||
drop(guard);
|
||||
}
|
||||
|
||||
fn build_start_command(&self) -> process::Command {
|
||||
let mut command = process::Command::new("java");
|
||||
command
|
||||
.arg("-jar")
|
||||
.arg(&self.data.jar_path)
|
||||
.arg("nogui")
|
||||
.current_dir(&self.data.root_dir)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::piped());
|
||||
|
||||
command.process_group(0);
|
||||
command
|
||||
}
|
||||
|
||||
fn spawn_child_process(&self, mut command: process::Command) -> Result<Child, ServerError> {
|
||||
command.spawn().map_err(|_| ServerError::CommandFailed)
|
||||
}
|
||||
|
||||
fn setup_stream_pumps(&mut self, mut child: Child) -> Result<(), ServerError> {
|
||||
let stdout = child.stdout.take().ok_or(ServerError::NoStdoutPipe)?;
|
||||
let stderr = child.stderr.take().ok_or(ServerError::NoStderrPipe)?;
|
||||
let stdin = child.stdin.take().ok_or(ServerError::NoStdinPipe)?;
|
||||
|
||||
let child = Arc::new(RwLock::new(child));
|
||||
self.child = Some(child);
|
||||
|
||||
let stdout_tx = self.stdout_tx.clone();
|
||||
let stderr_tx = broadcast::Sender::new(2048);
|
||||
self.stderr_tx = Some(stderr_tx.clone());
|
||||
let shutdown = self.shutdown.clone();
|
||||
|
||||
let stdout_status = self.status.clone();
|
||||
let stderr_status = self.status.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut stdout_reader = BufReader::new(stdout).lines();
|
||||
loop {
|
||||
match stdout_reader.next_line().await {
|
||||
Ok(Some(line)) => {
|
||||
let _ = stdout_tx.send(StreamLine::stdout(line));
|
||||
}
|
||||
_ => {
|
||||
let status_guard = stdout_status.read().await;
|
||||
if *status_guard != InstanceStatus::Killing
|
||||
|| *status_guard != InstanceStatus::Stopping
|
||||
{
|
||||
drop(status_guard);
|
||||
let mut status = stdout_status.write().await;
|
||||
*status = InstanceStatus::Crashed;
|
||||
drop(status);
|
||||
break;
|
||||
}
|
||||
drop(status_guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut stderr_reader = BufReader::new(stderr).lines();
|
||||
loop {
|
||||
match stderr_reader.next_line().await {
|
||||
Ok(Some(line)) => {
|
||||
let _ = stderr_tx.send(StreamLine::stderr(line));
|
||||
}
|
||||
_ => {
|
||||
let status_guard = stderr_status.read().await;
|
||||
if *status_guard != InstanceStatus::Killing
|
||||
|| *status_guard != InstanceStatus::Stopping
|
||||
{
|
||||
drop(status_guard);
|
||||
let mut status = stderr_status.write().await;
|
||||
*status = InstanceStatus::Crashed;
|
||||
drop(status);
|
||||
break;
|
||||
}
|
||||
drop(status_guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut stdin_rx = self.stdin_rx.take().ok_or(ServerError::NoStdinPipe)?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut writer = BufWriter::new(stdin);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = shutdown.cancelled() => {
|
||||
break;
|
||||
}
|
||||
maybe_cmd = stdin_rx.recv() => {
|
||||
if let Some(cmd) = maybe_cmd {
|
||||
_ = writer.write_all(cmd.as_bytes()).await;
|
||||
_ = writer.flush().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "events", any(feature = "mc-vanilla")))]
|
||||
fn setup_parser(&mut self) -> Result<(), ServerError> {
|
||||
let stdout_stream = self
|
||||
.subscribe(StreamSource::Stdout)
|
||||
.map_err(|_| ServerError::NoStdoutPipe)?;
|
||||
let shutdown = self.shutdown.clone();
|
||||
// TODO: Stream events!!!!
|
||||
let _event_tx = self.events_tx.clone();
|
||||
|
||||
#[cfg(feature = "mc-vanilla")]
|
||||
if self.data.mc_type == MinecraftType::Vanilla {
|
||||
use crate::config::LogMeta;
|
||||
tokio::spawn(async move {
|
||||
let mut rx = stdout_stream;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = shutdown.cancelled() => {
|
||||
break;
|
||||
}
|
||||
line = rx.next() => {
|
||||
if let Some(Ok(val)) = line {
|
||||
let msg = val.msg();
|
||||
let meta = LogMeta::new(msg);
|
||||
if let Ok(val) = meta
|
||||
&& val.is_some() {
|
||||
println!("{}", val.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn kill(&mut self) -> Result<(), ServerError> {
|
||||
if let Some(child_arc) = self.child.clone() {
|
||||
self.transition_status(InstanceStatus::Killing).await;
|
||||
let mut child = child_arc.write().await;
|
||||
|
||||
child.kill().await.map_err(|_| ServerError::CommandFailed)?;
|
||||
|
||||
self.shutdown.cancel();
|
||||
self.child = None;
|
||||
|
||||
self.transition_status(InstanceStatus::Killed).await;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServerError::NotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop(&mut self) -> Result<(), ServerError> {
|
||||
if let Some(child_arc) = self.child.clone() {
|
||||
self.transition_status(InstanceStatus::Stopping).await;
|
||||
|
||||
_ = self.send_command("stop").await;
|
||||
let mut child = child_arc.write().await;
|
||||
child.wait().await.map_err(|_| ServerError::CommandFailed)?;
|
||||
self.shutdown.cancel();
|
||||
self.child = None;
|
||||
|
||||
self.transition_status(InstanceStatus::Stopped).await;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServerError::NotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(
|
||||
&self,
|
||||
stream: StreamSource,
|
||||
) -> Result<BroadcastStream<StreamLine>, SubscribeError> {
|
||||
match stream {
|
||||
StreamSource::Stdout => {
|
||||
let rx = self.stdout_tx.subscribe();
|
||||
Ok(BroadcastStream::new(rx))
|
||||
}
|
||||
StreamSource::Stderr => {
|
||||
let rx = match &self.stderr_tx {
|
||||
Some(value) => value.subscribe(),
|
||||
None => return Err(SubscribeError::NoStderr),
|
||||
};
|
||||
Ok(BroadcastStream::new(rx))
|
||||
}
|
||||
#[cfg(feature = "events")]
|
||||
StreamSource::Event => {
|
||||
let rx = self.events_tx.subscribe();
|
||||
Ok(BroadcastStream::new(rx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
src/instance/handle.rs
Normal file
231
src/instance/handle.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use std::{process::Stdio, sync::Arc};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
process::{Child, Command},
|
||||
sync::{broadcast, RwLock},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{config::config::ServerConfig, error::HandleError, instance::types::InstanceStatus};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceHandle {
|
||||
config: Arc<RwLock<ServerConfig>>,
|
||||
status: Arc<RwLock<InstanceStatus>>,
|
||||
child: Option<Arc<RwLock<Child>>>,
|
||||
shutdown: CancellationToken,
|
||||
stdout_tx: broadcast::Sender<String>,
|
||||
stderr_tx: broadcast::Sender<String>,
|
||||
}
|
||||
|
||||
impl InstanceHandle {
|
||||
/// Create a new `InstanceHandle` with a blank `ServerConfig`
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: Arc::new(RwLock::new(ServerConfig::new())),
|
||||
status: Arc::new(RwLock::new(InstanceStatus::Stopped)),
|
||||
child: None,
|
||||
shutdown: CancellationToken::new(),
|
||||
stdout_tx: broadcast::Sender::new(1024),
|
||||
stderr_tx: broadcast::Sender::new(1024),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `InstanceHandle` with a `ServerConfig`, config is consumed
|
||||
pub fn with_cfg(config: ServerConfig) -> Result<Self, HandleError> {
|
||||
Ok(Self {
|
||||
config: Arc::new(RwLock::new(config)),
|
||||
status: Arc::new(RwLock::new(InstanceStatus::Stopped)),
|
||||
child: None,
|
||||
shutdown: CancellationToken::new(),
|
||||
stdout_tx: broadcast::Sender::new(1024),
|
||||
stderr_tx: broadcast::Sender::new(1024),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> Result<(), HandleError> {
|
||||
if !self.stopped_killed_or_crashed().await {
|
||||
return Err(HandleError::StartFailedNotStopped);
|
||||
}
|
||||
|
||||
if self.child.is_some() {
|
||||
return Err(HandleError::StartFailedChildExists);
|
||||
}
|
||||
|
||||
_ = self.change_status(InstanceStatus::Starting);
|
||||
|
||||
let mut command = self.build_command().await;
|
||||
let child = command.spawn();
|
||||
|
||||
self.setup_std_pumps().await?;
|
||||
|
||||
if cfg!(feature = "event") {
|
||||
// TODO: await server Done (...)! before status::Running for event module
|
||||
todo!()
|
||||
} else {
|
||||
_ = self.change_status(InstanceStatus::Running);
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn stop(&mut self) -> Result<(), HandleError> {
|
||||
if self.get_status().await != InstanceStatus::Running {
|
||||
return Err(HandleError::StopFailedNotRunning);
|
||||
}
|
||||
|
||||
let child = self.child.clone().ok_or(HandleError::StopFailedChildNotExists)?;
|
||||
|
||||
_ = self.change_status(InstanceStatus::Stopping);
|
||||
|
||||
let child_w = child.write().await;
|
||||
|
||||
// TODO:: Create send command for graceful stop, finish logic with shutdown token
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn kill(&mut self) -> Result<(), HandleError> {
|
||||
let child = self.child.clone().ok_or(HandleError::KillFailedChildNotExists)?;
|
||||
|
||||
_ = self.change_status(InstanceStatus::Killing);
|
||||
|
||||
let mut child_w = child.write().await;
|
||||
|
||||
child_w.kill().await.map_err(|_| HandleError::KillFailledInternal)?;
|
||||
|
||||
// TODO:: Finish kill logic including updating shutdown token status
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// region: --- Utils
|
||||
|
||||
impl InstanceHandle {
|
||||
async fn stopped_killed_or_crashed(&self) -> bool {
|
||||
let status = self.get_status().await;
|
||||
|
||||
if status == InstanceStatus::Stopped
|
||||
|| status == InstanceStatus::Killed
|
||||
|| status == InstanceStatus::Crashed
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
async fn get_status(&self) -> InstanceStatus {
|
||||
let status_r = self.status.read().await;
|
||||
let res = status_r.clone();
|
||||
drop(status_r);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
async fn change_status(
|
||||
&mut self,
|
||||
new_status: InstanceStatus,
|
||||
) -> (InstanceStatus, InstanceStatus) {
|
||||
let mut status_w = self.status.write().await;
|
||||
let old = status_w.clone();
|
||||
*status_w = new_status.clone();
|
||||
drop(status_w);
|
||||
(old, new_status)
|
||||
}
|
||||
|
||||
async fn get_config(&self) -> ServerConfig {
|
||||
let config_r = self.config.read().await;
|
||||
let res = config_r.clone();
|
||||
drop(config_r);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
async fn build_command(&self) -> Command {
|
||||
let cfg = self.get_config().await;
|
||||
let mut command = Command::new("java");
|
||||
|
||||
command.arg("-jar").arg(&cfg.jar_path).arg("nogui").current_dir(&cfg.core_path);
|
||||
|
||||
command.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
|
||||
command
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_status_arc(arc: Arc<RwLock<InstanceStatus>>) -> InstanceStatus {
|
||||
let status_r = arc.read().await;
|
||||
let res = status_r.clone();
|
||||
drop(status_r);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
async fn change_status_arc(
|
||||
arc: Arc<RwLock<InstanceStatus>>,
|
||||
new_status: InstanceStatus,
|
||||
) -> (InstanceStatus, InstanceStatus) {
|
||||
let mut status_w = arc.write().await;
|
||||
let old = status_w.clone();
|
||||
*status_w = new_status.clone();
|
||||
drop(status_w);
|
||||
(old, new_status)
|
||||
}
|
||||
|
||||
// endregion: --- Utils
|
||||
|
||||
// region: --- StdStream
|
||||
impl InstanceHandle {
|
||||
async fn setup_std_pumps(&mut self) -> Result<(), HandleError> {
|
||||
let child = self.child.clone().ok_or(HandleError::PumpsFailedNoChild)?;
|
||||
|
||||
let mut child_w = child.write().await;
|
||||
|
||||
let stdout_rx = child_w.stdout.take().ok_or(HandleError::PumpsFailedNoStdout)?;
|
||||
let stderr_rx = child_w.stderr.take().ok_or(HandleError::PumpsFailedNoStderr)?;
|
||||
|
||||
let stdout_tx = self.stdout_tx.clone();
|
||||
let stderr_tx = self.stderr_tx.clone();
|
||||
|
||||
let status_stdout = self.status.clone();
|
||||
let status_stderr = self.status.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut stdout_br = BufReader::new(stdout_rx).lines();
|
||||
loop {
|
||||
match stdout_br.next_line().await {
|
||||
Ok(Some(line)) => {
|
||||
_ = stdout_tx.send(line);
|
||||
}
|
||||
_ => {
|
||||
let status = get_status_arc(status_stdout.clone()).await;
|
||||
if status == InstanceStatus::Running {
|
||||
change_status_arc(status_stdout, InstanceStatus::Crashed).await;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut stdout_br = BufReader::new(stderr_rx).lines();
|
||||
loop {
|
||||
match stdout_br.next_line().await {
|
||||
Ok(Some(line)) => {
|
||||
_ = stderr_tx.send(line);
|
||||
}
|
||||
_ => {
|
||||
let status = get_status_arc(status_stderr.clone()).await;
|
||||
if status == InstanceStatus::Running {
|
||||
change_status_arc(status_stderr, InstanceStatus::Crashed).await;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
// endregion: --- StdStream
|
||||
2
src/instance/mod.rs
Normal file
2
src/instance/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod handle;
|
||||
pub mod types;
|
||||
10
src/instance/types.rs
Normal file
10
src/instance/types.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum InstanceStatus {
|
||||
Starting,
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Crashed,
|
||||
Killing,
|
||||
Killed,
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
pub mod config;
|
||||
mod config;
|
||||
mod instance;
|
||||
mod server;
|
||||
|
||||
pub use server::*;
|
||||
pub mod error;
|
||||
pub mod instance;
|
||||
|
||||
15
src/server/mod.rs
Normal file
15
src/server/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::instance::handle::InstanceHandle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MineGuardServer {
|
||||
handle: RwLock<InstanceHandle>,
|
||||
}
|
||||
|
||||
impl MineGuardServer {
|
||||
pub fn create() -> Self {
|
||||
let new_instance = InstanceHandle::new();
|
||||
Self { handle: RwLock::new(new_instance) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user