Skip to content

Instantly share code, notes, and snippets.

@coyote240
Forked from heckj/fabfile.py
Created May 1, 2017 11:23
Show Gist options
  • Select an option

  • Save coyote240/84873bf25a2efaae0fd8c1683e96be7f to your computer and use it in GitHub Desktop.

Select an option

Save coyote240/84873bf25a2efaae0fd8c1683e96be7f to your computer and use it in GitHub Desktop.

Revisions

  1. @fiee fiee revised this gist Sep 2, 2009. 1 changed file with 104 additions and 44 deletions.
    148 changes: 104 additions & 44 deletions fabfile.py
    Original file line number Diff line number Diff line change
    @@ -6,10 +6,11 @@
    see http://morethanseven.net/2009/07/27/fabric-django-git-apache-mod_wsgi-virtualenv-and-p/
    modified for fabric 0.9/1.0 by Hraban (fiëé visuëlle)
    several additions, corrections and customizations, too
    This fabric file makes setting up and deploying a django application much
    This fabric file makes setting up and deploying a Django application much
    easier, but it does make a few assumptions. Namely that you're using Git,
    Apache and mod_wsgi and your using Debian or Ubuntu. Also you should have
    Apache and `mod_wsgi` and you're using Debian or Ubuntu. Also you should have
    Django installed on your local machine and SSH installed on both the local
    machine and any servers you want to deploy to.
    @@ -23,61 +24,81 @@
    django-admin.py startproject project_name
    Now add a requirements file so pip knows to install Django. You'll probably
    add other required modules in here later. Creat a file called requirements.txt
    add other required modules in here later. Create a file called `requirements.txt`
    and save it at the top level with the following contents:
    Django
    (Add other requirements at will, e.g. django-tinymce)
    Then save this fabfile.py file in the top level directory which should give you:
    Then save this `fabfile.py` file (this gist!) in the top level directory
    which should give you:
    project_name
    project_name/
    fabfile.py
    requirements.txt
    project_name
    project_name/
    __init__.py
    manage.py
    settings.py
    urls.py
    You'll need a WSGI file called project_name.wsgi, where project_name
    is the name you gave to your django project. It will probably look
    You'll need a WSGI file called `django.wsgi`. It will probably look
    like the following, depending on your specific paths and the location
    of your settings module
    import os
    import sys
    # put the Django project on sys.path
    sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../")))
    sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))))
    os.environ["DJANGO_SETTINGS_MODULE"] = "project_name.settings"
    from django.core.handlers.wsgi import WSGIHandler
    application = WSGIHandler()
    Last but not least you'll want a virtualhost file for apache which looks
    something like the following. Save this as vhost.conf in the inner directory.
    You'll want to change /path/to/project_name/ to the location on the remote
    something like the following. Save this as `vhost.conf` in the inner directory.
    You'll want to change `/path/to/project_name/` to the location on the remote
    server you intent to deploy to.
    <VirtualHost *:80>
    WSGIDaemonProcess project_name-production user=project_name group=project_name threads=10 python-path=/path/to/project_name/lib/python2.6/site-packages
    WSGIProcessGroup project_name-production
    WSGIScriptAlias / /path/to/project_name/releases/current/project_name/project_name.wsgi
    <Directory /path/to/project_name/releases/current/project_name>
    Order deny,allow
    Allow from all
    </Directory>
    ErrorLog /var/log/apache2/error.log
    LogLevel warn
    CustomLog /var/log/apache2/access.log combined
    </VirtualHost>
    Now create a file called .gitignore, containing the following. This
    <VirtualHost *:80>
    ServerName www.project_name.com
    ServerAlias project_name.example.com # temporary location
    # disable listing and "guessing" of static files
    <Directory /var/www/>
    Options -Indexes FollowSymLinks -MultiViews
    AllowOverride None
    Order deny,allow
    Allow from all
    </Directory>
    Alias /favicon.ico /var/www/project_name/releases/current/project_name/static/favicon.ico
    # project media
    Alias /media /var/www/project_name/releases/current/project_name/static
    <Location "/media">
    SetHandler None
    </Location>
    # general admin media
    Alias /django_admin_media /var/www/project_name/lib/python2.5/site-packages/django/contrib/admin/media
    <Location "/django_admin_media">
    SetHandler None
    </Location>
    WSGIDaemonProcess project_name-production user=project_name group=project_name threads=10 python-path=/var/www/project_name/lib/python2.5/site-packages:/var/www/python
    WSGIProcessGroup project_name-production
    WSGIScriptAlias / /var/www/project_name/releases/current/django.wsgi
    ErrorLog /var/www/project_name/logs/error.log
    LogLevel warn
    CustomLog /var/www/project_name/logs/access.log combined
    </VirtualHost>
    Now create a file called `.gitignore`, containing the following. This
    prevents the compiled python code being included in the repository and
    the archive we use for deployment. (Add more that you don't want to
    publish, e.g. your local settings and development database.)
    @@ -86,24 +107,41 @@
    *.pyo
    .tmp*
    E.g. here's my default `.gitignore` file:
    *.pyc
    *.pyo
    .tmp*
    .DS_Store
    .project
    .pydevproject
    .settings
    .svn
    secret.txt
    settings_local.py
    dev.db
    build
    *.tar.gz
    *.bak
    You should now be ready to initialise a git repository in the top
    level project_name directory.
    git init
    git add .gitignore project_name
    git add .gitignore *
    git commit -m "Initial commit"
    All of that should leave you with
    project_name
    .git
    project_name/
    .git/
    .gitignore
    requirements.txt
    fabfile.py
    project_name
    project_name/
    __init__.py
    project_name
    project_name.wsgi
    django.wsgi
    manage.py
    settings.py
    urls.py
    @@ -119,8 +157,22 @@
    fab local setup
    _note: Since you can't properly setup the required user and database via fab anyway,
    it may be better to comment the lines calling aptitude._
    Here's what I do to create user and database:
    (I re-activated the wheel group for sudo-enabled users. That's a potential security risk!)
    adduser --ingroup wheel project_name
    mysql -u root -p
    create user 'project_name'@'localhost' identified by 'password';
    create database proejct_name character set 'utf-8';
    use project_name;
    grant all privileges on project_name.* to 'project_name'@'localhost';
    quit;
    After you've made a few changes and commit them to the master Git branch you
    can run to deply the changes.
    can run to deploy the changes.
    fab local deploy
    @@ -145,6 +197,7 @@

    # globals
    env.project_name = 'project_name'
    env.use_photologue = False # django-photologue gallery module

    # environments

    @@ -179,19 +232,24 @@ def setup():
    sudo('aptitude install -y python-setuptools')
    sudo('easy_install pip')
    sudo('pip install virtualenv')
    sudo('aptitude install -y apache2')
    sudo('aptitude install -y libapache2-mod-wsgi')
    # we want rid of the defult apache config
    sudo('aptitude install -y apache2-threaded')
    sudo('aptitude install -y libapache2-mod-wsgi') # beware, outdated on hardy!
    # we want to get rid of the default apache config
    sudo('cd /etc/apache2/sites-available/; a2dissite default;', pty=True)
    run('mkdir -p %(path)s; cd %(path)s; virtualenv .;' % env, pty=True)
    run('cd %(path)s; mkdir releases; mkdir shared; mkdir packages;' % env, pty=True)
    sudo('mkdir -p %(path)s; chown %(user)s:%(user)s %(path)s;' % env, pty=True)
    run('ln -s %(path)s www;' % env, pty=True) # symlink web dir in home
    with cd(env.path):
    run('virtualenv .;' % env, pty=True)
    run('mkdir logs; chmod a+w logs; mkdir releases; mkdir shared; mkdir packages;' % env, pty=True)
    if env.use_photologue: run('mkdir photologue');
    run('cd releases; ln -s . current; ln -s . previous;', pty=True)
    deploy()

    def deploy():
    """
    Deploy the latest version of the site to the servers, install any
    required third party modules, install the virtual host and
    then restart the webserver
    Deploy the latest version of the site to the servers,
    install any required third party modules,
    install the virtual host and then restart the webserver
    """
    require('hosts', provided_by=[localhost,webserver])
    require('path')
    @@ -230,10 +288,10 @@ def rollback():
    # Helpers. These are called by other functions rather than directly

    def upload_tar_from_git():
    require('release', provided_by=[deploy, setup])
    "Create an archive from the current Git master branch and upload it"
    require('release', provided_by=[deploy, setup])
    local('git archive --format=tar master | gzip > %(release)s.tar.gz' % env)
    run('mkdir %(path)s/releases/%(release)s' % env, pty=True)
    run('mkdir -p %(path)s/releases/%(release)s' % env, pty=True)
    put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env)
    run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
    local('rm %(release)s.tar.gz' % env)
    @@ -256,6 +314,8 @@ def symlink_current_release():
    with cd(env.path):
    run('rm releases/previous; mv releases/current releases/previous;')
    run('ln -s %(release)s releases/current' % env)
    if env.use_photologue:
    run('cd releases/current/%(project_name)s/static; rm -rf photologue; ln -s %(path)s/photologue;' % env, pty=True)

    def migrate():
    "Update the database"
    @@ -264,4 +324,4 @@ def migrate():

    def restart_webserver():
    "Restart the web server"
    sudo('/etc/init.d/apache2 graceful')
    sudo('/etc/init.d/apache2 reload', pty=True)
  2. @fiee fiee revised this gist Jul 29, 2009. 1 changed file with 7 additions and 5 deletions.
    12 changes: 7 additions & 5 deletions fabfile.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,11 @@
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    fabfile for Django
    ------------------
    see http://morethanseven.net/2009/07/27/fabric-django-git-apache-mod_wsgi-virtualenv-and-p/
    modified for fabric 0.9/1.0 by Hraban (fiëé visuëlle)
    This fabric file makes setting up and deploying a django application much
    easier, but it does make a few assumptions. Namely that you're using Git,
    @@ -133,11 +140,6 @@
    fab local test deploy
    """
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # fabfile for Django:
    # http://morethanseven.net/2009/07/27/fabric-django-git-apache-mod_wsgi-virtualenv-and-p/
    # modified for fabric 0.9/1.0
    from __future__ import with_statement # needed for python 2.5
    from fabric.api import *

  3. @fiee fiee renamed this gist Jul 29, 2009. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. @fiee fiee revised this gist Jul 29, 2009. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -151,14 +151,14 @@ def localhost():
    env.hosts = ['localhost']
    env.user = 'username'
    env.path = '/home/%(user)s/workspace/%(project_name)s' % env
    env.virtualhost_path = "/"
    env.virtualhost_path = env.path

    def webserver():
    "Use the actual webserver"
    env.hosts = ['www.example.com']
    env.user = 'username'
    env.path = '/var/www/%(project_name)s' % env
    env.virtualhost_path = "/"
    env.virtualhost_path = env.path

    # tasks

    @@ -232,9 +232,9 @@ def upload_tar_from_git():
    "Create an archive from the current Git master branch and upload it"
    local('git archive --format=tar master | gzip > %(release)s.tar.gz' % env)
    run('mkdir %(path)s/releases/%(release)s' % env, pty=True)
    put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env, pty=True)
    put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env)
    run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
    local('rm %(release)s.tar.gz' % env, pty=True)
    local('rm %(release)s.tar.gz' % env)

    def install_site():
    "Add the virtualhost file to apache"
  5. @fiee fiee renamed this gist Jul 29, 2009. 1 changed file with 71 additions and 54 deletions.
    125 changes: 71 additions & 54 deletions gistfile1 → gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,8 @@ add other required modules in here later. Creat a file called requirements.txt
    and save it at the top level with the following contents:

    Django

    (Add other requirements at will, e.g. django-tinymce)

    Then save this fabfile.py file in the top level directory which should give you:

    @@ -49,31 +51,33 @@ of your settings module
    application = WSGIHandler()

    Last but not least you'll want a virtualhost file for apache which looks
    something like the following. Save this as project_name in the inner directory.
    something like the following. Save this as vhost.conf in the inner directory.
    You'll want to change /path/to/project_name/ to the location on the remote
    server you intent to deploy to.

    <VirtualHost *:80>
    WSGIDaemonProcess project_name-production user=project_name group=project_name threads=10 python-path=/path/to/project_name/lib/python2.6/site-packages
    WSGIProcessGroup project_name-production

    WSGIScriptAlias / /path/to/project_name/releases/current/project_name/project_name.wsgi

    <Directory /path/to/project_name/releases/current/project_name>
    Order deny,allow
    Allow from all
    </Directory>

    ErrorLog /var/log/apache2/error.log
    LogLevel warn

    CustomLog /var/log/apache2/access.log combined
    </VirtualHost>

    Now create a file called .gitignore, containing the following. This
    prevents the compiled python code being included in the repository and
    the archive we use for deployment.
    the archive we use for deployment. (Add more that you don't want to
    publish, e.g. your local settings and development database.)

    *.pyc
    *.pyo
    .tmp*

    You should now be ready to initialise a git repository in the top
    level project_name directory.
    @@ -129,120 +133,133 @@ do the following.
    fab local test deploy

    """
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # fabfile for Django:
    # http://morethanseven.net/2009/07/27/fabric-django-git-apache-mod_wsgi-virtualenv-and-p/
    # modified for fabric 0.9/1.0
    from __future__ import with_statement # needed for python 2.5
    from fabric.api import *

    # globals

    config.project_name = 'project_name'
    env.project_name = 'project_name'

    # environments

    def local():
    def localhost():
    "Use the local virtual server"
    config.hosts = ['172.16.142.130']
    config.path = '/path/to/project_name'
    config.user = 'garethr'
    config.virtualhost_path = "/"

    env.hosts = ['localhost']
    env.user = 'username'
    env.path = '/home/%(user)s/workspace/%(project_name)s' % env
    env.virtualhost_path = "/"

    def webserver():
    "Use the actual webserver"
    env.hosts = ['www.example.com']
    env.user = 'username'
    env.path = '/var/www/%(project_name)s' % env
    env.virtualhost_path = "/"

    # tasks

    def test():
    "Run the test suite and bail out if it fails"
    local("cd $(project_name); python manage.py test", fail="abort")

    result = local("cd %(path)s; python manage.py test" % env) #, fail="abort")


    def setup():
    """
    Setup a fresh virtualenv as well as a few useful directories, then run
    a full deployment
    """
    require('hosts', provided_by=[local])
    require('hosts', provided_by=[localhost,webserver])
    require('path')

    sudo('aptitude install -y python-setuptools')
    sudo('easy_install pip')
    sudo('pip install virtualenv')
    sudo('aptitude install -y apache2')
    sudo('aptitude install -y libapache2-mod-wsgi')
    # we want rid of the defult apache config
    sudo('cd /etc/apache2/sites-available/; a2dissite default;')
    run('mkdir -p $(path); cd $(path); virtualenv .;')
    run('cd $(path); mkdir releases; mkdir shared; mkdir packages;', fail='ignore')
    sudo('cd /etc/apache2/sites-available/; a2dissite default;', pty=True)
    run('mkdir -p %(path)s; cd %(path)s; virtualenv .;' % env, pty=True)
    run('cd %(path)s; mkdir releases; mkdir shared; mkdir packages;' % env, pty=True)
    deploy()

    def deploy():
    """
    Deploy the latest version of the site to the servers, install any
    required third party modules, install the virtual host and
    then restart the webserver
    """
    require('hosts', provided_by=[local])
    require('hosts', provided_by=[localhost,webserver])
    require('path')

    import time
    config.release = time.strftime('%Y%m%d%H%M%S')

    env.release = time.strftime('%Y%m%d%H%M%S')
    upload_tar_from_git()
    install_requirements()
    install_site()
    symlink_current_release()
    migrate()
    restart_webserver()

    def deploy_version(version):
    "Specify a specific version to be made live"
    require('hosts', provided_by=[local])
    require('hosts', provided_by=[localhost,webserver])
    require('path')

    config.version = version
    run('cd $(path); rm releases/previous; mv releases/current releases/previous;')
    run('cd $(path); ln -s $(version) releases/current')
    env.version = version
    with cd(env.path):
    run('rm releases/previous; mv releases/current releases/previous;', pty=True)
    run('ln -s %(version)s releases/current' % env, pty=True)
    restart_webserver()

    def rollback():
    """
    Limited rollback capability. Simple loads the previously current
    version of the code. Rolling back again will swap between the two.
    """
    require('hosts', provided_by=[local])
    require('hosts', provided_by=[localhost,webserver])
    require('path')

    run('cd $(path); mv releases/current releases/_previous;')
    run('cd $(path); mv releases/previous releases/current;')
    run('cd $(path); mv releases/_previous releases/previous;')
    restart_webserver()
    with cd(env.path):
    run('mv releases/current releases/_previous;', pty=True)
    run('mv releases/previous releases/current;', pty=True)
    run('mv releases/_previous releases/previous;', pty=True)
    restart_webserver()

    # Helpers. These are called by other functions rather than directly

    def upload_tar_from_git():
    require('release', provided_by=[deploy, setup])
    "Create an archive from the current Git master branch and upload it"
    local('git archive --format=tar master | gzip > $(release).tar.gz')
    run('mkdir $(path)/releases/$(release)')
    put('$(release).tar.gz', '$(path)/packages/')
    run('cd $(path)/releases/$(release) && tar zxf ../../packages/$(release).tar.gz')
    local('rm $(release).tar.gz')

    local('git archive --format=tar master | gzip > %(release)s.tar.gz' % env)
    run('mkdir %(path)s/releases/%(release)s' % env, pty=True)
    put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env, pty=True)
    run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
    local('rm %(release)s.tar.gz' % env, pty=True)
    def install_site():
    "Add the virtualhost file to apache"
    require('release', provided_by=[deploy, setup])
    sudo('cd $(path)/releases/$(release); cp $(project_name)$(virtualhost_path)$(project_name) /etc/apache2/sites-available/')
    sudo('cd /etc/apache2/sites-available/; a2ensite $(project_name)')

    #sudo('cd %(path)s/releases/%(release)s; cp %(project_name)s%(virtualhost_path)s%(project_name)s /etc/apache2/sites-available/' % env)
    sudo('cd %(path)s/releases/%(release)s; cp vhost.conf /etc/apache2/sites-available/%(project_name)s' % env)
    sudo('cd /etc/apache2/sites-available/; a2ensite %(project_name)s' % env, pty=True)

    def install_requirements():
    "Install the required packages from the requirements file using pip"
    require('release', provided_by=[deploy, setup])
    run('cd $(path); pip install -E . -r ./releases/$(release)/requirements.txt')

    run('cd %(path)s; pip install -E . -r ./releases/%(release)s/requirements.txt' % env, pty=True)
    def symlink_current_release():
    "Symlink our current release"
    require('release', provided_by=[deploy, setup])
    run('cd $(path); rm releases/previous; mv releases/current releases/previous;', fail='ignore')
    run('cd $(path); ln -s $(release) releases/current')

    with cd(env.path):
    run('rm releases/previous; mv releases/current releases/previous;')
    run('ln -s %(release)s releases/current' % env)

    def migrate():
    "Update the database"
    require('project_name')
    run('cd $(path)/releases/current/$(project_name); ../../../bin/python manage.py syncdb --noinput')

    run('cd %(path)s/releases/current/%(project_name)s; ../../../bin/python manage.py syncdb --noinput' % env, pty=True)
    def restart_webserver():
    "Restart the web server"
    sudo('/etc/init.d/apache2 restart')
    sudo('/etc/init.d/apache2 graceful')
  6. @invalid-email-address Anonymous created this gist Jul 27, 2009.
    248 changes: 248 additions & 0 deletions gistfile1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,248 @@
    """

    This fabric file makes setting up and deploying a django application much
    easier, but it does make a few assumptions. Namely that you're using Git,
    Apache and mod_wsgi and your using Debian or Ubuntu. Also you should have
    Django installed on your local machine and SSH installed on both the local
    machine and any servers you want to deploy to.

    _note that I've used the name project_name throughout this example. Replace
    this with whatever your project is called._

    First step is to create your project locally:

    mkdir project_name
    cd project_name
    django-admin.py startproject project_name

    Now add a requirements file so pip knows to install Django. You'll probably
    add other required modules in here later. Creat a file called requirements.txt
    and save it at the top level with the following contents:

    Django

    Then save this fabfile.py file in the top level directory which should give you:

    project_name
    fabfile.py
    requirements.txt
    project_name
    __init__.py
    manage.py
    settings.py
    urls.py

    You'll need a WSGI file called project_name.wsgi, where project_name
    is the name you gave to your django project. It will probably look
    like the following, depending on your specific paths and the location
    of your settings module

    import os
    import sys

    # put the Django project on sys.path
    sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../")))

    os.environ["DJANGO_SETTINGS_MODULE"] = "project_name.settings"

    from django.core.handlers.wsgi import WSGIHandler
    application = WSGIHandler()

    Last but not least you'll want a virtualhost file for apache which looks
    something like the following. Save this as project_name in the inner directory.
    You'll want to change /path/to/project_name/ to the location on the remote
    server you intent to deploy to.

    <VirtualHost *:80>
    WSGIDaemonProcess project_name-production user=project_name group=project_name threads=10 python-path=/path/to/project_name/lib/python2.6/site-packages
    WSGIProcessGroup project_name-production

    WSGIScriptAlias / /path/to/project_name/releases/current/project_name/project_name.wsgi
    <Directory /path/to/project_name/releases/current/project_name>
    Order deny,allow
    Allow from all
    </Directory>

    ErrorLog /var/log/apache2/error.log
    LogLevel warn

    CustomLog /var/log/apache2/access.log combined
    </VirtualHost>

    Now create a file called .gitignore, containing the following. This
    prevents the compiled python code being included in the repository and
    the archive we use for deployment.

    *.pyc

    You should now be ready to initialise a git repository in the top
    level project_name directory.

    git init
    git add .gitignore project_name
    git commit -m "Initial commit"

    All of that should leave you with

    project_name
    .git
    .gitignore
    requirements.txt
    fabfile.py
    project_name
    __init__.py
    project_name
    project_name.wsgi
    manage.py
    settings.py
    urls.py

    In reality you might prefer to keep your wsgi files and virtual host files
    elsewhere. The fabfile has a variable (config.virtualhost_path) for this case.
    You'll also want to set the hosts that you intend to deploy to (config.hosts)
    as well as the user (config.user).

    The first task we're interested in is called setup. It installs all the
    required software on the remote machine, then deploys your code and restarts
    the webserver.

    fab local setup

    After you've made a few changes and commit them to the master Git branch you
    can run to deply the changes.

    fab local deploy

    If something is wrong then you can rollback to the previous version.

    fab local rollback

    Note that this only allows you to rollback to the release immediately before
    the latest one. If you want to pick a arbitrary release then you can use the
    following, where 20090727170527 is a timestamp for an existing release.

    fab local deploy_version:20090727170527

    If you want to ensure your tests run before you make a deployment then you can
    do the following.

    fab local test deploy

    """

    # globals

    config.project_name = 'project_name'

    # environments

    def local():
    "Use the local virtual server"
    config.hosts = ['172.16.142.130']
    config.path = '/path/to/project_name'
    config.user = 'garethr'
    config.virtualhost_path = "/"

    # tasks

    def test():
    "Run the test suite and bail out if it fails"
    local("cd $(project_name); python manage.py test", fail="abort")

    def setup():
    """
    Setup a fresh virtualenv as well as a few useful directories, then run
    a full deployment
    """
    require('hosts', provided_by=[local])
    require('path')

    sudo('aptitude install -y python-setuptools')
    sudo('easy_install pip')
    sudo('pip install virtualenv')
    sudo('aptitude install -y apache2')
    sudo('aptitude install -y libapache2-mod-wsgi')
    # we want rid of the defult apache config
    sudo('cd /etc/apache2/sites-available/; a2dissite default;')
    run('mkdir -p $(path); cd $(path); virtualenv .;')
    run('cd $(path); mkdir releases; mkdir shared; mkdir packages;', fail='ignore')
    deploy()

    def deploy():
    """
    Deploy the latest version of the site to the servers, install any
    required third party modules, install the virtual host and
    then restart the webserver
    """
    require('hosts', provided_by=[local])
    require('path')

    import time
    config.release = time.strftime('%Y%m%d%H%M%S')

    upload_tar_from_git()
    install_requirements()
    install_site()
    symlink_current_release()
    migrate()
    restart_webserver()

    def deploy_version(version):
    "Specify a specific version to be made live"
    require('hosts', provided_by=[local])
    require('path')

    config.version = version
    run('cd $(path); rm releases/previous; mv releases/current releases/previous;')
    run('cd $(path); ln -s $(version) releases/current')
    restart_webserver()

    def rollback():
    """
    Limited rollback capability. Simple loads the previously current
    version of the code. Rolling back again will swap between the two.
    """
    require('hosts', provided_by=[local])
    require('path')

    run('cd $(path); mv releases/current releases/_previous;')
    run('cd $(path); mv releases/previous releases/current;')
    run('cd $(path); mv releases/_previous releases/previous;')
    restart_webserver()

    # Helpers. These are called by other functions rather than directly

    def upload_tar_from_git():
    require('release', provided_by=[deploy, setup])
    "Create an archive from the current Git master branch and upload it"
    local('git archive --format=tar master | gzip > $(release).tar.gz')
    run('mkdir $(path)/releases/$(release)')
    put('$(release).tar.gz', '$(path)/packages/')
    run('cd $(path)/releases/$(release) && tar zxf ../../packages/$(release).tar.gz')
    local('rm $(release).tar.gz')

    def install_site():
    "Add the virtualhost file to apache"
    require('release', provided_by=[deploy, setup])
    sudo('cd $(path)/releases/$(release); cp $(project_name)$(virtualhost_path)$(project_name) /etc/apache2/sites-available/')
    sudo('cd /etc/apache2/sites-available/; a2ensite $(project_name)')

    def install_requirements():
    "Install the required packages from the requirements file using pip"
    require('release', provided_by=[deploy, setup])
    run('cd $(path); pip install -E . -r ./releases/$(release)/requirements.txt')

    def symlink_current_release():
    "Symlink our current release"
    require('release', provided_by=[deploy, setup])
    run('cd $(path); rm releases/previous; mv releases/current releases/previous;', fail='ignore')
    run('cd $(path); ln -s $(release) releases/current')

    def migrate():
    "Update the database"
    require('project_name')
    run('cd $(path)/releases/current/$(project_name); ../../../bin/python manage.py syncdb --noinput')

    def restart_webserver():
    "Restart the web server"
    sudo('/etc/init.d/apache2 restart')