parent
d94186c7b8
commit
dc005e460b
|
@ -1,6 +1,7 @@
|
|||
use crate::backup;
|
||||
use crate::config::{AlbatrossConfig, WorldConfig, WorldType};
|
||||
use crate::discord::send_webhook;
|
||||
use crate::error::Result;
|
||||
use crate::region::Region;
|
||||
use crate::remote_backup::remote_backup;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
@ -25,11 +26,11 @@ pub fn backup_file(
|
|||
file_name: &str,
|
||||
mut world_path: PathBuf,
|
||||
mut backup_path: PathBuf,
|
||||
) -> Result<u64, std::io::Error> {
|
||||
) -> Result<u64> {
|
||||
world_path.push(file_name);
|
||||
backup_path.push(file_name);
|
||||
|
||||
copy(world_path, backup_path)
|
||||
Ok(copy(world_path, backup_path)?)
|
||||
}
|
||||
|
||||
/// Backup a directory
|
||||
|
@ -38,11 +39,7 @@ pub fn backup_file(
|
|||
/// * `dir_name` - directory name
|
||||
/// * `world_path` - path to the world folder
|
||||
/// * `backup_path` - path to the backup folder
|
||||
pub fn backup_dir(
|
||||
dir_name: &str,
|
||||
world_path: &PathBuf,
|
||||
backup_path: &PathBuf,
|
||||
) -> Result<u64, std::io::Error> {
|
||||
pub fn backup_dir(dir_name: &str, world_path: &PathBuf, backup_path: &PathBuf) -> Result<u64> {
|
||||
let mut src_dir = world_path.clone();
|
||||
src_dir.push(dir_name);
|
||||
let mut backup_dir = backup_path.clone();
|
||||
|
@ -74,7 +71,7 @@ pub fn backup_region(
|
|||
save_radius: u64,
|
||||
world_path: &PathBuf,
|
||||
backup_path: &PathBuf,
|
||||
) -> Result<u64, std::io::Error> {
|
||||
) -> Result<u64> {
|
||||
let mut count: u64 = 0;
|
||||
let mut src_dir = world_path.clone();
|
||||
src_dir.push(dir_name);
|
||||
|
@ -112,7 +109,7 @@ pub fn backup_world(
|
|||
world_path: PathBuf,
|
||||
mut backup_path: PathBuf,
|
||||
world_config: &WorldConfig,
|
||||
) -> Result<u64, std::io::Error> {
|
||||
) -> Result<u64> {
|
||||
let region_count;
|
||||
backup_path.push(&world_config.world_name);
|
||||
create_dir(backup_path.as_path())?;
|
||||
|
@ -137,7 +134,7 @@ pub fn backup_overworld(
|
|||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
world_config: &WorldConfig,
|
||||
) -> Result<(u64, u64), std::io::Error> {
|
||||
) -> Result<(u64, u64)> {
|
||||
backup_dir("data", &world_path, &backup_path)?;
|
||||
backup_dir("stats", &world_path, &backup_path)?;
|
||||
|
||||
|
@ -162,7 +159,7 @@ pub fn backup_nether(
|
|||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
world_config: &WorldConfig,
|
||||
) -> Result<u64, std::io::Error> {
|
||||
) -> Result<u64> {
|
||||
let mut nether_path = world_path;
|
||||
nether_path.push("DIM-1");
|
||||
|
||||
|
@ -179,7 +176,7 @@ pub fn backup_end(
|
|||
world_path: PathBuf,
|
||||
backup_path: PathBuf,
|
||||
world_config: &WorldConfig,
|
||||
) -> Result<u64, std::io::Error> {
|
||||
) -> Result<u64> {
|
||||
let mut end_path = world_path;
|
||||
end_path.push("DIM1");
|
||||
|
||||
|
@ -191,7 +188,7 @@ pub fn backup_end(
|
|||
/// # Param
|
||||
/// * `tmp_dir`: tmp directory with the backed up files
|
||||
/// * `output_file`: output archive
|
||||
pub fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), std::io::Error> {
|
||||
pub fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<()> {
|
||||
let archive = File::create(output_file)?;
|
||||
let enc = GzEncoder::new(archive, Compression::default());
|
||||
let mut tar_builder = tar::Builder::new(enc);
|
||||
|
@ -199,7 +196,7 @@ pub fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), s
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uncompress_backup(backup: &PathBuf) -> Result<PathBuf, std::io::Error> {
|
||||
pub fn uncompress_backup(backup: &PathBuf) -> Result<PathBuf> {
|
||||
let backup_file = File::open(backup)?;
|
||||
let dec = GzDecoder::new(backup_file);
|
||||
let mut extract = Archive::new(dec);
|
||||
|
@ -219,7 +216,7 @@ pub fn convert_backup_to_sp(
|
|||
config: &AlbatrossConfig,
|
||||
backup: &PathBuf,
|
||||
output: &PathBuf,
|
||||
) -> Result<(), std::io::Error> {
|
||||
) -> Result<()> {
|
||||
let extract_path = uncompress_backup(backup)?;
|
||||
|
||||
if let Some(worlds) = &config.world_config {
|
||||
|
@ -255,13 +252,14 @@ pub fn convert_backup_to_sp(
|
|||
///
|
||||
/// # Param
|
||||
/// * `archive_entry`: archive entry
|
||||
fn get_time_from_file_name(
|
||||
archive_entry: &DirEntry,
|
||||
) -> Result<Option<NaiveDateTime>, std::io::Error> {
|
||||
fn get_time_from_file_name(archive_entry: &DirEntry) -> Result<NaiveDateTime> {
|
||||
let file_name = archive_entry.file_name().to_str().unwrap().to_string();
|
||||
let name: Vec<&str> = file_name.split("_backup.tar.gz").collect();
|
||||
|
||||
Ok(chrono::NaiveDateTime::parse_from_str(name[0], "%d-%m-%y_%H.%M.%S").ok())
|
||||
Ok(chrono::NaiveDateTime::parse_from_str(
|
||||
name[0],
|
||||
"%d-%m-%y_%H.%M.%S",
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Removes the old backups from the ouput directory
|
||||
|
@ -269,7 +267,7 @@ fn get_time_from_file_name(
|
|||
/// # 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> {
|
||||
fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<usize> {
|
||||
let mut backups = vec![];
|
||||
let mut num_of_removed_backups: usize = 0;
|
||||
|
||||
|
@ -285,8 +283,8 @@ fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<usize, std::io:
|
|||
|
||||
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();
|
||||
let a_time = get_time_from_file_name(a).unwrap();
|
||||
let b_time = get_time_from_file_name(b).unwrap();
|
||||
|
||||
b_time.cmp(&a_time)
|
||||
});
|
||||
|
@ -306,7 +304,7 @@ fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<usize, std::io:
|
|||
///
|
||||
/// # Params
|
||||
/// * `cfg` - config file
|
||||
pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> Result<(), std::io::Error> {
|
||||
pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> Result<()> {
|
||||
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();
|
||||
|
@ -320,27 +318,52 @@ pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> Result<(), st
|
|||
tmp_dir.push("tmp");
|
||||
remove_dir_all(&tmp_dir).ok();
|
||||
|
||||
create_dir_all(tmp_dir.clone()).unwrap();
|
||||
create_dir_all(tmp_dir.clone())?;
|
||||
|
||||
let timer = Instant::now();
|
||||
backup_worlds(&cfg, server_base_dir, worlds, &mut tmp_dir)?;
|
||||
|
||||
backup::compress_backup(&tmp_dir, &output_archive)?;
|
||||
send_webhook("**Albatross is swooping in to backup your worlds!**", &cfg);
|
||||
|
||||
backup_worlds(&cfg, server_base_dir, worlds, &mut tmp_dir).map_err(|e| {
|
||||
send_webhook("Failed to copy worlds to backup location", &cfg);
|
||||
println!("Failed to copy worlds: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
backup::compress_backup(&tmp_dir, &output_archive).map_err(|e| {
|
||||
send_webhook("Failed to compress backup", &cfg);
|
||||
println!("Failed to compress backup: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
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);
|
||||
match remove_old_backups(&cfg.backup.output_dir, cfg.backup.backups_to_keep) {
|
||||
Ok(backups_removed) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
send_webhook("Failed to remove old backups!", &cfg);
|
||||
println!("Failed to remove old backups: {}", e)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(remote_backup_cfg) = &cfg.remote {
|
||||
remote_backup(output_archive, remote_backup_cfg);
|
||||
match remote_backup(output_archive, remote_backup_cfg) {
|
||||
Ok(_) => {
|
||||
send_webhook("Remote backup completed!", &cfg);
|
||||
}
|
||||
Err(e) => {
|
||||
send_webhook("Remote backup failed!", &cfg);
|
||||
println!("Remote backup failed with error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let secs = timer.elapsed().as_secs();
|
||||
|
@ -356,8 +379,7 @@ fn backup_worlds(
|
|||
server_base_dir: PathBuf,
|
||||
worlds: Vec<WorldConfig>,
|
||||
tmp_dir: &mut PathBuf,
|
||||
) -> Result<(), std::io::Error> {
|
||||
send_webhook("**Albatross is swooping in to backup your worlds!**", &cfg);
|
||||
) -> Result<()> {
|
||||
for world in worlds {
|
||||
let mut world_dir = server_base_dir.clone();
|
||||
let world_name = world.world_name.clone();
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
use crate::region::RegionParseError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AlbatrossError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AlbatrossError {
|
||||
FileError(std::io::Error),
|
||||
SSHError(ssh2::Error),
|
||||
ChunkParseError(crate::chunk_coordinate::ChunkCoordinateErr),
|
||||
RegionParseError(crate::region::RegionParseError),
|
||||
ChronoParseError(chrono::ParseError),
|
||||
}
|
||||
|
||||
impl std::error::Error for AlbatrossError {}
|
||||
|
||||
impl std::fmt::Display for AlbatrossError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AlbatrossError::FileError(e) => write!(f, "File I/O error: {}", e),
|
||||
AlbatrossError::SSHError(e) => write!(f, "SSH error: {}", e),
|
||||
AlbatrossError::ChunkParseError(e) => {
|
||||
write!(f, "Unable to parse chunk coordinate: {}", e)
|
||||
}
|
||||
AlbatrossError::RegionParseError(e) => write!(f, "Unable to parse region name: {}", e),
|
||||
AlbatrossError::ChronoParseError(e) => write!(f, "Unable to parse time: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AlbatrossError {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
AlbatrossError::FileError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ssh2::Error> for AlbatrossError {
|
||||
fn from(e: ssh2::Error) -> Self {
|
||||
AlbatrossError::SSHError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::chunk_coordinate::ChunkCoordinateErr> for AlbatrossError {
|
||||
fn from(e: crate::chunk_coordinate::ChunkCoordinateErr) -> Self {
|
||||
AlbatrossError::ChunkParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::region::RegionParseError> for AlbatrossError {
|
||||
fn from(e: RegionParseError) -> Self {
|
||||
AlbatrossError::RegionParseError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chrono::ParseError> for AlbatrossError {
|
||||
fn from(e: chrono::ParseError) -> Self {
|
||||
AlbatrossError::ChronoParseError(e)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ mod backup;
|
|||
mod chunk_coordinate;
|
||||
mod config;
|
||||
mod discord;
|
||||
mod error;
|
||||
mod region;
|
||||
mod remote_backup;
|
||||
mod restore;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::config::RemoteBackupConfig;
|
||||
use crate::error::Result;
|
||||
use ssh2::Session;
|
||||
use std::net::TcpStream;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn open_ssh_session(remote_config: &RemoteBackupConfig) -> Session {
|
||||
fn open_ssh_session(remote_config: &RemoteBackupConfig) -> Result<Session> {
|
||||
let tcp = TcpStream::connect(&remote_config.sftp_server_addr).unwrap();
|
||||
let mut sess = Session::new().unwrap();
|
||||
sess.set_tcp_stream(tcp);
|
||||
|
@ -13,35 +14,27 @@ fn open_ssh_session(remote_config: &RemoteBackupConfig) -> Session {
|
|||
sess.userauth_password(&remote_config.username, password)
|
||||
.unwrap();
|
||||
} else if let Some(key) = &remote_config.private_key {
|
||||
let public_key =
|
||||
remote_config
|
||||
.public_key
|
||||
.as_ref()
|
||||
.map(|pub_key| pub_key.as_path().clone());
|
||||
sess.userauth_pubkey_file(
|
||||
&remote_config.username,
|
||||
public_key,
|
||||
key,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let public_key = remote_config.public_key.as_deref();
|
||||
sess.userauth_pubkey_file(&remote_config.username, public_key, key, None)?;
|
||||
} else {
|
||||
panic!("No key provided")
|
||||
}
|
||||
|
||||
sess
|
||||
Ok(sess)
|
||||
}
|
||||
|
||||
pub fn remote_backup(file: PathBuf, remote_config: &RemoteBackupConfig) {
|
||||
let sess = open_ssh_session(remote_config);
|
||||
pub fn remote_backup(file: PathBuf, remote_config: &RemoteBackupConfig) -> Result<()> {
|
||||
let sess = open_ssh_session(remote_config)?;
|
||||
|
||||
let remote_path = remote_config.remote_dir.join(file.file_name().unwrap());
|
||||
|
||||
let mut local_file = std::fs::File::open(&file).unwrap();
|
||||
let mut local_file = std::fs::File::open(&file)?;
|
||||
|
||||
let sftp = sess.sftp().unwrap();
|
||||
let sftp = sess.sftp()?;
|
||||
|
||||
let mut remote_file = sftp.create(&remote_path).unwrap();
|
||||
let mut remote_file = sftp.create(&remote_path)?;
|
||||
|
||||
std::io::copy(&mut local_file, &mut remote_file).unwrap();
|
||||
std::io::copy(&mut local_file, &mut remote_file)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::backup::uncompress_backup;
|
||||
use crate::chunk_coordinate::ChunkCoordinate;
|
||||
use crate::error::Result;
|
||||
use anvil_region::AnvilChunkProvider;
|
||||
use std::error;
|
||||
use std::fs::remove_dir_all;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -15,11 +15,7 @@ struct RestoreAccess {
|
|||
|
||||
impl RestoreAccess {
|
||||
/// Create new RestoreAccess
|
||||
pub fn new(
|
||||
world_name: &str,
|
||||
src_path: &PathBuf,
|
||||
dest_path: &PathBuf,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
pub fn new(world_name: &str, src_path: &PathBuf, dest_path: &PathBuf) -> Result<Self> {
|
||||
let src_path = uncompress_backup(src_path)?.join(world_name).join("region");
|
||||
let dest_path = dest_path.join(world_name).join("region");
|
||||
|
||||
|
@ -41,8 +37,8 @@ impl RestoreAccess {
|
|||
}
|
||||
|
||||
/// Cleanup process
|
||||
pub fn cleanup(self) -> Result<(), std::io::Error> {
|
||||
remove_dir_all("tmp")
|
||||
pub fn cleanup(self) -> Result<()> {
|
||||
Ok(remove_dir_all("tmp")?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +49,7 @@ pub fn restore_range_from_backup(
|
|||
upper: ChunkCoordinate,
|
||||
backup_path: &PathBuf,
|
||||
minecraft_dir: &PathBuf,
|
||||
) -> Result<u64, Box<dyn error::Error>> {
|
||||
) -> Result<u64> {
|
||||
let chunk_access = RestoreAccess::new(world_name, backup_path, minecraft_dir)?;
|
||||
let mut count = 0;
|
||||
|
||||
|
@ -74,7 +70,7 @@ pub fn restore_chunk_from_backup(
|
|||
chunk: ChunkCoordinate,
|
||||
backup_path: &PathBuf,
|
||||
minecraft_dir: &PathBuf,
|
||||
) -> Result<(), Box<dyn error::Error>> {
|
||||
) -> Result<()> {
|
||||
let chunk_access = RestoreAccess::new(world_name, backup_path, minecraft_dir)?;
|
||||
chunk_access.copy_chunk(chunk.x, chunk.z);
|
||||
|
||||
|
|
Loading…
Reference in New Issue