#!/bin/bash ################################################################################ # Sync shares from one location to another. # # For backing up shares (usual action), use `sync-shares backup {OPTIONS}` # To restore from a backup, use `sync-shares restore {OPTIONS}` # To see options, use `sync-shares --help` ################################################################################ set -e # Uncomment to show all commands as they run (used to debug the script). # set -x # The root directory where everything is stored in. # The specific file is determined after a mode is specified. LOG_ROOT="~jason/logs" # The external drive EXTERN_ROOT='/mnt/external' # The root of BTRFS shares to sync to/from SHARES_ROOT='/mnt2' # The external drive to mount. EXTERN_DEV=`ls /dev/sd* | sort -r | head -n1` CHECKSUM="" DRY_RUN="" DELETE="" VERBOSE="" UNMOUNT="" LARGE="" FULL_ARGS="$@" USAGE="sudo $0 (backup|restore) [--help] []" DESC=`cat << EOT Modes: backup: Backup shares from $SHARES_ROOT to the external drive at $EXTERN_ROOT restore: Restore files in directories in $EXTERN_ROOT to $SHARES_ROOT Options: --help: Show this message. --checksum: Compare duplicate files using checksums, rather than date/size. --dry-run: Show what would be transferred, without actually copying anything. --delete: Remove files from the destination that no longer exist in the source. eg: When running 'backup' any files deleted from a share since the last run will be deleted from the backup. --mount: Mount ${EXTERN_DEV?} to ${EXTERN_ROOT?}. --unmount: Unmount $EXTERN_ROOT when the sync has completed. --large: Include large shares that would otherwise be excluded. --verbose: Run rsync in verbose mode. EOT` function showUsage() { echo "Usage:" >&2 echo "$ $USAGE" >&2 echo "$DESC" >&2 } function checkModeSet() { if [ "$mode" ]; then echo "Only one mode can be set." >&2 exit -1 fi } while :; do case $1 in -h|--help) showUsage exit -2 ;; backup) checkModeSet mode=backup SRC_ROOT=$SHARES_ROOT DEST_ROOT=$EXTERN_ROOT ;; restore) checkModeSet mode=restore SRC_ROOT=$EXTERN_ROOT DEST_ROOT=$SHARES_ROOT ;; --checksum) echo "Using checksum comparison." CHECKSUM="--checksum" ;; --dry-run) echo "Running in dry-run mode." DRY_RUN="--dry-run" VERBOSE="--verbose" ;; --delete) echo "Deleting extraneous files." DELETE="--delete-after" ;; --mount) echo "Will mount ${EXTERN_DEV?}, and unmount when done." MOUNT="mount" UNMOUNT="unmount" ;; --unmount) echo "Will unmount when done." UNMOUNT="unmount" ;; --large) echo "Will include large shares." LARGE="large" ;; -v|--verbose) VERBOSE="--verbose" ;; "") break ;; *) printf "ERROR: Unknown option: %s\n" "$1" >&2 showUsage exit -1 ;; esac shift done echo # Check that the mode has been set if [ "$mode" = "" ]; then showUsage exit -2 fi echo "Checking that script is being run as root..." CURR_USER=`whoami` if [ "$CURR_USER" != "root" ]; then echo >&2 echo "ERROR: Must be run as root" exit -3 fi # Check if the drive is mounted. # Grep returns 1 when the string isn't found, which is an error, and `set -e` # stops the script. Extra bit at the end makes it return an empty string # when it's not found. MOUNTED=`mount | grep $EXTERN_ROOT || [[ $? == 1 ]]` if [ "$MOUNTED" != "" ]; then echo "${EXTERN_ROOT?} is already mounted." elif [ "$MOUNT" = "mount" ]; then echo "Mounting ${EXTERN_DEV?} to ${EXTERN_ROOT?}" mount ${EXTERN_DEV?} ${EXTERN_ROOT?} fi # Do a second check regardless of --mount to ensure everything is OK. echo "Checking that drive is mounted at ${EXTERN_ROOT?} ..." MOUNTED=`mount | grep $EXTERN_ROOT || [[ $? == 1 ]]` if [ "$MOUNTED" = "" ]; then >&2 echo "$EXTERN_ROOT is not mounted!" exit -4 fi LOG="${LOG_ROOT?}/$mode-shares-$(date '+%Y-%m-%d_%H-%M').log" echo "Everything looks good. Will log to ${LOG?}" function log() { date_str=`date '+%Y-%m-%d %H:%M:%S'` echo "[$date_str]: $1" 2>&1 | tee -a $LOG } function logCmd() { cmd="$@" log "Running '${cmd}'" (time $cmd) 2>&1 | tee -a $LOG } function pause() { read -s -n 1 -p "Press any key to continue, or ctrl+c to cancel." } function syncShare() { localShare="$1" if [ "$2" = "" ]; then remoteShare="$1" else remoteShare="$2" fi if [ "$mode" = "restore" ]; then srcShare="$remoteShare" destShare="$localShare" else srcShare="$localShare" destShare="$remoteShare" fi # use a trailing slash to sync the contents of the directory src="$SRC_ROOT/$srcShare/" dest="$DEST_ROOT/$destShare/" echo "" | tee -a $LOG log '============================' log "Syncing $src to $dest" log "Checking if $dest exists" if [[ ! -d "$dest" ]]; then if [ "$mode" = "backup" ]; then echo "$dest does not exist. Creating it." mkdir -p $dest else echo "$dest does not exist. Exiting." exit 1 fi fi if [ "$1" = "bittorrent" ]; then DELETE="--delete-before" fi # Set noglob to prevent * from being expanded before logCmd is called set -o noglob logCmd rsync --archive --hard-links $DRY_RUN $VERBOSE \ --progress --partial \ --human-readable \ --exclude ".[!.]*-daily_20*/" \ --exclude ".[!.]*-weekly_20*/" \ --exclude ".daily_20*/" \ --exclude ".weekly_20*/" \ --exclude "transcoding-temp/" \ --exclude ".mount" \ $CHECKSUM \ $DELETE \ $src \ $dest set +o noglob } function syncShares() { pause log "Starting sync" syncShare bitwarden syncShare jason Jason syncShare jenn Jenn syncShare nginx-config syncShare git-config syncShare git-storage syncShare party-rescue syncShare shared #syncShare video-recordings syncShare music #syncShare nginx-data syncShare emby-config #syncShare odoo-data if [ "$LARGE" ]; then syncShare bittorrent fi } function unmount() { if [ "$UNMOUNT" ]; then log "Unmounting drive" logCmd sudo umount $EXTERN_ROOT fi } log "Arguments: $FULL_ARGS" logCmd syncShares unmount log "Done"