Automatic Backup to External Disk Whenever Connected
Determine the ID_MODEL_ID=?
and ID_VENDOR_ID=?
of your external drive you want automatic backups to be done in with:
udevadm info -x /sys/class/block/sdb
(replace sdb
with appropriate block device name.)
Now, create a udev .rules file with the following content as /etc/udev/rules.d/99-trigger-backup-to-external-disk.udev.rules
:
SUBSYSTEM!="block", GOTO="label__my_rules_end"
KERNEL!="sd*", GOTO="label__my_rules_end"
ENV{DEVTYPE}!="disk", GOTO="label__my_rules_end"
For my SanDisk 3.2Gen1 64GB flash disk device:
ATTRS{idProduct}=="5590", ATTRS{idVendor}=="0781", GOTO="label__enlisted_devices"
ATTRS{idProduct}!="5590", ATTRS{idVendor}!="0781", GOTO="label__my_rules_end"
LABEL="label__enlisted_devices"
ACTION!="add", GOTO="label__my_rules_end"
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd*", ENV{DEVTYPE}=="disk", ATTRS{idProduct}=="5590", ATTRS{idVendor}=="0781", TAG+="systemd", ENV{SYSTEMD_USER_WANTS}+="backup_to_external_disk@dev-%k.target", GOTO="label__my_rules_end"
LABEL="label__my_rules_end"
In this file replace the integer values for ATTRS{idProduct} and ATTRS{idVendor} appropriately for your device obtained as above with udevadm info ...
command.
Now, create $HOME/.config/systemd/user/
directory with:
mkdir -p "${HOME}/.config/systemd/user/"
Create a file ${HOME}/.config/systemd/user/backup_to_external_disk@.target
and write the following content:
[Unit]
Description=Backup To External Disk - target unit for %I
Documentation=man:systemd.unit(5) man:systemd.exec(5) man:systemd.special(7) man:systemd.target(5)
StopWhenUnneeded=yes
Requires=backup_to_external_disk@%i.service
Before=backup_to_external_disk@%i.service
RefuseManualStart=true
RefuseManualStop=true
Create a file ${HOME}/.config/systemd/user/backup_to_external_disk@.service
and write the following content:
[Unit]
Description=Backup To External Disk
Documentation=man:systemd.unit(5) man:systemd.exec(5) man:systemd.special(7) man:systemd.target(5)
ConditionPathExistsGlob=/sys/class/block/sd*
RefuseManualStart=true
RefuseManualStop=true
[Service]
Type=exec
ExecStart=-/bin/bash -c '/opt/backup_to_external_disk/bin/backup_to_external_disk.sh -d %i --backup'
Now, create the directory /opt/backup_to_external_disk/bin/
and executable file backup_to_external_disk.sh
with:
sudo mkdir -p /opt/backup_to_external_disk/bin
touch /opt/backup_to_external_disk/bin/backup_to_external_disk.sh
chmod +x /opt/backup_to_external_disk/bin/backup_to_external_disk.sh
Now, write the following content to /opt/backup_to_external_disk/bin/backup_to_external_disk.sh
script:
#!/bin/bash
declare __SCRIPT_NAME="${0##*/}"
declare __SCRIPT_NAME_SH="${__SCRIPT_NAME%.sh}"
declare -i __FLAG__GUI_MODE=1 ## 0 = Non-GUI mode i.e. CLI mode; 1 = GUI mode
declare __DIR_TMP="/tmp/.${__SCRIPT_NAME_SH}"
declare __LOG_MESSAGES="${__DIR_TMP}/msg.log"
declare __BLOCK_DEVICE_NAME_SYSTEMDSTYLE="" ## systemd escaped name of block device e.g. "dev-sda"
declare __BLOCK_DEVICE_NAME="" ## name of block device e.g. "sda"
declare __BLOCK_DEVICE_PATH="" ## device path e.g. "/dev/sda"
shopt -s huponexit
_TrapHandle__HUP() {
if [[ -f "${__LOG_MESSAGES}" ]] ; then
_Write_Log "[_TrapHandle__HUP()] : The running terminal was closed, so the script '${__SCRIPT_NAME}' has to end unexpectedly."
fi
}
_TrapHandle__EXIT() {
if [[ -f "${__LOG_MESSAGES}" ]] ; then
_Write_Log "[_TrapHandle__EXIT()] : The script '${__SCRIPT_NAME}' is exiting now."
fi
}
trap _TrapHandle__HUP SIGHUP
trap _TrapHandle__EXIT EXIT
function _Show_Help() {
while read -r ; do
echo "${REPLY}"
done <<-__EOF
Synoptis:
${__SCRIPT_NAME} [OPTION]
OPTIONS:
-d
Systemd-escaped device name of the device to be backuped into.
--backup
Initiate automatic backup from directory '\$HOME/Documents' to the configured
backup external disk in a directory called 'backup/Documents'.
-h, --help
Show this help.
__EOF
}
function Write_Log() {
echo "$( date +%Y-%m-%d%H%M%S ) | ${@}" >> "${__LOG_MESSAGES}"
}
Execution begins:
if [[ ${#} -eq 0 ]] ; then
_Show_Help
exit 1
fi
if [[ ! -d "${__DIR_TMP}" ]] ; then
if ! mkdir -p "${__DIR_TMP}" ; then
echo "[ERROR] Could not create temporary directory. Terminate now." >&2
exit 1
fi
fi
_Write_Log "---------- $(date) ----------"
_Write_Log "Script '${__SCRIPT_NAME}' has already started and is processing."
_Write_Log "Command line arguments: $@"
Parse the command-line arguments:
declare __SCRIPT_ARGS=""
declare -i __SCRIPT_ARG_COUNT=${#}
declare -a __OPERANDS=()
declare __TASK="" ## check and call associated function
Assign a default task:
__TASK="action____show_help"
while [[ ${#} -gt 0 ]]; do
__SCRIPT_ARGS="${1}"
case "${__SCRIPT_ARGS}" in
--backup)
__TASK="action____perform_backup"
shift
;;
-d)
## The systemd escaped name for the block device:
__BLOCK_DEVICE_NAME_SYSTEMDSTYLE="${2}"
## If unescaped path is specified, escape it in anticipation of a
## block device path:
if [[ "${__BLOCK_DEVICE_NAME_SYSTEMDSTYLE}" =~ / ]] ; then
__BLOCK_DEVICE_NAME_SYSTEMDSTYLE=$( systemd-escape --path "${__BLOCK_DEVICE_NAME_SYSTEMDSTYLE}" )
fi
__BLOCK_DEVICE_NAME="${__BLOCK_DEVICE_NAME_SYSTEMDSTYLE##-}"
__BLOCK_DEVICE_PATH="/${__BLOCK_DEVICE_NAME_SYSTEMDSTYLE//-//}"
shift 2 ## shift past $1 and $2 .
;;
-h|--help)
__TASK="action____show_help"
shift
;;
)
__OPERANDS+=( "${1}" ) # save operands in array for later
shift ## past operand parameter
;;
esac
done
Restore saved positional parameters.
if [[ ${#} -ne ${__SCRIPT_ARG_COUNT} ]]; then
set -- "${__OPERANDS[@]}"
fi
Excess operands must not be at the command line:
if [[ ${#} -gt 0 ]]; then
echo "[ERROR] Some operands at the command line are not recognised." >&2
_Write_Log "[ERROR] Some operands at the command line are not recognised."
echo "Exiting now." >&2
exit 1
fi
case "${__TASK}" in
action____show_help)
_Show_Help
exit 0
;;
action____perform_backup)
if [[ -z "${__BLOCK_DEVICE_NAME_SYSTEMDSTYLE}" && ! "${__BLOCK_DEVICE_NAME_SYSTEMDSTYLE}" =~ ^dev-sd[a-z]$ ]] ; then
_Write_Log "[ERROR] Use option -d to specify the systemd instance name for the escaped block device path e.g. dev-sdb."
exit 1
fi
if [[ ! -b "${__BLOCK_DEVICE_PATH}" ]] ; then
_Write_Log "[ERROR] The block device '${__BLOCK_DEVICE_PATH}' does not exist."
exit 1
fi
## Check GUI mode:
if systemctl --quiet is-active graphical.target ; then
__FLAG__GUI_MODE=1
echo "GUI mode is active."
_Write_Log "GUI mode is active."
else
__FLAG__GUI_MODE=0
fi
# echo "The block device '${__BLOCK_DEVICE_PATH}' exists. Do you want to mount it and perform a backup? (Y/N)"
zenity --question --title="The block device '${__BLOCK_DEVICE_PATH}' exists." --text="Do you want to mount it and perform a backup from '${HOME}/Documents/' to 'backups/Documents' directory in the external disk?"
declare -i __ZENITY_REPLY_INT=${?}
if [[ ${__ZENITY_REPLY_INT} -ne 0 ]] ; then
_Write_Log "You chose not to backup the device. Will exit now."
exit 1
fi
_Write_Log "You chose to backup the device. Will backup now."
declare __BLOCK_DEVICE_PARTITION_PATH="${__BLOCK_DEVICE_PATH}1"
declare __MOUNT_POINT=""
__MOUNT_POINT="$( udisksctl mount -b "${__BLOCK_DEVICE_PARTITION_PATH}" )"
declare -i __RETURN_STATUS=${?}
## We need to obtain mount point as:
__MOUNT_POINT="${__MOUNT_POINT#Mounted ${__BLOCK_DEVICE_PARTITION_PATH} at }"
if [[ ${__RETURN_STATUS} -eq 0 ]] ; then
_Write_Log "Successfully mounted ${__BLOCK_DEVICE_PARTITION_PATH} at ${__MOUNT_POINT} ."
else
_Write_Log "Failed to mount ${__BLOCK_DEVICE_PARTITION_PATH} ."
exit 1
fi
declare __BACKUP_DESTINATION="${__MOUNT_POINT}/backups/Documents"
if [[ ! -d "${__BACKUP_DESTINATION}" ]] ; then
if ! mkdir -p "${__BACKUP_DESTINATION}" ; then
_Write_Log "Could not create backup destination '${__BACKUP_DESTINATION}' directory."
exit 1
fi
else
_Write_Log "Backup destination '${__BACKUP_DESTINATION}' exists."
fi
_Write_Log "Using backup command: rsync -a '${HOME}/Documents/' '${__BACKUP_DESTINATION}'"
if [[ -d "${__BACKUP_DESTINATION}" ]] ; then
rsync -a "${HOME}/Documents/" "${__BACKUP_DESTINATION}" | zenity --progress --pulsate --auto-close --title="DO NOT EJECT DEVICE ${__BLOCK_DEVICE_PARTITION_PATH}." --text="Backup is in progress..."
_Write_Log "Backup with rsync completed with status=${PIPESTATUS[0]}."
zenity --question --title "Backup Done :: Unmount Device?" --text="The backup operation using rsync command completed with status=${PIPESTATUS[0]}. Do you want to unmount the device?"
__RETURN_STATUS=${?}
if [[ ${__RETURN_STATUS} -eq 0 ]] ; then
udisksctl unmount -b "${__BLOCK_DEVICE_PARTITION_PATH}"
__RETURN_STATUS=${?}
if [[ ${__RETURN_STATUS} -ne 0 ]] ; then
_Write_Log "Unmounting the device ${__BLOCK_DEVICE_PARTITION_PATH} failed."
zenity --warning --title "Unmount failed!" --text="The udisksctl unmount -b ${__BLOCK_DEVICE_PARTITION_PATH} command failed."
else
_Write_Log "Unmounted the device ${__BLOCK_DEVICE_PARTITION_PATH} successfully."
zenity --info --title "Unmount done." --text="The udisksctl unmount -b ${__BLOCK_DEVICE_PARTITION_PATH} command completed successfully."
fi
fi
else
_Write_Log "The backup destination '${__BACKUP_DESTINATION}' does not exist."
exit 1
fi
;;
esac
exit 0
End of the script.
Now, reboot the system or run the following command:
systemctl --user daemon-reload ; sudo systemctl reload udev
Now, each time you connect the configured device to the system, you will be prompted for a backup.