#!/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 restic backup \ --cleanup-cache {% if not backup_restic_cache %} --no-cache{% endif %} \ --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