Umzug des Servers von Ubuntu auf Debian

Der Neuaufbau eines Servers erfordert immer Planung. Gibt es Änderungen in der bereitgestellten Hardware, gibt es neue und vor allem bessere Software zum Erreichen der Ziele?

Aber zunächst gilt es die Daten, welche anschließend noch benötigt werden, zu sichern. Das heißt, das Backup sollte vollumfänglich sein. In meinem Fall war dies zunächst die Sicherung der virtuellen Maschinen, und da sich unter den VM auch ein NAS und eine Cloud war, auch eine zusätzliche Sicherung der Daten.

Die vollzieht man vor der Sicherung der VMs. Allerdings gestaltet sich die Sicherung der VMs bei dem Volumen an VMs, welches ich laufen hatte, als schwierig. Jede einzelne VM zu sichern, würde immense Zeit in Anspruch nehmen. Also wäre ein Script die Wahl.

Das Ganze wird natürlich auf einem Backup-Server gesichert. Als Grundlage habe ich mir einen etwas in die Jahre gekommenen Athlon 5350 genommen, ihn mit einer kleinen SSD und einer großen Festplatte bestückt. Die CPU ist schwachbrüstig, aber das macht ehrlich gesagt nichts, denn mehr als das OS hosten und die Daten für die Backup-Platte entgegennehmen muss sie nicht.

Dennoch wäre es schön, wenn der Backup-Server nur dann laufen muss, wenn er wirklich benötigt wird. Und wozu gibt es WakeOnLAN, wenn nicht für solche Situationen?

Also kurzerhand die notwendigen Einstellungen im Bios gesetzt, und dann versucht, ihn mit Netplan zum Annehmen von Paketen zu bewegen.

Den zunächst testet man erstmal, ob das System WakeOnLan Pakete entgegennimmt.

sudo ethtool eth0 | grep Wake-on
        Supports Wake-on: pumbg
	Wake-on: g

Das wäre die optimale Einstellung, allerdings war dies bei mir nicht der Fall. Bei mir sagte der Server, dass er keine WakeOnLAN Pakete entgegennahm.

Es gibt im Netplan die Option:

wakeonlan: true

Aber die brachte mich auch nicht weiter.

Was mir dann doch geholfen hat, war ein Service zu kreieren, der mir mithilfe von ethtool den Service startet:

[Unit]
Description=Wake-on-LAN for eth0
Requires=network.target
After=network.target

[Service]
ExecStart=/sbin/ethtool -s eth0 wol g
Type=oneshot

[Install]
WantedBy=multi-user.target

Dieses Codesnippet in /etc/systemd/system als wol@eth0.service gespeichert und dann gestartet, und siehe da, der PC nimmt jetzt WakeOnLan Pakete an.

Zur weiteren Vorbereitung galt es dann, sich passwortlos auf dem Backup-Server anmelden zu können, am besten via ssh.

ssh-keygen -t ed25519 -f /Pfad/zum/privatenSchlüssel -C "Kommentar zum Schlüssel"

Dieser Befehl erstellt ein Paar Schlüssel. Einen privaten, und einen öffentlichen, welcher die Endung .pub (public) erhält. Bei der Erstellung wird eine doppelte Passwortabfrage zum privaten Schlüssel kommen, diese sollte mit einem ausreichenden Passwort beantwortet werden, kann aber auch leer gelassen werden. Ascnhließend transferiert man den öffentlichen Schlüssel auf die gweünschte Maschine.

ssh-copy-id -i /Pfad/zum/öffentlichenSchlüssel.pub benutzername@<IP Adresses oder Hostname>

Somit kann man jetzt: Anschalten, einloggen, Scheiße bauen, aber es fehlt noch: Ausschalten.

Dies geht zwar auch via SSH, aber spätestens bei dem Befehl:

sudo shutdown now

verlangt das System dann das Passwort.

Aber auch da gibt es Abhilfe:

Entweder man kreiert eine Gruppe, fügt den Nutzer hinzu, der passwortlos den Shutdown einleiten kann, oder man lässt es den User direkt machen.

Sollte man die Gruppe anlegen wollen, muss man /etc/group editieren.

Auf jeden Fall muss man anschließend /etc/sudoers editieren:

%Groupname ALL=NOPASSWD: /sbin/shutdown

Username ALL=NOPASSWD: /sbin/shutdown

Der erste Befehl erlaubt der Gruppe den passwortlosen shutdown, der zweite dem User. Es sollte selbstverständlich sein, dass der Nutzer, mit dem der ssh Zugriff erfolgt, der Gruppe angehören sollte, oder aber der singuläre Nutzer ist, der das System passwortlos herunterfahren darf.

Nachdem jetzt die Vorbereitungen getroffen sind, das Script:

#!/bin/bash
###################################################################################################
#                                                                                                 #
#                                                                                                 #
#                          Backup of Virtual Machines Script                                      #
#                                                                                                 #
#                                                                                                 #
#                                                                                                 #
#                                  by Florian Klaus                                               #
#                                                                                                 #
#                                                                                                 #
#                                                                                                 #
###################################################################################################

# We need xmlllint and wakeonlan, let's scheck if the correspondings software pakets are installed
# xmllint is part of libxml2-utils, etherwake will install wakeonlan automaticly and is also needed
paketEtherwake=$(dpkg -s etherwake | grep Status)
paketLibxmlutils=$(dpkg -s libxml2-utils | grep Status)

#echo $paketEtherwake
#echo $paketLibxmlutils

if [[ $paketEtherwake = "Status: install ok installed" ]] && [[ $paketLibxmlutils = "Status: install ok installed" ]]
then
	echo "etherwake and libxml2-utils are installed"
elif [[ $paketEtherwake = "Status: install ok installed" ]] && [[ $paketLibxmlutils != "Status: install ok installed" ]]
then
	echo "libxml2-utils musst be installed, doing so now"
	apt update
	apt install -y libxml2-utils
elif [[ $paketEtherwake != "Status: install ok installed" ]] && [[ $paketLibxmlutils = "Status: install ok installed" ]]
then
	echo "etherwake musst be installed. doing so now"
	apt update
	apt install -y etherwake
elif [[ $paketEtherwake != "Status: install ok installed" ]] && [[ $paketLibxmlutils != "Status: install ok installed" ]]
then
	echo "neither Paket is installed, installing both now"
	apt update
	apt install -y etherwake libxml2-utils
fi

# Highest Number of source files in a single (!!!) VM installation. Source files may include the installation disk, 
# that is still in use, for one reason or another (mostly .iso-files), but they may also include different hard disks 
# associated with on VM (qcow2-files). 
# Check your installations and take the highest number. if it exceeds 5, then, first of all,
# may god have mercy on your soul. But you will have to set this number higher. Default is 3.

sourceFileHigh=3


# Excluded VMs - Virtual Machines you might want to exclude (for reasons) from the backup
# We need the name of the VM, as defined in the corresponding XML File
# Since this file is going to be parsed one line at a time, one VM per line please

excludedVMsFile="excludedVMs.txt"

nrOfExcludedVMs=$(wc -l < $excludedVMsFile)


# the stat command-output is localized, so please check a file e.g. stat filename.txt and look at the last 4 lines, 
# and remember, they are case-sensitive!!!:

# lastModifiedFile="Modify"
# lastChangedFile="Change"
# birthOfFile="Birth"
lastModifiedFile="Modifiziert"
lastChangedFile="Geändert"
birthOfFile="Geburt"


# XML Folder, where the XMLs for the VirtualMachines are stored, usually '/etc/libvirt/qemu'

xmlDir="/etc/libvirt/qemu/"


# temporal directory to save backup files in. /tmp is not chosen because it only lasts until reboot

localBackupDir=/var/tmp/backup-vms

#local sshfs mount directory, needs to be created

localMntDir=/mnt/backup-server

if [[ -d $localMntDir ]]
then
	echo "Local mount directory exists, nothing to do here ..."
else
	echo "Local mount directory doesn't exist, creating ..."
	mkdir $localMntDir
fi

# external Data

externalIP="192.168.168.192"

macAddress="FF:FF:FF:FF:FF:FF"

externalUserName="Username"

externalBackupDir="/home/$externalUserName/vm-backup-dir"

# Establish connection to backup-server

ping -c 1 $externalIP  &> /dev/null
pingStatus1=$( echo $? )
if [[ $pingStatus1 == 0 ]]
then
	echo "Backup-Server online, establishing connection"
	mountPoint="$externalUserName@$externalIP:$externalBackupDir $localMntDir"
	sshfs $mountPoint
	sleep 5s
	#exit
else
	echo "Backup-Server offline, trying to start it now ..."
	wakeonlan $macAddress
	sleep 120s
	ping -c 1 $externalIP &> /dev/null
	pingStatus2=$( echo $? )
	if [[ $pingStatus2 == 0 ]]
	then
		echo "Backup-Server woken up, establishing connection"
		mountPoint="$externalUserName@$externalIP:$externalBackupDir $localMntDir"
		sshfs $mountPoint
		sleep 5s
		#exit
	else
		echo "No Connection to the back-server, exiting ..."
		exit
	fi
fi

# Let's check if the local backup directory excists, and, if not, create it

if [[ -d "$localBackupDir" ]]
then
	echo "local backup directory exists, nothing to do here ..."
else
	echo "local backup directory doesn't exist, creating ..."
	mkdir "$localBackupDir"
fi

# Let's check if the Exclusion File is found

if [ -f $excludedVMsFile ]
then
	echo "found exclusion file"
fi

# Let's name our VM textfile
vmTextFile="listOfXMLs.txt"

# Remove any list of XMLs, if there is any

if [ -f $vmTextFile ]
then
	echo "$vmTextFile exists, removing ..."
	rm $vmTextFile
fi


# Find all XMLs in the given XMLDirectory, and make a List of them
find $xmlDir*.xml | rev | cut -d '/' -f1 | rev > $vmTextFile

# count the Number of XMLs, e.g. the Number of virtual machines

nrOfXMLs=$(wc -l < $vmTextFile)

# Let's create a file with the current running VMs

runningVMs="runningVMs.txt"

# remove any list of running VMs, if there is any

if [ -f $runningVMs ]
then
	echo "$runningVMs exists, removing ..."
	rm $runningVMs
fi  

ps -ef | grep qemu-system-x86_64 | cut -d',' -f1 | grep name | cut -d'=' -f2 > "$runningVMs"

# Let's see how many running VMs we have

nrOfRunningVMs=$(ps -ef | grep qemu-system-x86_64 | cut -d',' -f1 | grep name | cut -d'=' -f2 | wc -l)

# Now we iterate through the list of XMLs (e.g. VMs) and start doing shit with them 

for (( i=1 ; i<=$nrOfXMLs ; i++ ))
do 
	# the current VM
	currentXML=$(sed "${i}q;d" $vmTextFile)
	
	# We grab the Name of the current VM, and cut loose all the shit that thing doesn't need.
	nameOfVM=$(xmllint "$xmlDir$currentXML" | grep '<name>' | cut -d '>' -f2 | rev | cut -d '<' -f2 | rev)

	# Check if the VM to backup isn't exscluded.
	if [ -f $excludedVMsFile ]
	then
		for (( n = 1; n <= $nrOfExcludedVMs; n++ )) 
		do
			excludedVM=$(sed "${n}q;d" $excludedVMsFile)
			if [[ $nameOfVM = $excludedVM ]]
			then
				echo "Nothing to do with $nameOfVM"
				break
			fi
		done
	else
		echo "$nameOfVM is not excluded, continuing ..."
	fi
	if [[ $nameOfVM = $excludedVM ]]
	then
		echo "$nameOfVM has been excluded"
		continue
	elif [[ $nameOfVM != $excludedVM ]]
	then
		if [[ $nrOfRunningVMs -ge 1 ]]
		then 
			for (( x = 1; x <= $nrOfRunningVMs; x++ ))
			do 
				currentRunningVM=$(sed "${x}q;d" $runningVMs)
				if [[ $nameOfVM = $currentRunningVM ]]
				then
					echo "$nameOfVM is currently running, suspending ..."
					virsh suspend $nameOfVM
					break
				fi
			done
		fi

		# bash in itself doesn't let one operate with objects, so we are circumventing this 
		# right now by creating txt-based backupfiles
	
		# We create a current backupfile
		currentBackupFile="$nameOfVM"-Backup.txt""

		currentFileSourceFile="$nameOfVM"-SourceFile.txt""
	
		# The current Backupfile is obsolete, so we are deleting it
		if [[ -f $currentBackupFile ]]
		then 
			rm "$currentBackupFile"
		fi
	
		# The name of the VM is the first line in the newly created backup file
		echo "$nameOfVM" > "$currentBackupFile"
	
		# The XML file (including the directory path) is the second line in the backup file
		echo ""XML File="$xmlDir$currentXML" >> "$currentBackupFile"
	
		# Determining how many source files are there, e.g. we count the number of sorce files. Source files may include the 
		# installation disk (mostly .iso-files),
		# but they may also include different hard disks (qcow2-files). That's why we set the number earlier
		
		currentNrOfSourceFiles=$(xmllint "$xmlDir$currentXML" | grep 'source file' | wc -l)
		
		currentSourceFiles=$(xmllint "$xmlDir$currentXML" | grep 'source file')

		if [[ -f $currentFileSourceFile ]]
		then
			rm "$currentFileSourceFile"
		fi

		# declare k and l, which will incrememnted by 2 every time in the loop, to cut out the path for the different source files
		k=1
		l=2	

		# Now comes the other for loop, in which we will itereate through the different source files. See above

		for (( j=1 ; j<=$sourceFileHigh ; j++  ))
		do			

			cutSourceFile=$(echo $currentSourceFiles | grep 'source file' | cut -d " " -f${k}-${l}) 
			modSourceFile=$(echo $cutSourceFile | cut -d "=" -f2 | cut -d '"' -f2) #| cut -d ">" -f1 | rev | cut -d "/" -f2 | rev | cut -d '"' -f1)
			
			# if the source file is empty, just put a blank line in there
			if [[ -z $modSourceFile ]]
			then
				echo "$modSourceFile" >> "$currentBackupFile"
			# else put the following info in there
			else
				echo ""Source Path="$modSourceFile" >> "$currentBackupFile"

				echo "$modSourceFile" >> "$currentFileSourceFile"

				# now, let's dive into the statistics for the source file 

				# now, let's grep the info and store it in a file
				modifySourceFile=$(stat $modSourceFile |  grep $lastModifiedFile | cut -d ":" -f2-4 | cut -d " " -f2-4)
				echo ""Last Modified="$modifySourceFile" >> "$currentBackupFile"
				changeSourceFile=$(stat $modSourceFile |  grep $lastChangedFile | cut -d ":" -f2-4 | cut -d " " -f2-4)
				echo ""Last Changed="$changeSourceFile" >> "$currentBackupFile"			
				birthOfSourceFile=$(stat $modSourceFile |  grep $birthOfFile | cut -d ":" -f2-4 | cut -d " " -f2-4)
				echo ""Birth Of File="$birthOfSourceFile" >> "$currentBackupFile"
			
				

			fi
	

			# k needs to be incremented by 2
			k=$((k+2))

			# l needs to be incremented by 2
			l=$((l+2))
		
		done

		# Last line in file
		echo "End of File" >> "$currentBackupFile"


		# Check if the local backup directory exists, and, if it doesn't, create it
		if [[ -d "$localBackupDir/$nameOfVM" ]]
		then
			echo "local backup directory for $nameOfVM exists, nothing to do here ..."
		else
			echo "local backup directory for $nameOfVM doesn't exist, creating ..."
			mkdir "$localBackupDir/$nameOfVM"
		fi

		# Let's check if there is a backup file already and compare it to the current file.
		# Since the we just accumulated all the information about the VM, we check if the VM has been started in any way, 
		# and if it hasn't, we won't need to do anything.
		# In the aforementioned case, the loop will stop here and continue to the next VM
		
		if [[ -f "$localBackupDir/$nameOfVM/$currentBackupFile" ]]		
		then
			if [[ 'cmp "$localBackupDir/$nameOfVM/$currentBackupFile" $currentBackupFile' ]]
			then
				echo "The VM hasn't changed since the last backup, no need to do anything ..."
				rm "$currentBackupFile"
				rm "$currentFileSourceFile"
				continue
			else
				echo "The VM has changed, removing old backup file ..."
				rm "$localBackupDir/$nameOfVM/$currentBackupFile"
				echo "Writing new local backup file"
				rsync --progress "$currentBackupFile" "$localBackupDir/$nameOfVM/$currentBackupFile"
			fi
		else
			echo "The backup file doesn't exist, creating ..."
			rsync --progress "$currentBackupFile" "$localBackupDir/$nameOfVM/$currentBackupFile"
		fi

		if [[ -d "$localMntDir/$nameOfVM" ]]
			then
				echo "Directory for $nameOfVM exists, nothing to do here ..."
			else
				echo "Directory for $nameOfVM doesn't exist, creating ..."
				mkdir "$localMntDir/$nameOfVM"
			fi

		date="$(date '+%Y-%m-%d')"

		# This checks if the backup was done today, and if it was, it assumes that something went wrong 
		# (usually there are not two backups in a single day,)
		# If this is the second backup in one day, it removes everything.
			
		if [[ -d "$localMntDir/$nameOfVM/$date" ]]
		then
			echo "Todays date directory exists, cleaning it up ..."
			if [[ -f "$localMntDir/$nameOfVM/$date/$currentXML" ]]
			then
				echo "$currentXML exists, removing ..."
				rm -v "$localMntDir/$nameOfVM/$date/$currentXML"
			fi
			if [[ -f "$localMntDir/$nameOfVM/$date/$currentBackupFile" ]]
			then
				echo "$currentBackupFile exists, removing ..."
				rm -v "$localMntDir/$nameOfVM/$date/$currentBackupFile"
			fi
		else
			echo "Todays date directory doesn't exist, creating ..."
			mkdir "$localMntDir/$nameOfVM/$date"
		fi

		# Last but not least, the copying process of the needed files

			if [[ -d "$localMntDir/$nameOfVM/$date" ]]
			then
				echo " Copying text files to backup destination ..."
				rsync --progress "$xmlDir$currentXML" "$localMntDir/$nameOfVM/$date/"
				rsync --progress "$currentBackupFile" "$localMntDir/$nameOfVM/$date/"
			fi

		sleep 10s

		nrOfSourceFiles=$(wc -l < $currentFileSourceFile)

		for (( z = 1; z <= $nrOfSourceFiles; z++ ))
		do
			currentQcow2=$(sed -n "${z}p" "$currentFileSourceFile")
			currentQcow2File=$(sed -n "${z}p" "$currentFileSourceFile" | rev | cut -d '/' -f1 | rev)

			if [[ -f "$localMntDir/$nameOfVM/$date/$currentQcow2File" ]]
			then
				echo "$currentQcow2 exists, removing ..."
				rm -v "$localMntDir/$nameOfVM/$date/$currentQcow2File"	
			fi

			if [[ -d "$localMntDir/$nameOfVM/$date" ]]
			then
				echo " Copying $currentQcow2 files to backup destination ..."
				rsync --progress "$currentQcow2" "$localMntDir/$nameOfVM/$date/"
			fi
			
		done
		
		# Cleanup ...

		echo "removing created files ..."

		rm "$currentBackupFile"
		rm "$currentFileSourceFile"

		# We need to resume the VM, if it was running in the first place ...

		if [[ $nameOfVM = $currentRunningVM ]]
		then
			echo "$nameOfVM needs to be resumed!"
			virsh resume $nameOfVM
		fi
	fi
done

# Cleanup ...

rm "$vmTextFile"
rm "$runningVMs"

# Unmount the sshfs directory

fusermount -u $localMntDir

# shutdown the remote backup-server

ssh "$externalUserName@$externalIP" "sudo shutdown now"

Knapp 450 geschmeidige Zeilen. Aber warum so viel?

Das Unterfangen wäre mit python wahrscheinlich einfacher gewesen, aber ich wollte es komplett auf bash-ebene durchziehen. Dennoch werden zwei Pakete benötigt, und die ersten Zeilen überpfrüfen ob diese installiert sind und tuen dies, wenn sie es nicht sind.

Anschließend werden ein paar Abfragen gestellt:

# Highest Number of source files in a single (!!!) VM installation. Source files may include the installation disk, 
# that is still in use, for one reason or another (mostly .iso-files), but they may also include different hard disks 
# associated with on VM (qcow2-files). 
# Check your installations and take the highest number. if it exceeds 5, then, first of all,
# may god have mercy on your soul. But you will have to set this number higher. Default is 3.

sourceFileHigh=3


# Excluded VMs - Virtual Machines you might want to exclude (for reasons) from the backup
# We need the name of the VM, as defined in the corresponding XML File
# Since this file is going to be parsed one line at a time, one VM per line please

excludedVMsFile="excludedVMs.txt"

nrOfExcludedVMs=$(wc -l < $excludedVMsFile)


# the stat command-output is localized, so please check a file e.g. stat filename.txt and look at the last 4 lines, 
# and remember, they are case-sensitive!!!:

# lastModifiedFile="Modify"
# lastChangedFile="Change"
# birthOfFile="Birth"
lastModifiedFile="Modifiziert"
lastChangedFile="Geändert"
birthOfFile="Geburt"


# XML Folder, where the XMLs for the VirtualMachines are stored, usually '/etc/libvirt/qemu'

xmlDir="/etc/libvirt/qemu/"


# temporal directory to save backup files in. /tmp is not chosen because it only lasts until reboot

localBackupDir=/var/tmp/backup-vms

#local sshfs mount directory, needs to be created

localMntDir=/mnt/backup-server

if [[ -d $localMntDir ]]
then
	echo "Local mount directory exists, nothing to do here ..."
else
	echo "Local mount directory doesn't exist, creating ..."
	mkdir $localMntDir
fi

# external Data

externalIP="192.168.168.192"

macAddress="FF:FF:FF:FF:FF:FF"

externalUserName="Username"

externalBackupDir="/home/$externalUserName/vm-backup-dir"

Zunächst einmal wird die maximale Anzahl an Source Files abgefragt. Dies sollten Platten sein, die der virtuellen Maschine zugeordnet sind, können aber auch Installationsmedien sein.

sourceFileHigh=3

Danach kann man eine Datei angeben, in die VMs geschrieben werden, die vom Backup ausgeschlossen sein sollen. Ausgehend von dieser Liste wird die Anzahl berechnet.

excludedVMsFile="excludedVMs.txt"

Anschließend wird darauf eingegangen, dass die Daten, die man zieht lokalisiert ausgegeben werden. Diese sollte man der entsprechenden Lokalisation anpassen.

lastModifiedFile="Modifiziert"
lastChangedFile="Geändert"
birthOfFile="Geburt"

Nun kommen noch die Abfragen zu einigen Verzeichnissen, und die Daten für den externen Backup-PC.

Und damit hat das Skript auch schon alles, was es benötigt.

# Establish connection to backup-server

ping -c 1 $externalIP  &> /dev/null
pingStatus1=$( echo $? )
if [[ $pingStatus1 == 0 ]]
then
	echo "Backup-Server online, establishing connection"
	mountPoint="$externalUserName@$externalIP:$externalBackupDir $localMntDir"
	sshfs $mountPoint
	sleep 5s
	#exit
else
	echo "Backup-Server offline, trying to start it now ..."
	wakeonlan $macAddress
	sleep 120s
	ping -c 1 $externalIP &> /dev/null
	pingStatus2=$( echo $? )
	if [[ $pingStatus2 == 0 ]]
	then
		echo "Backup-Server woken up, establishing connection"
		mountPoint="$externalUserName@$externalIP:$externalBackupDir $localMntDir"
		sshfs $mountPoint
		sleep 5s
		#exit
	else
		echo "No Connection to the back-server, exiting ..."
		exit
	fi
fi

Als Erstes stellt das Skript dann eine Verbindung zum Backup-Server her. Sollte diese nicht gegeben sein, wird es auch nichts mit dem Backup. Der Server wird auf Verfügbarkeit geprüft, und wenn dies nicht der Fall ist (wovon wir eigentlich ausgehen, aber man weiß ja nie), wird dieser via WakeOnLan gestartet.

Danach wird 2 Minuten gewartet, um dann eine erneute online Prüfung zu starten. Spätestens zu diesem Zeitpunkt sollte er da sein, und das externe Verzeichnis wird lokal gemountet.

Sollte zu diesem Zeitpunkt immer noch keine Verbindung bestehen, dann bricht das Skript an dieser Stelle ab.

# Let's check if the local backup directory excists, and, if not, create it

if [[ -d "$localBackupDir" ]]
then
	echo "local backup directory exists, nothing to do here ..."
else
	echo "local backup directory doesn't exist, creating ..."
	mkdir "$localBackupDir"
fi

Als Nächstes wird das lokale Backup Verzeichnis erstellt. Moment mal: Lokales Backup-Verzeichnis? Keine Panik, da wir eh mit Text-basierten Dateien arbeiten, ist dies ein Feature, welches uns zu einem späteren Zeitpunkt noch gelegen kommen wird.

# Let's check if the Exclusion File is found

if [ -f $excludedVMsFile ]
then
	echo "found exclusion file"
fi

# Let's name our VM textfile
vmTextFile="listOfXMLs.txt"

# Remove any list of XMLs, if there is any

if [ -f $vmTextFile ]
then
	echo "$vmTextFile exists, removing ..."
	rm $vmTextFile
fi


# Find all XMLs in the given XMLDirectory, and make a List of them
find $xmlDir*.xml | rev | cut -d '/' -f1 | rev > $vmTextFile

# count the Number of XMLs, e.g. the Number of virtual machines

nrOfXMLs=$(wc -l < $vmTextFile)

# Let's create a file with the current running VMs

runningVMs="runningVMs.txt"

# remove any list of running VMs, if there is any

if [ -f $runningVMs ]
then
	echo "$runningVMs exists, removing ..."
	rm $runningVMs
fi  

ps -ef | grep qemu-system-x86_64 | cut -d',' -f1 | grep name | cut -d'=' -f2 > "$runningVMs"

# Let's see how many running VMs we have

nrOfRunningVMs=$(ps -ef | grep qemu-system-x86_64 | cut -d',' -f1 | grep name | cut -d'=' -f2 | wc -l)

Nun geht es um die VMs. Als erstes wird überprüft, ob es eine Ausschlussliste gibt.

Dann wird die VM-Textdatei deklariert, geprüft ob sie bereits existiert (was sie nicht sollte, deswegen werden Vorgänger – Versionen auch gelöscht), und dann wurden alle XML-Dateien im XML Vereichnis gesucht und als Liste in die Datei gepackt. Anschließend wird die Anzahl ermittelt.

Und bevor wir uns jetzt in die Schleife stürzen, wird auch noch eine Liste aller laufenden VMs erstellt. Denn, sollten diese laufen, müssen sie nachher zumindest pausiert werden, damit es keine Inkonsistenten bei den Daten gibt. Auch hier wird die Anzah erneut ermittelt.

Dann beginnt die for Schleife ….

for (( i=1 ; i<=$nrOfXMLs ; i++ ))
do 
	# the current VM
	currentXML=$(sed "${i}q;d" $vmTextFile)
	
	# We grab the Name of the current VM, and cut loose all the shit that thing doesn't need.
	nameOfVM=$(xmllint "$xmlDir$currentXML" | grep '<name>' | cut -d '>' -f2 | rev | cut -d '<' -f2 | rev)

	# Check if the VM to backup isn't exscluded.
	if [ -f $excludedVMsFile ]
	then
		for (( n = 1; n <= $nrOfExcludedVMs; n++ )) 
		do
			excludedVM=$(sed "${n}q;d" $excludedVMsFile)
			if [[ $nameOfVM = $excludedVM ]]
			then
				echo "Nothing to do with $nameOfVM"
				break
			fi
		done
	else
		echo "$nameOfVM is not excluded, continuing ..."
	fi
	if [[ $nameOfVM = $excludedVM ]]
	then
		echo "$nameOfVM has been excluded"
		continue
	elif [[ $nameOfVM != $excludedVM ]]
	then
		if [[ $nrOfRunningVMs -ge 1 ]]
		then 
			for (( x = 1; x <= $nrOfRunningVMs; x++ ))
			do 
				currentRunningVM=$(sed "${x}q;d" $runningVMs)
				if [[ $nameOfVM = $currentRunningVM ]]
				then
					echo "$nameOfVM is currently running, suspending ..."
					virsh suspend $nameOfVM
					break
				fi
			done
		fi

Die Hauptiteration erfolgt durch die Liste an XMLs, die vorher erstellt wurde. Damit sollten alle VMs aufgefangen werden.

Zunächst wird die zueghörige XML Datei herausgeuchst, und in ihr nach dem Namen der VM gesucht, und dieser dann extrahiert.

Nach einer proforma-Abfrage ob die Ausschlussdatei existiert, wird diese dann mit dem Namen der jetzigen VM verglichen. Sollte es eine Übereinstimmung geben, wird aus der schleife ausgebrochen.

Der Vergleich wird nochmal herangezogen, aber diesmal mit dem Unterschied, dass die Hauptiteration nicht verlassen wird, sondern einfach einen Zähler heraufgesetzt wird, und somit die restlichen Anweisungen in der Schleife für diese VMeinfach nicht zum tragen kommen. Sie war ja ausgeschlossen.

Anschließend gibt es die proforma-Abfrage umgekehrt, und dann wird überprüft, ob es sich um eine laufende VM handelt. Diese wird dann angehalten, um die Datenkonsistenz zu garantieren. Nach dem Anhalten wird aus der dazugehörigen Schleife ausgebrochen.

Alles was jetzt kommt, wird mit jeder VM gemacht, es sei denn, sie ist von vorneherein ausgeschlossen.

# bash in itself doesn't let one operate with objects, so we are circumventing this 
		# right now by creating txt-based backupfiles
	
		# We create a current backupfile
		currentBackupFile="$nameOfVM"-Backup.txt""

		currentFileSourceFile="$nameOfVM"-SourceFile.txt""
	
		# The current Backupfile is obsolete, so we are deleting it
		if [[ -f $currentBackupFile ]]
		then 
			rm "$currentBackupFile"
		fi
	
		# The name of the VM is the first line in the newly created backup file
		echo "$nameOfVM" > "$currentBackupFile"
	
		# The XML file (including the directory path) is the second line in the backup file
		echo ""XML File="$xmlDir$currentXML" >> "$currentBackupFile"
	
		# Determining how many source files are there, e.g. we count the number of sorce files. Source files may include the 
		# installation disk (mostly .iso-files),
		# but they may also include different hard disks (qcow2-files). That's why we set the number earlier
		
		currentNrOfSourceFiles=$(xmllint "$xmlDir$currentXML" | grep 'source file' | wc -l)
		
		currentSourceFiles=$(xmllint "$xmlDir$currentXML" | grep 'source file')

		if [[ -f $currentFileSourceFile ]]
		then
			rm "$currentFileSourceFile"
		fi

		# declare k and l, which will incrememnted by 2 every time in the loop, to cut out the path for the different source files
		k=1
		l=2	

		# Now comes the other for loop, in which we will itereate through the different source files. See above

		for (( j=1 ; j<=$sourceFileHigh ; j++  ))
		do			

			cutSourceFile=$(echo $currentSourceFiles | grep 'source file' | cut -d " " -f${k}-${l}) 
			modSourceFile=$(echo $cutSourceFile | cut -d "=" -f2 | cut -d '"' -f2) #| cut -d ">" -f1 | rev | cut -d "/" -f2 | rev | cut -d '"' -f1)
			
			# if the source file is empty, just put a blank line in there
			if [[ -z $modSourceFile ]]
			then
				echo "$modSourceFile" >> "$currentBackupFile"
			# else put the following info in there
			else
				echo ""Source Path="$modSourceFile" >> "$currentBackupFile"

				echo "$modSourceFile" >> "$currentFileSourceFile"
				  
				# now let's grep the info and store it in a file
				modifySourceFile=$(stat $modSourceFile |  grep $lastModifiedFile | cut -d ":" -f2-4 | cut -d " " -f2-4)
				echo ""Last Modified="$modifySourceFile" >> "$currentBackupFile"
				changeSourceFile=$(stat $modSourceFile |  grep $lastChangedFile | cut -d ":" -f2-4 | cut -d " " -f2-4)
				echo ""Last Changed="$changeSourceFile" >> "$currentBackupFile"			
				birthOfSourceFile=$(stat $modSourceFile |  grep $birthOfFile | cut -d ":" -f2-4 | cut -d " " -f2-4)
				echo ""Birth Of File="$birthOfSourceFile" >> "$currentBackupFile"

			fi
	

			# k needs to be incremented by 2
			k=$((k+2))

			# l needs to be incremented by 2
			l=$((l+2))
		
		done

		# Last line in file
		echo "End of File" >> "$currentBackupFile"

Es werden zwei Dateien kreiert, zum einen die Backup-Datei, zum anderen die Quellen-Datei.

Erstere spielt längerfristig eine Rolle, die zweite nur temporär.

Es erfolgt die Standardmäßige überprüfung, ob sie existiert.

Dann wird angefangen die Backup-Datei zu schreiben.

Daten in ihr:

Name
XML Datei mit Pfad
Quell-Datei-1 mit Pfad
Datum der letzen Modifizierung von Quell-Datei-1
Datum der letzte Änderung von Quell-Datei-1
Datum der Erschaffung von Quell-Datei-1
# Bei mehreren Quell-Dateien
Quell-Datei-2 mit Pfad
Datum der letzen Modifizierung von Quell-Datei-2
Datum der letzte Änderung von Quell-Datei-2
Datum der Erschaffung von Quell-Datei-2

Ende der Datei

Ursprünglich war Zugriff mit in der Datei, aber da dieser sich bei jedem Kopiervorgang ändert, wurde dieser entfernt.

# Check if the local backup directory exists, and, if it doesn't, create it
		if [[ -d "$localBackupDir/$nameOfVM" ]]
		then
			echo "local backup directory for $nameOfVM exists, nothing to do here ..."
		else
			echo "local backup directory for $nameOfVM doesn't exist, creating ..."
			mkdir "$localBackupDir/$nameOfVM"
		fi

Ein Verzeichnis mit Namen der aktuellen VM wird lokal erstellt, sollte es nicht existieren, um darin die Backup-Datei zu speichern.

# Let's check if there is a backup file already and compare it to the current file
    # Since the we just accumulated all the information about the VM, we check if the VM has been        started in any way, 
    # and if it hasn't, we won't need to do anything.
    # In the aforementioned case, the loop will stop here and continue to the next VM

    if [[ -f "$localBackupDir/$nameOfVM/$currentBackupFile" ]]       
    then
        if [[ 'cmp "$localBackupDir/$nameOfVM/$currentBackupFile" $currentBackupFile' ]]
        then
            echo "The VM hasn't changed since the last backup, no need to do anything ..."
            rm "$currentBackupFile"
            rm "$currentFileSourceFile"
            continue
        else
            echo "The VM has changed, removing old backup file ..."
            rm "$localBackupDir/$nameOfVM/$currentBackupFile"
            echo "Writing new local backup file"
            rsync --progress "$currentBackupFile" "$localBackupDir/$nameOfVM/$currentBackupFile"
        fi
    else
        echo "The backup file doesn't exist, creating ..."
        rsync --progress "$currentBackupFile" "$localBackupDir/$nameOfVM/$currentBackupFile"
    fi

Es erfolgt der Vergleich (sofern schon eine Backup-Datei existiert) zwischen der gerade erstellten und der gespeicherten Backup-Datei. Sollten diese identisch sein, wird auf das Backup verzichtet, und mit der nächsten VM weitergemacht. Es lohnt sich nicht, die VM, die bereits auf der backup-Platte existiert, erneut zu backupsichernen. Für diesen Vergleich existiert die Backup-Datei.

if [[ -d "$localMntDir/$nameOfVM" ]]
			then
				echo "Directory for $nameOfVM exists, nothing to do here ..."
			else
				echo "Directory for $nameOfVM doesn't exist, creating ..."
				mkdir "$localMntDir/$nameOfVM"
			fi

		date="$(date '+%Y-%m-%d')"

		# This checks if the backup was done today, and if it was, it assumes that something went wrong 
		# (usually there are not two backups in a single day,)
		# If this is the second backup in one day, it removes everything.
			
		if [[ -d "$localMntDir/$nameOfVM/$date" ]]
		then
			echo "Todays date directory exists, cleaning it up ..."
			if [[ -f "$localMntDir/$nameOfVM/$date/$currentXML" ]]
			then
				echo "$currentXML exists, removing ..."
				rm -v "$localMntDir/$nameOfVM/$date/$currentXML"
			fi
			if [[ -f "$localMntDir/$nameOfVM/$date/$currentBackupFile" ]]
			then
				echo "$currentBackupFile exists, removing ..."
				rm -v "$localMntDir/$nameOfVM/$date/$currentBackupFile"
			fi
		else
			echo "Todays date directory doesn't exist, creating ..."
			mkdir "$localMntDir/$nameOfVM/$date"
		fi

		# Last but not least, the copying process of the needed files

			if [[ -d "$localMntDir/$nameOfVM/$date" ]]
			then
				echo " Copying text files to backup destination ..."
				rsync --progress "$xmlDir$currentXML" "$localMntDir/$nameOfVM/$date/"
				rsync --progress "$currentBackupFile" "$localMntDir/$nameOfVM/$date/"
			fi

		sleep 10s

Jetzt wird Verzeichnis auf dem gemounteten Datenträger kreiert, auch wieder mit Namen der VM. Hinzu kommt ein Verzeichnis im Datumsformat. Sollte es (aus Gründen) zwei Backups am selben Tag geben, ist davon auszugehen, dass etwas schief gelaufen ist. In dem fall werden die vorhandenen daten gelöscht.

nrOfSourceFiles=$(wc -l < $currentFileSourceFile)

		for (( z = 1; z <= $nrOfSourceFiles; z++ ))
		do
			currentQcow2=$(sed -n "${z}p" "$currentFileSourceFile")
			currentQcow2File=$(sed -n "${z}p" "$currentFileSourceFile" | rev | cut -d '/' -f1 | rev)

			if [[ -f "$localMntDir/$nameOfVM/$date/$currentQcow2File" ]]
			then
				echo "$currentQcow2 exists, removing ..."
				rm -v "$localMntDir/$nameOfVM/$date/$currentQcow2File"	
			fi

			if [[ -d "$localMntDir/$nameOfVM/$date" ]]
			then
				echo " Copying $currentQcow2 files to backup destination ..."
				rsync --progress "$currentQcow2" "$localMntDir/$nameOfVM/$date/"
			fi
			
		done

Nun wird die Quellen-Datei untersucht, und alle in ihr befindlichen Dateien in das neue Verzeichnis kopiert. Auch hier: Sollte dies am selben Tag passieren, werden die Daten zuvor gelöscht.

# Cleanup ...

		echo "removing created files ..."

		rm "$currentBackupFile"
		rm "$currentFileSourceFile"

		# We need to resume the VM, if it was running in the first place ...

		if [[ $nameOfVM = $currentRunningVM ]]
		then
			echo "$nameOfVM needs to be resumed!"
			virsh resume $nameOfVM
		fi
	fi
done

# Cleanup ...

rm "$vmTextFile"
rm "$runningVMs"

# Unmount the sshfs directory

fusermount -u $localMntDir

# shutdown the remote backup-server

ssh "$externalUserName@$externalIP" "sudo shutdown now"

Abschließend wird aufgeräumt. Sämtliche erstellten Dateien werden gelöscht, und sobald die Hauptschleife verlassen wurde, wird das lokal gemountete Verzeichnis wieder freigegeben. Somit bleibt nur noch eines: den Backup-Server herunterzufahren.

Das Script war so im Einsatz und hat zwei so gewollte Sicherungen generiert. Aber es ist definitiv noch nicht perfekt. Der Existenz-Abgleich der Dateien könnte noch besser werden, und es gibt noch kein „Packen“ der VMs, d.h. sie werden in ihrer ursprünglichen Größe transferiert.

Dies hat aber folgende Gründe: Ich hatte den Platz auf der dem Backup-Server, und ich wollte die Sicherung in einem angemessenen Zeitrahmen vonstatten bringen. Die Sicherung aller VMs hat mich so schon ca. 36 Stunden gekostet, mit „Packen“ hätte das länger gedauert.

Für die Zukunft ziehe ich das aber in Betracht.

Und das Script, mit ein paar Modifikationen, wird dabei helfen.

Je nach Wochentag kann ich eine bestimmte Liste an VMs sichern, indem ich den Rest einfach auf die „nicht sichern Liste“ setze, und diese kann ich dann auch packen. Dann habe nehme ich mir das Zeitfenster von zwei bis sechs Uhr morgens, und sichere diese dann zu diesem Zeitpunkt weg.