msynk/msynk.sh

332 lines
11 KiB
Bash
Raw Normal View History

2022-03-11 21:07:56 +01:00
#!/bin/bash
# Made by Matthias Quintern
# 02/2022
# This software comes with no warranty.
# ABOUT
# Synchronizes files from a sender to a receiver using rsync
# The files can be encrypted before or decrypted after the transfer using the mkrypt script
#
# SETTINGS
#
# The directory that is searched for config files
CONFIG_DIR=~/.config/msynk/
# When using encrypt/decrypt, the source is en/decrypted to TMP_DIR and then rsynced to receiver from there
# Path must contain 'msynk'
TMP_DIR=/tmp/msynk/
# When using --delete, this file is used to determine the files that will be deleted
TMP_FILE=/tmp/msynk_file
# Path to the mkrypt script
mkrypt=/usr/bin/mkrypt
# Additional flags for mkrypt
mkrypt_flags=
rsync_flags=(-ruvh)
# rsync_flags+=(--rsh="ssh -p 69")
# r - relative
# v - verbose
# Ut - preserve modification&access times
# u - update, skip files that are newer on the receiver
# P - show progress and keep partially transferred files
# h - human readable
# see "man rsync" for more
#
# UTILITY
#
FMT_MESSAGE="\e[1;34m%s\e[0m\n"
FMT_ERROR="\e[1;31mERROR: \e[0m%s\n"
FMT_SYNC="\e[1;32m:Syncing\e[0m %s\n"
# FMT_UPDATE="\e[1;33mUpdating:\e[0m %s\n"
FMT_CONFIG="\e[34m%s\e:\t\e[1;33m%s\e[0m\n"
quiet() { "$@" > /dev/null 2>&1; }
# check if a passed parameter is contained in file
check_path_in_args()
{
if [ $all = 1 ]; then
return 0
fi
for string in ${FILES[@]}; do
if [[ $path == *$string* ]]; then
return 0
fi
done
return 1
}
#
# sync sender to receiver
#
sync_sender_to_receiver()
{
# perform a dry run to see which files would be deleted
if [[ -n $delete && -z $skip_dryrun ]]; then
[[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "Performing dryrun to generate list of files that will be deleted from $receiver..."
echo "" > $TMP_FILE
for path in "${filtered_paths[@]}"; do
dest_subdir=$receiver$(basename $path)
rsync $rsync_flags -v --dry-run --delete $path $dest_subdir | grep deleting | sed "s(deleting (${BACKUP_SUBDIR}/(" >> $TMP_FILE
done
# print files that will be deleted and ask if continue
# grep deleting $TMP_FILE | sed "s(deleting (${receiver}/(" | less
less $TMP_FILE
printf "$FMT_MESSAGE" "The listed files will be deleted from $receiver."
# FMT_UPDATE="\e[1;33mUpdating:\e[0m %s\n"
printf "Proceed?\e[34m (y/n)\e[0m: "
read answer
case $answer in
y|Y)
# printf "$FMT_MESSAGE" "Backing up to $receiver..."
;;
*)
printf "$FMT_MESSAGE" "Cancelled."
exit 0 ;;
esac
fi
# actual syncing
for path in "${filtered_paths[@]}"; do
[[ $v -ge 1 ]] && printf "$FMT_SYNC" "$path"
if [[ -d $path ]]; then
dest=$receiver$(basename $path)
elif [[ -f $path ]]; then
dest=$receiver
elif [[ $sender == *@*:* ]]; then # if sender is remote, assume the path is a dir if it has a slash and a file otherwise
[[ $path == *"/" ]] && dest=$receiver$(basename $path) || dest=$receiver
else
printf "$FMT_ERROR" "Invalid path: $path"; exit 1
fi
if [[ -n $encrypt || -n $decrypt ]]; then
[[ -d $path ]] && tmp_source=$TMP_DIR
if [[ -n $encrypt ]]; then
[[ -f $path ]] && tmp_source=$TMP_DIR$(basename $path).gpg
[[ $v -ge 3 ]] && printf "$FMT_CMD" "bash $mkrypt --encrypt $path --output $TMP_DIR $mkrypt_flags"
bash $mkrypt --encrypt $path --output $TMP_DIR $mkrypt_flags || exit 1
[[ $v -ge 3 ]] && printf "$FMT_CMD" "rsync ${rsync_flags[@]} $tmp_source $dest"
rsync "${rsync_flags[@]}" $tmp_source $dest
else
[[ -f $path ]] && tmp_source=$TMP_DIR$(basename $path)
[[ $v -ge 3 ]] && printf "$FMT_CMD" "rsync ${rsync_flags[@]} $path $TMP_DIR"
rsync "${rsync_flags[@]}" $path $TMP_DIR
[[ $v -ge 3 ]] && printf "$FMT_CMD" "bash $mkrypt --decrypt $tmp_source --output $(readlink -f $dest) $mkrypt_flags"
bash $mkrypt --decrypt $tmp_source --output $(readlink -f $dest) $mkrypt_flags || exit 1
fi
else
printf "$FMT_CMD" "rsync ${rsync_flags[@]} $delete $path $dest"
rsync "${rsync_flags[@]}" $delete $path $dest
fi
[[ $TMP_DIR == *msynk* ]] && {
rm -rf $TMP_DIR/* || { printf "$FMT_ERROR" "Can not delete \$TMP_DIR: $TMP_DIR"; exit 1; }
} || { printf "$FMT_ERROR" "Will not delete \$TMP_DIR because it might be dangerous (path does not contain 'msynk'): $TMP_DIR"; exit 1; }
done
[[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Running 'sync' to write cached writes to persistent storage"
sync
[[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "Done!"
}
#
# HELP
#
show_help()
{
printf "\e[33mArgument Short Action:\e[0m
--help -h show this
--settings -s show current settings
--config [name] -c use sender, receiver, paths from config file with [name]
--sender [path] -s sender directory, with trailing slash! Defaults to the current working directory
--receiver [path] -r receiver directory, with trailing slash!
--reverse swap receiver and sender
--encrypt encrypt files before syncing
--decrypt decrypt files before syncing
--mkrypt-flags [flags] additional flags for mkrypt
--delete -d delete files that exist on receiver, but not on sender
--skip-dryrun delete without asking first
--rsync-flags [flags] additional flags for rsync
--verbose -v increase verbosity
--silent decrease verbosity
--debug maximum verbosity
Positional arguments are:
- if using a config: a string that must be in the configs paths: eg. 'foo' will include ~/foo but nor ~/bar
- if not using a config: paths to sync (must be relative to --sender)
See the manpage for more information.
"
}
show_settings()
{
printf "\e[1mThe current settings are:\e[0m\n"
printf "$FMT_CONFIG" "\$TMP_FILE " "$TMP_FILE"
printf "$FMT_CONFIG" "\$rsync_flags " "${rsync_flags[*]}"
}
#
# PARSE ARGS
#
if [ -z $1 ]; then
show_help
exit 0
fi
all=0
BACKUP=1 # use if the script gets more functions later
# all command line args with no "-" are interpreted as part of filepaths.
paths_2=()
while (( "$#" )); do
case "$1" in
-h|--help)
show_help
exit 0 ;;
--settings)
show_settings
exit 0 ;;
-c|--config)
if [[ -n $2 && ${2:0:1} != "-" ]]; then
config=$2
shift 2
else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;;
-s|--sender)
if [[ -n $2 && ${2:0:1} != "-" ]]; then
sender_2=$2
shift 2
else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;;
-r|--receiver)
if [[ -n $2 && ${2:0:1} != "-" ]]; then
receiver_2=$2
shift 2
else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;;
--reverse)
reverse=1
shift ;;
--encrypt)
encrypt=1
shift ;;
--decrypt)
decrypt=1
shift;;
-d|--delete)
delete=--delete
shift ;;
--skip-dryrun)
skip_dryrun=1
shift ;;
--rsync-flags)
if [[ -n ${2} ]]; then # && "${2:0:1}" != "-" ]]; then # possibly problematic if $2 is an msynk flag
rsync_flags+=("${2}")
shift 2
else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;;
--msynk-flags)
if [[ -n ${2} ]]; then # && "${2:0:1}" != "-" ]]; then # possibly problematic if $2 is an msynk flag
mkrypt_flags+=("${2}")
shift 2
else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;;
-v|--verbose)
v=2
rsync_flags+=(-v)
mkrypt_flags+=(-v)
shift ;;
--silent)
v=0
mkrypt_flags+=(--silent)
shift ;;
--debug)
v=3
rsync_flags+=(-v)
mkrypt_flags+=(-v)
shift;;
-*|--*=) # unsupported flags
printf "$FMT_ERROR" "Unsupported flag $1" >&2
exit 1 ;;
*) # everything that does not have a - is interpreted as filepath
all=
paths_2+=($1)
shift ;;
esac
done
# check if rsync is installed
if ! command -v rsync &> /dev/null; then
printf "$FMT_ERROR" "rsync is not installed."
fi
# if using a config
if [[ -n $config ]]; then
bash $CONFIG_DIR/$config || { printf "$FMT_ERROR" "Error running the config file: $CONFIG_DIR/$config"; exit 1; }
fi
# overwrite stuff from the config if anything else was given / set variables of no config was given
[[ -n $sender_2 ]] && sender=$sender_2
[[ -n $receiver_2 ]] && receiver=$receiver_2
[[ -z $sender ]] && {
[[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "Missing sender. Using working directory: $(pwd)";
sender="$(pwd)/"
}
# reverse: swap sender and reveiver
if [[ -n $reverse ]]; then
tmp_sender=$sender
sender=$receiver
receiver=$tmp_sender
fi
# make sure sender is absolute with slash
wd=$PWD
# quiet cd $sender && sender="$PWD/" || { printf "$FMT_ERROR" "Sender directory does not exist: $sender"; exit 1; }
# make sure reveiver is absolute
# cd $wd
# mkdir -p $receiver || { printf "$FMT_ERROR" "Can not create config dir: $receiver"; exit 1; }
# quiet cd $receiver && receiver="$PWD/" || { printf "$FMT_ERROR" "Receiver directory does not exist: $receiver"; exit 1; }
# make sure receiver has trailing slash
[[ $receiver != *"/" ]] && receiver=$receiver"/"
echo "sender:$sender rec:$receiver paths=$paths paths2=$paths_2"
# filter paths:
# - if using config: all paths from config that have a pos.arg in them
# - else: pos.args, must be relative to specified --sender
filtered_paths=()
if [[ -n $config ]]; then
for path in ${paths[@]}; do
if check_path_in_args; then
filtered_paths+=($sender$path)
fi
done
else
for path in ${paths_2[@]}; do # add slash if directory
# quiet cd $sender
[[ -d $path ]] && [[ $path != *"/" ]] && path=$path"/"
# quiet cd $sender && quiet cd $path && filtered_paths+=($PWD/) || {
# quiet cd $sender && ls -d $path && filtered_paths+=($PWD/$path)
# }
filtered_paths+=($sender$path)
done
fi
cd $wd
echo "FPaths: ${filtered_paths[@]}"
# sanity checks
[[ -z $filtered_paths ]] && { printf "$FMT_ERROR" "Missing valid paths."; exit 1; }
[[ -z $receiver ]] && { printf "$FMT_ERROR" "Missing receiver. Specifiy with --receiver"; exit 1; }
sync_sender_to_receiver
exit 0