To get absolutely consistent filesystem backups, it is necessary to snapshot them and do an backup of this snapshot. Especially if you backup database files in use, you get consistent data if all your datafiles reside on the same volume. This config does it for me:
default.conf:
client: <you local hostname> tree: /backup/mnt/ /boot pre-client: source /usr/local/lib/snap_functions.sh && mount_snap /boot /backup/mnt post-client: source /usr/local/lib/snap_functions.sh && umount_snap /backup/mnt
/usr/local/lib/snap_functions.sh:
#!/bin/bash
VGBASE="/dev/_your_volgroup_dir_"
VGNAME="${VGBASE#/dev/}"
VGRETRYC=5 # anzahl versuche + 2 (5 -> 7 versuche)
KEYFILE=_your_pgp_keyfile_for_crypto_loop_
LOOPDEV=/dev/loop7
SNAPFLAGF=".snapped"
cd /tmp || { echo "ERROR: can not set workdir to /tmp"; exit 1; }
test -d "$VGBASE" || { echo "ERROR: Volgroup dir not found: $VGBASE"; exit 1; }
set +o posix
mount_snap () {
local mountp
local snapmnt
local lvname
local mntinf
local fstype
local i
local sleepint
local snapname
local mntopt
local mntdev
declare -i i
declare -i sleepint
mountp="$1"
snapmnt="$2"
read -a mntinf < <(mount | grep " on $mountp ")
lvname=${mntinf[0]##*/}
test -z "$lvname" && { echo "ERROR: not mounted: $mountp"; return 1; }
if [[ "$lvname" != $VGNAME-* ]]; then
mount_bind "$mountp" "$snapmnt"
return
fi
lvname=${lvname#$VGNAME-}
test -d "$snapmnt" || return 1
test -d "$mountp" || return 1
test -e "$VGBASE/$lvname" || return 1
date --rfc-3339=ns > "$mountp/$SNAPFLAGF"
sync
i=0
snapname="${lvname}_$i.backup"
while [ -e "$VGBASE/$snapname" ]; do
test $((i++)) -ge 10 && break
snapname="${lvname}_$i.backup"
done
i=$VGRETRYC
sleepint=0
# SuSE has shiti race conditionbug in lvcreate snapshot: https://bugzilla.novell.com/show_bug.cgi?id=178321
while ! lvcreate -A n -s -n "$snapname" -L 1G "$VGBASE/$lvname" 2>/dev/null; do
sleep $((sleepint=sleepint+1))
while ! lvremove -A n -f "$VGBASE/$snapname" 2>/dev/null; do
sleep $((sleepint=sleepint+1))
let i--
done
let i-- || break
done
test $i -lt 0 && return 1
fstype=${mntinf[4]}
case $fstype in
reiserfs) mntopt="nolog," ;;
ext3) mntopt="" ;;
esac
if [[ ${mntinf[5]} == *encryption=AES* ]]; then
losetup $LOOPDEV 2>/dev/null && { echo "ERROR: loopdev $LOOPDEV already in use"; return 1; }
gpg -o - $KEYFILE | losetup -e AES -p 0 $LOOPDEV $VGBASE/$snapname || return 1
mountdev=$LOOPDEV
else
mountdev="$VGBASE/$snapname"
fi
mount -t $fstype -o ${mntopt}ro,acl,user_xattr $mountdev $snapmnt
}
umount_snap () {
local snapmnt
local mntinf
local mntdev
local lvname
local tmp
snapmnt="$1"
read -a mntinf < <(mount | grep " on $snapmnt ")
mntdev=${mntinf[0]}
case $mntdev in
$LOOPDEV)
read -a tmp < <(losetup $LOOPDEV)
mntdev=${tmp[2]//[()]/}
umount $snapmnt || return 1
losetup -d $LOOPDEV || return 1
remove_snap $mntdev
;;
*/$VGNAME-*)
umount $snapmnt || return 1
remove_snap $mntdev
;;
*) umount $snapmnt
;;
esac
}
remove_snap () {
local mntdev
local lvname
local i
local sleepint
declare -i i
declare -i sleepint
mntdev="$1"
lvname=${mntdev##*/}
lvname=${lvname#$VGNAME-}
if [[ "$lvname" != *_[0-9].backup ]]; then
echo "ERROR: $mntdev is not backup LV"
return 1
fi
i=$VGRETRYC
sleepint=0
while ! lvremove -A n -f "$VGBASE/$lvname" 2>/dev/null; do
sleep $((sleepint=sleepint+1))
let i-- || break
done
test $i -lt 0 && return 1
return 0
}
mount_bind () {
local mountp
local snapmnt
mountp="$1"
snapmnt="$2"
test -d "$snapmnt" || return 1
test -d "$mountp" || return 1
date --rfc-3339=ns > "$mountp/$SNAPFLAGF"
mount --bind -o ro "$mountp" "$snapmnt"
}Optionally:
There is an bug in dirvish 1.2.1 if you use an alias on the tree: config line, resulting in cut of the last char of the src path. You can add an trailing slash or undo an "SpacesInSource fix" like this:
--- /tmp/dirvish.old 2006-10-30 21:31:42.000000000 +0100
+++ /tmp/dirvish 2006-11-02 11:19:43.000000000 +0100
@@ -419,7 +419,7 @@
#+SIS: KHL 2005-02-18 SpacesInSource fix
#-SIS: ($srctree, $aliastree) = split(/\s+/, $$Options{tree})
-($srctree, $aliastree) = split(/[^\\]\s+/, $$Options{tree})
+($srctree, $aliastree) = split(/\s+/, $$Options{tree})
or seppuku 228, "ERROR: no source tree defined";
$srctree =~ s(\\ )( )g; #+SIS
$srctree =~ s(/+$)();Best Regards, Roland <roland.friedwagner@wu-wien.ac.at>
(with a little bit of spelling help by Keith)
Keith Lofstrom adds:
Good catch! However, this breaks the intent of the SpacesInSource fix, which is intended to preserve names with spaces in them, as is common on non-unix machines (and allowed on Unix machines, though discouraged!). See the SpacesInSource page for a better solution to this problem.
Updated snap_functions.sh library to run on ubuntu:
#!/bin/bash
VGBASE="/dev/vgsys"
VGNAME="${VGBASE#/dev/}"
VGRETRYC=1 # anzahl versuche + 2 (5 -> 7 versuche)
KEYFILE=/opt/rfried/keys/aes_keys/user_crypt_fs_keyfile_v1.gpg
LOOPDEV=/dev/loop7
SNAPFLAGF=".snapped"
cd /tmp || { echo "ERROR: can not set workdir to /tmp"; exit 1; }
test -d "$VGBASE" || { echo "ERROR: Volgroup dir not found: $VGBASE"; exit 1; }
set +o posix
mount_snap () {
local mountp
local snapmnt
local lvname
local mntinf
local fstype
local snapname
local mntopt
local mntdev
local is_crypt=""
mountp="$1"
snapmnt="$2"
#while read a b c; do echo "$a - $b - $c"; test $b = /crypt && break; done < <(cat /proc/mounts | grep /tmp)
#mountp=$(mount | grep "$(echo /dev/vg_sys | rev | cut -f 1 -d / | rev)-$lvname" | cut -f 3 -d ' ')
#mountp=$(mount | grep "${VGBASE##*/}-$lvname" | cut -f 3 -d ' ')
#test -n "$mountp"
#fstype=$(mount | grep "${VGBASE##*/}-$lvname" | sed -r -e "s/.* type ([[:alnum:]]+) \(.*/\1/")
#test -n "$fstype"
read -a mntinf < <(mount | grep " on $mountp ")
lvname=${mntinf[0]##*/}
test -z "$lvname" && { echo "ERROR: not mounted: $mountp"; return 1; }
if [[ ${mntinf[0]} == /dev/mapper/* ]] && cryptsetup isLuks $VGBASE/$lvname 2>/dev/null; then
is_crypt=y
elif [[ "$lvname" != $VGNAME-* ]]; then
mount_bind "$mountp" "$snapmnt"
return
fi
lvname=${lvname#$VGNAME-}
test -d "$snapmnt" || return 1
test -d "$mountp" || return 1
test -e "$VGBASE/$lvname" || return 1
{ date --rfc-3339=ns > "$mountp/$SNAPFLAGF"; } 2>/dev/null
sync
i=0
snapname="${lvname}_$i.backup"
while [ -e "$VGBASE/$snapname" ]; do
test $((i++)) -ge 10 && break
snapname="${lvname}_$i.backup"
done
sleep 1; sync; sleep 1
lvcreate -A n -s -n "$snapname" -L 1G "$VGBASE/$lvname"
sleep 1; sync; sleep 1
fstype=${mntinf[4]}
case $fstype in
reiserfs) mntopt="nolog," ;;
ext3) mntopt="" ;;
esac
if [[ ${mntinf[5]} == *encryption=AES* ]]; then
losetup $LOOPDEV 2>/dev/null && { echo "ERROR: loopdev $LOOPDEV already in use"; return 1; }
gpg -o - $KEYFILE | losetup -e AES -p 0 $LOOPDEV $VGBASE/$snapname || return 1
mountdev=$LOOPDEV
elif [ $is_crypt ]; then
cryptsetup luksOpen $VGBASE/$snapname $snapname
mountdev=/dev/mapper/$snapname
else
mountdev="$VGBASE/$snapname"
fi
mount -t $fstype -o ${mntopt}ro,acl,user_xattr $mountdev $snapmnt
}
umount_snap () {
local snapmnt
local mntinf
local mntdev
local lvname
local tmp
snapmnt="${1%%+(/)}"
read -a mntinf < <(mount | grep " on $snapmnt ")
mntdev=${mntinf[0]}
lvname=${mntdev##*/}
case $mntdev in
$LOOPDEV)
read -a tmp < <(losetup $LOOPDEV)
mntdev=${tmp[2]//[()]/}
umount $snapmnt || return 1
losetup -d $LOOPDEV || return 1
remove_snap $mntdev
;;
*/$VGNAME-*)
umount $snapmnt || return 1
remove_snap $mntdev
;;
/dev/mapper/*)
if cryptsetup isLuks $VGBASE/$lvname 2>/dev/null; then
umount $snapmnt || return 1
cryptsetup luksClose $lvname || return 1
remove_snap $VGBASE/$lvname
fi
;;
*) umount $snapmnt
;;
esac
}
remove_snap () {
local mntdev
local lvname
mntdev="$1"
lvname=${mntdev##*/}
lvname=${lvname#$VGNAME-}
if [[ "$lvname" != *_[0-9].backup ]]; then
echo "ERROR: $mntdev is not backup LV"
return 1
fi
sleep 1; sync; sleep 1
lvremove -A n -f "$VGBASE/$lvname"
sleep 1; sync; sleep 1
}
mount_bind () {
local mountp
local snapmnt
mountp="$1"
snapmnt="$2"
test -d "$snapmnt" || return 1
test -d "$mountp" || return 1
{ date --rfc-3339=ns > "$mountp/$SNAPFLAGF"; } 2>/dev/null
mount --bind -o ro "$mountp" "$snapmnt"
}
aclattr_bak () {
local src="$1"
local dst="$2"
if command -v getfacl >/dev/null && command -v getfattr >/dev/null; then
( cd "$src"
getfacl --skip-base -R -P . | gzip > "$dst/../getfacl-R.gz"
getfattr -d -R -P . | gzip > "$dst/../getfattr-dR.gz"
)
else
echo "WARNING: getfacl or getfattr not found" >&2
fi
}Example dirvish.conf:
client: titan tree: /backup/mnt/ /opt pre-client: ; source /opt/rfried/lib/snap_functions.sh && mount_snap /opt /backup/mnt post-client: ; source /opt/rfried/lib/snap_functions.sh; aclattr_bak $DIRVISH_SRC $DIRVISH_DEST; umount_snap /backup/mnt rsync-option: # --acls
