1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
#![feature(try_blocks)]
use std::path::PathBuf;
use clap::Parser;
use eyre::{Context, Result};
mod cfg;
mod zfs;
#[derive(Debug, Parser)]
struct Args {
/// Path to the configuration file
#[clap(long, env = "ZSNAP_CONFIG")]
config: Option<PathBuf>,
#[clap(subcommand)]
command: Subcommand,
}
#[derive(Debug, Parser)]
enum Subcommand {
Run(RunArgs),
Snapshot(SnapshotArgs),
Prune(PruneArgs),
}
/// Run the zsnap daemon.
///
/// This command will periodically create and prune snapshots according to the configuration file.
#[derive(Debug, Parser)]
struct RunArgs {}
/// Create a snapshot.
#[derive(Debug, Parser)]
struct SnapshotArgs {
/// the tag to snapshot with
#[clap(long)]
tag: String,
/// whether to snapshot recursively
#[clap(long)]
recursive: bool,
/// the datasets to snapshot
dataset: Vec<String>,
}
/// Prune snapshots.
#[derive(Debug, Parser)]
struct PruneArgs {
/// the tag to prune
#[clap(long)]
tag: String,
/// the number of snapshots to keep
#[clap(long)]
keep: usize,
/// whether to prune recursively
#[clap(long)]
recursive: bool,
/// the datasets to prune
dataset: Vec<String>,
}
fn main() -> Result<()> {
color_eyre::install()?;
tracing_subscriber::fmt::init();
let args = Args::parse();
let config = match args.config {
Some(config_path) => cfg::read(&config_path).context("reading config")?,
None => Default::default(),
};
match args.command {
Subcommand::Run(_) => cmd_run(config),
Subcommand::Snapshot(args) => cmd_snapshot(args),
Subcommand::Prune(args) => cmd_prune(args),
}
}
fn cmd_run(config: cfg::Config) -> Result<()> {
for schedule in config.schedules {
let datasets = config.datasets.clone();
std::thread::spawn(move || snapshot_mainloop(schedule, datasets));
}
loop {
std::thread::park();
}
}
fn cmd_snapshot(args: SnapshotArgs) -> Result<()> {
for dataset in args.dataset {
zfs::snapshot_create(&args.tag, &dataset, args.recursive)?;
}
Ok(())
}
fn cmd_prune(args: PruneArgs) -> Result<()> {
for dataset in args.dataset {
zfs::snapshot_prune(&args.tag, &dataset, args.recursive, args.keep)?;
}
Ok(())
}
fn snapshot_mainloop(schedule: cfg::Schedule, datasets: Vec<cfg::Dataset>) {
for next_trigger in schedule.cron.upcoming(chrono::Utc) {
let now = chrono::Utc::now().timestamp();
let next = next_trigger.timestamp();
let sleep = next.saturating_sub(now) as u64;
tracing::debug!(
tag = schedule.tag,
"sleeping for {} seconds until next trigger",
sleep
);
std::thread::sleep(std::time::Duration::from_secs(sleep));
for dataset in datasets.iter().filter(|d| d.has_tag(&schedule.tag)) {
if let Err(err) = zfs::snapshot_create(&schedule.tag, &dataset.name, dataset.recursive)
{
tracing::error!(
tag = schedule.tag,
dataset = dataset.name,
"failed to create snapshot: {:#}",
err
);
}
if let Err(err) = zfs::snapshot_prune(
&schedule.tag,
&dataset.name,
dataset.recursive,
schedule.keep,
) {
tracing::error!(
tag = schedule.tag,
dataset = dataset.name,
"failed to prune snapshots: {:#}",
err
);
}
}
}
}
|