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.mdbackup_error_fix
parent
b54ac0efef
commit
b408ed4d2a
|
@ -35,7 +35,6 @@ name = "albatross"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"config",
|
"config",
|
||||||
"discord-hooks-rs",
|
"discord-hooks-rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -43,6 +42,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde 1.0.117",
|
"serde 1.0.117",
|
||||||
|
"structopt",
|
||||||
"tar",
|
"tar",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -390,6 +390,15 @@ dependencies = [
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
|
@ -810,6 +819,30 @@ version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check 0.9.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check 0.9.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
|
@ -1089,6 +1122,30 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structopt"
|
||||||
|
version = "0.3.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"lazy_static 1.4.0",
|
||||||
|
"structopt-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structopt-derive"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.46"
|
version = "1.0.46"
|
||||||
|
@ -1256,6 +1313,12 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.0"
|
structopt = "0.3.20"
|
||||||
serde = { version="1.0.116", features=["derive"] }
|
serde = { version="1.0.116", features=["derive"] }
|
||||||
config = "0.9"
|
config = "0.9"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
|
|
24
README.md
24
README.md
|
@ -4,6 +4,30 @@ Back up what you care about in your Minecraft worlds.
|
||||||
Albatross backs up player files and region files within a certain configurable radius. It can also send Discord
|
Albatross backs up player files and region files within a certain configurable radius. It can also send Discord
|
||||||
webhooks. Backups are compressed and stored as `tar.gz` archives.
|
webhooks. Backups are compressed and stored as `tar.gz` archives.
|
||||||
|
|
||||||
|
## Help
|
||||||
|
```
|
||||||
|
albatross 0.2.0
|
||||||
|
Backup your Minecraft Server!
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
albatross --config-path <config-path> <SUBCOMMAND>
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information
|
||||||
|
-V, --version Prints version information
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-c, --config-path <config-path> Path to the Albatross config [env: ALBATROSS_CONFIG=]
|
||||||
|
|
||||||
|
SUBCOMMANDS:
|
||||||
|
backup Backup a server
|
||||||
|
export Export a backup as a single player world
|
||||||
|
help Prints this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
|
Process finished with exit code 1
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
```toml
|
```toml
|
||||||
[backup]
|
[backup]
|
||||||
|
|
193
src/backup.rs
193
src/backup.rs
|
@ -1,10 +1,18 @@
|
||||||
use crate::config::WorldConfig;
|
use crate::backup;
|
||||||
|
use crate::config::{AlbatrossConfig, WorldConfig, WorldType};
|
||||||
|
use crate::discord::send_webhook;
|
||||||
use crate::region::Region;
|
use crate::region::Region;
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fs::{copy, create_dir, File};
|
use std::fs::{
|
||||||
|
copy, create_dir, create_dir_all, remove_dir_all, remove_file, rename, DirEntry, File,
|
||||||
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
use tar::Archive;
|
||||||
|
|
||||||
/// Backup a file
|
/// Backup a file
|
||||||
///
|
///
|
||||||
|
@ -189,3 +197,184 @@ pub fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), s
|
||||||
tar_builder.append_dir_all(".", tmp_dir)?;
|
tar_builder.append_dir_all(".", tmp_dir)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes an existing backup and converts it to a singleplayer world
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * config - Albatross config
|
||||||
|
/// * backup - path of the backup to convert
|
||||||
|
/// * output - output path
|
||||||
|
pub fn convert_backup_to_sp(
|
||||||
|
config: &AlbatrossConfig,
|
||||||
|
backup: &PathBuf,
|
||||||
|
output: &PathBuf,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let backup_file = File::open(backup)?;
|
||||||
|
let dec = GzDecoder::new(backup_file);
|
||||||
|
let mut extract = Archive::new(dec);
|
||||||
|
let extract_path = PathBuf::from("tmp");
|
||||||
|
extract.unpack(&extract_path)?;
|
||||||
|
|
||||||
|
if let Some(worlds) = &config.world_config {
|
||||||
|
for world in worlds {
|
||||||
|
let world_type = match world.world_type.clone() {
|
||||||
|
Some(world_type) => world_type,
|
||||||
|
None => WorldType::OVERWORLD,
|
||||||
|
};
|
||||||
|
let src = PathBuf::from(&extract_path).join(&world.world_name);
|
||||||
|
let dest = PathBuf::from(&extract_path);
|
||||||
|
match world_type {
|
||||||
|
WorldType::OVERWORLD => {
|
||||||
|
rename(src.clone().join("poi"), dest.clone().join("poi"))?;
|
||||||
|
rename(src.clone().join("region"), dest.clone().join("region"))?;
|
||||||
|
}
|
||||||
|
WorldType::NETHER => {
|
||||||
|
rename(src, dest.clone().join("DIM-1"))?;
|
||||||
|
}
|
||||||
|
WorldType::END => {
|
||||||
|
rename(src, dest.clone().join("DIM1"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compress_backup(&extract_path, output)?;
|
||||||
|
remove_dir_all(&extract_path)?;
|
||||||
|
|
||||||
|
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<usize, std::io::Error> {
|
||||||
|
let mut backups = vec![];
|
||||||
|
let mut num_of_removed_backups: usize = 0;
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup the configured worlds from a minecraft server
|
||||||
|
///
|
||||||
|
/// # Params
|
||||||
|
/// * `cfg` - config file
|
||||||
|
pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> 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 = match output {
|
||||||
|
Some(out_path) => out_path,
|
||||||
|
None => 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(())
|
||||||
|
}
|
||||||
|
|
204
src/main.rs
204
src/main.rs
|
@ -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::path::PathBuf;
|
||||||
use std::time::Instant;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
mod backup;
|
mod backup;
|
||||||
mod config;
|
mod config;
|
||||||
mod discord;
|
mod discord;
|
||||||
mod region;
|
mod region;
|
||||||
|
|
||||||
use crate::config::{AlbatrossConfig, WorldType};
|
use crate::backup::{convert_backup_to_sp, do_backup};
|
||||||
use backup::{backup_end, backup_nether, backup_overworld};
|
use crate::config::AlbatrossConfig;
|
||||||
use discord::send_webhook;
|
|
||||||
|
|
||||||
/// Get the time of the backup from a file name
|
#[derive(Debug, StructOpt)]
|
||||||
///
|
#[structopt(about = "Backup your Minecraft Server!")]
|
||||||
/// # Param
|
struct Albatross {
|
||||||
/// * `archive_entry`: archive entry
|
/// Path to the Albatross config
|
||||||
fn get_time_from_file_name(
|
#[structopt(short, long, env = "ALBATROSS_CONFIG", parse(from_os_str))]
|
||||||
archive_entry: &DirEntry,
|
config_path: PathBuf,
|
||||||
) -> Result<Option<NaiveDateTime>, std::io::Error> {
|
/// Subcommand
|
||||||
let file_name = archive_entry.file_name().to_str().unwrap().to_string();
|
#[structopt(subcommand)]
|
||||||
let name: Vec<&str> = file_name.split('_').collect();
|
sub_command: SubCommand,
|
||||||
|
|
||||||
Ok(chrono::NaiveDateTime::parse_from_str(name[0], "%d-%m-%y-%H.%M.%S").ok())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the old backups from the ouput directory
|
#[derive(Debug, StructOpt)]
|
||||||
///
|
enum SubCommand {
|
||||||
/// # Params
|
/// Backup a server
|
||||||
/// * `output_dir` - output directory containing
|
Backup {
|
||||||
/// * `keep` - number of backups to keep
|
/// Output location override
|
||||||
fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<usize, std::io::Error> {
|
#[structopt(short = "o", long = "ouptut", parse(from_os_str))]
|
||||||
let mut backups = vec![];
|
output: Option<PathBuf>,
|
||||||
let mut num_of_removed_backups: usize = 0;
|
},
|
||||||
|
/// 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()? {
|
/// Output location override
|
||||||
let entry = entry?;
|
#[structopt(parse(from_os_str))]
|
||||||
|
output: PathBuf,
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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() {
|
fn main() {
|
||||||
let mut app = App::new("Albatross").about("Backup your worlds").arg(
|
let opt = Albatross::from_args();
|
||||||
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();
|
|
||||||
|
|
||||||
if let Some(cfg_path) = matches.value_of("config") {
|
let cfg = AlbatrossConfig::new(opt.config_path.into_os_string().to_str().unwrap())
|
||||||
let cfg = AlbatrossConfig::new(cfg_path).expect("Config not found");
|
.expect("Config not found");
|
||||||
|
|
||||||
if cfg.world_config.is_some() {
|
if cfg.world_config.is_some() {
|
||||||
|
match opt.sub_command {
|
||||||
|
SubCommand::Backup { output } => {
|
||||||
println!("Starting backup");
|
println!("Starting backup");
|
||||||
match do_backup(cfg) {
|
match do_backup(cfg, output) {
|
||||||
Ok(_) => println!("Backup complete"),
|
Ok(_) => println!("Backup complete!"),
|
||||||
Err(e) => println!("Error doing backup: {:?}", e),
|
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 {
|
} else {
|
||||||
println!("No worlds specified to backed up!")
|
println!("No worlds specified in config file!")
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.print_help().expect("Unable to print help");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue