summaryrefslogtreecommitdiff
path: root/go.sh
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