Python Script to Update WordPress in One Step

During the past week, I think I had to update all my wordpress instances twice, and it’s become really annoying doing this manually. I’ve written a python script which I’ll share with you.

How I keep my wordpress updated by hand
I tend to keep my wp-content folder outside of my wordpress installation for 2 reasons:

1. I don’t like to loose my themes, plugins and customizations
2. I like to keep all my customization changes under subversion

So, if I had my wordpress installation say at:
/home/user/public_html/blog

I’d keep my wp-content folder for that here:

/home/user/public_html/wp-content-for-blog

So when I upgrade my blog, I always remove the original wp-content folder that comes along wordpress, and I symlink my hard worked on wp-content folder that lives outside to the freshly unzipped wordpress folder.

[bash]
user@machine:~/public_html/blog$ ls -l

lrwxrwxr-x 1 user www 54 2008-11-26 09:29 wp-content -> /home/user/public_html/wp-content-for-blog

[/bash]

So what I endup doing all the time, is downloading the latest.zip to ~/public_html/, it will unzip under ~/public_html/wordpress, and then I’ll copy the current ~/public_html/blog/wp-config.php to ~/public_html/wordpress, then I’ll remove the default ~/public_html/wordpress/wp-content and symlink the outer wp-content with all my customizations, themes and plugins to it. Once done, I’ll make a backup of the old wordpress folder, and then I’ll rename wordpress folder to the name of the blog folder, and it’s all done.

It’s simple, but when you have to do it for 5 blogs, every week, it’s not fun anymore.

The Update Script

So here’s a script to do it in one step. If you’re not using my symlinked technique, this will do it for you, you only need to specify the full path to the folder where you want to keep your current wp-content folder outside the new installation before you apply the update, and the name of the folder where your current blog lives. The script below will have its configuration variables towards the beginning set so that they are in line with the example I’ve been talking about.

[python]
#!/usr/bin/python
#########################################################################################
#
# upgrade_wordpress.py – Script to automatically upgrade your wordpress installation.
#
# Requirements:
# – Python 2.4 or older
# – WordPress should already be installed
# – CURL (sudo apt-get install curl)
#
# Author: Angel (Gubatron) Leon
# LICENSE: See the GPL2 license.
# 2008
#########################################################################################
import os

#########################################################################################
#Config (relative to the folder where this script will be run from)
#########################################################################################

#The current folder where the blog lives
BLOG_FOLDER=’blog’

#
# The first time you run the script, it will try to make a copy of your
# current wp-content folder outside. Copy here the location of where
# the wp-content folder with your themes and plugins should exist.
#
# After it unzips, it will remove the default wp-content folder from
# the new installation, and it will symlink the external wp-content
# That way you don’t ever have to worry about loosing your customizations
# and plugins.
#
WP_CONTENT_OUTSIDE_COPY_FOLDER="/home/user/public_html/wp-content-for-blog"

#This is where a backup of your current blog will be
BLOG_FOLDER_BACKUP_FOLDER=BLOG_FOLDER+’.old’

#Where to download the wordpress latest.zip from
WORDPRESS_LATEST_ZIP_URL=’http://wordpress.org/latest.zip’

#### DO NOT MODIFY AFTER THESE LINES ####

def downloadWordpress(url=WORDPRESS_LATEST_ZIP_URL):
if os.path.exists(‘latest.zip’):
print "Removing old latest.zip"
os.remove(‘latest.zip’)

#Try to download with CURL
print "Attempting to download latest.zip from wordpress.org"
os.system(‘curl %s -o latest.zip’ % url)

if not os.path.exists(‘latest.zip’):
os.system(‘wget ‘ + url)

return os.path.exists(‘latest.zip’)

def dirExists(dirName):
return os.path.exists(dirName) and os.path.isdir(dirName)

def backupBlog(currentBlogFolder=BLOG_FOLDER,
wpContentOriginalFolder=WP_CONTENT_OUTSIDE_COPY_FOLDER,
backupFolder=BLOG_FOLDER_BACKUP_FOLDER):

#Remove any previous backups
if os.path.exists(backupFolder) and os.path.isdir(backupFolder):
print "Removing previous backup folder"
os.system(‘rm -fr ‘ + backupFolder)

#Copy the current blog folder into a backup folder just in case.
#We won’t do any database backups for now.
print "Creating new backup folder"
os.system(‘cp -r %s %s’ % (currentBlogFolder,backupFolder))

#Check for the copy of wp-content outside the blog, if it doesn’t exist
#we’ll make it for the first time.
if not dirExists(wpContentOriginalFolder):
print "Creating outside copy of wp-content"
os.system(‘cp -r %s %s’ % (os.path.join(currentBlogFolder,’wp-content’),
wpContentOriginalFolder))

#Copy the latest wp-config.php outside to the current folder
print "Copying your latest wp-config.php outside"
os.system(‘cp %s .’ % (os.path.join(currentBlogFolder,’wp-config.php’)))

backupFolderExists = dirExists(backupFolder)
wpContentFolderExists = dirExists(wpContentOriginalFolder)
configFileExists = os.path.exists(‘wp-config.php’)

return backupFolderExists and wpContentOriginalFolder and configFileExists

def upgradeBlog(currentBlogFolder=BLOG_FOLDER,
backupFolder=BLOG_FOLDER_BACKUP_FOLDER,
url=WORDPRESS_LATEST_ZIP_URL,
wpContentOriginalFolder=WP_CONTENT_OUTSIDE_COPY_FOLDER):

if not downloadWordpress(url):
print "Could not download latest.zip, aborting."
return False

if not backupBlog(currentBlogFolder,wpContentOriginalFolder,backupFolder):
print "Could not backup blog or wp-config.ph, aborting."
return False

if currentBlogFolder == ‘wordpress’:
print "The current blog folder cannot be ‘wordpress, aborting."
return False

#1. If a wordpress/ folder exists, wipe it.
if dirExists(‘wordpress’):
print "Removing old wordpress folder"
os.system(‘rm -fr wordpress’)

if dirExists(‘%s.delete’ % currentBlogFolder):
print "Removing old %s.delete folder" % currentBlogFolder
os.system(‘rm -fr %s.delete folder’ % currentBlogFolder)

#2. Unzip new copy
os.system(‘unzip latest.zip’)

if not dirExists(‘wordpress’):
print "Could not unzip the wordpress installation, aborting."
return False

#1. Copy wp-config.php into the new installation
os.system(‘cp wp-config.php wordpress/’)

#2. Remove the default wp-content folder
os.system(‘rm -fr wordpress/wp-content’)

#3. Symlink the original wp-content that lives outside
os.system(‘ln -s %s wordpress/wp-content’ % (wpContentOriginalFolder))

#4. Verify symlink was created
if not (os.path.exists(‘wordpress/wp-content’) and os.path.islink(‘wordpress/wp-content’)):
print "Could not create symlink to wp-content, aborting."
return False

#5. Move original folder to folder.delete, and make this wordpress folder the current folder.
os.system(‘mv %s %s.delete’ % (currentBlogFolder,currentBlogFolder))

if not dirExists(currentBlogFolder + ".delete"):
print "Could not rename current folder for later deletion, aborting."
return False

#6. Rename the new installation as the current blog
os.system(‘mv %s %s’ % (‘wordpress’,currentBlogFolder))

if dirExists(‘wordpress’):
print "ALERT: The wordpress folder still exists."
return False

if not dirExists(currentBlogFolder):
print "ALERT: The blog doesn’t exist, recover from the backup folder %s please" % (backupFolder)
return False

#7 Cleanup
os.system(‘rm -fr %s.delete’ % (currentBlogFolder))

return True

if __name__ == ‘__main__’:
upgradeBlog()
[/python]

Requirements

  • shell access to the machine where you have your wordpress installed
  • a python interpreter installed
  • curl (sudo apt-get install curl) to download the zip. If you don’t have it it’ll attempt to use wget
  • Installation

  • Right outside your wordpress installation folder, create a new file called upgrade_wordpress.py
  • Copy and paste the script inside that file
  • Edit the configuration variables to point to the name of your wordpress installation folder, and give it a full path to where you want to keep your wp-content folder (including the name of the folder, so if you want to name it the same way, you could do for example /home/user/wp-content and it’ll be saved right under your home)
  • Usage:
    [bash]python upgrade_wordpress.py[/bash]

    The script is very fault proof, it will always try to abort in case something is not going the way it’s expected. At the end of the day it’ll also leave a backup copy of your current blog in case something goes bad, you can always recover.

    Geek T-Shirt Collection #4 – SpamTShirt.com/”Healthy Semen”

    Geek T-Shirt - SpamTShirt.com - Healthy Semen (front)

    This is one of the oldest self made t-shirts. I was constantly pissed at the amount of spam email I was receiving, and I thought some of the subjects were really eye catching for t-shirts.

    For a few months I thought maybe I could make money on the creativity of spammers by mocking them with t-shirts, called, SpamTShirts, but nobody caught on my joke, and I desisted from the business idea.

    Geek T-Shirt - SpamTShirt.com - Healthy Semen (back)

    If you can appreciate it, the icon/logo of the website is a Trash can, honoring junk mail.

    I believe I made this t-shirt at cafepress.com

    See the Previous T-Shirt
    See the Next T-Shirt

    Amazon’s Black Friday deals voting is now open

    Amazon's Black friday Playstation dealsBlack friday is around the corner and it’s become a shopping tradition at Amazon.com to make the craziest deals available in a democratic fashion.

    They have 18 crazy deals to propose organized into 6 rounds. Amazon users get to vote on each of these rounds for their favorite offer, what will you vote for?

    Then a limited number of customers are selected at random to participate in the buying round, where they race to buy deals for that round.

    This year’s potential winning deals include a Samsung 46-Inch 1080p HDTV for $699, an ASUS Eee PC 900 Netbook for $129 and a KitchenAid Professional Stand Mixer for $69. See the Product Pricing and Availability section for the full list.

    It’s different from last year
    The biggest difference is that this year, Customers Vote is a race. Amazon will invite more customers to participate in the buying round than they have available deals.

    Amazon recommends selected customers sign in early on the buying day, as deals are sold on a first come, first served basis. (Check here for the buying days calendar.)

    Another difference is that customers selected to participate in the buying round will be able to buy any (or all) of the deals, not just the one they voted for.

    And remember, only customers who voted can participate, so vote now!

    New FrostWire 4.17.2 for Windows is out

    Download FrostWire 4.17.2 for Windows

    CHANGELOG:

    Version 4.17.2 (November 2008)

  • Fixes a newly introduced bug in Windows which would make FrostWire(tm) take over the .bittorrent file association without asking the user.
  • Reduces DHT network load. The LimeWire team found out that the Mojito “store forwarding” feature would not provide extra data availability, so it’s been turned off from the DHT on all new FrostWires.
  • FrostWire.ico has been updated. Now when FrostWire(tm) is associated to .bittorrent files, .bittorrent files will use the FrostWire(tm) icon.
  • About FrostWire
    FrostWire, a BitTorrent/Gnutella Peer-to-Peer client, is a collaborative effort from many Open Source developers and contributors from all around the world. In late 2005, concerned developers of LimeWire’s open source community announced the start of a new project fork “FrostWire” that would protect the developmental source code of the LimeWire client and any improvements to the Gnutella protocol design. The developers of FrostWire give high regard and respect to the GNU General Public License and consider it to be the ideal foundation of a creative and free enterprise market.

    Geek T-Shirt Collection #3 – CSS Protest / wedoit4you.com

    wedoit4you.com was one of my personal efforts from school, with it I learned a lot about web development, technology, news, and the tech industry since it was a blog before the term was even invented.

    During those times, I was learning CSS, and I had to learn about centering elements, I always found it very annoying that you could float elements to the left, or to the right, but there was no “float:center”, and you had to go through all this hoops to do what in the past, we could do just by using the <center/> tag.

    This is a self made tshirt, ordered at spreadshirt.com

    About wedoit4you.com

    More recently wedoit4you.com became a spanish podcast that reviewed the latest happenings in the Internet, Technology and entertainment industries. Me along La Tati did a series of 75 weekly episodes, until the end of the spring of 2008 where my work got pretty hectic and I couldn’t find more time to continue recording, editing, and distributing the podcasts. It reached well over 1500 weekly subscribers listening to it, and we still get mails from users asking for the podcast to come back. Maybe in 2009 🙂

    See the Previous T-Shirt
    See the Next T-Shirt

    Java/Reflection notes: Invoking a static main() method from a dinamically loaded class.

    Maybe for some wild reason, your Java application will need to execute a pre launcher that won’t know about the Main class it’s supposed to invoke until it’s being executed. For example, you have distributed your Java application but you used pack200 to compress your jars, and your new application launcher will unpack everything, but up to this point, it can’t do an import of the main class, since the jar that contained it, wasn’t available to the virtual machine when java was invoked.

    So, your launcher class, finishes unpacking your jars, adds the jars to the current classloader, and now you need to invoke your Main.main(String[] args).

    This is how I managed to do it, using Java’s reflection mechanisms

    [java]
    public final void startMain(String[] args) {
    //this will create a new class loader out of all jars available, see below on next
    //code section of this blog post
    addJars2Classpath();

    try {
    //Instantiate the main class, and execute it’s static main method
    Class clazz = jarsClassloader.loadClass("com.mycomp.somepackage.Main");
    Class[] argTypes = { args.getClass(), };
    Object[] passedArgs = { args };
    Method main = clazz.getMethod("main",argTypes);
    main.invoke(null, passedArgs);
    } catch (Exception e) {
    //oh oh
    e.printStackTrace();
    }

    } //startMain
    [/java]

    In case you’re interested in how to add new Jars to the classpath during runtime, this is how I managed to do it:

    [java]
    /**
    * Adds all the newly available jars to the classpath
    */
    public final void addJars2Classpath() throws Exception {
    //Create a new class loader with all the jars.
    Object[] jars = getFiles(getApplicationResourcesJavaFolder(), ".jar");
    URL[] jarUrls = new URL[jars.length];

    for (int i=0; i < jars.length; i++) {
    URL jarURL = null;

    try {
    jarURL = new URL("jar:file:"+(String) jars[i]+"!/");
    } catch (Exception e) {
    //LOG.error("Bad URL for jar ("+jarFile+"):n"+e.toString()+" ("+jarURL+")n");
    return;
    }

    jarUrls[i] = jarURL;
    }

    //and this guy here, is the classloader used to load
    jarsClassloader = URLClassLoader.newInstance(jarUrls);
    } //addJar2Classpath

    /**
    * Get a Object<String> array that contains the names of the files
    * that end with ‘type’ on the given folderPath
    *
    * e.g
    *
    * String[] jarFiles = getFiles(".",".jar");
    *
    */
    public final Object[] getFiles(String folderPath, String type) {
    File f = new File(folderPath);

    String[] files = f.list();

    Vector results = new Vector();

    for (int i=0; i < files.length; i++) {
    if (files[i].endsWith(type)) {
    results.add((String) files[i]);
    }
    }

    if (results.size() == 0)
    return null;

    return results.toArray();
    } //getFiles
    [/java]

    Joost shows its claws, Embedding now available

    Here we go, Episode 35 of Bleach, embed test:

    Notes to Joost developers: I had to resize the size of the embedded object, by default it was 640×360, but this is too wide for the standard blog, which usually has a 2 column layout.

    It’d recommend 400×225 as the default size for most bloggers to copy and paste without further editing.

    This should probably the standard embed code you should have for your users to embrace this:

    <object width="400" height="225"><param name="movie" value="http://www.joost.com/embed/37aigyt"></param><param name="allowFullScreen" value="true"></param><param name="allowNetworking" value="all"></param><param name="allowScriptAccess" value="always"></param><embed src="http://www.joost.com/embed/37aigyt" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" allownetworking="all" width="400" height="225"></embed></object>

    Hat tip to the Joost team for allowing embedding, I was waiting for this to start recommending content properly.

    Monetizing Free Video works better in the Living Room

    Well over a year ago I asked a Joost executive why not use some of that money they recently had gotten in funding into going for the living room via Xbox or PS3. I got a politically correct answer about yes, we’ll do it eventually, but that’s not our focus right now.

    In the meantime, Hulu was building their platform for the web, and 4 months later it was king of the hill. Joost then had to rethink their distribution and ditched their p2p client for an all Flash streaming based approach, which seems to start picking up traffic but the differences in the audience sizes are ridiculous. A tip for your current approach, and it has to be done a month ago: Allow non-signed users to watch your some of your content (short-clips), and allow for embedding, worked wonders for YouTube, it’d be awesome for all that niche oriented content you have. (Update Nov 22 2008: Joost has just released video embedding!)

    But even though Hulu has almost 10 times more traffic now than Joost, it’s earnings are still a joke compared to the advertising earnings of Broadcast and Cable, it’s just peanuts. With Ad Spending going down, it would make sense to me as an advertiser to somehow stay in the living room, with cheaper CPM, and better targeting. Hulu & Joost on the Xbox could do that for me, if only they were available in the living room.

    Netflix on the other hand, monetizes month to month, and they also saw the opportunity of getting to your living room in different ways. They’ve tried with their own $100 box, but I find the most interesting and convenient way to get an audience of at least 15 million people that spend hours and hours wasting their time in front of their TV playing video games (with barely any ads) in the Xbox console.
    They give me as an Xbox Live customer yet another option to keep my Xbox Live subscription and think twice about getting the PS3 and ditching the Xbox live payments since my Netflix subscription gains added value cause I don’t have to plug my laptop anymore to the tv. Wouldn’t it be awesome to have all those Hulu and Joost shows there too?

    Netflix has released their stream service this week via Xbox, and it’s exactly the way I suggested to Joost, make it a free download, negotiate with Microsoft an ad revenue agreement, I can think up so many business models from this partnership… I wish I had super powers.

    In any case, pictures speak louder than words some times. I hope bizdevs at Hulu & Joost take a look at this and remotely consider this as a probably great channel for content distribution. This could be your chance to grow your audience several orders of magnite, just become a Microsoft Live developer and port your technology (wish it was as easily done as said, I know), Internet Video needs to compete in the living room, forget about mobile for now, the living room is here now.

    See More pictures of my Xbox Live update experience.