Added File remote backups
+ Copies the backup to a target directory + Useful for backing up to another local drive or mounted network drive + Various improvements + Fixed .drone.ymlmaster latest
parent
d207c6df19
commit
8378f2ead3
|
@ -10,7 +10,7 @@ trigger:
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
pull: always
|
pull: always
|
||||||
image: rust:1.46.0
|
image: rust:1.55.0
|
||||||
commands:
|
commands:
|
||||||
- cargo build --verbose
|
- cargo build --verbose
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ trigger:
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
pull: always
|
pull: always
|
||||||
image: rust:1.46.0
|
image: rust:1.55.0
|
||||||
commands:
|
commands:
|
||||||
- cargo build --verbose --release
|
- cargo build --verbose --release
|
||||||
- name: gitea-release
|
- name: gitea-release
|
||||||
|
@ -37,6 +37,6 @@ steps:
|
||||||
settings:
|
settings:
|
||||||
token:
|
token:
|
||||||
from_secret: gitea_token
|
from_secret: gitea_token
|
||||||
base: https://git.etztech.xyz
|
base: https://git.canopymc.net
|
||||||
files:
|
files:
|
||||||
- "target/release/albatross"
|
- "target/release/albatross"
|
|
@ -1,5 +1,7 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
@ -41,7 +43,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "albatross"
|
name = "albatross"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anvil-region",
|
"anvil-region",
|
||||||
"chrono 0.4.19",
|
"chrono 0.4.19",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "albatross"
|
name = "albatross"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Joey Hines <joey@ahines.net>"]
|
authors = ["Joey Hines <joey@ahines.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
14
README.md
14
README.md
|
@ -54,14 +54,16 @@ Restoring a range of chunks (from -2,-2 to 2,2):
|
||||||
[backup]
|
[backup]
|
||||||
# Minecraft sever directory
|
# Minecraft sever directory
|
||||||
minecraft_dir = "/home/mc/server"
|
minecraft_dir = "/home/mc/server"
|
||||||
# Directory to place backups
|
|
||||||
output_dir = "/home/mc/backups"
|
|
||||||
# Number of backups to keep
|
|
||||||
backups_to_keep = 10
|
|
||||||
# Optional Discord webhook
|
# Optional Discord webhook
|
||||||
discord_webhook = "https://discordapp.com/api/webhooks/"
|
discord_webhook = "https://discordapp.com/api/webhooks/"
|
||||||
|
# Number of backups to keep
|
||||||
|
backups_to_keep = 10
|
||||||
|
|
||||||
# Optional remote backup config
|
[backup.output_config]
|
||||||
|
# Directory to place backups
|
||||||
|
path = "/home/mc/backups"
|
||||||
|
|
||||||
|
# Optional remote_backup backup config
|
||||||
[remote]
|
[remote]
|
||||||
# SFTP server host:port
|
# SFTP server host:port
|
||||||
sftp_server_addr = "localhost:22"
|
sftp_server_addr = "localhost:22"
|
||||||
|
@ -74,7 +76,7 @@ password = "cooluser123"
|
||||||
# Key Auth
|
# Key Auth
|
||||||
#public_key = /home/user/.ssh/id_rsa.pub"
|
#public_key = /home/user/.ssh/id_rsa.pub"
|
||||||
#private_key = /home/user/.ssh/id_rsa"
|
#private_key = /home/user/.ssh/id_rsa"
|
||||||
# Backups to keep on the remote host
|
# Backups to keep on the remote_backup host
|
||||||
backups_to_keep = 3
|
backups_to_keep = 3
|
||||||
|
|
||||||
# World config options
|
# World config options
|
||||||
|
|
|
@ -137,11 +137,11 @@ pub fn backup_overworld(
|
||||||
world_config: &WorldConfig,
|
world_config: &WorldConfig,
|
||||||
) -> Result<(u64, u64)> {
|
) -> Result<(u64, u64)> {
|
||||||
backup_dir("data", &world_path, &backup_path)?;
|
backup_dir("data", &world_path, &backup_path)?;
|
||||||
backup_dir("stats", &world_path, &backup_path)?;
|
backup_dir("stats", &world_path, &backup_path).ok();
|
||||||
|
|
||||||
backup_file("level.dat", world_path.clone(), backup_path.clone())?;
|
backup_file("level.dat", world_path.clone(), backup_path.clone())?;
|
||||||
backup_file("level.dat_old", world_path.clone(), backup_path.clone())?;
|
backup_file("level.dat_old", world_path.clone(), backup_path.clone()).ok();
|
||||||
backup_file("session.lock", world_path.clone(), backup_path.clone())?;
|
backup_file("session.lock", world_path.clone(), backup_path.clone()).ok();
|
||||||
backup_file("uid.dat", world_path.clone(), backup_path.clone())?;
|
backup_file("uid.dat", world_path.clone(), backup_path.clone())?;
|
||||||
|
|
||||||
let player_count = backup_dir("playerdata", &world_path, &backup_path)?;
|
let player_count = backup_dir("playerdata", &world_path, &backup_path)?;
|
||||||
|
@ -249,20 +249,25 @@ pub fn convert_backup_to_sp(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preform a remote backup, if configured
|
/// Preform a remote_backup backup, if configured
|
||||||
pub fn do_remote_backup(
|
pub fn do_remote_backup(
|
||||||
remote_backup_cfg: &RemoteBackupConfig,
|
remote_backup_cfg: &RemoteBackupConfig,
|
||||||
backup_path: PathBuf,
|
backup_path: PathBuf,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if remote_backup_cfg.sftp.is_some() {
|
if let Some(config) = &remote_backup_cfg.sftp {
|
||||||
let mut sftp_backup = SFTPBackup::new(&remote_backup_cfg)?;
|
let mut sftp_backup = SFTPBackup::new(config, remote_backup_cfg.backups_to_keep)?;
|
||||||
sftp_backup.backup_to_remote(backup_path)?;
|
sftp_backup.backup_to_remote(backup_path)?;
|
||||||
sftp_backup.cleanup()?;
|
sftp_backup.cleanup()?;
|
||||||
} else if remote_backup_cfg.ftp.is_some() {
|
} else if let Some(config) = &remote_backup_cfg.ftp {
|
||||||
let mut ftps_backup = FTPBackup::new(&remote_backup_cfg)?;
|
let mut ftps_backup = FTPBackup::new(config, remote_backup_cfg.backups_to_keep)?;
|
||||||
ftps_backup.backup_to_remote(backup_path)?;
|
ftps_backup.backup_to_remote(backup_path)?;
|
||||||
ftps_backup.cleanup()?;
|
ftps_backup.cleanup()?;
|
||||||
}
|
}
|
||||||
|
else if let Some(config) = &remote_backup_cfg.file {
|
||||||
|
let mut file_backup = FileBackup::new(config, remote_backup_cfg.backups_to_keep)?;
|
||||||
|
file_backup.backup_to_remote(backup_path)?;
|
||||||
|
file_backup.cleanup()?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -278,10 +283,10 @@ pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> Result<()> {
|
||||||
let backup_name = format!("{}_backup.tar.gz", time_str);
|
let backup_name = format!("{}_backup.tar.gz", time_str);
|
||||||
let mut output_archive = match output {
|
let mut output_archive = match output {
|
||||||
Some(out_path) => out_path,
|
Some(out_path) => out_path,
|
||||||
None => cfg.backup.output_dir.clone(),
|
None => cfg.backup.output_config.path.clone(),
|
||||||
};
|
};
|
||||||
output_archive.push(backup_name);
|
output_archive.push(backup_name);
|
||||||
let mut tmp_dir = cfg.backup.output_dir.clone();
|
let mut tmp_dir = cfg.backup.output_config.path.clone();
|
||||||
tmp_dir.push("tmp");
|
tmp_dir.push("tmp");
|
||||||
remove_dir_all(&tmp_dir).ok();
|
remove_dir_all(&tmp_dir).ok();
|
||||||
|
|
||||||
|
@ -305,7 +310,7 @@ pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> Result<()> {
|
||||||
|
|
||||||
remove_dir_all(&tmp_dir)?;
|
remove_dir_all(&tmp_dir)?;
|
||||||
|
|
||||||
let mut local_backup = FileBackup::new(&cfg.backup).unwrap();
|
let mut local_backup = FileBackup::new(&cfg.backup.output_config, cfg.backup.backups_to_keep).unwrap();
|
||||||
|
|
||||||
match local_backup.cleanup() {
|
match local_backup.cleanup() {
|
||||||
Ok(backups_removed) => {
|
Ok(backups_removed) => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod remote;
|
pub(crate) mod remote;
|
||||||
|
|
||||||
use crate::config::remote::{FTPConfig, SFTPConfig};
|
use crate::config::remote::{FTPConfig, SFTPConfig, FileConfig};
|
||||||
use config::{Config, ConfigError, File};
|
use config::{Config, ConfigError, File};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -39,17 +39,18 @@ pub struct WorldConfig {
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct BackupConfig {
|
pub struct BackupConfig {
|
||||||
pub minecraft_dir: PathBuf,
|
pub minecraft_dir: PathBuf,
|
||||||
pub output_dir: PathBuf,
|
pub backups_to_keep: usize,
|
||||||
pub backups_to_keep: u64,
|
|
||||||
pub discord_webhook: Option<String>,
|
pub discord_webhook: Option<String>,
|
||||||
|
pub output_config: FileConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config for remote backups
|
/// Config for remote_backup backups
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct RemoteBackupConfig {
|
pub struct RemoteBackupConfig {
|
||||||
pub backups_to_keep: u64,
|
pub backups_to_keep: usize,
|
||||||
pub sftp: Option<SFTPConfig>,
|
pub sftp: Option<SFTPConfig>,
|
||||||
pub ftp: Option<FTPConfig>,
|
pub ftp: Option<FTPConfig>,
|
||||||
|
pub file: Option<FileConfig>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configs
|
/// Configs
|
||||||
|
|
|
@ -30,3 +30,10 @@ pub struct FTPConfig {
|
||||||
/// Password
|
/// Password
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// File Config
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct FileConfig {
|
||||||
|
/// Path to backup to
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ pub enum AlbatrossError {
|
||||||
RegionParseError(crate::region::RegionParseError),
|
RegionParseError(crate::region::RegionParseError),
|
||||||
ChronoParseError(chrono::ParseError),
|
ChronoParseError(chrono::ParseError),
|
||||||
NoSSHAuth,
|
NoSSHAuth,
|
||||||
RemoteConfigError(String),
|
|
||||||
FTPError(ftp::FtpError),
|
FTPError(ftp::FtpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +26,6 @@ impl std::fmt::Display for AlbatrossError {
|
||||||
AlbatrossError::RegionParseError(e) => write!(f, "Unable to parse region name: {}", e),
|
AlbatrossError::RegionParseError(e) => write!(f, "Unable to parse region name: {}", e),
|
||||||
AlbatrossError::ChronoParseError(e) => write!(f, "Unable to parse time: {}", e),
|
AlbatrossError::ChronoParseError(e) => write!(f, "Unable to parse time: {}", e),
|
||||||
AlbatrossError::NoSSHAuth => write!(f, "No SSH auth methods provided in the config"),
|
AlbatrossError::NoSSHAuth => write!(f, "No SSH auth methods provided in the config"),
|
||||||
AlbatrossError::RemoteConfigError(e) => write!(f, "Invalid configuration for {}", e),
|
|
||||||
AlbatrossError::FTPError(e) => write!(f, "FTP error: {}", e),
|
AlbatrossError::FTPError(e) => write!(f, "FTP error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::BackupConfig;
|
use crate::config::remote::FileConfig;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::remote::{PathLocation, RemoteBackupSite};
|
use crate::remote::{PathLocation, RemoteBackupSite};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -12,10 +12,10 @@ pub struct FileBackup {
|
||||||
|
|
||||||
impl FileBackup {
|
impl FileBackup {
|
||||||
/// New FileBackup
|
/// New FileBackup
|
||||||
pub fn new(config: &BackupConfig) -> Result<Self> {
|
pub fn new(config: &FileConfig, backups_to_keep: usize) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
target_dir: config.output_dir.clone(),
|
target_dir: config.path.clone(),
|
||||||
backups_to_keep: config.backups_to_keep as usize,
|
backups_to_keep,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use ftp::FtpStream;
|
use ftp::FtpStream;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::config::RemoteBackupConfig;
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::error::AlbatrossError;
|
|
||||||
use crate::remote::{PathLocation, RemoteBackupSite};
|
use crate::remote::{PathLocation, RemoteBackupSite};
|
||||||
|
use crate::config::remote::FTPConfig;
|
||||||
|
|
||||||
/// FTP Remote Site
|
/// FTP Remote Site
|
||||||
pub struct FTPBackup {
|
pub struct FTPBackup {
|
||||||
|
@ -18,20 +17,15 @@ pub struct FTPBackup {
|
||||||
|
|
||||||
impl FTPBackup {
|
impl FTPBackup {
|
||||||
/// New FTPBackup
|
/// New FTPBackup
|
||||||
pub fn new(config: &RemoteBackupConfig) -> error::Result<Self> {
|
pub fn new(config: &FTPConfig, backups_to_keep: usize) -> error::Result<Self> {
|
||||||
let ftp_config = config
|
let mut ftp_stream = FtpStream::connect(&config.server_addr)?;
|
||||||
.ftp
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| AlbatrossError::RemoteConfigError("FTP".to_string()))?;
|
|
||||||
|
|
||||||
let mut ftp_stream = FtpStream::connect(&ftp_config.server_addr)?;
|
ftp_stream.login(&config.username, &config.password)?;
|
||||||
|
|
||||||
ftp_stream.login(&ftp_config.username, &ftp_config.password)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
stream: ftp_stream,
|
stream: ftp_stream,
|
||||||
target_dir: ftp_config.remote_dir.clone(),
|
target_dir: config.remote_dir.clone(),
|
||||||
backups_to_keep: config.backups_to_keep as usize,
|
backups_to_keep,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ pub mod ftp;
|
||||||
pub mod sftp;
|
pub mod sftp;
|
||||||
|
|
||||||
pub trait RemoteBackupFile {
|
pub trait RemoteBackupFile {
|
||||||
/// Type containing the location of the remote backup
|
/// Type containing the location of the remote_backup backup
|
||||||
type LocationType;
|
type LocationType;
|
||||||
|
|
||||||
/// Get the underlying location type
|
/// Get the underlying location type
|
||||||
fn location(&self) -> Self::LocationType;
|
fn location(&self) -> Self::LocationType;
|
||||||
|
|
||||||
/// Get the time the remote file was created
|
/// Get the time the remote_backup file was created
|
||||||
fn time_created(&self) -> chrono::NaiveDateTime;
|
fn time_created(&self) -> chrono::NaiveDateTime;
|
||||||
|
|
||||||
/// Parse the time created from the file name
|
/// Parse the time created from the file name
|
||||||
|
@ -34,10 +34,10 @@ pub trait RemoteBackupSite {
|
||||||
/// Struct representing the location of a backup on the site
|
/// Struct representing the location of a backup on the site
|
||||||
type FileType: RemoteBackupFile;
|
type FileType: RemoteBackupFile;
|
||||||
|
|
||||||
/// Backup a file to the the remote site
|
/// Backup a file to the the remote_backup site
|
||||||
fn backup_to_remote(&mut self, file: PathBuf) -> Result<()>;
|
fn backup_to_remote(&mut self, file: PathBuf) -> Result<()>;
|
||||||
|
|
||||||
/// Get the locations backups contained on the remote site
|
/// Get the locations backups contained on the remote_backup site
|
||||||
fn get_backups(&mut self) -> Result<Vec<Self::FileType>>;
|
fn get_backups(&mut self) -> Result<Vec<Self::FileType>>;
|
||||||
|
|
||||||
/// Remove a backup from the side
|
/// Remove a backup from the side
|
||||||
|
@ -46,7 +46,7 @@ pub trait RemoteBackupSite {
|
||||||
/// Number of backups to keep on the site
|
/// Number of backups to keep on the site
|
||||||
fn backups_to_keep(&self) -> usize;
|
fn backups_to_keep(&self) -> usize;
|
||||||
|
|
||||||
/// Cleanup old backups on the remote site
|
/// Cleanup old backups on the remote_backup site
|
||||||
fn cleanup(&mut self) -> Result<usize> {
|
fn cleanup(&mut self) -> Result<usize> {
|
||||||
let mut backups = self.get_backups()?;
|
let mut backups = self.get_backups()?;
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use ssh2::Session;
|
use ssh2::Session;
|
||||||
|
|
||||||
use crate::config::RemoteBackupConfig;
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::error::AlbatrossError;
|
use crate::error::AlbatrossError;
|
||||||
use crate::remote::{PathLocation, RemoteBackupSite};
|
use crate::remote::{PathLocation, RemoteBackupSite};
|
||||||
|
use crate::config::remote::SFTPConfig;
|
||||||
|
|
||||||
/// SFTP Remote Site
|
/// SFTP Remote Site
|
||||||
pub struct SFTPBackup {
|
pub struct SFTPBackup {
|
||||||
|
@ -20,30 +20,25 @@ pub struct SFTPBackup {
|
||||||
|
|
||||||
impl SFTPBackup {
|
impl SFTPBackup {
|
||||||
/// New SFTPBackup
|
/// New SFTPBackup
|
||||||
pub fn new(config: &RemoteBackupConfig) -> error::Result<Self> {
|
pub fn new(config: &SFTPConfig, backups_to_keep: usize) -> error::Result<Self> {
|
||||||
let sftp_config = config
|
let tcp = TcpStream::connect(&config.server_addr)?;
|
||||||
.sftp
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| AlbatrossError::RemoteConfigError("SFTP".to_string()))?;
|
|
||||||
|
|
||||||
let tcp = TcpStream::connect(&sftp_config.server_addr)?;
|
|
||||||
let mut sess = Session::new()?;
|
let mut sess = Session::new()?;
|
||||||
sess.set_tcp_stream(tcp);
|
sess.set_tcp_stream(tcp);
|
||||||
sess.handshake().unwrap();
|
sess.handshake().unwrap();
|
||||||
|
|
||||||
if let Some(password) = &sftp_config.password {
|
if let Some(password) = &config.password {
|
||||||
sess.userauth_password(&sftp_config.username, password)?;
|
sess.userauth_password(&config.username, password)?;
|
||||||
} else if let Some(key) = &sftp_config.private_key {
|
} else if let Some(key) = &config.private_key {
|
||||||
let public_key = sftp_config.public_key.as_deref();
|
let public_key = config.public_key.as_deref();
|
||||||
sess.userauth_pubkey_file(&sftp_config.username, public_key, key, None)?;
|
sess.userauth_pubkey_file(&config.username, public_key, key, None)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(AlbatrossError::NoSSHAuth);
|
return Err(AlbatrossError::NoSSHAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
session: sess,
|
session: sess,
|
||||||
target_dir: sftp_config.remote_dir.clone(),
|
target_dir: config.remote_dir.clone(),
|
||||||
backups_to_keep: config.backups_to_keep as usize,
|
backups_to_keep,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue