diff --git a/.cargo/config.toml b/.cargo/config.toml index c5834ea..ed8b85c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] -runs = "run -- run --config data/config --backup data/backups --world data/worlds --jar paper-1.19.4-550.jar --layers 2min,2,4,4;3min,3,2,2" +runs = "run -- run --config data/config --backup data/backups --world data/worlds --jar paper-1.19.4-550.jar --frequency 2" 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/src/backup/manager/mod.rs b/src/backup/manager.rs similarity index 98% rename from src/backup/manager/mod.rs rename to src/backup/manager.rs index 6c991c0..cc6dd0c 100644 --- a/src/backup/manager/mod.rs +++ b/src/backup/manager.rs @@ -1,9 +1,3 @@ -mod config; -mod meta; - -pub use config::ManagerConfig; -pub use meta::MetaManager; - use super::Backup; use chrono::Utc; use serde::Deserialize; @@ -32,6 +26,7 @@ where { const METADATA_FILE: &str = "alex.json"; + /// Initialize a new instance of a `BackupManager`. pub fn new, P2: Into, P3: Into>( backup_dir: P1, config_dir: P2, diff --git a/src/backup/manager/config.rs b/src/backup/manager/config.rs deleted file mode 100644 index 8adbd5a..0000000 --- a/src/backup/manager/config.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::str::FromStr; - -#[derive(Clone, Debug)] -pub struct ManagerConfig { - pub name: String, - pub frequency: u32, - pub chains: u64, - pub chain_len: u64, -} - -#[derive(Debug)] -pub struct ParseManagerConfigErr; - -impl Error for ParseManagerConfigErr {} - -impl fmt::Display for ParseManagerConfigErr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "parse manager config err") - } -} - -impl FromStr for ManagerConfig { - type Err = ParseManagerConfigErr; - - fn from_str(s: &str) -> Result { - let splits: Vec<&str> = s.split(',').collect(); - - if let [name, frequency, chains, chain_len] = splits[..] { - let name: String = name.parse().map_err(|_| ParseManagerConfigErr)?; - let frequency: u32 = frequency.parse().map_err(|_| ParseManagerConfigErr)?; - let chains: u64 = chains.parse().map_err(|_| ParseManagerConfigErr)?; - let chain_len: u64 = chain_len.parse().map_err(|_| ParseManagerConfigErr)?; - - Ok(ManagerConfig { - name, - chains, - chain_len, - frequency, - }) - } else { - Err(ParseManagerConfigErr) - } - } -} diff --git a/src/backup/manager/meta.rs b/src/backup/manager/meta.rs deleted file mode 100644 index bf5cc4b..0000000 --- a/src/backup/manager/meta.rs +++ /dev/null @@ -1,102 +0,0 @@ -use super::{Manager, ManagerConfig}; -use chrono::Utc; -use serde::Deserialize; -use serde::Serialize; -use std::collections::HashMap; -use std::io; -use std::path::PathBuf; - -pub struct MetaManager -where - T: Clone + Serialize + for<'de> Deserialize<'de>, -{ - backup_dir: PathBuf, - config_dir: PathBuf, - world_dir: PathBuf, - default_metadata: T, - managers: HashMap>, -} - -impl MetaManager -where - T: Clone + Serialize + for<'de> Deserialize<'de>, -{ - pub fn new, P2: Into, P3: Into>( - backup_dir: P1, - config_dir: P2, - world_dir: P3, - default_metadata: T, - ) -> Self { - MetaManager { - backup_dir: backup_dir.into(), - config_dir: config_dir.into(), - world_dir: world_dir.into(), - default_metadata, - managers: HashMap::new(), - } - } - - pub fn add(&mut self, config: &ManagerConfig) -> io::Result<()> { - // Backup dir itself should exist, but we control its contents, so we can create - // separate directories for each layer - let path = self.backup_dir.join(&config.name); - - // If the directory already exists, that's okay - match std::fs::create_dir(&path) { - Ok(()) => (), - Err(e) => match e.kind() { - io::ErrorKind::AlreadyExists => (), - _ => return Err(e), - }, - }; - - let mut manager = Manager::new( - path, - self.config_dir.clone(), - self.world_dir.clone(), - self.default_metadata.clone(), - config.chain_len, - config.chains, - chrono::Duration::minutes(config.frequency.into()), - ); - manager.load()?; - self.managers.insert(config.name.clone(), manager); - - Ok(()) - } - - pub fn add_all(&mut self, configs: &Vec) -> io::Result<()> { - for config in configs { - self.add(config)?; - } - - Ok(()) - } - - pub fn next_scheduled_layer(&self) -> Option<&str> { - self.managers - .iter() - .min_by_key(|(_, m)| m.next_scheduled_time()) - .map(|(k, _)| k.as_str()) - } - - pub fn next_scheduled_time(&self) -> Option> { - self.managers - .values() - .map(|m| m.next_scheduled_time()) - .min() - } - - pub fn perform_backup_cycle(&mut self) -> io::Result<()> { - if let Some(manager) = self - .managers - .values_mut() - .min_by_key(|m| m.next_scheduled_time()) - { - manager.create_backup()?; - manager.remove_old_backups() - } else { - Ok(()) - } - } -} diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 780c557..5b12b8c 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -1,11 +1,9 @@ mod delta; -pub mod manager; +mod manager; mod path; use delta::Delta; pub use manager::Manager; -pub use manager::ManagerConfig; -pub use manager::MetaManager; use chrono::Utc; use flate2::write::GzEncoder; diff --git a/src/cli.rs b/src/cli.rs index 8d44af3..d9ea9a9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,3 @@ -use crate::backup::ManagerConfig; use crate::server::ServerType; use clap::{Args, Parser, Subcommand}; use std::path::PathBuf; @@ -36,10 +35,34 @@ pub struct Cli { )] pub backup: PathBuf, - /// What backup layers to employ, provided as a list of tuples name,frequency,chain_len,chains - /// delimited by semicolons (;). - #[arg(long, env = "ALEX_LAYERS", global = true, value_delimiter = ';')] - pub layers: Vec, + /// Length of a backup chain + #[arg( + short = 'l', + long, + default_value_t = 4, + env = "ALEX_CHAIN_LEN", + global = true + )] + pub chain_len: u64, + /// How many backup chains to keep + #[arg( + short = 'n', + long, + default_value_t = 7, + env = "ALEX_CHAINS", + global = true + )] + pub chains: u64, + + /// How frequently to perform a backup, in minutes; 0 to disable. + #[arg( + short = 't', + long, + default_value_t = 0, + env = "ALEX_FREQUENCY", + global = true + )] + pub frequency: u32, /// Type of server #[arg(long, default_value = "unknown", env = "ALEX_SERVER", global = true)] diff --git a/src/main.rs b/src/main.rs index b7187e4..f4b9ef2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod signals; mod stdin; use clap::Parser; -use cli::{Cli, Commands, RunArgs}; +use cli::{BackupArgs, Cli, Commands, RunArgs}; use std::io; use std::sync::{Arc, Mutex}; @@ -13,7 +13,7 @@ fn backups_thread(counter: Arc>) { loop { let next_scheduled_time = { let server = counter.lock().unwrap(); - server.backups.next_scheduled_time().unwrap() + server.backups.next_scheduled_time() }; let now = chrono::offset::Utc::now(); @@ -39,9 +39,11 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { .config(cli.config.clone()) .world(cli.world.clone()) .backup(cli.backup.clone()) - .managers(cli.layers.clone()) .xms(args.xms) - .xmx(args.xmx); + .xmx(args.xmx) + .chain_len(cli.chain_len) + .chains_to_keep(cli.chains) + .frequency(cli.frequency); cmd.canonicalize()?; if args.dry { @@ -52,7 +54,7 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { let counter = Arc::new(Mutex::new(cmd.spawn()?)); - if !cli.layers.is_empty() { + if cli.frequency > 0 { let clone = Arc::clone(&counter); std::thread::spawn(move || backups_thread(clone)); } @@ -65,23 +67,32 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { signals::handle_signals(&mut signals, counter) } -// fn commands_backup(cli: &Cli, _args: &BackupArgs) -> io::Result<()> { -// let metadata = server::Metadata { -// server_type: cli.server, -// server_version: cli.server_version.clone(), -// }; -// let mut meta = MetaManager::new(cli.backup, cli.config, cli.world, metadata); -// meta.add_all(&cli.layers)?; +fn commands_backup(cli: &Cli, _args: &BackupArgs) -> io::Result<()> { + let metadata = server::Metadata { + server_type: cli.server, + server_version: cli.server_version.clone(), + }; -// manager.create_backup()?; -// manager.remove_old_backups() -// } + let mut manager = backup::Manager::new( + cli.backup.clone(), + cli.config.clone(), + cli.world.clone(), + metadata, + cli.chain_len, + cli.chains, + chrono::Duration::minutes(cli.frequency.into()), + ); + manager.load()?; + + manager.create_backup()?; + manager.remove_old_backups() +} fn main() -> io::Result<()> { let cli = Cli::parse(); match &cli.command { Commands::Run(args) => command_run(&cli, args), - Commands::Backup(_) => Ok(()), + Commands::Backup(args) => commands_backup(&cli, args), } } diff --git a/src/server/command.rs b/src/server/command.rs index ec1b302..1ce5ce0 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,12 +1,40 @@ -use crate::backup::ManagerConfig; -use crate::backup::MetaManager; -use crate::server::{Metadata, ServerProcess, ServerType}; +use crate::backup::Manager as BackupManager; +use crate::server::ServerProcess; +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)] +pub enum ServerType { + Unknown, + Paper, + Forge, + Vanilla, +} + +impl fmt::Display for ServerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + ServerType::Unknown => "Unknown", + ServerType::Paper => "PaperMC", + ServerType::Forge => "Forge", + ServerType::Vanilla => "Vanilla", + }; + + write!(f, "{}", s) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Metadata { + pub server_type: ServerType, + pub server_version: String, +} + pub struct ServerCommand { type_: ServerType, version: String, @@ -17,7 +45,9 @@ pub struct ServerCommand { backup_dir: PathBuf, xms: u64, xmx: u64, - managers: Vec, + chain_len: u64, + chains_to_keep: u64, + frequency: u32, } impl ServerCommand { @@ -32,7 +62,9 @@ impl ServerCommand { backup_dir: PathBuf::from("backups"), xms: 1024, xmx: 2048, - managers: Vec::new(), + chain_len: 4, + chains_to_keep: 7, + frequency: 0, } } @@ -73,9 +105,18 @@ impl ServerCommand { self } - pub fn managers(mut self, configs: Vec) -> Self { - self.managers = configs; + pub fn chain_len(mut self, v: u64) -> Self { + self.chain_len = v; + self + } + pub fn chains_to_keep(mut self, v: u64) -> Self { + self.chains_to_keep = v; + self + } + + pub fn frequency(mut self, v: u32) -> Self { + self.frequency = v; self } @@ -166,19 +207,22 @@ impl ServerCommand { server_type: self.type_, server_version: self.version.clone(), }; - let mut meta = MetaManager::new( + let mut manager = BackupManager::new( self.backup_dir.clone(), self.config_dir.clone(), self.world_dir.clone(), metadata, + self.chain_len, + self.chains_to_keep, + chrono::Duration::minutes(self.frequency.into()), ); - meta.add_all(&self.managers)?; + manager.load()?; let mut cmd = self.create_cmd(); self.accept_eula()?; let child = cmd.spawn()?; - Ok(ServerProcess::new(meta, child)) + Ok(ServerProcess::new(manager, child)) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 09f90a5..0f01c9a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,36 +1,5 @@ mod command; mod process; -pub use command::ServerCommand; +pub use command::{Metadata, ServerCommand, ServerType}; pub use process::ServerProcess; - -use clap::ValueEnum; -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)] -pub enum ServerType { - Unknown, - Paper, - Forge, - Vanilla, -} - -impl fmt::Display for ServerType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - ServerType::Unknown => "Unknown", - ServerType::Paper => "PaperMC", - ServerType::Forge => "Forge", - ServerType::Vanilla => "Vanilla", - }; - - write!(f, "{}", s) - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct Metadata { - pub server_type: ServerType, - pub server_version: String, -} diff --git a/src/server/process.rs b/src/server/process.rs index 7748c1d..17b86b1 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -1,15 +1,15 @@ -use crate::backup::MetaManager; +use crate::backup::Manager as BackupManager; use crate::server::Metadata; use std::io::Write; use std::process::Child; pub struct ServerProcess { child: Child, - pub backups: MetaManager, + pub backups: BackupManager, } impl ServerProcess { - pub fn new(manager: MetaManager, child: Child) -> ServerProcess { + pub fn new(manager: BackupManager, child: Child) -> ServerProcess { ServerProcess { child, backups: manager, @@ -48,8 +48,7 @@ impl ServerProcess { /// 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<()> { - let layer_name = String::from(self.backups.next_scheduled_layer().unwrap()); - self.custom(&format!("say starting backup for layer '{}'", layer_name))?; + self.custom("say backing up server")?; // Make sure the server isn't modifying the files during the backup self.custom("save-off")?; @@ -60,12 +59,16 @@ impl ServerProcess { std::thread::sleep(std::time::Duration::from_secs(10)); let start_time = chrono::offset::Utc::now(); - let res = self.backups.perform_backup_cycle(); + let res = self.backups.create_backup(); // The server's save feature needs to be enabled again even if the archive failed to create self.custom("save-on")?; self.custom("save-all")?; + if res.is_ok() { + self.backups.remove_old_backups()?; + } + let duration = chrono::offset::Utc::now() - start_time; let duration_str = format!( "{}m{}s", @@ -74,14 +77,11 @@ impl ServerProcess { ); if res.is_ok() { - self.custom(&format!( - "say backup created for layer '{}' in {}", - layer_name, duration_str - ))?; + self.custom(&format!("say server backed up in {}", duration_str))?; } else { self.custom(&format!( - "an error occured after {} while creating backup for layer '{}'", - duration_str, layer_name + "an error occured after {} while backing up the server", + duration_str ))?; }