From c177c04a743d25a38210c0d96cce307d606a1b12 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 10:17:08 +0200 Subject: [PATCH 01/13] chore: updated deps; renamed project to alex --- .gitignore | 2 +- Cargo.lock | 415 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 +- 3 files changed, 421 insertions(+), 6 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index d1bb021..28fc10c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +#Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..212ac73 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,415 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "log" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" + +[[package]] +name = "mc-wrapper" +version = "0.1.0" +dependencies = [ + "chrono", + "flate2", + "tar", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml index 279ad89..dabe62b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] -name = "mc-wrapper" +name = "alex" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] # Used for creating tarballs for backups -tar = "0.4.37" +tar = "0.4.38" # Used to compress said tarballs using gzip -flate2 = "1.0.21" +flate2 = "1.0.26" # Used for backup filenames -chrono = "0.4.19" +chrono = "0.4.26" [profile.release] lto = "fat" From d176fac420ab4b9905ec365c56a59582e7f5603e Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 12:20:15 +0200 Subject: [PATCH 02/13] refactor: split process into own module --- .gitignore | 4 ++ Cargo.lock | 18 +++---- src/main.rs | 6 ++- src/server/command.rs | 70 ++++++++++++++++++++++++++++ src/server/mod.rs | 5 ++ src/{server.rs => server/process.rs} | 39 +++++++--------- 6 files changed, 111 insertions(+), 31 deletions(-) create mode 100644 src/server/command.rs create mode 100644 src/server/mod.rs rename src/{server.rs => server/process.rs} (58%) diff --git a/.gitignore b/.gitignore index 28fc10c..4259b1b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ target/ # Added by cargo /target + +# testing files +*.jar +data/ diff --git a/Cargo.lock b/Cargo.lock index 212ac73..00d9738 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "alex" +version = "0.1.0" +dependencies = [ + "chrono", + "flate2", + "tar", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -149,15 +158,6 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" -[[package]] -name = "mc-wrapper" -version = "0.1.0" -dependencies = [ - "chrono", - "flate2", - "tar", -] - [[package]] name = "miniz_oxide" version = "0.7.1" diff --git a/src/main.rs b/src/main.rs index f5cd3e5..43e1272 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,11 @@ use std::io; mod server; fn main() { - let mut server = server::Server::spawn(); + let mut server = + server::ServerCommand::new("paper-1.19.4-545.jar", "data/config", "data/worlds") + .mem(512, 1024) + .spawn() + .unwrap(); let stdin = io::stdin(); let input = &mut String::new(); diff --git a/src/server/command.rs b/src/server/command.rs new file mode 100644 index 0000000..da142fc --- /dev/null +++ b/src/server/command.rs @@ -0,0 +1,70 @@ +use crate::server::ServerProcess; +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs::File; +use std::io; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Stdio}; + +pub struct ServerCommand { + java: String, + jar: PathBuf, + config_dir: PathBuf, + world_dir: PathBuf, + xms: u32, + xmx: u32, + child: Option, +} + +impl ServerCommand { + pub fn new(jar: &str, config_dir: &str, world_dir: &str) -> Self { + ServerCommand { + java: String::from("java"), + jar: Path::new(jar).canonicalize().unwrap(), + config_dir: Path::new(config_dir).canonicalize().unwrap(), + world_dir: Path::new(world_dir).canonicalize().unwrap(), + xms: 1024, + xmx: 2048, + child: None, + } + } + + pub fn java(mut self, java: &str) -> Self { + self.java = String::from(java); + + self + } + + pub fn mem(mut self, xms: u32, xmx: u32) -> Self { + self.xms = xms; + self.xmx = xmx; + + self + } + + fn accept_eula(&self) -> std::io::Result<()> { + let mut eula_path = self.config_dir.clone(); + eula_path.push("eula.txt"); + let mut eula_file = File::create(eula_path)?; + eula_file.write(b"eula=true")?; + + Ok(()) + } + + pub fn spawn(&mut self) -> std::io::Result { + self.accept_eula()?; + + let child = Command::new(&self.java) + .current_dir(&self.config_dir) + .arg("-jar") + .arg(&self.jar) + .arg("--universe") + .arg(&self.world_dir) + .arg("--nogui") + .stdin(Stdio::piped()) + .spawn()?; + + Ok(ServerProcess::new(child)) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..a43ca4f --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod process; + +pub use command::ServerCommand; +pub use process::ServerProcess; diff --git a/src/server.rs b/src/server/process.rs similarity index 58% rename from src/server.rs rename to src/server/process.rs index 1e8db39..69c4425 100644 --- a/src/server.rs +++ b/src/server/process.rs @@ -1,27 +1,19 @@ +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs::File; use std::io; use std::io::Write; -use std::process::{Child, Command, Stdio}; -use std::fs::File; -use flate2::Compression; -use flate2::write::GzEncoder; +use std::path::{Path, PathBuf}; +use std::process::Child; +use std::process::{Command, Stdio}; -pub struct Server { +pub struct ServerProcess { child: Child, } -impl Server { - pub fn spawn() -> Server { - let child = Command::new("/usr/lib/jvm/java-16-openjdk/bin/java") - .arg("-jar") - .arg("paper-1.17.1-259.jar") - .arg("--universe") - .arg("worlds") - .arg("--nogui") - .stdin(Stdio::piped()) - .spawn() - .expect("Couldn't start child process"); - - Server { child } +impl ServerProcess { + pub fn new(child: Child) -> ServerProcess { + ServerProcess { child } } pub fn send_command(&mut self, cmd: &str) { @@ -33,8 +25,10 @@ impl Server { } fn custom(&mut self, cmd: &str) { - let mut stdin = self.child.stdin.as_ref().unwrap(); - stdin.write_all(format!("{}\n", cmd.trim()).as_bytes()).unwrap(); + let mut stdin = self.child.stdin.take().unwrap(); + stdin + .write_all(format!("{}\n", cmd.trim()).as_bytes()) + .unwrap(); stdin.flush().unwrap(); } @@ -49,7 +43,10 @@ impl Server { self.custom("save-all"); // Create a gzip-compressed tarball of the worlds folder - let filename = format!("{}", chrono::offset::Local::now().format("backups/%Y-%m-%d_%H-%M-%S.tar.gz")); + let filename = format!( + "{}", + chrono::offset::Local::now().format("backups/%Y-%m-%d_%H-%M-%S.tar.gz") + ); let tar_gz = std::fs::File::create(filename).unwrap(); let enc = GzEncoder::new(tar_gz, Compression::default()); let mut tar = tar::Builder::new(enc); From 826565dbaef9138b114cf0b2de07d529929e68b4 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 14:18:39 +0200 Subject: [PATCH 03/13] feat: some improvements --- src/main.rs | 22 +++++++---- src/server/command.rs | 82 +++++++++++++++++++++++++++++++------- src/server/mod.rs | 2 +- src/server/process.rs | 92 +++++++++++++++++++++++++++++++------------ 4 files changed, 150 insertions(+), 48 deletions(-) diff --git a/src/main.rs b/src/main.rs index 43e1272..38b2f6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,17 @@ -use std::io; - mod server; +use server::{ServerCommand, ServerType}; +use std::io; + fn main() { - let mut server = - server::ServerCommand::new("paper-1.19.4-545.jar", "data/config", "data/worlds") - .mem(512, 1024) - .spawn() - .unwrap(); + let mut server = server::ServerCommand::new(ServerType::Paper, "1.19.4-545") + .jar("paper-1.19.4-545.jar") + .config("data/config") + .world("data/worlds") + .backup("data/backups") + .mem(512, 1024) + .spawn() + .unwrap(); let stdin = io::stdin(); let input = &mut String::new(); @@ -16,7 +20,9 @@ fn main() { input.clear(); stdin.read_line(input); println!("input: {}", input.trim()); - server.send_command(input); + if let Err(e) = server.send_command(input) { + println!("{}", e); + }; if input.trim() == "stop" { break; diff --git a/src/server/command.rs b/src/server/command.rs index da142fc..10864dc 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,32 +1,52 @@ use crate::server::ServerProcess; -use flate2::write::GzEncoder; -use flate2::Compression; use std::fs::File; -use std::io; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; +use std::fmt; + +pub enum ServerType { + Paper, + Forge, + Vanilla, +} + +impl fmt::Display for ServerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + ServerType::Paper => "PaperMC", + ServerType::Forge => "Forge", + ServerType::Vanilla => "Vanilla", + }; + + write!(f, "{}", s) + } +} pub struct ServerCommand { + type_: ServerType, + version: String, java: String, jar: PathBuf, config_dir: PathBuf, world_dir: PathBuf, + backup_dir: PathBuf, xms: u32, xmx: u32, - child: Option, } impl ServerCommand { - pub fn new(jar: &str, config_dir: &str, world_dir: &str) -> Self { + pub fn new(type_: ServerType, version: &str) -> Self { ServerCommand { + type_, + version: String::from(version), java: String::from("java"), - jar: Path::new(jar).canonicalize().unwrap(), - config_dir: Path::new(config_dir).canonicalize().unwrap(), - world_dir: Path::new(world_dir).canonicalize().unwrap(), + jar: PathBuf::from("server.jar"), + config_dir: PathBuf::from("config"), + world_dir: PathBuf::from("worlds"), + backup_dir: PathBuf::from("backups"), xms: 1024, xmx: 2048, - child: None, } } @@ -36,6 +56,27 @@ impl ServerCommand { self } + pub fn jar>(mut self, path: T) -> Self { + self.jar = PathBuf::from(path.as_ref()); + self + } + + pub fn config>(mut self, path: T) -> Self { + self.config_dir = PathBuf::from(path.as_ref()); + self + } + + pub fn world>(mut self, path: T) -> Self { + self.world_dir = PathBuf::from(path.as_ref()); + self + } + + pub fn backup>(mut self, path: T) -> Self { + self.backup_dir = PathBuf::from(path.as_ref()); + + self + } + pub fn mem(mut self, xms: u32, xmx: u32) -> Self { self.xms = xms; self.xmx = xmx; @@ -52,19 +93,32 @@ impl ServerCommand { Ok(()) } - pub fn spawn(&mut self) -> std::io::Result { + pub fn spawn(self) -> std::io::Result { + // To avoid any issues, we use absolute paths for everything when spawning the process + let jar = self.jar.canonicalize()?; + let config_dir = self.config_dir.canonicalize()?; + let world_dir = self.world_dir.canonicalize()?; + let backup_dir = self.backup_dir.canonicalize()?; + self.accept_eula()?; let child = Command::new(&self.java) - .current_dir(&self.config_dir) + .current_dir(&config_dir) .arg("-jar") - .arg(&self.jar) + .arg(&jar) .arg("--universe") - .arg(&self.world_dir) + .arg(&world_dir) .arg("--nogui") .stdin(Stdio::piped()) .spawn()?; - Ok(ServerProcess::new(child)) + Ok(ServerProcess::new( + self.type_, + self.version, + config_dir, + world_dir, + backup_dir, + child, + )) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index a43ca4f..e3e3131 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,5 @@ mod command; mod process; -pub use command::ServerCommand; +pub use command::{ServerCommand, ServerType}; pub use process::ServerProcess; diff --git a/src/server/process.rs b/src/server/process.rs index 69c4425..a633b4e 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -1,3 +1,4 @@ +use crate::server::ServerType; use flate2::write::GzEncoder; use flate2::Compression; use std::fs::File; @@ -8,50 +9,91 @@ use std::process::Child; use std::process::{Command, Stdio}; pub struct ServerProcess { + type_: ServerType, + version: String, + config_dir: PathBuf, + world_dir: PathBuf, + backup_dir: PathBuf, child: Child, } impl ServerProcess { - pub fn new(child: Child) -> ServerProcess { - ServerProcess { child } - } - - pub fn send_command(&mut self, cmd: &str) { - match cmd.trim() { - "stop" | "exit" => self.stop(), - "backup" => self.backup(), - s => self.custom(s), + pub fn new( + type_: ServerType, + version: String, + config_dir: PathBuf, + world_dir: PathBuf, + backup_dir: PathBuf, + child: Child, + ) -> ServerProcess { + ServerProcess { + type_, + version, + config_dir, + world_dir, + backup_dir, + child, } } - fn custom(&mut self, cmd: &str) { - let mut stdin = self.child.stdin.take().unwrap(); - stdin - .write_all(format!("{}\n", cmd.trim()).as_bytes()) - .unwrap(); - stdin.flush().unwrap(); + pub fn send_command(&mut self, cmd: &str) -> std::io::Result<()> { + match cmd.trim() { + "stop" | "exit" => self.stop()?, + "backup" => self.backup()?, + s => self.custom(s)?, + } + + Ok(()) } - pub fn stop(&mut self) { - self.custom("stop"); - self.child.wait(); + fn custom(&mut self, cmd: &str) -> std::io::Result<()> { + let mut stdin = self.child.stdin.as_ref().unwrap(); + stdin.write_all(format!("{}\n", cmd.trim()).as_bytes())?; + stdin.flush()?; + + Ok(()) } - pub fn backup(&mut self) { + pub fn stop(&mut self) -> std::io::Result<()> { + self.custom("stop")?; + self.child.wait()?; + + Ok(()) + } + + pub fn backup(&mut self) -> std::io::Result<()> { // Make sure the server isn't modifying the files during the backup - self.custom("save-off"); - self.custom("save-all"); + self.custom("save-off")?; + self.custom("save-all")?; // Create a gzip-compressed tarball of the worlds folder let filename = format!( "{}", - chrono::offset::Local::now().format("backups/%Y-%m-%d_%H-%M-%S.tar.gz") + chrono::offset::Local::now().format("%Y-%m-%d_%H-%M-%S.tar.gz") ); - let tar_gz = std::fs::File::create(filename).unwrap(); + let path = self.backup_dir.join(&filename); + let tar_gz = std::fs::File::create(path)?; let enc = GzEncoder::new(tar_gz, Compression::default()); let mut tar = tar::Builder::new(enc); - tar.append_dir_all("worlds", "worlds").unwrap(); - self.custom("save-on"); + tar.append_dir_all("worlds", &self.world_dir)?; + + // We don't store all files in the config, as this would include caches + tar.append_path_with_name(self.config_dir.join("server.properties"), "config/server.properties")?; + + + // We add a file to the backup describing for what version it was made + let info = format!("{} {}", self.type_, self.version); + let info_bytes = info.as_bytes(); + let mut header = tar::Header::new_gnu(); + header.set_size(info_bytes.len().try_into().unwrap()); + + tar.append_data(&mut header, "info.txt", info_bytes)?; + + // tar.append_dir_all("config", &self.config_dir)?; + // + // Backup file gets finalized in the drop + + self.custom("save-on") } } From ec646381592819dcba9514678cb7982cad4c51ab Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 15:01:17 +0200 Subject: [PATCH 04/13] chore: add woodpecker config --- .woodpecker/build.yml | 18 ++++++++++++++++++ .woodpecker/clippy.yml | 11 +++++++++++ .woodpecker/lint.yml | 11 +++++++++++ 3 files changed, 40 insertions(+) create mode 100644 .woodpecker/build.yml create mode 100644 .woodpecker/clippy.yml create mode 100644 .woodpecker/lint.yml diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml new file mode 100644 index 0000000..12a09df --- /dev/null +++ b/.woodpecker/build.yml @@ -0,0 +1,18 @@ +matrix: + ARCH: + - 'amd64' + - 'arm64' + +platform: "linux/${ARCH}" + +branches: + exclude: [main] + +pipeline: + build: + image: 'rust:1.70' + commands: + - cargo build --verbose + - cargo test --verbose + # Binaries, even debug ones, should be statically compiled + - '[ "$(readelf -d target/debug/alex | grep NEEDED | wc -l)" = 0 ]' diff --git a/.woodpecker/clippy.yml b/.woodpecker/clippy.yml new file mode 100644 index 0000000..44a4b69 --- /dev/null +++ b/.woodpecker/clippy.yml @@ -0,0 +1,11 @@ +platform: 'linux/amd64' + +branches: + exclude: [main] + +pipeline: + clippy: + image: 'rust:1.70' + commands: + - rustup component add clippy + - cargo clippy -- --no-deps -Dwarnings diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml new file mode 100644 index 0000000..66d066f --- /dev/null +++ b/.woodpecker/lint.yml @@ -0,0 +1,11 @@ +platform: 'linux/amd64' + +branches: + exclude: [main] + +pipeline: + lint: + image: 'rust:1.70' + commands: + - rustup component add rustfmt + - cargo fmt -- --check From c5b579b8dbf2aadac85d0bff8fe0be3442acd2ad Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 15:03:41 +0200 Subject: [PATCH 05/13] fix(ci): use alpine image for builds --- .woodpecker/build.yml | 2 +- src/server/command.rs | 2 +- src/server/process.rs | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index 12a09df..f6863f0 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -10,7 +10,7 @@ branches: pipeline: build: - image: 'rust:1.70' + image: 'rust:1.70-alpine3.18' commands: - cargo build --verbose - cargo test --verbose diff --git a/src/server/command.rs b/src/server/command.rs index 10864dc..456841d 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,9 +1,9 @@ use crate::server::ServerProcess; +use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; -use std::fmt; pub enum ServerType { Paper, diff --git a/src/server/process.rs b/src/server/process.rs index a633b4e..ffc8982 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -79,8 +79,10 @@ impl ServerProcess { tar.append_dir_all("worlds", &self.world_dir)?; // We don't store all files in the config, as this would include caches - tar.append_path_with_name(self.config_dir.join("server.properties"), "config/server.properties")?; - + tar.append_path_with_name( + self.config_dir.join("server.properties"), + "config/server.properties", + )?; // We add a file to the backup describing for what version it was made let info = format!("{} {}", self.type_, self.version); From d0321f7ed1d9f1fadb12da1ab690cf75318441c3 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 15:59:47 +0200 Subject: [PATCH 06/13] feat: started cli interface --- Cargo.lock | 186 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + src/main.rs | 56 +++++++++++-- src/server/command.rs | 11 ++- 4 files changed, 246 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00d9738..5953204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,7 @@ name = "alex" version = "0.1.0" dependencies = [ "chrono", + "clap", "flate2", "tar", ] @@ -32,6 +33,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -77,6 +127,54 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ed2379f8603fa2b7509891660e802b88c70a79a6427a70abb5968054de2c28" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e9ef9a08ee1c0e1f2e162121665ac45ac3783b0f897db7244ae75ad9a8f65b" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -92,6 +190,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "filetime" version = "0.2.21" @@ -114,6 +233,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "iana-time-zone" version = "0.1.56" @@ -137,6 +268,29 @@ dependencies = [ "cc", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "js-sys" version = "0.3.63" @@ -152,6 +306,12 @@ version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "log" version = "0.4.18" @@ -209,6 +369,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.18" @@ -248,6 +428,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index dabe62b..1db76f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "alex" version = "0.1.0" +description = "Wrapper around Minecraft server processes, designed to complement Docker image installations." +authors = ["Jef Roosens"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,8 +14,10 @@ tar = "0.4.38" flate2 = "1.0.26" # Used for backup filenames chrono = "0.4.26" +clap = { version = "4.3.1", features = ["derive"] } [profile.release] lto = "fat" codegen-units = 1 panic = "abort" +strip = true diff --git a/src/main.rs b/src/main.rs index 38b2f6b..658dc57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,57 @@ mod server; +use clap::{Parser, Subcommand}; use server::{ServerCommand, ServerType}; use std::io; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Type of server + type_: ServerType, + /// Version string for the server, e.g. 1.19.4-545 + server_version: String, + /// Server jar to execute + jar: PathBuf, + + /// Directory where configs are stored, and where the server will run; defaults to the current + /// directory. + #[arg(long, value_name = "CONFIG_DIR")] + config: Option, + /// Directory where world files will be saved; defaults to ../worlds + #[arg(long, value_name = "WORLD_DIR")] + world: Option, + /// Directory where backups will be stored; defaults to ../backups + #[arg(long, value_name = "BACKUP_DIR")] + backup: Option, + + /// XMS value for the server instance + #[arg(long)] + xms: Option, + /// XMX value for the server instance + #[arg(long)] + xmx: Option, +} fn main() { - let mut server = server::ServerCommand::new(ServerType::Paper, "1.19.4-545") - .jar("paper-1.19.4-545.jar") - .config("data/config") - .world("data/worlds") - .backup("data/backups") - .mem(512, 1024) - .spawn() - .unwrap(); + let cli = Cli::parse(); + + let mut cmd = server::ServerCommand::new(cli.type_, &cli.server_version) + .jar(cli.jar) + .config(cli.config.unwrap_or(".".into())) + .world(cli.world.unwrap_or("../worlds".into())) + .backup(cli.backup.unwrap_or("../backups".into())); + + if let Some(xms) = cli.xms { + cmd = cmd.xms(xms); + } + + if let Some(xmx) = cli.xmx { + cmd = cmd.xmx(xmx); + } + + let mut server = cmd.spawn().expect("Failed to start server."); let stdin = io::stdin(); let input = &mut String::new(); diff --git a/src/server/command.rs b/src/server/command.rs index 456841d..50075de 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,10 +1,12 @@ use crate::server::ServerProcess; +use clap::ValueEnum; use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum ServerType { Paper, Forge, @@ -77,10 +79,13 @@ impl ServerCommand { self } - pub fn mem(mut self, xms: u32, xmx: u32) -> Self { - self.xms = xms; - self.xmx = xmx; + pub fn xms(mut self, v: u32) -> Self { + self.xms = v; + self + } + pub fn xmx(mut self, v: u32) -> Self { + self.xmx = v; self } From 7248ea8b90cb0df7414230fcc40ca293a99fb9e3 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 17:40:57 +0200 Subject: [PATCH 07/13] fix(ci): add build-base to build steps --- .woodpecker/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index f6863f0..1073cc7 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -12,6 +12,7 @@ pipeline: build: image: 'rust:1.70-alpine3.18' commands: + - apk add --no-cache build-base - cargo build --verbose - cargo test --verbose # Binaries, even debug ones, should be statically compiled From 49546a449e858adbecddb9e48ec57f22223a0ddb Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 3 Jun 2023 23:36:43 +0200 Subject: [PATCH 08/13] feat: add periodic backups thread --- .cargo/config.toml | 2 ++ src/main.rs | 75 +++++++++++++++++++++++++++---------------- src/server/command.rs | 20 ++++++++---- src/server/process.rs | 72 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 129 insertions(+), 40 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..52f99fb --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +runs = "run -- paper 1.19.4-545 --config data/config --backup data/backups --world data/worlds data/paper-1.19.4-545.jar" diff --git a/src/main.rs b/src/main.rs index 658dc57..35a5b9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ mod server; -use clap::{Parser, Subcommand}; -use server::{ServerCommand, ServerType}; +use clap::Parser; +use server::ServerType; use std::io; use std::path::PathBuf; +use std::sync::{Arc, Mutex}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -15,43 +16,61 @@ struct Cli { /// Server jar to execute jar: PathBuf, - /// Directory where configs are stored, and where the server will run; defaults to the current - /// directory. + /// Directory where configs are stored, and where the server will run [default: .] #[arg(long, value_name = "CONFIG_DIR")] config: Option, - /// Directory where world files will be saved; defaults to ../worlds + /// Directory where world files will be saved [default: ../worlds] #[arg(long, value_name = "WORLD_DIR")] world: Option, - /// Directory where backups will be stored; defaults to ../backups + /// Directory where backups will be stored [default: ../backups] #[arg(long, value_name = "BACKUP_DIR")] backup: Option, + /// Java command to run the server jar with + #[arg(long, value_name = "JAVA_CMD", default_value_t = String::from("java"))] + java: String, - /// XMS value for the server instance - #[arg(long)] - xms: Option, - /// XMX value for the server instance - #[arg(long)] - xmx: Option, + /// XMS value in megabytes for the server instance + #[arg(long, default_value_t = 1024)] + xms: u64, + /// XMX value in megabytes for the server instance + #[arg(long, default_value_t = 2048)] + xmx: u64, + + /// How many backups to keep + #[arg(short = 'n', long, default_value_t = 7)] + max_backups: u64, + /// How frequently to perform a backup, in minutes + #[arg(short = 't', long, default_value_t = 720)] + frequency: u64, +} + +fn backups_thread(counter: Arc>, frequency: u64) { + loop { + std::thread::sleep(std::time::Duration::from_secs(frequency * 60)); + + { + let mut server = counter.lock().unwrap(); + server.backup(); + } + } } fn main() { let cli = Cli::parse(); - let mut cmd = server::ServerCommand::new(cli.type_, &cli.server_version) + let cmd = server::ServerCommand::new(cli.type_, &cli.server_version) + .java(&cli.java) .jar(cli.jar) .config(cli.config.unwrap_or(".".into())) .world(cli.world.unwrap_or("../worlds".into())) - .backup(cli.backup.unwrap_or("../backups".into())); + .backup(cli.backup.unwrap_or("../backups".into())) + .xms(cli.xms) + .xmx(cli.xmx) + .max_backups(cli.max_backups); + let counter = Arc::new(Mutex::new(cmd.spawn().expect("Failed to start server."))); - if let Some(xms) = cli.xms { - cmd = cmd.xms(xms); - } - - if let Some(xmx) = cli.xmx { - cmd = cmd.xmx(xmx); - } - - let mut server = cmd.spawn().expect("Failed to start server."); + let clone = Arc::clone(&counter); + std::thread::spawn(move || backups_thread(clone, cli.frequency)); let stdin = io::stdin(); let input = &mut String::new(); @@ -59,10 +78,12 @@ fn main() { loop { input.clear(); stdin.read_line(input); - println!("input: {}", input.trim()); - if let Err(e) = server.send_command(input) { - println!("{}", e); - }; + { + let mut server = counter.lock().unwrap(); + if let Err(e) = server.send_command(input) { + println!("{}", e); + }; + } if input.trim() == "stop" { break; diff --git a/src/server/command.rs b/src/server/command.rs index 50075de..e774bb5 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -4,7 +4,7 @@ use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; -use std::process::{Child, Command, Stdio}; +use std::process::{Command, Stdio}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum ServerType { @@ -33,8 +33,9 @@ pub struct ServerCommand { config_dir: PathBuf, world_dir: PathBuf, backup_dir: PathBuf, - xms: u32, - xmx: u32, + xms: u64, + xmx: u64, + max_backups: u64, } impl ServerCommand { @@ -49,6 +50,7 @@ impl ServerCommand { backup_dir: PathBuf::from("backups"), xms: 1024, xmx: 2048, + max_backups: 7, } } @@ -79,21 +81,26 @@ impl ServerCommand { self } - pub fn xms(mut self, v: u32) -> Self { + pub fn xms(mut self, v: u64) -> Self { self.xms = v; self } - pub fn xmx(mut self, v: u32) -> Self { + pub fn xmx(mut self, v: u64) -> Self { self.xmx = v; self } + pub fn max_backups(mut self, v: u64) -> Self { + self.max_backups = v; + self + } + fn accept_eula(&self) -> std::io::Result<()> { let mut eula_path = self.config_dir.clone(); eula_path.push("eula.txt"); let mut eula_file = File::create(eula_path)?; - eula_file.write(b"eula=true")?; + eula_file.write_all(b"eula=true")?; Ok(()) } @@ -123,6 +130,7 @@ impl ServerCommand { config_dir, world_dir, backup_dir, + self.max_backups, child, )) } diff --git a/src/server/process.rs b/src/server/process.rs index ffc8982..2b6f6fa 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -1,12 +1,15 @@ use crate::server::ServerType; use flate2::write::GzEncoder; use flate2::Compression; -use std::fs::File; -use std::io; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::Child; -use std::process::{Command, Stdio}; + +#[link(name = "c")] +extern "C" { + fn geteuid() -> u32; + fn getegid() -> u32; +} pub struct ServerProcess { type_: ServerType, @@ -14,6 +17,7 @@ pub struct ServerProcess { config_dir: PathBuf, world_dir: PathBuf, backup_dir: PathBuf, + max_backups: u64, child: Child, } @@ -24,6 +28,7 @@ impl ServerProcess { config_dir: PathBuf, world_dir: PathBuf, backup_dir: PathBuf, + max_backups: u64, child: Child, ) -> ServerProcess { ServerProcess { @@ -32,6 +37,7 @@ impl ServerProcess { config_dir, world_dir, backup_dir, + max_backups, child, } } @@ -61,17 +67,41 @@ impl ServerProcess { Ok(()) } + /// Perform a backup by disabling the server's save feature and flushing its data, before + /// creating an archive file. pub fn backup(&mut self) -> std::io::Result<()> { + self.custom("say backing up server")?; + // Make sure the server isn't modifying the files during the backup self.custom("save-off")?; self.custom("save-all")?; + // TODO implement a better mechanism + // We wait some time to (hopefully) ensure the save-all call has completed + std::thread::sleep(std::time::Duration::from_secs(10)); + + let res = self.create_backup_archive(); + + if res.is_ok() { + self.remove_old_backups()?; + } + + // The server's save feature needs to be enabled again even if the archive failed to create + self.custom("save-on")?; + + self.custom("say server backed up successfully")?; + + res + } + + /// Create a new compressed backup archive of the server's data. + fn create_backup_archive(&mut self) -> std::io::Result<()> { // Create a gzip-compressed tarball of the worlds folder let filename = format!( "{}", chrono::offset::Local::now().format("%Y-%m-%d_%H-%M-%S.tar.gz") ); - let path = self.backup_dir.join(&filename); + let path = self.backup_dir.join(filename); let tar_gz = std::fs::File::create(path)?; let enc = GzEncoder::new(tar_gz, Compression::default()); let mut tar = tar::Builder::new(enc); @@ -87,15 +117,43 @@ impl ServerProcess { // We add a file to the backup describing for what version it was made let info = format!("{} {}", self.type_, self.version); let info_bytes = info.as_bytes(); + let mut header = tar::Header::new_gnu(); header.set_size(info_bytes.len().try_into().unwrap()); + header.set_mode(0o100644); + unsafe { + header.set_gid(getegid().into()); + header.set_uid(geteuid().into()); + } tar.append_data(&mut header, "info.txt", info_bytes)?; // tar.append_dir_all("config", &self.config_dir)?; - // + // Backup file gets finalized in the drop - self.custom("save-on") + Ok(()) + } + + /// Remove the oldest backups + fn remove_old_backups(&mut self) -> std::io::Result<()> { + // The naming format used allows us to sort the backups by name and still get a sorting by + // creation time + let mut backups = std::fs::read_dir(&self.backup_dir)? + .filter_map(|res| res.map(|e| e.path()).ok()) + .collect::>(); + backups.sort(); + + let max_backups: usize = self.max_backups.try_into().unwrap(); + + if backups.len() > max_backups { + let excess_backups = backups.len() - max_backups; + + for backup in &backups[0..excess_backups] { + std::fs::remove_file(backup)?; + } + } + + Ok(()) } } From 3c5cdaef4fb05c46c95c811183d4e7258c60f298 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sun, 4 Jun 2023 08:55:31 +0200 Subject: [PATCH 09/13] chore: started changelog --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..21d0397 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev) + +### Added + +* CLI interface +* Working backup command +* Thread for periodic backups + From f083d7e70187d631dee54922119caef9f8b3e27e Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sun, 4 Jun 2023 08:57:31 +0200 Subject: [PATCH 10/13] feat: ability to disable backup thread --- src/main.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 35a5b9d..72f8cb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,8 +39,8 @@ struct Cli { /// How many backups to keep #[arg(short = 'n', long, default_value_t = 7)] max_backups: u64, - /// How frequently to perform a backup, in minutes - #[arg(short = 't', long, default_value_t = 720)] + /// How frequently to perform a backup, in minutes; 0 to disable. + #[arg(short = 't', long, default_value_t = 0)] frequency: u64, } @@ -69,8 +69,10 @@ fn main() { .max_backups(cli.max_backups); let counter = Arc::new(Mutex::new(cmd.spawn().expect("Failed to start server."))); - let clone = Arc::clone(&counter); - std::thread::spawn(move || backups_thread(clone, cli.frequency)); + if cli.frequency > 0 { + let clone = Arc::clone(&counter); + std::thread::spawn(move || backups_thread(clone, cli.frequency)); + } let stdin = io::stdin(); let input = &mut String::new(); From bef9698fc1954df63c557b4731f8df5b34744d3f Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 5 Jun 2023 10:06:50 +0200 Subject: [PATCH 11/13] feat: add env vars for flags --- Cargo.toml | 2 +- src/main.rs | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1db76f8..dd2f79b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ tar = "0.4.38" flate2 = "1.0.26" # Used for backup filenames chrono = "0.4.26" -clap = { version = "4.3.1", features = ["derive"] } +clap = { version = "4.3.1", features = ["derive", "env"] } [profile.release] lto = "fat" diff --git a/src/main.rs b/src/main.rs index 72f8cb8..05a6d96 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,30 +17,30 @@ struct Cli { jar: PathBuf, /// Directory where configs are stored, and where the server will run [default: .] - #[arg(long, value_name = "CONFIG_DIR")] + #[arg(long, value_name = "CONFIG_DIR", env = "ALEX_CONFIG_DIR")] config: Option, /// Directory where world files will be saved [default: ../worlds] - #[arg(long, value_name = "WORLD_DIR")] + #[arg(long, value_name = "WORLD_DIR", env = "ALEX_WORLD_DIR")] world: Option, /// Directory where backups will be stored [default: ../backups] - #[arg(long, value_name = "BACKUP_DIR")] + #[arg(long, value_name = "BACKUP_DIR", env = "ALEX_WORLD_DIR")] backup: Option, /// Java command to run the server jar with - #[arg(long, value_name = "JAVA_CMD", default_value_t = String::from("java"))] + #[arg(long, value_name = "JAVA_CMD", default_value_t = String::from("java"), env = "ALEX_JAVA")] java: String, /// XMS value in megabytes for the server instance - #[arg(long, default_value_t = 1024)] + #[arg(long, default_value_t = 1024, env = "ALEX_XMS")] xms: u64, /// XMX value in megabytes for the server instance - #[arg(long, default_value_t = 2048)] + #[arg(long, default_value_t = 2048, env = "ALEX_XMX")] xmx: u64, /// How many backups to keep - #[arg(short = 'n', long, default_value_t = 7)] + #[arg(short = 'n', long, default_value_t = 7, env = "ALEX_MAX_BACKUPS")] max_backups: u64, /// How frequently to perform a backup, in minutes; 0 to disable. - #[arg(short = 't', long, default_value_t = 0)] + #[arg(short = 't', long, default_value_t = 0, env = "ALEX_FREQUENCY")] frequency: u64, } @@ -50,7 +50,9 @@ fn backups_thread(counter: Arc>, frequency: u64) { { let mut server = counter.lock().unwrap(); - server.backup(); + + // We explicitely ignore the error here, as we don't want the thread to fail + let _ = server.backup(); } } } @@ -79,9 +81,14 @@ fn main() { loop { input.clear(); - stdin.read_line(input); + + if stdin.read_line(input).is_err() { + continue; + }; + { let mut server = counter.lock().unwrap(); + if let Err(e) = server.send_command(input) { println!("{}", e); }; From 3782d3af2e6c4dbfc3ec7c20335a40625a9fcb05 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 5 Jun 2023 10:32:50 +0200 Subject: [PATCH 12/13] feat(ci): add release config --- .woodpecker/release.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .woodpecker/release.yml diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml new file mode 100644 index 0000000..5c4954c --- /dev/null +++ b/.woodpecker/release.yml @@ -0,0 +1,33 @@ +matrix: + PLATFORM: + - 'linux/amd64' + # - 'linux/arm64' + +platform: $PLATFORM +branches: [ main ] + +pipeline: + build: + image: 'rust:1.70-alpine3.18' + commands: + - apk add --no-cache build-base + - cargo build --release --verbose + # Ensure the release binary is also statically compiled + - '[ "$(readelf -d target/release/alex | grep NEEDED | wc -l)" = 0 ]' + - du -h target/release/alex + - mv target/release/alex target/release/alex-$(echo '${PLATFORM}' | sed 's:/:-:g')" + when: + event: tag + + publish: + image: 'plugins/gitea-release' + secrets: + - gitea_release_api_key + settings: + base_url: https://git.rustybever.be + files: target/release/alex-* + checksum: + - sha256 + title: ${CI_COMMIT_TAG} + when: + event: tag From 3c846f7b2cbd4411dd6b872a4d61fc91bd0fbd1a Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 5 Jun 2023 10:41:55 +0200 Subject: [PATCH 13/13] chore: update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d0397..fa75097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev) +## [0.1.0](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.1.0) + ### Added * CLI interface * Working backup command * Thread for periodic backups - +* Env var support