blob: 23d06d01dcac53dbee42d22b6b89d434bb19544d (
plain)
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
|
#!/bin/bash
MAX_AGE_SECONDS=60
read_config()
{
eval config="($(jq -r '. | to_entries | .[] | "[\"" + .key + "\"]=" + (.value | @sh)'))"
}
check_dependencies()
{
for c in flock jq btrfs pv realpath stat date mv ln rm
do
command -v $c >/dev/null
done
}
check_user_is_root()
{
[ "$UID" = 0 ]
}
is_subvolume()
{
btrfs subvolume show -- "$1" >/dev/null 2>&1
}
get_age()
{
now=$(date +%s)
then=$(stat -L -c %Y "$1")
echo $((now - then))
}
remove_old_link()
{
if [ -L "$1" ] && [ "$(get_age "$1")" -gt "$2" ]
then
rm -- "$1"
fi
}
set -e
check_dependencies
check_user_is_root
if [ $# = 1 ]
then
config_file=$1
else
echo "Usage: $0 <config.json>" >&2
exit 1
fi
[ -r "$config_file" ]
exec 3<>"$config_file"
flock -n 3
declare -A config
read_config <&3
src=${config[source]}
dst=${config[destination]}
remote_head=${config[head]}
is_subvolume "$src"
local_head_link=${src%/}/.snapshot~HEAD
remove_old_link "$local_head_link" "$MAX_AGE_SECONDS"
if [ -e "$local_head_link" ]
then
local_head=$(realpath -e "$local_head_link")
else
new_snapshot=${src%/}/.snapshot~$(date -Ins)
btrfs subvolume snapshot -r -- "$src" "$new_snapshot"
# We don't need to create this link at all.
ln -rs -T "$new_snapshot" "$local_head_link"
local_head=$new_snapshot
fi
is_subvolume "$local_head"
if [ "${local_head##*/}" = "${remote_head##*/}" ]
then
echo "Up-to-date." >&2
exit
fi
btrfs send ${remote_head:+ -p "$remote_head"} -- "$local_head" | pv | btrfs receive -- "$dst"
jq --arg h "$local_head" '. | .head=$h' <"$config_file" >."$config_file"~tmp
mv -T -- ."$config_file"~tmp "$config_file"
### OK, basic idea:
#
## the system has a list of source subvolumes
## each source subvolume has a snapshot period
## each source subvolume has a list of subscribers
## each source subvolume has an ordered list of its snapshots
## each subscriber has a last-received snapshot (if any)
# when a subvolume's last snapshot is older than the configured period, a new snapshot is created
# when a subvolume has subscribers whose last snapshot is out-of-date, a snapshot is pushed (using the common ancestor)
# the subscriber's last snapshot is then updated
# if there are no subscriber's holding open a snapshot which is not the latest snapshot, then it is deleted
|