commit 84f24980c7049d2093e438f059f730e0970c1a04 Author: GRMrGecko Date: Thu Dec 29 12:42:17 2022 -0600 first commit diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..1ea4a5e --- /dev/null +++ b/License.txt @@ -0,0 +1,19 @@ +Copyright (c) 2022 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..684e177 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# KVM Backup Scripts + +These are scripts I wrote to perform backup of KVM images in my homelab environment. I am publishing them here for the case it is useful to someone. These scripts backups to a (borg backup)[https://borgbackup.readthedocs.io/en/stable/index.html] archive, as that is my favorite backup software. \ No newline at end of file diff --git a/kvm-backup-images.sh b/kvm-backup-images.sh new file mode 100644 index 0000000..919f8e1 --- /dev/null +++ b/kvm-backup-images.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# Copyright (c) 2022 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ + +# This is for backing up block devices in virsh +# which use image files such as qcow2. +# This also works with GlusterFS so long as your +# volume is mounted. + +# The borg repository we're backing up to. +export BORG_REPO='/media/Storage/Backup/kvm' +# If you have a passphrase for your repository, +# set it here or you can use bash to retrieve it. +# export BORG_PASSPHRASE='' +# Set to empty string to disable pruning. +PRUNE_OPTIONS="--keep-daily 7 --keep-weekly 4 --keep-monthly 6" + +# I save the status in a temporary file so I can error out and exit if a failure occurs. +DOMLIST_STATUS_TMP="/tmp/backup-image-domlist-tmp" +while read -r line; do + # Extract the domain name and status from the line. + DOMAIN=$(echo $line | awk '{print $2}') + DOMSTATUS=$(echo $line | awk '{for (i=3; i=3) print $NF}') + + # If the domain is empty, its not needed. + if [ -z "$DOMAIN" ]; then + continue + fi + + # Get the images that need backing up. + DEVS=() + IMAGES=() + BLKLIST_STATUS_TMP="/tmp/backup-image-blklist-tmp" + while read -r line; do + # Extract the device and image from the line. + DEV=$(echo $line | awk '{print $1}') + IMAGE=$(echo $line | awk '{for (i=2; i=2) print $NF}') + + # Ignore empty line or no image. + if [ -z "$IMAGE" ] || [[ "$IMAGE" == "-" ]]; then + continue + fi + + # Ignore iso files. + if [[ "$IMAGE" =~ \.iso$ ]]; then + continue + fi + + # This image needs backing up. + DEVS+=("$DEV") + IMAGES+=("$IMAGE") + done < <( + virsh domblklist $DOMAIN | tail -n +3 + echo $? >$BLKLIST_STATUS_TMP + ) + + # Get status from the block listing. + status=1 + if [ -f $BLKLIST_STATUS_TMP ]; then + status=$(cat $BLKLIST_STATUS_TMP) + rm $BLKLIST_STATUS_TMP + fi + + # If status has an error, exit. + if [ $status -ne 0 ]; then + echo "Domain block listing failed" + exit 1 + fi + + # For each image we can backup, back it up. + for ((i = 0; i < ${#DEVS[@]}; i++)); do + DEV=${DEVS[$i]} + IMAGE=${IMAGES[$i]} + + # If the domain is running, we need to snapshot the disk so we can backup cleanly. + if [[ "$DOMSTATUS" == "running" ]]; then + echo "Creating snapshot for $DOMAIN ($DEV)" + virsh snapshot-create-as --domain "$DOMAIN" \ + --name backup \ + --no-metadata \ + --atomic \ + --disk-only \ + --diskspec $DEV,snapshot=external + + if [ $? -ne 0 ]; then + echo "Failed to create snapshot for $DOMAIN ($DEV)" + exit 1 + fi + fi + + # Backup the image. + echo "Creating backup for $DOMAIN ($DEV)" + IMAGENAME=$(basename "$IMAGE") + cat "$IMAGE" | pv | borg create \ + --verbose \ + --stats \ + --show-rc \ + --stdin-name "$IMAGENAME" \ + "::$DOMAIN-$DEV-{now}" - + + if [ $? -ne 0 ]; then + echo "Failed to backup $DOMAIN ($DEV)" + exit 1 + fi + + # Prune if options are configured. + if [ -n "$PRUNE_OPTIONS" ]; then + echo "Pruning backups for $DOMAIN ($DEV)" + borg prune --list \ + --show-rc \ + --glob-archives "$DOMAIN-$DEV-*" \ + $PRUNE_OPTIONS + + if [ $? -ne 0 ]; then + echo "Failed to prune $DOMAIN ($DEV)" + exit 1 + fi + fi + + # If the domain is running, commit the changes saved to the snapshot to the image to finish the backup. + if [[ "$DOMSTATUS" == "running" ]]; then + echo "Commit changes for $DOMAIN ($DEV)" + virsh blockcommit \ + "$DOMAIN" \ + "$DEV" \ + --active \ + --verbose \ + --pivot \ + --delete + + if [ $? -ne 0 ]; then + echo "Could not commit changes $DOMAIN ($DEV). This may be a major issue and VM may be broken now." + exit 1 + fi + fi + done + + # Backup the domain info. + echo "Backing up $DOMAIN xml" + virsh dumpxml "$DOMAIN" | borg create \ + --verbose \ + --stats \ + --show-rc \ + "::$DOMAIN-xml-{now}" - + + if [ $? -ne 0 ]; then + echo "Failed to backup $DOMAIN" + exit 1 + fi + + # Prune if options are configured. + if [ -n "$PRUNE_OPTIONS" ]; then + echo "Pruning backups for $IMAGE" + borg prune --list \ + --show-rc \ + --glob-archives "$DOMAIN-xml-*" \ + $PRUNE_OPTIONS + + if [ $? -ne 0 ]; then + echo "Failed to prune $DOMAIN" + exit 1 + fi + fi +done < <( + virsh list --all | tail -n +3 + echo $? >$DOMLIST_STATUS_TMP +) + +# Get status from the domain listing. +status=1 +if [ -f $DOMLIST_STATUS_TMP ]; then + status=$(cat $DOMLIST_STATUS_TMP) + rm $DOMLIST_STATUS_TMP +fi + +# If status has an error, exit. +if [ $status -ne 0 ]; then + echo "Domain listing failed" + exit 1 +fi + +# Shrink repo. +borg compact diff --git a/kvm-backup-rbd.sh b/kvm-backup-rbd.sh new file mode 100644 index 0000000..a0c70c9 --- /dev/null +++ b/kvm-backup-rbd.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Copyright (c) 2022 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ + +# This is for backing up Rados Block Device (Ceph) storage. + +# The pool in Ceph that you would like to backup. +POOL="libvirt" +# Pull images in pull from rbd driver. +IMAGES=$(rbd -p $POOL ls) +# The borg repository we're backing up to. +export BORG_REPO='/media/Storage/Backup/kvm' +# If you have a passphrase for your repository, +# set it here or you can use bash to retrieve it. +# export BORG_PASSPHRASE='' + +for IMAGE in $IMAGES; do + # Export volume to borg backup. + echo "Creating backup for $IMAGE" + rbd export $POOL/$IMAGE - | pv | borg create \ + --verbose \ + --stats \ + --show-rc \ + "::$IMAGE-{now}" - + + if [ $? -ne 0 ]; then + echo "Failed to backup $IMAGE" + exit 1 + fi + + # If you want to prune backups, you can set the prune options. + echo "Pruning backups for $IMAGE" + borg prune --list \ + --show-rc \ + --glob-archives "$IMAGE-*" \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 + + if [ $? -ne 0 ]; then + echo "Failed prune $IMAGE" + exit 1 + fi +done + +# I save the status in a temporary file so I can error out and exit if a failure occurs. +DOMLIST_STATUS_TMP="/tmp/backup-rbd-domlist-tmp" +while read -r line; do + # Extract the domain name from the line. + DOMAIN=$(echo $line | awk '{print $2}') + + # Backup the domain info. + echo "Backing up $DOMAIN xml" + virsh dumpxml "$DOMAIN" | borg create \ + --verbose \ + --stats \ + --show-rc \ + "::$DOMAIN-xml-{now}" - + + if [ $? -ne 0 ]; then + echo "Failed to backup $DOMAIN" + exit 1 + fi + + # Prune if options are configured. + if [ -n "$PRUNE_OPTIONS" ]; then + echo "Pruning backups for $IMAGE" + borg prune --list \ + --show-rc \ + --glob-archives "$DOMAIN-xml-*" \ + $PRUNE_OPTIONS + + if [ $? -ne 0 ]; then + echo "Failed to prune $DOMAIN" + exit 1 + fi + fi +done < <( + virsh list --all | tail -n +3 + echo $? >$DOMLIST_STATUS_TMP +) + +# Get status from the domain listing. +status=1 +if [ -f $DOMLIST_STATUS_TMP ]; then + status=$(cat $DOMLIST_STATUS_TMP) + rm $DOMLIST_STATUS_TMP +fi + +# If status has an error, exit. +if [ $status -ne 0 ]; then + echo "Domain listing failed" + exit 1 +fi + +# Shrink repo. +borg compact