backup: Allow multiple sources

This commit is contained in:
Alexander Weiss 2022-09-03 04:09:13 +02:00
parent e5f77ca443
commit 3c77efb181
2 changed files with 145 additions and 132 deletions

View File

@ -18,7 +18,7 @@ pub struct LocalSource {
cache: UsersCache,
}
#[derive(Parser)]
#[derive(Clone, Parser)]
pub struct LocalSourceOptions {
/// Save access time for files and directories
#[clap(long)]

View File

@ -60,7 +60,8 @@ pub(super) struct Opts {
ignore_opts: LocalSourceOptions,
/// Backup source, use - for stdin
source: String,
#[clap(value_name = "SOURCE")]
sources: Vec<String>,
}
pub(super) async fn execute(
@ -74,146 +75,158 @@ pub(super) async fn execute(
let mut be = DryRunBackend::new(be.clone(), opts.dry_run);
be.set_zstd(zstd);
let backup_stdin = opts.source == "-";
let backup_path = if backup_stdin {
PathBuf::from(&opts.stdin_filename)
} else {
PathBuf::from(&opts.source).absolutize()?.to_path_buf()
};
let backup_path_str = backup_path
.to_str()
.ok_or_else(|| anyhow!("non-unicode path {:?}", backup_path))?
.to_string();
if opts.sources.is_empty() {
v1!("no backup source given.");
return Ok(());
}
let hostname = gethostname();
let hostname = hostname
.to_str()
.ok_or_else(|| anyhow!("non-unicode hostname {:?}", hostname))?
.to_string();
let index = IndexBackend::only_full_trees(&be.clone(), progress_counter()).await?;
let parent = match (backup_stdin, opts.force, opts.parent) {
(true, _, _) | (false, true, _) => None,
(false, false, None) => SnapshotFile::latest(
&be,
|snap| snap.hostname == hostname && snap.paths.contains(&backup_path_str),
progress_counter(),
)
.await
.ok(),
(false, false, Some(parent)) => SnapshotFile::from_id(&be, &parent).await.ok(),
};
let parent_tree = match &parent {
Some(snap) => {
v1!("using parent {}", snap.id);
Some(snap.tree)
}
None => {
v1!("using no parent");
None
}
};
let delete = match (opts.delete_never, opts.delete_after) {
(true, _) => DeleteOption::Never,
(_, Some(d)) => DeleteOption::After(time + Duration::from_std(*d)?),
(false, None) => DeleteOption::NotSet,
};
let mut snap = SnapshotFile {
time,
parent: parent.map(|sn| sn.id),
hostname,
delete,
summary: Some(SnapshotSummary {
command,
..Default::default()
}),
..Default::default()
};
snap.paths.add(backup_path_str.clone());
snap.set_tags(opts.tag);
let index = IndexBackend::only_full_trees(&be, progress_counter()).await?;
let parent = Parent::new(&index, parent_tree, opts.ignore_ctime, opts.ignore_inode).await;
let snap = if backup_stdin {
v1!("starting backup from stdin...");
let mut archiver = Archiver::new(be, index, &config, parent, snap)?;
let p = progress_bytes();
archiver
.backup_reader(
std::io::stdin(),
Node::new(
backup_path_str,
NodeType::File,
Metadata::default(),
None,
None,
),
p,
)
.await?;
archiver.finalize_snapshot().await?
} else {
let src = LocalSource::new(opts.ignore_opts, backup_path)?;
let size = if get_verbosity_level() == 1 {
v1!("determining size of backup source...");
src.size()?
for source in opts.sources {
v1!("\nbacking up {source}...");
let be = be.clone();
let index = index.clone();
let backup_stdin = source == "-";
let backup_path = if backup_stdin {
PathBuf::from(&opts.stdin_filename)
} else {
0
PathBuf::from(&source).absolutize()?.to_path_buf()
};
v1!("starting backup...");
let mut archiver = Archiver::new(be, index, &config, parent, snap)?;
let p = progress_bytes();
p.set_length(size);
for item in src {
match item {
Err(e) => {
eprintln!("ignoring error {}\n", e)
}
Ok((path, node)) => {
if let Err(e) = archiver.add_entry(&path, node, p.clone()).await {
eprintln!("ignoring error {} for {:?}\n", e, path);
let backup_path_str = backup_path
.to_str()
.ok_or_else(|| anyhow!("non-unicode path {:?}", backup_path))?
.to_string();
let hostname = gethostname();
let hostname = hostname
.to_str()
.ok_or_else(|| anyhow!("non-unicode hostname {:?}", hostname))?
.to_string();
let parent = match (backup_stdin, opts.force, opts.parent.clone()) {
(true, _, _) | (false, true, _) => None,
(false, false, None) => SnapshotFile::latest(
&be,
|snap| snap.hostname == hostname && snap.paths.contains(&backup_path_str),
progress_counter(),
)
.await
.ok(),
(false, false, Some(parent)) => SnapshotFile::from_id(&be, &parent).await.ok(),
};
let parent_tree = match &parent {
Some(snap) => {
v1!("using parent {}", snap.id);
Some(snap.tree)
}
None => {
v1!("using no parent");
None
}
};
let delete = match (opts.delete_never, opts.delete_after) {
(true, _) => DeleteOption::Never,
(_, Some(d)) => DeleteOption::After(time + Duration::from_std(*d)?),
(false, None) => DeleteOption::NotSet,
};
let mut snap = SnapshotFile {
time,
parent: parent.map(|sn| sn.id),
hostname,
delete,
summary: Some(SnapshotSummary {
command: command.clone(),
..Default::default()
}),
..Default::default()
};
snap.paths.add(backup_path_str.clone());
snap.set_tags(opts.tag.clone());
let parent = Parent::new(&index, parent_tree, opts.ignore_ctime, opts.ignore_inode).await;
let snap = if backup_stdin {
v1!("starting backup from stdin...");
let mut archiver = Archiver::new(be, index, &config, parent, snap)?;
let p = progress_bytes();
archiver
.backup_reader(
std::io::stdin(),
Node::new(
backup_path_str,
NodeType::File,
Metadata::default(),
None,
None,
),
p,
)
.await?;
archiver.finalize_snapshot().await?
} else {
let src = LocalSource::new(opts.ignore_opts.clone(), backup_path)?;
let size = if get_verbosity_level() == 1 {
v1!("determining size of backup source...");
src.size()?
} else {
0
};
v1!("starting backup...");
let mut archiver = Archiver::new(be, index.clone(), &config, parent, snap)?;
let p = progress_bytes();
p.set_length(size);
for item in src {
match item {
Err(e) => {
eprintln!("ignoring error {}\n", e)
}
Ok((path, node)) => {
if let Err(e) = archiver.add_entry(&path, node, p.clone()).await {
eprintln!("ignoring error {} for {:?}\n", e, path);
}
}
}
}
}
p.finish_with_message("done");
archiver.finalize_snapshot().await?
};
p.finish_with_message("done");
archiver.finalize_snapshot().await?
};
let summary = snap.summary.unwrap();
let summary = snap.summary.unwrap();
v1!(
"Files: {} new, {} changed, {} unchanged",
summary.files_new,
summary.files_changed,
summary.files_unmodified
);
v1!(
"Dirs: {} new, {} changed, {} unchanged",
summary.dirs_new,
summary.dirs_changed,
summary.dirs_unmodified
);
v2!("Data Blobs: {} new", summary.data_blobs);
v2!("Tree Blobs: {} new", summary.tree_blobs);
v1!(
"Added to the repo: {} (raw: {})",
bytes(summary.data_added_packed),
bytes(summary.data_added)
);
v1!(
"Files: {} new, {} changed, {} unchanged",
summary.files_new,
summary.files_changed,
summary.files_unmodified
);
v1!(
"Dirs: {} new, {} changed, {} unchanged",
summary.dirs_new,
summary.dirs_changed,
summary.dirs_unmodified
);
v2!("Data Blobs: {} new", summary.data_blobs);
v2!("Tree Blobs: {} new", summary.tree_blobs);
v1!(
"Added to the repo: {} (raw: {})",
bytes(summary.data_added_packed),
bytes(summary.data_added)
);
v1!(
"processed {} files, {}",
summary.total_files_processed,
bytes(summary.total_bytes_processed)
);
v1!("snapshot {} successfully saved.", snap.id);
v1!(
"processed {} files, {}",
summary.total_files_processed,
bytes(summary.total_bytes_processed)
);
v1!("snapshot {} successfully saved.", snap.id);
v1!("backup of {source} done.");
}
Ok(())
}