-
Notifications
You must be signed in to change notification settings - Fork 3
/
rsyncatron.sh
executable file
·147 lines (125 loc) · 5.46 KB
/
rsyncatron.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env bash
#
# This script takes a source and destination suitable for rsync, checks which
# files are out of date or missing at the destination, and prompts the user to
# interactively select which files they want to update at the destination using
# rsync and fzf. The user's selections are written to a JSON5/JSONC file. If a
# config file exists in the current working directory, the user is prompted if
# they want to use the existing selections configured instead.
#
# Inputs: Source and destination suitable for rsync (e.g., user@computer:/mnt/directory /tmp/files)
# Outputs: An updated destination directory and a log file in the current working directory
# Requires: fzf, fd, rsync, and jq to be installed on the system
set -o errexit
set -o nounset
set -o pipefail
set -x
# Replace the placeholder with your actual username and hostname (or IP address) of your remote server
USER_AT_COMPUTER="root@rockpi"
# Check if required tools are installed
command -v fzf >/dev/null 2>&1 || {
echo >&2 "fzf is required but not installed. Exiting."
exit 1
}
command -v rsync >/dev/null 2>&1 || {
echo >&2 "rsync is required but not installed. Exiting."
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo >&2 "jq is required but not installed. Exiting."
exit 1
}
command -v fd >/dev/null 2>&1 || {
echo >&2 "fd is required but not installed. Exiting."
exit 1
}
# Function to print error messages
function error() {
echo "Error: $1" >&2
exit 1
}
# Check input arguments
if [ "$#" -ne 2 ]; then
error "Invalid number of arguments. Usage: $0 <source> <destination>"
fi
SOURCE="$1"
DESTINATION="$2"
LOGFILE="$(pwd)/rsync_fzf.log"
CONFIGFILE="$(pwd)/rsync_fzf_config.json5"
# Check if a config file exists and prompt the user if they want to use it
if [ -f "$CONFIGFILE" ]; then
read -p "A config file was found. Do you want to use the existing selections? (y/n) " USE_CONFIG
if [ "$USE_CONFIG" == "y" ]; then
SELECTIONS=$(cat "$CONFIGFILE")
fi
else
# Create a temporary script file to store the dir_tree function
TEMP_SCRIPT=$(mktemp)
# Write the dir_tree function to the temporary script file
cat >"$TEMP_SCRIPT" <<EOL
cat >"$TEMP_SCRIPT" <<EOL
#!/usr/bin/env bash
user_at_computer="\$1"
SOURCE="\$2"
path="\$3"
trimmed_source=\$(echo "\$SOURCE" | sed 's:/*$::') # Add this line to trim trailing slashes
if [ -z "\$path" ]; then
# List top-level directories, including hidden ones
ssh \$user_at_computer "find \$trimmed_source -mindepth 1 -maxdepth 1 -type d -printf '%f\n'
else
# Check if the given path is a directory
is_directory=\$(ssh \$user_at_computer "if [ -d \"\$trimmed_source/\$path\" ]; then echo 'yes'; else echo 'no'; fi")
if [ "\$is_directory" == "yes" ]; then
# List files and directories in the selected path, including hidden ones
ssh \$user_at_computer "find \$trimmed_source/\$path -mindepth 1 -maxdepth 1 -printf '%P\n'
else
# If the path is not a directory, do not list its content
echo ""
fi
fi
EOL
# Make the temporary script executable
chmod +x "$TEMP_SCRIPT"
# Prompt user to interactively select files using fzf with directory browsing
SELECTIONS=$(fzf --ansi --multi --prompt="Select files to update: " --preview="bash -c '$TEMP_SCRIPT \"$USER_AT_COMPUTER\" \"$SOURCE\" {}'" --preview-window=right:50% --bind "ctrl-d:preview-page-down" --bind "ctrl-u:preview-page-up" --bind "enter:execute($TEMP_SCRIPT \"$USER_AT_COMPUTER\" \"$SOURCE\" {} | fzf --ansi --multi --header='Enter: select, Esc: back' --preview='bash -c \"$TEMP_SCRIPT \\\"$USER_AT_COMPUTER\\\" \\\"$SOURCE\\\" {}/{}\"' --preview-window=right:50% --bind 'enter:execute(echo {}/{} | tr -d \"''\")')")
# Remove any empty lines from the selections
SELECTIONS=$(echo "$SELECTIONS" | sed '/^$/d')
# Clean up the temporary script file
rm -f "$TEMP_SCRIPT"
# Save user selections to the config file
echo "$SELECTIONS" | jq --slurp --raw-input --raw-output 'split("\n")[:-1]' >"$CONFIGFILE"
fi
# Perform a dry run first
echo "Performing a dry run..." | tee -a "$LOGFILE"
rsync -n -av --files-from=<(echo "$SELECTIONS") "$SOURCE" "$DESTINATION" | tee -a "$LOGFILE"
# Prompt the user to continue, exit, or change the selections
while true; do
read -p "Do you want to continue with the updates (c), exit (e), or change the selections (s)? " CHOICE
case "$CHOICE" in
c)
break
;;
e)
echo "Exiting without updating files." | tee -a "$LOGFILE"
exit 0
;;
s)
# List the directories in the source path
DIRS=$(ssh user@computer "find $SOURCE -type d")
# Prompt user to interactively select files using fzf with tree preview
SELECTIONS=$(echo "$DIRS" | fzf --ansi --multi --prompt="Select files to update: " --preview='ssh '$USER_AT_COMPUTER' "tree -C -f -L 1 --noreport {} | tail -n +2"' --preview-window=right:50% --bind "ctrl-d:preview-page-down" --bind "ctrl-u:preview-page-up")
# Save user selections to the config file
echo "$SELECTIONS" | jq --slurp --raw-input --raw-output 'split("\n")[:-1]' >"$CONFIGFILE"
# Perform a dry run again
echo "Performing a dry run with new selections..." | tee -a "$LOGFILE"
rsync -n -av --files-from=<(echo "$SELECTIONS") "$SOURCE" "$DESTINATION" | tee -a "$LOGFILE"
;;
*)
echo "Invalid input. Please enter 'c', 'e' or 's'"
;;
esac
done
# Update the selected files at the destination using rsync
echo "Updating selected files..." | tee -a "$LOGFILE"
rsync -av --files-from=<(echo "$SELECTIONS") "$SOURCE" "$DESTINATION" | tee -a "$LOGFILE"
echo "Update complete." | tee -a "$LOGFILE"