diff options
-rw-r--r-- | btrfs-functions.sh | 137 | ||||
-rw-r--r-- | btrfs-receive-root.sh | 37 | ||||
-rw-r--r-- | btrfs-send-root.sh | 42 | ||||
-rw-r--r-- | var.sh | 75 |
4 files changed, 291 insertions, 0 deletions
diff --git a/btrfs-functions.sh b/btrfs-functions.sh new file mode 100644 index 0000000..3648c24 --- /dev/null +++ b/btrfs-functions.sh | |||
@@ -0,0 +1,137 @@ | |||
1 | push() | ||
2 | { | ||
3 | $(ARGS_NE mnt src dst_dir) | ||
4 | |||
5 | now=$(date +%F.%H%M%S) || die | ||
6 | snap_dir=$mnt/snapshot.$now | ||
7 | prev_dir=$mnt/snapshot.prev | ||
8 | |||
9 | local BTRFS_RECEIVE_DESTINATION_PATH="$dst_dir" | ||
10 | push_helper true "$snap_dir" "$prev_dir" "$src" local_btrfs_receiver | ||
11 | } | ||
12 | |||
13 | push_simple() | ||
14 | { | ||
15 | $(ARGS_NE mnt src dst_dir) | ||
16 | local BTRFS_RECEIVE_DESTINATION_PATH="$dst_dir" | ||
17 | push_helper false "$mnt" "$src" local_btrfs_receiver | ||
18 | } | ||
19 | |||
20 | sex() | ||
21 | { | ||
22 | (set -x; "$@") | ||
23 | } | ||
24 | |||
25 | local_btrfs_receiver() | ||
26 | { | ||
27 | btrfs receive "$BTRFS_RECEIVE_DESTINATION_PATH" | ||
28 | } | ||
29 | |||
30 | shellescape() | ||
31 | { | ||
32 | if [ "$BASH_VERSION" ]; then | ||
33 | printf %q "$1" | ||
34 | else | ||
35 | bash -c 'printf %q "$1"' bash "$1" | ||
36 | fi | ||
37 | } | ||
38 | |||
39 | remote_btrfs_receiver() | ||
40 | { | ||
41 | ssh "$BTRFS_RECEIVE_DESTINATION_HOST" -- "btrfs receive $(shellescape "$BTRFS_RECEIVE_DESTINATION_PATH")" | ||
42 | } | ||
43 | |||
44 | push_helper() | ||
45 | { | ||
46 | $(ARGS keep_as_prev snap_dir prev_dir src dst_pipe) | ||
47 | $(NONEMPTY keep_as_prev snap_dir src dst_pipe) | ||
48 | |||
49 | local full_dest rw_dest | ||
50 | |||
51 | btrfs subvolume snapshot -r "$src" "$snap_dir" || die | ||
52 | |||
53 | if [ "$prev_dir" -a -d "$prev_dir" ]; then | ||
54 | btrfs send -p "$prev_dir" "$snap_dir" | ||
55 | else | ||
56 | btrfs send "$snap_dir" | ||
57 | fi | "$dst_pipe" || die | ||
58 | |||
59 | if [ "$dst_pipe" = local_btrfs_receiver ]; then | ||
60 | local dst="$BTRFS_RECEIVE_DESTINATION_PATH" | ||
61 | full_dest=$dst/$(basename "$snap_dir") | ||
62 | rw_dest=$full_dest.rw | ||
63 | btrfs subvolume snapshot "$full_dest" "$rw_dest" || die | ||
64 | btrfs_replace_default_subvolume_with "$rw_dest" | ||
65 | fi | ||
66 | |||
67 | if $keep_as_prev && [ "$prev_dir" ] | ||
68 | then | ||
69 | # keep the pushed snapshot in order to reuse it on subsequent pushes. | ||
70 | with_dir "$prev_dir" btrfs subvolume delete || die | ||
71 | sex mv "$snap_dir" "$prev_dir" || die | ||
72 | else | ||
73 | btrfs subvolume delete "$snap_dir" | ||
74 | fi | ||
75 | } | ||
76 | |||
77 | btrfs_mountpoint() | ||
78 | { | ||
79 | $(ARGS_NE dir) | ||
80 | btrfs filesystem show -m "$dir" >/dev/null 2>&1 | ||
81 | } | ||
82 | |||
83 | btrfs_get_mountpoint() | ||
84 | { | ||
85 | $(ARGS_NE dir) | ||
86 | while [ "$dir" -a "$dir" != '.' ]; do | ||
87 | if btrfs_mountpoint "$dir" | ||
88 | then printf '%s\n' "$dir" | ||
89 | return | ||
90 | fi | ||
91 | dir=$(dirname "$dir") | ||
92 | done | ||
93 | false | ||
94 | } | ||
95 | |||
96 | btrfs_show_default_path() | ||
97 | { | ||
98 | $(ARGS_NE mp) | ||
99 | local path | ||
100 | mp=$(btrfs_get_mountpoint "$mp") || die # TODO: fix caller? | ||
101 | btrfs_mountpoint "$mp" || die "not a mountpoint: $mp" | ||
102 | path=$(btrfs subvolume get-default "$mp"/|sed -n -e 's/.* path //p') | ||
103 | printf '%s\n' "$mp/$path" | ||
104 | } | ||
105 | |||
106 | btrfs_replace_default_subvolume_with() | ||
107 | { | ||
108 | $(ARGS_NE new_default) | ||
109 | local old_default subvol_id | ||
110 | old_default=$(btrfs_show_default_path "$new_default") || die | ||
111 | |||
112 | [ "$new_default" = "$old_default" ] && return | ||
113 | |||
114 | subvol_id=$(btrfs_show_subvolume_id "$new_default") || die | ||
115 | btrfs subvolume set-default "$subvol_id" "$new_default" || die | ||
116 | btrfs subvolume delete "$old_default" | ||
117 | sex mv "$new_default" "$old_default" | ||
118 | } | ||
119 | |||
120 | btrfs_show_subvolume_id() | ||
121 | { | ||
122 | $(ARGS_NE path) | ||
123 | local result | ||
124 | result=$(btrfs subvolume show "$path" | sed -n -e 's/^[ \t]*Subvolume ID:[ \t]*//p') | ||
125 | if [ "$result" ] | ||
126 | then printf '%s\n' "$result" | ||
127 | else false | ||
128 | fi | ||
129 | } | ||
130 | |||
131 | with_dir() | ||
132 | { | ||
133 | $(ARGS_NE d) | ||
134 | shift | ||
135 | [ -d "$d" ] || return 0 | ||
136 | "$@" "$d" | ||
137 | } | ||
diff --git a/btrfs-receive-root.sh b/btrfs-receive-root.sh new file mode 100644 index 0000000..178fd64 --- /dev/null +++ b/btrfs-receive-root.sh | |||
@@ -0,0 +1,37 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | layer_dir=/home/d/sami | ||
4 | seed_file=debian-live-8.4.0-amd64-standard.btrfs | ||
5 | layer_file=debian-live-8.4.0-amd64-standard.layer.$$.btrfs | ||
6 | layer_size=1000 | ||
7 | mountpoint=layer_dest.$$ | ||
8 | |||
9 | disable_stdout() { exec 3>&1; exec >&2; } | ||
10 | enable_stdout() { exec >&3; } | ||
11 | |||
12 | with_stdout() { enable_stdout; "$@"; disable_stdout; } | ||
13 | |||
14 | create_layer_filesystem() | ||
15 | { | ||
16 | [ ! -e "$layer_file" ] || return | ||
17 | mkdir -p "$mountpoint" && | ||
18 | dd if=/dev/zero of="$layer_file" bs=1M count="$layer_size" && | ||
19 | mount -o subvol=/,compress "$seed_file" "$mountpoint" && | ||
20 | layer_dev=$(losetup -f --show "$layer_file") && | ||
21 | btrfs device add "$layer_dev" "$mountpoint" && | ||
22 | mount -o rw,remount "$mountpoint" | ||
23 | } | ||
24 | |||
25 | finish() | ||
26 | { | ||
27 | umount "$mountpoint" && | ||
28 | btrfstune -S1 "$layer_file" | ||
29 | } | ||
30 | |||
31 | set -ex | ||
32 | |||
33 | cd "$layer_dir" && | ||
34 | disable_stdout && | ||
35 | create_layer_filesystem && | ||
36 | with_stdout btrfs receive "$mountpoint" && | ||
37 | finish | ||
diff --git a/btrfs-send-root.sh b/btrfs-send-root.sh new file mode 100644 index 0000000..6152db8 --- /dev/null +++ b/btrfs-send-root.sh | |||
@@ -0,0 +1,42 @@ | |||
1 | #!/bin/sh | ||
2 | . ./var.sh | ||
3 | . ./btrfs-functions.sh | ||
4 | |||
5 | rootfs_uuid () | ||
6 | { | ||
7 | btrfs filesystem show / | sed -ne 's/.*uuid: //p' | ||
8 | } | ||
9 | |||
10 | remote_btrfs_receiver() | ||
11 | { | ||
12 | # ssh "$BTRFS_RECEIVE_DESTINATION_HOST" -- "sudo btrfs receive $(shellescape "$BTRFS_RECEIVE_DESTINATION_PATH")" | ||
13 | ssh "$BTRFS_RECEIVE_DESTINATION_HOST" -- "sudo sh sami/btrfs-receive-root.sh" | ||
14 | } | ||
15 | |||
16 | dummy_receiver() | ||
17 | { | ||
18 | true | ||
19 | } | ||
20 | |||
21 | push_remote() | ||
22 | { | ||
23 | $(ARGS_NE mnt src ssh_dst) | ||
24 | |||
25 | now=$(date +%F.%H%M%S) || die | ||
26 | snap_dir=$mnt/snapshot.$now | ||
27 | prev_dir=$mnt/SEED | ||
28 | |||
29 | case "$ssh_dst" in | ||
30 | *:*) ;; | ||
31 | *) return 1;; | ||
32 | esac | ||
33 | local BTRFS_RECEIVE_DESTINATION_PATH="${ssh_dst#*:}" | ||
34 | local BTRFS_RECEIVE_DESTINATION_HOST="${ssh_dst%%:*}" | ||
35 | push_helper false "$snap_dir" "$prev_dir" "$src" remote_btrfs_receiver | ||
36 | } | ||
37 | |||
38 | ssh_dst=d@fifty.local:sami/test_dest | ||
39 | |||
40 | mkdir -p /mnt/rootfs || die | ||
41 | mountpoint -q /mnt/rootfs || mount -o subvol=/ UUID=$(rootfs_uuid) /mnt/rootfs || die | ||
42 | push_remote /mnt/rootfs / "$ssh_dst" | ||
@@ -0,0 +1,75 @@ | |||
1 | die() | ||
2 | { | ||
3 | if [ "$*" ]; then | ||
4 | printf 'Error: %s\n' "$*" >&2 | ||
5 | else | ||
6 | echo 'Error: fatal error' >&2 | ||
7 | fi | ||
8 | exit 1 | ||
9 | } | ||
10 | |||
11 | nosex() | ||
12 | { | ||
13 | case $- in | ||
14 | *x*) set +x; "$@"; set -x;; | ||
15 | *) "$@";; | ||
16 | esac | ||
17 | } | ||
18 | |||
19 | _nonempty() | ||
20 | { | ||
21 | printf '[ "${%s}" ] || die \"mandatory parameter is empty: %s\";\n' "$1" "$1" | ||
22 | } | ||
23 | |||
24 | _mandatory() | ||
25 | { | ||
26 | printf '[ $# -ge %d ] || die \"mandatory parameter is missing: %s\";\n' "$2" "$1" | ||
27 | } | ||
28 | |||
29 | _assign() | ||
30 | { | ||
31 | printf 'local %s="${%d}";\n' "$1" "$2" | ||
32 | } | ||
33 | |||
34 | _args() | ||
35 | { | ||
36 | local v i=1 check="$1" assign="$2" | ||
37 | shift | ||
38 | shift | ||
39 | for v; do | ||
40 | $assign "$v" "$i" | ||
41 | $check "$v" "$i" | ||
42 | i=$((i+1)) | ||
43 | done | ||
44 | } | ||
45 | |||
46 | _ARGS() | ||
47 | { | ||
48 | echo eval "$(_args _mandatory _assign "$@")" | ||
49 | } | ||
50 | |||
51 | _ARGS_NONEMPTY() | ||
52 | { | ||
53 | echo eval "$(_args _nonempty _assign "$@")" | ||
54 | } | ||
55 | |||
56 | _ARGS_OPTIONAL() | ||
57 | { | ||
58 | echo eval "$(_args : _assign "$@")" | ||
59 | } | ||
60 | |||
61 | _NONEMPTY() | ||
62 | { | ||
63 | echo eval "$(_args _nonempty : "$@")" | ||
64 | } | ||
65 | |||
66 | ARGS() { nosex _ARGS "$@"; } | ||
67 | ARGS_NONEMPTY() { nosex _ARGS_NONEMPTY "$@"; } | ||
68 | ARGS_OPTIONAL() { nosex _ARGS_OPTIONAL "$@"; } | ||
69 | NONEMPTY() { nosex _NONEMPTY "$@"; } | ||
70 | |||
71 | ARGS_NE() { ARGS_NONEMPTY "$@"; } | ||
72 | |||
73 | if [ "${0#-}" = bash ]; then | ||
74 | export -f die _nonempty _mandatory _args ARGS ARGS_NONEMPTY ARGS_OPTIONAL | ||
75 | fi | ||