mirror of
https://git.ahines.net/joeyahines/Albatross.git
synced 2026-11-08 09:13:57 +00:00
Added compressed backups and removing old backups
+ Added docs + Backups are compressed as `tar.gz` files + a configurable amount of backups are kept
This commit is contained in:
1 parent
c11748d696
commit
c2cdbd56ac
4 files changed
+245
-38
No files matched your search
@@ -1,14 +1,19 @@
|
||||
use config::{Config, ConfigError, File};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// World types supported
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub enum WorldType {
|
||||
/// The End (DIM1)
|
||||
END,
|
||||
/// Nether (DIM-1)
|
||||
NETHER,
|
||||
/// Overworld
|
||||
OVERWORLD,
|
||||
}
|
||||
|
||||
impl From<String> for WorldType {
|
||||
/// Convert config strings to WorldType
|
||||
fn from(string: String) -> Self {
|
||||
match string.as_str() {
|
||||
"END" => WorldType::END,
|
||||
@@ -18,6 +23,7 @@ impl From<String> for WorldType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for individual WorldConfig
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct WorldConfig {
|
||||
pub world_name: String,
|
||||
@@ -25,6 +31,7 @@ pub struct WorldConfig {
|
||||
pub world_type: Option<WorldType>,
|
||||
}
|
||||
|
||||
/// Config for doing backups
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct BackupConfig {
|
||||
pub minecraft_dir: PathBuf,
|
||||
@@ -32,6 +39,7 @@ pub struct BackupConfig {
|
||||
pub backups_to_keep: u64,
|
||||
}
|
||||
|
||||
/// Configs
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct AlbatrossConfig {
|
||||
pub backup: BackupConfig,
|
||||
@@ -39,6 +47,7 @@ pub struct AlbatrossConfig {
|
||||
}
|
||||
|
||||
impl AlbatrossConfig {
|
||||
/// Create new backup from file
|
||||
pub fn new(config_path: &str) -> Result<Self, ConfigError> {
|
||||
let mut cfg = Config::new();
|
||||
cfg.merge(File::with_name(config_path))?;
|
||||
|
||||
+157
-7
@@ -3,18 +3,23 @@ extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use chrono::Utc;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use clap::{App, Arg};
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
use regex::Regex;
|
||||
use std::fs::{copy, create_dir, create_dir_all};
|
||||
use std::fs::{copy, create_dir, create_dir_all, remove_dir_all, remove_file, DirEntry, File};
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod albatross_config;
|
||||
|
||||
use albatross_config::{AlbatrossConfig, WorldConfig, WorldType};
|
||||
|
||||
/// Struct to store information about the region
|
||||
struct Region {
|
||||
/// x position of the region
|
||||
x: i64,
|
||||
/// y position of the region
|
||||
y: i64,
|
||||
}
|
||||
|
||||
@@ -34,6 +39,11 @@ impl Region {
|
||||
}
|
||||
}
|
||||
|
||||
/// Backup a directory
|
||||
///
|
||||
/// # Param
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
fn backup_dir(
|
||||
dir_name: &str,
|
||||
world_path: &PathBuf,
|
||||
@@ -56,6 +66,13 @@ fn backup_dir(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Backup the regions
|
||||
///
|
||||
/// # Param
|
||||
/// * `dir_name` - name of the backup folder
|
||||
/// * `save_radius` - block radius to save
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
fn backup_region(
|
||||
dir_name: &str,
|
||||
save_radius: u64,
|
||||
@@ -91,6 +108,12 @@ fn backup_region(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Backup a world
|
||||
///
|
||||
/// # Param
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
/// * `world_config` - world config options
|
||||
fn backup_world(
|
||||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
@@ -112,6 +135,12 @@ fn backup_world(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Backup the overworld
|
||||
///
|
||||
/// # Param
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
/// * `world_config` - world config options
|
||||
fn backup_overworld(
|
||||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
@@ -121,13 +150,119 @@ fn backup_overworld(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Backup the nether
|
||||
///
|
||||
/// # Param
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
/// * `world_config` - world config options
|
||||
fn backup_nether(
|
||||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
world_config: &WorldConfig,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let mut nether_path = world_path.clone();
|
||||
nether_path.push("DIM-1");
|
||||
|
||||
backup_world(nether_path, backup_path, world_config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Backup the end
|
||||
///
|
||||
/// # Param
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
/// * `world_config` - world config options
|
||||
fn backup_end(
|
||||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
world_config: &WorldConfig,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let mut end_path = world_path.clone();
|
||||
end_path.push("DIM1");
|
||||
|
||||
backup_world(end_path, backup_path, world_config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compress the backup after the files have been copied
|
||||
///
|
||||
/// # Param
|
||||
/// * `tmp_dir`: tmp directory with the backed up files
|
||||
/// * `output_file`: output archive
|
||||
fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), std::io::Error> {
|
||||
let archive = File::create(output_file)?;
|
||||
let enc = GzEncoder::new(archive, Compression::default());
|
||||
let mut tar_builder = tar::Builder::new(enc);
|
||||
tar_builder.append_dir_all(".", tmp_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the time of the backup from a file name
|
||||
///
|
||||
/// # Param
|
||||
/// * `archive_entry`: archive entry
|
||||
fn get_time_from_file_name(
|
||||
archive_entry: &DirEntry,
|
||||
) -> Result<Option<NaiveDateTime>, std::io::Error> {
|
||||
let file_name = archive_entry.file_name().to_str().unwrap().to_string();
|
||||
let name: Vec<&str> = file_name.split("_").collect();
|
||||
|
||||
Ok(chrono::NaiveDateTime::parse_from_str(name[0], "%d-%m-%y-%H:%M:%S").ok())
|
||||
}
|
||||
|
||||
/// Removes the old backups from the ouput directory
|
||||
///
|
||||
/// # Params
|
||||
/// * `output_dir` - output directory containing
|
||||
/// * `keep` - number of backups to keep
|
||||
fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<(), std::io::Error> {
|
||||
let mut backups = vec![];
|
||||
|
||||
for entry in output_dir.read_dir()? {
|
||||
let entry = entry?;
|
||||
|
||||
if let Some(ext) = entry.path().extension() {
|
||||
if ext == "gz" {
|
||||
backups.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if backups.len() > keep as usize {
|
||||
backups.sort_by(|a, b| {
|
||||
let a_time = get_time_from_file_name(a).unwrap().unwrap();
|
||||
let b_time = get_time_from_file_name(b).unwrap().unwrap();
|
||||
|
||||
b_time.cmp(&a_time)
|
||||
});
|
||||
|
||||
let number_to_remove = backups.len() - keep as usize;
|
||||
|
||||
for _i in 0..number_to_remove {
|
||||
let oldest = backups.pop().unwrap();
|
||||
remove_file(oldest.path())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Backup the configured worlds from a minecraft server
|
||||
///
|
||||
/// # Params
|
||||
/// * `cfg` - config file
|
||||
fn do_backup(cfg: AlbatrossConfig) -> Result<(), std::io::Error> {
|
||||
let server_base_dir = cfg.backup.minecraft_dir.clone();
|
||||
let worlds = cfg.world_config.unwrap().clone();
|
||||
let time_str = Utc::now().format("%d-%m-%y_%H:%M%S").to_string();
|
||||
let backup_name = format!("{}-backup", time_str);
|
||||
let time_str = Utc::now().format("%d-%m-%y-%H:%M:%S").to_string();
|
||||
let backup_name = format!("{}_backup.tar.gz", time_str);
|
||||
let mut output_archive = cfg.backup.output_dir.clone();
|
||||
output_archive.push(backup_name);
|
||||
let mut tmp_dir = cfg.backup.output_dir.clone();
|
||||
tmp_dir.push(backup_name);
|
||||
tmp_dir.push("tmp");
|
||||
remove_dir_all(&tmp_dir).ok();
|
||||
|
||||
create_dir_all(tmp_dir.clone()).unwrap();
|
||||
|
||||
@@ -143,18 +278,33 @@ fn do_backup(cfg: AlbatrossConfig) -> Result<(), std::io::Error> {
|
||||
if world_dir.exists() && world_dir.is_dir() {
|
||||
match world_type {
|
||||
WorldType::OVERWORLD => {
|
||||
backup_overworld(world_dir, tmp_dir.clone(), &world)?;
|
||||
backup_overworld(world_dir.clone(), tmp_dir.clone(), &world)?;
|
||||
backup_dir("playerdata", &world_dir, &tmp_dir)?;
|
||||
}
|
||||
WorldType::NETHER => {
|
||||
backup_nether(world_dir, tmp_dir.clone(), &world)?;
|
||||
}
|
||||
WorldType::END => {
|
||||
backup_end(world_dir, tmp_dir.clone(), &world)?;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
} else {
|
||||
println!("World \"{}\" not found", world_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
compress_backup(&tmp_dir, &output_archive)?;
|
||||
|
||||
remove_dir_all(&tmp_dir)?;
|
||||
|
||||
remove_old_backups(&cfg.backup.output_dir, cfg.backup.backups_to_keep)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Albatross
|
||||
///
|
||||
/// Run backups of a Minecraft world
|
||||
fn main() {
|
||||
let mut app = App::new("Albatross").about("Backup your worlds").arg(
|
||||
Arg::with_name("config")
|
||||
|
||||
Reference in new issue
Block a user