Merge pull request #63 from dtroyer/2-checkout-devstack

checkout devstack
diff --git a/README b/README.md
similarity index 94%
rename from README
rename to README.md
index 61119e9..82ef7fb 100644
--- a/README
+++ b/README.md
@@ -7,6 +7,8 @@
 * To make it easier for developers to dive into openstack so that they can productively contribute without having to understand every part of the system at once
 * To make it easy to prototype cross-project features
 
+Read more at http://devstack.org (built from the gh-pages branch)
+
 Be sure to carefully read these scripts before you run them as they install software and may alter your networking configuration.
 
 # To start a dev cloud on your local machine (installing on a dedicated vm is safer!):
@@ -32,7 +34,6 @@
 
 * Add python-novaclient cli support
 * syslog
-* allow rabbit connection to be specified via environment variables with sensible defaults
 * Add volume support
 * Add quantum support
 
diff --git a/exercise.sh b/exercise.sh
index d9d4c0a..1a812f4 100755
--- a/exercise.sh
+++ b/exercise.sh
@@ -7,11 +7,11 @@
 #
 
 
-# This script exits on an error so that errors don't compound and you see 
+# This script exits on an error so that errors don't compound and you see
 # only the first error that occured.
 set -o errexit
 
-# Print the commands being run so that we can see the command that triggers 
+# Print the commands being run so that we can see the command that triggers
 # an error.  It is also useful for following allowing as the install occurs.
 set -o xtrace
 
@@ -19,6 +19,9 @@
 # Settings
 # ========
 
+# Use stackrc and localrc for settings
+source ./stackrc
+
 HOST=${HOST:-localhost}
 
 # Nova original used project_id as the *account* that owned resources (servers,
@@ -33,13 +36,13 @@
 export NOVA_USERNAME=${USERNAME:-demo}
 
 # With Keystone you pass the keystone password instead of an api key.
-export NOVA_API_KEY=${PASSWORD:-secrete}
+export NOVA_API_KEY=${ADMIN_PASSWORD:-secrete}
 
-# With the addition of Keystone, to use an openstack cloud you should 
-# authenticate against keystone, which returns a **Token** and **Service 
-# Catalog**.  The catalog contains the endpoint for all services the user/tenant 
-# has access to - including nova, glance, keystone, swift, ...  We currently 
-# recommend using the 2.0 *auth api*.  
+# With the addition of Keystone, to use an openstack cloud you should
+# authenticate against keystone, which returns a **Token** and **Service
+# Catalog**.  The catalog contains the endpoint for all services the user/tenant
+# has access to - including nova, glance, keystone, swift, ...  We currently
+# recommend using the 2.0 *auth api*.
 #
 # *NOTE*: Using the 2.0 *auth api* does mean that compute api is 2.0.  We will
 # use the 1.1 *compute api*
@@ -52,10 +55,17 @@
 # FIXME - why does this need to be specified?
 export NOVA_REGION_NAME=RegionOne
 
+# set log level to DEBUG (helps debug issues)
+export NOVACLIENT_DEBUG=1
 
 # Get a token for clients that don't support service catalog
 # ==========================================================
-SERVICE_TOKEN=`curl -s -d  "{\"auth\":{\"passwordCredentials\": {\"username\": \"$NOVA_PROJECT_ID\", \"password\": \"$NOVA_API_KEY\"}}}" -H "Content-type: application/json" http://$HOST:5000/v2.0/tokens | python -c "import sys; import json; tok = json.loads(sys.stdin.read()); print tok['access']['token']['id'];"`
+
+# manually create a token by querying keystone (sending JSON data).  Keystone
+# returns a token and catalog of endpoints.  We use python to parse the token
+# and save it.
+
+TOKEN=`curl -s -d  "{\"auth\":{\"passwordCredentials\": {\"username\": \"$NOVA_USERNAME\", \"password\": \"$NOVA_API_KEY\"}}}" -H "Content-type: application/json" http://$HOST:5000/v2.0/tokens | python -c "import sys; import json; tok = json.loads(sys.stdin.read()); print tok['access']['token']['id'];"`
 
 # Launching a server
 # ==================
@@ -63,9 +73,6 @@
 # List servers for tenant:
 nova list
 
-# List of flavors:
-nova flavor-list
-
 # Images
 # ------
 
@@ -73,10 +80,92 @@
 nova image-list
 
 # But we recommend using glance directly
-glance -A $SERVICE_TOKEN index
+glance -A $TOKEN index
 
-# show details of the active servers::
-#
-#     nova show 1234
-#
-nova list | grep ACTIVE | cut -d \| -f2 | xargs -n1 nova show
+# Let's grab the id of the first AMI image to launch
+IMAGE=`glance -A $TOKEN index | egrep ami | cut -d" " -f1`
+
+# Security Groups
+# ---------------
+SECGROUP=test_secgroup
+
+# List of secgroups:
+nova secgroup-list
+
+# Create a secgroup
+nova secgroup-create $SECGROUP "test_secgroup description"
+
+# Flavors
+# -------
+
+# List of flavors:
+nova flavor-list
+
+# and grab the first flavor in the list to launch
+FLAVOR=`nova flavor-list | head -n 4 | tail -n 1 | cut -d"|" -f2`
+
+NAME="myserver"
+
+nova boot --flavor $FLAVOR --image $IMAGE $NAME --security_groups=$SECGROUP
+
+# let's give it 10 seconds to launch
+sleep 10
+
+# check that the status is active
+nova show $NAME | grep status | grep -q ACTIVE
+
+# get the IP of the server
+IP=`nova show $NAME | grep "private network" | cut -d"|" -f3`
+
+# ping it once (timeout of a second)
+ping -c1 -w1 $IP || true
+
+# sometimes the first ping fails (10 seconds isn't enough time for the VM's
+# network to respond?), so let's wait 5 seconds and really test ping
+sleep 5
+
+ping -c1 -w1 $IP
+# allow icmp traffic
+nova secgroup-add-rule $SECGROUP icmp -1 -1 0.0.0.0/0
+
+# List rules for a secgroup
+nova secgroup-list-rules $SECGROUP
+
+# allocate a floating ip
+nova floating-ip-create
+
+# store  floating address
+FIP=`nova floating-ip-list | grep None | head -1 | cut -d '|' -f2 | sed 's/ //g'`
+
+# add floating ip to our server
+nova add-floating-ip $NAME $FIP
+
+# sleep for a smidge
+sleep 1
+
+# ping our fip
+ping -c1 -w1 $FIP
+
+# dis-allow icmp traffic
+nova secgroup-delete-rule $SECGROUP icmp -1 -1 0.0.0.0/0
+
+# sleep for a smidge
+sleep 1
+
+# ping our fip
+if ( ping -c1 -w1 $FIP); then
+    print "Security group failure - ping should not be allowed!"
+    exit 1
+fi
+
+# de-allocate the floating ip
+nova floating-ip-delete $FIP
+
+# shutdown the server
+nova delete $NAME
+
+# Delete a secgroup
+nova secgroup-delete $SECGROUP
+
+# FIXME: validate shutdown within 5 seconds
+# (nova show $NAME returns 1 or status != ACTIVE)?
diff --git a/files/apts/general b/files/apts/general
index 32379c0..b47a60d 100644
--- a/files/apts/general
+++ b/files/apts/general
@@ -12,3 +12,7 @@
 locate # useful when debugging
 python-virtualenv
 python-unittest2
+iputils-ping
+wget
+curl
+tcpdump
diff --git a/files/keystone_data.sh b/files/keystone_data.sh
index f48eaf9..2cca345 100755
--- a/files/keystone_data.sh
+++ b/files/keystone_data.sh
@@ -37,7 +37,7 @@
 $BIN_DIR/keystone-manage $* token add %SERVICE_TOKEN% admin admin 2015-02-05T00:00
 
 # EC2 related creds - note we are setting the token to user_password
-# but keystone doesn't parse them - it is just a blob from keystone's 
+# but keystone doesn't parse them - it is just a blob from keystone's
 # point of view
 $BIN_DIR/keystone-manage $* credentials add admin EC2 'admin_%ADMIN_PASSWORD%' admin admin || echo "no support for adding credentials"
 $BIN_DIR/keystone-manage $* credentials add demo EC2 'demo_%ADMIN_PASSWORD%' demo demo || echo "no support for adding credentials"
diff --git a/stack.sh b/stack.sh
index f6bf534..18206cd 100755
--- a/stack.sh
+++ b/stack.sh
@@ -4,7 +4,7 @@
 
 # This script installs and configures *nova*, *glance*, *dashboard* and *keystone*
 
-# This script allows you to specify configuration options of what git 
+# This script allows you to specify configuration options of what git
 # repositories to use, enabled services, network configuration and various
 # passwords.  If you are crafty you can run the script on multiple nodes using
 # shared settings for common resources (mysql, rabbitmq) and build a multi-node
@@ -20,9 +20,6 @@
 # Sanity Check
 # ============
 
-# Record the start time.  This allows us to print how long this script takes to run.
-START_TIME=`python -c "import time; print time.time()"`
-
 # Warn users who aren't on natty, but allow them to override check and attempt
 # installation with ``FORCE=yes ./stack``
 if ! grep -q natty /etc/lsb-release; then
@@ -33,57 +30,20 @@
     fi
 fi
 
+# Keep track of the current devstack directory.
+TOP_DIR=$(cd $(dirname "$0") && pwd)
+
 # stack.sh keeps the list of **apt** and **pip** dependencies in external
 # files, along with config templates and other useful files.  You can find these
 # in the ``files`` directory (next to this script).  We will reference this
 # directory using the ``FILES`` variable in this script.
-FILES=`pwd`/files
+FILES=$TOP_DIR/files
 if [ ! -d $FILES ]; then
     echo "ERROR: missing devstack/files - did you grab more than just stack.sh?"
     exit 1
 fi
 
-# OpenStack is designed to be run as a regular user (Dashboard will fail to run
-# as root, since apache refused to startup serve content from root user).  If
-# stack.sh is run as root, it automatically creates a stack user with
-# sudo privileges and runs as that user.
 
-if [[ $EUID -eq 0 ]]; then
-    echo "You are running this script as root."
-
-    # since this script runs as a normal user, we need to give that user
-    # ability to run sudo
-    apt-get update
-    apt-get install -qqy sudo
-
-    if ! getent passwd | grep -q stack; then
-        echo "Creating a user called stack"
-        useradd -U -G sudo -s /bin/bash -m stack
-    fi
-    echo "Giving stack user passwordless sudo priviledges"
-    echo "stack ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
-
-    echo "Copying files to stack user"
-    cp -r -f `pwd` /home/stack/
-    THIS_DIR=$(basename $(dirname $(readlink -f $0)))
-    chown -R stack /home/stack/$THIS_DIR
-    echo "Running the script as stack in 3 seconds..."
-    sleep 3
-    if [[ "$SHELL_AFTER_RUN" != "no" ]]; then
-	exec su -c "cd /home/stack/$THIS_DIR/; bash stack.sh; bash" stack
-    else
-	exec su -c "cd /home/stack/$THIS_DIR/; bash stack.sh" stack
-    fi
-    exit 0
-fi
-
-# So that errors don't compound we exit on any errors so you see only the
-# first error that occured.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following allowing as the install occurs.
-set -o xtrace
 
 # Settings
 # ========
@@ -91,34 +51,72 @@
 # This script is customizable through setting environment variables.  If you
 # want to override a setting you can either::
 #
-#     export MYSQL_PASS=anothersecret
+#     export MYSQL_PASSWORD=anothersecret
 #     ./stack.sh
 #
-# You can also pass options on a single line ``MYSQL_PASS=simple ./stack.sh``
+# You can also pass options on a single line ``MYSQL_PASSWORD=simple ./stack.sh``
 #
 # Additionally, you can put any local variables into a ``localrc`` file, like::
 #
-#     MYSQL_PASS=anothersecret
+#     MYSQL_PASSWORD=anothersecret
 #     MYSQL_USER=hellaroot
 #
 # We try to have sensible defaults, so you should be able to run ``./stack.sh``
 # in most cases.
 #
 # We our settings from ``stackrc``.  This file is distributed with devstack and
-# contains locations for what repositories to use.  If you want to use other 
-# repositories and branches, you can add your own settings with another file 
+# contains locations for what repositories to use.  If you want to use other
+# repositories and branches, you can add your own settings with another file
 # called ``localrc``
 #
-# If ``localrc`` exists, then ``stackrc`` will load those settings.  This is 
+# If ``localrc`` exists, then ``stackrc`` will load those settings.  This is
 # useful for changing a branch or repostiory to test other versions.  Also you
-# can store your other settings like **MYSQL_PASS** or **ADMIN_PASSWORD** instead
+# can store your other settings like **MYSQL_PASSWORD** or **ADMIN_PASSWORD** instead
 # of letting devstack generate random ones for you.
 source ./stackrc
 
 # Destination path for installation ``DEST``
 DEST=${DEST:-/opt/stack}
-sudo mkdir -p $DEST
-sudo chown `whoami` $DEST
+
+# OpenStack is designed to be run as a regular user (Dashboard will fail to run
+# as root, since apache refused to startup serve content from root user).  If
+# stack.sh is run as root, it automatically creates a stack user with
+# sudo privileges and runs as that user.
+
+if [[ $EUID -eq 0 ]]; then
+    ROOTSLEEP=${ROOTSLEEP:-10}
+    echo "You are running this script as root."
+    echo "In $ROOTSLEEP seconds, we will create a user 'stack' and run as that user"
+    sleep $ROOTSLEEP
+
+    # since this script runs as a normal user, we need to give that user
+    # ability to run sudo
+    apt_get update
+    apt_get install sudo
+
+    if ! getent passwd stack >/dev/null; then
+        echo "Creating a user called stack"
+        useradd -U -G sudo -s /bin/bash -d $DEST -m stack
+    fi
+
+    echo "Giving stack user passwordless sudo priviledges"
+    # natty uec images sudoers does not have a '#includedir'. add one.
+    grep -q "^#includedir.*/etc/sudoers.d" /etc/sudoers ||
+        echo "#includedir /etc/sudoers.d" >> /etc/sudoers
+    ( umask 226 && echo "stack ALL=(ALL) NOPASSWD:ALL" \
+        > /etc/sudoers.d/50_stack_sh )
+
+    echo "Copying files to stack user"
+    STACK_DIR="$DEST/${PWD##*/}"
+    cp -r -f "$PWD" "$STACK_DIR"
+    chown -R stack "$STACK_DIR"
+    if [[ "$SHELL_AFTER_RUN" != "no" ]]; then
+        exec su -c "set -e; cd $STACK_DIR; bash stack.sh; bash" stack
+    else
+        exec su -c "set -e; cd $STACK_DIR; bash stack.sh" stack
+    fi
+    exit 1
+fi
 
 # Set the destination directories for openstack projects
 NOVA_DIR=$DEST/nova
@@ -146,10 +144,55 @@
     HOST_IP=`LC_ALL=C /sbin/ifconfig  | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
 fi
 
+# apt-get wrapper to just get arguments set correctly
+function apt_get() {
+    local sudo="sudo"
+    [ "$(id -u)" = "0" ] && sudo=""
+    $sudo DEBIAN_FRONTEND=noninteractive apt-get \
+        --option "Dpkg::Options::=--force-confold" --assume-yes "$@"
+}
+
+# Generic helper to configure passwords
+function read_password {
+    set +o xtrace
+    var=$1; msg=$2
+    pw=${!var}
+
+    localrc=$TOP_DIR/localrc
+
+    # If the password is not defined yet, proceed to prompt user for a password.
+    if [ ! $pw ]; then
+        # If there is no localrc file, create one
+        if [ ! -e $localrc ]; then
+            touch $localrc
+        fi
+
+        # Presumably if we got this far it can only be that our localrc is missing
+        # the required password.  Prompt user for a password and write to localrc.
+        echo ''
+        echo '################################################################################'
+        echo $msg
+        echo '################################################################################'
+        echo "This value will be written to your localrc file so you don't have to enter it again."
+        echo "It is probably best to avoid spaces and weird characters."
+        echo "If you leave this blank, a random default value will be used."
+        echo "Enter a password now:"
+        read $var
+        pw=${!var}
+        if [ ! $pw ]; then
+            pw=`openssl rand -hex 10`
+        fi
+        eval "$var=$pw"
+        echo "$var=$pw" >> $localrc
+    fi
+    set -o xtrace
+}
+
+
 # Nova Network Configuration
 # --------------------------
 
-# FIXME: more documentation about why these are important flags.  Also 
+# FIXME: more documentation about why these are important flags.  Also
 # we should make sure we use the same variable names as the flag names.
 
 PUBLIC_INTERFACE=${PUBLIC_INTERFACE:-eth0}
@@ -170,15 +213,15 @@
 # variable but make sure that the interface doesn't already have an
 # ip or you risk breaking things.
 #
-# **DHCP Warning**:  If your flat interface device uses DHCP, there will be a 
-# hiccup while the network is moved from the flat interface to the flat network 
-# bridge.  This will happen when you launch your first instance.  Upon launch 
-# you will lose all connectivity to the node, and the vm launch will probably 
+# **DHCP Warning**:  If your flat interface device uses DHCP, there will be a
+# hiccup while the network is moved from the flat interface to the flat network
+# bridge.  This will happen when you launch your first instance.  Upon launch
+# you will lose all connectivity to the node, and the vm launch will probably
 # fail.
-# 
-# If you are running on a single node and don't need to access the VMs from 
+#
+# If you are running on a single node and don't need to access the VMs from
 # devices other than that node, you can set the flat interface to the same
-# value as ``FLAT_NETWORK_BRIDGE``.  This will stop the network hiccup from 
+# value as ``FLAT_NETWORK_BRIDGE``.  This will stop the network hiccup from
 # occuring.
 FLAT_INTERFACE=${FLAT_INTERFACE:-eth0}
 
@@ -188,38 +231,59 @@
 # MySQL & RabbitMQ
 # ----------------
 
-# We configure Nova, Dashboard, Glance and Keystone to use MySQL as their 
+# We configure Nova, Dashboard, Glance and Keystone to use MySQL as their
 # database server.  While they share a single server, each has their own
 # database and tables.
 
-# By default this script will install and configure MySQL.  If you want to 
+# By default this script will install and configure MySQL.  If you want to
 # use an existing server, you can pass in the user/password/host parameters.
-# You will need to send the same ``MYSQL_PASS`` to every host if you are doing
+# You will need to send the same ``MYSQL_PASSWORD`` to every host if you are doing
 # a multi-node devstack installation.
-MYSQL_USER=${MYSQL_USER:-root}
-MYSQL_PASS=${MYSQL_PASS:-`openssl rand -hex 12`}
 MYSQL_HOST=${MYSQL_HOST:-localhost}
+MYSQL_USER=${MYSQL_USER:-root}
+read_password MYSQL_PASSWORD "ENTER A PASSWORD TO USE FOR MYSQL."
 
 # don't specify /db in this string, so we can use it for multiple services
-BASE_SQL_CONN=${BASE_SQL_CONN:-mysql://$MYSQL_USER:$MYSQL_PASS@$MYSQL_HOST}
+BASE_SQL_CONN=${BASE_SQL_CONN:-mysql://$MYSQL_USER:$MYSQL_PASSWORD@$MYSQL_HOST}
 
 # Rabbit connection info
 RABBIT_HOST=${RABBIT_HOST:-localhost}
-RABBIT_PASSWORD=${RABBIT_PASSWORD:-`openssl rand -hex 12`}
+read_password RABBIT_PASSWORD "ENTER A PASSWORD TO USE FOR RABBIT."
 
 # Glance connection info.  Note the port must be specified.
 GLANCE_HOSTPORT=${GLANCE_HOSTPORT:-$HOST_IP:9292}
 
+
 # Keystone
 # --------
 
 # Service Token - Openstack components need to have an admin token
 # to validate user tokens.
-SERVICE_TOKEN=${SERVICE_TOKEN:-`openssl rand -hex 12`}
+read_password SERVICE_TOKEN "ENTER A SERVICE_TOKEN TO USE FOR THE SERVICE ADMIN TOKEN."
 # Dash currently truncates usernames and passwords at 20 characters
-# so use 10 bytes
-ADMIN_PASSWORD=${ADMIN_PASSWORD:-`openssl rand -hex 10`}
+read_password ADMIN_PASSWORD "ENTER A PASSWORD TO USE FOR DASH AND KEYSTONE (20 CHARS OR LESS)."
 
+LOGFILE=${LOGFILE:-"$PWD/stack.sh.$$.log"}
+(
+# So that errors don't compound we exit on any errors so you see only the
+# first error that occured.
+trap failed ERR
+failed() {
+    local r=$?
+    set +o xtrace
+    [ -n "$LOGFILE" ] && echo "${0##*/} failed: full log in $LOGFILE"
+    exit $r
+}
+
+# Print the commands being run so that we can see the command that triggers
+# an error.  It is also useful for following along as the install occurs.
+set -o xtrace
+
+# create the destination directory and ensure it is writable by the user
+sudo mkdir -p $DEST
+if [ ! -w $DEST ]; then
+    sudo chown `whoami` $DEST
+fi
 
 # Install Packages
 # ================
@@ -228,7 +292,8 @@
 
 
 # install apt requirements
-sudo apt-get install -qqy `cat $FILES/apts/* | cut -d\# -f1 | grep -Ev "mysql-server|rabbitmq-server"`
+apt_get update
+apt_get install `cat $FILES/apts/* | cut -d\# -f1 | grep -Ev "mysql-server|rabbitmq-server"`
 
 # install python requirements
 sudo PIP_DOWNLOAD_CACHE=/var/cache/pip pip install `cat $FILES/pips/*`
@@ -237,9 +302,16 @@
 # be owned by the installation user, we create the directory and change the
 # ownership to the proper user.
 function git_clone {
+    # if there is an existing checkout, move it out of the way
+    if [[ "$RECLONE" == "yes" ]]; then
+        # FIXME(ja): if we were smarter we could speed up RECLONE by
+        # using the old git repo as the basis of our new clone...
+        if [ -d $2 ]; then
+            mv $2 /tmp/stack.`date +%s`
+        fi
+    fi
+
     if [ ! -d $2 ]; then
-        sudo mkdir $2
-        sudo chown `whoami` $2
         git clone $1 $2
         cd $2
         # This checkout syntax works for both branches and tags
@@ -269,10 +341,10 @@
 
 # setup our checkouts so they are installed into python path
 # allowing ``import nova`` or ``import glance.client``
-cd $NOVA_DIR; sudo python setup.py develop
-cd $NOVACLIENT_DIR; sudo python setup.py develop
 cd $KEYSTONE_DIR; sudo python setup.py develop
 cd $GLANCE_DIR; sudo python setup.py develop
+cd $NOVACLIENT_DIR; sudo python setup.py develop
+cd $NOVA_DIR; sudo python setup.py develop
 cd $OPENSTACKX_DIR; sudo python setup.py develop
 cd $DASH_DIR/django-openstack; sudo python setup.py develop
 cd $DASH_DIR/openstack-dashboard; sudo python setup.py develop
@@ -288,7 +360,11 @@
 
 if [[ "$ENABLED_SERVICES" =~ "rabbit" ]]; then
     # Install and start rabbitmq-server
-    sudo apt-get install -y -q rabbitmq-server
+    # the temp file is necessary due to LP: #878600
+    tfile=$(mktemp)
+    apt_get install rabbitmq-server > "$tfile" 2>&1
+    cat "$tfile"
+    rm -f "$tfile"
     # change the rabbit password since the default is "guest"
     sudo rabbitmqctl change_password guest $RABBIT_PASSWORD
 fi
@@ -301,15 +377,28 @@
     # Seed configuration with mysql password so that apt-get install doesn't
     # prompt us for a password upon install.
     cat <<MYSQL_PRESEED | sudo debconf-set-selections
-mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
-mysql-server-5.1 mysql-server/root_password_again password $MYSQL_PASS
+mysql-server-5.1 mysql-server/root_password password $MYSQL_PASSWORD
+mysql-server-5.1 mysql-server/root_password_again password $MYSQL_PASSWORD
 mysql-server-5.1 mysql-server/start_on_boot boolean true
 MYSQL_PRESEED
 
+    # while ``.my.cnf`` is not needed for openstack to function, it is useful
+    # as it allows you to access the mysql databases via ``mysql nova`` instead
+    # of having to specify the username/password each time.
+    if [[ ! -e $HOME/.my.cnf ]]; then
+        cat <<EOF >$HOME/.my.cnf
+[client]
+user=$MYSQL_USER
+password=$MYSQL_PASSWORD
+host=$MYSQL_HOST
+EOF
+        chmod 0600 $HOME/.my.cnf
+    fi
+
     # Install and start mysql-server
-    sudo apt-get -y -q install mysql-server
+    apt_get install mysql-server
     # Update the DB to give user ‘$MYSQL_USER’@’%’ full control of the all databases:
-    sudo mysql -uroot -p$MYSQL_PASS -e "GRANT ALL PRIVILEGES ON *.* TO '$MYSQL_USER'@'%' identified by '$MYSQL_PASS';"
+    sudo mysql -uroot -p$MYSQL_PASSWORD -h127.0.0.1 -e "GRANT ALL PRIVILEGES ON *.* TO '$MYSQL_USER'@'%' identified by '$MYSQL_PASSWORD';"
 
     # Edit /etc/mysql/my.cnf to change ‘bind-address’ from localhost (127.0.0.1) to any (0.0.0.0) and restart the mysql service:
     sudo sed -i 's/127.0.0.1/0.0.0.0/g' /etc/mysql/my.cnf
@@ -335,6 +424,8 @@
     # ``local_settings.py`` is used to override dashboard default settings.
     cp $FILES/dash_settings.py $DASH_DIR/openstack-dashboard/local/local_settings.py
 
+    # Initialize the dashboard database (it stores sessions and notices shown to
+    # users).  The user system is external (keystone).
     cd $DASH_DIR/openstack-dashboard
     dashboard/manage.py syncdb
 
@@ -360,9 +451,10 @@
     mkdir -p $GLANCE_IMAGE_DIR
 
     # (re)create glance database
-    mysql -u$MYSQL_USER -p$MYSQL_PASS -e 'DROP DATABASE IF EXISTS glance;'
-    mysql -u$MYSQL_USER -p$MYSQL_PASS -e 'CREATE DATABASE glance;'
-    # Copy over our glance-registry.conf
+    mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'DROP DATABASE IF EXISTS glance;'
+    mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'CREATE DATABASE glance;'
+
+    # Copy over our glance configurations and update them
     GLANCE_CONF=$GLANCE_DIR/etc/glance-registry.conf
     cp $FILES/glance-registry.conf $GLANCE_CONF
     sudo sed -e "s,%SQL_CONN%,$BASE_SQL_CONN/glance,g" -i $GLANCE_CONF
@@ -381,7 +473,7 @@
 # We are going to use the sample http middleware configuration from the keystone
 # project to launch nova.  This paste config adds the configuration required
 # for nova to validate keystone tokens - except we need to switch the config
-# to use our admin token instead (instead of the token from their sample data).
+# to use our service token instead (instead of the invalid token 999888777666).
 sudo sed -e "s,999888777666,$SERVICE_TOKEN,g" -i $KEYSTONE_DIR/examples/paste/nova-api-paste.ini
 
 if [[ "$ENABLED_SERVICES" =~ "n-cpu" ]]; then
@@ -392,9 +484,9 @@
     # attempt to load modules: network block device - used to manage qcow images
     sudo modprobe nbd || true
 
-    # Check for kvm (hardware based virtualization).  If unable to load kvm, 
-    # set the libvirt type to qemu.  Note: many systems come with hardware 
-    # virtualization disabled in BIOS.
+    # Check for kvm (hardware based virtualization).  If unable to initialize
+    # kvm, we drop back to the slower emulation mode (qemu).  Note: many systems
+    # come with hardware virtualization disabled in BIOS.
     if [[ "$LIBVIRT_TYPE" == "kvm" ]]; then
         sudo modprobe kvm || true
         if [ ! -e /dev/kvm ]; then
@@ -407,8 +499,9 @@
     # splitting a system into many smaller parts.  LXC uses cgroups and chroot
     # to simulate multiple systems.
     if [[ "$LIBVIRT_TYPE" == "lxc" ]]; then
-        sudo apt-get install lxc -y
-        # lxc requires cgroups to be configured on /cgroup
+        apt_get install lxc
+        # lxc uses cgroups (a kernel interface via virtual filesystem) configured
+        # and mounted to ``/cgroup``
         sudo mkdir -p /cgroup
         if ! grep -q cgroup /etc/fstab; then
             echo none /cgroup cgroup cpuacct,memory,devices,cpu,freezer,blkio 0 0 | sudo tee -a /etc/fstab
@@ -418,9 +511,12 @@
         fi
     fi
 
-    # User needs to be member of libvirtd group for nova-compute to use libvirt.
+    # The user that nova runs as needs to be member of libvirtd group otherwise
+    # nova-compute will be unable to use libvirt.
     sudo usermod -a -G libvirtd `whoami`
-    # if kvm wasn't running before we need to restart libvirt to enable it
+    # libvirt detects various settings on startup, as we potentially changed
+    # the system configuration (modules, filesystems), we need to restart
+    # libvirt to detect those changes.
     sudo /etc/init.d/libvirt-bin restart
 
 
@@ -430,12 +526,14 @@
     # Nova stores each instance in its own directory.
     mkdir -p $NOVA_DIR/instances
 
-    # if there is a partition labeled nova-instances use it (ext filesystems
-    # can be labeled via e2label)
-    ## FIXME: if already mounted this blows up...
+    # You can specify a different disk to be mounted and used for backing the
+    # virtual machines.  If there is a partition labeled nova-instances we
+    # mount it (ext filesystems can be labeled via e2label).
     if [ -L /dev/disk/by-label/nova-instances ]; then
-        sudo mount -L nova-instances $NOVA_DIR/instances
-        sudo chown -R `whoami` $NOVA_DIR/instances
+        if ! mount -n | grep -q nova-instances; then
+            sudo mount -L nova-instances $NOVA_DIR/instances
+            sudo chown -R `whoami` $NOVA_DIR/instances
+        fi
     fi
 
     # Clean out the instances directory.
@@ -449,6 +547,31 @@
     mkdir -p $NOVA_DIR/networks
 fi
 
+# Volume Service
+# --------------
+
+if [[ "$ENABLED_SERVICES" =~ "n-vol" ]]; then
+    #
+    # Configure a default volume group called 'nova-volumes' for the nova-volume
+    # service if it does not yet exist.  If you don't wish to use a file backed
+    # volume group, create your own volume group called 'nova-volumes' before
+    # invoking stack.sh.
+    #
+    # By default, the backing file is 2G in size, and is stored in /opt/stack.
+    #
+    if ! sudo vgdisplay | grep -q nova-volumes; then
+        VOLUME_BACKING_FILE=${VOLUME_BACKING_FILE:-/opt/stack/nova-volumes-backing-file}
+        VOLUME_BACKING_FILE_SIZE=${VOLUME_BACKING_FILE_SIZE:-2052M}
+        truncate -s $VOLUME_BACKING_FILE_SIZE $VOLUME_BACKING_FILE
+        DEV=`sudo losetup -f --show $VOLUME_BACKING_FILE`
+        sudo vgcreate nova-volumes $DEV
+    fi
+
+    # Configure iscsitarget
+    sudo sed 's/ISCSITARGET_ENABLE=false/ISCSITARGET_ENABLE=true/' -i /etc/default/iscsitarget
+    sudo /etc/init.d/iscsitarget restart
+fi
+
 function add_nova_flag {
     echo "$1" >> $NOVA_DIR/bin/nova.conf
 }
@@ -490,8 +613,8 @@
 
 if [[ "$ENABLED_SERVICES" =~ "mysql" ]]; then
     # (re)create nova database
-    mysql -u$MYSQL_USER -p$MYSQL_PASS -e 'DROP DATABASE IF EXISTS nova;'
-    mysql -u$MYSQL_USER -p$MYSQL_PASS -e 'CREATE DATABASE nova;'
+    mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'DROP DATABASE IF EXISTS nova;'
+    mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'CREATE DATABASE nova;'
 
     # (re)create nova database
     $NOVA_DIR/bin/nova-manage db sync
@@ -509,8 +632,8 @@
 
 if [[ "$ENABLED_SERVICES" =~ "key" ]]; then
     # (re)create keystone database
-    mysql -u$MYSQL_USER -p$MYSQL_PASS -e 'DROP DATABASE IF EXISTS keystone;'
-    mysql -u$MYSQL_USER -p$MYSQL_PASS -e 'CREATE DATABASE keystone;'
+    mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'DROP DATABASE IF EXISTS keystone;'
+    mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'CREATE DATABASE keystone;'
 
     # FIXME (anthony) keystone should use keystone.conf.example
     KEYSTONE_CONF=$KEYSTONE_DIR/etc/keystone.conf
@@ -557,36 +680,39 @@
 # launch the glance api and wait for it to answer before continuing
 if [[ "$ENABLED_SERVICES" =~ "g-api" ]]; then
     screen_it g-api "cd $GLANCE_DIR; bin/glance-api --config-file=etc/glance-api.conf"
-    while ! wget -q -O- http://$GLANCE_HOSTPORT; do
-        echo "Waiting for g-api ($GLANCE_HOSTPORT) to start..."
-        sleep 1
-    done
+    echo "Waiting for g-api ($GLANCE_HOSTPORT) to start..."
+    if ! timeout 60 sh -c "while ! wget -q -O- http://$GLANCE_HOSTPORT; do sleep 1; done"; then
+      echo "g-api did not start"
+      exit 1
+    fi
 fi
 
 # launch the keystone and wait for it to answer before continuing
 if [[ "$ENABLED_SERVICES" =~ "key" ]]; then
     screen_it key "cd $KEYSTONE_DIR && $KEYSTONE_DIR/bin/keystone --config-file $KEYSTONE_CONF -d"
-    while ! wget -q -O- http://127.0.0.1:5000; do
-        echo "Waiting for keystone to start..."
-        sleep 1
-    done
+    echo "Waiting for keystone to start..."
+    if ! timeout 60 sh -c "while ! wget -q -O- http://127.0.0.1:5000; do sleep 1; done"; then
+      echo "keystone did not start"
+      exit 1
+    fi
 fi
 
 # launch the nova-api and wait for it to answer before continuing
 if [[ "$ENABLED_SERVICES" =~ "n-api" ]]; then
     screen_it n-api "cd $NOVA_DIR && $NOVA_DIR/bin/nova-api"
-    while ! wget -q -O- http://127.0.0.1:8774; do
-        echo "Waiting for nova-api to start..."
-        sleep 1
-    done
+    echo "Waiting for nova-api to start..."
+    if ! timeout 60 sh -c "while ! wget -q -O- http://127.0.0.1:8774; do sleep 1; done"; then
+      echo "nova-api did not start"
+      exit 1
+    fi
 fi
 # Launching nova-compute should be as simple as running ``nova-compute`` but
 # have to do a little more than that in our script.  Since we add the group
 # ``libvirtd`` to our user in this script, when nova-compute is run it is
 # within the context of our original shell (so our groups won't be updated).
-# We can send the command nova-compute to the ``newgrp`` command to execute
-# in a specific context.
-screen_it n-cpu "cd $NOVA_DIR && echo $NOVA_DIR/bin/nova-compute | newgrp libvirtd"
+# Use 'sg' to execute nova-compute as a member of the libvirtd group.
+screen_it n-cpu "cd $NOVA_DIR && sg libvirtd $NOVA_DIR/bin/nova-compute"
+screen_it n-vol "cd $NOVA_DIR && $NOVA_DIR/bin/nova-volume"
 screen_it n-net "cd $NOVA_DIR && $NOVA_DIR/bin/nova-network"
 screen_it n-sch "cd $NOVA_DIR && $NOVA_DIR/bin/nova-scheduler"
 screen_it n-vnc "cd $NOVNC_DIR && ./utils/nova-wsproxy.py 6080 --web . --flagfile=../nova/bin/nova.conf"
@@ -595,57 +721,53 @@
 # Install Images
 # ==============
 
-# Upload a couple images to glance.  **TTY** is a simple small image that use the 
-# lets you login to it with username/password of user/password.  TTY is useful 
-# for basic functionality.  We all include an Ubuntu cloud build of **Natty**.
-# Natty uses cloud-init, supporting login via keypair and sending scripts as
-# userdata.  
+# Upload an image to glance.
 #
-# Read more about cloud-init at https://help.ubuntu.com/community/CloudInit
+# The default image is a small ***TTY*** testing image, which lets you login
+# the username/password of root/password.
+#
+# TTY also uses cloud-init, supporting login via keypair and sending scripts as
+# userdata.  See https://help.ubuntu.com/community/CloudInit for more on cloud-init
+#
+# Override ``IMAGE_URLS`` with a comma-seperated list of uec images.
+#
+#  * **natty**: http://uec-images.ubuntu.com/natty/current/natty-server-cloudimg-amd64.tar.gz
+#  * **oneiric**: http://uec-images.ubuntu.com/oneiric/current/oneiric-server-cloudimg-amd64.tar.gz
 
 if [[ "$ENABLED_SERVICES" =~ "g-reg" ]]; then
-    # create a directory for the downloadedthe images tarballs.
+    # Create a directory for the downloaded image tarballs.
     mkdir -p $FILES/images
 
-    # Debug Image (TTY)
-    # -----------------
+    for image_url in ${IMAGE_URLS//,/ }; do
+        # Downloads the image (uec ami+aki style), then extracts it.
+        IMAGE_FNAME=`echo "$image_url" | python -c "import sys; print sys.stdin.read().split('/')[-1]"`
+        IMAGE_NAME=`echo "$IMAGE_FNAME" | python -c "import sys; print sys.stdin.read().split('.tar.gz')[0].split('.tgz')[0]"`
+        if [ ! -f $FILES/$IMAGE_FNAME ]; then
+            wget -c $image_url -O $FILES/$IMAGE_FNAME
+        fi
 
-    # Downloads the image (ami/aki/ari style), then extracts it.  Upon extraction
-    # we upload to glance with the glance cli tool.  TTY is a stripped down 
-    # version of ubuntu.
-    if [ ! -f $FILES/tty.tgz ]; then
-        wget -c http://images.ansolabs.com/tty.tgz -O $FILES/tty.tgz
-    fi
+        # Extract ami and aki files
+        tar -zxf $FILES/$IMAGE_FNAME -C $FILES/images
 
-    # extract ami-tty/image, aki-tty/image & ari-tty/image
-    tar -zxf $FILES/tty.tgz -C $FILES/images
-
-    # Use glance client to add the kernel, ramdisk and finally the root 
-    # filesystem.  We parse the results of the uploads to get glance IDs of the
-    # ramdisk and kernel and use them for the root filesystem.
-    RVAL=`glance add -A $SERVICE_TOKEN name="tty-kernel" is_public=true container_format=aki disk_format=aki < $FILES/images/aki-tty/image`
-    KERNEL_ID=`echo $RVAL | cut -d":" -f2 | tr -d " "`
-    RVAL=`glance add -A $SERVICE_TOKEN name="tty-ramdisk" is_public=true container_format=ari disk_format=ari < $FILES/images/ari-tty/image`
-    RAMDISK_ID=`echo $RVAL | cut -d":" -f2 | tr -d " "`
-    glance add -A $SERVICE_TOKEN name="tty" is_public=true container_format=ami disk_format=ami kernel_id=$KERNEL_ID ramdisk_id=$RAMDISK_ID < $FILES/images/ami-tty/image
-
-    # Ubuntu 11.04 aka Natty
-    # ----------------------
-
-    # Downloaded from ubuntu enterprise cloud images.  This
-    # image doesn't use the ramdisk functionality
-    if [ ! -f $FILES/natty.tgz ]; then
-        wget -c http://uec-images.ubuntu.com/natty/current/natty-server-cloudimg-amd64.tar.gz -O $FILES/natty.tgz
-    fi
-    
-    tar -zxf $FILES/natty.tgz -C $FILES/images
-
-    RVAL=`glance add -A $SERVICE_TOKEN name="uec-natty-kernel" is_public=true container_format=aki disk_format=aki < $FILES/images/natty-server-cloudimg-amd64-vmlinuz-virtual`
-    KERNEL_ID=`echo $RVAL | cut -d":" -f2 | tr -d " "`
-    glance add -A $SERVICE_TOKEN name="uec-natty" is_public=true container_format=ami disk_format=ami kernel_id=$KERNEL_ID < $FILES/images/natty-server-cloudimg-amd64.img
-
+        # Use glance client to add the kernel the root filesystem.
+        # We parse the results of the first upload to get the glance ID of the
+        # kernel for use when uploading the root filesystem.
+        RVAL=`glance add -A $SERVICE_TOKEN name="$IMAGE_NAME-kernel" is_public=true container_format=aki disk_format=aki < $FILES/images/$IMAGE_NAME-vmlinuz*`
+        KERNEL_ID=`echo $RVAL | cut -d":" -f2 | tr -d " "`
+        glance add -A $SERVICE_TOKEN name="$IMAGE_NAME" is_public=true container_format=ami disk_format=ami kernel_id=$KERNEL_ID < $FILES/images/$IMAGE_NAME.img
+    done
 fi
 
+# Fin
+# ===
+
+
+) 2>&1 | tee "${LOGFILE}"
+
+# Check that the left side of the above pipe succeeded
+for ret in "${PIPESTATUS[@]}"; do [ $ret -eq 0 ] || exit $ret; done
+
+(
 # Using the cloud
 # ===============
 
@@ -663,10 +785,7 @@
     echo "the password: $ADMIN_PASSWORD"
 fi
 
-# Fin
-# ===
+# indicate how long this took to run (bash maintained variable 'SECONDS')
+echo "stack.sh completed in $SECONDS seconds."
 
-# End our timer and give a timing summary
-END_TIME=`python -c "import time; print time.time()"`
-ELAPSED=`python -c "print $END_TIME - $START_TIME"`
-echo "stack.sh completed in $ELAPSED seconds."
+) | tee -a "$LOGFILE"
diff --git a/stackrc b/stackrc
index 15d73ce..aaee6ec 100644
--- a/stackrc
+++ b/stackrc
@@ -16,7 +16,7 @@
 
 # django powered web control panel for openstack
 DASH_REPO=https://github.com/cloudbuilders/openstack-dashboard.git
-DASH_BRANCH=master
+DASH_BRANCH=diablo
 
 # python client library to nova that dashboard (and others) use
 NOVACLIENT_REPO=https://github.com/cloudbuilders/python-novaclient.git
@@ -27,6 +27,9 @@
 OPENSTACKX_REPO=https://github.com/cloudbuilders/openstackx.git
 OPENSTACKX_BRANCH=diablo
 
+# Specify a comma-separated list of uec images to download and install into glance.
+IMAGE_URLS=http://smoser.brickies.net/ubuntu/ttylinux-uec/ttylinux-uec-amd64-11.2_2.6.35-15_1.tar.gz
+
 # allow local overrides of env variables
 if [ -f ./localrc ]; then
     source ./localrc
diff --git a/tools/build_bm.sh b/tools/build_bm.sh
new file mode 100755
index 0000000..44cf303
--- /dev/null
+++ b/tools/build_bm.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# Build an OpenStack install on a bare metal machine.
+set +x
+
+# Source params
+source ./stackrc
+
+# Param string to pass to stack.sh.  Like "EC2_DMZ_HOST=192.168.1.1 MYSQL_USER=nova"
+STACKSH_PARAMS=${STACKSH_PARAMS:-}
+
+# Option to use the version of devstack on which we are currently working
+USE_CURRENT_DEVSTACK=${USE_CURRENT_DEVSTACK:-1}
+
+# Configure the runner
+RUN_SH=`mktemp`
+cat > $RUN_SH <<EOF
+#!/usr/bin/env bash
+# Install and run stack.sh
+cd devstack
+$STACKSH_PARAMS ./stack.sh
+EOF
+
+# Make the run.sh executable
+chmod 755 $RUN_SH
+
+scp -r . root@$CONTAINER_IP:devstack
+scp $RUN_SH root@$CONTAINER_IP:$RUN_SH
+ssh root@$CONTAINER_IP $RUN_SH
diff --git a/tools/build_bm_multi.sh b/tools/build_bm_multi.sh
new file mode 100755
index 0000000..44c0705
--- /dev/null
+++ b/tools/build_bm_multi.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# Build an OpenStack install on several bare metal machines.
+SHELL_AFTER_RUN=no
+
+# Variables common amongst all hosts in the cluster
+COMMON_VARS="MYSQL_HOST=$HEAD_HOST RABBIT_HOST=$HEAD_HOST GLANCE_HOSTPORT=$HEAD_HOST:9292 NET_MAN=FlatDHCPManager FLAT_INTERFACE=eth0 FLOATING_RANGE=$FLOATING_RANGE MULTI_HOST=1 SHELL_AFTER_RUN=$SHELL_AFTER_RUN"
+
+# Helper to launch containers
+function run_bm {
+    # For some reason container names with periods can cause issues :/
+    CONTAINER=$1 CONTAINER_IP=$2 CONTAINER_NETMASK=$NETMASK CONTAINER_GATEWAY=$GATEWAY NAMESERVER=$NAMESERVER TERMINATE=$TERMINATE STACKSH_PARAMS="$COMMON_VARS $3" ./tools/build_bm.sh
+}
+
+# Launch the head node - headnode uses a non-ip domain name,
+# because rabbit won't launch with an ip addr hostname :(
+run_bm STACKMASTER $HEAD_HOST "ENABLED_SERVICES=g-api,g-reg,key,n-api,n-sch,n-vnc,dash,mysql,rabbit"
+
+# Wait till the head node is up
+if [ ! "$TERMINATE" = "1" ]; then
+    echo "Waiting for head node ($HEAD_HOST) to start..."
+    if ! timeout 60 sh -c "while ! wget -q -O- http://$HEAD_HOST | grep -q username; do sleep 1; done"; then
+      echo "Head node did not start"
+      exit 1
+    fi
+fi
+
+PIDS=""
+# Launch the compute hosts in parallel
+for compute_host in ${COMPUTE_HOSTS//,/ }; do
+    run_bm $compute_host $compute_host "ENABLED_SERVICES=n-cpu,n-net,n-api" &
+    PIDS="$PIDS $!"
+done
+
+for x in $PIDS; do
+    wait $x
+done
+echo "build_bm_multi complete"
diff --git a/tools/build_kvm.sh b/tools/build_kvm.sh
new file mode 100755
index 0000000..207f86b
--- /dev/null
+++ b/tools/build_kvm.sh
@@ -0,0 +1,377 @@
+#!/usr/bin/env bash
+
+# Make sure that we have the proper version of ubuntu
+UBUNTU_VERSION=`cat /etc/lsb-release | grep CODENAME | sed 's/.*=//g'`
+if [ ! "oneiric" = "$UBUNTU_VERSION" ]; then
+    if [ ! "natty" = "$UBUNTU_VERSION" ]; then
+        echo "This script only works with oneiric and natty"
+        exit 1
+    fi
+fi
+
+# Echo commands
+set -o xtrace
+
+# Keep track of the current directory
+TOOLS_DIR=$(cd $(dirname "$0") && pwd)
+TOP_DIR=$TOOLS_DIR/..
+
+# Configure the root password of the vm
+ROOT_PASSWORD=${ROOT_PASSWORD:password}
+
+# Where to store files and instances
+KVMSTACK_DIR=${KVMSTACK_DIR:-/opt/kvmstack}
+
+# Where to store images
+IMAGES_DIR=$KVMSTACK_DIR/images
+
+# Create images dir
+mkdir -p $IMAGES_DIR
+
+# Move to top devstack dir
+cd $TOP_DIR
+
+# Abort if localrc is not set
+if [ ! -e ./localrc ]; then
+    echo "You must have a localrc with ALL necessary passwords defined before proceeding."
+    echo "See stack.sh for required passwords."
+    exit 1
+fi
+
+# Source params
+source ./stackrc
+
+# Base image (natty by default)
+DIST_NAME=${DIST_NAME:-natty}
+IMAGE_FNAME=$DIST_NAME.raw
+
+# Original version of built image
+BASE_IMAGE=$KVMSTACK_DIR/images/natty.raw
+
+# Copy of base image, which we pre-install with tasty treats
+BASE_IMAGE_COPY=$IMAGES_DIR/$DIST_NAME.raw.copy
+
+# Name of our instance, used by libvirt
+CONTAINER_NAME=${CONTAINER_NAME:-kvmstack}
+
+# Mop up after previous runs
+virsh destroy $CONTAINER_NAME
+
+# Where this vm is stored
+VM_DIR=$KVMSTACK_DIR/instances/$CONTAINER_NAME
+
+# Create vm dir
+mkdir -p $VM_DIR
+
+# Mount point into copied base image
+COPY_DIR=$VM_DIR/copy
+mkdir -p $COPY_DIR
+
+# Create the base image if it does not yet exist
+if [ ! -e $IMAGES_DIR/$IMAGE_FNAME ]; then
+    cd $TOOLS_DIR
+    ./make_image.sh -m -r 5000  natty raw
+    mv natty.raw $BASE_IMAGE
+    cd $TOP_DIR
+fi
+
+# Create a copy of the base image
+if [ ! -e $BASE_IMAGE_COPY ]; then
+    cp -p $BASE_IMAGE $BASE_IMAGE_COPY
+fi
+
+# Unmount the copied base image
+function unmount_images() {
+    # unmount the filesystem
+    while df | grep -q $COPY_DIR; do
+        umount $COPY_DIR || echo 'ok'
+        sleep 1
+    done
+}
+
+# Unmount from failed runs
+unmount_images
+
+# Ctrl-c catcher
+function kill_unmount() {
+    unmount_images
+    exit 1
+}
+
+# Install deps
+apt-get install -y --force-yes kvm libvirt-bin kpartx
+
+# Let Ctrl-c kill tail and exit
+trap kill_unmount SIGINT
+
+# Where Openstack code will live in image
+DEST=${DEST:-/opt/stack}
+
+# Mount the file system
+mount -o loop,offset=32256 $BASE_IMAGE_COPY  $COPY_DIR
+
+# git clone only if directory doesn't exist already.  Since ``DEST`` might not
+# be owned by the installation user, we create the directory and change the
+# ownership to the proper user.
+function git_clone {
+    if [ ! -d $2 ]; then
+        sudo mkdir $2
+        sudo chown `whoami` $2
+        git clone $1 $2
+        cd $2
+        # This checkout syntax works for both branches and tags
+        git checkout $3
+    fi
+}
+
+# Make sure that base requirements are installed
+cp /etc/resolv.conf $COPY_DIR/etc/resolv.conf
+chroot $COPY_DIR apt-get update
+chroot $COPY_DIR apt-get install -y --force-yes `cat files/apts/* | cut -d\# -f1 | egrep -v "(rabbitmq|libvirt-bin|mysql-server)"`
+chroot $COPY_DIR apt-get install -y --download-only rabbitmq-server libvirt-bin mysql-server
+chroot $COPY_DIR pip install `cat files/pips/*`
+
+# Clean out code repos if directed to do so
+if [ "$CLEAN" = "1" ]; then
+    rm -rf $COPY_DIR/$DEST
+fi
+
+# Cache openstack code
+mkdir -p $COPY_DIR/$DEST
+git_clone $NOVA_REPO $COPY_DIR/$DEST/nova $NOVA_BRANCH
+git_clone $GLANCE_REPO $COPY_DIR/$DEST/glance $GLANCE_BRANCH
+git_clone $KEYSTONE_REPO $COPY_DIR/$DESTkeystone $KEYSTONE_BRANCH
+git_clone $NOVNC_REPO $COPY_DIR/$DEST/noVNC $NOVNC_BRANCH
+git_clone $DASH_REPO $COPY_DIR/$DEST/dash $DASH_BRANCH $DASH_TAG
+git_clone $NOVACLIENT_REPO $COPY_DIR/$DEST/python-novaclient $NOVACLIENT_BRANCH
+git_clone $OPENSTACKX_REPO $COPY_DIR/$DEST/openstackx $OPENSTACKX_BRANCH
+git_clone $KEYSTONE_REPO $COPY_DIR/$DEST/keystone $KEYSTONE_BRANCH
+git_clone $NOVNC_REPO $COPY_DIR/$DEST/noVNC $NOVNC_BRANCH
+
+# Unmount the filesystems
+unmount_images
+
+# Back to devstack
+cd $TOP_DIR
+
+# Network configuration variables
+BRIDGE=${BRIDGE:-br0}
+CONTAINER=${CONTAINER:-STACK}
+CONTAINER_IP=${CONTAINER_IP:-192.168.1.50}
+CONTAINER_CIDR=${CONTAINER_CIDR:-$CONTAINER_IP/24}
+CONTAINER_NETMASK=${CONTAINER_NETMASK:-255.255.255.0}
+CONTAINER_GATEWAY=${CONTAINER_GATEWAY:-192.168.1.1}
+CONTAINER_MAC=${CONTAINER_MAC:-"02:16:3e:07:69:`printf '%02X' $(echo $CONTAINER_IP | sed "s/.*\.//")`"}
+CONTAINER_RAM=${CONTAINER_RAM:-1524288}
+CONTAINER_CORES=${CONTAINER_CORES:-1}
+
+# libvirt.xml configuration
+LIBVIRT_XML=libvirt.xml
+cat > $LIBVIRT_XML <<EOF
+<domain type='kvm'>
+    <name>$CONTAINER_NAME</name>
+    <memory>$CONTAINER_RAM</memory>
+    <os>
+        <type>hvm</type>
+        <bootmenu enable='yes'/>
+    </os>
+    <features>
+        <acpi/>
+    </features>
+    <vcpu>$CONTAINER_CORES</vcpu>
+    <devices>
+        <disk type='file'>
+            <driver type='qcow2'/>
+            <source file='$VM_DIR/disk'/>
+            <target dev='vda' bus='virtio'/>
+        </disk>
+
+        <interface type='bridge'>
+            <source bridge='$BRIDGE'/>
+            <mac address='$CONTAINER_MAC'/>
+        </interface>
+
+        <!-- The order is significant here.  File must be defined first -->
+        <serial type="file">
+            <source path='$VM_DIR/console.log'/>
+            <target port='1'/>
+        </serial>
+
+        <console type='pty' tty='/dev/pts/2'>
+            <source path='/dev/pts/2'/>
+            <target port='0'/>
+        </console>
+
+        <serial type='pty'>
+            <source path='/dev/pts/2'/>
+            <target port='0'/>
+        </serial>
+
+        <graphics type='vnc' port='-1' autoport='yes' keymap='en-us' listen='0.0.0.0'/>
+    </devices>
+</domain>
+EOF
+
+# Mount point for instance fs
+ROOTFS=$VM_DIR/root
+mkdir -p $ROOTFS
+
+# Make sure we have nbd-ness
+modprobe nbd max_part=63
+
+# Which NBD device to use?
+NBD=${NBD:-/dev/nbd5}
+
+# Clean up from previous runs
+umount $ROOTFS || echo 'ok'
+qemu-nbd -d $NBD || echo 'ok'
+
+# Clean up old runs
+cd $VM_DIR
+rm -f $VM_DIR/disk
+
+# Create our instance fs
+qemu-img create -f qcow2 -b $BASE_IMAGE_COPY disk
+
+sleep 5
+
+qemu-nbd -c $NBD disk
+
+sleep 5
+
+# Mount the instance
+mount $NBD $ROOTFS -o offset=32256 -t ext4
+
+# Configure instance network
+INTERFACES=$ROOTFS/etc/network/interfaces
+cat > $INTERFACES <<EOF
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet static
+        address $CONTAINER_IP
+        netmask $CONTAINER_NETMASK
+        gateway $CONTAINER_GATEWAY
+EOF
+
+# User configuration for the instance
+chroot $ROOTFS groupadd libvirtd
+chroot $ROOTFS useradd stack -s /bin/bash -d $DEST -G libvirtd
+cp -pr $TOOLS_DIR/.. $ROOTFS/$DEST/devstack
+echo "root:$ROOT_PASSWORD" | chroot $ROOTFS chpasswd
+echo "stack:pass" | chroot $ROOTFS chpasswd
+echo "stack ALL=(ALL) NOPASSWD: ALL" >> $ROOTFS/etc/sudoers
+
+# Gracefully cp only if source file/dir exists
+function cp_it {
+    if [ -e $1 ] || [ -d $1 ]; then
+        cp -pRL $1 $2
+    fi
+}
+
+# Copy over your ssh keys and env if desired
+COPYENV=${COPYENV:-1}
+if [ "$COPYENV" = "1" ]; then
+    cp_it ~/.ssh $ROOTFS/$DEST/.ssh
+    cp_it ~/.ssh/id_rsa.pub $ROOTFS/$DEST/.ssh/authorized_keys
+    cp_it ~/.gitconfig $ROOTFS/$DEST/.gitconfig
+    cp_it ~/.vimrc $ROOTFS/$DEST/.vimrc
+    cp_it ~/.bashrc $ROOTFS/$DEST/.bashrc
+fi
+
+# Configure the runner
+RUN_SH=$ROOTFS/$DEST/run.sh
+cat > $RUN_SH <<EOF
+#!/usr/bin/env bash
+
+# Kill any existing screens
+killall screen
+
+# Install and run stack.sh
+sudo apt-get update
+sudo apt-get -y --force-yes install git-core vim-nox sudo
+if [ ! -d "$DEST/devstack" ]; then
+    git clone git://github.com/cloudbuilders/devstack.git $DEST/devstack
+fi
+cd $DEST/devstack && $STACKSH_PARAMS FORCE=yes ./stack.sh > /$DEST/run.sh.log
+echo >> /$DEST/run.sh.log
+echo >> /$DEST/run.sh.log
+echo "All done! Time to start clicking." >> /$DEST/run.sh.log
+cat $DEST/run.sh.log
+EOF
+chmod 755 $RUN_SH
+
+# Make runner launch on boot
+RC_LOCAL=$ROOTFS/etc/init.d/local
+cat > $RC_LOCAL <<EOF
+#!/bin/sh -e
+# Reboot if this is our first run to enable console log on natty :(
+if [ ! -e /root/firstlaunch ]; then
+    touch /root/firstlaunch
+    reboot -f
+    exit 0
+fi
+su -c "$DEST/run.sh" stack
+EOF
+chmod +x $RC_LOCAL
+chroot $ROOTFS sudo update-rc.d local defaults 80
+
+# Make our ip address hostnames look nice at the command prompt
+echo "export PS1='${debian_chroot:+($debian_chroot)}\\u@\\H:\\w\\$ '" >> $ROOTFS/$DEST/.bashrc
+echo "export PS1='${debian_chroot:+($debian_chroot)}\\u@\\H:\\w\\$ '" >> $ROOTFS/etc/profile
+
+# Give stack ownership over $DEST so it may do the work needed
+chroot $ROOTFS chown -R stack $DEST
+
+# Change boot params so that we get a console log
+sudo sed -e "s/quiet splash/splash console=ttyS0 console=ttyS1,19200n8/g" -i $ROOTFS/boot/grub/menu.lst
+sudo sed -e "s/^hiddenmenu//g" -i $ROOTFS/boot/grub/menu.lst
+#chroot $ROOTFS grub-install /dev/vda
+
+# Unmount
+umount $ROOTFS || echo 'ok'
+qemu-nbd -d $NBD
+
+# Create the instance
+cd $VM_DIR && virsh create libvirt.xml
+
+# Tail the console log till we are done
+WAIT_TILL_LAUNCH=${WAIT_TILL_LAUNCH:-1}
+if [ "$WAIT_TILL_LAUNCH" = "1" ]; then
+    # Done creating the container, let's tail the log
+    echo
+    echo "============================================================="
+    echo "                          -- YAY! --"
+    echo "============================================================="
+    echo
+    echo "We're done launching the vm, about to start tailing the"
+    echo "stack.sh log. It will take a second or two to start."
+    echo
+    echo "Just CTRL-C at any time to stop tailing."
+
+    while [ ! -e "$VM_DIR/console.log" ]; do
+      sleep 1
+    done
+
+    tail -F $VM_DIR/console.log &
+
+    TAIL_PID=$!
+
+    function kill_tail() {
+        kill $TAIL_PID
+        exit 1
+    }
+
+    # Let Ctrl-c kill tail and exit
+    trap kill_tail SIGINT
+
+    echo "Waiting stack.sh to finish..."
+    while ! cat $VM_DIR/console.log | grep -q 'All done' ; do
+        sleep 5
+    done
+
+    kill $TAIL_PID
+    echo ""
+    echo "Finished - Zip-a-dee Doo-dah!"
+fi
diff --git a/build_lxc.sh b/tools/build_lxc.sh
similarity index 68%
rename from build_lxc.sh
rename to tools/build_lxc.sh
index 643da7e..fc71be8 100755
--- a/build_lxc.sh
+++ b/tools/build_lxc.sh
@@ -6,9 +6,17 @@
   exit 1
 fi
 
-# Warn users who aren't on natty
-if ! grep -q natty /etc/lsb-release; then
-    echo "WARNING: this script has only been tested on natty"
+# Keep track of ubuntu version
+UBUNTU_VERSION=`cat /etc/lsb-release | grep CODENAME | sed 's/.*=//g'`
+
+# Move to top devstack dir
+cd ..
+
+# Abort if localrc is not set
+if [ ! -e ./localrc ]; then
+    echo "You must have a localrc with ALL necessary passwords defined before proceeding."
+    echo "See stack.sh for required passwords."
+    exit 1
 fi
 
 # Source params
@@ -24,12 +32,14 @@
 CONTAINER_CIDR=${CONTAINER_CIDR:-$CONTAINER_IP/24}
 CONTAINER_NETMASK=${CONTAINER_NETMASK:-255.255.255.0}
 CONTAINER_GATEWAY=${CONTAINER_GATEWAY:-192.168.1.1}
-NAMESERVER=${NAMESERVER:-$CONTAINER_GATEWAY}
+NAMESERVER=${NAMESERVER:-`cat /etc/resolv.conf | grep nameserver | head -1 | cut -d " " -f2`}
 COPYENV=${COPYENV:-1}
 DEST=${DEST:-/opt/stack}
+WAIT_TILL_LAUNCH=${WAIT_TILL_LAUNCH:-1}
 
 # Param string to pass to stack.sh.  Like "EC2_DMZ_HOST=192.168.1.1 MYSQL_USER=nova"
-STACKSH_PARAMS=${STACKSH_PARAMS:-}
+# By default, n-vol is disabled for lxc, as iscsitarget doesn't work properly in lxc
+STACKSH_PARAMS=${STACKSH_PARAMS:-"ENABLED_SERVICES=g-api,g-reg,key,n-api,n-cpu,n-net,n-sch,n-vnc,dash,mysql,rabbit"}
 
 # Option to use the version of devstack on which we are currently working
 USE_CURRENT_DEVSTACK=${USE_CURRENT_DEVSTACK:-1}
@@ -82,8 +92,21 @@
     fi
 }
 
+# Helper to create the container
+function create_lxc {
+    if [ "natty" = "$UBUNTU_VERSION" ]; then
+        lxc-create -n $CONTAINER -t natty -f $LXC_CONF
+    else
+        lxc-create -n $CONTAINER -t ubuntu -f $LXC_CONF
+    fi
+}
+
 # Location of the base image directory
-CACHEDIR=/var/cache/lxc/natty/rootfs-amd64
+if [ "natty" = "$UBUNTU_VERSION" ]; then
+    CACHEDIR=/var/cache/lxc/natty/rootfs-amd64
+else
+    CACHEDIR=/var/cache/lxc/oneiric/rootfs-amd64
+fi
 
 # Provide option to do totally clean install
 if [ "$CLEAR_LXC_CACHE" = "1" ]; then
@@ -96,13 +119,16 @@
     # lazy and doesn't do anything if a container already exists)
     lxc-destroy -n $CONTAINER
     # trigger the initial debootstrap
-    lxc-create -n $CONTAINER -t natty -f $LXC_CONF
-    chroot $CACHEDIR apt-get update
-    chroot $CACHEDIR apt-get install -y --force-yes `cat files/apts/* | cut -d\# -f1 | egrep -v "(rabbitmq|libvirt-bin|mysql-server)"`
-    chroot $CACHEDIR pip install `cat files/pips/*`
+    create_lxc
     touch $CACHEDIR/bootstrapped
 fi
 
+# Make sure that base requirements are installed
+chroot $CACHEDIR apt-get update
+chroot $CACHEDIR apt-get install -y --force-yes `cat files/apts/* | cut -d\# -f1 | egrep -v "(rabbitmq|libvirt-bin|mysql-server)"`
+chroot $CACHEDIR apt-get install -y --download-only rabbitmq-server libvirt-bin mysql-server
+chroot $CACHEDIR pip install `cat files/pips/*`
+
 # Clean out code repos if directed to do so
 if [ "$CLEAN" = "1" ]; then
     rm -rf $CACHEDIR/$DEST
@@ -113,10 +139,12 @@
 git_clone $NOVA_REPO $CACHEDIR/$DEST/nova $NOVA_BRANCH
 git_clone $GLANCE_REPO $CACHEDIR/$DEST/glance $GLANCE_BRANCH
 git_clone $KEYSTONE_REPO $CACHEDIR/$DESTkeystone $KEYSTONE_BRANCH
-git_clone $NOVNC_REPO $CACHEDIR/$DEST/novnc $NOVNC_BRANCH
+git_clone $NOVNC_REPO $CACHEDIR/$DEST/noVNC $NOVNC_BRANCH
 git_clone $DASH_REPO $CACHEDIR/$DEST/dash $DASH_BRANCH $DASH_TAG
 git_clone $NOVACLIENT_REPO $CACHEDIR/$DEST/python-novaclient $NOVACLIENT_BRANCH
 git_clone $OPENSTACKX_REPO $CACHEDIR/$DEST/openstackx $OPENSTACKX_BRANCH
+git_clone $KEYSTONE_REPO $CACHEDIR/$DEST/keystone $KEYSTONE_BRANCH
+git_clone $NOVNC_REPO $CACHEDIR/$DEST/novnc $NOVNC_BRANCH
 
 # Use this version of devstack?
 if [ "$USE_CURRENT_DEVSTACK" = "1" ]; then
@@ -133,7 +161,7 @@
 fi
 
 # Create the container
-lxc-create -n $CONTAINER -t natty -f $LXC_CONF
+create_lxc
 
 # Specify where our container rootfs lives
 ROOTFS=/var/lib/lxc/$CONTAINER/rootfs/
@@ -197,6 +225,10 @@
 #!/usr/bin/env bash
 # Make sure dns is set up
 echo "nameserver $NAMESERVER" | sudo resolvconf -a eth0
+# Make there is a default route - needed for natty
+if ! route | grep -q default; then
+    sudo ip route add default via $CONTAINER_GATEWAY
+fi
 sleep 1
 
 # Kill any existing screens
@@ -208,7 +240,7 @@
 if [ ! -d "$DEST/devstack" ]; then
     git clone git://github.com/cloudbuilders/devstack.git $DEST/devstack
 fi
-cd $DEST/devstack && $STACKSH_PARAMS ./stack.sh > /$DEST/run.sh.log
+cd $DEST/devstack && $STACKSH_PARAMS FORCE=yes ./stack.sh > /$DEST/run.sh.log
 echo >> /$DEST/run.sh.log
 echo >> /$DEST/run.sh.log
 echo "All done! Time to start clicking." >> /$DEST/run.sh.log
@@ -218,11 +250,13 @@
 chmod 755 $RUN_SH
 
 # Make runner launch on boot
-RC_LOCAL=$ROOTFS/etc/rc.local
+RC_LOCAL=$ROOTFS/etc/init.d/local
 cat > $RC_LOCAL <<EOF
 #!/bin/sh -e
 su -c "$DEST/run.sh" stack
 EOF
+chmod +x $RC_LOCAL
+chroot $ROOTFS sudo update-rc.d local defaults 80
 
 # Configure cgroup directory
 if ! mount | grep -q cgroup; then
@@ -233,19 +267,40 @@
 # Start our container
 lxc-start -d -n $CONTAINER
 
-# Done creating the container, let's tail the log
-echo
-echo "============================================================="
-echo "                          -- YAY! --"
-echo "============================================================="
-echo
-echo "We're done creating the container, about to start tailing the"
-echo "stack.sh log. It will take a second or two to start."
-echo
-echo "Just CTRL-C at any time to stop tailing."
+if [ "$WAIT_TILL_LAUNCH" = "1" ]; then
+    # Done creating the container, let's tail the log
+    echo
+    echo "============================================================="
+    echo "                          -- YAY! --"
+    echo "============================================================="
+    echo
+    echo "We're done creating the container, about to start tailing the"
+    echo "stack.sh log. It will take a second or two to start."
+    echo
+    echo "Just CTRL-C at any time to stop tailing."
 
-while [ ! -e "$ROOTFS/$DEST/run.sh.log" ]; do
-  sleep 1
-done
+    while [ ! -e "$ROOTFS/$DEST/run.sh.log" ]; do
+      sleep 1
+    done
 
-tail -F $ROOTFS/$DEST/run.sh.log
+    tail -F $ROOTFS/$DEST/run.sh.log &
+
+    TAIL_PID=$!
+
+    function kill_tail() {
+        kill $TAIL_PID
+        exit 1
+    }
+
+    # Let Ctrl-c kill tail and exit
+    trap kill_tail SIGINT
+
+    echo "Waiting stack.sh to finish..."
+    while ! cat $ROOTFS/$DEST/run.sh.log | grep -q 'All done' ; do
+        sleep 5
+    done
+
+    kill $TAIL_PID
+    echo ""
+    echo "Finished - Zip-a-dee Doo-dah!"
+fi
diff --git a/build_lxc_multi.sh b/tools/build_lxc_multi.sh
similarity index 100%
rename from build_lxc_multi.sh
rename to tools/build_lxc_multi.sh
diff --git a/tools/build_nfs.sh b/tools/build_nfs.sh
index 651bae2..36126fa 100755
--- a/tools/build_nfs.sh
+++ b/tools/build_nfs.sh
@@ -22,7 +22,7 @@
 # clean install of natty
 if [ ! -d $CHROOTCACHE/natty-base ]; then
     $PROGDIR/make_image.sh -C natty $CHROOTCACHE/natty-base
-    # copy kernel modules...  
+    # copy kernel modules...
     # NOTE(ja): is there a better way to do this?
     cp -pr /lib/modules/`uname -r` $CHROOTCACHE/natty-base/lib/modules
     # a simple password - pass
@@ -35,7 +35,7 @@
     chroot $CHROOTCACHE/natty-dev apt-get install -y `cat files/apts/* | cut -d\# -f1 | egrep -v "(rabbitmq|libvirt-bin|mysql-server)"`
     chroot $CHROOTCACHE/natty-dev pip install `cat files/pips/*`
 
-    # Create a stack user that is a member of the libvirtd group so that stack 
+    # Create a stack user that is a member of the libvirtd group so that stack
     # is able to interact with libvirt.
     chroot $CHROOTCACHE/natty-dev groupadd libvirtd
     chroot $CHROOTCACHE/natty-dev useradd stack -s /bin/bash -d $DEST -G libvirtd
@@ -45,7 +45,7 @@
     # a simple password - pass
     echo stack:pass | chroot $CHROOTCACHE/natty-dev chpasswd
 
-    # and has sudo ability (in the future this should be limited to only what 
+    # and has sudo ability (in the future this should be limited to only what
     # stack requires)
     echo "stack ALL=(ALL) NOPASSWD: ALL" >> $CHROOTCACHE/natty-dev/etc/sudoers
 fi
diff --git a/tools/build_pxe_boot.sh b/tools/build_pxe_boot.sh
index 84aa43b..7fd964b 100755
--- a/tools/build_pxe_boot.sh
+++ b/tools/build_pxe_boot.sh
@@ -4,15 +4,8 @@
 # build_pxe_boot.sh [-k kernel-version] destdir
 #
 # Assumes syslinux is installed
-# Assumes devstack files are in `pwd`/pxe
 # Only needs to run as root if the destdir permissions require it
 
-UBUNTU_MIRROR=http://archive.ubuntu.com/ubuntu/dists/natty/main/installer-amd64/current/images/netboot/ubuntu-installer/amd64
-
-MEMTEST_VER=4.10
-MEMTEST_BIN=memtest86+-${MEMTEST_VER}.bin
-MEMTEST_URL=http://www.memtest.org/download/${MEMTEST_VER}/
-
 KVER=`uname -r`
 if [ "$1" = "-k" ]; then
     KVER=$2
@@ -27,11 +20,11 @@
 mkdir -p $DEST_DIR/pxelinux.cfg
 cd $DEST_DIR
 for i in memdisk menu.c32 pxelinux.0; do
-	cp -p /usr/lib/syslinux/$i $DEST_DIR
+    cp -p /usr/lib/syslinux/$i $DEST_DIR
 done
 
-DEFAULT=$DEST_DIR/pxelinux.cfg/default
-cat >$DEFAULT <<EOF
+CFG=$DEST_DIR/pxelinux.cfg/default
+cat >$CFG <<EOF
 default menu.c32
 prompt 0
 timeout 0
@@ -59,7 +52,7 @@
     sudo $PROGDIR/build_ramdisk.sh $PXEDIR/stack-initrd.gz
 fi
 cp -p $PXEDIR/stack-initrd.gz $DEST_DIR/ubuntu
-cat >>$DEFAULT <<EOF
+cat >>$CFG <<EOF
 
 LABEL devstack
     MENU LABEL ^devstack
@@ -69,48 +62,21 @@
 EOF
 
 # Get Ubuntu
-if [ -d $PXEDIR ]; then
+if [ -d $PXEDIR -a -r $PXEDIR/natty-base-initrd.gz ]; then
     cp -p $PXEDIR/natty-base-initrd.gz $DEST_DIR/ubuntu
-fi
-cat >>$DEFAULT <<EOF
+    cat >>$CFG <<EOF
 
 LABEL ubuntu
     MENU LABEL ^Ubuntu Natty
     KERNEL ubuntu/vmlinuz-$KVER
     APPEND initrd=ubuntu/natty-base-initrd.gz ramdisk_size=419600 root=/dev/ram0
 EOF
-
-# Get Memtest
-cd $DEST_DIR
-if [ ! -r $MEMTEST_BIN ]; then
-    wget -N --quiet ${MEMTEST_URL}/${MEMTEST_BIN}.gz
-    gunzip $MEMTEST_BIN
 fi
-cat >>$DEFAULT <<EOF
-
-LABEL memtest
-    MENU LABEL ^Memtest86+
-    KERNEL $MEMTEST_BIN
-EOF
-
-# Get FreeDOS
-mkdir -p $DEST_DIR/freedos
-cd $DEST_DIR/freedos
-wget -N --quiet http://www.fdos.org/bootdisks/autogen/FDSTD.288.gz
-gunzip -f FDSTD.288.gz
-cat >>$DEFAULT <<EOF
-
-LABEL freedos
-	MENU LABEL ^FreeDOS bootdisk
-	KERNEL memdisk
-	APPEND initrd=freedos/FDSTD.288
-EOF
 
 # Local disk boot
-cat >>$DEFAULT <<EOF
+cat >>$CFG <<EOF
 
 LABEL local
     MENU LABEL ^Local disk
-    MENU DEFAULT
     LOCALBOOT 0
 EOF
diff --git a/tools/build_ramdisk.sh b/tools/build_ramdisk.sh
index 0f93735..be6ca77 100755
--- a/tools/build_ramdisk.sh
+++ b/tools/build_ramdisk.sh
@@ -18,13 +18,16 @@
 
 DEST=${DEST:-/opt/stack}
 
+# Param string to pass to stack.sh.  Like "EC2_DMZ_HOST=192.168.1.1 MYSQL_USER=nova"
+STACKSH_PARAMS=${STACKSH_PARAMS:-}
+
 # Option to use the version of devstack on which we are currently working
 USE_CURRENT_DEVSTACK=${USE_CURRENT_DEVSTACK:-1}
 
 # clean install of natty
 if [ ! -d $CHROOTCACHE/natty-base ]; then
     $PROGDIR/make_image.sh -C natty $CHROOTCACHE/natty-base
-    # copy kernel modules...  
+    # copy kernel modules...
     # NOTE(ja): is there a better way to do this?
     cp -pr /lib/modules/`uname -r` $CHROOTCACHE/natty-base/lib/modules
     # a simple password - pass
@@ -37,7 +40,7 @@
     chroot $CHROOTCACHE/natty-dev apt-get install -y `cat files/apts/* | cut -d\# -f1 | egrep -v "(rabbitmq|libvirt-bin|mysql-server)"`
     chroot $CHROOTCACHE/natty-dev pip install `cat files/pips/*`
 
-    # Create a stack user that is a member of the libvirtd group so that stack 
+    # Create a stack user that is a member of the libvirtd group so that stack
     # is able to interact with libvirt.
     chroot $CHROOTCACHE/natty-dev groupadd libvirtd
     chroot $CHROOTCACHE/natty-dev useradd stack -s /bin/bash -d $DEST -G libvirtd
@@ -47,7 +50,7 @@
     # a simple password - pass
     echo stack:pass | chroot $CHROOTCACHE/natty-dev chpasswd
 
-    # and has sudo ability (in the future this should be limited to only what 
+    # and has sudo ability (in the future this should be limited to only what
     # stack requires)
     echo "stack ALL=(ALL) NOPASSWD: ALL" >> $CHROOTCACHE/natty-dev/etc/sudoers
 fi
@@ -111,6 +114,34 @@
 iface eth0 inet dhcp
 EOF
 
+# Set hostname
+echo "ramstack" >$CHROOTCACHE/natty-stack/etc/hostname
+echo "127.0.0.1		localhost	ramstack" >$CHROOTCACHE/natty-stack/etc/hosts
+
+# Configure the runner
+RUN_SH=$CHROOTCACHE/natty-stack/$DEST/run.sh
+cat > $RUN_SH <<EOF
+#!/usr/bin/env bash
+
+# Get IP range
+set \`ip addr show dev eth0 | grep inet\`
+PREFIX=\`echo \$2 | cut -d. -f1,2,3\`
+export FLOATING_RANGE="\$PREFIX.224/27"
+
+# Kill any existing screens
+killall screen
+
+# Run stack.sh
+cd $DEST/devstack && \$STACKSH_PARAMS ./stack.sh > $DEST/run.sh.log
+echo >> $DEST/run.sh.log
+echo >> $DEST/run.sh.log
+echo "All done! Time to start clicking." >> $DEST/run.sh.log
+EOF
+
+# Make the run.sh executable
+chmod 755 $RUN_SH
+chroot $CHROOTCACHE/natty-stack chown stack $DEST/run.sh
+
 # build a new image
 BASE=$CHROOTCACHE/build.$$
 IMG=$BASE.img
diff --git a/tools/build_usb_boot.sh b/tools/build_usb_boot.sh
new file mode 100755
index 0000000..332c869
--- /dev/null
+++ b/tools/build_usb_boot.sh
@@ -0,0 +1,106 @@
+#!/bin/bash -e
+# build_usb_boot.sh - Create a syslinux boot environment
+#
+# build_usb_boot.sh [-k kernel-version] destdev
+#
+# Assumes syslinux is installed
+# Needs to run as root
+
+KVER=`uname -r`
+if [ "$1" = "-k" ]; then
+    KVER=$2
+    shift;shift
+fi
+
+DEST_DIR=${1:-/tmp/syslinux-boot}
+PXEDIR=${PXEDIR:-/var/cache/devstack/pxe}
+OPWD=`pwd`
+PROGDIR=`dirname $0`
+
+if [ -b $DEST_DIR ]; then
+    # We have a block device, install syslinux and mount it
+    DEST_DEV=$DEST_DIR
+    DEST_DIR=`mktemp -d mntXXXXXX`
+    mount $DEST_DEV $DEST_DIR
+
+    if [ ! -d $DEST_DIR/syslinux ]; then
+        mkdir -p $DEST_DIR/syslinux
+    fi
+
+    # Install syslinux on the device
+    syslinux --install --directory syslinux $DEST_DEV
+else
+    # We have a directory (for sanity checking output)
+    DEST_DEV=""
+    if [ ! -d $DEST_DIR/syslinux ]; then
+        mkdir -p $DEST_DIR/syslinux
+    fi
+fi
+
+# Get some more stuff from syslinux
+for i in memdisk menu.c32; do
+    cp -p /usr/lib/syslinux/$i $DEST_DIR/syslinux
+done
+
+CFG=$DEST_DIR/syslinux/syslinux.cfg
+cat >$CFG <<EOF
+default /syslinux/menu.c32
+prompt 0
+timeout 0
+
+MENU TITLE Boot Menu
+
+EOF
+
+# Setup devstack boot
+mkdir -p $DEST_DIR/ubuntu
+if [ ! -d $PXEDIR ]; then
+    mkdir -p $PXEDIR
+fi
+if [ ! -r $PXEDIR/vmlinuz-${KVER} ]; then
+    sudo chmod 644 /boot/vmlinuz-${KVER}
+    if [ ! -r /boot/vmlinuz-${KVER} ]; then
+        echo "No kernel found"
+    else
+        cp -p /boot/vmlinuz-${KVER} $PXEDIR
+    fi
+fi
+cp -p $PXEDIR/vmlinuz-${KVER} $DEST_DIR/ubuntu
+if [ ! -r $PXEDIR/stack-initrd.gz ]; then
+    cd $OPWD
+    sudo $PROGDIR/build_ramdisk.sh $PXEDIR/stack-initrd.gz
+fi
+cp -p $PXEDIR/stack-initrd.gz $DEST_DIR/ubuntu
+cat >>$CFG <<EOF
+
+LABEL devstack
+    MENU LABEL ^devstack
+    MENU DEFAULT
+    KERNEL /ubuntu/vmlinuz-$KVER
+    APPEND initrd=/ubuntu/stack-initrd.gz ramdisk_size=2109600 root=/dev/ram0
+EOF
+
+# Get Ubuntu
+if [ -d $PXEDIR -a -r $PXEDIR/natty-base-initrd.gz ]; then
+    cp -p $PXEDIR/natty-base-initrd.gz $DEST_DIR/ubuntu
+    cat >>$CFG <<EOF
+
+LABEL ubuntu
+    MENU LABEL ^Ubuntu Natty
+    KERNEL /ubuntu/vmlinuz-$KVER
+    APPEND initrd=/ubuntu/natty-base-initrd.gz ramdisk_size=419600 root=/dev/ram0
+EOF
+fi
+
+# Local disk boot
+cat >>$CFG <<EOF
+
+LABEL local
+    MENU LABEL ^Local disk
+    LOCALBOOT 0
+EOF
+
+if [ -n "$DEST_DEV" ]; then
+    umount $DEST_DIR
+    rmdir $DEST_DIR
+fi
diff --git a/lxc_network_hostonlyplusnat.sh b/tools/lxc_network_hostonlyplusnat.sh
similarity index 100%
rename from lxc_network_hostonlyplusnat.sh
rename to tools/lxc_network_hostonlyplusnat.sh
diff --git a/tools/make_image.sh b/tools/make_image.sh
index 0d5074b..32d59bd 100755
--- a/tools/make_image.sh
+++ b/tools/make_image.sh
@@ -110,7 +110,7 @@
 
 # Install stuff if necessary
 if [ -z `which vmbuilder` ]; then
-    sudo apt-get install ubuntu-vm-builder
+    sudo apt-get install -y ubuntu-vm-builder
 fi
 
 if [ -n "$CHROOTONLY" ]; then