diff --git a/.gitignore b/.gitignore index af66537..f6f0573 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ doc/ .vagrant /cache -/dummy-ubuntu-cloudimg.box +/boxes/**/*.tar.gz +/boxes/output/ diff --git a/boxes/ubuntu-cloud/lxc-ubuntu-cloud b/boxes/ubuntu-cloud/lxc-ubuntu-cloud new file mode 100755 index 0000000..7f3e422 --- /dev/null +++ b/boxes/ubuntu-cloud/lxc-ubuntu-cloud @@ -0,0 +1,406 @@ +#!/bin/bash + +# template script for generating ubuntu container for LXC based on released cloud +# images +# +# Copyright © 2012 Serge Hallyn +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2, as +# published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +set -e + +if [ -r /etc/default/lxc ]; then + . /etc/default/lxc +fi + +copy_configuration() +{ + path=$1 + rootfs=$2 + name=$3 + arch=$4 + release=$5 + + if [ $arch = "i386" ]; then + arch="i686" + fi + + # if there is exactly one veth network entry, make sure it has an + # associated hwaddr. + nics=`grep -e '^lxc\.network\.type[ \t]*=[ \t]*veth' $path/config | wc -l` + if [ $nics -eq 1 ]; then + grep -q "^lxc.network.hwaddr" $path/config || cat <> $path/config +lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//') +EOF + fi + + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config + cat <> $path/config +lxc.utsname = $name + +lxc.tty = 4 +lxc.pts = 1024 +lxc.mount = $path/fstab +lxc.arch = $arch +lxc.cap.drop = sys_module mac_admin +lxc.pivotdir = lxc_putold + +# uncomment the next line to run the container unconfined: +#lxc.aa_profile = unconfined + +lxc.cgroup.devices.deny = a +# Allow any mknod (but not using the node) +lxc.cgroup.devices.allow = c *:* m +lxc.cgroup.devices.allow = b *:* m +# /dev/null and zero +lxc.cgroup.devices.allow = c 1:3 rwm +lxc.cgroup.devices.allow = c 1:5 rwm +# consoles +lxc.cgroup.devices.allow = c 5:1 rwm +lxc.cgroup.devices.allow = c 5:0 rwm +#lxc.cgroup.devices.allow = c 4:0 rwm +#lxc.cgroup.devices.allow = c 4:1 rwm +# /dev/{,u}random +lxc.cgroup.devices.allow = c 1:9 rwm +lxc.cgroup.devices.allow = c 1:8 rwm +lxc.cgroup.devices.allow = c 136:* rwm +lxc.cgroup.devices.allow = c 5:2 rwm +# rtc +lxc.cgroup.devices.allow = c 254:0 rwm +#fuse +lxc.cgroup.devices.allow = c 10:229 rwm +#tun +lxc.cgroup.devices.allow = c 10:200 rwm +#full +lxc.cgroup.devices.allow = c 1:7 rwm +#hpet +lxc.cgroup.devices.allow = c 10:228 rwm +#kvm +lxc.cgroup.devices.allow = c 10:232 rwm +EOF + + cat < $path/fstab +proc proc proc nodev,noexec,nosuid 0 0 +sysfs sys sysfs defaults 0 0 +EOF + + # rmdir /dev/shm for containers that have /run/shm + # I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did + # get bind mounted to the host's /run/shm. So try to rmdir + # it, and in case that fails move it out of the way. + if [ ! -L $rootfs/dev/shm ] && [ -d $rootfs/run/shm ] && [ -e $rootfs/dev/shm ]; then + mv $rootfs/dev/shm $rootfs/dev/shm.bak + ln -s /run/shm $rootfs/dev/shm + fi + + return 0 +} + +usage() +{ + cat < ]: Release name of container, defaults to host +[ -a | --arch ]: Arhcitecture of container, defaults to host arcitecture +[ -C | --cloud ]: Configure container for use with meta-data service, defaults to no +[ -T | --tarball ]: Location of tarball +[ -d | --debug ]: Run with 'set -x' to debug errors +[ -s | --stream]: Use specified stream rather than 'released' + +Options, mutually exclusive of "-C" and "--cloud": + [ -i | --hostid ]: HostID for cloud-init, defaults to random string + [ -u | --userdata ]: Cloud-init user-data file to configure container on start + [ -S | --auth-key ]: SSH Public key file to inject into container + [ -L | --nolocales ]: Do not copy host's locales into container + +EOF + return 0 +} + +options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds:u: -l arch:,help,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata: -- "$@") +if [ $? -ne 0 ]; then + usage $(basename $0) + exit 1 +fi +eval set -- "$options" + +release=lucid +if [ -f /etc/lsb-release ]; then + . /etc/lsb-release + case "$DISTRIB_CODENAME" in + lucid|natty|oneiric|precise|quantal) + release=$DISTRIB_CODENAME + ;; + esac +fi + +arch=$(arch) + +# Code taken from debootstrap +if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then + arch=`/usr/bin/dpkg --print-architecture` +elif type udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then + arch=`/usr/bin/udpkg --print-architecture` +else + arch=$(arch) + if [ "$arch" = "i686" ]; then + arch="i386" + elif [ "$arch" = "x86_64" ]; then + arch="amd64" + elif [ "$arch" = "armv7l" ]; then + # note: arm images don't exist before oneiric; are called armhf in + # precise and later; and are not supported by the query, so we don't actually + # support them yet (see check later on). When Query2 is available, + # we'll use that to enable arm images. + arch="armel" + fi +fi + +debug=0 +hostarch=$arch +cloud=0 +locales=1 +flushcache=0 +stream="released" +while true +do + case "$1" in + -h|--help) usage $0 && exit 0;; + -p|--path) path=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -F|--flush-cache) flushcache=1; shift 1;; + -r|--release) release=$2; shift 2;; + -a|--arch) arch=$2; shift 2;; + -i|--hostid) host_id=$2; shift 2;; + -u|--userdata) userdata=$2; shift 2;; + -C|--cloud) cloud=1; shift 1;; + -S|--auth-key) auth_key=$2; shift 2;; + -L|--no_locales) locales=0; shift 1;; + -T|--tarball) tarball=$2; shift 2;; + -d|--debug) debug=1; shift 1;; + -s|--stream) stream=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +if [ $debug -eq 1 ]; then + set -x +fi + +if [ "$arch" == "i686" ]; then + arch=i386 +fi + +if [ $hostarch = "i386" -a $arch = "amd64" ]; then + echo "can't create amd64 container on i386" + exit 1 +fi + +if [ $arch != "i386" -a $arch != "amd64" ]; then + echo "Only i386 and amd64 are supported by the ubuntu cloud template." + exit 1 +fi + +if [ "$stream" != "daily" -a "$stream" != "released" ]; then + echo "Only 'daily' and 'released' streams are supported" + exit 1 +fi + +if [ -n "$userdata" ]; then + if [ ! -f "$userdata" ]; then + echo "Userdata ($userdata) does not exist" + exit 1 + else + userdata=`readlink -f $userdata` + fi +fi + +if [ -n "$auth_key" ]; then + if [ ! -f "$auth_key" ]; then + echo "--auth-key=${auth_key} must reference a file" + exit 1 + fi + auth_key=$(readlink -f "${auth_key}") || + { echo "failed to get full path for auth_key"; exit 1; } +fi + +if [ -z "$path" ]; then + echo "'path' parameter is required" + exit 1 +fi + +if [ "$(id -u)" != "0" ]; then + echo "This script should be run as 'root'" + exit 1 +fi + +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi + +type ubuntu-cloudimg-query +type wget + +# determine the url, tarball, and directory names +# download if needed +cache="/var/cache/lxc/cloud-$release" + +mkdir -p $cache + +if [ -n "$tarball" ]; then + url2="$tarball" +else + url1=`ubuntu-cloudimg-query $release $stream $arch --format "%{url}\n"` + url2=`echo $url1 | sed -e 's/.tar.gz/-root\0/'` +fi + +filename=`basename $url2` + +wgetcleanup() +{ + rm -f $filename +} + +buildcleanup() +{ + cd $rootfs + umount -l $cache/$xdir || true + rm -rf $cache +} + +# if the release doesn't have a *-rootfs.tar.gz, then create one from the +# cloudimg.tar.gz by extracting the .img, mounting it loopback, and creating +# a tarball from the mounted image. +build_root_tgz() +{ + url=$1 + filename=$2 + + xdir=`mktemp -d -p .` + tarname=`basename $url` + imgname="$release-*-cloudimg-$arch.img" + trap buildcleanup EXIT SIGHUP SIGINT SIGTERM + if [ $flushcache -eq 1 -o ! -f $cache/$tarname ]; then + rm -f $tarname + echo "Downloading cloud image from $url" + wget $url || { echo "Couldn't find cloud image $url."; exit 1; } + fi + echo "Creating new cached cloud image rootfs" + tar --wildcards -zxf $tarname $imgname + mount -o loop $imgname $xdir + (cd $xdir; tar zcf ../$filename .) + umount $xdir + rm -f $tarname $imgname + rmdir $xdir + echo "New cloud image cache created" + trap EXIT + trap SIGHUP + trap SIGINT + trap SIGTERM +} + +mkdir -p /var/lock/subsys/ +( + flock -x 200 + + cd $cache + if [ $flushcache -eq 1 ]; then + echo "Clearing the cached images" + rm -f $filename + fi + + trap wgetcleanup EXIT SIGHUP SIGINT SIGTERM + if [ ! -f $filename ]; then + wget $url2 || build_root_tgz $url1 $filename + fi + trap EXIT + trap SIGHUP + trap SIGINT + trap SIGTERM + + echo "Extracting container rootfs" + mkdir -p $rootfs + cd $rootfs + tar --numeric-owner -zxf $cache/$filename + + + if [ $cloud -eq 0 ]; then + echo "Configuring for running outside of a cloud environment" + echo "If you want to configure for a cloud evironment, please use '-- -C' to create the container" + + seed_d=$rootfs/var/lib/cloud/seed/nocloud-net + rhostid=$(uuidgen | cut -c -8) + host_id=${hostid:-$rhostid} + mkdir -p $seed_d + + cat > "$seed_d/meta-data" <> "$seed_d/meta-data" + [ $? -eq 0 ] || + { echo "failed to write public keys to metadata"; exit 1; } + fi + + rm $rootfs/etc/hostname + + if [ $locales -eq 1 ]; then + cp /usr/lib/locale/locale-archive $rootfs/usr/lib/locale/locale-archive + fi + + if [ -f "$userdata" ]; then + echo "Using custom user-data" + cp $userdata $seed_d/user-data + else + + if [ -z "$MIRROR" ]; then + MIRROR="http://archive.ubuntu.com/ubuntu" + fi + + cat > "$seed_d/user-data" </var/lock/subsys/lxc-ubucloud + +copy_configuration $path $rootfs $name $arch $release + +echo "Container $name created." +exit 0 + +# vi: ts=4 expandtab diff --git a/boxes/ubuntu-cloud/metadata.json b/boxes/ubuntu-cloud/metadata.json new file mode 100644 index 0000000..45d00b9 --- /dev/null +++ b/boxes/ubuntu-cloud/metadata.json @@ -0,0 +1,7 @@ +{ + "provider": "lxc", + "vagrant-lxc-version": "0.0.1", + "template-name": "lxc-ubuntu-cloud", + "after-create-script": "set-vagrant-user.sh", + "tar-cache": "ubuntu-12.10-server-cloudimg-amd64-root.tar.gz" +} diff --git a/dummy-box-files/metadata.json b/dummy-box-files/metadata.json deleted file mode 100644 index 959aa0e..0000000 --- a/dummy-box-files/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"provider":"lxc"} diff --git a/setup-vagrant-dev-box b/setup-vagrant-dev-box index 76db20b..5917bef 100755 --- a/setup-vagrant-dev-box +++ b/setup-vagrant-dev-box @@ -5,12 +5,11 @@ raise 'You should not run this script from the dev box' if ENV['USER'] == 'vagra require 'bundler' require 'json' -IMAGE_ROOT = 'https://cloud-images.ubuntu.com/releases/quantal/release-20130206' -IMAGE_NAME = 'ubuntu-12.10-server-cloudimg-amd64-root.tar.gz' -VAGRANT_REPO = 'https://raw.github.com/mitchellh/vagrant/master' +IMAGE_ROOT = 'https://cloud-images.ubuntu.com/releases/quantal/release-20130206' +IMAGE_NAME = 'ubuntu-12.10-server-cloudimg-amd64-root.tar.gz' def download(source, destination) - destination = "#{File.dirname __FILE__}/cache/#{destination}" + destination = "#{File.dirname __FILE__}/#{destination}" return if File.exists?(destination) sh "wget #{source} -O #{destination}" @@ -61,8 +60,8 @@ end # Fetches vagrant submodule `git submodule update --init` -# Cache container image between vagrant box destructions -download "#{IMAGE_ROOT}/#{IMAGE_NAME}", IMAGE_NAME +# Download container image for building the base ubuntu-cloud box +download "#{IMAGE_ROOT}/#{IMAGE_NAME}", "boxes/ubuntu-cloud/#{IMAGE_NAME}" # Start vagrant sh 'vagrant up' diff --git a/tasks/boxes.rake b/tasks/boxes.rake new file mode 100644 index 0000000..a2117ee --- /dev/null +++ b/tasks/boxes.rake @@ -0,0 +1,10 @@ +namespace :boxes do + namespace :build do + desc 'Packages an Ubuntu cloud image as a Vagrant LXC box' + task 'ubuntu-cloud' do + sh 'mkdir -p boxes/output' + sh 'cp cache/ubuntu-12.10-server-cloudimg-amd64-root.tar.gz boxes/ubuntu-cloud' + sh 'cd boxes/ubuntu-cloud && tar -czf ../output/ubuntu-cloud.box ./*' + end + end +end diff --git a/tasks/package_dummy_box.rake b/tasks/package_dummy_box.rake deleted file mode 100644 index 02bcec4..0000000 --- a/tasks/package_dummy_box.rake +++ /dev/null @@ -1,4 +0,0 @@ -desc 'Packages a dummy Vagrant box to be used during development' -task :package_dummy_box do - sh 'cd dummy-box-files/ && tar -czf ../dummy-ubuntu-cloudimg.box ./*' -end