Worpress dev clone (Ubuntu)

This article describes how to create a fully operational development clone of your WordPress site. You will need to install a web server and database software and edit some configuration files to make it work. Note: Follow this article by Abhijit Menon-Sen (ams@toroid.org) if you want to use git to keep the live site in sync with your development version.

Installing MySQL, Apache and PHP

First we install MySQL, Apache and PHP on the local Ubuntu machine. And enable the userdir and url-rewrite modules.

$ sudo apt-get install apache2 php5 php5-mysql mysql-server php5-mysql phpmyadmin
$ sudo a2enmod userdir rewrite

Configure Apache

To enable per-user web directories you will have also have to edit the /etc/apache2/mods-enabled/php5.conf file. Comment out or remove the following part:

$ cat /etc/apache2/mods-enabled/php5.conf
...
<IfModule mod_userdir.c>
 <Directory /home/*/public_html>	 	
   php_admin_value engine Off
 </Directory>	 
</IfModule>

If you want to use symlinks in public_html , for example to link to a git repo (see this article), you will have to make sure that the user privileges are correct. In doubt run the following commands:

$ chmod -R a+rwx ~/path/of/symlink

$ chmod +rx ~/path/of/symlink
$ chmod +rx ~/path/of
$ chmod +rx ~/path

Your web server is now set up correctly.

Database synchronisation

Export the remote database and import it locally. The simplest is to use the same database name, username and password.

The following script will let you pull or push the database between the remote and local machine.

#!/bin/sh

# Copyright (C) 2013 Uco Mesdag
# Usage:	database-sync.sh [OPTIONS] [USER@HOST]
#   --push, --pull                   pull: overwrite local mysql datasbase with
#                                    remote database
#                                    push: overwrite remote mysql datasbase with
#                                    local database
#   --db=DATABASE                    use if local and remote mysql database have
#                                    the same name
#   --mysql-user=USERNAME            use if local and remote mysql database have
#                                    the same username
#   --mysql-password=PASSWORD        use if local and remote mysql database have
#                                    the same password
#   --local-db=DATABASE              local mysql database name
#   --local-mysql-user=USERNAME      local mysql username
#   --local-mysql-password=PASSWORD  local mysql database password
#   --remote-db=DATABASE             remote mysql database name
#   --remote-mysql-user=USERNAME     remote mysql database username
#   --remote-mysql-password=PASSWORD remote mysql database password

# Instructions: you can also set all parameters in this script self,
#              so you can run "database-sync.sh" without arguments.

# User configuration
REMOTE=
USER=
ACTION=pull			# default action 'push' or 'pull'

MYSQL_REMOTE_USER=root
MYSQL_REMOTE_PASSWORD=
REMOTE_DB_NAME=

MYSQL_LOCAL_USER=root
MYSQL_LOCAL_PASSWORD=
LOCAL_DB_NAME=

#
#
#

BASEDIR="$( cd $(dirname "$0") ; pwd )"
OPTS=`getopt -a -l push,pull,db:,remote-db:,local-db:,mysql-user:,remote-mysql-user:,local-mysql-user:,mysql-password:,remote-mysql-password:,local-mysql-password: -o "" -- "$@"`
ACTION=pull

usage() {
	echo "Usage:	$(basename "$0") [OPTIONS] [USER@HOST]" >/dev/stdout
	echo "  --push, --pull                   pull: overwrite local mysql datasbase with" >/dev/stdout
	echo "                                   remote database" >/dev/stdout
	echo "                                   push: overwrite remote mysql datasbase with" >/dev/stdout
	echo "                                   local database" >/dev/stdout
	echo "  --db=DATABASE                    use if local and remote mysql database have" >/dev/stdout
	echo "                                   the same name" >/dev/stdout
	echo "  --mysql-user=USERNAME            use if local and remote mysql database have" >/dev/stdout
	echo "                                   the same username" >/dev/stdout
	echo "  --mysql-password=PASSWORD        use if local and remote mysql database have" >/dev/stdout
	echo "                                   the same password" >/dev/stdout
	echo "  --local-db=DATABASE              local mysql database name" >/dev/stdout
	echo "  --local-mysql-user=USERNAME      local mysql username" >/dev/stdout
	echo "  --local-mysql-password=PASSWORD  local mysql database password" >/dev/stdout
	echo "  --remote-db=DATABASE             remote mysql database name" >/dev/stdout
	echo "  --remote-mysql-user=USERNAME     remote mysql database username" >/dev/stdout
	echo "  --remote-mysql-password=PASSWORD remote mysql database password" >/dev/stdout
	echo "" >/dev/stdout
	echo "Instuctions: you can also set all parameters in this script self," >/dev/stdout
	echo "             so you can run \"$(basename "$0")\" without arguments." >/dev/stdout
}

password() {
	if [ "$MYSQL_REMOTE_PASSWORD" = "" ]; then
		echo ""
		read -p " Enter the mysql password for $MYSQL_REMOTE_USER on $REMOTE: " MYSQL_REMOTE_PASSWORD
	fi
	if [ "$MYSQL_LOCAL_PASSWORD" = "" ]; then
		echo ""
		read -p " Enter the local mysql password for $MYSQL_LOCAL_USER: " MYSQL_LOCAL_PASSWORD
	fi
	echo ""
}

if [ $? != 0 ]; then
  usage
  exit 1
fi

if [ "$( echo $1 | grep '.*\(help\|\?\)$' )" ]; then
	usage
	exit 0
fi

setopt() {
	if [ "$( echo $2 | grep '[[:alnum:]]\+@[[:alnum:]\.]\+' )" ]; then
    	ARGS="$@"
		IFS="@"
	 	set -- $2
		if [ $# -ne 2 ]; then
		    echo "invalid remote" >/dev/stderr
		    exit 1
		fi
		USER="$1"
		REMOTE="$2"
		if ! dig @8.8.8.8 $REMOTE | grep "ANSWER: [1-9][0-9]*" >/dev/null; then
			echo "invalid domain" >/dev/stderr
			exit 1
		fi
		unset IFS
		shift
	    setopt "$@"
	else
		if [ "$( echo $1 | grep '.*\(pull\|push\)$' )" ]; then
			ACTION="$( echo $1 | sed 's/^--\(pull\|push\)$/\1/' | grep '\(pull\|push\)$' )"
			shift
			setopt "$@"
		else
			if [ -n "$1" -a -n "$2" ]; then
			    OPTNAME=OPT_${1#--}
			    OPTNAME="$( echo $OPTNAME | sed 's/\-/_/g' )"
			    OPTVAL="\"$2\""
			    eval $OPTNAME="$OPTVAL"
			    shift
			    shift
			    setopt "$@"
			fi
		fi
	fi
}

eval setopt $OPTS

if [ "$OPT_db" ]; then
	LOCAL_DB_NAME="$OPT_db"
	REMOTE_DB_NAME="$OPT_db"
fi

if [ "$OPT_local_db" ]; then
	LOCAL_DB_NAME="$OPT_local_db"
fi

if [ "$OPT_remote_db" ]; then
	REMOTE_DB_NAME="$OPT_remote_db"
fi

if [ "$OPT_mysql_user" ]; then
	MYSQL_REMOTE_USER="$OPT_mysql_user"
	MYSQL_LOCAL_USER="$OPT_mysql_user"
fi

if [ "$OPT_remote_mysql_user" ]; then
	MYSQL_LOCAL_USER="$OPT_remote_mysql_user"
fi

if [ "$OPT_local_mysql_user" ]; then
	MYSQL_LOCAL_USER="$OPT_local_mysql_user"
fi

if [ "$OPT_mysql_password" ]; then
	MYSQL_REMOTE_PASSWORD="$OPT_passw"
	MYSQL_LOCAL_PASSWORD="$OPT_passw"
fi

if [ "$OPT_remote_mysql_password" ]; then
	MYSQL_LOCAL_PASSWORD="$OPT_lpassw"
fi

if [ "$OPT_local_mysql_password" ]; then
	MYSQL_LOCAL_PASSWORD="$OPT_rpassw"
fi

if [ ! "$REMOTE" ] || [ ! "$USER" ] || [ ! "$LOCAL_DB_NAME" ] || [ ! "$REMOTE_DB_NAME" ] || [ ! "$MYSQL_REMOTE_USER" ] || [ ! "$MYSQL_LOCAL_USER" ]; then
	usage
	exit 1
fi

if [ ! "$MYSQL_LOCAL_PASSWORD" ] ||  [ ! "$MYSQL_REMOTE_PASSWORD" ]; then
	password
fi

pull() {
	echo "Dumping database $REMOTE_DB_NAME on $REMOTE..."
	ssh $USER@$REMOTE \
	"mysqldump -u$MYSQL_REMOTE_USER -p$( echo "'"$MYSQL_REMOTE_PASSWORD"'" ) $REMOTE_DB_NAME > /home/$USER/$REMOTE_DB_NAME.sql && \
	gzip -f /home/$USER/$REMOTE_DB_NAME.sql && \
	chown $USER /home/$USER/$REMOTE_DB_NAME.sql.gz" || exit 1

	echo "Receiving $REMOTE_DB_NAME.sql.gz from $USER@$REMOTE..."
	scp $USER@$REMOTE:"/home/$USER/$REMOTE_DB_NAME.sql.gz" "$BASEDIR/"  || exit 1

	echo ""
	if [ -f "$BASEDIR/$REMOTE_DB_NAME.sql.gz" ]; then
		echo "Replacing database tables..."
		mysqldump -u$MYSQL_LOCAL_USER -p$MYSQL_LOCAL_PASSWORD --add-drop-table --no-data $LOCAL_DB_NAME  | grep ^DROP | mysql -u$MYSQL_LOCAL_USER -p$MYSQL_LOCAL_PASSWORD $LOCAL_DB_NAME  || exit 1
		gzip -dc < "$BASEDIR/$REMOTE_DB_NAME.sql.gz" | mysql -u$MYSQL_LOCAL_USER -p$MYSQL_LOCAL_PASSWORD $LOCAL_DB_NAME  || exit 1
	fi
}

push() {
	echo "Dumping database $LOCAL_DB_NAME on localhost..."
	mysqldump -u$MYSQL_LOCAL_USER -p$MYSQL_LOCAL_PASSWORD $LOCAL_DB_NAME > "$BASEDIR/$LOCAL_DB_NAME.sql" && \
	gzip -f "$BASEDIR/$LOCAL_DB_NAME.sql"  || exit 1

	echo "Sending $LOCAL_DB_NAME.sql.gz to $USER@$REMOTE..."
	scp "$BASEDIR/$LOCAL_DB_NAME.sql.gz" $USER@$REMOTE:"/home/$USER/"  || exit 1

	echo ""
	ssh $USER@$REMOTE \
	"if [ -f """/home/$USER/$LOCAL_DB_NAME.sql.gz""" ]; then echo """Replacing database tables...""" && \
	mysqldump -u$MYSQL_REMOTE_USER -p$( echo "'"$MYSQL_REMOTE_PASSWORD"'" ) --add-drop-table --no-data $REMOTE_DB_NAME  | grep ^DROP | mysql -u$MYSQL_REMOTE_USER -p$( echo "'"$MYSQL_REMOTE_PASSWORD"'" ) $REMOTE_DB_NAME && \
	gzip -dc < """/home/$USER/$LOCAL_DB_NAME.sql.gz""" | mysql -u$MYSQL_REMOTE_USER -p$( echo "'"$MYSQL_REMOTE_PASSWORD"'" ) $REMOTE_DB_NAME; fi"  || exit 1
}

echo ""

if [ "$ACTION" = "pull" ]; then
	pull
fi

if [ "$ACTION" = "push" ]; then
	push
fi

echo "Done..."
echo ""
exit 0

Edit the WordPress setup

Add the following to your wp-config.php so all uri's get redirected to the local site.

$ cat wp-config.php
<?php
if( in_array($_SERVER['HTTP_HOST'], array('localhost', '127.0.0.1')) ){
define('WP_SITEURL', 'http://localhost/~user/');
define('WP_HOME', 'http://localhost/~user/');
define('FS_METHOD','direct');
}
/**
* The base configurations of the WordPress.

...

Finally

Add the following to your .htaccess so all media uri's get redirected to the live site where all the original media lives.

$ cat .htaccess

...
redirect /~user/wp-content/uploads/ http://yourdomain.com/wp-content/uploads/

If you get the error: "Permission denied: /home/*/public_html/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable" , when directing your browser to http://localhost/~user then do the following: sudo chmod +rx /home/*/public_html

Share

Uco Mesdag

A sysadmin by day and a coder by night. Working as a senior Linux system engineer with plus 20 years of experience. I write about Linux, tech, code and other things that have my interest.

You've successfully subscribed to Mesd.ag
Great! Next, complete checkout for full access to Mesd.ag
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.