diff --git a/msynk.sh b/msynk.sh index b400041..bccc9ca 100755 --- a/msynk.sh +++ b/msynk.sh @@ -1,6 +1,5 @@ -#!/bin/bash -# Made by Matthias Quintern -# 03/2022 +#!/bin/bash +# Copyright © 2022 Matthias Quintern. # This software comes with no warranty. # This software is licensed under the GPL3 @@ -9,8 +8,9 @@ # 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/ +# The directory that is searched for config files, use ~/.config if $XDG_CONFIG_HOME is not defined +[[ -z $XDG_CONFIG_HOME ]] && XDG_CONFIG_HOME=~/.config +CONFIG_DIR=$XDG_CONFIG_HOME/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/ @@ -19,10 +19,10 @@ TMP_FILE=/tmp/msynk_file # Path to the mkrypt script mkrypt=/usr/bin/mkrypt # Additional flags for mkrypt -mkrypt_flags= +mkrypt_flags=() rsync_flags=(-ruh) -# rsync_flags+=(--rsh="ssh -p 69") +# rsync_flags+=(--rsh="ssh -p 42") # r - relative # v - verbose # Ut - preserve modification&access times @@ -31,10 +31,13 @@ rsync_flags=(-ruh) # h - human readable # see "man rsync" for more +# expand * to hidden files as well +shopt -s dotglob # UTILITY -FMT_MESSAGE="\e[1;34m%s\e[0m\n" -FMT_ERROR="\e[1;31mERROR: \e[0m%s\n" +FMT_MESSAGE="\e[1;34m%s\e[0m %s\n" +FMT_ERROR="\e[1;31mmsync ERROR: \e[0m%s\n" +# exit 1: error, exit 2: rsync exited non 0, exit 3: mkrypt exited non 0 FMT_SYNC="\e[1;32mSyncing: \e[0m%s\n" FMT_CONFIG="\e[34m%s\e:\t\e[1;33m%s\e[0m\n" FMT_CMD="\e[1;33mRunning: \e[0m%s\n" @@ -44,12 +47,13 @@ FMT_CMD="\e[1;33mRunning: \e[0m%s\n" quiet() { "$@" > /dev/null 2>&1; } # check if a passed parameter is contained in file +# path must be set before calling check_path_in_args() { if [ $all = 1 ]; then return 0 fi - for string in ${FILES[@]}; do + for string in ${paths_2[@]}; do if [[ $path == *$string* ]]; then return 0 fi @@ -63,7 +67,7 @@ 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..." + [[ $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) @@ -74,7 +78,6 @@ sync_sender_to_receiver() 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 @@ -90,52 +93,61 @@ sync_sender_to_receiver() # actual syncing for path in "${filtered_paths[@]}"; do [[ $v -ge 1 ]] && printf "$FMT_SYNC" "$path" - if [[ -d $path ]]; then - dest=$receiver$(basename $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 + 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 + [[ -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 3 + # check if TMP_DIR has enough space + tmp_free=$(df --output=avail $TMP_DIR | grep -E "[[:digit:]]+") + path_size=$(du -s "$path" | sed -e "s/[^0-9]//g") + if [[ $tmp_free -le $path_size ]]; then + printf "$FMT_ERROR" "Not enough space on $TMP_DIR to copy $path! $TMP_DIR $tmp_free free - Size $path: $path_size" + printf "$FMT_MESSAGE" "" "If using a tmpfs, you can increase its size by running: 'sudo mount -o remount,size=XXG,noatime /tmp' to increase the size to XXG (You will need a large enough swap space)" + exit 1 + fi + + [[ -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 3 [[ $v -ge 3 ]] && printf "$FMT_CMD" "rsync ${rsync_flags[*]} $tmp_source $dest" - rsync ${rsync_flags[*]} $tmp_source $dest || { printf "$FMT_ERROR" "rsync exited with exit code $?"; exit 2; } + rsync "${rsync_flags[@]}" "$tmp_source" "$dest" || { printf "$FMT_ERROR" "rsync exited with exit code $?"; exit 2; } 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 || { printf "$FMT_ERROR" "rsync exited with exit code $?"; exit 2; } - [[ $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 3 + rsync "${rsync_flags[@]}" "$path" "$TMP_DIR" || { printf "$FMT_ERROR" "rsync exited with exit code $?"; exit 2; } + [[ $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 3 fi else [[ $v -ge 3 ]] && printf "$FMT_CMD" "rsync ${rsync_flags[*]} $delete $path $dest" - rsync ${rsync_flags[*]} $delete $path $dest || { printf "$FMT_ERROR" "rsync exited with exit code $?"; exit 2; } + rsync "${rsync_flags[@]}" $delete "$path" "$dest" || { printf "$FMT_ERROR" "rsync exited with exit code $?"; exit 2; } fi - [[ $TMP_DIR == *msynk* ]] && { - rm -rf $TMP_DIR/* || { printf "$FMT_ERROR" "Can not delete \$TMP_DIR: $TMP_DIR"; exit 1; } + [[ "$TMP_DIR" == *msynk* ]] && { + rm -rf "$TMP_DIR"/* || { printf "$FMT_ERROR" "Can not delete contents of \$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 # put todays date in the config if [[ -f $CONFIG_DIR/$config ]]; then if grep -xq "date=.*" $CONFIG_DIR$config; then - [[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Updating date in $CONFIG_DIR$config." - sed "s/date=.*/date=\"$(date)\"/" $CONFIG_DIR$config -i + [[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Updating" "date in $CONFIG_DIR$config." + sed "s/date=.*/date=\"$(date --iso=sec)\"/" $CONFIG_DIR$config -i else - [[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Writing current date to $CONFIG_DIR$config." - echo "date=\"$(date)\"" >> $CONFIG_DIR$config + [[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Writing" "current date to $CONFIG_DIR$config." + echo "date=\"$(date --iso=sec)\"" >> $CONFIG_DIR$config fi fi - [[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Running 'sync' to write cached writes to persistent storage" + [[ $v -ge 2 ]] && printf "$FMT_MESSAGE" "Running:" "'sync' to write cached writes to persistent storage" sync [[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "Done!" } @@ -144,31 +156,32 @@ sync_sender_to_receiver() # HELP show_help() { - printf "\e[33mArgument Short Action:\e[0m ---help -h Show this. ---settings -s Show current settings. + 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]. +--config [name] -c [name] Use sender, receiver, paths from config file with [name]. +--show-config [name] Print variables defined by a config 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. +--sender [path] -s [path] Sender directory, with trailing slash! Defaults to the current working directory. +--receiver [path] -r [path] Receiver directory, with trailing slash!. +--reverse Swap receiver and sender. ---encrypt Encrypt files before syncing. ---decrypt Decrypt files before syncing. ---date Only process files that have been modified since the program was last run. Needs -c and (--encrypt/--decrypt). ---mkrypt-flags [flags] Additional flags for mkrypt. +--encrypt Encrypt files before syncing. +--decrypt Decrypt files before syncing. +--check-date Only process files that have been modified since the program was last run. Needs -c and (--encrypt/--decrypt). +--mkrypt-flag [arg] Additional argument for mkrypt. ---delete -d Delete files that exist on receiver, but not on sender. Does not work with --encrypt/--decrypt. ---skip-dryrun Delete without asking first. ---rsync-flags [flags] Additional flags for rsync. +--delete -d Delete files that exist on receiver, but not on sender. Does not work with --encrypt/--decrypt. +--skip-dryrun Delete without asking first. +--rsync-flag [arg] Additional argument for rsync. ---verbose -v Increase verbosity. ---silent Decrease verbosity. ---debug Maximum verbosity. +--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 using a config: a string that must be in the configs paths: eg. 'foo' will include ~/foo but not ~/bar - if not using a config: paths to sync (must be relative to --sender) See the manpage for more information. @@ -188,6 +201,17 @@ show_settings() printf "$FMT_CONFIG" "\$rsync_flags " "${rsync_flags[*]}" } +show_config() +{ + [[ ! -f $CONFIG_DIR$config ]] && { printf "$FMT_ERROR" "Invalid path: $CONFIG_DIR$config"; exit 1; } + source $CONFIG_DIR$config + printf "\e[1m$CONFIG_DIR$config:\e[0m\n" + printf "$FMT_CONFIG" "\$sender " "$sender" + printf "$FMT_CONFIG" "\$receiver " "$receiver" + printf "$FMT_CONFIG" "\$paths " "${paths[*]}" + printf "$FMT_CONFIG" "\$use_encryption " "$use_encryption" + printf "$FMT_CONFIG" "Last run: $date" +} # PARSE ARGS if [ -z $1 ]; then @@ -210,6 +234,12 @@ while (( "$#" )); do config=$2 shift 2 else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;; + --show-config) + if [[ -n $2 && ${2:0:1} != "-" ]]; then + config=$2 + show_config + exit 0 + else printf "$FMT_ERROR" "Missing argument for $1"; exit 1; fi ;; -s|--sender) if [[ -n $2 && ${2:0:1} != "-" ]]; then sender_2=$2 @@ -237,15 +267,15 @@ while (( "$#" )); do --skip-dryrun) skip_dryrun=1 shift ;; - --rsync-flags) + --rsync-flag) 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 ;; - --date) - use_date=1 + --check-date) + check_date=1 shift ;; - --msynk-flags) + --mkrypt-flag) if [[ -n ${2} ]]; then # && "${2:0:1}" != "-" ]]; then # possibly problematic if $2 is an msynk flag mkrypt_flags+=("${2}") shift 2 @@ -268,13 +298,18 @@ while (( "$#" )); do printf "$FMT_ERROR" "Unsupported flag $1" >&2 exit 1 ;; *) # everything that does not have a - is interpreted as filepath - all= + all=0 paths_2+=($1) shift ;; esac done # PREPARATION & CHECKS +# clear $TMP_DIR: might be non-empty if previous msynk failed +[[ $TMP_DIR == *msynk* ]] && { + rm -rf $TMP_DIR/* || { printf "$FMT_ERROR" "Can not contents of \$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; } + # check if rsync is installed if ! command -v rsync &> /dev/null; then printf "$FMT_ERROR" "rsync is not installed." @@ -282,22 +317,23 @@ if ! command -v rsync &> /dev/null; then fi # if using encryption, check if mkrypt is installed -[[ $use_encryption = 1 && ! -f $mkrypt ]] && printf "$FMT_ERROR" "Can not use encryption: mkrypt is not installed at given path: $mkrypt." +[[ $use_encryption = 1 && ! -f $mkrypt ]] && { printf "$FMT_ERROR" "Can not use encryption: mkrypt is not installed at given path: $mkrypt."; exit 1; } + # if using a config if [[ -n $config ]]; then - source $CONFIG_DIR$config || { printf "$FMT_ERROR" "Error running the config file: $CONFIG_DIR/$config"; exit 1; } + source $CONFIG_DIR$config || { printf "$FMT_ERROR" "Error running the config file: $CONFIG_DIR$config"; exit 1; } if [[ $use_encryption = 1 ]]; then [[ -z $reverse ]] && encrypt=1 || decrypt=1 fi - [[ $v -ge 3 ]] && printf "$FMT_MESSAGE" "Loaded config: $CONFIG_DIR$config:: date:$date sender:$sender receiver:$receiver use_encryption:$use_encryption encrypt:$encrypt decrypt:$decrypt paths:${paths[*]}" + [[ $v -ge 3 ]] && printf "$FMT_MESSAGE" "Loaded config:" "$CONFIG_DIR$config:: date:$date sender:$sender receiver:$receiver use_encryption:$use_encryption encrypt:$encrypt decrypt:$decrypt paths:${paths[*]}" 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)"; + [[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "Missing sender:" "Using working directory: $(pwd)" sender="$(pwd)/" } @@ -311,39 +347,45 @@ fi # make sure receiver has trailing slash [[ $receiver != *"/" ]] && receiver=$receiver"/" # create receiver dir if its local -[[ ! $receiver == *:* ]] && { mkdir -p $receiver || { printf "$FMT_ERROR" "Can not create receiver directory: $receiver"; exit 1; }} +[[ ! $receiver == *:* ]] && { mkdir -p $receiver || { printf "$FMT_ERROR" "Can not create receiver directory: $receiver"; exit 1; }; } -[[ $v -ge 3 ]] && printf "$FMT_MESSAGE" "sender:$sender receiver:$receiver paths:$paths paths_2:$paths_2" +[[ $v -ge 3 ]] && printf "$FMT_MESSAGE" "Variables:" "sender:$sender receiver:$receiver paths:${paths[*]} paths_2:${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=() +ifs=$IFS +IFS=$'\n' if [[ -n $config ]]; then [[ $sender != *"/" ]] && sender=$sender"/" - for path in ${paths[@]}; do + for path in "${paths[@]}"; do if check_path_in_args; then - filtered_paths+=($sender$path) + # if reverse and encryption (use_encryption might be overwritten by config when --decrypt is used) and file, it will have a .gpg extension + [[ -n $reverse && ($use_encryption = 1 || $decrypt = 1) && "$path" != *"/" ]] && path="$path".gpg + filtered_paths+=("$sender$path") fi done else for path in ${paths_2[@]}; do # add slash if directory - [[ -d $path ]] && [[ $path != *"/" ]] && path=$path"/" - filtered_paths+=($sender$path) + [[ -d "$path" ]] && [[ "$path" != *"/" ]] && path="$path/" + filtered_paths+=("$sender$path") done fi -[[ $v -ge 3 ]] && printf "$FMT_MESSAGE" "Filtered paths: ${filtered_paths[@]}" +[[ $v -ge 3 ]] && printf "$FMT_MESSAGE" "Filtered paths:" "${filtered_paths[*]}" +IFS=$ifs # 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; } +[[ -n $delete && ( -n $decrypt || -n $encrypt ) ]] && { printf "$FMT_ERROR" "Can not use --delete and encryption at the same time."; exit 1; } -# if using --date -if [[ -n $use_date && -z $date ]]; then # if --date but date is not set, use 0 (unix time) - [[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "--date passed but date not set in config, using 0 (unix time)" - date=$(date -d @0) +# if using --check-date +if [[ -n $check_date && -z $date ]]; then # if --check-date but date is not set, use 0 (unix time) + [[ $v -ge 1 ]] && printf "$FMT_MESSAGE" "--check-date:" "passed but date not set in config, using 0 (unix time)" + date=$(date --iso=sec -d @0) fi -[[ -n $use_date ]] && mkrypt_flags+=("--date \"${date}\"") +[[ -n $check_date ]] && { mkrypt_flags+=(--check-date "${date}"); rsync_flags+=(--times); } # RUN! sync_sender_to_receiver