summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--btrfs-functions.sh137
-rw-r--r--btrfs-receive-root.sh37
-rw-r--r--btrfs-send-root.sh42
-rw-r--r--var.sh75
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 @@
1push()
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
13push_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
20sex()
21{
22 (set -x; "$@")
23}
24
25local_btrfs_receiver()
26{
27 btrfs receive "$BTRFS_RECEIVE_DESTINATION_PATH"
28}
29
30shellescape()
31{
32 if [ "$BASH_VERSION" ]; then
33 printf %q "$1"
34 else
35 bash -c 'printf %q "$1"' bash "$1"
36 fi
37}
38
39remote_btrfs_receiver()
40{
41 ssh "$BTRFS_RECEIVE_DESTINATION_HOST" -- "btrfs receive $(shellescape "$BTRFS_RECEIVE_DESTINATION_PATH")"
42}
43
44push_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
77btrfs_mountpoint()
78{
79 $(ARGS_NE dir)
80 btrfs filesystem show -m "$dir" >/dev/null 2>&1
81}
82
83btrfs_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
96btrfs_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
106btrfs_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
120btrfs_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
131with_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
3layer_dir=/home/d/sami
4seed_file=debian-live-8.4.0-amd64-standard.btrfs
5layer_file=debian-live-8.4.0-amd64-standard.layer.$$.btrfs
6layer_size=1000
7mountpoint=layer_dest.$$
8
9disable_stdout() { exec 3>&1; exec >&2; }
10enable_stdout() { exec >&3; }
11
12with_stdout() { enable_stdout; "$@"; disable_stdout; }
13
14create_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
25finish()
26{
27 umount "$mountpoint" &&
28 btrfstune -S1 "$layer_file"
29}
30
31set -ex
32
33cd "$layer_dir" &&
34disable_stdout &&
35create_layer_filesystem &&
36with_stdout btrfs receive "$mountpoint" &&
37finish
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
5rootfs_uuid ()
6{
7 btrfs filesystem show / | sed -ne 's/.*uuid: //p'
8}
9
10remote_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
16dummy_receiver()
17{
18 true
19}
20
21push_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
38ssh_dst=d@fifty.local:sami/test_dest
39
40mkdir -p /mnt/rootfs || die
41mountpoint -q /mnt/rootfs || mount -o subvol=/ UUID=$(rootfs_uuid) /mnt/rootfs || die
42push_remote /mnt/rootfs / "$ssh_dst"
diff --git a/var.sh b/var.sh
new file mode 100644
index 0000000..d0c7df5
--- /dev/null
+++ b/var.sh
@@ -0,0 +1,75 @@
1die()
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
11nosex()
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
66ARGS() { nosex _ARGS "$@"; }
67ARGS_NONEMPTY() { nosex _ARGS_NONEMPTY "$@"; }
68ARGS_OPTIONAL() { nosex _ARGS_OPTIONAL "$@"; }
69NONEMPTY() { nosex _NONEMPTY "$@"; }
70
71ARGS_NE() { ARGS_NONEMPTY "$@"; }
72
73if [ "${0#-}" = bash ]; then
74 export -f die _nonempty _mandatory _args ARGS ARGS_NONEMPTY ARGS_OPTIONAL
75fi