I searched quite a while for a solution to backup parts of my Truenas storage contents to external USB drives.
I need external USB drives to be able to carry them around (offsite storage)
I wanted the possibility to hot-swap them to easily exchange a pool of USB drives (currently I use 3 different drives)
I just want to exchange the drives without changing anything in the config or initiate anything in the webui
The backup should run on a weekly cycle (Disk 1 Tue, Disk 2 Thu, Disk 3 Sun)
As I couldn’t find one I wrote a script myself and after having it run some time I thought it might be useful for the one or another
The script backups the Truenas config and any number of folders you specify to the USB drives.
If you have any suggestions on how to make it more useful or improve it, do let me know.
You need:
at least one external USB drive
be able format the drive in ext4 file format (issues with windows?!)
basic knowledge on how to use the command line and truenas webui
Caveat:
I first saved the script in /mnt/ but it apperently got deleted after a reboot/powerloss so I placed it in home. So now root is executing a script from the truenas_admin home folder! Maybe someone has a better solution or another folder that might be more suitable.
Steps to set up the backup script:
Format your external USB Drive(s) with ext4 file system.
Linux: I used Gparted.
Windows: You need an application for this.
After formatting your USB drive(s) get the UUID(s) by entering in your console and copy it:
Linux: sudo blkid -sUUID
Windows: I don’t know how to do it there maybe someone can help. I tried the below but it gave me another UUID compared to Linux
Type “diskpart” and press Enter. Next, type list disk to see a list of connected disks. Identify your USB drive by its disk number. Finally, type “select disk ” (replace with the actual disk number). Then enter “UNIQUEID DISK”
Copy my script to your local file editor and edit the “SOURCEFOLDERS=” line.
My setup: SOURCEFOLDERS=(“/mnt/Data/Dokumente” “/mnt/Data/Bilder” “/mnt/Data/Backup” “/mnt/Data/Syncthing”)
You can just edit/remove/add those folders as you need them to be backuped
The exact folders can be found in your Truenas webui under “Datasets” - click a dataset and then look at the “Dataset Details” section. There should be written “Path: …” (in my case one is “Path: Data/Dokumente”). You can copy it by clicking on the “Copy to Clipboard”-icon right next to it. Make sure the “/mnt/” path is not changed (in my case it would be “/mnt/Data/Dokumente”).
When you have inserted/edited your folders, access the Truenas Webui and go to “System” - “Services” and start the “SSH” service by clicking in the “Running” coloumn.
Go to your operating system console and login truenas via the ssh access:
ssh truenas_admin@192.168.178.13
Enter truenas_admin password
You will be in the /home/truenas_admin/ folder by default
Backspace might not work in the console so enter the commands carefully!
Create a script by using the following command (open with file editor Vim):
vim ./rsync_backup.sh
Wait till Vim opened, then
press “i” for insert mode
copy the code of the script into the file (copy the edited code from your local file editor into the clipboard then insert it with Ctrl+Shift+v into Vim)
press ESC for normal mode
enter “:”
enter “wq!”
make the script executable by typing:
sudo chmod +x ./rsync_backup.sh
Go back to your Truenas webui and go to “System” - “Advanced Settings” - “Cron Jobs” - click “Add”
Enter the following information:
Description: Rsync to usb disk 1
Command: bash /home/truenas_admin/rsync_backup.sh 505382a2-2768-4e59-be75-68abfd162797
(For the command replace the UUID that you have extracted from your external USB Drive at step 2.)
Run As User: root
Schedule: select as needed
Options: Tick the two boxes “Hide Standard Output” and “Enabled”
Click “Save”
Now you can test run your Backup job by clicking the “Run Job” Button next to your newly created Cron Job.
Repeat step 8 for any additional disk you would like to add to the backup cycle.
My Backup Script:
#!/bin/bash
inputUUID="$1"
#Configuration
LOGDATE=$(date +"%Y%m%d-%H%M%S")
LOGNAME=$LOGDATE"_"$inputUUID
LOGFOLDER="/mnt/Data/Dokumente/backup_logs"
SOURCEFOLDERS=("/mnt/Data/Dokumente" "/mnt/Data/Bilder" "/mnt/Data/Backup" "/mnt/Data/Syncthing")
DESTFOLDER="/mnt/backupdisk"
isUuidMounted() { findmnt --source UUID="$1" >/dev/null;} #UUID only
mkdir -p /mnt/backupdisk
mount UUID=$inputUUID /mnt/backupdisk
mkdir -p $LOGFOLDER
LOGFILE=$LOGFOLDER"/"$LOGNAME".log"
exec 3>&1 1>"$LOGFILE" 2>&1
echo "Start: "$(date -R)
echo "UUID: "$inputUUID
echo "#############################################################"
echo
if isUuidMounted $inputUUID;
then
echo "Disk is mounted"
echo
echo "#############################################################"
echo "Truenas Configuration Backup"
echo "#############################################################"
echo
mkdir -p $DESTFOLDER"/truenas_config"
rsync -rltgoDv /var/db/system/configs-*/ $DESTFOLDER"/truenas_config"
for FOLDER in "${SOURCEFOLDERS[@]}"
do
echo
echo "#############################################################"
echo "Sourcefolder: " $FOLDER
echo "#############################################################"
echo
mkdir -p $FOLDER
rsync -rltgoDv $FOLDER $DESTFOLDER
done
else
echo "Disk is not mounted"
fi
umount /mnt/backupdisk
echo
echo "#############################################################"
echo "End: "$(date -R)
echo "#############################################################"
Tolle Idee, möglicherweise geht es viel einfacher. Rsync job einrichten, fixen mountpoint als destination z.B. /mnt/backupdisk hinterlegen, fertig.
Zuvor habe ich für Umbrel+Start9 was ähnliches gebastelt. Die blockchain sollte rotierend auf verschiedene Disks gesichert werden. Hat gut funktioniert. Nur eine Routine für die Autoerkennung der Disks habe ich nie fertiggestellt (Zeile 19-32).
Hier mein (sub)script…
#!/bin/bash
# backup blockchain to USB disk
set -e
echo "-------------------------------------------------------------------------------"
echo "Backup blockchain to external drive"
if [ ! $bakservice ]; then
echo "[ ! ] Attention: Script direct started, the services are RUNNING and files are OPEN!"
sleep 5
$bakservice=bitcoin
else
start-cli backup target mount disk-/dev/sdb1 "*umbrel*password*here*" --package-ids ${bakservice} >> /root/backup_blockchain.log 2>&1
# here loop
echo "Searching a valid disk... ";
# white
#destuuid=1f40def2-b068-4762-94bc-c3832ae0bd2f
#destlabel=usb2000
#rsyncopts="-avihH --fsync --mkpath --delete"
# wd red
#destuuid=11bd8580-ba11-4101-8aeb-509334377032
#destlabel=hdd1000
#rsyncopts="-avihH --fsync --mkpath --delete"
# ventoy black, exfat, need sudo -i
destuuid=7F09-E574
destlabel=usb1000
rsyncopts="-vrltD --delete"
#srcdir="/data/blockchain/${bakservice}" # umbrel
srcdir="/embassy-data/package-data/volumes/${bakservice}/data/main" # start9
destdev=/dev/disk/by-uuid/$destuuid
if [ -L "$destdev" ]; then
echo "found attached $destlabel."
destmnt=/mnt/$destlabel
if [ ! -f "$destmnt/blockchain/blockchain.dummy" ]; then
echo "Mounting..."
mkdir -p $destmnt
mount -o noatime $(readlink -f $destdev) $destmnt || exit 1
fi
else
# here switch to next disk
echo "[ ! ] Don't see a valid disk. Exit."
echo "-------------------------------------------------------------------------------"
exit 1
fi
if [ ! -f "$destmnt/blockchain/blockchain.dummy" ]; then
echo "Mount disk $destlabel..."
mount -o noatime $destdev $destmnt || exit 1
fi
destdir=$destmnt/blockchain/${bakservice}
if [ -f "$destdir/../blockchain.dummy" ] && [ -w "$destdir/" ]; then
# loop recheck, is mounted and writable
status_rsync=1
while [ $status_rsync -ne 0 ]; do
sync
if [ ${bakservice} == "bitcoind" ]; then
destdir="$destmnt/blockchain/bitcoin" # simple replacement to cut trailing "d"
elif [ ${bakservice} == "monerod" ]; then
destdir="$destmnt/blockchain/monero-prune" # pruned service
else
destdir="$destmnt/blockchain/${bakservice}"
fi
echo "Backup job: ${bakservice}"
ionice -c 2 \
rsync \
$rsyncopts --progress --stats \
$srcdir/ \
$destdir/
status_rsync=$?
echo "Job status: $status_rsync"
done
sync
echo "Unmounting Disk $destlabel..."
umount $destmnt
echo "End."
echo "-------------------------------------------------------------------------------"
else
echo "[ ! ] Error accessing the disk $destlabel."
echo "Try 'sudo chown -R 1000:1000 $destdir' and rerun the script. Exit."
echo "-------------------------------------------------------------------------------"
fi
fi
return
Great idea, maybe it’s much easier. Set up Rsync job, fix mountpoint as destination e.g. deposit /mnt/usbdisk, finish.
Previously, I tinkered for Umbrel+Start9 something similar. The blockchain should be ensured rotating on different discs. Has worked well. Only a routine for the car detection of the discs I never finished (line 19-32)
@mratix Thanks for the ideas from your script. I just created the possibility to use all attached ext4 fromated drives for backup and cycle through them one by one every time the script gets executed.
@ davistw Vielen Dank fĂĽr die Ăśbersetzung
@Arwen Thanks for the hint, I have seen your post before but didn’t see your script. Defenetly I will copy some things around your error handling and documentation style
Maybe I put a few more things in and upload the new version soon. Happy to get your advice!
i have implemented the functionality to autodetect any ext4 (or any other filesystem spedified) connected disks. You can either still specify an UUID to back up to or let the script select any ext4 disk. It will automatically cycle through the connected disks by saving the last used UUID into the $DATAFILE.
I also moved my script location to a seperate dataset called Scripts as I read that the filesystem of Truenas is not a safe location as it might be deleted on an update.
I might have overengineered the saving functionality of the last UUID but had fun while doing it and either on future amendments it might be useful or other scripts I will work on in the future.
Thanks again for your previous inputs and scripts to copy. @Arwen I copied your script documentation format
I might still implement the error codes or any bugfixes. For now this is already more then sufficient for me
Feel free to use any parts of the script for yourself!
I will also edit the first post, once I have the rights to do so. Could anyone help me with this please?
#!/bin/bash
#ident "TrueNAS backup to USB disks 0.02 2025/08/03 Matthias Geffert"
#
# Script name:
# rsync_backup.sh
#
# Description:
# Perform the following:
# - Create a log file
# - Either backups to a specified UUID given as a parameter ./rsync_backup.sh
# e.g. ./rsync_backup.sh e9624b97-f9f1-4048-af11-a91b7dc85d0c
# Or backups to any connected disk with the defined filesystem in configuration
# section and changes the disk each time the script runs
#
# Usage:
# e.g. ./rsync_backup.sh
# e.g. ./rsync_backup.sh e9624b97-f9f1-4048-af11-a91b7dc85d0c
#
# Author info:
# Matthias Geffert
# (C) Copyright Matthias Geffert
#
# Permission and fees:
# You have the right to run this script without any warranty. If you
# find it useful, let me know. If it breaks something, I am not
# responsible. If you modify this script, do not alter my credits,
# add your own.
#
# Notes:
# It is assumed that the backup will fit on the destination. Thus,
# checking log file after backup is helpful to detect errors.
#
# History:
# 2025/07/31 - Matthias Geffert - First version of script
# 2025/08/04 - Matthias Geffert - Added automatic UUID selction and
# read/write functionality to save last used UUID
#
# To be added:
# - creation of exit codes
#
##############################################################################
# debug information for UUID selection
#exec 3>&1 1>"/mnt/Data/Scripts/test.log" 2>&1
##############
#Configuration
##############
DATAFILE="/mnt/Data/Scripts/rsync_backup_data.txt"
LOGFOLDER="/mnt/Data/Dokumente/backup_logs"
SOURCEFOLDERS=("/mnt/Data/Dokumente" "/mnt/Data/Bilder" "/mnt/Data/Backup" "/mnt/Data/Syncthing" "mnt/Data/Scripts")
DESTFOLDER="/mnt/backupdisk"
FILESYSTEMTYPE="ext4"
##############
#Functions
##############
#check if UUID disk is mounted
isUuidMounted() { findmnt --source UUID="$CURUUID" >/dev/null;} #UUID only
#Read data from $DATAFILE
readData() {
SEARCHCONTENT=$1
if test -f $DATAFILE; then
#Read file content into arrays
mapfile -t FILECONTENTNAMES < <( cat $DATAFILE | cut -d'=' -f1)
mapfile -t FILECONTENTVALUES < <( cat $DATAFILE | cut -d'=' -f2)
#Search for Parameter
COUNTCONTENT=${#FILECONTENTNAMES[@]}
for (( i=0; i<$COUNTCONTENT; i++ ));
do
if [ "${FILECONTENTNAMES[$i]}" = "$SEARCHCONTENT" ]; then
#Return Parameter value
echo ${FILECONTENTVALUES[$i]}
fi
done
else
echo "File not found"
fi
}
#Save data to $DATAFILE
saveData() {
PARAMETER=$1
PARAMETERCONTENT=$2
if test -f $DATAFILE; then
#Read file content into arrays
mapfile -t FILECONTENTNAMES < <( cat $DATAFILE | cut -d'=' -f1)
mapfile -t FILECONTENTVALUES < <( cat $DATAFILE | cut -d'=' -f2)
#Search for parameter to write
COUNTCONTENT=${#FILECONTENTNAMES[@]}
for (( i=0; i<$COUNTCONTENT; i++ ));
do
if [ "${FILECONTENTNAMES[$i]}" = "$PARAMETER" ]; then
FILECONTENTVALUES[$i]=$PARAMETERCONTENT
FOUNDPARAMETER=1
fi
done
#write parameter
COUNTCONTENT=${#FILECONTENTNAMES[@]}
WRITEARRAY=
for (( i=0; i<$COUNTCONTENT; i++ ));
do
WRITEARRAY+=${FILECONTENTNAMES[$i]}"="${FILECONTENTVALUES[$i]}"\n"
done
#if parameter has not been found
if [[ ! $FOUNDPARAMETER ]]; then
WRITEARRAY+=$PARAMETER"="$PARAMETERCONTENT"\n"
fi
#if file has not been found
else
WRITEARRAY+=$PARAMETER"="$PARAMETERCONTENT"\n"
fi
printf ${WRITEARRAY[@]} > $DATAFILE
}
######################
#Start of backupscript
######################
#Check if UUID has been given as parameter, otherwise select one of all connected ext4 disks
if [ "$1" != "" ]; then
CURUUID="$1"
else
mapfile -t ALLUUIDS < <( sudo blkid -tTYPE=$FILESYSTEMTYPE -sUUID | cut -d'"' -f2)
#mapfile -t ALLUUIDS < <( sudo blkid -tTYPE=ext4 -sUUID | cut -d'"' -f2)
CURUUID=""
#check if there was saved a previously backup UUID
if test -f $DATAFILE; then
LASTUUID=$(readData "lastUUID")
echo "LASTUUID:"$LASTUUID
if [ ! $LASTUUID = "" ]; then
#loop through UUIDs to check what is the next UUID
COUNTUUIDS=${#ALLUUIDS[@]}
for (( i=0; i<COUNTUUIDS; i++ ));
do
if [ "${ALLUUIDS[$i]}" = "$LASTUUID" ]; then
CURUUID=${ALLUUIDS[$i + 1]}
fi
done
#if the File for storage of UUID has not been found or UUID was at the end of array use the first UUID
if [ "$CURUUID" = "File not found" ] || [ "$CURUUID" = "" ]; then
CURUUID=${ALLUUIDS[0]}
fi
#if LASTUUID is empty
else
CURUUID=${ALLUUIDS[0]}
fi
#if $DATAFILE does not exist
else
CURUUID=${ALLUUIDS[0]}
fi
#save current used UUID
saveData "lastUUID" $CURUUID
fi
#debug info for UUID selection
echo "array0 :"${ALLUUIDS[0]}
echo "array1 :"${ALLUUIDS[1]}
echo "array2 :"${ALLUUIDS[2]}
echo "array3 :"${ALLUUIDS[3]}
echo "LASTUUID:"$LASTUUID
echo "CURUUID :"$CURUUID
#set Logfilename
LOGDATE=$(date +"%Y%m%d-%H%M%S")
LOGNAME=$LOGDATE"_"$CURUUID
#mount UUID Disk
mkdir -p /mnt/backupdisk
mount UUID=$CURUUID /mnt/backupdisk
#create logfile
mkdir -p $LOGFOLDER
LOGFILE=$LOGFOLDER"/"$LOGNAME".log"
exec 3>&1 1>"$LOGFILE" 2>&1
#Start backup
echo "Start: "$(date -R)
echo "UUID: "$CURUUID
echo "#############################################################"
echo
#check if disk is mounted
if isUuidMounted $CURUUID;
then
echo "Disk is mounted"
echo
echo "#############################################################"
echo "Truenas Configuration Backup"
echo "#############################################################"
echo
mkdir -p $DESTFOLDER"/truenas_config"
rsync -rltgoDv /var/db/system/configs-*/ $DESTFOLDER"/truenas_config"
#do backup of all specified folders
for FOLDER in "${SOURCEFOLDERS[@]}"
do
echo
echo "#############################################################"
echo "Sourcefolder: " $FOLDER
echo "#############################################################"
echo
mkdir -p $FOLDER
rsync -rltgoDv $FOLDER $DESTFOLDER
done
else
echo "Disk is not mounted"
fi
#unmount disk
umount /mnt/backupdisk
echo
echo "#############################################################"
echo "End: "$(date -R)
echo "#############################################################"
exit 0