use btrfs-send for data syncing, testing still in progress
authorIan Kelling <ian@iankelling.org>
Wed, 16 Nov 2016 17:39:19 +0000 (09:39 -0800)
committerIan Kelling <ian@iankelling.org>
Wed, 16 Nov 2016 17:39:19 +0000 (09:39 -0800)
btrbk-run
check-subvol-stale [new file with mode: 0644]
distro-begin
install-btrfs-sync-tools [new file with mode: 0755]
mail-setup [moved from postfix-setup with 98% similarity]
mount-latest-subvol [new file with mode: 0644]

index 4f4739d954e39d33c423ea59d06e174244893316..05589d1c8626391454b9e0cf261e4196d4e52a1e 100755 (executable)
--- a/btrbk-run
+++ b/btrbk-run
@@ -1,4 +1,4 @@
-#!/bin/bash -l
+#!/bin/bash
 
 set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
@@ -11,10 +11,15 @@ usage() {
     exit $1
 }
 
+script_dir=$(dirname $(readlink "$BASH_SOURCE"))
+
+# todo: finish figuring out fai / distro-setup
+# initial fstab / subvol setup.
+
 conf_only=false
 dry_run=false # mostly for testing
 
-temp=$(getopt -l help,long-opt hcnt "$@") || usage 1
+temp=$(getopt -l help hcnt: "$@") || usage 1
 eval set -- "$temp"
 while true; do
     case $1 in
@@ -30,9 +35,6 @@ read primary <<<"$@"
 
 ##### end command line parsing ########
 
-sed="sed -r --follow-symlinks"
-last_snaps=()
-
 target-section() {
     local root=$1
     local subvol=$2
@@ -51,24 +53,18 @@ rsync-dirs() {
     rsync $dry_run_arg -ahi --relative --delete "$path" "root@$host:/"
 }
 
-last-snap() {
-    vol=${1##*/}
-    cd /mnt/root
-    last_snap=$(
-        for f in $vol.20*; do
-            printf "%s %s\n" $(date -d $(sed -r  's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<${f#$vol.}) +%s) $f
-        done | sort -r | head -n 1 | awk '{print $2}'
-             )
-    last_snaps+=($last_snap)
-}
 
 # note q is owned by root:1000
 # note p is owned 1000:1000 and chmod 700
 mountpoints=(/q)
-if mountpoint /p; then
+if awk '{print $2}' /etc/fstab | grep -xF /p &>/dev/null; then
     mountpoints+=(/p)
 fi
 
+# if our mountpoints are from stale snapshots,
+# it doesn't make sense to do a backup.
+check-subvol-stale ${mountpoints[@]} || exit 1
+
 if [[ ! $targets ]]; then
     case $HOSTNAME in
         tp|x2)
@@ -81,24 +77,13 @@ if [[ ! $targets ]]; then
 fi
 
 
+# todo: make bash shell prompt show something when
+# a subvol on current host is not fresh.
 # umount first to ensure we don't have any errors
 # todo: do some kill fuser stuff to make umount more reliable
-# todo: setup sync systemd timer on $primary, once per hour.
+# todo: run this on a systemd timer on $primary, once per hour.
 # todo: setup lock so that if this is already running, we exit out, so
 # that manual runs don't interfere with cronjobs.
-if [[ $primary ]] && ! $dry_run; then
-    for m in ${mountpoints[@]}; do
-        # note, this won't work for /i, due to path being /mnt/iroot
-        # todo: include /i for treetowl/frodo
-        btrfs property set -ts /mnt/root$m ro true
-        ssh root@$primary bash <<EOF
-set -ex
-umount $m
-[[ -e /mnt/root$m ]] || exit 0
-btrfs sub del /mnt/root$m
-EOF
-    done
-fi
 
 for tg in ${targets[@]}; do
     cat >/etc/btrbk.conf <<'EOF'
@@ -115,7 +100,11 @@ timestamp_format long-iso
 snapshot_create onchange
 # much less snapshots because I have less space on the
 # local filesystem.
-snapshot_preserve 2h 2d
+#snapshot_preserve 2h 2d
+# for now, keeping them equal for simplicity sake
+snapshot_preserve 48h 14d 8w 24m
+snapshot_preserve_min 6h
+snapshot_dir btrbk
 
 # so, total backups = ~89
 target_preserve 48h 14d 8w 24m
@@ -125,13 +114,13 @@ target_preserve_min 6h
 # btrbk -l debug -v dryrun
 EOF
 
-    remote_target="target send-receive ssh://${tg}/mnt/root"
+    remote_target="target send-receive ssh://${tg}/mnt/root/btrbk"
 
     if [[ $tg == frodo && $HOSTNAME == treetowl ]]; then
         target-section /mnt/iroot i
     fi
-    for m in ${mountpoints[@]}; do
-        target-section /mnt/root ${m##*/}
+    for d in ${mountpoints[@]}; do
+        target-section /mnt/root ${d##*/}
     done
 done
 
@@ -146,15 +135,14 @@ else
 fi
 
 # if we have /p, rsync to targets without /p
-if mountpoint /p; then
+if mountpoint /p >/dev/null; then
     for tg in ${targets[@]}; do
         case $tg in
             tp|li|lk)
-                # todo, test this
                 for x in /p/c/machine_specific/*.hosts; do
                     if grep -qxF $tg $x; then
                         dir=${x%.hosts}
-                        rsync-dirs ${dir##*/} $dir
+                        rsync-dirs $tg $dir
                     fi
                 done
                 ;;
@@ -162,38 +150,23 @@ if mountpoint /p; then
     done
 fi
 
-first_root=$(awk '$2 == "/mnt/root" {print $1}' /etc/mtab)
-
-# make $primary have the rw snapshot
-if [[ $primary ]] && ! $dry_run; then
-    fstab=()
-    for m in ${mountpoints[@]}; do
-        last-snap $m
-        fstab+=("$first_root  $m  btrfs  noatime,subvol=$last_snap  0 0")
-    done
-
-    printf "%s\n" "${fstab[@]}" | cedit /etc/fstab
-    for d in ${mountpoints[@]}; do
-        mount $d
-        btrfs sub del /mnt/root$d
-    done
-    ssh root@primary bash -s "${mountpoints[*]}" "${last_snaps[*]}" <<'EOF'
-set -xe
-mountpoints=($1)
-last_snaps=($2)
-first_root=$(awk '$2 == "/mnt/root" {print $1}' /etc/mtab)
-for ((i=0; i < ${#mountpoints[@]}; i++)); do
-  m=${mountpoints[i]}
-  vol=${m##*/}
-  fstab+=("$first_root $m  btrfs  noatime,subvol=$vol  0 0")
-  cd /mnt/root
-  btrfs sub snapshot ${last_snaps[i]} $vol
-  mount $m
-done
+if ! $dry_run; then
+    for tg in ${targets[@]}; do
+        scp $script_dir/{mount-latest-subvol,check-subvol-stale} \
+            root@tg:/usr/local/bin
+        ssh root@$tg bash <<'EOF'
+set -e
+chmod +x /usr/local/bin/{mount-latest-subvol,check-subvol-stale}
+mount-latest-subvol
 EOF
+    done
 fi
 
 
+# todo: move variable data we don't care about backing up
+# to /nocow and symlink it.
+
+
 # background on btrbk timezones. with short/long, timestamps use local time.
 # for long, if your local time moves backwards, by moving timezones or
 # for an hour when daylight savings changes it, you will temporarily get
@@ -208,3 +181,5 @@ fi
 # However, in the short term, there will be no inconsistencies.
 # I don't see any problem with shifting when the day starts for
 # retention, so I'm using long-iso.
+
+# note to create a long-iso timestamp: date +%Y%m%dT%H%M%S%z
diff --git a/check-subvol-stale b/check-subvol-stale
new file mode 100644 (file)
index 0000000..d958228
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+[[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
+
+set -eE -o pipefail
+trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
+
+shopt -s nullglob
+
+ret=0
+for d; do
+    vol=${d##*/}
+    cd /mnt/root/btrbk
+    snaps=($vol.20*)
+    if [[ ! $snaps ]]; then
+        # no snapshots yet
+        continue
+    fi
+    # when a btrbk bugfix makes it into the distro,
+    # we might replace this with btrbk list latest /mnt/root/$vol | ...
+    # note: this is duplicated in mount-latest-subvol
+    last_snap=$(
+        for f in ${snaps[@]}; do
+            printf "%s %s\n" $(date -d $(sed -r  's/(.{4})(..)(.{5})(..)(.*)/\1-\2-\3:\4:\5/' <<<${f#$vol.}) +%s) $f
+        done | sort -r | head -n 1 | awk '{print $2}'
+             )
+    if [[ ! $last_snap ]]; then
+        echo "$d stale"
+        ret=1
+        continue
+    fi
+    stale=true
+    if btrfs sub show $d|sed '0,/^\t*Snapshot(s):/d;s/^\s*//' | \
+            grep -xF btrbk/$last_snap &>/dev/null; then
+        stale=false
+    else
+        last_uuid=$(btrfs sub show $last_snap| awk '$1 == "UUID:" {print $2}')
+        if btrfs sub show $d| grep "^\s*Parent UUID:\s*$last_uuid$" &>/dev/null; then
+            stale=false
+        fi
+    fi
+    stale_dir=/nocow/btrfs-stale
+    stale_file=$stale_dir/$vol
+    if $stale; then
+        mkdir -p $stale_dir
+        printf "%s\n" $last_snap > $stale_file
+        echo "$d stale"
+        ret=1
+        continue
+    else
+        rm -f $stale_file
+    fi
+done
+exit $ret
+
+# todo: figure out what to do when there are no
+# snapshots yet. I guess that should be
+# yes to being stale? see the implications
+# in the other script.
index eda0b4d2f3df013810c86cef0b1145f9314d4f58..5297da810986af9fe2484dbc71b409f4ff0fc6ae 100755 (executable)
@@ -68,7 +68,7 @@ fi
 for f in iank-dev htpc treetowl x2 frodo tp li lj demohost; do
     eval "$f() { [[ $HOSTNAME == $f ]]; }"
 done
-has_p() { treetowl || iank-dev || x2 || frodo || tp || demohost; }
+has_p() { treetowl || x2 || frodo || tp || demohost; }
 has_x() { ! linode; }
 linode() { lj || li; }
 has_btrfs() { ! linode; }
@@ -219,7 +219,6 @@ EOF
 isfedora && tu /etc/sysctl.conf 'kernel.sysrq = 1'
 
 
-s lnf -T /q/p /p
 # this needs to be before installing pacserve so we have gpg conf.
 conflink
 
@@ -324,7 +323,8 @@ case $(distro-name) in
                 # for a while, firefox/unstable did not have
                 # dependencies satisfied by testing packages, and i hit
                 # a conflict, it wanted a newer libfontconfig1, but
-                # emacs build-deps wanted an older one. note: They seem
+                # emacs build-deps wanted an older one. In this case,
+                # I switch to using firefox-esr. note: They seem
                 # to release a new esr version every 9 months or so.
                 pi firefox/unstable
             fi
@@ -543,35 +543,6 @@ dirs=(/mnt/{1,2,3,4,5,6,7,8,9})
 s mkdir -p "${dirs[@]}"
 s chown ian:ian  "${dirs[@]}"
 
-if [[ $HOSTNAME == treetowl ]]; then
-    # partitioned it with fai partitioner outside of fai,
-    # because it\'s worth it to have 1% space reserved for boot and
-    # swap partitions in case I ever want to boot off those drives.
-    # as root:
-    # . /a/bin/fai/fai-wrapper
-    # eval-fai-classfile /a/bin/fai/fai/config/class/51-multi-boot
-    # fai-setclass ROTATIONAL
-    # export LUKS_DIR=/q/root/luks/
-    # # because the partition nums existed already
-    # fai-setclass REPARTITION
-    # /a/bin/fai/fai/config/hooks/partition.DEFAULT
-
-    # just the first in the btrfs raid
-    dev=ata-TOSHIBA_MD04ACA500_84REK6NTFS9A-part1
-    tu /etc/fstab <<EOF
-/dev/mapper/crypt_dev_$dev /i btrfs  noatime,subvol=i  0 0
-EOF
-
-    tu /etc/crypttab <<EOF
-crypt_dev_$dev  /dev/disk/by-id/$dev  /q/root/luks/host-treetowl  discard,luks
-EOF
-
-else
-    tu /etc/fstab <<'EOF'
-/q/i  /i  none  bind  0 0
-EOF
-
-fi
 
 tu /etc/fstab <<'EOF'
 /i/w  /w  none  bind  0 0
@@ -698,8 +669,18 @@ umask 002
 EOF
 
 
-
-postfix-setup
+if isdeb; then
+    # I've had problems with postfix on debian:
+    # on stretch, a startup ordering issue caused all mail to fail.
+    # postfix changed defaults to only use ipv6 dns, causing all my mail to fail.
+    # exim4 is default on debian, so I assume it would
+    # be packaged better to avoid these types of things.
+    # I haven't gotten around to getting a non-debian exim
+    # setup.
+    mail-setup exim4
+else
+    mail-setup postfix
+fi
 
 if isubuntu; then
     # disable crash report annoying crap
diff --git a/install-btrfs-sync-tools b/install-btrfs-sync-tools
new file mode 100755 (executable)
index 0000000..da72956
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Running these files directly won't be good since we are
+# unmounting the volume they live on.
+# This never really get's run, since we normally only
+# seed these files to other hosts using btrbk-run.
+
+
+set -eE -o pipefail
+trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
+
+[[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
+
+cd $(dirname $(readlink -f "${BASH_SOURCE}"))
+
+
+echo install mount-latest-subvol check-subvol-stale /usr/local/bin
+install mount-latest-subvol check-subvol-stale /usr/local/bin
similarity index 98%
rename from postfix-setup
rename to mail-setup
index efe27bb87aaa70d5e2d492de49ecb6eb614d9cae..819aa8d0623cd48420bd7dfac345f88b046a794c 100755 (executable)
@@ -48,9 +48,9 @@ trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
 
 type=$1
 postfix() { [[ $type == postfix ]]; }
-exim() { [[ $type == exim ]]; }
+exim() { [[ $type == exim4 ]]; }
 if ! exim && ! postfix; then
-    echo "$1: error: expected exim or postfix as first arg"
+    echo "$1: error: expected exim4 or postfix as first arg"
     exit 1
 fi
 
diff --git a/mount-latest-subvol b/mount-latest-subvol
new file mode 100644 (file)
index 0000000..6a714d1
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/bash
+
+[[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
+
+errcatch() {
+    set -E; shopt -s extdebug
+    _err-trap() {
+        err=$?
+        exec >&2
+        set +x
+        echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}:in \`$BASH_COMMAND' returned $err"
+        bash-trace 2
+        echo "$0: exiting with code $err"
+        exit $err
+    }
+    trap _err-trap ERR
+    set -o pipefail
+}
+bash-trace() {
+    local -i argc_index=0 arg frame i start=${1:-1} max_indent=8 indent
+    local source
+    local extdebug=false
+    if [[ $(shopt -p extdebug) == *-s* ]]; then
+        extdebug=true
+    fi
+
+    for ((frame=0; frame < ${#FUNCNAME[@]}-1; frame++)); do
+        argc=${BASH_ARGC[frame]}
+        argc_index+=$argc
+        ((frame < start)) && continue
+        if (( ${#BASH_SOURCE[@]} > 1 )); then
+            source="${BASH_SOURCE[frame+1]}:${BASH_LINENO[frame]}:"
+        fi
+        indent=$((frame-start+1))
+        indent=$((indent < max_indent ? indent : max_indent))
+        printf "%${indent}s↳%sin \`%s" '' "$source" "${FUNCNAME[frame]}"
+        if $extdebug; then
+            for ((i=argc_index-1; i >= argc_index-argc; i--)); do
+                printf " %s" "${BASH_ARGV[i]}"
+            done
+        fi
+        echo \'
+    done
+}
+errcatch
+
+
+e() { printf "%s\n" "$*"; "$@"; }
+
+ret=0
+
+first_root_crypt=$(awk '$2 == "/" {print $1}' /etc/mtab)
+tu /etc/fstab <<EOF
+$first_root_crypt  /q  btrfs  noatime,subvol=q  0 0
+/q/a  /a  none  bind  0 0
+EOF
+case $HOSTNAME in
+    treetowl|x2|frodo)
+        tu /etc/fstab <<EOF
+$first_root_crypt  /p  btrfs  noatime,subvol=p  0 0
+EOF
+        ;;
+esac
+if [[ $HOSTNAME == treetowl ]]; then
+    # partitioned it with fai partitioner outside of fai,
+    # because it\'s worth it to have 1% space reserved for boot and
+    # swap partitions in case I ever want to boot off those drives.
+    # as root:
+    # . /a/bin/fai/fai-wrapper
+    # eval-fai-classfile /a/bin/fai/fai/config/class/51-multi-boot
+    # fai-setclass ROTATIONAL
+    # export LUKS_DIR=/q/root/luks/
+    # # because the partition nums existed already
+    # fai-setclass REPARTITION
+    # /a/bin/fai/fai/config/hooks/partition.DEFAULT
+
+    # just the first in the btrfs raid
+    dev=ata-TOSHIBA_MD04ACA500_84REK6NTFS9A-part1
+    tu /etc/fstab <<EOF
+/dev/mapper/crypt_dev_$dev /i btrfs  noatime,subvol=i  0 0
+EOF
+    tu /etc/crypttab <<EOF
+crypt_dev_$dev  /dev/disk/by-id/$dev  /q/root/luks/host-treetowl  discard,luks
+EOF
+
+else
+    tu /etc/fstab <<'EOF'
+/q/i  /i  none  bind  0 0
+EOF
+
+fi
+
+mkdir -p /q /p /i
+
+for vol in q p; do
+    d=/$vol
+    if ! awk '{print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
+        continue
+    fi
+
+
+    binds=()
+    roots=($d)
+    while true; do
+        new_roots=()
+        for r in ${roots[@]}; do
+            # /q/a  /a  none  bind  0 0
+            new_roots+=($(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+bind\s.*#\1#" /etc/fstab))
+        done
+        (( ${#new_roots} )) || break
+        binds+=(${new_roots[@]})
+        roots=( ${new_roots[@]} )
+    done
+
+    if e check-subvol-stale $d; then
+        for b in ${binds[@]}; do
+            mount $b
+        done
+        continue
+    fi
+
+    last_snap=$(</nocow/btrfs-stale/$vol)
+    if [[ ! $last_snap ]]; then
+        echo "$0: error. empty last_snap var"
+        ret=1
+        continue
+    fi
+
+    umount_ret=true
+    unmounted=()
+    for dir in $(echo $d ${binds[*]}\ |tac -s\ ); do
+        if mountpoint $dir; then
+            if e umount -R $dir; then
+                unmounted+=($dir)
+            else
+                umount_ret=false
+                echo "$0: failed to umount $dir"
+                break
+            fi
+        fi
+    done
+
+    if ! $umount_ret; then
+        for dir in ${unmounted[@]}; do
+            mount $dir
+        done
+        ret=1
+        continue
+    fi
+
+    cd /mnt/root
+    if [[ -e $vol ]]; then
+        e btrfs sub del $vol
+    fi
+    # Note, we make a few assumptions in this script, like
+    # $d was not a different subvol id than $vol, and
+    # things otherwise didn't get mounted very strangely.
+    e btrfs sub snapshot btrbk/$last_snap $vol
+    for dir in $d ${binds[@]}; do
+        e mount $dir
+    done
+done
+exit $ret