Writing a WordPress Restoration script

W

WordPress sites get hacked all the time, because the typical WordPress blogger install 100’s of shitty plugins and rarely updates their site. On the one hand, it’s great that WordPress has empowered so many people to begin blogging without requiring the ‘hard’ technical skills, on the other it just gives criminals a large number of potential victims.

Two years ago, when I studied the details of phishing attacks that targeted Maybank and RHB, I found that attackers use compromised WordPress sites to host their phishing content. They’d first hack into a seemingly random WordPress website, host their phishing content there, and then blast out emails to unsuspecting victims with links to pointing back to their hacked bounty. If the hack works they’d get free username and passwords, and if they were ever caught, most evidence would point to the unsuspecting WordPress site owner.

So if you have a WordPress site (like me), chances are you’re in the cross-hairs of hackers already, and securing your site is the responsible thing to do.

In general WordPress sites should be:

  • Updated Automatically
  • Use a minimal number of plugins
  • Use plugins only from reputable publishers
  • Use themes only from reputable publishers–and have only one theme in the install directory
  • Employ strong passwords for the admin & user
  • Have the permissions of the underlying folders set accordingly (i.e.CHMOD them all)

But even if you took all precautions to hardened your site, there’s always a possibility of it getting hacked. No security is perfect, and you should look into backups–backup often and to a separate location. That way, a compromised site can be rebuilt, even if it were defaced. The last thing you want is to lose your precious design and data, because some one installed a shitty plugin over the weekend.

Today, I’ll walk through a short bash script I wrote to backup (and restore) a WordPress installation from scratch. It took me quite a while to write this (partly because I have no experience with Bash scripts), but I thought it would be good to walkthrough the details of the script and what it does.

The full script is available on github here, and the usage instructions will be maintained there. The write-up below describes code the first production release, linked here, even though I’ve since updated the scripts to include some modifications, and as we speak I’m just about the release version 1.2.

So here we go…

Special Thanks

The following 3 folks, were greatly influential in the writing of the script, listed in no particular order. No to mention, the wonderful folks at stackoverflow that helped tremendously as well.

Thanks to Andrea Fabrizi for the awesome DropboxUploader script
Thanks to Ben Kulbertis for the awesome Cloudflare update script
Thanks to Peteris.Rocks for inspiring me with his Unattended WordPress Installation script

Pre-Requisites

As a pre-requisite to all this, I made the following decisions.

The back ups would be stored in DropBox– Dropbox has free options (up to 2GB) and has versioning by default.All your backups are versioned and kept for 30 days (not just the latest upload, which gets destroyed if you’re hit by malware). Doing this on AWS requires extra work, which I wasn’t prepared to do, and AWS has no free tier for S3 storage.

Also, I use CloudFlare to maintain the DNS. It’s optional of course, but I needed a DNS provider that had an API, and they were the logical choice. This allowed the script to update your DNS as well.

Finally, the script assumes a standard LAMP stack, i.e. Linux (specifically Ubuntu 16.04), Apache , MySql and PHP. PHP is enforced by WordPress itself so that’s fine.But the ‘trend’ these days is to have NGINX instead of Apache, and MariaDB instead of MySQL. I kept things in ‘classic’ mode for now, I may revisit in the future.

High level setup

The entire thing (both backup and restore) runs on Bash scripts. There are 5 .sh files, 3 of which are core :

  • setup.sh – first time initialization of the scripts (use this for a site that already has WordPress installed)
  • backupWP.sh – backs up the WordPress Installation (called from a cronjob)
  • restoreWP.sh – restores WordPress Installation on a blank machine

The remaining 2 non-core files:

  • cloudflare.sh – updates Cloudflare DNS entry
  • functions.sh – common functions between 3 core files

Non-core files are only called from the core scripts, and are used to keep common functions in a single re-usable location.

File 1: Setup.sh

Setup.sh takes in 4 command line arguments as defined in the README.md file. I won’t repeat here. Once all the usual command-line parameter parsing is done, we get into the meat of things.

Section 1: Download DropboxUploader

DropboxUploader is a fantastic piece of code that enables upload/download to Dropbox via command line. The first line of my code downloads it and sets it up according.

#---------------------------------------------------------------------------------------
# Download DropboxUploader and Setup
#---------------------------------------------------------------------------------------
GetDropboxUploader $DROPBOXTOKEN #in functions.sh

Section 2: Setup wpsettings File

The .wpsettings file is an important configuration file created by the script to store values for future use. Since the back up script should run everyday without user intervention, we need to store the configuration parameters for this setup somewhere on disk, and I used a file called .wpsettings stored in the user folder.

To maintain idempotency, the script first checks for any old .wpsettings file and deletes them before starting. You’ll see this often throughout the script to ensure that I can run the script over and over again without any worries.

Once done, I create a .wpsettings file from scratch that stores:

  • WPDIR : The WordPress installation directory
  • WPCONFDIR: The WordPress configuration Directory (the wp-config.php file can be stored somewhere else)
  • DROPBOXPATH: The Path for the DropBoxUploader.

The script also creates a an .enckey file, that stores the encryption key. All uploads to dropbox are encrypted before transmission (for added security), and obviously the encrypted key shouldn’t be uploaded to Dropbox. This way, even if your Dropbox is compromised, the backup wordpress files have a separate layer of protection afforded by AES-256 encryption.

When you wish to restore you’ll need the provide the encryption key to the restoration script.I created the full call (including the encryption key as a command-line argument) and stored it in Lastpass under my encrypted notes. That way, I can simply spin up an instance of Ubuntu on any cloud provider, and run a few lines of code I copied from Lastpass to restore my site.

#---------------------------------------------------------------------------------------
# Setup .wpsettings file
#---------------------------------------------------------------------------------------
if [ -f "$WPSETTINGSFILE" ]; then
echo "Deleting old $WPSETTINGSFILE (probably from previous installation)"
rm $WPSETTINGSFILE
fi

echo "WPDIR=$WPDIR" >> $WPSETTINGSFILE #store wordpress directory in config file
echo "WPCONFDIR=$WPCONFDIR" >> $WPSETTINGSFILE #store wordpress config (wp-config.php) directory in config file
echo "DROPBOXPATH=$DROPBOXPATH" >> $WPSETTINGSFILE #store dropbox uploader path in directory

SetEncKey $ENCKEY #in functions.sh

Section 3: Setup CRON job

At the end, a Cronjob is created to ensure backupWP.sh runs are a specified time everyday. For more info on these functions look into functions.sh for SetCronJob

#---------------------------------------------------------------------------------------
# Download Backup Script and create CRON job
#---------------------------------------------------------------------------------------

SetCronJob #in functions.sh

echo "Setup Complete"

File 2: backupWP.sh

backupWP.sh doesn’t take in any command line arguments (how could it? it runs automatically everyday)

Section 1: Delete previous backups

To ensure everything is idempotent, again we have check if the backup directory exist. If it does, we delete it and create a new one, ensuring we have a nice and empty backup folder to store our new backups in.

#---------------------------------------------------------------------------------------
# Download Backup Script and create CRON job
#---------------------------------------------------------------------------------------

SetCronJob #in functions.sh

echo "Setup Complete"

Section 2: Backup DB

The following section grabs the data from wp-config.php to access the database. And then makes a .sql backup of the database. Reading the database parameters from the wp-config.php file ensures that I don’t have to store the database credentials anywhere else.

#-------------------------------------------------------------------------
# mysqldump the MYSQL Database
#-------------------------------------------------------------------------
echo -e "\\n######### Backing Up Mysql Database BEGIN #########\\n"

WPDBNAME=`cat $WPCONFDIR/wp-config.php | grep DB_NAME | cut -d \' -f 4`
WPDBUSER=`cat $WPCONFDIR/wp-config.php | grep DB_USER | cut -d \' -f 4`
WPDBPASS=`cat $WPCONFDIR/wp-config.php | grep DB_PASSWORD | cut -d \' -f 4`

if [ -z $WPDBNAME ]; then
    echo "ERROR: unable to extract DB NAME from $WPCONFDIR/wp-config.php"
    exit 0
else
    echo "INFO: Dumping MYSQL Files"
    mysqldump -u $WPDBUSER -p$WPDBPASS $WPDBNAME | sudo tee $BACKUPPATH/$WPSQLFILE > /dev/null
    echo "GOOD: MYSQL successfully backed up to $BACKUPPATH/$WPSQLFILE"
fi

echo -e "\\n#########    END    #########\\n"

Section 3: Zip WordPress & Apache Directory

A separate assumption is that WordPress is installed with Apache, and this part of the script creates two zip files, one for the WordPress installation, and another for the Apache configuration (typically /etc/apache2).

This allows us to copy over SSL certs and apache configuration to restore, on the fly. Later on the script, we grab data from letsencrypt (if it exist).

#-------------------------------------------------------------------------
# Zip $WPDIR folder
#-------------------------------------------------------------------------
echo -e "\\n######### Zipping WordPress BEGIN #########\\n"

echo "INFO: Zipping the $WPDIR to : $BACKUPPATH/$WPZIPFILE"
sudo tar -czf $BACKUPPATH/$WPZIPFILE -C $WPDIR . #turn off verbose and don't keep directory structure
echo "INFO: $WPDIR successfully zipped to $BACKUPPATH/$WPZIPFILE"

echo -e "\\n#########    END    #########\\n"
#-------------------------------------------------------------------------
# Copy all Apache Configurations files
#-------------------------------------------------------------------------
echo -e "\\n######### Zipping APACHE BEGIN #########\\n"

echo "INFO: Zipping $APACHEDIR"
sudo tar -czf $BACKUPPATH/$APACHECONFIG -C $APACHEDIR . #turn off verbose and don't keep directory structure
echo "INFO: $APACHEDIR successfully zipped to $BACKUPPATH/$WPZIPFILE"

echo -e "\\n#########    END    #########\\n"

Section 4: Encrypt backup files and remove unencrypted ones

Before uploading to Dropbox, we need to encrypt the files (for security purposes) and delete the unencrypted ones, so that they’re not lying around.

#-------------------------------------------------------------------------
# Encrypting files before uploading
#-------------------------------------------------------------------------
echo -e "\\n######### Encrypting files BEGIN #########\\n"

echo -e "INFO: Encrypting MYSQL FIles"
sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$WPSQLFILE -out $BACKUPPATH/$WPSQLFILE.enc -k $ENCKEY
sudo rm $BACKUPPATH/$WPSQLFILE #remove unencrypted file

echo -e "INFO: Encrypting WordPress Backup file:"
sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$WPZIPFILE -out $BACKUPPATH/$WPZIPFILE.enc -k $ENCKEY
sudo rm $BACKUPPATH/$WPZIPFILE #remove unencrypted file

echo -e "INFO: Encrypting Apache Configuration"
sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$APACHECONFIG -out $BACKUPPATH/$APACHECONFIG.enc -k $ENCKEY
sudo rm $BACKUPPATH/$APACHECONFIG #remove unencrypted file



# Encrypt wp-config.php file
if [ "$WPCONFDIR" != "$WPDIR" ]; then #already copied, don't proceed
    echo "INFO: Encrypting wp-config.php file in $WPCONFDIR"   
    sudo openssl enc -aes-256-cbc -in $WPCONFDIR/$WPCONFIGFILE -out $BACKUPPATH/$WPCONFIGFILE.enc -k $ENCKEY
else
    echo "INFO: wp-config.php file is in the wordpress directory, no separate zipping necessary"
fi

sudo openssl enc -aes-256-cbc -in $WPSETTINGSFILE -out $BACKUPPATH/$WPSETTINGSFILENAME.enc -k $ENCKEY
echo -e "WARNING: The encryption key in $ENCKEYFILE will not be uploaded to Dropbox"
echo -e "WARNING: Store $ENCKEYFILE in a safe place"

echo -e "\\n#########    END    #########\\n"

Section 5: Upload to Dropbox

Finally we use dropbox_uploader.sh (which was downloaded during setup.sh), to upload the files into Dropbox.

#-------------------------------------------------------------------------
# Upload to Dropbox
#-------------------------------------------------------------------------
echo -e "\\n######### Upload to Dropbox BEGIN #########\\n"

echo -e "INFO: Uploading Files to Dropbox"
sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPSQLFILE.enc /
sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPZIPFILE.enc /
sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$APACHECONFIG.enc /
if [ "$WPCONFDIR" != "$WPDIR" ]; then #already copied, don't proceed
    sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPCONFIGFILE.enc /
fi
sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$WPSETTINGSFILENAME.enc /

echo -e "\\n#########    END    #########\\n"

Section 6: Letsencrypt

As you can tell, this was a last-minute bolt-on. If you used Letsencrypt to generate your certificates, this part of the script copies across the letsencryt configuration and files to be restored later on.

You might be wondering–why back this up at all? Why not just run letsencrypt during the restoration process, and you’d be right. But as I said, this was a last-minute bolt-on.

Letsencrypt only works if your domain already resolves to the new server. During restoration, you might find yourself waiting 24 hours before the new DNS entry propagates across the internet.–hence copying over letsencrypt gives you the opportunity you set server even before the resolution has been fixed.

#-------------------------------------------------------------------------
# Lets Encrypt
#-------------------------------------------------------------------------
echo -e "\\n######### LetsEncrypt BEGIN #########\\n"
if [ -d $LETSENCRYPTDIR ]; then
    echo -e "INFO: LetsEncrypt detected, backing up files"
    sudo tar -czf $BACKUPPATH/$LETSENCRYPTCONFIG -C $LETSENCRYPTDIR .
    echo -e "INFO: Encrypting Letsencrypt Configuration"
    sudo openssl enc -aes-256-cbc -in $BACKUPPATH/$LETSENCRYPTCONFIG -out $BACKUPPATH/$LETSENCRYPTCONFIG.enc -k $ENCKEY
    sudo rm $BACKUPPATH/$LETSENCRYPTCONFIG #remove unencrypted file
    echo -e "INFO: Uploading Letsencrypt to Dropbox"
    sudo $DROPBOXPATH/dropbox_uploader.sh upload $BACKUPPATH/$LETSENCRYPTCONFIG.enc /
else
    echo -e "LetsEncrypt not found"
fi
echo -e "\\n#########    END    #########\\n"

echo -e "\\n#########    SCRIPT END    #########\\n"

 

File 3: restoreWP.sh

Easily the most complex of the 3 core files is restoreWP.sh. This script is meant to run on a bare Ubuntu 16.04 box, and will build the WordPress Installation from scratch, specifically:

  • Update Cloudflare DNS entry to point to the new server (optional)
  • Download the backup files from Dropbox
  • Restore WordPress files (preserving directory & configuration..passwords etc)
  • Download necessary packages for MySQL
  • Restore WordPress Database (preserving username, password, db name..etc)
  • Download all necessary packages Apache & PHP
  • Restore Apache configuration (optional) or; Build Apache configuration from scratch
  • Setup backup script (to continue backing up to cloud)
  • Setup a swapfile configuration (1GB)
  • Setup firewall rules for Ubuntu (ports 22,80,443 only)
  • Restore letsencrypt configuration from backup or; Call letsencrypt to get certificates or

Everything here has an order for a reason.

  • Cloudflare ‘should’ got first, because DNS propagation takes time. Placing it first is just logical.
  • Download the files from Dropbox before installing packages, because if downloads aren’t available, end script now
  • We restore wordpress first, because we need the DB parameters from wp-config file to setup DB
  • Setup DB according to user,database name and password set in previous WP configuration
  • Finally we restore Apache last, because it’s the most complicated
  • Once there, we setup backup, swapfile and firewall
  • Only then call letsencrypt because letsencrypt is the only interactive portion
    • I chose to use standard letsencrypt call which is interactive (Certbot)
    • but it means I’m not maintaining a custom script for this…more sustainable

Section 0: Main Initialization

#---------------------------------------------------------------------------------------
# Main-Initilization
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### REPO UPDATE #########\\n\\n"

echo "INFO: Updating REPO"
sudo apt-get update >>$LOGFILE
#we will upgrade after deletion of unwanted packages
export DEBIAN_FRONTEND=noninteractive #Silence all interactions

Section 1: Update Cloudflare DNS entry

A quick update on Cloudflare, I placed it at the beginning simply because DNS updates take time to propogate, so putting it upfront seemed to make sense.

What makes less sense though, is that if the script fails later on, the DNS has already been updated. That’s a risk worth taking. If you really are worried don’t supply the cloudflare credentials and the DNS update won’t occur.

#---------------------------------------------------------------------------------------
# DNS Update with Cloudflare - (done first because it takes time to propagate)
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### CLOUDFLARE UPDATE #########\\n\\n"

if [ "$DNSUPDATE" = true ]; then
	
	echo "INFO: Updating cloudflare record $CFRECORD in zone $CFZONE using credentials $CFEMAIL , $CFKEY "
	./cloudflare.sh --email $CFEMAIL --key $CFKEY --zone $CFZONE --record $CFRECORD
	echo "INFO: Removing Cloudflare script"
	rm cloudflare.sh #you only need it once
	echo "GOOD: Cloudflare update complete"
	
else
	echo "WARNING: DNS wasn't updated"
fi

echo -e "\\n\\n######### CLOUDFLARE UPDATE END#########"

Section 2: Remove Previous Installations (Idempotent)

Just to make sure the server is a blank Ubuntu, we uninstall all the stuff we’ll install later on.

#---------------------------------------------------------------------------------------
# Remove previous installations if necessary
#---------------------------------------------------------------------------------------
echo "INFO: Attempting to delete older packages if they exist -- idempotency"
sudo apt-get --purge -y remove mysql* >>$LOGFILE #remove all mysql packages
sudo apt-get --purge -y remove apache2 >>$LOGFILE 
sudo apt-get --purge -y remove php >>$LOGFILE
sudo apt-get --purge -y remove libapache2-mod-php >>$LOGFILE
sudo apt-get --purge -y remove php-mcrypt >>$LOGFILE
sudo apt-get --purge -y remove php-mysql >>$LOGFILE
sudo apt-get --purge -y remove python-letsencrypt-apache >>$LOGFILE

sudo apt-get -y autoremove >>$LOGFILE
sudo apt-get -y autoclean >>$LOGFILE

echo "INFO: Upgrading installed packages" #do this after deletion to avoid upgrading packages set for deletion
#sudo apt-get upgrade >>$LOGFILE #Disabled for now

echo -e "\\n\\n######### REPO UPDATE COMPLETE #########\\n\\n"

Section 3: Download files from Dropbox

Setup DropboxUploader, and then download the backup files from Dropbox.

$WPSETTINGSFILE is downloaded first, because it contains the basic wordpress configuration information, which will be useful later on. You’ll notice I use openssl to decrypt the files, because by default all files are encrypted to before being uploaded to Dropbox as part of the backup script.

#---------------------------------------------------------------------------------------
#Setup DropboxUploader
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Downloading from Dropbox #########\\n\\n"

GetDropboxUploader $DROPBOXTOKEN #in functions.sh

#---------------------------------------------------------------------------------------
#Download .wpsettings file
#---------------------------------------------------------------------------------------
delFile $WPSETTINGSFILE
delFile $WPSETTINGSFILEDIR/$WPSETTINGSFILE #remove old wpsettings file (if exists)--functions.sh

echo "INFO: Checking if $WPSETTINGSFILE exist on Dropbox"
sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPSETTINGSFILE.enc #wpsettings file

if [ -f $WPSETTINGSFILE.enc ]; then
	echo "GOOD: $WPSETTINGSFILE exist, decrypting and loading"
	sudo openssl enc -aes-256-cbc -d -in $WPSETTINGSFILE.enc -out $WPSETTINGSFILEDIR/$WPSETTINGSFILE -k $ENCKEY 
	echo "INFO: Loading $WPSETTINGSFILE"
	source "$WPSETTINGSFILEDIR/$WPSETTINGSFILE" 2>/dev/null #file exist, load variables
else 
	echo "ERROR: unable to find $WPSETTINGSFILE, check dropbox location to see if the file exists"
	exit 0
fi


#---------------------------------------------------------------------------------------
#Download files from dropbox
#---------------------------------------------------------------------------------------
delFile $WPSQLFILE #delete files if it exist, functions.sh
delFile $WPZIPFILE
delFile $APACHECONFIG
delFile $LETSENCRYPTCONFIG
delFile $WPCONFIGFILE

echo "INFO: Downloading and decrypting SQL backup file"
sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPSQLFILE.enc #Wordpress.sql file
sudo openssl enc -aes-256-cbc -d -in $WPSQLFILE.enc -out $WPSQLFILE -k $ENCKEY 

echo "INFO: Downloading and decrypting WordPress zip file"
sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPZIPFILE.enc #zip file with all wordpress contents
sudo openssl enc -aes-256-cbc -d -in $WPZIPFILE.enc -out $WPZIPFILE -k $ENCKEY

echo "INFO: Downloading and decrypting Apache configuration"
sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$APACHECONFIG.enc #zip file with all wordpress contents
sudo openssl enc -aes-256-cbc -d -in $APACHECONFIG.enc -out $APACHECONFIG -k $ENCKEY

echo "INFO: Downloading and decrypting LetsEncrypt configuration"
sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$LETSENCRYPTCONFIG.enc #zip file with all wordpress contents
if [ -f $LETSENCRYPTCONFIG.enc ]; then
	sudo openssl enc -aes-256-cbc -d -in $LETSENCRYPTCONFIG.enc -out $LETSENCRYPTCONFIG -k $ENCKEY
else
	echo "WARNING: Letsencrypt.tar not found"
fi

if [ "$WPDIR" = "$WPCONFDIR" ]; then
	echo "INFO: wp-config is in $WPZIPFILE, no further downloads required"
else
	echo "INFO: wp-config is a separate file, downloading $WPCONFIGFILE from Dropbox"
	sudo /var/Dropbox-Uploader/dropbox_uploader.sh download /$WPCONFIGFILE.enc #encrypted Wp-config.php file
	sudo openssl enc -aes-256-cbc -d -in $WPCONFIGFILE.enc -out $WPCONFIGFILE -k $ENCKEY
fi

sudo rm *.enc #remove encrypted files after decryption
echo -e "\\n\\n######### Downloaded backup files from Dropbox #########\\n\\n"

Section 4: Restore WordPress files

Extract the WordPress files into the $WPDIR, which is a variable extracted from the $WPSETTINGSFILE earlier.

#---------------------------------------------------------------------------------------
# Extracting WordPress Files
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Extracting WordPress Files #########\\n\\n"

if [ -d $WPDIR ]; then
	echo "WARNING: Removing older version of $WPDIR"
	sudo rm -r $WPDIR #remove current directory (to avoid conflicts)
else 
	echo "GOOD: $WPDIR not found, proceeding to extraction"
fi

echo "INFO: Extracting $WPDIR"
sudo mkdir -p $WPDIR
sudo tar -xzf $WPZIPFILE -C $WPDIR .

if [ "$WPDIR" = "$WPCONFDIR" ]; then
	echo "INFO: wp-config file is part of $WPDIR, no further action required"
else
	echo "INFO: wp-config is a separate file, moving it to $WPCONFDIR"
	sudo mv $WPCONFIGFILE $WPCONFDIR
	echo "INFO: wp-config file moved to $WPCONFDIR"
fi

echo "GOOD: WordPress Files extracted"

Section 5: Download packages for MySQL & Restore database

Extract WordPress configuration (database user, database name, database password) from wp-config.php, and then restore the DB based on those variables.

#---------------------------------------------------------------------------------------
# Get DB Parameters from wp-config.php
#---------------------------------------------------------------------------------------

echo "INFO: Obtaining configuration parameters from wp-config.php"

WPDBNAME=`cat $WPCONFDIR/$WPCONFIGFILE | grep DB_NAME | cut -d \' -f 4`
WPDBUSER=`cat $WPCONFDIR/$WPCONFIGFILE | grep DB_USER | cut -d \' -f 4`
WPDBPASS=`cat $WPCONFDIR/$WPCONFIGFILE | grep DB_PASSWORD | cut -d \' -f 4`

echo -e "\\n\\n######### WordPress Extractiong Complete #########\\n\\n"
#---------------------------------------------------------------------------------------
# Install MySQL and Dependencies
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Installing mysql Server #########\\n\\n"
echo "INFO: Installing mysql-server"
sudo -E apt-get -q -y install mysql-server >>$LOGFILE  #non-interactive mysql installation

#Some security cleaning up on mysql-----------------------------------------------------
sudo mysql -u root -e "DELETE FROM mysql.user WHERE User='';"
echo "INFO: Setting password for root user to $DBPASS"
sudo mysql -u root -e "UPDATE mysql.user SET authentication_string=PASSWORD('$DBPASS') WHERE User='root';"
sudo mysql -u root -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');"
sudo mysql -u root -e "DROP DATABASE IF EXISTS test;"
sudo mysql -u root -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';"
sudo mysql -u root -e "FLUSH PRIVILEGES;"

#Create DB for WordPress with user------------------------------------------------------
echo "INFO: Creating Database with name $WPDBNAME"
sudo mysql -u root -e "CREATE DATABASE IF NOT EXISTS $WPDBNAME;"
echo "INFO: Granting Permission to $WPDBUSER with password: $WPDBPASS"
sudo mysql -u root -e "GRANT ALL ON *.* TO '$WPDBUSER'@'localhost' IDENTIFIED BY '$WPDBPASS';"
sudo mysql -u root -e "FLUSH PRIVILEGES;"

#Setup permission for my.cnf propery----------------------------------------------------
sudo chmod 644 /etc/mysql/my.cnf

#Extract mysqlfiles---------------------------------------------------------------------
echo "INFO: Loading $WPSQLFILE into database $WPDBNAME"
sudo mysql $WPDBNAME < $WPSQLFILE -u $WPDBUSER -p$WPDBPASS #load .sql file into newly created DB

echo -e "\\n\\n######### MYSQL Server Installed #########\\n\\n"

Section 6: Apache Configuration

Restore Apache, with 2 options:

  • $APRESTORE=1 : Restore apache configuration from backup files
  • $APRESTORE is empty: Script default Apache configuration based on wordpress directory. In this setting no further security is set for apache, you’ll have to modify manually later on.

#---------------------------------------------------------------------------------------
# Basic Apache and PHP Installations
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Installing APACHE & PHP #########\\n\\n"
echo "INFO: Installing Apache2"
sudo apt-get -y install apache2 >>$LOGFILE #non-interactive apache2 install
echo "GOOD: Apache Installed"

echo "INFO: Installing PHP and libapache2-mod-php"
sudo apt-get -y install php >>$LOGFILE
sudo apt-get -y install libapache2-mod-php >>$LOGFILE
sudo apt-get -y install php-mcrypt >>$LOGFILE
sudo apt-get -y install php-mysql >>$LOGFILE
echo "GOOD: PHP Installed"

#---------------------------------------------------------------------------------------
# Loading Apache Configurations
#---------------------------------------------------------------------------------------

echo "INFO: Stopping Apache Service to load configurations"
sudo service apache2 stop

if [ $APRESTORE = 1 ]; then

	echo "INFO: Removing configurations file--to prevent conflicts"
	delDir $APACHEDIR
	sudo mkdir -p $APACHEDIR
	sudo tar -xzf $APACHECONFIG -C $APACHEDIR .

else
	echo "INFO: Setting up Apache default values"
	echo "### WARNING: Apache config files will not be secured ###"
	echo "### Consider modifying the config files post-install ###"
	echo "INFO: Copying 000-default config for $DOMAIN.conf"
	sudo cp $SITESAVAILABLEDIR/$DEFAULTAPACHECONF $SITESAVAILABLEDIR/$DOMAIN.conf #create a temporary Apache Configuration
	
	echo "INFO: Updating $DOMAIN.conf"	
	sudo sed -i "/ServerAdmin*/aServerName $DOMAIN" $SITESAVAILABLEDIR/$DOMAIN.conf #insert ServerName setting
	sudo sed -i "/ServerAdmin*/aServerAlias $DOMAIN" $SITESAVAILABLEDIR/$DOMAIN.conf #insert ServerAlias setting
	sudo sed -i "s|\("DocumentRoot" * *\).*|\1$WPDIR|" $SITESAVAILABLEDIR/$DOMAIN.conf #change DocumentRoot to $WPDIR
	sudo sed -i "/DocumentRoot*/a<Directory $WPDIR>\nAllowOverride All\nOrder allow,deny\nallow from all\n</Directory>" $SITESAVAILABLEDIR/$DOMAIN.conf
	sudo sed -i "/ServerAdmin*/aServerAlias $DOMAIN" $SITESAVAILABLEDIR/$DOMAIN.conf #insert ServerAlias setting
	
	#Format $DOMAIN.conf file
	sudo sed -i "s|\(^ServerName*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i "s|\(^ServerAlias*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i "s|\(^<Directory*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i "s|\(^AllowOverride*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i "s|\(^Order*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i "s|\(^allow*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i "s|\(^</Directory*\)|$EIGHTSPACES\1|" $SITESAVAILABLEDIR/$DOMAIN.conf #tab-ing
	sudo sed -i '/#.*/ d' $SITESAVAILABLEDIR/$DOMAIN.conf #remove all comments in file (nice & clean!)
	
	echo "INFO: Enabling $DOMAIN on Apache"
	sudo a2ensite $DOMAIN >>log.txt
	echo "GOOD: $DOMAIN enabled, restarting Apache2 service"
fi

sudo rm $APACHECONFIG #remove downloaded Apache configurations
sudo a2enmod rewrite >>$LOGFILE #enable rewrite for permalinks to work
sudo service apache2 start

echo "GOOD: LAMP Stack Installed!!"
echo -e "\\n\\n######### APACHE & PHP INSTALLED #########\\n\\n"

Section 7: Setup backup script

Once everything is fine, setup a backup script, and set the cronjob. Also save the encryption key for further backups to Dropbox.

#---------------------------------------------------------------------------------------
# Setup backup script & Cron jobs
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Setting CRON Job, Swap File and Firewall #########\\n\\n"

SetCronJob #from functions.sh
SetEncKey $ENCKEY
ENCKEY=0 #for security reasons set back to 0

Section 8: Setup a swapfile

Some basic things we need to do, to ensure our site stability.

#---------------------------------------------------------------------------------------
# Swap File creation (1GB) thanks to peteris.rocks for this code: http://bit.ly/2kf7KQm
#---------------------------------------------------------------------------------------
sudo swapoff -a #switch of swap -- idempotency
delFile $SWAPFILE
sudo fallocate -l 1G $SWAPFILE
sudo chmod 0600 $SWAPFILE
sudo mkswap $SWAPFILE
sudo swapon $SWAPFILE
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Section 9: Setup firewall rules

Setup firewall rules to only allow port 80, port 443 and port 22. You should know what these ports are for.

#---------------------------------------------------------------------------------------
# Setup uncomplicated firewall rules for SSH, Http and Https: http://bit.ly/2kf7KQm
#---------------------------------------------------------------------------------------
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
echo y | sudo ufw enable
echo -e "\\n\\n######### CRON jobs, firewall and swap file COMPLETE #########\\n\\n"

Section 10: Letsencrypt

Finally call letsencrypt. I call certbot, which is the default implementation from EFF. However, this part is interactive (requires user input), hence I placed it at the end.

There are ‘some’ implementations by others that don’t require user interaction–but I’d rather use the official implementation, and trust they’d continue updating it.

#---------------------------------------------------------------------------------------
# Lets encrypt
#---------------------------------------------------------------------------------------
echo -e "\\n\\n######### Let's encrypt #########\\n\\n"
#Future Feature to ping $Domain and check if IP=this machine, only then proceed
#While possible to do this automatically, I prefer to use letsencrypt supported script
sudo apt-get -y install python-letsencrypt-apache >>$LOGFILE

if [ -z "$PRODCERT" ]; then #Check for prodcert
	if [ $APRESTORE = 1 ]; then
		echo "Let's encrypt not called, attempting to restore from backup"
		if [ -f $LETSENCRYPTCONFIG ]; then
			echo "GOOD: $LETSENCRYPTCONFIG found. Restoring configuration from backup"
			delDir $LETSENCRYPTDIR
			echo "INFO: Creating $LETSENCRYPTDIR"
			sudo mkdir $LETSENCRYPTDIR
			echo "INFO: Extracting Configuration"
			sudo tar -xzf $LETSENCRYPTCONFIG -C $LETSENCRYPTDIR .
		else
			echo "WARNING: Letsencrypt.tar not found, looks like you don't have lets encrypt installed"
		fi
	else
		echo "WARNING: Apache wasn't restored from Backup, unable to restore Lets Encrypt"
		echo "INFO: Consider installing let's encrypt by using letsencrypt --apache"
		#no point copying over letsencrypt configs if Apache wasn't restored (fresh install)
	fi
else
	echo -e "\\n\\n######### Getting Certs #########"
	
	( crontab -l ; echo "0 6 * * * letsencrypt renew" ) | crontab -
	( crontab -l ; echo "0 23 * * * letsencrypt renew" ) | crontab -
	
	if [ $PRODCERT = 1 ]; then
		echo "WARNING: Obtaining production certs, these are rate-limited so be sure this is a Production server"
		sudo letsencrypt --apache 
	else
		echo "Obtaining staging certs (for test)"
		sudo letsencrypt --apache --staging
	fi
fi
echo -e "\\n\\n######### Let's encrypt COMPLETE #########\\n\\n"

Add comment

Astound us with your intelligence