mirror of
https://git.ahines.net/joeyahines/Albatross.git
synced 2026-11-08 09:13:57 +00:00
Added export subcommand
+ Takes a backup and exports it as a playable single player world + Changed file name format + Switched to using StructOpt for the argument parsing + Updated README.md
This commit is contained in:
1 parent
b54ac0efef
commit
b408ed4d2a
5 files changed
+331
-161
No files matched your search
+51
-157
@@ -1,178 +1,72 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use clap::{App, Arg};
|
||||
use std::fs::{create_dir_all, remove_dir_all, remove_file, DirEntry};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod backup;
|
||||
mod config;
|
||||
mod discord;
|
||||
mod region;
|
||||
|
||||
use crate::config::{AlbatrossConfig, WorldType};
|
||||
use backup::{backup_end, backup_nether, backup_overworld};
|
||||
use discord::send_webhook;
|
||||
use crate::backup::{convert_backup_to_sp, do_backup};
|
||||
use crate::config::AlbatrossConfig;
|
||||
|
||||
/// 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())
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(about = "Backup your Minecraft Server!")]
|
||||
struct Albatross {
|
||||
/// Path to the Albatross config
|
||||
#[structopt(short, long, env = "ALBATROSS_CONFIG", parse(from_os_str))]
|
||||
config_path: PathBuf,
|
||||
/// Subcommand
|
||||
#[structopt(subcommand)]
|
||||
sub_command: SubCommand,
|
||||
}
|
||||
|
||||
/// 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<usize, std::io::Error> {
|
||||
let mut backups = vec![];
|
||||
let mut num_of_removed_backups: usize = 0;
|
||||
#[derive(Debug, StructOpt)]
|
||||
enum SubCommand {
|
||||
/// Backup a server
|
||||
Backup {
|
||||
/// Output location override
|
||||
#[structopt(short = "o", long = "ouptut", parse(from_os_str))]
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
/// Export a backup as a single player world
|
||||
Export {
|
||||
/// Convert backup to singleplayer world
|
||||
#[structopt(parse(from_os_str))]
|
||||
input_backup: PathBuf,
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
num_of_removed_backups = backups.len() - keep as usize;
|
||||
|
||||
for _i in 0..num_of_removed_backups {
|
||||
let oldest = backups.pop().unwrap();
|
||||
remove_file(oldest.path())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(num_of_removed_backups)
|
||||
/// Output location override
|
||||
#[structopt(parse(from_os_str))]
|
||||
output: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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.clone().expect("No worlds configured");
|
||||
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("tmp");
|
||||
remove_dir_all(&tmp_dir).ok();
|
||||
|
||||
create_dir_all(tmp_dir.clone()).unwrap();
|
||||
|
||||
send_webhook("**Albatross is swooping in to backup your worlds!**", &cfg);
|
||||
let timer = Instant::now();
|
||||
for world in worlds {
|
||||
let mut world_dir = server_base_dir.clone();
|
||||
let world_name = world.world_name.clone();
|
||||
let world_type = match world.world_type.clone() {
|
||||
Some(world_type) => world_type,
|
||||
None => WorldType::OVERWORLD,
|
||||
};
|
||||
world_dir.push(world_name.clone());
|
||||
|
||||
if world_dir.exists() && world_dir.is_dir() {
|
||||
send_webhook(
|
||||
format!("Starting backup of **{}**", world_name).as_str(),
|
||||
&cfg,
|
||||
);
|
||||
let webhook_msg = match world_type {
|
||||
WorldType::OVERWORLD => {
|
||||
let (region_count, player_count) =
|
||||
backup_overworld(world_dir.clone(), tmp_dir.clone(), &world)?;
|
||||
format!(
|
||||
"{} regions and {} player files backed up.",
|
||||
region_count, player_count
|
||||
)
|
||||
}
|
||||
WorldType::NETHER => {
|
||||
let region_count = backup_nether(world_dir, tmp_dir.clone(), &world)?;
|
||||
format!("{} regions backed up.", region_count)
|
||||
}
|
||||
WorldType::END => {
|
||||
let region_count = backup_end(world_dir, tmp_dir.clone(), &world)?;
|
||||
format!("{} regions backed up.", region_count)
|
||||
}
|
||||
};
|
||||
|
||||
send_webhook(&webhook_msg, &cfg);
|
||||
} else {
|
||||
send_webhook(format!("Error: {} not found.", world_name).as_str(), &cfg);
|
||||
println!("World \"{}\" not found", world_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
backup::compress_backup(&tmp_dir, &output_archive)?;
|
||||
|
||||
remove_dir_all(&tmp_dir)?;
|
||||
|
||||
let backups_removed = remove_old_backups(&cfg.backup.output_dir, cfg.backup.backups_to_keep)?;
|
||||
|
||||
if backups_removed > 0 {
|
||||
let msg = format!(
|
||||
"Albatross mistook **{}** of your old backups for some french fries and ate them!! SKRAWWWW",
|
||||
backups_removed
|
||||
);
|
||||
send_webhook(msg.as_str(), &cfg);
|
||||
}
|
||||
|
||||
let secs = timer.elapsed().as_secs();
|
||||
send_webhook(
|
||||
format!("**Full backup completed in {}s**! *SKREEEEEEEEEE*", secs).as_str(),
|
||||
&cfg,
|
||||
);
|
||||
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")
|
||||
.index(1)
|
||||
.short("c")
|
||||
.long("config")
|
||||
.value_name("CONFIG_PATH")
|
||||
.help("Config file path"),
|
||||
);
|
||||
// Get arg parser
|
||||
let matches = app.clone().get_matches();
|
||||
let opt = Albatross::from_args();
|
||||
|
||||
if let Some(cfg_path) = matches.value_of("config") {
|
||||
let cfg = AlbatrossConfig::new(cfg_path).expect("Config not found");
|
||||
let cfg = AlbatrossConfig::new(opt.config_path.into_os_string().to_str().unwrap())
|
||||
.expect("Config not found");
|
||||
|
||||
if cfg.world_config.is_some() {
|
||||
println!("Starting backup");
|
||||
match do_backup(cfg) {
|
||||
Ok(_) => println!("Backup complete"),
|
||||
Err(e) => println!("Error doing backup: {:?}", e),
|
||||
if cfg.world_config.is_some() {
|
||||
match opt.sub_command {
|
||||
SubCommand::Backup { output } => {
|
||||
println!("Starting backup");
|
||||
match do_backup(cfg, output) {
|
||||
Ok(_) => println!("Backup complete!"),
|
||||
Err(e) => println!("Error doing backup: {:?}", e),
|
||||
};
|
||||
}
|
||||
SubCommand::Export {
|
||||
input_backup,
|
||||
output,
|
||||
} => {
|
||||
println!("Starting export");
|
||||
match convert_backup_to_sp(&cfg, &input_backup, &output) {
|
||||
Ok(_) => println!("Export complete!"),
|
||||
Err(e) => println!("Error exporting backup: {:?}", e),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
println!("No worlds specified to backed up!")
|
||||
}
|
||||
} else {
|
||||
app.print_help().expect("Unable to print help");
|
||||
println!("No worlds specified in config file!")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user