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.yml
master latest
Joey Hines 2021-09-19 13:56:30 -06:00
parent d207c6df19
commit 8378f2ead3
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
12 changed files with 70 additions and 66 deletions

View File

@ -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"

4
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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) => {

View File

@ -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

View File

@ -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,
}

View File

@ -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),
} }
} }

View File

@ -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,
}) })
} }
} }

View File

@ -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,
}) })
} }
} }

View File

@ -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()?;

View File

@ -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,
}) })
} }
} }