Skip to content

Commit 289f820

Browse files
committed
Version 6!
1 parent 7374720 commit 289f820

File tree

5 files changed

+552
-327
lines changed

5 files changed

+552
-327
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ There are plenty of plugins available to take backups within WordPress. However,
1515
- Automatic deletion of local backups.
1616
- Support for sub-directory installation of WordPress!
1717
- Support for simple encryption using GnuPG
18+
- Alert via email when the offsite backup fails (and succeeds)
1819

1920
## Roadmap
2021

@@ -59,6 +60,8 @@ There are plenty of plugins available to take backups within WordPress. However,
5960
/path/to/db-backup.sh example3.com
6061
```
6162

63+
For more usage options, please run `/path/to/db-backup.sh -h`.
64+
6265
The above is applicable to all the scripts!
6366

6467
## Contributors

changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
version: 6.0.0
2+
- date: 2023-02-16
3+
- pass important variables as arguments. No breaking changes. Old usage format still works.
4+
- change the default location of DB backup inside a full backup
5+
- fix and simplify excludes in full backup.
6+
17
version: 5.3.0
28
- date: 2023-02-03
39
- alert upon success

db-backup.sh

Lines changed: 171 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,183 +1,235 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
# requirements
44
# ~/log, ~/backups, ~/path/to/example.com/public
55

6-
# version - 5.2.1
6+
version=6.0.0
77

88
### Variables - Please do not add trailing slash in the PATHs
99

10+
# auto delete older backups after certain number days
11+
# configurable using -k|--keepfor <days>
12+
AUTODELETEAFTER=7
13+
1014
# where to store the database backups?
1115
BACKUP_PATH=${HOME}/backups/db-backups
12-
ENCRYPTED_BACKUP_PATH=${HOME}/backups/encrypted-db-backups
1316

14-
# the script assumes your sites are stored like ~/sites/example.com/public, ~/sites/example.net/public, ~/sites/example.org/public and so on.
15-
# if you have a different pattern, such as ~/app/example.com/public, please change the following to fit the server environment!
17+
# a passphrase for encryption, in order to being able to use almost any special characters use ""
18+
# it's best to configure it in ~/.envrc file
19+
PASSPHRASE=
20+
21+
# the script assumes your sites are stored like ~/sites/example.com, ~/sites/example.net, ~/sites/example.org and so on.
22+
# if you have a different pattern, such as ~/app/example.com, please change the following to fit the server environment!
1623
SITES_PATH=${HOME}/sites
1724

18-
# if WP is in a sub-directory, please leave this empty!
25+
#-------- Do NOT Edit Below This Line --------#
26+
27+
log_file=${HOME}/log/backups.log
28+
exec > >(tee -a "${log_file}")
29+
exec 2> >(tee -a "${log_file}" >&2)
30+
31+
# Variables defined later in the script
32+
success_alert=
33+
custom_email=
34+
custom_wp_path=
35+
BUCKET_NAME=
36+
DOMAIN=
1937
PUBLIC_DIR=public
2038

21-
# a passphrase for encryption, in order to being able to use almost any special characters use ""
22-
PASSPHRASE=
39+
# get environment variables, if exists
40+
[ -f "$HOME/.envrc" ] && source ~/.envrc
41+
[ -f "$HOME/.env" ] && source ~/.env
2342

24-
# auto delete older backups after certain number days - default 60. YMMV
25-
AUTODELETEAFTER=30
43+
print_help() {
44+
printf '%s\n' "Take a database backup"
45+
echo
46+
printf 'Usage: %s [-b <name>] [-k <days>] [-e <email-address>] [-s] [-p <WP path>] [-v] [-h] example.com\n' "$0"
47+
echo
48+
printf '\t%s\t%s\n' "-b, --bucket" "Name of the bucket for offsite backup (default: none)"
49+
printf '\t%s\t%s\n' "-k, --keepfor" "# of days to keep the local backups (default: 7)"
50+
printf '\t%s\t%s\n' "-e, --email" "Email to send success/failures alerts (default: root@localhost)"
51+
printf '\t%s\t%s\n' "-s, --success" "Alert on successful backup too (default: alert only on failures)"
52+
printf '\t%s\t%s\n' "-p, --path" "Path to WP files (default: ~/sites/example.com/public or ~/public_html for cPanel)"
53+
echo
54+
printf '\t%s\t%s\n' "-v, --version" "Prints the version info"
55+
printf '\t%s\t%s\n' "-h, --help" "Prints help"
56+
57+
echo
58+
echo "For more info, changelog and documentation... https://github.com/pothi/backup-wordpress"
59+
}
60+
61+
# https://stackoverflow.com/a/62616466/1004587
62+
# Convenience functions.
63+
EOL=$(printf '\1\3\3\7')
64+
opt=
65+
usage_error () { echo >&2 "$(basename $0): $1"; exit 2; }
66+
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }
67+
68+
# One loop, nothing more.
69+
if [ "$#" != 0 ]; then
70+
set -- "$@" "$EOL"
71+
while [ "$1" != "$EOL" ]; do
72+
opt="$1"; shift
73+
case "$opt" in
74+
75+
# Your options go here.
76+
-v|--version) echo $version; exit 0;;
77+
-V) echo $version; exit 0;;
78+
-h|--help) print_help; exit 0;;
79+
-b|--bucket) assert_argument "$1" "$opt"; BUCKET_NAME="$1"; shift;;
80+
-k|--keepfor) assert_argument "$1" "$opt"; AUTODELETEAFTER="$1"; shift;;
81+
-p|--path) assert_argument "$1" "$opt"; custom_wp_path="$1"; shift;;
82+
-e|--email) assert_argument "$1" "$opt"; custom_email="$1"; shift;;
83+
-s|--success) success_alert=1;;
84+
85+
# Arguments processing. You may remove any unneeded line after the 1st.
86+
-|''|[!-]*) set -- "$@" "$opt";; # positional argument, rotate to the end
87+
--*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";; # convert '--name=arg' to '--name' 'arg'
88+
-[!-]?*) set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";; # convert '-abc' to '-a' '-b' '-c'
89+
--) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;; # process remaining arguments as positional
90+
-*) usage_error "unknown option: '$opt'";; # catch misspelled options
91+
*) usage_error "this should NEVER happen ($opt)";; # sanity test for previous patterns
92+
93+
esac
94+
done
95+
shift # $EOL
96+
fi
2697

27-
# You may hard-code the domain name
28-
DOMAIN=
98+
# Do something cool with "$@"... \o/
2999

30-
# AWS Variable can be hard-coded here
31-
AWS_S3_BUCKET_NAME=
100+
# Get example.com
101+
if [ "$#" -gt 0 ]; then
102+
DOMAIN=$1
103+
shift
104+
else
105+
print_help
106+
exit 2
107+
fi
32108

33-
#-------- Do NOT Edit Below This Line --------#
109+
# compatibility with old syntax to get bucket name
110+
# To be removed in the future
111+
if [ "$#" -gt 0 ]; then
112+
BUCKET_NAME=$1
113+
echo "You are using old syntax."
114+
print_help
115+
shift
116+
fi
117+
118+
# unwanted argument/s
119+
if [ "$#" -gt 0 ]; then
120+
print_help
121+
exit 2
122+
fi
34123

35124
# to capture non-zero exit code in the pipeline
36125
set -o pipefail
37126

38127
# attempt to create log directory if it doesn't exist
39-
[ -d "${HOME}/log" ] || mkdir -p ${HOME}/log
40-
if [ "$?" -ne "0" ]; then
41-
echo "Log directory not found at ~/log. This script can't create it, either!"
42-
echo 'You may create it manually and re-run this script.'
43-
exit 1
128+
if [ ! -d "${HOME}/log" ]; then
129+
if ! mkdir -p "${HOME}"/log; then
130+
echo "Log directory not found at ~/log. This script can't create it, either!"
131+
echo 'You may create it manually and re-run this script.'
132+
exit 1
133+
fi
44134
fi
45135
# attempt to create the backups directory, if it doesn't exist
46-
[ -d "$BACKUP_PATH" ] || mkdir -p $BACKUP_PATH
47-
if [ "$?" -ne "0" ]; then
48-
echo "BACKUP_PATH is not found at $BACKUP_PATH. This script can't create it, either!"
49-
echo 'You may create it manually and re-run this script.'
50-
exit 1
51-
fi
52-
# if passphrase is supplied, attempt to create backups directory for encrypt backups, if it doesn't exist
53-
if [ -n "$PASSPHRASE" ]; then
54-
[ -d "$ENCRYPTED_BACKUP_PATH" ] || mkdir -p $ENCRYPTED_BACKUP_PATH
55-
if [ "$?" -ne "0" ]; then
56-
echo "ENCRYPTED_BACKUP_PATH Is not found at $ENCRYPTED_BACKUP_PATH. This script can't create it, either!"
136+
if [ ! -d "$BACKUP_PATH" ]; then
137+
if ! mkdir -p "$BACKUP_PATH"; then
138+
echo "BACKUP_PATH is not found at $BACKUP_PATH. This script can't create it, either!"
57139
echo 'You may create it manually and re-run this script.'
58140
exit 1
59141
fi
60142
fi
61143

62-
log_file=${HOME}/log/backups.log
63-
exec > >(tee -a ${log_file} )
64-
exec 2> >(tee -a ${log_file} >&2)
65-
66144
export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
67145

68-
declare -r script_name=$(basename "$0")
69-
declare -r timestamp=$(date +%F_%H-%M-%S)
70-
declare -r wp_cli=`which wp`
71-
declare -r aws_cli=`which aws`
72-
73-
if [ -z "$wp_cli" ]; then
74-
echo "wp-cli is not found in $PATH. Exiting."
75-
exit 1
76-
fi
77-
78-
if [ -z "$aws_cli" ]; then
79-
echo "aws-cli is not found in $PATH. Exiting."
80-
exit 1
81-
fi
146+
script_name=$(basename "$0")
147+
timestamp=$(date +%F_%H-%M-%S)
82148

83-
cPanel=$(/usr/local/cpanel/cpanel -V 2>/dev/null)
84-
if [ ! -z "$cPanel" ]; then
85-
SITES_PATH=$HOME
86-
PUBLIC_DIR=public_html
87-
fi
88-
89-
echo "'$script_name' started on... $(date +%c)"
149+
command -v wp >/dev/null || { echo >&2 "wp cli is not found in $PATH. Exiting."; exit 1; }
150+
command -v aws >/dev/null || { echo >&2 "aws cli is not found in $PATH. Exiting."; exit 1; }
151+
command -v mail >/dev/null || echo >&2 "[WARNING]: 'mail' command is not found in $PATH; Alerts will not be sent!"
90152

91-
let AUTODELETEAFTER--
92-
93-
# get environment variables, if exists
94-
[ -f "$HOME/.envrc" ] && source ~/.envrc
95-
[ -f "$HOME/.env" ] && source ~/.env
153+
((AUTODELETEAFTER--))
96154

97155
# check for the variable/s in three places
98156
# 1 - hard-coded value
99157
# 2 - optional parameter while invoking the script
100158
# 3 - environment files
101159

102-
if [ "$DOMAIN" == "" ]; then
103-
if [ "$1" == "" ]; then
104-
if [ "$WP_DOMAIN" != "" ]; then
105-
DOMAIN=$WP_DOMAIN
106-
else
107-
echo "Usage $script_name example.com (S3 bucket name)"; exit 1
108-
fi
109-
else
110-
DOMAIN=$1
111-
fi
160+
alertEmail=${custom_email:-${BACKUP_ADMIN_EMAIL:-${ADMIN_EMAIL:-"root@localhost"}}}
161+
162+
# Define paths
163+
# cPanel uses a different directory structure
164+
# dir_to_backup and db_dump are used only in full-backup.sh
165+
cPanel=$(/usr/local/cpanel/cpanel -V 2>/dev/null)
166+
if [ "$cPanel" ]; then
167+
SITES_PATH=$HOME
168+
PUBLIC_DIR=public_html
169+
WP_PATH=${SITES_PATH}/${PUBLIC_DIR}
170+
# dir_to_backup=public_html
171+
# db_dump=${WP_PATH}/db.sql
172+
else
173+
WP_PATH=${SITES_PATH}/${DOMAIN}/${PUBLIC_DIR}
174+
# dir_to_backup=${DOMAIN}
175+
# db_dump=${SITES_PATH}/${DOMAIN}/db.sql
112176
fi
113177

114-
if [ "$BUCKET_NAME" == "" ]; then
115-
if [ "$2" != "" ]; then
116-
BUCKET_NAME=$2
117-
elif [ "$AWS_S3_BUCKET_NAME" != "" ]; then
118-
BUCKET_NAME=$AWS_S3_BUCKET_NAME
119-
fi
178+
if [ "$custom_wp_path" ]; then
179+
WP_PATH="$custom_wp_path"
180+
DB_OUTPUT_FILE_NAME=${custom_wp_path}/db.sql
120181
fi
121182

122-
# WordPress root
123-
WP_PATH=${SITES_PATH}/${DOMAIN}/${PUBLIC_DIR}
124-
# For cPanel - main site
125-
[ ! -d "$WP_PATH" ] && WP_PATH=${SITES_PATH}/${PUBLIC_DIR}
126183
[ ! -d "$WP_PATH" ] && echo "WordPress is not found at $WP_PATH" && exit 1
127184

185+
echo "'$script_name' started on... $(date +%c)"
186+
128187
# convert forward slash found in sub-directories to hyphen
129188
# ex: example.com/test would become example.com-test
130-
DOMAIN_FULL_PATH=$(echo $DOMAIN | awk '{gsub(/\//,"-")}; 1')
189+
DOMAIN_FULL_PATH=$(echo "$DOMAIN" | awk '{gsub(/\//,"-")}; 1')
131190

132191
DB_OUTPUT_FILE_NAME=${BACKUP_PATH}/${DOMAIN_FULL_PATH}-${timestamp}.sql.gz
133-
ENCRYPTED_DB_OUTPUT_FILE_NAME=${ENCRYPTED_BACKUP_PATH}/${DOMAIN_FULL_PATH}-${timestamp}.sql.gz
134192
DB_LATEST_FILE_NAME=${BACKUP_PATH}/${DOMAIN_FULL_PATH}-latest.sql.gz
135193

136194
# take actual DB backup
137-
if [ -f "$wp_cli" ]; then
138-
$wp_cli --path=${WP_PATH} transient delete --all
139-
$wp_cli --path=${WP_PATH} db export --no-tablespaces=true --add-drop-table - | gzip > $DB_OUTPUT_FILE_NAME
140-
if [ "$?" != "0" ]; then
141-
echo; echo 'Something went wrong while taking local backup!'
142-
[ -f $DB_OUTPUT_FILE_NAME ] && rm -f $DB_OUTPUT_FILE_NAME
143-
fi
144-
145-
[ -L $DB_LATEST_FILE_NAME ] && rm $DB_LATEST_FILE_NAME
146-
if [ -n "$PASSPHRASE" ] ; then
147-
gpg --symmetric --passphrase $PASSPHRASE --batch -o ${ENCRYPTED_DB_OUTPUT_FILE_NAME} $DB_OUTPUT_FILE_NAME
148-
[ -f $DB_OUTPUT_FILE_NAME ] && rm -f $DB_OUTPUT_FILE_NAME
149-
ln -s $ENCRYPTED_DB_OUTPUT_FILE_NAME $DB_LATEST_FILE_NAME
150-
else
151-
ln -s $DB_OUTPUT_FILE_NAME $DB_LATEST_FILE_NAME
152-
fi
195+
wp --path="${WP_PATH}" transient delete --all
196+
if [ -n "$PASSPHRASE" ] ; then
197+
DB_OUTPUT_FILE_NAME="${DB_OUTPUT_FILE_NAME}".gpg
198+
wp --path="${WP_PATH}" db export --no-tablespaces=true --add-drop-table - | gzip | gpg --symmetric --passphrase "$PASSPHRASE" --batch -o "$DB_OUTPUT_FILE_NAME"
153199
else
154-
echo 'Please install wp-cli and re-run this script'; exit 1;
200+
wp --path="${WP_PATH}" db export --no-tablespaces=true --add-drop-table - | gzip > "$DB_OUTPUT_FILE_NAME"
201+
fi
202+
if [ "$?" != "0" ]; then
203+
msg='[Error] Something went wrong while taking DB backup!'
204+
echo; echo "$msg"; echo
205+
echo "$msg" | mail -s 'DB Backup Failure' "$alertEmail"
206+
[ -f "$DB_OUTPUT_FILE_NAME" ] && rm -f "$DB_OUTPUT_FILE_NAME"
207+
exit 1
155208
fi
156209

157-
# external backup
158-
if [ "$BUCKET_NAME" != "" ]; then
159-
if [ -z "$PASSPHRASE" ] ; then
160-
$aws_cli s3 cp $DB_OUTPUT_FILE_NAME s3://$BUCKET_NAME/${DOMAIN_FULL_PATH}/db-backups/ --only-show-errors
161-
else
162-
$aws_cli s3 cp $ENCRYPTED_DB_OUTPUT_FILE_NAME s3://$BUCKET_NAME/${DOMAIN_FULL_PATH}/encrypted-db-backups/ --only-show-errors
163-
fi
164-
if [ "$?" != "0" ]; then
165-
echo; echo 'Something went wrong while taking offsite backup';
166-
echo "Check $LOG_FILE for any log info"; echo
210+
[ -L "$DB_LATEST_FILE_NAME" ] && rm "$DB_LATEST_FILE_NAME"
211+
ln -s "$DB_OUTPUT_FILE_NAME" "$DB_LATEST_FILE_NAME"
212+
213+
# send the backup offsite
214+
if [ "$BUCKET_NAME" ]; then
215+
cmd="aws s3 cp $DB_OUTPUT_FILE_NAME s3://$BUCKET_NAME/${DOMAIN_FULL_PATH}/db-backups/ --only-show-errors"
216+
if $cmd; then
217+
msg='Offsite backup successful.'
218+
echo; echo "$msg"; echo
219+
[ "$success_alert" ] && echo "$msg" | mail -s 'Offsite Backup Info' "$alertEmail"
167220
else
168-
echo; echo 'Offsite backup successful'; echo
221+
msg='[Error] Something went wrong while taking offsite backup.'
222+
echo; echo "$msg"; echo
223+
echo "$msg" | mail -s 'Offsite Backup Info' "$alertEmail"
169224
fi
170225
fi
171226

172-
# Auto delete backups
173-
[ -d "$BACKUP_PATH" ] && find $BACKUP_PATH -type f -mtime +$AUTODELETEAFTER -exec rm {} \;
174-
[ -d $ENCRYPTED_BACKUP_PATH ] && find $ENCRYPTED_BACKUP_PATH -type f -mtime +$AUTODELETEAFTER -exec rm {} \;
227+
# Auto delete backups
228+
find -L "$BACKUP_PATH" -type f -mtime +$AUTODELETEAFTER -exec rm {} \;
175229

176-
if [ -z "$PASSPHRASE" ] ; then
177-
echo; echo 'DB backup is done without encryption: '${DB_LATEST_FILE_NAME}' -> '${DB_OUTPUT_FILE_NAME}; echo
178-
else
179-
echo; echo 'DB backup is done encrypted: '${DB_LATEST_FILE_NAME}' -> '${ENCRYPTED_DB_OUTPUT_FILE_NAME}; echo
180-
fi
230+
echo "Database backup is done; please check the latest backup in '${BACKUP_PATH}'."
231+
echo "Latest backup is at ${DB_OUTPUT_FILE_NAME}"
181232

182233
echo "Script ended on... $(date +%c)"
234+
echo
183235

0 commit comments

Comments
 (0)