A while ago I bought myself a NAS. It had a neat function called “one touch backup” but I was missing some more sophisticated features, so I implemented my own one.
The following script detects new partitions (it ignores the ones which were connected when the script was started), mounts them, creates a new folder with a time stamp for the new backup and copies the content to the backup folder. Beeper script is used to get response from the device so no monitor or any other interface is needed.
When a flash disk is connected a folder will be created for each partition on that flash disk and the content will be copied. A sound will be played when the flash is connected. After the copying job is finished a sound will be played until the flash is removed. A successful removal is also announced by a different sound.
In case more flash disks are connected at once, first will be backed up. After finish and removal second will be backed up and so on. This works fine when a flash disk is being backup up and a second is inserted into the device. But if a third one is inserted before the first one finishes, there is no guarantee which one will be backed up next.
#!/bin/bash
LOG_FILE="/var/log/${APP_NAME}.log"
APP_NAME="ot-backup"
BACKUP_PATH="/mnt/backup/xerocomus"
BACKUP_DIR="one-touch-backup"
PID_FILE="/var/run/${APP_NAME}.pid"
MOUNT_POINT="/mnt/ot-backup"
TMP_PATH="/tmp/${APP_NAME}"
MAX_ATTEMPTS=10
function checkLogFile() {
if [ ! -f ${LOG_FILE} ]; then
touch ${LOG_FILE}
fi
}
function debug() {
MESSAGE=$1
logger -is -f ${LOG_FILE} -p daemon.debug -t "${APP_NAME}" "${MESSAGE}"
}
function info() {
MESSAGE=$1
logger -is -f ${LOG_FILE} -p daemon.info -t "${APP_NAME}" "${MESSAGE}"
}
function error() {
MESSAGE=$1
logger -is -f ${LOG_FILE} -p daemon.error -t "${APP_NAME}" "${MESSAGE}"
/usr/local/bin/beeper ERROR
exit 1
}
function checkPath(){
if [ ! -d ${BACKUP_PATH} ]; then
mount -a
fi
if [ -d ${BACKUP_PATH} ]; then
if [ ! -d ${BACKUP_PATH}/${BACKUP_DIR} ]; then
mkdir -p ${BACKUP_PATH}/${BACKUP_DIR}
fi
if [ ! -d ${BACKUP_PATH}/${BACKUP_DIR} ]; then
error "Backup directory ${BACKUP_PATH} does not exist and could not be created."
fi
else
error "Backup directory ${BACKUP_PATH} does not exist"
fi
}
function checkPid() {
if [ -f ${PID_FILE} ]; then
PID=`cat ${PID_FILE}`
if [ -n "`ps ax | cut -d" " -f1 | grep ${PID}`" ]; then
error "Another $0 is still running with PID ${PID}"
else
rm -f ${PID_FILE}
fi
fi
PID=$$
echo ${PID} > ${PID_FILE}
}
function mount_usb(){
DEVICE=$1
LABEL=$2
if [ -n "$3" ]; then
ATTEMPT=$3
else
ATTEMPT=1
fi
if [ "${ATTEMPT}" -gt "${MAX_ATTEMPTS}" ]; then
error "Mount of ${DEVICE} failed, attempts: ${MAX_ATTEMPTS}"
while [[ -n `ls /dev/${DEVICE} 2> /dev/null` ]]; do
/usr/local/bin/beeper ERROR
done
fi
if [ -n "`mount | grep ${MOUNT_POINT}`" ]; then
info "${MOUNT_POINT} mounted."
else
debug "Trying to mount /dev/${DEVICE} (${LABEL})... attempt ${ATTEMPT}"
mount /dev/${DEVICE} ${MOUNT_POINT}
sleep 1;
mount_usb ${DEVICE} ${LABEL} `expr ${ATTEMPT} + 1`
fi
}
function unmount_usb() {
if [ -n "$1" ]; then
ATTEMPT=$1
else
ATTEMPT=1
fi
if [ ! -d ${MOUNT_POINT} ]; then
mkdir -p ${MOUNT_POINT}
fi
if [ "${ATTEMPT}" -gt "${MAX_ATTEMPTS}" ]; then
DEVICE=`mount | grep ${MOUNT_POINT} | cut -d" " -f1`
error "Unmount of ${DEVICE} failed, attempts: ${MAX_ATTEMPTS}"
fi
if [ -n "`mount | grep ${MOUNT_POINT}`" ]; then
debug "Trying to unmount ${MOUNT_POINT}... attempt ${ATTEMPT}"
umount ${MOUNT_POINT}
sleep 1
unmount_usb `expr ${ATTEMPT} + 1`
else
info "${MOUNT_POINT} unmounted."
fi
}
function backup(){
LABEL=$1
TIMESTAMP=`date +%Y-%m-%d-%H-%M-%S`
SRC="${MOUNT_POINT}/*"
DEST="${BACKUP_PATH}/${BACKUP_DIR}/${LABEL}/${TIMESTAMP}/"
info "Backup of ${LABEL} into ${DEST} started."
mkdir -p ${DEST}
if [ ! -d ${DEST} ]; then
error "Could not create backup destination directory: ${DEST}"
fi
cp -a ${SRC} ${DEST}
info "Backup of ${LABEL} ended."
}
checkLogFile
checkPid
unmount_usb
checkPath
ignored=""
for device_path in `ls /dev/sd*`; do
device=`echo ${device_path} | cut -d"/" -f3`
if [[ $device =~ ^sd[a-z][0-9]$ ]]; then
if [[ $ignored != "" ]]; then ignored="${ignored}\n"; fi
ignored="${ignored}${device}"
fi
done
debug "Ignoring: $ignored"
# Loop
while true; do
for device_path in `ls /dev/sd*`; do
device=`echo ${device_path} | cut -d"/" -f3`
if [[ $device =~ ^sd[a-z][0-9]$ ]]; then
# Check if not ignored
ignore=0
for ignored_device in `echo -e ${ignored}`; do
if [[ $ignored_device == $device ]]; then
ignore=1
break
fi
done
# If not ignored and partition
if [[ $ignore == 0 ]]; then
disk=${device:0:3}
debug "Detected disk ${disk}"
sleep 1
for part_path in `ls /dev/${disk}*`; do
part=`echo ${part_path} | cut -d"/" -f3`
if [[ $part =~ ^${disk}[0-9]$ ]]; then
debug "Detected partition $part"
# Detect label
if [[ -n `ls -l /dev/disk/by-label | grep "../../${part}"` ]]; then
label=`ls -l /dev/disk/by-label | grep "../../${part}" | awk '{print $9}'`
elif [[ -n `ls -l /dev/disk/by-id | grep "../../${part}"` ]]; then
label=`ls -l /dev/disk/by-id | grep "../../${part}" | awk '{print $9}'`
else
label=$part
fi
debug "Mounting $part ($label)"
mount_usb ${part} ${label}
if [ -n "`mount | grep ${MOUNT_POINT}`" ]; then
/usr/local/bin/beeper CONNECT
backup ${label}
unmount_usb
/usr/local/bin/beeper DISCONNECT
fi
fi
done
while [ -n "`ls /dev/${device} 2> /dev/null`" ]; do
/usr/local/bin/beeper FINISHED
done
/usr/local/bin/beeper DISCONNECT
fi
fi
done
sleep 1
done