ansible-role-backup-client/templates/backup-vm.j2
2022-08-01 21:32:37 +02:00

140 lines
3.9 KiB
Django/Jinja
Executable file

#!/bin/bash
set -euo pipefail
(
set -euo pipefail
flock -x -w 10 200 || exit 1
export LVM_SUPPRESS_FD_WARNINGS=1
DOMAIN=${1:?VM name musst be passed!}
DOMAIN_MOUNTBASE="/var/backup-client/vm-mountpoint"
function unfreeze_vm {
virsh resume "$DOMAIN" > /dev/null || true
}
function delete_snapshots {
cleanup_vmmount > /dev/null 2>&1
for i in $DISKS; do
extract_lvm
SNAPSHOT="/dev/$VG/backup-$DEVICE"
lvremove -f "$SNAPSHOT" > /dev/null 2>&1 || true
done
for i in $DISKS_QCOW2; do
extract_lvm_qcow2
SNAPSHOT="/dev/$VG/backup-$DEVICE"
lvremove -f "$SNAPSHOT" > /dev/null 2>&1 || true
done
}
function cleanup_vmmount {
umount -l $DOMAIN_MOUNTBASE/* || true
rmdir ${DOMAIN_MOUNTBASE:?}/* || true
rm "$DOMAIN_MOUNTBASE/config.xml" || true
}
function extract_lvm {
DISK=`echo $i | awk -F, '{print $1}'`
DEVICE=`echo $i | awk -F, '{print $2}'`
VG=`lvs --noheadings -o vg_name "$DISK" | tr -d ' '`
LV=`lvs --noheadings -o lv_name "$DISK" | tr -d ' '`
}
function extract_lvm_qcow2 {
DISK=`echo $i | awk -F, '{print $1}'`
DEVICE=`echo $i | awk -F, '{print $2}'`
DEVICEPATH=`df "$DISK" | awk '{print $1}' | tail -n1`
MOUNTPOINT=`df "$DISK" | awk '{print $6}' | tail -n1`
RELIMAGEPATH="${DISK#$MOUNTPOINT}"
VG=`lvs --noheadings -o vg_name "$DEVICEPATH" | tr -d ' '`
LV=`lvs --noheadings -o lv_name "$DEVICEPATH" | tr -d ' '`
}
function backup_vm {
# get a list of disks
DISKS=`virsh domblklist "$DOMAIN" --details \
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \
| grep ^block \
| grep -v swap \
| awk '{printf "%s,%s ",$4, $3}'`
DISKS_QCOW2=`virsh domblklist "$DOMAIN" --details \
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \
| grep ^file \
| grep -v swap \
| grep '.qcow2' \
| awk '{printf "%s,%s ",$4, $3}'`
echo "backing up $DOMAIN: $DISKS $DISKS_QCOW2"
trap "unfreeze_vm || true; delete_snapshots" INT TERM EXIT
# in case an earlier script crashed we have to clear snapshots
delete_snapshots
# freez vm
virsh suspend "$DOMAIN" > /dev/null || true
# create disk snapshots
for i in $DISKS; do
extract_lvm
lvcreate -L16G -s -n "backup-$DEVICE" "$DISK" > /dev/null
done
# create snapshots of all volumes containing qcow2 images
for i in $DISKS_QCOW2; do
extract_lvm_qcow2
lvcreate -L16G -s -n "backup-$DEVICE" "$DEVICEPATH" > /dev/null
done
# dump vm config
virsh dumpxml "$DOMAIN" > "$DOMAIN_MOUNTBASE/config.xml"
unfreeze_vm
trap "delete_snapshots" INT TERM EXIT
# mount disks snapshots
for i in $DISKS; do
extract_lvm
SNAPSHOT="/dev/$VG/backup-$DEVICE"
fsck -y "$SNAPSHOT" > /dev/null 2> /dev/null
(
mkdir "$DOMAIN_MOUNTBASE/$DEVICE"
mount -o ro "$SNAPSHOT" "$DOMAIN_MOUNTBASE/$DEVICE"
) 2> /dev/null
done
# mount qcow2 disk images backing storage
for i in $DISKS_QCOW2; do
extract_lvm_qcow2
SNAPSHOT="/dev/$VG/backup-$DEVICE"
fsck -y "$SNAPSHOT" > /dev/null 2> /dev/null
(
mkdir "$DOMAIN_MOUNTBASE/IMAGECONTAINER_$DEVICE"
# to be able to run fsck inside the vm we have to mount the containers r/w
mount -o rw "$SNAPSHOT" "$DOMAIN_MOUNTBASE/IMAGECONTAINER_$DEVICE"
mkdir "$DOMAIN_MOUNTBASE/$DEVICE"
guestmount -m /dev/sda -a "$DOMAIN_MOUNTBASE/IMAGECONTAINER_$DEVICE/$RELIMAGEPATH" "$DOMAIN_MOUNTBASE/$DEVICE"
) 2> /dev/null
done
{% if backup_backend == 'restic' %}
# restic backend
source /etc/backup-client/restic.env
export RESTIC_PROGRESS_FPS=1
restic backup \
{{ restic_combined_flags }} \
--verbose \
--host "$DOMAIN" \
--exclude-file "/etc/backup-client/vms/$DOMAIN/exclude_files" \
--exclude="$DOMAIN_MOUNTBASE/IMAGECONTAINER_*" \
"$DOMAIN_MOUNTBASE"
{% endif %}
# delete snapshot
delete_snapshots;
trap - INT TERM EXIT
}
test -f "/etc/backup-client/vms/$DOMAIN/enabled" && backup_vm || echo "Backup for $DOMAIN is disabled"
) 200>/var/lock/backup-vm.lock