Compare commits

..

No commits in common. "03e21fda87c9368ed1aaabca93acbeeed65ffb4d" and "0a459ee30b840fbc08ab85c1f04e04c7351c8a27" have entirely different histories.

10 changed files with 125 additions and 233 deletions

View File

@ -1,3 +1,3 @@
[alias] [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" 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"

View File

@ -1,9 +1,3 @@
mod config;
mod meta;
pub use config::ManagerConfig;
pub use meta::MetaManager;
use super::Backup; use super::Backup;
use chrono::Utc; use chrono::Utc;
use serde::Deserialize; use serde::Deserialize;
@ -32,6 +26,7 @@ where
{ {
const METADATA_FILE: &str = "alex.json"; const METADATA_FILE: &str = "alex.json";
/// Initialize a new instance of a `BackupManager`.
pub fn new<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>( pub fn new<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>(
backup_dir: P1, backup_dir: P1,
config_dir: P2, config_dir: P2,

View File

@ -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<Self, Self::Err> {
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)
}
}
}

View File

@ -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<T>
where
T: Clone + Serialize + for<'de> Deserialize<'de>,
{
backup_dir: PathBuf,
config_dir: PathBuf,
world_dir: PathBuf,
default_metadata: T,
managers: HashMap<String, Manager<T>>,
}
impl<T> MetaManager<T>
where
T: Clone + Serialize + for<'de> Deserialize<'de>,
{
pub fn new<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>(
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<ManagerConfig>) -> 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<chrono::DateTime<Utc>> {
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(())
}
}
}

View File

@ -1,11 +1,9 @@
mod delta; mod delta;
pub mod manager; mod manager;
mod path; mod path;
use delta::Delta; use delta::Delta;
pub use manager::Manager; pub use manager::Manager;
pub use manager::ManagerConfig;
pub use manager::MetaManager;
use chrono::Utc; use chrono::Utc;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;

View File

@ -1,4 +1,3 @@
use crate::backup::ManagerConfig;
use crate::server::ServerType; use crate::server::ServerType;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use std::path::PathBuf; use std::path::PathBuf;
@ -36,10 +35,34 @@ pub struct Cli {
)] )]
pub backup: PathBuf, pub backup: PathBuf,
/// What backup layers to employ, provided as a list of tuples name,frequency,chain_len,chains /// Length of a backup chain
/// delimited by semicolons (;). #[arg(
#[arg(long, env = "ALEX_LAYERS", global = true, value_delimiter = ';')] short = 'l',
pub layers: Vec<ManagerConfig>, 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 /// Type of server
#[arg(long, default_value = "unknown", env = "ALEX_SERVER", global = true)] #[arg(long, default_value = "unknown", env = "ALEX_SERVER", global = true)]

View File

@ -5,7 +5,7 @@ mod signals;
mod stdin; mod stdin;
use clap::Parser; use clap::Parser;
use cli::{Cli, Commands, RunArgs}; use cli::{BackupArgs, Cli, Commands, RunArgs};
use std::io; use std::io;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -13,7 +13,7 @@ fn backups_thread(counter: Arc<Mutex<server::ServerProcess>>) {
loop { loop {
let next_scheduled_time = { let next_scheduled_time = {
let server = counter.lock().unwrap(); let server = counter.lock().unwrap();
server.backups.next_scheduled_time().unwrap() server.backups.next_scheduled_time()
}; };
let now = chrono::offset::Utc::now(); let now = chrono::offset::Utc::now();
@ -39,9 +39,11 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> {
.config(cli.config.clone()) .config(cli.config.clone())
.world(cli.world.clone()) .world(cli.world.clone())
.backup(cli.backup.clone()) .backup(cli.backup.clone())
.managers(cli.layers.clone())
.xms(args.xms) .xms(args.xms)
.xmx(args.xmx); .xmx(args.xmx)
.chain_len(cli.chain_len)
.chains_to_keep(cli.chains)
.frequency(cli.frequency);
cmd.canonicalize()?; cmd.canonicalize()?;
if args.dry { if args.dry {
@ -52,7 +54,7 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> {
let counter = Arc::new(Mutex::new(cmd.spawn()?)); let counter = Arc::new(Mutex::new(cmd.spawn()?));
if !cli.layers.is_empty() { if cli.frequency > 0 {
let clone = Arc::clone(&counter); let clone = Arc::clone(&counter);
std::thread::spawn(move || backups_thread(clone)); 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) signals::handle_signals(&mut signals, counter)
} }
// fn commands_backup(cli: &Cli, _args: &BackupArgs) -> io::Result<()> { fn commands_backup(cli: &Cli, _args: &BackupArgs) -> io::Result<()> {
// let metadata = server::Metadata { let metadata = server::Metadata {
// server_type: cli.server, server_type: cli.server,
// server_version: cli.server_version.clone(), server_version: cli.server_version.clone(),
// }; };
// let mut meta = MetaManager::new(cli.backup, cli.config, cli.world, metadata);
// meta.add_all(&cli.layers)?;
// manager.create_backup()?; let mut manager = backup::Manager::new(
// manager.remove_old_backups() 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<()> { fn main() -> io::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Commands::Run(args) => command_run(&cli, args), Commands::Run(args) => command_run(&cli, args),
Commands::Backup(_) => Ok(()), Commands::Backup(args) => commands_backup(&cli, args),
} }
} }

View File

@ -1,12 +1,40 @@
use crate::backup::ManagerConfig; use crate::backup::Manager as BackupManager;
use crate::backup::MetaManager; use crate::server::ServerProcess;
use crate::server::{Metadata, ServerProcess, ServerType}; use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; 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 { pub struct ServerCommand {
type_: ServerType, type_: ServerType,
version: String, version: String,
@ -17,7 +45,9 @@ pub struct ServerCommand {
backup_dir: PathBuf, backup_dir: PathBuf,
xms: u64, xms: u64,
xmx: u64, xmx: u64,
managers: Vec<ManagerConfig>, chain_len: u64,
chains_to_keep: u64,
frequency: u32,
} }
impl ServerCommand { impl ServerCommand {
@ -32,7 +62,9 @@ impl ServerCommand {
backup_dir: PathBuf::from("backups"), backup_dir: PathBuf::from("backups"),
xms: 1024, xms: 1024,
xmx: 2048, xmx: 2048,
managers: Vec::new(), chain_len: 4,
chains_to_keep: 7,
frequency: 0,
} }
} }
@ -73,9 +105,18 @@ impl ServerCommand {
self self
} }
pub fn managers(mut self, configs: Vec<ManagerConfig>) -> Self { pub fn chain_len(mut self, v: u64) -> Self {
self.managers = configs; 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 self
} }
@ -166,19 +207,22 @@ impl ServerCommand {
server_type: self.type_, server_type: self.type_,
server_version: self.version.clone(), server_version: self.version.clone(),
}; };
let mut meta = MetaManager::new( let mut manager = BackupManager::new(
self.backup_dir.clone(), self.backup_dir.clone(),
self.config_dir.clone(), self.config_dir.clone(),
self.world_dir.clone(), self.world_dir.clone(),
metadata, 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(); let mut cmd = self.create_cmd();
self.accept_eula()?; self.accept_eula()?;
let child = cmd.spawn()?; let child = cmd.spawn()?;
Ok(ServerProcess::new(meta, child)) Ok(ServerProcess::new(manager, child))
} }
} }

View File

@ -1,36 +1,5 @@
mod command; mod command;
mod process; mod process;
pub use command::ServerCommand; pub use command::{Metadata, ServerCommand, ServerType};
pub use process::ServerProcess; 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,
}

View File

@ -1,15 +1,15 @@
use crate::backup::MetaManager; use crate::backup::Manager as BackupManager;
use crate::server::Metadata; use crate::server::Metadata;
use std::io::Write; use std::io::Write;
use std::process::Child; use std::process::Child;
pub struct ServerProcess { pub struct ServerProcess {
child: Child, child: Child,
pub backups: MetaManager<Metadata>, pub backups: BackupManager<Metadata>,
} }
impl ServerProcess { impl ServerProcess {
pub fn new(manager: MetaManager<Metadata>, child: Child) -> ServerProcess { pub fn new(manager: BackupManager<Metadata>, child: Child) -> ServerProcess {
ServerProcess { ServerProcess {
child, child,
backups: manager, backups: manager,
@ -48,8 +48,7 @@ impl ServerProcess {
/// Perform a backup by disabling the server's save feature and flushing its data, before /// Perform a backup by disabling the server's save feature and flushing its data, before
/// creating an archive file. /// creating an archive file.
pub fn backup(&mut self) -> std::io::Result<()> { pub fn backup(&mut self) -> std::io::Result<()> {
let layer_name = String::from(self.backups.next_scheduled_layer().unwrap()); self.custom("say backing up server")?;
self.custom(&format!("say starting backup for layer '{}'", layer_name))?;
// Make sure the server isn't modifying the files during the backup // Make sure the server isn't modifying the files during the backup
self.custom("save-off")?; self.custom("save-off")?;
@ -60,12 +59,16 @@ impl ServerProcess {
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
let start_time = chrono::offset::Utc::now(); 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 // The server's save feature needs to be enabled again even if the archive failed to create
self.custom("save-on")?; self.custom("save-on")?;
self.custom("save-all")?; self.custom("save-all")?;
if res.is_ok() {
self.backups.remove_old_backups()?;
}
let duration = chrono::offset::Utc::now() - start_time; let duration = chrono::offset::Utc::now() - start_time;
let duration_str = format!( let duration_str = format!(
"{}m{}s", "{}m{}s",
@ -74,14 +77,11 @@ impl ServerProcess {
); );
if res.is_ok() { if res.is_ok() {
self.custom(&format!( self.custom(&format!("say server backed up in {}", duration_str))?;
"say backup created for layer '{}' in {}",
layer_name, duration_str
))?;
} else { } else {
self.custom(&format!( self.custom(&format!(
"an error occured after {} while creating backup for layer '{}'", "an error occured after {} while backing up the server",
duration_str, layer_name duration_str
))?; ))?;
} }