Commit 6412af76 authored by Sophie Herold's avatar Sophie Herold

Introduce config format version 1

parent 200a19ad
pub mod prelude;
mod utils; mod utils;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
...@@ -5,60 +6,10 @@ use arc_swap::ArcSwap; ...@@ -5,60 +6,10 @@ use arc_swap::ArcSwap;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::sync::Arc; use std::sync::Arc;
use super::shared::{self, *}; use crate::shared::{self, *};
use crate::ui::prelude::*; use crate::ui::prelude::*;
use utils::*; use utils::*;
/*
thread_local!(
static SERVICE: Service = Service {
volume_monitor: gio::VolumeMonitor::get(),
}
);
struct Service {
volume_monitor: gio::VolumeMonitor,
}
*/
pub fn init_device_listening() {
// TODO: Reactivate detection
/*
SERVICE.with(|service| {
service.volume_monitor.connect_mount_added(|_, mount| {
ui::APP.with(|app| {
let backups = &SETTINGS.load().backups;
let uuid = shared::get_mount_uuid(mount);
if let Some(uuid) = uuid {
let backup = backups
.values()
.find(|b| b.volume_uuid.as_ref() == Some(&uuid));
if let Some(backup) = backup {
let notification = gio::Notification::new("Backup Medium Connected");
notification.set_body(Some(
format!(
"{} on Disk '{}'",
backup.label.as_ref().unwrap(),
&backup.device.as_ref().unwrap()
)
.as_str(),
));
notification.add_button_with_target_value(
"Run Backup",
"app.detail",
Some(&backup.id.to_variant()),
);
gtk_app()
.send_notification(Some(uuid.as_str()), &notification);
}
}
});
});
});
*/
}
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct Status { pub struct Status {
pub run: Run, pub run: Run,
...@@ -102,13 +53,15 @@ pub struct StatsArchiveStats { ...@@ -102,13 +53,15 @@ pub struct StatsArchiveStats {
pub original_size: u64, pub original_size: u64,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Info { pub struct List {
pub archives: Vec<InfoArchive>, pub archives: Vec<ListArchive>,
pub encryption: Encryption,
pub repository: Repository,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InfoArchive { pub struct ListArchive {
pub id: String, pub id: String,
pub name: String, pub name: String,
pub comment: String, pub comment: String,
...@@ -116,116 +69,95 @@ pub struct InfoArchive { ...@@ -116,116 +69,95 @@ pub struct InfoArchive {
pub hostname: String, pub hostname: String,
pub start: chrono::naive::NaiveDateTime, pub start: chrono::naive::NaiveDateTime,
pub end: chrono::naive::NaiveDateTime, pub end: chrono::naive::NaiveDateTime,
pub stats: StatsArchiveStats,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct List { pub struct Encryption {
pub archives: Vec<ListArchive>, pub mode: String,
pub keyfile: Option<std::path::PathBuf>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ListArchive { pub struct Repository {
pub id: String, pub id: String,
pub name: String, pub last_modified: chrono::naive::NaiveDateTime,
pub comment: String, pub location: std::path::PathBuf,
pub username: String,
pub hostname: String,
pub start: chrono::naive::NaiveDateTime,
pub end: chrono::naive::NaiveDateTime,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Borg { pub struct Borg {
config: BackupConfig, config: BackupConfig,
password: Option<Password>, password: Option<Password>,
last: u64,
} }
impl Borg { #[derive(Clone)]
pub fn new(config: BackupConfig) -> Self { pub struct BorgOnlyRepo {
Self { repo: BackupRepo,
config, password: Option<Password>,
password: None, }
last: 1000,
}
}
pub fn get_config(&self) -> BackupConfig { pub trait BorgRunConfig {
self.config.clone() fn get_repo(&self) -> BackupRepo;
} fn get_password(&self) -> Option<Password>;
fn unset_password(&mut self);
fn set_password(&mut self, password: Password);
fn is_encrypted(&self) -> bool;
fn get_config_id(&self) -> Option<String>;
}
pub fn set_password(&mut self, password: Password) { impl BorgRunConfig for Borg {
fn get_repo(&self) -> BackupRepo {
self.config.repo.clone()
}
fn get_password(&self) -> Option<Password> {
self.password.clone()
}
fn set_password(&mut self, password: Password) {
self.password = Some(password); self.password = Some(password);
} }
fn unset_password(&mut self) {
pub fn unset_password(&mut self) {
self.password = None; self.password = None;
} }
fn is_encrypted(&self) -> bool {
pub fn set_limit_last(&mut self, last: u64) -> &Self { self.config.encrypted
self.last = last;
self
} }
fn get_config_id(&self) -> Option<String> {
pub fn version() -> Result<String, BorgErr> { Some(self.config.id.clone())
let borg = BorgCall::new_raw()
.add_options(&["--log-json", "--version"])
.output()?;
check_stderr(&borg)?;
Ok(String::from_utf8_lossy(&borg.stdout).to_string())
} }
}
pub fn peek(&self) -> Result<(), BorgErr> { impl BorgRunConfig for BorgOnlyRepo {
let borg = BorgCall::new("list") fn get_repo(&self) -> BackupRepo {
.add_options(&["--json", "--last=1"]) self.repo.clone()
.add_envs(vec![
("BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK", "yes"),
("BORG_RELOCATED_REPO_ACCESS_IS_OK", "yes"),
])
.add_basics(self)?
.output()?;
check_stderr(&borg)?;
let _: serde_json::Value = serde_json::from_slice(&borg.stdout)?;
Ok(())
} }
fn get_password(&self) -> Option<Password> {
pub fn list(&self) -> Result<Vec<ListArchive>, BorgErr> { self.password.clone()
let borg = BorgCall::new("list")
.add_options(&[
"--json",
"--last",
&self.last.to_string(),
"--format={hostname}{username}{comment}{end}",
])
.add_basics(self)?
.output()?;
check_stderr(&borg)?;
let json: List = serde_json::from_slice(&borg.stdout)?;
Ok(json.archives)
} }
fn set_password(&mut self, password: Password) {
self.password = Some(password);
}
fn unset_password(&mut self) {
self.password = None;
}
fn is_encrypted(&self) -> bool {
false
}
fn get_config_id(&self) -> Option<String> {
None
}
}
pub fn mount(&self) -> Result<(), BorgErr> { /// Features that need a complete backup config
std::fs::DirBuilder::new() impl Borg {
.recursive(true) pub fn new(config: BackupConfig) -> Self {
.create(self.get_mount_point())?; Self {
config,
let borg = BorgCall::new("mount") password: None,
.add_basics(self)? }
.add_positional(&self.get_mount_point().to_string_lossy()) }
.output()?;
check_stderr(&borg)?;
Ok(()) pub fn get_config(&self) -> BackupConfig {
self.config.clone()
} }
pub fn umount(&self) -> Result<(), BorgErr> { pub fn umount(&self) -> Result<(), BorgErr> {
...@@ -244,7 +176,7 @@ impl Borg { ...@@ -244,7 +176,7 @@ impl Borg {
Ok(()) Ok(())
} }
fn get_mount_dir() -> std::path::PathBuf { pub fn get_mount_dir() -> std::path::PathBuf {
let mut dir = shared::get_home_dir(); let mut dir = shared::get_home_dir();
dir.push(crate::REPO_MOUNT_DIR); dir.push(crate::REPO_MOUNT_DIR);
dir dir
...@@ -256,23 +188,14 @@ impl Borg { ...@@ -256,23 +188,14 @@ impl Borg {
dir dir
} }
pub fn info(&self) -> Result<Info, BorgErr> { pub fn mount(&self) -> Result<(), BorgErr> {
let borg = BorgCall::new("info") std::fs::DirBuilder::new()
.add_options(&["--json", "--last=100"]) .recursive(true)
.add_basics(self)? .create(self.get_mount_point())?;
.output()?;
check_stderr(&borg)?;
let x = serde_json::from_slice(&borg.stdout)?;
Ok(x)
}
pub fn init(&self) -> Result<(), BorgErr> { let borg = BorgCall::new("mount")
let borg = BorgCall::new("init")
.add_options(&["--encryption=repokey"])
.add_basics(self)? .add_basics(self)?
.add_positional(&self.get_mount_point().to_string_lossy())
.output()?; .output()?;
check_stderr(&borg)?; check_stderr(&borg)?;
...@@ -381,6 +304,82 @@ impl Borg { ...@@ -381,6 +304,82 @@ impl Borg {
} }
} }
impl BorgOnlyRepo {
pub fn new(repo: BackupRepo) -> Self {
Self {
repo,
password: None,
}
}
}
impl BorgBasics for Borg {}
impl BorgBasics for BorgOnlyRepo {}
/// Features that are available without complete backup config
pub trait BorgBasics: BorgRunConfig + Sized + Clone + Send {
fn peek(&self) -> Result<List, BorgErr> {
let borg = BorgCall::new("list")
.add_options(&[
"--json",
"--last=1",
"--format={hostname}{username}{comment}{end}",
])
.add_envs(vec![
("BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK", "yes"),
("BORG_RELOCATED_REPO_ACCESS_IS_OK", "yes"),
])
.add_basics(self)?
.output()?;
check_stderr(&borg)?;
let json: List = serde_json::from_slice(&borg.stdout)?;
Ok(json)
}
fn list(&self) -> Result<Vec<ListArchive>, BorgErr> {
let borg = BorgCall::new("list")
.add_options(&[
"--json",
"--last",
//TODO: pass as arg
"100",
"--format={hostname}{username}{comment}{end}",
])
.add_basics(self)?
.output()?;
check_stderr(&borg)?;
let json: List = serde_json::from_slice(&borg.stdout)?;
Ok(json.archives)
}
fn init(&self) -> Result<List, BorgErr> {
let borg = BorgCall::new("init")
.add_options(&["--encryption=repokey"])
.add_basics(self)?
.output()?;
check_stderr(&borg)?;
self.peek()
}
}
pub fn version() -> Result<String, BorgErr> {
let borg = BorgCall::new_raw()
.add_options(&["--log-json", "--version"])
.output()?;
check_stderr(&borg)?;
Ok(String::from_utf8_lossy(&borg.stdout).to_string())
}
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct Communication { pub struct Communication {
pub status: Arc<ArcSwap<Status>>, pub status: Arc<ArcSwap<Status>>,
......
pub use super::{BorgBasics, BorgRunConfig};
use super::Borg; use super::{Borg, BorgRunConfig};
use std::io::Write; use std::io::Write;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::os::unix::io::IntoRawFd; use std::os::unix::io::IntoRawFd;
...@@ -138,7 +138,7 @@ impl BorgCall { ...@@ -138,7 +138,7 @@ impl BorgCall {
self self
} }
pub fn add_password(&mut self, borg: &Borg) -> Result<&mut Self, BorgErr> { pub fn add_password<T: BorgRunConfig>(&mut self, borg: &T) -> Result<&mut Self, BorgErr> {
// Password pipe // Password pipe
let (pipe_reader, mut pipe_writer) = std::os::unix::net::UnixStream::pair()?; let (pipe_reader, mut pipe_writer) = std::os::unix::net::UnixStream::pair()?;
...@@ -159,15 +159,15 @@ impl BorgCall { ...@@ -159,15 +159,15 @@ impl BorgCall {
pipe_reader.into_raw_fd().to_string(), pipe_reader.into_raw_fd().to_string(),
); );
if let Some(ref password) = borg.password { if let Some(ref password) = borg.get_password() {
debug!("Using password enforced by explicitly passed password"); debug!("Using password enforced by explicitly passed password");
pipe_writer.write_all(password)?; pipe_writer.write_all(password)?;
} else if borg.config.encrypted { } else if borg.is_encrypted() {
debug!("Config says the backup is encrypted"); debug!("Config says the backup is encrypted");
let password: Zeroizing<Vec<u8>> = let password: Zeroizing<Vec<u8>> =
secret_service::SecretService::new(secret_service::EncryptionType::Dh)? secret_service::SecretService::new(secret_service::EncryptionType::Dh)?
.search_items(vec![ .search_items(vec![
("backup_id", &borg.config.id), ("backup_id", &borg.get_config_id().unwrap_or_default()),
("program", env!("CARGO_PKG_NAME")), ("program", env!("CARGO_PKG_NAME")),
])? ])?
.get(0) .get(0)
...@@ -185,11 +185,11 @@ impl BorgCall { ...@@ -185,11 +185,11 @@ impl BorgCall {
Ok(self) Ok(self)
} }
pub fn add_basics(&mut self, borg: &Borg) -> Result<&mut Self, BorgErr> { pub fn add_basics<T: BorgRunConfig>(&mut self, borg: &T) -> Result<&mut Self, BorgErr> {
self.add_options(&["--log-json"]); self.add_options(&["--log-json"]);
if self.positional.is_empty() { if self.positional.is_empty() {
self.add_positional(&borg.config.repo.to_string()); self.add_positional(&borg.get_repo().to_string());
} }
self.add_password(borg)?; self.add_password(borg)?;
......
...@@ -9,6 +9,17 @@ extern crate matches; ...@@ -9,6 +9,17 @@ extern crate matches;
#[macro_use] #[macro_use]
extern crate enclose; extern crate enclose;
static CONFIG_VERSION: u16 = 1;
static BORG_DELAY_RECONNECT: std::time::Duration = std::time::Duration::from_secs(60);
static BORG_LOCK_WAIT_RECONNECT: std::time::Duration = std::time::Duration::from_secs(60 * 7);
const REPO_MOUNT_DIR: &str = ".mnt/borg";
// require borg 1.1
const BORG_MIN_MAJOR: u32 = 1;
const BORG_MIN_MINOR: u32 = 1;
macro_rules! data_dir { macro_rules! data_dir {
() => { () => {
concat!(env!("CARGO_MANIFEST_DIR"), "/data") concat!(env!("CARGO_MANIFEST_DIR"), "/data")
...@@ -24,15 +35,6 @@ macro_rules! application_id { ...@@ -24,15 +35,6 @@ macro_rules! application_id {
static APPLICATION_ID: &str = application_id!(); static APPLICATION_ID: &str = application_id!();
static APPLICATION_NAME: &str = include_str!(concat!(data_dir!(), "/APPLICATION_NAME")); static APPLICATION_NAME: &str = include_str!(concat!(data_dir!(), "/APPLICATION_NAME"));
static BORG_DELAY_RECONNECT: std::time::Duration = std::time::Duration::from_secs(60);
static BORG_LOCK_WAIT_RECONNECT: std::time::Duration = std::time::Duration::from_secs(60 * 7);
const REPO_MOUNT_DIR: &str = ".mnt/borg";
// require borg 1.1
const BORG_MIN_MAJOR: u32 = 1;
const BORG_MIN_MINOR: u32 = 1;
pub mod borg; pub mod borg;
pub mod globals; pub mod globals;
pub mod shared; pub mod shared;
......
...@@ -7,14 +7,24 @@ use zeroize::Zeroizing; ...@@ -7,14 +7,24 @@ use zeroize::Zeroizing;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BackupConfig { pub struct BackupConfig {
#[serde(default)]
pub config_version: u16,
pub id: String, pub id: String,
#[serde(default = "fake_repo_id")]
pub repo_id: String,
pub repo: BackupRepo, pub repo: BackupRepo,
pub encrypted: bool, pub encrypted: bool,
#[serde(default)]
pub encryption_mode: String,
pub include: BTreeSet<path::PathBuf>, pub include: BTreeSet<path::PathBuf>,
pub exclude: BTreeSet<Pattern>, pub exclude: BTreeSet<Pattern>,
pub last_run: Option<RunInfo>, pub last_run: Option<RunInfo>,
} }
fn fake_repo_id() -> String {
format!("-randomid-{}", glib::uuid_string_random().to_string())
}
impl BackupConfig { impl BackupConfig {
pub fn include_dirs(&self) -> Vec<path::PathBuf> { pub fn include_dirs(&self) -> Vec<path::PathBuf> {
let mut dirs = Vec::new(); let mut dirs = Vec::new();
...@@ -83,9 +93,9 @@ impl RunInfo { ...@@ -83,9 +93,9 @@ impl RunInfo {
pub type Password = Zeroizing<Vec<u8>>; pub type Password = Zeroizing<Vec<u8>>;
impl BackupConfig { impl BackupRepo {
pub fn new_from_uri(uri: String) -> Self { pub fn new_from_uri(uri: String) -> Self {
Self::new_from_repo(BackupRepo::Remote { uri }) BackupRepo::Remote { uri }
} }
pub fn new_from_path(repo: &path::Path) -> Self { pub fn new_from_path(repo: &path::Path) -> Self {
...@@ -112,7 +122,7 @@ impl BackupConfig { ...@@ -112,7 +122,7 @@ impl BackupConfig {
.as_ref() .as_ref()
.map(std::string::ToString::to_string); .map(std::string::ToString::to_string);
Self::new_from_repo(BackupRepo::Local { BackupRepo::Local {
path: repo.to_path_buf(), path: repo.to_path_buf(),
icon, icon,
label: mount label: mount
...@@ -125,19 +135,24 @@ impl BackupConfig { ...@@ -125,19 +135,24 @@ impl BackupConfig {
.map(Into::into), .map(Into::into),
removable: drive.as_ref().map_or(false, gio::Drive::is_removable), removable: drive.as_ref().map_or(false, gio::Drive::is_removable),
volume_uuid, volume_uuid,
}) }
} }
}
pub