-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
552 additions
and
327 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,183 +1,235 @@ | ||
#!/bin/bash | ||
#!/usr/bin/env bash | ||
|
||
# requirements | ||
# ~/log, ~/backups, ~/path/to/example.com/public | ||
|
||
# version - 5.2.1 | ||
version=6.0.0 | ||
|
||
### Variables - Please do not add trailing slash in the PATHs | ||
|
||
# auto delete older backups after certain number days | ||
# configurable using -k|--keepfor <days> | ||
AUTODELETEAFTER=7 | ||
|
||
# where to store the database backups? | ||
BACKUP_PATH=${HOME}/backups/db-backups | ||
ENCRYPTED_BACKUP_PATH=${HOME}/backups/encrypted-db-backups | ||
|
||
# the script assumes your sites are stored like ~/sites/example.com/public, ~/sites/example.net/public, ~/sites/example.org/public and so on. | ||
# if you have a different pattern, such as ~/app/example.com/public, please change the following to fit the server environment! | ||
# a passphrase for encryption, in order to being able to use almost any special characters use "" | ||
# it's best to configure it in ~/.envrc file | ||
PASSPHRASE= | ||
|
||
# the script assumes your sites are stored like ~/sites/example.com, ~/sites/example.net, ~/sites/example.org and so on. | ||
# if you have a different pattern, such as ~/app/example.com, please change the following to fit the server environment! | ||
SITES_PATH=${HOME}/sites | ||
|
||
# if WP is in a sub-directory, please leave this empty! | ||
#-------- Do NOT Edit Below This Line --------# | ||
|
||
log_file=${HOME}/log/backups.log | ||
exec > >(tee -a "${log_file}") | ||
exec 2> >(tee -a "${log_file}" >&2) | ||
|
||
# Variables defined later in the script | ||
success_alert= | ||
custom_email= | ||
custom_wp_path= | ||
BUCKET_NAME= | ||
DOMAIN= | ||
PUBLIC_DIR=public | ||
|
||
# a passphrase for encryption, in order to being able to use almost any special characters use "" | ||
PASSPHRASE= | ||
# get environment variables, if exists | ||
[ -f "$HOME/.envrc" ] && source ~/.envrc | ||
[ -f "$HOME/.env" ] && source ~/.env | ||
|
||
# auto delete older backups after certain number days - default 60. YMMV | ||
AUTODELETEAFTER=30 | ||
print_help() { | ||
printf '%s\n' "Take a database backup" | ||
echo | ||
printf 'Usage: %s [-b <name>] [-k <days>] [-e <email-address>] [-s] [-p <WP path>] [-v] [-h] example.com\n' "$0" | ||
echo | ||
printf '\t%s\t%s\n' "-b, --bucket" "Name of the bucket for offsite backup (default: none)" | ||
printf '\t%s\t%s\n' "-k, --keepfor" "# of days to keep the local backups (default: 7)" | ||
printf '\t%s\t%s\n' "-e, --email" "Email to send success/failures alerts (default: root@localhost)" | ||
printf '\t%s\t%s\n' "-s, --success" "Alert on successful backup too (default: alert only on failures)" | ||
printf '\t%s\t%s\n' "-p, --path" "Path to WP files (default: ~/sites/example.com/public or ~/public_html for cPanel)" | ||
echo | ||
printf '\t%s\t%s\n' "-v, --version" "Prints the version info" | ||
printf '\t%s\t%s\n' "-h, --help" "Prints help" | ||
|
||
echo | ||
echo "For more info, changelog and documentation... https://github.com/pothi/backup-wordpress" | ||
} | ||
|
||
# https://stackoverflow.com/a/62616466/1004587 | ||
# Convenience functions. | ||
EOL=$(printf '\1\3\3\7') | ||
opt= | ||
usage_error () { echo >&2 "$(basename $0): $1"; exit 2; } | ||
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; } | ||
|
||
# One loop, nothing more. | ||
if [ "$#" != 0 ]; then | ||
set -- "$@" "$EOL" | ||
while [ "$1" != "$EOL" ]; do | ||
opt="$1"; shift | ||
case "$opt" in | ||
|
||
# Your options go here. | ||
-v|--version) echo $version; exit 0;; | ||
-V) echo $version; exit 0;; | ||
-h|--help) print_help; exit 0;; | ||
-b|--bucket) assert_argument "$1" "$opt"; BUCKET_NAME="$1"; shift;; | ||
-k|--keepfor) assert_argument "$1" "$opt"; AUTODELETEAFTER="$1"; shift;; | ||
-p|--path) assert_argument "$1" "$opt"; custom_wp_path="$1"; shift;; | ||
-e|--email) assert_argument "$1" "$opt"; custom_email="$1"; shift;; | ||
-s|--success) success_alert=1;; | ||
|
||
# Arguments processing. You may remove any unneeded line after the 1st. | ||
-|''|[!-]*) set -- "$@" "$opt";; # positional argument, rotate to the end | ||
--*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";; # convert '--name=arg' to '--name' 'arg' | ||
-[!-]?*) set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";; # convert '-abc' to '-a' '-b' '-c' | ||
--) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;; # process remaining arguments as positional | ||
-*) usage_error "unknown option: '$opt'";; # catch misspelled options | ||
*) usage_error "this should NEVER happen ($opt)";; # sanity test for previous patterns | ||
|
||
esac | ||
done | ||
shift # $EOL | ||
fi | ||
|
||
# You may hard-code the domain name | ||
DOMAIN= | ||
# Do something cool with "$@"... \o/ | ||
|
||
# AWS Variable can be hard-coded here | ||
AWS_S3_BUCKET_NAME= | ||
# Get example.com | ||
if [ "$#" -gt 0 ]; then | ||
DOMAIN=$1 | ||
shift | ||
else | ||
print_help | ||
exit 2 | ||
fi | ||
|
||
#-------- Do NOT Edit Below This Line --------# | ||
# compatibility with old syntax to get bucket name | ||
# To be removed in the future | ||
if [ "$#" -gt 0 ]; then | ||
BUCKET_NAME=$1 | ||
echo "You are using old syntax." | ||
print_help | ||
shift | ||
fi | ||
|
||
# unwanted argument/s | ||
if [ "$#" -gt 0 ]; then | ||
print_help | ||
exit 2 | ||
fi | ||
|
||
# to capture non-zero exit code in the pipeline | ||
set -o pipefail | ||
|
||
# attempt to create log directory if it doesn't exist | ||
[ -d "${HOME}/log" ] || mkdir -p ${HOME}/log | ||
if [ "$?" -ne "0" ]; then | ||
echo "Log directory not found at ~/log. This script can't create it, either!" | ||
echo 'You may create it manually and re-run this script.' | ||
exit 1 | ||
if [ ! -d "${HOME}/log" ]; then | ||
if ! mkdir -p "${HOME}"/log; then | ||
echo "Log directory not found at ~/log. This script can't create it, either!" | ||
echo 'You may create it manually and re-run this script.' | ||
exit 1 | ||
fi | ||
fi | ||
# attempt to create the backups directory, if it doesn't exist | ||
[ -d "$BACKUP_PATH" ] || mkdir -p $BACKUP_PATH | ||
if [ "$?" -ne "0" ]; then | ||
echo "BACKUP_PATH is not found at $BACKUP_PATH. This script can't create it, either!" | ||
echo 'You may create it manually and re-run this script.' | ||
exit 1 | ||
fi | ||
# if passphrase is supplied, attempt to create backups directory for encrypt backups, if it doesn't exist | ||
if [ -n "$PASSPHRASE" ]; then | ||
[ -d "$ENCRYPTED_BACKUP_PATH" ] || mkdir -p $ENCRYPTED_BACKUP_PATH | ||
if [ "$?" -ne "0" ]; then | ||
echo "ENCRYPTED_BACKUP_PATH Is not found at $ENCRYPTED_BACKUP_PATH. This script can't create it, either!" | ||
if [ ! -d "$BACKUP_PATH" ]; then | ||
if ! mkdir -p "$BACKUP_PATH"; then | ||
echo "BACKUP_PATH is not found at $BACKUP_PATH. This script can't create it, either!" | ||
echo 'You may create it manually and re-run this script.' | ||
exit 1 | ||
fi | ||
fi | ||
|
||
log_file=${HOME}/log/backups.log | ||
exec > >(tee -a ${log_file} ) | ||
exec 2> >(tee -a ${log_file} >&2) | ||
|
||
export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin | ||
|
||
declare -r script_name=$(basename "$0") | ||
declare -r timestamp=$(date +%F_%H-%M-%S) | ||
declare -r wp_cli=`which wp` | ||
declare -r aws_cli=`which aws` | ||
|
||
if [ -z "$wp_cli" ]; then | ||
echo "wp-cli is not found in $PATH. Exiting." | ||
exit 1 | ||
fi | ||
|
||
if [ -z "$aws_cli" ]; then | ||
echo "aws-cli is not found in $PATH. Exiting." | ||
exit 1 | ||
fi | ||
script_name=$(basename "$0") | ||
timestamp=$(date +%F_%H-%M-%S) | ||
|
||
cPanel=$(/usr/local/cpanel/cpanel -V 2>/dev/null) | ||
if [ ! -z "$cPanel" ]; then | ||
SITES_PATH=$HOME | ||
PUBLIC_DIR=public_html | ||
fi | ||
|
||
echo "'$script_name' started on... $(date +%c)" | ||
command -v wp >/dev/null || { echo >&2 "wp cli is not found in $PATH. Exiting."; exit 1; } | ||
command -v aws >/dev/null || { echo >&2 "aws cli is not found in $PATH. Exiting."; exit 1; } | ||
command -v mail >/dev/null || echo >&2 "[WARNING]: 'mail' command is not found in $PATH; Alerts will not be sent!" | ||
|
||
let AUTODELETEAFTER-- | ||
|
||
# get environment variables, if exists | ||
[ -f "$HOME/.envrc" ] && source ~/.envrc | ||
[ -f "$HOME/.env" ] && source ~/.env | ||
((AUTODELETEAFTER--)) | ||
|
||
# check for the variable/s in three places | ||
# 1 - hard-coded value | ||
# 2 - optional parameter while invoking the script | ||
# 3 - environment files | ||
|
||
if [ "$DOMAIN" == "" ]; then | ||
if [ "$1" == "" ]; then | ||
if [ "$WP_DOMAIN" != "" ]; then | ||
DOMAIN=$WP_DOMAIN | ||
else | ||
echo "Usage $script_name example.com (S3 bucket name)"; exit 1 | ||
fi | ||
else | ||
DOMAIN=$1 | ||
fi | ||
alertEmail=${custom_email:-${BACKUP_ADMIN_EMAIL:-${ADMIN_EMAIL:-"root@localhost"}}} | ||
|
||
# Define paths | ||
# cPanel uses a different directory structure | ||
# dir_to_backup and db_dump are used only in full-backup.sh | ||
cPanel=$(/usr/local/cpanel/cpanel -V 2>/dev/null) | ||
if [ "$cPanel" ]; then | ||
SITES_PATH=$HOME | ||
PUBLIC_DIR=public_html | ||
WP_PATH=${SITES_PATH}/${PUBLIC_DIR} | ||
# dir_to_backup=public_html | ||
# db_dump=${WP_PATH}/db.sql | ||
else | ||
WP_PATH=${SITES_PATH}/${DOMAIN}/${PUBLIC_DIR} | ||
# dir_to_backup=${DOMAIN} | ||
# db_dump=${SITES_PATH}/${DOMAIN}/db.sql | ||
fi | ||
|
||
if [ "$BUCKET_NAME" == "" ]; then | ||
if [ "$2" != "" ]; then | ||
BUCKET_NAME=$2 | ||
elif [ "$AWS_S3_BUCKET_NAME" != "" ]; then | ||
BUCKET_NAME=$AWS_S3_BUCKET_NAME | ||
fi | ||
if [ "$custom_wp_path" ]; then | ||
WP_PATH="$custom_wp_path" | ||
DB_OUTPUT_FILE_NAME=${custom_wp_path}/db.sql | ||
fi | ||
|
||
# WordPress root | ||
WP_PATH=${SITES_PATH}/${DOMAIN}/${PUBLIC_DIR} | ||
# For cPanel - main site | ||
[ ! -d "$WP_PATH" ] && WP_PATH=${SITES_PATH}/${PUBLIC_DIR} | ||
[ ! -d "$WP_PATH" ] && echo "WordPress is not found at $WP_PATH" && exit 1 | ||
|
||
echo "'$script_name' started on... $(date +%c)" | ||
|
||
# convert forward slash found in sub-directories to hyphen | ||
# ex: example.com/test would become example.com-test | ||
DOMAIN_FULL_PATH=$(echo $DOMAIN | awk '{gsub(/\//,"-")}; 1') | ||
DOMAIN_FULL_PATH=$(echo "$DOMAIN" | awk '{gsub(/\//,"-")}; 1') | ||
|
||
DB_OUTPUT_FILE_NAME=${BACKUP_PATH}/${DOMAIN_FULL_PATH}-${timestamp}.sql.gz | ||
ENCRYPTED_DB_OUTPUT_FILE_NAME=${ENCRYPTED_BACKUP_PATH}/${DOMAIN_FULL_PATH}-${timestamp}.sql.gz | ||
DB_LATEST_FILE_NAME=${BACKUP_PATH}/${DOMAIN_FULL_PATH}-latest.sql.gz | ||
|
||
# take actual DB backup | ||
if [ -f "$wp_cli" ]; then | ||
$wp_cli --path=${WP_PATH} transient delete --all | ||
$wp_cli --path=${WP_PATH} db export --no-tablespaces=true --add-drop-table - | gzip > $DB_OUTPUT_FILE_NAME | ||
if [ "$?" != "0" ]; then | ||
echo; echo 'Something went wrong while taking local backup!' | ||
[ -f $DB_OUTPUT_FILE_NAME ] && rm -f $DB_OUTPUT_FILE_NAME | ||
fi | ||
|
||
[ -L $DB_LATEST_FILE_NAME ] && rm $DB_LATEST_FILE_NAME | ||
if [ -n "$PASSPHRASE" ] ; then | ||
gpg --symmetric --passphrase $PASSPHRASE --batch -o ${ENCRYPTED_DB_OUTPUT_FILE_NAME} $DB_OUTPUT_FILE_NAME | ||
[ -f $DB_OUTPUT_FILE_NAME ] && rm -f $DB_OUTPUT_FILE_NAME | ||
ln -s $ENCRYPTED_DB_OUTPUT_FILE_NAME $DB_LATEST_FILE_NAME | ||
else | ||
ln -s $DB_OUTPUT_FILE_NAME $DB_LATEST_FILE_NAME | ||
fi | ||
wp --path="${WP_PATH}" transient delete --all | ||
if [ -n "$PASSPHRASE" ] ; then | ||
DB_OUTPUT_FILE_NAME="${DB_OUTPUT_FILE_NAME}".gpg | ||
wp --path="${WP_PATH}" db export --no-tablespaces=true --add-drop-table - | gzip | gpg --symmetric --passphrase "$PASSPHRASE" --batch -o "$DB_OUTPUT_FILE_NAME" | ||
else | ||
echo 'Please install wp-cli and re-run this script'; exit 1; | ||
wp --path="${WP_PATH}" db export --no-tablespaces=true --add-drop-table - | gzip > "$DB_OUTPUT_FILE_NAME" | ||
fi | ||
if [ "$?" != "0" ]; then | ||
msg='[Error] Something went wrong while taking DB backup!' | ||
echo; echo "$msg"; echo | ||
echo "$msg" | mail -s 'DB Backup Failure' "$alertEmail" | ||
[ -f "$DB_OUTPUT_FILE_NAME" ] && rm -f "$DB_OUTPUT_FILE_NAME" | ||
exit 1 | ||
fi | ||
|
||
# external backup | ||
if [ "$BUCKET_NAME" != "" ]; then | ||
if [ -z "$PASSPHRASE" ] ; then | ||
$aws_cli s3 cp $DB_OUTPUT_FILE_NAME s3://$BUCKET_NAME/${DOMAIN_FULL_PATH}/db-backups/ --only-show-errors | ||
else | ||
$aws_cli s3 cp $ENCRYPTED_DB_OUTPUT_FILE_NAME s3://$BUCKET_NAME/${DOMAIN_FULL_PATH}/encrypted-db-backups/ --only-show-errors | ||
fi | ||
if [ "$?" != "0" ]; then | ||
echo; echo 'Something went wrong while taking offsite backup'; | ||
echo "Check $LOG_FILE for any log info"; echo | ||
[ -L "$DB_LATEST_FILE_NAME" ] && rm "$DB_LATEST_FILE_NAME" | ||
ln -s "$DB_OUTPUT_FILE_NAME" "$DB_LATEST_FILE_NAME" | ||
|
||
# send the backup offsite | ||
if [ "$BUCKET_NAME" ]; then | ||
cmd="aws s3 cp $DB_OUTPUT_FILE_NAME s3://$BUCKET_NAME/${DOMAIN_FULL_PATH}/db-backups/ --only-show-errors" | ||
if $cmd; then | ||
msg='Offsite backup successful.' | ||
echo; echo "$msg"; echo | ||
[ "$success_alert" ] && echo "$msg" | mail -s 'Offsite Backup Info' "$alertEmail" | ||
else | ||
echo; echo 'Offsite backup successful'; echo | ||
msg='[Error] Something went wrong while taking offsite backup.' | ||
echo; echo "$msg"; echo | ||
echo "$msg" | mail -s 'Offsite Backup Info' "$alertEmail" | ||
fi | ||
fi | ||
|
||
# Auto delete backups | ||
[ -d "$BACKUP_PATH" ] && find $BACKUP_PATH -type f -mtime +$AUTODELETEAFTER -exec rm {} \; | ||
[ -d $ENCRYPTED_BACKUP_PATH ] && find $ENCRYPTED_BACKUP_PATH -type f -mtime +$AUTODELETEAFTER -exec rm {} \; | ||
# Auto delete backups | ||
find -L "$BACKUP_PATH" -type f -mtime +$AUTODELETEAFTER -exec rm {} \; | ||
|
||
if [ -z "$PASSPHRASE" ] ; then | ||
echo; echo 'DB backup is done without encryption: '${DB_LATEST_FILE_NAME}' -> '${DB_OUTPUT_FILE_NAME}; echo | ||
else | ||
echo; echo 'DB backup is done encrypted: '${DB_LATEST_FILE_NAME}' -> '${ENCRYPTED_DB_OUTPUT_FILE_NAME}; echo | ||
fi | ||
echo "Database backup is done; please check the latest backup in '${BACKUP_PATH}'." | ||
echo "Latest backup is at ${DB_OUTPUT_FILE_NAME}" | ||
|
||
echo "Script ended on... $(date +%c)" | ||
echo | ||
|
Oops, something went wrong.