Skip to content

Commit

Permalink
Add support for --max-age
Browse files Browse the repository at this point in the history
  • Loading branch information
marius committed Mar 30, 2024
1 parent 16eecca commit b3f007a
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 15 deletions.
11 changes: 9 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/target/debug/bacify",
"args": [],
"cwd": "${workspaceFolder}"
"args": [
"--max-age=700d"
],
"cwd": "${workspaceFolder}",
"env": {
"LOG_LEVEL": "debug",
"RESTIC_PASSWORD": "foo",
"RESTIC_REPOSITORY": "/home/marius/tmp/bacify-relative-repo",
}
}
]
}
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ clap = { version = "4.5.4", features = ["derive"] }
dirs = "5.0.1"
env_logger = "0.11.3"
generic-array = "1.0.0"
humantime = "2.1.0"
log = "0.4.21"
serde_json = "1.0.115"
sha2 = "0.10.8"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ but the files are actually restored without the leading path components.
> At the moment there is only support for a hard-coded, single exclude file named `$HOME/.backup_exclude`.<br>
> Bacify does ***NOT*** (yet) support the full exclude file syntax, only prefixes are compared!
### Maximum backup age

You can use `--max-age` to make bacify return an error if the backup is too old. Human readable format, e.g. `3d` or `2w`, should work fine.

## License

MIT
38 changes: 25 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use chrono::prelude::*;
use clap::Parser;
use env_logger::{Builder, Env, Target};
use log::{debug, info, warn};
use log::{debug, error, info, warn};
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::collections::HashSet;
Expand All @@ -21,16 +21,20 @@ struct BackupVerifier {
id: String,
excludes: Vec<String>,
relative_path: bool,
max_age: Option<humantime::Duration>,
}

#[derive(Parser, Debug)]
struct Args {
#[arg(short, long)]
relative_path: bool,

#[arg(short, long)]
max_age: Option<humantime::Duration>,
}

impl BackupVerifier {
fn new(relative_path: bool) -> BackupVerifier {
fn new(relative_path: bool, max_age: Option<humantime::Duration>) -> BackupVerifier {
BackupVerifier {
missing: HashSet::new(),
corrupt: HashSet::new(),
Expand All @@ -40,6 +44,7 @@ impl BackupVerifier {
id: String::new(),
excludes: Vec::new(),
relative_path,
max_age,
}
}

Expand Down Expand Up @@ -121,11 +126,6 @@ impl BackupVerifier {
}

fn main(&mut self) -> Result<(), Box<dyn Error>> {
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
let excludes_file = home_dir.join(".backup_exclude");

self.excludes = self.load_excludes(excludes_file)?;

let snapshot_info = Command::new("restic")
.args(["snapshots", "--json", "--latest", "1"])
.output()?;
Expand Down Expand Up @@ -157,6 +157,20 @@ impl BackupVerifier {
return Err(format!("Couldn't find source directory {:?}", self.source_dir).into());
}

// Check if the backup is too old
if let Some(max_age) = self.max_age {
let now = chrono::Local::now().fixed_offset();
let seconds_since_backup = (now - self.backup_time).num_seconds();
if seconds_since_backup > max_age.as_secs().try_into()? {
return Err("Backup is too old".into());
}
}

// Load excludes from ~/.backup_exclude
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
let excludes_file = home_dir.join(".backup_exclude");
self.excludes = self.load_excludes(excludes_file)?;

// Log some information about the snapshot
Command::new("restic")
.arg("stats")
Expand Down Expand Up @@ -225,8 +239,6 @@ impl BackupVerifier {
}

fn main() {
// TODO: Add support for --max-age

// Set the default logging level to info, if not set via LOG_LEVEL
Builder::from_env(Env::default().filter_or("LOG_LEVEL", "info"))
.target(Target::Stdout)
Expand All @@ -239,10 +251,10 @@ fn main() {
std::env::set_var("RESTIC_PROGRESS_FPS", "0.5");
}

let mut verifier = BackupVerifier::new(args.relative_path);
let mut verifier = BackupVerifier::new(args.relative_path, args.max_age);
match verifier.main() {
Err(e) => {
info!("Error: {}", e);
error!("Error: {}", e);
std::process::exit(1)
}
Ok(()) => info!("Verification succeeded."),
Expand All @@ -262,7 +274,7 @@ mod tests {
let mut file = File::create(&exclude_file_path)?;
file.write_all(&[0xff, 0xfe, 0xfd])?; // Invalid UTF-8 sequence

let verifier = BackupVerifier::new(true);
let verifier = BackupVerifier::new(true, None);

let result = verifier.load_excludes(exclude_file_path);
assert!(result.is_err());
Expand All @@ -276,7 +288,7 @@ mod tests {
let temp_dir = tempfile::TempDir::with_prefix("bacify-test-")?;
let exclude_file_path = temp_dir.path().join("nonexistent_file");

let verifier = BackupVerifier::new(true);
let verifier = BackupVerifier::new(true, None);

let result = verifier.load_excludes(exclude_file_path)?;
assert!(result.is_empty());
Expand Down

0 comments on commit b3f007a

Please sign in to comment.