Remote (Headless) Image Backup and Restore of Proxmox Hosts With Clonezilla

In this little tutorial you’ll get an idea of how to backup and restore a Proxmox VE host system

  • with the help of Clonezilla
  • without the need to attach a monitor to the host computer because it will work remotely with a SSH shell
  • with support for LVM based installations

All the above bullets are pros in my opinion πŸ˜‰ Despite that there is one major and one minor downside:

  • Major: The backup/restore cannot be done online, the host needs to reboot into Clonezilla as a live system ;(
  • Minor: The backup task is hard to fully automate so scheduled (and also incremental) backups are out of scope at least for this little showcase.

Why Clonezilla (TLDR)?

First and foremost: Clonezilla is a pretty reliable and up to a certain kind a user friendly backup and restore utility box when it comes to image backup/restore. For file based backups/restores there are other solutions out there like UrBackup that I really like for this task. But I never found a better solution to do image based backup/restore tasks. Speaking of UrBackup, a little TLDR why UrBackup (and other solutions) do not fulfill my needs:

  • Third party software needed: You need to install a client software on the host
  • Online backups: To achieve real online backups you need a mechanism that enables you to do so. For example:
    • dattobd:
      • Not a official kernel module
      • Not that good support for Debian 11/12 that Proxmox relies on
    • device mapper snapshots:
      • You need to have this enabled AND you need to have enough LVM/disk space left since the underlying mechanism uses LVM snapshots that first create copys of the source. I most often do not have that space reserved on my little cluster computers so it does not work for me.

If online backup is not that crucial to you then why not use the good old Clonezilla? Probably you gonna say that this is not an ideal solution because your Proxmox host system has no monitor or similar attached and/or the host is located somewhere where you can’t just sit next to it. Not the best situation to boot a live system from that machine, right? Partially true πŸ˜‰

How-to

IMPORTANT: Please test this procedure with the help of virtualized instances of Proxmox to ensure everything works well. Although this will cover a lot of obstacles, if any, that might arise in you specific environment, there will be a remaining risk that you need to have access to your host system via monitor or some kind of another rescue system that can help you troubleshoot boot errors…

This won’t be an Clonezilla overview at all. I’ll just give you an idea of how to set up a solution that considers LVM based installations of Proxmox, that lets you successfully boot into Clonezilla remotely and that also lets you interact with Clonezilla remotely with the help of a SSH shell. Every time you want to boot into Clonezilla you should run this script again because it is time based. See explaination below. The overall workflow to use this script is:

  • Change the script variables if needed
  • Make the script executable
  • Run the script with elevated rights (sudo’ed/with root priviliges in most cases)
  • Reboot the machine once (boots it into Clonezilla)
  • Connect via SSH with the machine again which now runs Clonzilla live.
  • Reboot the machine a second time (will boot into Proxmox again).
  • (optional but recommanded) Run ‘update-grub’ to restore grub.cfg that was temporarily altered by this script.

The most recent state of the script can be found here: Github Down below is the one valid when I wrote this blog entry. The script does the following:

Create GRUB Header Script

It creates a GRUB header script that helps to calculate a end date/time upt to that a boot into Clonezilla will be valid (default = now + 5 minutes). After executing the script a reboot within this next 5 minutes will result in a Clonezilla boot (meaning: choosing the Clonezilla boot entry created in the next step), after that Proxmox is going to be booted again (default GRUB entry, entry 0). That time based boot is a solution to overcome the limitation of LVM based capabilities that GRUB comes with. GRUB is not able to write internal states to LVM partitions so there is no mechanism for GRUB to remember which boot entry it should choose now and which next.

Create GRUB Header Menu Entry Script

The script also creates a GRUB menu entry script that holds all neccessary parameters (kernel parameters as well as specific Clonezilla boot parameters) to start a Clonezilla live instance from an ISO file. The ISO file you have to download in advance.

Updating the GRUB Configuration File grub.cfg

With the header and menu scripts in place the next step is to update the grub.cfg accordingly. This is done internally with the update-grub command. Finally, when used in conjunction with LVM, this script will ask you if it should comment out all occurences of the save_env command in grub.cfg. This is neccessary for LVM usage because, as also mentioned earlier, GRUB can’t write anything on LVM partitions and the save_env command will exactly do such a thing what will lead you to an error while booting that says:

Diskfilter writes are not supported

So, now it would be time to boot reboot. You might check if the grub.cfg was altered as intended (commented save_env command, header and menu entry in place).

Once Clonezilla booted you can SSH into it with the credentials user:user password:user. This is not the default one, the default password is ‘live’. But for security reasons I wanted to provide you with a hint how to alter the default password (see usercrypted in the script).

Cheers.

#!/bin/bash

# Test for sufficient priviliges
if !  [ -w "/boot/grub/grub.cfg" ]
then
        printf "[\e[31m-\e[0m] Please run this script as a privileged user (i.e. with the help of sudo)\n"
        exit 1
fi

GRUBFILE_HEADER=/etc/grub.d/01_clonezilla_header
GRUBFILE_MENU=/etc/grub.d/30_clonezilla

echo -e "Preparing the environment for a temporary boot into Clonezilla"

####################################################################################################
# Preparing GRUB HEADER
####################################################################################################

SWDATETIME=`date -u +"%Y-%m-%d %H:%M:%S"`
HWDATETIME=`TZ=UTC hwclock | cut -d "." -f1`
echo -e "Just informative:"
echo -e ">> The actual software clock date and time is (UTC): $SWDATETIME"
echo -e ">> The actual hardware clock date and time is (UTC): $HWDATETIME"
echo -e "The hardware time is going to be used for the operation because GRUB refers to that one."

ENDDATETIME=$(date -d "$HWDATETIME 5 minutes" +"%Y-%m-%d %H:%M:%S")

# Parse the output into variables
YEAR=$(echo "$ENDDATETIME" | awk '{print $1}' | cut -d "-" -f1)
MONTH=$(echo "$ENDDATETIME" | awk '{print $1}' | cut -d "-" -f2)
DAY=$(echo "$ENDDATETIME" | awk '{print $1}' | cut -d "-" -f3)
HOUR=$(echo "$ENDDATETIME" | awk '{print $2}' | cut -d ":" -f1)
MINUTE=$(echo "$ENDDATETIME" | awk '{print $2}' | cut -d ":" -f2)
SECOND=$(echo "$ENDDATETIME" | awk '{print $2}' | cut -d ":" -f3)

cat << EOF > $GRUBFILE_HEADER
#!/bin/sh
exec tail -n +3 \$0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
insmod datehook

if [ \$MONTH -lt 10 ]; then PADMONTH="0"; else PADMONTH=""; fi
if [ \$DAY -lt 10 ]; then PADDAY="0"; else PADDAY=""; fi

if [ \$HOUR -lt 10 ]; then PADHOUR="0"; else PADHOUR=""; fi
if [ \$MINUTE -lt 10 ]; then PADMINUTE="0"; else PADMINUTE=""; fi
if [ \$SECOND -lt 10 ]; then PADSECOND="0"; else PADSECOND=""; fi

ACTDATE=\$YEAR\$PADMONTH\$MONTH\$PADDAY\$DAY
ACTTIME=\$HOUR\$PADMINUTE\$MINUTE\$PADSECOND\$SECOND

ENDDATE=$YEAR$MONTH$DAY
ENDTIME=$HOUR$MINUTE$SECOND

echo "Clonezilla time based boot decision:"
echo "- Time now: \$ACTDATE-\$PADHOUR\$ACTTIME"
echo "- Time max: \$ENDDATE-\$ENDTIME"

if [ \$ENDDATE -ge \$ACTDATE -a \$ENDTIME -ge \$ACTTIME ]; then
    echo "--> Setting Clonezilla as boot entry"
    set default="Clonezilla"
    set next_entry=
else
    echo "--> Don't set Clonezolla as boot entry"
fi

sleep --verbose 5
EOF

chmod +x $GRUBFILE_HEADER

####################################################################################################
# Preparing GRUB MENU
####################################################################################################

ISOFILE="/boot/clonezilla.iso"
KERNEL_PARAMS="\
boot=live \
live-media=/dev/mapper/pve-root \
findiso=$ISOFILE \
username=user \
usercrypted='\$y\$j9T\$YBiChb.DicuF4zqZVrXyb/\$3aXx6w.C919TAzlZ2t8NIKYau6E7TFJXJlYfNrb4IP3' \
toram=filesystem.squashfs \
ip=192.168.xxx.xxx::192.168.xxx.1:255.255.255.0::enp0s3 \
config components quiet noswap edd=on nomodeset keyboard-layouts=de \
"
CLONEZILLA_PARAMS="\
ocs_live_run=\"ocs-live-general\" ocs_live_extra_param=\"\" ocs_live_batch=\"no\" \
ocs_daemonon=\"ssh\" \
"
cat << EOF > $GRUBFILE_MENU
#!/bin/sh
exec tail -n +3 \$0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
menuentry "Clonezilla" {
        insmod lvm
        insmod ext2
        loopback loop $ISOFILE
        linux (loop)/live/vmlinuz $KERNEL_PARAMS $CLONEZILLA_PARAMS
        initrd (loop)/live/initrd.img
}
EOF

chmod +x $GRUBFILE_MENU

####################################################################################################
# Committing the changes for temporary usage
####################################################################################################

echo -e "Updating GRUB's config (/boot/grub/grub.cfg)..."
update-grub > /dev/null

read -p "Disable save_env command in grub.cfg (important on LVM etc.)? [Y/N]" -n 1 -r DISABLE_SAVE_ENV
echo    # (optional) move to a new line
if [[ $DISABLE_SAVE_ENV =~ ^[Yy]$ ]]
then
    sed -i 's/save_env/#save_env/g' /boot/grub/grub.cfg
fi

rm $GRUBFILE_HEADER
rm $GRUBFILE_MENU

echo -e "All done, ready to boot into Clonezilla until $YEAR-$MONTH-$DAY $HOUR:$MINUTE:$SECOND"

Leave a Comment