diff --git a/bin/bitpocket b/bin/bitpocket index 7f99ab7..67c67d2 100755 --- a/bin/bitpocket +++ b/bin/bitpocket @@ -40,11 +40,8 @@ if [ -n "$REMOTE_HOST" ]; then else REMOTE_RUNNER="bash -c" REMOTE="$REMOTE_PATH" - REMOTE_BACKUPS=false fi -LOCAL="$(pwd -P)" - REMOTE_TMP_DIR="$REMOTE_PATH/$DOT_DIR/tmp" # Don't sync user excluded files @@ -67,6 +64,13 @@ USER_RULES="$user_filter $user_include $user_exclude" TIMESTAMP=$(date "+%Y-%m-%d.%H%M%S") +GREEN="" +RED="" +if [[ -t 1 ]]; then + GREEN="\x1b\x5b1;32m" + RED="\x1b\x5b1;31m" +fi + export RSYNC_RSH function init { @@ -87,7 +91,8 @@ function init { REMOTE_HOST=$1 REMOTE_PATH="$2" -# Make revisions of files on the REMOTE_HOST in the push phase. +## Backups ----------------------------------- +## Make revisions of files on the REMOTE_HOST in the push phase. REMOTE_BACKUPS=false ## SSH command with options for connecting to \$REMOTE @@ -119,78 +124,22 @@ function pull() { echo "# Pulling changes from server" echo "# >> Saving current state and backing up files (if needed)" - cp "$STATE_DIR/tree-current" "$TMP_DIR/tree-after" + local BACKUP_TARGET="$DOT_DIR/backups/$TIMESTAMP" + local DO_BACKUP="--backup --backup-dir="$DOT_DIR"/backups/$TIMESTAMP" - # Create a duplicate of STDOUT for logging of backed-up files, and use fd#4 - # for logging of deleted files, which need to be sorted - exec 3>&1 - exec 4> >(sort > "$TMP_DIR/pull-delete") + cp "$STATE_DIR/tree-current" "$TMP_DIR/tree-after" # Determine what will be fetched from server and make backup # copies of any local files to be deleted or overwritten. # Order of includes/excludes/filters is EXTREMELY important - rsync --dry-run -auzxi --delete --exclude "/$DOT_DIR" \ + rsync -auzxi --delete --exclude "/$DOT_DIR" \ --exclude-from="$TMP_DIR/local-add-del" \ + $DO_BACKUP \ $RSYNC_OPTS $USER_RULES $REMOTE/ . \ - | backup_inline \ - | rsync --files-from=- --from0 -auzxi $RSYNC_OPTS $USER_RULES \ - $REMOTE/ . \ | sed "s/^/ | /" || die "PULL" - [[ -d "$DOT_DIR"/backups/$TIMESTAMP ]] \ - && echo " | Some files were backed up to $DOT_DIR/backups/$TIMESTAMP" - - # Close extra file descriptors - exec 4>&- - exec 3>&- -} - -function backup_inline() { - # Read file names from STDIN, and, if they exist locally, create a backup - # of each of the files. If they are requested to be deleted (indicated by - # the tag at the line beginning), then they are deleted here. Write the - # filenames to STDOUT which need to be synchronized by `rsync` - while read line - do - filename=$(sed "s:^\S*\s*::" <<< "$line" | sed 's:\d96:\\\`:g') - operation=${line%% *} - if [[ "$line" =~ ^[ch\<\>][fd]|^\*deleting ]] - then - if [[ -f "$filename" ]] - then - [[ -d "$DOT_DIR"/backups/$TIMESTAMP ]] \ - || mkdir -p "$DOT_DIR"/backups/$TIMESTAMP - cp --parents -p --reflink=auto "$filename" \ - "$DOT_DIR"/backups/$TIMESTAMP || die "BACKUP" - echo " B $filename" >&3 - fi - # rsync does not support DELETE via --files-from, so delete inline here - # after the backup was made - if [[ "$operation" == "*deleting" ]] - then - # Only delete locally if deleted remotely - if grep -q -x "/$filename" "$TMP_DIR/remote-del" - then - # Delete here rather than syncing - echo " | *deleting $filename" >&3 - echo "/$filename" >&4 - [[ -d "$filename" ]] && rmdir "$filename" || rm "$filename" - continue - fi - elif [[ "$operation" =~ \+\+\+\+$ ]] - then - # Mark as added locally - echo "/$filename" >> "$TMP_DIR/tree-after" - fi - fi - # Don't sync changes to folders -- only the files in them - if [[ ${operation:0:5} == '.d..t' ]] - then - continue - fi - # Sync the file. Use a NULL byte delimiter - printf '%s\0' "$filename" - done + [[ -d "$BACKUP_TARGET" ]] \ + && echo " | Some files were backed up to $BACKUP_TARGET" } function push() { @@ -201,93 +150,23 @@ function push() { echo echo "# Pushing changes to server" + local DO_BACKUP="" + if [[ -n "$REMOTE_BACKUPS" ]] + then + echo "# >> Saving current state and backing up files (if needed)" + DO_BACKUP="--backup --backup-dir="$DOT_DIR"/backups/$TIMESTAMP" + fi + # Do not push back remotely deleted files - cat "$TMP_DIR/remote-del" \ - | rsync -auzxi --delete $RSYNC_OPTS --exclude "/$DOT_DIR" \ - --exclude-from - $USER_RULES . $REMOTE/ \ + rsync -auzxi --delete $RSYNC_OPTS --exclude "/$DOT_DIR" \ + --exclude-from="$TMP_DIR/remote-del" \ + $DO_BACKUP \ + $USER_RULES . $REMOTE/ \ | sed "s/^/ | /" || die "PUSH" -} -function remote_pull() { - # Similar to the pull(), except the rsync output it sent to an rsync - # session on the remote server. Bitpocket is used on the remote side - # so that backups can be captured before the files are overwritten - # by the push sync. - # - # Here, the files to sync are sent to STDIN, piped through - # backup_inline, and piped through an rsync process to fetch the files - - exec 3>&1 - exec 4>/dev/null - - local REMOTE_PATH="$2" - local REMOTE="$1:$2" - - echo "# >> Saving current state and backing up files (if needed)" - - # Send new and updated, remotely remove files deleted locally - # Order of includes/excludes/filters is EXTREMELY important - backup_inline \ - | rsync --files-from=- --from0 -auzxi $RSYNC_OPTS $USER_RULES \ - $REMOTE/ . \ - | sed "s/^/ | /" \ - || die "REMOTE-PULL" - - [[ -d "$DOT_DIR"/backups/$TIMESTAMP ]] \ - && echo " | Some files were backed up to $DOT_DIR/backups/$TIMESTAMP" - - # Close extra file descriptors - exec 4>&- - exec 3>&- -} - -# Thanks, https://stackoverflow.com/a/246128 -function bitpocket_source() { - SOURCE="${BASH_SOURCE[0]}" - # resolve $SOURCE until the file is no longer a symlink - while [ -h "$SOURCE" ]; do - TARGET="$(readlink "$SOURCE")" - if [[ $TARGET == /* ]]; then - SOURCE="$TARGET" - else - DIR="$( dirname "$SOURCE" )" - # if $SOURCE was a relative symlink, we need to resolve it relative - # to the path where the symlink file was located - SOURCE="$DIR/$TARGET" - fi - done - echo "$SOURCE" -} - -function remote_bitpocket() { - # Transfer this bitpocket script source and the local-add-del as the - # remotely deleted files. Then run the `bitpocket` script with the - # args passed to this function. - # This assumes `bash` is in the PATH of the remote environment - $REMOTE_RUNNER " - cd \"$REMOTE_PATH\"; - cat << '=EOF:' > .bitpocket/bin -$(cat "$(bitpocket_source)" | sed "s/=EOF:/=EOF2:/") -=EOF: - cat << '=EOF:' > .bitpocket/tmp/remote-del -$(cat "$TMP_DIR/local-add-del") -=EOF: - bash .bitpocket/bin $*" -} - -function push_remote() { - # Works with remote_pull() to send the files which would be sent by the - # push() method, but allows for a backup on the remote side. - - echo - echo "# Pushing changes to server" - - rsync --dry-run -auzxi --delete --exclude "/$DOT_DIR" \ - --exclude-from="$TMP_DIR/remote-del" \ - $USER_RULES . $REMOTE/ \ - | remote_bitpocket remote-pull `hostname` $LOCAL \ - | sed "s/^/(remote) /" \ - || die "REMOTE-PUSH" + if $REMOTE_RUNNER "[ -d '$BACKUP_TARGET' ]"; then + echo " | Some files were backed up to $BACKUP_TARGET" + fi } # Do the actual synchronization @@ -297,7 +176,7 @@ function sync { acquire_remote_lock echo - echo -e "\x1b\x5b1;32mbitpocket started\x1b\x5b0m at `date`." + echo -e "${GREEN}bitpocket started\x1b\x5b0m at `date`." echo # Fire off slow sync start notifier in background @@ -348,7 +227,7 @@ function sync { fi pull - [[ $REMOTE_BACKUPS == true ]] && push_remote || push + push # Save after-sync state @@ -366,7 +245,7 @@ function sync { cleanup echo - echo -e "\x1b\x5b1;32mbitpocket finished\x1b\x5b0m at `date`." + echo -e "${GREEN}bitpocket finished\x1b\x5b0m at `date`." echo } @@ -464,7 +343,7 @@ function acquire_lock { echo "There's already an instance of BitPocket syncing this directory. Exiting." exit 1 else - echo -e "\x1b\x5b1;31mbitpocket error:\x1b\x5b0m Bitpocket found a stale lock directory:" + echo -e "${RED}bitpocket error:\x1b\x5b0m Bitpocket found a stale lock directory:" echo " | Root dir: $(pwd)" echo " | Lock dir: $LOCK_DIR" echo " | Command: LOCK_PATH=$(pwd)/$LOCK_DIR && rm \$LOCK_PATH/pid && rmdir \$LOCK_PATH" @@ -525,7 +404,7 @@ function die { # List all files in the sync set function list { - echo -e "\x1b\x5b1;32mbitpocket\x1b\x5b0m will sync the following files:" + echo -e "${GREEN}bitpocket\x1b\x5b0m will sync the following files:" rsync -av --list-only --exclude "/$DOT_DIR" $USER_RULES . | grep "^-\|^d" \ | sed "s:^\S*\s*\S*\s*\S*\s*\S*\s*:/:" | sed "s:^/\.::" | sort }