vmtools/vmbackup

357 lines
7.4 KiB
Bash
Executable file

#!/bin/sh
# vim: tabstop=4 shiftwidth=4 expandtab
#
# This script performs backups of VMs filesystems. It achieves this by
# ssh into the VM and using dump with FSS to get a consistent point
# in time snapshot.
#
# Locate libraries
script_dir="$(dirname "$(readlink -f "$0")")"
lib_dir="$script_dir/lib"
# Include libraries
. $lib_dir/common.sh
# Exit if not running under root
if [ $(id -u) -ne 0 ]; then
log crit "This script requires root permissions."
exit 1
fi
# Read configuration
. $lib_dir/vmtoolsrc
if [ -r $HOME/.vmtoolsrc ]; then
. $HOME/.vmtoolsrc
log info "Loaded local configuration from $HOME/.vmtoolsrc"
else
log warning "Local configuration not found, using defaults"
fi
# Default options
gptlabel_opt=""
backupset_opt=""
targetdir_opt=""
# Globals
GLOBAL_FILE_LABEL_PREVIOUS=/var/tmp/label.prev
configure() {
log info "Configuration started"
if [ ! -w $HOME/.vmtoolsrc ]; then
log notice "Create new local configuration from defaults"
cp $lib_dir/vmtoolsrc $HOME/.vmtoolsrc
fi
vi $HOME/.vmtoolsrc
log info "Configuration finished"
}
#
# Properly handle Ctrl+C
#
trap exit_handler 2
#
# Exit handler
#
exit_handler() {
log warning "Ctrl+C received, shutting down ..."
sync
umount_target
release_lock
exit 2
}
#
# Mount the target filesystem
#
mount_target() {
local gptlabel=$1
local targetdir=$2
mount -o log NAME=$gptlabel $targetdir
if [ $? -gt 0 ]; then
log alert "Mount of target filesystem failed."
release_lock
exit 1
fi
}
#
# Check if label (UUID) and backup set are present
#
check_labels() {
local targetdir=$1
if [ ! -f $targetdir/.label ]
then
log warning "Missing target label"
return 1
fi
if [ ! -f $targetdir/.backupset ]
then
log warning "Missing target backup set"
return 2
fi
echo `cat $targetdir/.backupset`
}
#
# Label Target
#
label_target() {
local targetdir=$1
local backupset=$2
echo $backupset > $targetdir/.backupset
local uuid=`uuidgen`
echo $uuid > $targetdir/.label
log info "Labeled target with ID $uuid and assignet to backup set $backupset"
}
#
# Detect Target Media Change
#
detect_media_change() {
local targetdir=$1
local label_current=`cat $targetdir/.label`
local label_previous=`cat $GLOBAL_FILE_LABEL_PREVIOUS 2>/dev/null`
if [ -z $label_previous ];then
log warning "No cached target ID, assuming target has changed"
echo $label_current > $GLOBAL_FILE_LABEL_PREVIOUS
return 1
fi
# If label not equal - new drive or drive swapped
if [ $label_current != $label_previous ]; then
log notice "Target changed from $label_previous to $label_current"
echo $label_current > $GLOBAL_FILE_LABEL_PREVIOUS
return 1
else
log notice "Reusing target $label_current"
return 0
fi
}
#
# Clean Target
#
clean_target() {
local targetdir=$1
log notice "Cleaning target ..."
rm -f $targetdir/*.dump
}
#
# Get next dump sequence for given dumpbase
#
get_next_sequence()
{
local dumpbase=$1
local level_last=-1
local level=-1
for dump_file in `ls $dumpbase.*.dump 2>/dev/null`
do
level=`echo $dump_file |cut -d "." -f 2`
if [ $level -gt $level_last ]
then
level_last=$level
fi
done
echo `expr $level_last + 1`
}
#
# Unmount the target filesystem
#
umount_target()
{
local targetdir=$1
umount $targetdir
}
dump_filesystems() {
local fqhostname=$1
local targetdir=$2
local backupset=$3
local hostname=`echo $fqhostname|cut -d. -f1`
for filesystem in $(ssh user@$fqhostname "awk '\$5 == "$backupset" { print \$2 }' /etc/fstab"); do
local name=$(basename $filesystem)
if [ $name = "/" ]; then
name="root"
fi
local dumpbase="${targetdir}/${hostname}_${name}"
local next_seq=$(get_next_sequence $dumpbase)
local dumpname=${dumpbase}.${next_seq}.dump
local dump_level='i'
if [ $next_seq = '0' ]; then
dump_level='0'
fi
log notice "Dump of filesystem $filesystem to $dumpname started."
ssh user@$fqhostname "doas /sbin/dump -X -h 0 -b 64 -${dump_level}auf - $filesystem" | dd bs=4m oflag=creat,direct of=$dumpname msgfmt=quiet
if [ $? -gt 0 ]; then
log alert "Dump of filesystem $filesystem to $dumpname failed."
release_lock
exit 2
fi
log notice "Dump of filesystem $filesystem to $dumpname completed."
done
}
#
# Print usage
#
usage() {
echo "Usage: $0 command [options] <hostname>"
echo
echo "Commands:"
echo " auto Backup given host and automatically choose settings"
echo " manual Backup given host with manual settings"
echo " label Label the backup media"
echo " configure Configure the backup settings"
echo
echo "Options:"
echo " -l <gptlabel> Mount filesytem with GPT label and use as backup target"
echo " -s <backupset> Specify the number of backup set"
echo " -t <targetdir> Specify the target dir for the dump"
echo
exit 1
}
#
# CLI
#
command=$1
# No command provided
if [ -z "$command" ]; then
echo "No command provided"
usage
fi
shift
# Invalid command provided
case "$command" in
auto|manual|label|configure)
;;
*)
echo "Invalid command: $command"
usage
;;
esac
# Process options
while getopts "l:s:t:" option; do
case $option in
l)
gptlabel_opt=$OPTARG
;;
s)
backupset_opt=$OPTARG
;;
t)
targetdir_opt=$OPTARG
;;
\?)
usage
;;
esac
done
shift $((OPTIND - 1))
hostname_arg=$1
# Process commands
case "$command" in
label)
if [ -z "$backupset_opt" ]; then
echo "backupset option is required for 'label' command."
usage
fi
if [ -z "$gptlabel_opt" ]; then
echo "gptlabel option is required for 'label' command"
usage
fi
aquire_lock
mnt=`mktemp -d`
mount_target $gptlabel_opt $mnt
if [ $? -gt 0 ]; then
log alert "Target media not present, exiting."
release_lock
exit 1
fi
label_target $mnt $backupset_opt
umount_target $mnt
release_lock
;;
auto)
if [ -z "$hostname_arg" ]; then
echo "Hostname argument is required for 'auto' command"
usage
fi
if [ -z "$gptlabel_opt" ]; then
echo "gptlabel option is required for 'auto' command"
usage
fi
aquire_lock
log info "Backup of host $hostname_arg started."
mnt=`mktemp -d`
mount_target $gptlabel_opt $mnt
backupset=$(check_labels $mnt)
if [ $? -gt 0 ]; then
log alert "Target media not usable, exiting."
umount_target $mnt
release_lock
exit 1
fi
log notice "Backupset selected: $backupset"
detect_media_change $mnt
media_changed=$?
if [ $media_changed -eq 1 ]; then
clean_target
fi
dump_filesystems $hostname_arg $mnt $backupset
umount_target $mnt
rmdir $mnt
release_lock
log info "Backup of host $argument finished."
;;
manual)
if [ -z "$hostname_arg" ]; then
echo "Hostname argument is required for 'manual' command"
usage
fi
if [ -z "$backupset_opt" ]; then
echo "backupset option is required for 'manual' command"
usage
fi
if [ -z "$targetdir_opt" ]; then
echo "targetdir option is required for 'manual' command"
usage
fi
aquire_lock
log info "Backup of host $hostname_arg started."
dump_filesystems $hostname_arg $targetdir_opt $backupset_opt
release_lock
log info "Backup of host $hostname_arg finished."
;;
configure)
configure
;;
esac