diff --git a/datman.old/Cargo.toml b/datman.old/Cargo.toml deleted file mode 100644 index 58c910a..0000000 --- a/datman.old/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "datman" -version = "0.7.0-alpha.1" -authors = ["Olivier 'reivilibre' "] -edition = "2021" -repository = "https://bics.ga/reivilibre/yama" -license = "GPL-3.0-or-later" - -description = "A chunked and deduplicated backup system using Yama" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "3.1.18", features = ["derive"] } -crossbeam-channel = "0.5.1" -anyhow = "1.0" -thiserror = "1.0" -serde = { version = "1.0.104", features = ["derive"] } -serde_json = "1.0.64" -toml = "0.5.5" -log = "0.4" -env_logger = "0.7.1" -indicatif = "0.14.0" -arc-interner = "0.5.1" -zstd = "0.11.2" # 0.11.2+zstd.1.5.2 -byteorder = "1" -termion = "1.5.6" -glob = "0.3.0" -humansize = "1.1.1" -chrono = "0.4.19" -itertools = "0.10.1" -hostname = "0.3.1" -yama = { path = "../yama", version = "0.7.0-alpha.1" } -metrics = "0.17.1" -bare-metrics-recorder = { version = "0.1.0" } -comfy-table = "6.0.0-rc.1" -libc = "0.2.126" -io-streams = "0.11.0" \ No newline at end of file diff --git a/datman.old/README.md b/datman.old/README.md deleted file mode 100644 index 934b50e..0000000 --- a/datman.old/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# datman: DATa MANager - -Datman is a tool to make it easier to use Yama for backups. - -Features: - -* Chunk-based deduplication -* (optional) Compression using Zstd and a specifiable dictionary -* (optional) Encryption -* Ability to back up to remote machines over SSH -* Labelling of files in a backup source; different destinations can choose to backup either all or a subset of the labels. - -See the documentation for more information. diff --git a/datman.old/src/bin/datman.rs b/datman.old/src/bin/datman.rs deleted file mode 100644 index 01f03ea..0000000 --- a/datman.old/src/bin/datman.rs +++ /dev/null @@ -1,468 +0,0 @@ -/* -This file is part of Yama. - -Yama is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Yama is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Yama. If not, see . -*/ - -use std::fs::File; -use std::io::{BufReader, BufWriter, Write}; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; - -use clap::Parser; -use env_logger::Env; - -use anyhow::{bail, Context}; -use bare_metrics_recorder::recording::BareMetricsRecorderCore; -use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeZone, Utc}; -use datman::commands::backup::{backup_all_sources_to_destination, backup_source_to_destination}; -use datman::commands::ilabel::interactive_labelling_session; -use datman::commands::prune::{prune_with_retention_policy, RetentionPolicy}; -use datman::commands::{init_descriptor, pushpull}; -use datman::descriptor::{load_descriptor, SourceDescriptor}; -use datman::get_hostname; -use datman::remote::backup_source_requester::backup_remote_source_to_destination; -use datman::remote::backup_source_responder; -use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; -use itertools::Itertools; -use log::info; -use std::str::FromStr; -use yama::commands::load_pile_descriptor; -use yama::operations::legacy_pushpull::{open_pile_with_work_bypass, BypassLevel}; - -pub const FAILURE_SYMBOL_OBNOXIOUS_FLASHING: &str = "\x1b[5m\x1b[31m⚠️ \x1b[25m\x1b[22m"; -pub const BOLD: &str = "\x1b[1m"; -pub const BOLD_OFF: &str = "\x1b[22m"; -pub const WHITE: &str = "\x1b[37m"; -pub const RED: &str = "\x1b[31m"; -pub const GREEN: &str = "\x1b[32m"; - -#[derive(Parser)] -pub enum DatmanCommand { - /// Initialise a datman descriptor in this directory. - Init {}, - - /// - Status {}, - - #[clap(name = "ilabel")] - InteractiveLabelling { - /// Name of the source to label. - source_name: String, - }, - - #[clap(name = "ibrowse")] - InteractiveBrowsing { - /// Name of the source to browse. - source_name: String, - }, - - /// Back up a source locally or over the network. - BackupOne { - /// Name of the source to back up. - source_name: String, - - /// Name of the destination to back up to. - destination_name: String, - }, - - BackupAll { - /// Name of the remote to back up. - /// Special value 'self' means 'this host only'. - /// Special value 'all' means 'all hosts'. - remote_name: String, - - /// Name of the destination to back up to. - destination_name: String, - }, - - Extract { - /// Name of the 'source' to extract - /// Omit for 'all'. - #[clap(short)] - source_name: Option, - - /// If specified, will get the first backup after this date. - #[clap(long)] - after: Option, - - /// If specified, will get the last backup before this date. The default behaviour is to get the latest. - #[clap(long)] - before: Option, - - /// If not specified, time-restricted extractions that don't have a pointer for every source - /// will instead lead to an error. - #[clap(long)] - accept_partial: bool, // TODO unimplemented. - - /// Name of the pile to extract from - pile_name: String, - - /// Place to extract to. - destination: PathBuf, - - /// Skip applying metadata. Might be needed to extract without superuser privileges. - #[clap(long)] - skip_metadata: bool, - }, - - Report { - /// Name of the pile to report on. - pile_name: String, - - /// Don't summarise months. - #[clap(long)] - individual: bool, - }, - - #[clap(name = "_backup_source_responder")] - InternalBackupSourceResponder, - - /// Pulls all pointers from a remote pile to a local pile. - /// Does not yet support label filtering, but will do in the future. - Pull { - /// e.g. 'myserver:main' - remote_and_remote_pile: String, - - pile_name: String, - }, - - /// Applies a retention policy by removing unnecessary backups. - /// Does not reclaim space by itself: use - /// `yama check --apply-gc --shallow` - /// & `yama compact` - /// to do that. - Prune { pile_name: String }, - - #[clap(name = "_pull_responder_offerer")] - InternalPullResponderOfferer { - datman_path: PathBuf, - pile_name: String, - }, -} - -pub struct HumanDateTime(pub DateTime); - -impl FromStr for HumanDateTime { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - if let Ok(date_only) = NaiveDate::parse_from_str(s, "%Y-%m-%d") { - let local_date = chrono::offset::Local.from_local_date(&date_only).unwrap(); - let local_datetime = local_date.and_hms(0, 0, 0); - Ok(HumanDateTime(local_datetime)) - } else if let Ok(date_and_time) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { - let local_datetime = chrono::offset::Local - .from_local_datetime(&date_and_time) - .unwrap(); - Ok(HumanDateTime(local_datetime)) - } else if let Ok(date_and_time) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { - let local_datetime = chrono::offset::Local - .from_local_datetime(&date_and_time) - .unwrap(); - Ok(HumanDateTime(local_datetime)) - } else { - bail!("Couldn't parse using either format. Use one of: 2021-05-16 OR 2021-05-16T17:42:14 OR 2021-05-16 17:42:14"); - } - } -} - -fn with_obvious_successfail_message(result: anyhow::Result) -> anyhow::Result { - match &result { - Ok(_) => { - eprintln!("Operation {}successful{}.", GREEN, WHITE); - } - Err(error) => { - eprintln!("{:?}", error); - eprintln!( - "{}{}Operation {}{}FAILED{}!{}", - FAILURE_SYMBOL_OBNOXIOUS_FLASHING, WHITE, RED, BOLD, WHITE, BOLD_OFF - ); - } - }; - result -} - -fn with_exitcode(result: anyhow::Result) { - match &result { - Ok(_) => { - std::process::exit(0); - } - Err(_) => { - std::process::exit(5); - } - }; -} - -fn main() -> anyhow::Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - - let now = Utc::now(); - - let (shard, _stopper) = BareMetricsRecorderCore::new(File::create(format!( - "/tmp/datman_{}.baremetrics", - now.format("%F_%H%M%S") - ))?) - .start("datman".to_string())?; - shard.install_as_metrics_recorder()?; - - let opts: DatmanCommand = DatmanCommand::parse(); - - match opts { - DatmanCommand::Init {} => { - init_descriptor(Path::new(".")).unwrap(); - } - DatmanCommand::Status { .. } => { - unimplemented!(); - } - DatmanCommand::InteractiveLabelling { source_name } => { - interactive_labelling_session(Path::new("."), source_name)?; - } - DatmanCommand::InteractiveBrowsing { source_name } => { - datman::commands::ibrowse::session(Path::new("."), source_name)?; - } - DatmanCommand::BackupOne { - source_name, - destination_name, - } => { - let my_hostname = get_hostname(); - let descriptor = load_descriptor(Path::new(".")).unwrap(); - let source = &descriptor.sources[&source_name]; - let destination = &descriptor.piles[&destination_name]; - - let mut pbar = ProgressBar::with_draw_target(0, ProgressDrawTarget::stdout_with_hz(10)); - pbar.set_style( - ProgressStyle::default_bar().template( - "[{elapsed_precise}]/[{eta}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}", - ), - ); - pbar.set_message("storing"); - - let is_remote = if let SourceDescriptor::DirectorySource { hostname, .. } = source { - hostname != &my_hostname - } else { - false - }; - - let result = if is_remote { - backup_remote_source_to_destination( - source, - destination, - &descriptor, - Path::new("."), - &source_name, - &destination_name, - yama::utils::get_number_of_workers("YAMA_CHUNKERS"), - pbar, - ) - } else { - backup_source_to_destination( - source, - destination, - &descriptor, - Path::new("."), - &source_name, - &destination_name, - yama::utils::get_number_of_workers("YAMA_CHUNKERS"), - &mut pbar, - ) - }; - with_exitcode(with_obvious_successfail_message(result)) - } - DatmanCommand::BackupAll { - remote_name, - destination_name, - } => { - let descriptor = load_descriptor(Path::new(".")).unwrap(); - let destination = &descriptor.piles[&destination_name]; - - let mut pbar = ProgressBar::with_draw_target(0, ProgressDrawTarget::stdout_with_hz(10)); - pbar.set_style( - ProgressStyle::default_bar().template( - "[{elapsed_precise}]/[{eta}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}", - ), - ); - pbar.set_message("storing"); - - backup_all_sources_to_destination( - destination, - &descriptor, - Path::new("."), - &destination_name, - yama::utils::get_number_of_workers("YAMA_CHUNKERS"), - &mut pbar, - remote_name, - ) - .unwrap(); - } - DatmanCommand::Extract { - source_name, - after, - before, - accept_partial, - pile_name, - destination, - skip_metadata, - } => { - if !accept_partial { - bail!("Specify --accept-partial until running without it is supported."); - } - - if after.is_some() && before.is_some() { - bail!("Can't specify both before and after!"); - } - - let before = before.map(|dt| dt.0.with_timezone(&Utc)); - let after = after.map(|dt| dt.0.with_timezone(&Utc)); - - datman::commands::extract::extract( - &destination, - Path::new("."), - source_name.as_ref().map(|x| x.as_ref()), - &pile_name, - before.into(), - after.into(), - !skip_metadata, - !skip_metadata, - !skip_metadata, - yama::utils::get_number_of_workers("YAMA_EXTRACTORS"), - )?; - } - - DatmanCommand::InternalBackupSourceResponder => { - info!("Datman responder at {:?}", std::env::current_exe()?); - backup_source_responder::handler_stdio()?; - } - - DatmanCommand::Report { - pile_name, - individual, - } => { - let descriptor = load_descriptor(Path::new(".")).unwrap(); - let destination = &descriptor.piles[&pile_name]; - let report = - datman::commands::report::generate_report(destination, &descriptor, !individual)?; - - datman::commands::report::print_filesystem_space(&destination.path)?; - datman::commands::report::print_report(&report)?; - } - DatmanCommand::Pull { - remote_and_remote_pile, - pile_name, - } => { - let (hostname, remote_datman_path, remote_pile_name) = remote_and_remote_pile - .split(':') - .collect_tuple() - .context("You must pull from a remote pile specified as remote:path:pile.")?; - - let descriptor = load_descriptor(Path::new(".")).unwrap(); - let source = &descriptor.piles[&pile_name]; - - let pile_desc = load_pile_descriptor(&source.path)?; - let (pile, bypass_raw_pile) = open_pile_with_work_bypass( - &source.path, - &pile_desc, - BypassLevel::CompressionBypass, - )?; - - let pbar = ProgressBar::with_draw_target(0, ProgressDrawTarget::stdout_with_hz(10)); - pbar.set_style( - ProgressStyle::default_bar().template( - "[{elapsed_precise}]/[{eta}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}", - ), - ); - pbar.set_message("pulling"); - - let remote_host_descriptor = descriptor - .remote_hosts - .get(hostname) - .ok_or_else(|| anyhow::anyhow!("No remote host by that name: {:?}.", hostname))?; - - let mut connection = Command::new("ssh") - .arg(&remote_host_descriptor.user_at_host) - .arg("--") - .arg( - &remote_host_descriptor - .path_to_datman - .as_ref() - .map(|x| x.as_str()) - .unwrap_or("datman"), - ) - .arg("_pull_responder_offerer") - .arg(remote_datman_path) - .arg(remote_pile_name) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn()?; - - let mut reader = BufReader::new(connection.stdout.take().unwrap()); - let mut writer = BufWriter::new(connection.stdin.take().unwrap()); - - pushpull::accepting_side( - &pile, - &bypass_raw_pile, - &mut reader, - &mut writer, - Box::new(pbar), - )?; - } - - DatmanCommand::Prune { pile_name } => { - let descriptor = load_descriptor(Path::new(".")).unwrap(); - let retention_policy = descriptor - .retention - .context("No retention policy set in descriptor")?; - let dest_desc = &descriptor.piles[&pile_name]; - - let pile_desc = load_pile_descriptor(&dest_desc.path)?; - - prune_with_retention_policy( - &dest_desc.path, - &pile_desc, - &RetentionPolicy::from_config(retention_policy), - true, - )?; - } - - DatmanCommand::InternalPullResponderOfferer { - datman_path, - pile_name, - } => { - let descriptor = load_descriptor(&datman_path).unwrap(); - let source = &descriptor.piles[&pile_name]; - - let pile_desc = load_pile_descriptor(&source.path)?; - let (pile, bypass_raw_pile) = open_pile_with_work_bypass( - &source.path, - &pile_desc, - BypassLevel::CompressionBypass, - )?; - - let mut stdin = BufReader::new(io_streams::StreamReader::stdin()?); - let mut stdout = BufWriter::new(io_streams::StreamWriter::stdout()?); - - pushpull::offering_side( - &pile, - &bypass_raw_pile, - &mut stdin, - &mut stdout, - Box::new(()), - )?; - - stdout.flush()?; - } - } - Ok(()) -} diff --git a/datman.old/src/commands.rs b/datman.old/src/commands.rs deleted file mode 100644 index 58d171d..0000000 --- a/datman.old/src/commands.rs +++ /dev/null @@ -1,66 +0,0 @@ -/* -This file is part of Yama. - -Yama is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Yama is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Yama. If not, see . -*/ - -use std::collections::HashMap; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -use crate::descriptor::{Descriptor, RetentionPolicyConfig, SourceDescriptor}; - -pub mod backup; -pub mod extract; -pub mod ibrowse; -pub mod ilabel; -pub mod prune; -pub mod pushpull; -pub mod report; - -pub fn init_descriptor(path: &Path) -> anyhow::Result<()> { - std::fs::create_dir_all(path)?; - std::fs::create_dir(path.join("labelling"))?; - - let mut datman_toml_file = File::create(path.join("datman.toml"))?; - - let source: HashMap = Default::default(); - /*source.insert("demo1".to_owned(), SourceDescriptor::DirectorySource { - hostname: "demohost1".to_string(), - directory: PathBuf::from("/dev/null") - }); - source.insert("demo2".to_owned(), SourceDescriptor::VirtualSource { blah: "".to_string(), label: "wat".to_string() });*/ - - let bytes = toml::to_vec(&Descriptor { - labels: vec![ - "pocket".to_owned(), - "precious".to_owned(), - "bulky".to_owned(), - ], - sources: source, - piles: Default::default(), - remote_hosts: Default::default(), - retention: Some(RetentionPolicyConfig { - daily: 14, - weekly: 12, - monthly: 24, - yearly: 9001, - }), - })?; - - datman_toml_file.write_all(&bytes)?; - - Ok(()) -} diff --git a/datman.old/src/commands/backup.rs b/datman.old/src/commands/backup.rs deleted file mode 100644 index e701445..0000000 --- a/datman.old/src/commands/backup.rs +++ /dev/null @@ -1,391 +0,0 @@ -/* -This file is part of Yama. - -Yama is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Yama is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Yama. If not, see . -*/ - -use crate::descriptor::{Descriptor, DestPileDescriptor, SourceDescriptor, VirtualSourceKind}; -use crate::get_hostname; -use crate::labelling::{ - label_node, load_labelling_rules, str_to_label, Label, LabellingRules, State, -}; -use crate::tree::{scan, FileTree, FileTree1}; -use anyhow::{anyhow, bail}; -use arc_interner::ArcIntern; -use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; -use log::{info, warn}; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::io::Write; -use std::path::Path; -use std::process::{Child, Command, Stdio}; -use std::sync::Arc; -use yama::chunking::SENSIBLE_THRESHOLD; -use yama::commands::{load_pile_descriptor, open_pile, store_tree_node}; -use yama::definitions::{ - FilesystemOwnership, FilesystemPermissions, PointerData, RecursiveChunkRef, RootTreeNode, - TreeNode, -}; -use yama::progress::ProgressTracker; - -pub const POINTER_DATETIME_FORMAT: &'static str = "%F_%T"; -pub const POINTER_FIELD_SEPARATOR: char = '+'; - -pub fn get_pointer_name_at(source_name: &str, datetime: DateTime) -> String { - format!( - "{}{}{}", - source_name, - POINTER_FIELD_SEPARATOR, - datetime.format(POINTER_DATETIME_FORMAT).to_string() - ) -} - -pub fn split_pointer_name(pointer_name: &str) -> Option<(String, DateTime)> { - let (source_name, date_time_str) = pointer_name.rsplit_once("+")?; - let date_time = NaiveDateTime::parse_from_str(date_time_str, POINTER_DATETIME_FORMAT).ok()?; - let date_time = Utc.from_utc_datetime(&date_time); - Some((source_name.to_owned(), date_time)) -} - -pub fn open_stdout_backup_process( - extra_args: &HashMap, - program_name: &str, -) -> anyhow::Result { - let mut child = Command::new(format!("datman-helper-{}-backup", program_name)) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .stdin(Stdio::piped()) - .spawn()?; - let mut child_stdin = child.stdin.as_mut().unwrap(); - serde_json::to_writer(&mut child_stdin, extra_args)?; - child_stdin.flush()?; - // close stdin! - child.stdin = None; - Ok(child) -} - -pub fn label_filter_and_convert( - tree: FileTree1<()>, - descriptor: &Descriptor, - source_name: &str, - rules: &LabellingRules, - dest: &DestPileDescriptor, -) -> anyhow::Result> { - info!("Labelling."); - let mut tree = tree.replace_meta(&None); - let labels = descriptor - .labels - .iter() - .map(|l| Label(ArcIntern::new(l.clone()))) - .collect(); - label_node("".to_owned(), None, &mut tree, &labels, rules)?; - - let included_labels: HashSet