Added FTPS backups

+ Made remote backups more generic, should be easier to add new backup types in the future
+ General cleanup
+ clippy + fmt
This commit is contained in:
zerohd committed 2021-02-09 20:47:40 -06:00
1 parent 9c7f9906ad
commit 9a264abe88
12 files changed
+418 -161

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
use crate::config::BackupConfig;
use crate::error::Result;
use crate::remote::{PathLocation, RemoteBackupSite};
use std::path::PathBuf;
pub struct FileBackup {
/// Target directory on the file system
target_dir: PathBuf,
/// Number of backups to keep
backups_to_keep: usize,
}
impl FileBackup {
/// New FileBackup
pub fn new(config: &BackupConfig) -> Result<Self> {
Ok(Self {
target_dir: config.output_dir.clone(),
backups_to_keep: config.backups_to_keep as usize,
})
}
}
impl RemoteBackupSite for FileBackup {
type FileType = PathLocation;
fn backup_to_remote(&mut self, file: PathBuf) -> Result<()> {
let dest = self.target_dir.join(file.file_name().unwrap());
std::fs::copy(file, dest)?;
Ok(())
}
fn get_backups(&mut self) -> Result<Vec<Self::FileType>> {
Ok(self
.target_dir
.read_dir()?
.filter_map(|file| Self::FileType::new(file.unwrap().path()))
.collect())
}
fn remove_backup(&mut self, backup: Self::FileType) -> Result<()> {
Ok(std::fs::remove_file(backup.location)?)
}
fn backups_to_keep(&self) -> usize {
self.backups_to_keep
}
}
+79
View File
@@ -0,0 +1,79 @@
use ftp::native_tls::TlsConnector;
use ftp::FtpStream;
use std::path::PathBuf;
use crate::config::RemoteBackupConfig;
use crate::error;
use crate::error::AlbatrossError;
use crate::remote::{PathLocation, RemoteBackupSite};
/// FTPS Remote Site
pub struct FTPSBackup {
/// FTP command stream
stream: FtpStream,
/// Remote target directory
target_dir: PathBuf,
/// Number of backups to keep
backups_to_keep: usize,
}
impl FTPSBackup {
/// New FTPSBackup
pub fn new(config: &RemoteBackupConfig) -> error::Result<Self> {
let ftps_config = config
.ftps
.as_ref()
.ok_or_else(|| AlbatrossError::RemoteConfigError("FTPS".to_string()))?;
let ctx = TlsConnector::new().unwrap();
let (ftp_stream, _) = FtpStream::connect(&ftps_config.server_addr)?;
let domain = ftps_config
.domain
.as_ref()
.map_or_else(|| "".to_string(), |s| s.clone());
let mut ftp_stream = ftp_stream.into_secure(ctx, domain.as_str())?;
ftp_stream.login(&ftps_config.username, &ftps_config.password)?;
Ok(Self {
stream: ftp_stream,
target_dir: ftps_config.remote_dir.clone(),
backups_to_keep: config.backups_to_keep as usize,
})
}
}
impl Drop for FTPSBackup {
fn drop(&mut self) {
self.stream.quit().ok();
}
}
impl RemoteBackupSite for FTPSBackup {
type FileType = PathLocation;
fn backup_to_remote(&mut self, file: PathBuf) -> error::Result<()> {
let mut local_file = std::fs::File::open(&file)?;
let location = self.target_dir.join(file);
self.stream
.put(location.to_str().unwrap(), &mut local_file)?;
Ok(())
}
fn get_backups(&mut self) -> error::Result<Vec<Self::FileType>> {
let files = self.stream.list(Some(self.target_dir.to_str().unwrap()))?;
Ok(files
.into_iter()
.filter_map(|file| Self::FileType::new(PathBuf::from(file)))
.collect())
}
fn remove_backup(&mut self, backup: Self::FileType) -> error::Result<()> {
Ok(self.stream.rm(backup.location.to_str().unwrap())?)
}
fn backups_to_keep(&self) -> usize {
self.backups_to_keep
}
}
+107
View File
@@ -0,0 +1,107 @@
use std::path::PathBuf;
use chrono::NaiveDateTime;
use crate::error::Result;
pub mod file;
pub mod ftps;
pub mod sftp;
pub trait RemoteBackupFile {
/// Type containing the location of the remote backup
type LocationType;
/// Get the underlying location type
fn location(&self) -> Self::LocationType;
/// Get the time the remote file was created
fn time_created(&self) -> chrono::NaiveDateTime;
/// Parse the time created from the file name
fn parse_file_name(file_name: &str) -> Option<chrono::NaiveDateTime> {
let time: Vec<&str> = file_name.split("_backup.tar.gz").collect();
if let Some(time_str) = time.get(0) {
chrono::NaiveDateTime::parse_from_str(time_str, "%d-%m-%y_%H.%M.%S").ok()
} else {
None
}
}
}
pub trait RemoteBackupSite {
/// Struct representing the location of a backup on the site
type FileType: RemoteBackupFile;
/// Backup a file to the the remote site
fn backup_to_remote(&mut self, file: PathBuf) -> Result<()>;
/// Get the locations backups contained on the remote site
fn get_backups(&mut self) -> Result<Vec<Self::FileType>>;
/// Remove a backup from the side
fn remove_backup(&mut self, backup: Self::FileType) -> Result<()>;
/// Number of backups to keep on the site
fn backups_to_keep(&self) -> usize;
/// Cleanup old backups on the remote site
fn cleanup(&mut self) -> Result<usize> {
let mut backups = self.get_backups()?;
backups.sort_by_key(|backup| backup.time_created());
let mut backups: Vec<Self::FileType> = backups.into_iter().rev().collect();
let mut removed_count: usize = 0;
if backups.len() > self.backups_to_keep() {
for _ in 0..(backups.len() - self.backups_to_keep()) {
if let Some(backup) = backups.pop() {
self.remove_backup(backup)?;
removed_count += 1;
}
}
}
Ok(removed_count)
}
}
/// Backup location that can be represented by a path
pub struct PathLocation {
location: PathBuf,
time_created: NaiveDateTime,
}
impl PathLocation {
/// New PathLocation
fn new(path: PathBuf) -> Option<Self> {
if let Some(file_name) = path.file_name() {
let file_name = file_name.to_str().unwrap();
if let Some(time) = Self::parse_file_name(file_name) {
Some(Self {
location: path,
time_created: time,
})
} else {
None
}
} else {
None
}
}
}
impl RemoteBackupFile for PathLocation {
type LocationType = PathBuf;
fn location(&self) -> Self::LocationType {
self.location.to_path_buf()
}
fn time_created(&self) -> NaiveDateTime {
self.time_created
}
}
+83
View File
@@ -0,0 +1,83 @@
use std::net::TcpStream;
use std::path::PathBuf;
use ssh2::Session;
use crate::config::RemoteBackupConfig;
use crate::error;
use crate::error::AlbatrossError;
use crate::remote::{PathLocation, RemoteBackupSite};
/// SFTP Remote Site
pub struct SFTPBackup {
/// SSH Session
session: Session,
/// Remote target directory
target_dir: PathBuf,
/// Number of backups to keep
backups_to_keep: usize,
}
impl SFTPBackup {
/// New SFTPBackup
pub fn new(config: &RemoteBackupConfig) -> error::Result<Self> {
let sftp_config = config
.sftp
.as_ref()
.ok_or_else(|| AlbatrossError::RemoteConfigError("SFTP".to_string()))?;
let tcp = TcpStream::connect(&sftp_config.server_addr)?;
let mut sess = Session::new()?;
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
if let Some(password) = &sftp_config.password {
sess.userauth_password(&sftp_config.username, password)?;
} else if let Some(key) = &sftp_config.private_key {
let public_key = sftp_config.public_key.as_deref();
sess.userauth_pubkey_file(&sftp_config.username, public_key, key, None)?;
} else {
return Err(AlbatrossError::NoSSHAuth);
}
Ok(Self {
session: sess,
target_dir: sftp_config.remote_dir.clone(),
backups_to_keep: config.backups_to_keep as usize,
})
}
}
impl RemoteBackupSite for SFTPBackup {
type FileType = PathLocation;
fn backup_to_remote(&mut self, file: PathBuf) -> error::Result<()> {
let remote_path = self.target_dir.join(file.file_name().unwrap());
let mut local_file = std::fs::File::open(&file)?;
let sftp = self.session.sftp()?;
let mut remote_file = sftp.create(&remote_path)?;
std::io::copy(&mut local_file, &mut remote_file)?;
Ok(())
}
fn get_backups(&mut self) -> error::Result<Vec<Self::FileType>> {
let files = self.session.sftp()?.readdir(&self.target_dir)?;
Ok(files
.into_iter()
.filter_map(|(file, _)| Self::FileType::new(file))
.collect())
}
fn remove_backup(&mut self, backup: Self::FileType) -> error::Result<()> {
Ok(self.session.sftp()?.unlink(&*backup.location)?)
}
fn backups_to_keep(&self) -> usize {
self.backups_to_keep
}
}