In my previous post, I described how to install the proxmox backup client and how to create an account within PBS (Proxmox Backup Server) to allow backup and restore operations on a specific PBS datastore. As well as create a manual backup of a TrueNAS Scale datastore. For a once off backup it would suffice but I wanted a regular backup of my TrueNAS datasets onto the PBS, with logging for trouble shooting if the need arises.

In this article I will list my backup script, invocation method and backup workflow.

Script Setup

To use this script, modify the PBS_PASSWORD, PBS_REPOSITORY, LOGDIR,STOREAGEPOOLS and PXAR_NAMES environment variables to supply your PBS account, password, server and datastore information.

STORAGEPOOLS is an array of the TrueNAS storage pools and datasets that you with to backup.

PXAR_NAMES is an array of file names for the pxar archive files to be created on the PBS server. Note that the pxar file names must be in the same order as the storage pools in the STORAGEPOOLS array.

Finally, the LOGDIR variable contains the location where you want to store the backup logs. In my case I have a dataset called backup, which hosts this script and the backup logs within a Logs subdirectory. This dataset is also backed up onto the PBS.

Requirements

This script will create a temporary ZFS snapshot of the dataset called PreBackupSnapshot and backup the contents of this snapshot. Once the backup has completed, it will delete the PreBackupSnapshot snapshot. The script expects the snapshot directory to be visible, creating a .zfs folder within the parent folder of the dataset. Ensure that the snapdir property is set to visible for all datasets you wish to backup. You can view the status of this feature by opening a TrueNAS shell and using zfs get to show the snapdir property. This example will show the value of the snapdir property of the backup dataset within the pool StoragePool.

sudo zfs get snapdir StoragePool/backup

The StoragePool/backup dataset snapdir property is set to hidden.

NAME                PROPERTY  VALUE    SOURCE
StoragePool/backup  snapdir   hidden  local

Lets change that.

sudo zfs set snapdir=visible StoragePool/backup

Now using ls -a /mnt/StoragePool/backup will show a .zfs folder. Inside the .zfs/snapshot folder all the snapshots for this dataset will be available.

admin@nas[~]$ ls -al /mnt/StoragePool/backup 
total 62
drwxr-xr-x 4 root root     7 Mar  1 10:50 .
drwxr-xr-x 9 root root    10 Feb 28 17:28 ..
drwxrwxrwx 1 root root     0 Feb 28 09:17 .zfs
drwxrwxr-x 2 root root     2 Mar  1 10:44 Logs
-rwxr-xr-x 1 root root 13541 Feb 26 23:29 backup_TestShare.sh
-rwxr-xr-x 1 root root 13471 Feb 26 23:06 backup_vm.sh
-rwxr-xr-x 1 root root  3834 Mar  1 10:49 pbs-backup.sh
-rwxr-xr-x 1 root root  3689 Mar  1 10:45 test.sh

Backup Methodology

Within TrueNAS I have scheduled daily snapshots of my datasets with a two week retention. I then setup a cron job (under System Settings/System) within TrueNAS to run my backup script daily. This gives me a quick method to restore backups within the snapshot retention policy as well as a second long term backup, that can then be replicated to a cloud provider or second PBS server. The PBS server has a retention policy set to two months which does the clean up of old backups as well as the de-duplication of all the backups.

The Script

I don’t claim to be an expert coder but this script serves me well. Hopefully it will serve you just as well.

#!/bin/bash

### TrueNAS-PBS: Backup TrueNAS data sets into Proxmox Backup Server (PBS)
### Written by Chris MacKenzie, 2023
###
### Requires proxmox-backup-client installed on TrueNAS host and an account created in PBS with appropriate permissions.
###
### TODO: Add Log cleanup function with retention policy.

export PBS_PASSWORD=truenas
export PBS_REPOSITORY=truenas@pbs@192.168.0.150:Mirror8

SCRIPTNAME=`basename -s .sh ${0}` # Name of this script minus path and the .sh extension
CUR_DATE=`date +%Y-%m-%d-%H-%M-%S`
LOGDIR=/mnt/StoragePool/backup/Logs # Where to store the logs
LOGNAME="$LOGDIR/$SCRIPTNAME-${CUR_DATE}.log"

STORAGEPOOLS=("StoragePool/backup" "StoragePool/FileStore/NAS-Data" "StoragePool/FileStore/NAS-Media" "StoragePool/FileStore/NAS-Home")
PXAR_NAMES=("backup.pxar" "nas-data.pxar" "nas-media.pxar" "nas-home.pxar") # pxar archive file names for pbs server (in same order as dataset array)

STORAGEPOOL_LENGTH=${#STORAGEPOOLS[@]} # Number of array elements
TEMP_SNAPSHOTNAME="PreBackupSnapshot"
SPEC="" # Specification variable for proxmox-backup-client

MY_DEBUG=0 # Set to 1 to do everything except the calling of proxmox-backup-client, essentially a dry-run


#### Declare Functions
WriteLog () { # Writes to both log and stdout
	echo $1
	echo $1 >> ${LOGNAME}
}

CreateSnapshot () {
	TEMP_SNAPSHOT="$STORAGEPOOL@$TEMP_SNAPSHOTNAME"

	WriteLog " - Creating Snapshot -> $TEMP_SNAPSHOT"
	zfs snapshot $TEMP_SNAPSHOT 2>/dev/nul # Redirect STDERROR to /dev/nul
	EXITCODE=$?
	if [ $EXITCODE != 0 ]; then
        	WriteLog " !! Cannot create snapshot (error $EXITCODE)"
	fi
	return $EXITCODE
}

DeleteSnapshot () {
        TEMP_SNAPSHOT="$STORAGEPOOL@$TEMP_SNAPSHOTNAME"

        WriteLog " - deleting Snapshot -> $TEMP_SNAPSHOT"
        zfs destroy $TEMP_SNAPSHOT 2>/dev/nul # Redirect STDERROR to /dev/nul
        EXITCODE=$?
        if [ $EXITCODE != 0 ]; then
                WriteLog " !! Cannot delete snapshot (error $EXITCODE)"
        fi
        return $EXITCODE
}

CleanUp () {
	WriteLog "Performing CleanUp()"
	for STORAGEPOOL in "${STORAGEPOOLS[@]}"
	do
		DeleteSnapshot $STORAGEPOOL
		EXITCODE=$?
        	if [ $EXITCODE != 0 ]; then
                	WriteLog " !! Error performing CleanUp ($EXITCODE)."
                	WriteLog
                	exit 255
        	fi
	done
}
#### End of Functions

## Create log file and log directory if it does not exist.
ls $LOGDIR >/dev/nul 2>/dev/nul
if [ "$?" != "0" ]; then
	mkdir -m 775 -p $LOGDIR
	if [ "$?" != "0" ]; then
		echo " !! Cannot create log directory $LOGDIR, Aborting."
		exit 255
	fi
fi

touch ${LOGNAME}
if [ "$?" != "0" ]; then
	echo " !! Cannot create logfile $LOGNAME"
	exit 255
fi

## Do Preperations (Create snapshots and build SPEC variable)
WriteLog "Doing Preperations, Please Wait."
for ((i=0; i<STORAGEPOOL_LENGTH; i++))
do
	SPEC="$SPEC ${PXAR_NAMES[$i]}:/mnt/${STORAGEPOOLS[$i]}/.zfs/snapshot/$TEMP_SNAPSHOTNAME/"
        STORAGEPOOL="${STORAGEPOOLS[$i]}"
        CreateSnapshot $STORAGEPOOL
        EXITCODE=$?
        if [ $EXITCODE != 0 ]; then
        	WriteLog " !! Cannot create snapshot (error $EXITCODE), exiting."
                WriteLog
                CleanUp # Attempt Cleanup
                WriteLog
                WriteLog "Backup Aborted."
                exit 255
	fi
done

### Run Backup
WriteLog
WriteLog "SPEC = $SPEC"

if [ $MY_DEBUG -eq 1 ]; then
	WriteLog "Debug Detected."
	CleanUp
	WriteLog
	WriteLog "Debug Exit."
	WriteLog
	exit 0
fi

WriteLog
WriteLog "Starting Backup....."
WriteLog

proxmox-backup-client backup $SPEC >> $LOGNAME 2>&1
EXITCODE=$?
if [ $EXITCODE != 0 ]; then
        WriteLog "proxmox-backup-client failed (error $EXITCODE), aborting."
	CleanUp
        exit 255
fi

WriteLog

CleanUp
EXITCODE=$?
if [ $EXITCODE != 0 ]; then
	WriteLog " !! Error performing CleanUp."
	exit 255
fi

WriteLog
WriteLog "Backup completed successfully."
exit 0