-
Notifications
You must be signed in to change notification settings - Fork 3
/
backup.sh
194 lines (175 loc) · 7.52 KB
/
backup.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env bash
# Copyright 2018 LNFWebsite
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################
# TurtleBackup - backup.sh
#
# TurtleBackup is a multi-purpose backup script which encrypts your data before
# uploading to your cloud of choice.
#
# By default, it's set up to use Google Drive using the Drive program:
#
# https://github.com/odeke-em/drive
#
# Instructions:
#
# All you need to do is change a couple settings below...
#
PASSWORD="(enter a strong password here)"
# This is a cryptographic salt for use with encrypting the filenames (essentially a random string)
# Please run the following command on your system to generate a strong salt for use here (command from: https://unix.stackexchange.com/a/230676/101736):
# `head /dev/urandom | tr -dc A-Za-z0-9 | head -c 128 ; echo ''`
FILENAME_ENC_SALT="ReallyLongStringOfRandomCharactersGeneratedByTheCommandListedAbove"
# Enter directories which you would like to backup (make sure there is a leading and trailing slash, separate multiple directories by space)
DIRS_TO_BACKUP="/example/directory/to/backup/ /another/example/directory/"
# Enter the directory you chose to use with your uploader (no trailing slash, gdrive by default)
ROOT_DIRECTORY="/home/my_username/gdrive"
# Enter the subdirectory to put backup files in the root directory of your uploader (no trailing slash, will create if directory non-existent)
BACKUP_LOCATION="${ROOT_DIRECTORY}/backup"
# (Optional): Uncomment and enter regex to match files/directories which should be skipped during backup
#SKIP_IF_MATCH_REGEX="^\/some\/regex\/.+$"
# What command to run in order to upload (uses drive by default)
BACKUP_ENGINE_CMDS="cd \"${BACKUP_LOCATION}\"; drive push -no-prompt;"
################################################################################ That's it!
echo "TurtleBackup is starting...";
STAT_FILES_LOCATION="${ROOT_DIRECTORY}/statfiles"
ENC_FILE_EXTENSION=".tar.gz.enc"
function encrypt_path() {
ENCRYPTED_PATH=""
#create array based on directory/file names separated by slash
IFS='/' read -r -a DIRECTORIES_ARRAY <<< "$1"
#loop through them
for element in "${DIRECTORIES_ARRAY[@]}"
do
#ignore the first empty element
if [ ! -z "${element}" ]
then
#get the hash of this element
ENC_FILE_HASHNAME=$(echo "${FILENAME_ENC_SALT}${element}" -n | openssl dgst -sha256);
#strip the stuff that openssl adds in the beginning
ENC_FILE_HASHNAME="${ENC_FILE_HASHNAME#"(stdin)= "}"
#append to the ENCRYPTED_PATH
ENCRYPTED_PATH="${ENCRYPTED_PATH}/${ENC_FILE_HASHNAME}"
fi
done
echo "${ENCRYPTED_PATH}";
}
#if the backup location does not exist, create it
if [ ! -d ${BACKUP_LOCATION} ]
then
echo "Creating empty backup directory...";
`mkdir -p "${BACKUP_LOCATION}"`
fi
echo "Checking for updated files...";
#initialize ORIGIN_COMPARE for automatic file deletion
ORIGIN_COMPARE=""
#list all files within all directories and loop through them (https://unix.stackexchange.com/a/9499)
while IFS= read -r -d '' FILE_TO_CHECK;
do
SKIP_FILE=false
#if regex set
if [ ! -z ${SKIP_IF_MATCH_REGEX} ]
then
#check file against the regex
if [[ $FILE_TO_CHECK =~ $SKIP_IF_MATCH_REGEX ]]
then
SKIP_FILE=true
fi
fi
#if this file was not specified to be ignored by SKIP_IF_MATCH_REGEX
if [ "${SKIP_FILE}" = false ]
then
#encrypt all directory and file names with sha256 hash (needs to be unchanging relative to actual names, is not decrypted because tar includes original filenames and paths for uncompression)
ENCRYPTED_PATH=$(encrypt_path "${FILE_TO_CHECK}")
#specify the location of the backups
ENC_FILE="${BACKUP_LOCATION}${ENCRYPTED_PATH}"
ENC_FILE_DIR=$(dirname "${ENC_FILE}");
#specify the location of the stat files
OLD_STAT_FILE="${STAT_FILES_LOCATION}${ENCRYPTED_PATH}.txt"
STAT_FILE_DIR=$(dirname "${OLD_STAT_FILE}");
#append the path of this file to the ORIGIN_COMPARE variable
ORIGIN_COMPARE="${ORIGIN_COMPARE}"$'\n'"${ENC_FILE}${ENC_FILE_EXTENSION}"
#if the stat file directory does not exist, create it
if [ ! -d $STAT_FILE_DIR ]
then
`mkdir -p "${STAT_FILE_DIR}"`
fi
#if the stat file exists, read its contents into OLD_STAT
if [ -e ${OLD_STAT_FILE} ]
then
OLD_STAT=`cat "${OLD_STAT_FILE}"`
else
OLD_STAT="nothing"
fi
#run the stat command to store the last time this file has been edited
NEW_STAT=`stat -c '%y' "${FILE_TO_CHECK}"`
#if the file has changed as evident by the differing stat results to the old stat file
if [ "${OLD_STAT}" != "${NEW_STAT}" ]
then
echo $'\n'"File ${FILE_TO_CHECK} has changed. Encrypting and backing up...";
#if the path which the encrypted file will be in does not exist, make it
if [ ! -d ${ENC_FILE_DIR} ]
then
`mkdir -p "${ENC_FILE_DIR}"`
fi
#create a compressed and encrypted backup file in the same directory structure (hashed now), except, in the backup folder
tar cz -C / "${FILE_TO_CHECK#"/"}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass pass:${PASSWORD} -e > "${ENC_FILE}${ENC_FILE_EXTENSION}";
# update the OLD_STAT_FILE
echo "${NEW_STAT}" > "${OLD_STAT_FILE}";
else
echo -n ".";
fi
else
echo -n "s";
fi
done < <(find ${DIRS_TO_BACKUP} -type f -print0)
#create the GDRIVE_COMPARE variable, listing the files in backup
GDRIVE_COMPARE=$(find "${BACKUP_LOCATION}" -type f);
#create the comparison files
if [ ! -e "${STAT_FILES_LOCATION}/gdrive.txt" ]
then
`touch "${STAT_FILES_LOCATION}/gdrive.txt"`
fi
if [ ! -e "${STAT_FILES_LOCATION}/origin.txt" ]
then
`touch "${STAT_FILES_LOCATION}/origin.txt"`
fi
#fill them with the list of files (with encrypted paths)
echo "${GDRIVE_COMPARE}" > "${STAT_FILES_LOCATION}/gdrive.txt"
echo "${ORIGIN_COMPARE}" > "${STAT_FILES_LOCATION}/origin.txt"
#compare the list of files newly-path-encrypted from origin to the already path-encrypted files in backup
COMPARE_RESULT=$(comm -23 <(sort "${STAT_FILES_LOCATION}/gdrive.txt") <(sort "${STAT_FILES_LOCATION}/origin.txt"));
#if there are any files that exist on gdrive yet not in origin
if [ "${COMPARE_RESULT}" ]
then
#loop through all the files (https://superuser.com/a/284226)
while read -r FILE_TO_DELETE;
do
#deleting every one of them
echo $'\n'"Deleting non-existent file from backups...";
`rm "${FILE_TO_DELETE}"`
#likewise, delete the corresponding stat file
STAT_FILE_DELETE="${STAT_FILES_LOCATION}${FILE_TO_DELETE#$BACKUP_LOCATION}"
STAT_FILE_DELETE="${STAT_FILE_DELETE%$ENC_FILE_EXTENSION}.txt"
`rm "${STAT_FILE_DELETE}"`
done <<< "${COMPARE_RESULT}"
fi
echo $'\n'"Deleting any empty directories from backups...";
#delete all empty directories in backup and stat, at this point the backups are ready to be uploaded
`find ${BACKUP_LOCATION} ${STAT_FILES_LOCATION} -type d -empty -delete`
echo "Done! Files were encrypted and stored in the backups. Now starting upload...";
#finally, upload all of the encrypted files
eval "${BACKUP_ENGINE_CMDS}"
echo "TurtleBackup is complete!";
exit 0