Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 204 lines (173 sloc) 6.84 KB
#!/usr/bin/env python
#
# This software is provided "as is". No warranty of the software is
# provided whatsoever, whether express, implied, or statutory, including,
# but not limited to, any warranty of merchantability or fitness for a
# particular purpose or any warranty that the software will be error-free.
import contextlib, errno, glob, MySQLdb, os, re, shutil, subprocess, sys, time
from optparse import OptionParser
from phpserialize import *
MYSQL_USER = "user" # User to access the WordPress databases
MYSQL_PASS = "pass" # MySQL user password
MYSQL_DIR = "/var/lib/mysql" # Location of MySQL database files
WP_PROD_DB = "wp_prod" # Name of the production WordPress database
WP_PROD_DIR = "/home/wordpress/www/wordpress_prod" # Path to production installation
WP_DEV_DB = "wp_dev" # Name of the development WordPress database
WP_DEV_DIR = "/home/wordpress/www/wordpress_dev" # Path to the development installation
PROG_NAME = "wp-clone-blog"
LOCK_FILE = "/var/lock/wp-clone-blog"
def main():
description = "Copy a WordPress blog from a multisite production installation to a development installation."
usage = "usage: %prog [options]"
parser = OptionParser(
description=description,
usage=usage,
prog=PROG_NAME
)
parser.add_option("-s", "--simulate", dest="simulate", action='store_const', const=True, default=False, help="don't make permanent changes")
parser.add_option("-b", "--blog-id", dest="blog_id", type=int, help="the production blog ID")
(options, args) = parser.parse_args()
# Create lock file
with flock(LOCK_FILE):
# Check for root, although this script should probably be chowned to
# root:root and chmoded 700
if not os.geteuid() == 0:
sys.exit("\nThis script must be run as root\n");
# Check for production blog ID
if not options.blog_id:
parser.error("Must specify --blog-id")
try:
prod_blog_id = int(options.blog_id)
except ValueError:
sys.stderr.write("%s: error: --blog-id should be an integer\n" % PROG_NAME)
sys.exit(1);
# Get information from wp_blogs table about production blog
sys.stdout.write("Getting information for blog with ID %d\n" % prod_blog_id)
conn = MySQLdb.Connection(db=WP_PROD_DB, host="localhost", user=MYSQL_USER, passwd=MYSQL_PASS)
mysql = conn.cursor()
sql = """
SELECT domain, path, registered, last_updated, public, archived, mature, spam, deleted, lang_id
FROM wp_blogs
WHERE blog_id = %d
""" % prod_blog_id
mysql.execute(sql)
rows = mysql.fetchall()
# Error if no results found
if len(rows) != 1:
sys.stderr.write("%s: error: Couldn't find any information for a blog with ID %d\n" % (PROG_NAME, prod_blog_id))
sys.exit(1)
row = rows[0]
# Create SQL for new development blog in wp_blogs
sql = """
INSERT INTO wp_blogs
SET site_id = 1,
domain = '%s',
path = '%s',
registered = '%s',
last_updated = '%s',
public = %d,
archived = '%s',
mature = %d,
spam = %d,
deleted = %d,
lang_id = %d
""" % (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9])
mysql.close()
conn.close()
# Insert record for new development blog; capture blog ID
dev_blog_id = 0
if options.simulate:
sys.stdout.write("Would create wp_blogs entry for new developement blog.\n")
else:
sys.stdout.write("Creating wp_blogs entry for new developement blog...\n")
conn = MySQLdb.Connection(db=WP_DEV_DB, host="localhost", user=MYSQL_USER, passwd=MYSQL_PASS)
mysql = conn.cursor()
mysql.execute(sql)
dev_blog_id = conn.insert_id()
mysql.close()
conn.close()
sys.stdout.write("Created new development blog: %d\n" % dev_blog_id)
# Flush production tables and lock for reading
sys.stdout.write("Flushing production tables and locking...\n")
sql = "FLUSH TABLES WITH READ LOCK"
conn = MySQLdb.Connection(db=WP_PROD_DB, host="localhost", user=MYSQL_USER, passwd=MYSQL_PASS)
mysql = conn.cursor()
mysql.execute(sql)
# Copy database tables from production to development
sys.stdout.write("Copy database tables:\n")
# All wp_<blog_id>_* files in database directory
table_files = glob.glob(("%s/%s/wp_%d_*" % (MYSQL_DIR, WP_PROD_DB, prod_blog_id)))
# Pattern to capture the part of the filename after wp_<id>_
pattern = re.compile(".*%s/wp_%d_(.*)" % (WP_PROD_DB, prod_blog_id))
for file in table_files:
match = pattern.match(file)
dest = "%s/%s/wp_%d_%s" % (MYSQL_DIR, WP_DEV_DB, dev_blog_id, match.group(1))
if options.simulate:
sys.stdout.write("Would copy %s to %s\n" % (file, dest))
else:
sys.stdout.write("Copying %s to %s...\n" % (file, dest))
subprocess.call(['cp', '-p', file, dest])
sys.stdout.write("Unlocking tables...\n")
sql = "UNLOCK TABLES"
mysql.execute(sql)
# Get list of active plugins on production blog
sql = """
SELECT option_value
FROM wp_%d_options
WHERE option_name = "active_plugins"
""" % prod_blog_id
mysql.execute(sql)
rows = mysql.fetchall()
if len(rows) != 1:
sys.stderr.write("%s: error: Couldn't get the list of active plugins for blog with ID %d\n" % (PROG_NAME, prod_blog_id))
sys.exit(1)
row = rows[0]
plugin_list = loads(row[0])
# Copy any missing plugins from production to development
sys.stdout.write("Copying plugins:\n")
for index, plugin in plugin_list.iteritems():
dest = WP_DEV_DIR + "/wp-content/plugins/" + plugin
if not os.path.exists(dest):
src = WP_PROD_DIR + "/wp-content/plugins/" + plugin
if options.simulate:
sys.stdout.write("Would copy %s to %s\n" % (src, dest))
else:
if os.path.dirname(plugin):
sys.stdout.write("Copying %s to %s...\n" % (os.path.dirname(src), os.path.dirname(dest)))
subprocess.call(['cp', '-pr', os.path.dirname(src), os.path.dirname(dest)])
else:
sys.stdout.write("Copying %s to %s\n" % (src, dest))
subprocess.call(['cp', '-p', src, dest])
mysql.close()
conn.close()
# Copy uploads directory
if not os.path.exists("%s/wp-content/blogs.dir" % WP_DEV_DIR):
sys.stderr.write("%s: error: blogs.dir directory does not exist for development WordPress: not copying uploads directory")
else:
src = "%s/wp-content/blogs.dir/%d" % (WP_PROD_DIR, prod_blog_id)
dest = "%s/wp-content/blogs.dir/%d" % (WP_DEV_DIR, dev_blog_id)
if options.simulate:
sys.stdout.write("Would copy upload directory from production to development:\n\t%s => %s\n" % (src, dest))
else:
sys.stdout.write("Copying upload directory:\n\t%s => %s\n" % (src, dest))
subprocess.call(['cp', '-pr', src, dest])
sys.stdout.write("Done.\n")
@contextlib.contextmanager
def flock(path, wait_delay=.5):
while True:
try:
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
except OSError, e:
if e.errno != errno.EEXIST:
raise
time.sleep(wait_delay)
continue
else:
break
try:
yield fd
finally:
os.close(fd)
os.unlink(path)
if __name__ == '__main__':
main()