357 lines
7.4 KiB
Bash
Executable file
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
|