diff --git a/.cargo/config.toml b/.cargo/config.toml index 09b7896..37bb90a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] -runs = "run -- run paper 1.19.4-550 --config data/config --backup data/backups --world data/worlds --jar paper-1.19.4-550.jar" +runs = "run -- paper 1.19.4-545 --config data/config --backup data/backups --world data/worlds --jar data/paper-1.19.4-525.jar" runrs = "run --release -- paper 1.19.4-545 --config data/config --backup data/backups --world data/worlds --jar data/paper-1.19.4-525.jar" diff --git a/CHANGELOG.md b/CHANGELOG.md index e431581..4339669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,28 +7,6 @@ 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) -### Changed - -* Running the server now uses the `run` CLI subcommand - -## [0.2.2](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.2.2) - -### Fixed - -* Use correct env var for backup directory - -## [0.2.1](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.2.1) - -### Added - -* `--dry` flag to inspect command that will be run - -### Changed - -* JVM flags now narrowely follow Aikar's specifications - -## [0.2.0](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.2.0) - ### Added * Rudimentary signal handling for gently stopping server diff --git a/Cargo.lock b/Cargo.lock index 3e798dd..0991d89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "alex" -version = "0.2.2" +version = "0.2.0" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2c4045b..1de2d06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alex" -version = "0.2.2" +version = "0.2.0" description = "Wrapper around Minecraft server processes, designed to complement Docker image installations." authors = ["Jef Roosens"] edition = "2021" diff --git a/Dockerfile b/Dockerfile index dba2674..cb3b9e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,11 +18,11 @@ RUN cargo build && \ # We use ${:-} instead of a default value because the argument is always passed # to the build, it'll just be blank most likely -FROM eclipse-temurin:18-jre-alpine +FROM eclipse-temurin:17-jre-alpine # Build arguments ARG MC_VERSION=1.19.4 -ARG PAPERMC_VERSION=525 +ARG PAPERMC_VERSION=545 RUN addgroup -Sg 1000 paper && \ adduser -SHG paper -u 1000 paper @@ -61,5 +61,4 @@ EXPOSE 25565 # Switch to non-root user USER paper:paper -ENTRYPOINT ["/bin/dumb-init", "--"] -CMD ["/bin/alex", "paper"] +ENTRYPOINT ["/bin/alex", "paper"] diff --git a/README.md b/README.md index 4e00b78..f6215c3 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,3 @@ -# Alex +# mc-wrapper -Alex is a wrapper around a typical Minecraft server process. It acts as the -parent process, and sits in between the user's input and the server's stdin. -This allows Alex to support additional commands that execute Rust code. - -## Why - -The primary usecase for this is backups. A common problem I've had with -Minecraft backups is that they fail, because the server is writing to one of -the region files as the backup is being created. Alex solves this be sending -`save-off` and `save-all` to the server, before creating the tarball. -Afterwards, saving is enabled again with `save-on`. - -## Features - -* Create safe backups as gzip-compressed tarballs using the `backup` command -* Automatically create backups periodically -* Properly configures the process (working directory, optimisation flags) -* Configure everything as CLI arguments or environment variables - -## Installation - -Alex is distributed as statically compiled binaries for Linux amd64 and arm64. -These can be found -[here](https://git.rustybever.be/Chewing_Bever/alex/packages). +A wrapper around a standard Minecraft server, written in Rust. \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 1acbe36..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::server::ServerType; -use clap::{Args, Parser, Subcommand}; -use std::path::PathBuf; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -pub struct Cli { - #[command(subcommand)] - pub command: Commands, - /// Directory where configs are stored, and where the server will run - #[arg( - long, - value_name = "CONFIG_DIR", - default_value = ".", - env = "ALEX_CONFIG_DIR", - global = true - )] - pub config: PathBuf, - /// Directory where world files will be saved - #[arg( - long, - value_name = "WORLD_DIR", - default_value = "../worlds", - env = "ALEX_WORLD_DIR", - global = true - )] - pub world: PathBuf, - /// Directory where backups will be stored - #[arg( - long, - value_name = "BACKUP_DIR", - default_value = "../backups", - env = "ALEX_BACKUP_DIR", - global = true - )] - pub backup: PathBuf, - - /// How many backups to keep - #[arg( - short = 'n', - long, - default_value_t = 7, - env = "ALEX_MAX_BACKUPS", - global = true - )] - pub max_backups: u64, -} - -#[derive(Subcommand)] -pub enum Commands { - /// Run the server - Run(RunArgs), -} - -#[derive(Args)] -pub struct RunArgs { - /// Type of server - pub type_: ServerType, - /// Version string for the server, e.g. 1.19.4-545 - #[arg(env = "ALEX_SERVER_VERSION")] - pub server_version: String, - - /// Server jar to execute - #[arg( - long, - value_name = "JAR_PATH", - default_value = "server.jar", - env = "ALEX_JAR" - )] - pub jar: PathBuf, - - /// Java command to run the server jar with - #[arg(long, value_name = "JAVA_CMD", default_value_t = String::from("java"), env = "ALEX_JAVA")] - pub java: String, - - /// XMS value in megabytes for the server instance - #[arg(long, default_value_t = 1024, env = "ALEX_XMS")] - pub xms: u64, - /// XMX value in megabytes for the server instance - #[arg(long, default_value_t = 2048, env = "ALEX_XMX")] - pub xmx: u64, - - /// How frequently to perform a backup, in minutes; 0 to disable. - #[arg(short = 't', long, default_value_t = 0, env = "ALEX_FREQUENCY")] - pub frequency: u64, - - /// Don't actually run the server, but simply output the server configuration that would have - /// been ran - #[arg(short, long, default_value_t = false)] - pub dry: bool, -} diff --git a/src/main.rs b/src/main.rs index a1ae21c..9f993a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,73 @@ -mod cli; mod server; mod signals; mod stdin; use clap::Parser; -use cli::{Cli, Commands, RunArgs}; +use server::ServerType; use std::io; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; +#[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 + #[arg(env = "ALEX_SERVER_VERSION")] + server_version: String, + + /// Server jar to execute + #[arg( + long, + value_name = "JAR_PATH", + default_value = "server.jar", + env = "ALEX_JAR" + )] + jar: PathBuf, + /// Directory where configs are stored, and where the server will run + #[arg( + long, + value_name = "CONFIG_DIR", + default_value = ".", + env = "ALEX_CONFIG_DIR" + )] + config: PathBuf, + /// Directory where world files will be saved + #[arg( + long, + value_name = "WORLD_DIR", + default_value = "../worlds", + env = "ALEX_WORLD_DIR" + )] + world: PathBuf, + /// Directory where backups will be stored + #[arg( + long, + value_name = "BACKUP_DIR", + default_value = "../backups", + env = "ALEX_WORLD_DIR" + )] + backup: PathBuf, + /// Java command to run the server jar with + #[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, env = "ALEX_XMS")] + xms: u64, + /// XMX value in megabytes for the server instance + #[arg(long, default_value_t = 2048, env = "ALEX_XMX")] + xmx: u64, + + /// How many backups to keep + #[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, env = "ALEX_FREQUENCY")] + frequency: u64, +} + fn backups_thread(counter: Arc>, frequency: u64) { loop { std::thread::sleep(std::time::Duration::from_secs(frequency * 60)); @@ -21,32 +81,24 @@ fn backups_thread(counter: Arc>, frequency: u64) { } } -fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { +fn main() -> io::Result<()> { let (_, mut signals) = signals::install_signal_handlers()?; + let cli = Cli::parse(); - let mut cmd = server::ServerCommand::new(args.type_, &args.server_version) - .java(&args.java) - .jar(args.jar.clone()) - .config(cli.config.clone()) - .world(cli.world.clone()) - .backup(cli.backup.clone()) - .xms(args.xms) - .xmx(args.xmx) + let cmd = server::ServerCommand::new(cli.type_, &cli.server_version) + .java(&cli.java) + .jar(cli.jar) + .config(cli.config) + .world(cli.world) + .backup(cli.backup) + .xms(cli.xms) + .xmx(cli.xmx) .max_backups(cli.max_backups); - cmd.canonicalize()?; - - if args.dry { - print!("{}", cmd); - - return Ok(()); - } - let counter = Arc::new(Mutex::new(cmd.spawn()?)); - if args.frequency > 0 { + if cli.frequency > 0 { let clone = Arc::clone(&counter); - let frequency = args.frequency; - std::thread::spawn(move || backups_thread(clone, frequency)); + std::thread::spawn(move || backups_thread(clone, cli.frequency)); } // Spawn thread that handles the main stdin loop @@ -56,11 +108,3 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { // Signal handler loop exits the process when necessary signals::handle_signals(&mut signals, counter) } - -fn main() -> io::Result<()> { - let cli = Cli::parse(); - - match &cli.command { - Commands::Run(args) => command_run(&cli, args), - } -} diff --git a/src/server/backups.rs b/src/server/backups.rs index 2620e17..b117e22 100644 --- a/src/server/backups.rs +++ b/src/server/backups.rs @@ -1,11 +1,10 @@ -use chrono::{Local, Utc}; use flate2::write::GzEncoder; use flate2::Compression; -use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io; use std::path::{Path, PathBuf}; -use std::sync::Arc; +use chrono::{Utc, Local}; +use std::collections::HashSet; #[link(name = "c")] extern "C" { @@ -13,272 +12,17 @@ extern "C" { fn getegid() -> u32; } -const FILENAME_FORMAT: &str = "%Y-%m-%d_%H-%M-%S.tar.gz"; - -fn files(src_dir: PathBuf) -> io::Result> { - let mut dirs = vec![src_dir.clone()]; - let mut files: HashSet = HashSet::new(); - - while let Some(dir) = dirs.pop() { - for res in dir.read_dir()? { - let entry = res?; - - if entry.file_name() == "cache" { - continue; - } - - if entry.file_type()?.is_dir() { - dirs.push(entry.path()); - } else { - files.insert(entry.path().strip_prefix(&src_dir).unwrap().to_path_buf()); - } - } - } - - Ok(files) -} - -/// Check whether a file has been modified since the given timestamp. -/// -/// Note that this function will *only* return true if it can determine with certainty that the -/// file has not been modified. If any errors occur while obtaining the required metadata (e.g. if -/// the file system does not support this metadata), this function will return false. -fn not_modified_since>(time: chrono::DateTime, path: T) -> bool { - let path = path.as_ref(); - - if let Ok(metadata) = path.metadata() { - let last_modified = metadata.modified(); - - if let Ok(last_modified) = last_modified { - let t: chrono::DateTime = last_modified.into(); - let t = t.with_timezone(&Local); - - return t < time; - } - } - - false -} - -#[derive(Debug, PartialEq)] -pub enum BackupType { - Full, - Incremental, -} - -#[derive(Debug)] -pub enum BackupError { - NoFullAncestor, -} - -type BackupResult = Result; - -/// Represents the changes relative to the previous backup -#[derive(Debug)] -pub struct BackupDelta { - /// What files were added/modified in each part of the tarball. - pub added: HashMap>, - /// What files were removed in this backup, in comparison to the previous backup. For full - /// backups, this will always be empty, as they do not consider previous backups. - /// The map stores a separate list for each top-level directory, as the contents of these - /// directories can come for different source directories. - pub removed: HashMap>, -} - -impl BackupDelta { - pub fn new() -> Self { - BackupDelta { - added: HashMap::new(), - removed: HashMap::new(), - } - } - - /// Update the current state so that its result becomes the merge of itself and the other - /// state. - pub fn merge(&mut self, delta: &BackupDelta) { - for (dir, added) in delta.added.iter() { - // Files that were removed in the current state, but added in the new state, are no - // longer removed - if let Some(orig_removed) = self.removed.get_mut(dir) { - orig_removed.retain(|k| !added.contains(k)); - } - - // Newly added files are added to the state as well - if let Some(orig_added) = self.added.get_mut(dir) { - orig_added.extend(added.iter().cloned()); - } else { - self.added.insert(dir.clone(), added.clone()); - } - } - - for (dir, removed) in delta.removed.iter() { - // Files that were originally added, but now deleted are removed from the added list - if let Some(orig_added) = self.added.get_mut(dir) { - orig_added.retain(|k| !removed.contains(k)); - } - - // Newly removed files are added to the state as well - if let Some(orig_removed) = self.removed.get_mut(dir) { - orig_removed.extend(removed.iter().cloned()); - } else { - self.removed.insert(dir.clone(), removed.clone()); - } - } - } - - /// Modify the given state by applying this delta's changes to it - pub fn apply(&self, state: &mut HashMap>) { - // First we add new files, then we remove the old ones - for (dir, added) in self.added.iter() { - if let Some(current) = state.get_mut(dir) { - current.extend(added.iter().cloned()); - } else { - state.insert(dir.clone(), added.clone()); - } - } - - for (dir, removed) in self.removed.iter() { - if let Some(current) = state.get_mut(dir) { - current.retain(|k| !removed.contains(k)); - } - } - } -} - -/// Represents a successful backup -#[derive(Debug)] -pub struct Backup { - previous: Option>, - /// When the backup was started (also corresponds to the name) - start_time: chrono::DateTime, - /// Type of the backup - type_: BackupType, - delta: BackupDelta, -} - -impl Backup { - /// Calculate the full state of the backup by applying all its ancestors delta's in order, - /// starting from the last full ancestor. - pub fn state(&self) -> BackupResult>> { - if self.type_ == BackupType::Full { - let mut state = HashMap::new(); - self.delta.apply(&mut state); - - Ok(state) - } else if let Some(previous) = &self.previous { - let mut state = previous.state()?; - self.delta.apply(&mut state); - - Ok(state) - } else { - return Err(BackupError::NoFullAncestor); - } - } - /// Create a new Full backup, populated with the given directories. - /// - /// # Arguments - /// - /// * `backup_dir` - Directory to store archive in - /// * `dirs` - list of tuples `(path_in_tar, src_dir)` with `path_in_tar` the directory name - /// under which `src_dir`'s contents should be stored in the archive - /// - /// # Returns - /// - /// The `Backup` instance describing this new backup. - pub fn create>( - backup_dir: P, - dirs: Vec<(PathBuf, PathBuf)>, - ) -> io::Result { - let backup_dir = backup_dir.as_ref(); - let start_time = chrono::offset::Utc::now(); - - let filename = format!("{}", start_time.format(FILENAME_FORMAT)); - let path = backup_dir.join(filename); - let tar_gz = File::create(path)?; - let enc = GzEncoder::new(tar_gz, Compression::default()); - let mut ar = tar::Builder::new(enc); - - let mut added: HashMap> = HashMap::new(); - - for (dir_in_tar, src_dir) in dirs { - let files = files(src_dir.clone())?; - - for path in &files { - ar.append_path_with_name(src_dir.join(path), dir_in_tar.join(path))?; - } - - added.insert(dir_in_tar, files); - } - - Ok(Backup { - previous: None, - type_: BackupType::Full, - start_time, - delta: BackupDelta { - added, - removed: HashMap::new(), - }, - }) - } - - /// Create a new incremental backup from a given previous backup - pub fn create_from>( - previous: Arc, - backup_dir: P, - dirs: Vec<(PathBuf, PathBuf)>, - ) -> io::Result { - let backup_dir = backup_dir.as_ref(); - let start_time = chrono::offset::Utc::now(); - - let filename = format!("{}", start_time.format(FILENAME_FORMAT)); - let path = backup_dir.join(filename); - let tar_gz = File::create(path)?; - let enc = GzEncoder::new(tar_gz, Compression::default()); - let mut ar = tar::Builder::new(enc); - - // TODO remove unwrap - let previous_state = previous.state().unwrap(); - let mut delta = BackupDelta::new(); - - for (dir_in_tar, src_dir) in dirs { - let files = files(src_dir.clone())?; - let added_files = files - .iter() - // This explicit negation is because we wish to also include files for which we - // couldn't determine the last modified time - .filter(|p| !not_modified_since(previous.start_time, src_dir.join(p))) - .cloned() - .collect::>(); - - for path in added_files.iter() { - ar.append_path_with_name(src_dir.join(path), dir_in_tar.join(path))?; - } - - delta.added.insert(dir_in_tar.clone(), added_files); - - if let Some(previous_files) = previous_state.get(&dir_in_tar) { - delta.removed.insert( - dir_in_tar, - previous_files.difference(&files).cloned().collect(), - ); - } - } - - Ok(Backup { - previous: Some(previous), - type_: BackupType::Incremental, - start_time, - delta, - }) - } -} +static FILENAME_FORMAT: &str = "%Y-%m-%d_%H-%M-%S.tar.gz"; pub struct BackupManager { backup_dir: PathBuf, config_dir: PathBuf, world_dir: PathBuf, max_backups: u64, - last_backup: Option>, + /// Start time of the last successful backup + last_start_time: Option>, + /// Files contained in the last successful backup + last_files: HashSet<(PathBuf, PathBuf)> } impl BackupManager { @@ -293,23 +37,96 @@ impl BackupManager { config_dir, world_dir, max_backups, - last_backup: None, + last_start_time: None, + last_files: HashSet::new() } } - pub fn create_backup(&mut self) -> io::Result<()> { - let dirs = vec![ - (PathBuf::from("config"), self.config_dir.clone()), + fn files_to_backup(&mut self) -> io::Result> { + let mut dirs = vec![ (PathBuf::from("worlds"), self.world_dir.clone()), + (PathBuf::from("config"), self.config_dir.clone()), ]; + let mut files: HashSet<(PathBuf, PathBuf)> = HashSet::new(); - let backup = if let Some(last_backup) = &self.last_backup { - Backup::create_from(Arc::clone(last_backup), &self.backup_dir, dirs)? - } else { - Backup::create(&self.backup_dir, dirs)? - }; + while let Some((path_in_tar, path)) = dirs.pop() { + for res in path.read_dir()? { + let entry = res?; - self.last_backup = Some(Arc::new(backup)); + if entry.file_name() == "cache" { + continue; + } + + let new_path_in_tar = path_in_tar.join(entry.file_name()); + + // All dirs get expanded recursively, while all files get returned as output + // NOTE: does this remove empty directories from backups? Is this a problem? + if entry.file_type()?.is_dir() { + dirs.push((new_path_in_tar, entry.path())); + } else { + // Only add files that have been updated since the last backup (incremental backup) + if let Some(last_start_time) = self.last_start_time { + let last_modified = entry.path().metadata()?.modified(); + + if let Ok(last_modified) = last_modified { + let t: chrono::DateTime = last_modified.into(); + let t = t.with_timezone(&Local); + + if t < last_start_time { + continue + } + } + } + + files.insert((new_path_in_tar, entry.path())); + } + } + } + + Ok(files) + } + + pub fn create_archive(&mut self) -> io::Result<()> { + let start_time = chrono::offset::Utc::now(); + + let filename = format!("{}", start_time.format(FILENAME_FORMAT)); + let path = self.backup_dir.join(filename); + let tar_gz = File::create(path)?; + let enc = GzEncoder::new(tar_gz, Compression::default()); + let mut ar = tar::Builder::new(enc); + + let files = self.files_to_backup()?; + + for (path_in_tar, path) in &files { + ar.append_path_with_name(path, path_in_tar)?; + } + + let deleted_files = self.last_files.difference(&files); + + println!("{} {}", files.len(), self.last_files.len()); + + for (path_in_tar, path) in deleted_files { + println!("{path_in_tar:?}: {path:?}"); + } + + // TODO re-add this info file in some way + // 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)?; + + // After a successful backup, we store the original metadata + self.last_start_time = Some(start_time); + self.last_files = files; Ok(()) } diff --git a/src/server/command.rs b/src/server/command.rs index 641c6b8..7b6d948 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -105,19 +105,14 @@ impl ServerCommand { Ok(()) } - /// Canonicalize all paths to absolute paths. Without this command, all paths will be - /// interpreted relatively from the config directory. - pub fn canonicalize(&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 - self.jar = self.jar.canonicalize()?; - self.config_dir = self.config_dir.canonicalize()?; - self.world_dir = self.world_dir.canonicalize()?; - self.backup_dir = self.backup_dir.canonicalize()?; + 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()?; - Ok(()) - } - - fn create_cmd(&self) -> std::process::Command { + self.accept_eula()?; let mut cmd = Command::new(&self.java); // Apply JVM optimisation flags @@ -131,6 +126,15 @@ impl ServerCommand { "-XX:+UnlockExperimentalVMOptions", "-XX:+DisableExplicitGC", "-XX:+AlwaysPreTouch", + "-XX:G1HeapWastePercent=5", + "-XX:G1MixedGCCountTarget=4", + "-XX:G1MixedGCLiveThresholdPercent=90", + "-XX:G1RSetUpdatingPauseTimePercent=5", + "-XX:SurvivorRatio=32", + "-XX:+PerfDisableSharedMem", + "-XX:MaxTenuringThreshold=1", + "-Dusing.aikars.flags=https://mcflags.emc.gs", + "-Daikars.new.flags=true", ]); if self.xms > 12 * 1024 { @@ -139,84 +143,36 @@ impl ServerCommand { "-XX:G1MaxNewSizePercent=50", "-XX:G1HeapRegionSize=16M", "-XX:G1ReservePercent=15", + "-XX:InitiatingHeapOccupancyPercent=20", ]); } else { cmd.args([ "-XX:G1NewSizePercent=30", "-XX:G1MaxNewSizePercent=40", "-XX:G1HeapRegionSize=8M", - "-XX:G1ReservePercent=20", + "-XX:G1ReservePercent=15", + "-XX:InitiatingHeapOccupancyPercent=15", ]); } - cmd.args(["-XX:G1HeapWastePercent=5", "-XX:G1MixedGCCountTarget=4"]); - - if self.xms > 12 * 1024 { - cmd.args(["-XX:InitiatingHeapOccupancyPercent=20"]); - } else { - cmd.args(["-XX:InitiatingHeapOccupancyPercent=15"]); - } - - cmd.args([ - "-XX:G1MixedGCLiveThresholdPercent=90", - "-XX:G1RSetUpdatingPauseTimePercent=5", - "-XX:SurvivorRatio=32", - "-XX:+PerfDisableSharedMem", - "-XX:MaxTenuringThreshold=1", - "-Dusing.aikars.flags=https://mcflags.emc.gs", - "-Daikars.new.flags=true", - ]); - - cmd.current_dir(&self.config_dir) + cmd.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()); - cmd - } - - pub fn spawn(&mut self) -> std::io::Result { - let mut cmd = self.create_cmd(); - self.accept_eula()?; let child = cmd.spawn()?; Ok(ServerProcess::new( self.type_, - self.version.clone(), - self.config_dir.clone(), - self.world_dir.clone(), - self.backup_dir.clone(), + self.version, + config_dir, + world_dir, + backup_dir, self.max_backups, child, )) } } - -impl fmt::Display for ServerCommand { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let cmd = self.create_cmd(); - - writeln!(f, "Command: {}", self.java)?; - writeln!(f, "Working dir: {}", self.config_dir.as_path().display())?; - - // Print command env vars - writeln!(f, "Environment:")?; - - for (key, val) in cmd.get_envs().filter(|(_, v)| v.is_some()) { - let val = val.unwrap(); - writeln!(f, " {}={}", key.to_string_lossy(), val.to_string_lossy())?; - } - - // Print command arguments - writeln!(f, "Arguments:")?; - - for arg in cmd.get_args() { - writeln!(f, " {}", arg.to_string_lossy())?; - } - - Ok(()) - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 4c2beb2..ed5cb21 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,5 @@ mod backups; mod command; -mod path; mod process; pub use backups::BackupManager; diff --git a/src/server/path.rs b/src/server/path.rs deleted file mode 100644 index d9df799..0000000 --- a/src/server/path.rs +++ /dev/null @@ -1,19 +0,0 @@ -use chrono::Utc; -use std::collections::HashSet; -use std::path::PathBuf; -use std::{fs, io}; - -struct ReadDirRecursive { - ignored_dirs: HashSet, - read_dir: Option, - stack: Vec, -} - -impl ReadDirRecursive { - // pub fn new() -} - -trait PathExt { - fn modified_since(timestamp: chrono::DateTime) -> bool; - fn read_dir_recusive() -> ReadDirRecursive; -} diff --git a/src/server/process.rs b/src/server/process.rs index 6edc484..7555aa3 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -89,7 +89,7 @@ impl ServerProcess { std::thread::sleep(std::time::Duration::from_secs(10)); let start_time = chrono::offset::Utc::now(); - let res = self.backups.create_backup(); + let res = self.backups.create_archive(); if res.is_ok() { self.backups.remove_old_backups()?;