#!/bin/bash
#
# Creates a ubuntu based container by cloning a btrfs snapshot
#
# This script is based on the lxc-ubuntu example script that ships with lxc.
#
# Copyright (C) 2010 Nigel McNie
# Modified for Ubuntu 2010 phbaer
# Modified for btrfs snapshot cloning 2011 by Darren Wurf
#

CONFFILE="lxc.conf"
MNTFILE=
TMPMNTFILE=
UTSNAME=
MTU="1500"
#DEBMIRROR=http://archive.ubuntu.com/ubuntu
DEBMIRROR=http://192.168.1.117:3142/mirror.internode.on.net/pub/ubuntu/ubuntu/
FQDN=$(hostname --fqdn)
FSBASE=/btrfs/vms
TEMPLATES=/btrfs/templates/
UBUNTUVERSION=natty


################################################################################
# ubuntu custom configuration files
################################################################################

# Disable selinux in the container
write_ubuntu_selinux() {
    mkdir -p $ROOTFS/selinux
    echo 0 > $ROOTFS/selinux/enforce
}

# Create an init.d config for /dev/console
write_ubuntu_console() {
cat <<EOF > $ROOTFS/etc/init/console.conf
# /dev/console - getty
#
# This service maintains a getty on /dev/console from the point the system is
# started until it is shut down again.

start on stopped rc RUNLEVEL=[2345]
stop on runlevel [!2345]

respawn
exec /sbin/getty -8 38400 /dev/console
EOF
}

# Write out a custom fstab
write_ubuntu_fstab() {
cat <<EOF > $ROOTFS/etc/fstab
tmpfs /dev/shm tmpfs defaults 0 0
EOF
}

# Write out network config (dhcp)
write_ubuntu_network() {
cat <<EOF > $ROOTFS/etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
EOF
if [ -d "$ROOTFS/etc/dhcp3/" ]; then
cat <<EOF >> $ROOTFS/etc/dhcp3/dhclient.conf
send host-name "$UTSNAME";
EOF
elif [ -d "$ROOTFS/etc/dhcp/" ]; then
    sed -i 's/send host-name .*/send hostname -"$UTSNAME"/' "$ROOTFS/etc/dhcp/dhclient.conf"
fi
}

# Set the hostname for the container
write_ubuntu_hostname() {
cat <<EOF > $ROOTFS/etc/hostname
$UTSNAME
EOF
cat <<EOF > $ROOTFS/etc/hosts
127.0.0.1 $UTSNAME localhost

# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
EOF
}

# Writes a custom ssh config that allows root logins
write_ubuntu_sshd_config() {
cat <<EOF > $ROOTFS/etc/ssh/sshd_config
Port 22
Protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
UsePrivilegeSeparation yes
KeyRegenerationInterval 3600
ServerKeyBits 768
SyslogFacility AUTH
LogLevel INFO
LoginGraceTime 120
PermitRootLogin yes
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
PermitEmptyPasswords yes
ChallengeResponseAuthentication no
EOF
}

disable_upstart_mounts() {
    cat <<EOF > $ROOTFS/lib/init/fstab
#
# These are the filesystems that are always mounted on boot, you can
# override any of these by copying the appropriate line from this file into
# /etc/fstab and tweaking it as you see fit. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
/dev/root / rootfs defaults 0 1
#none /proc proc nodev,noexec,nosuid 0 0
none /proc/sys/fs/binfmt_misc binfmt_misc nodev,noexec,nosuid,optional 0 0
#none /sys sysfs nodev,noexec,nosuid 0 0
none /sys/fs/fuse/connections fusectl optional 0 0
none /sys/kernel/debug debugfs optional 0 0
none /sys/kernel/security securityfs optional 0 0
none /spu spufs gid=spu,optional 0 0
#none /dev devtmpfs,tmpfs mode=0755 0 0
none /dev/pts devpts noexec,nosuid,gid=tty,mode=0620 0 0
none /dev/shm tmpfs nosuid,nodev 0 0
none /tmp none defaults 0 0
none /var/run tmpfs mode=0755,nosuid,showthrough 0 0
none /var/lock tmpfs nodev,noexec,nosuid,showthrough 0 0
none /lib/init/rw tmpfs mode=0755,nosuid,optional 0 0
EOF
}

modify_upstart_sysinit() {
    sed -i 's/start on filesystem and net-device-up IFACE=lo/start on filesystem #and net-device-up IFACE=lo/' $ROOTFS/etc/init/rc-sysinit.conf
}

write_upstart_lxc() {
    cat <<EOF > $ROOTFS/etc/init/lxc.conf
# LXC – Fix init sequence to have LXC containers boot with upstart

# description “Fix LXC container - $UBUNTUVERSION”

start on startup

task
pre-start script
mount -t proc proc /proc
mount -t devpts devpts /dev/pts
mount -t sysfs sys /sys
mount -t tmpfs varrun /var/run
mount -t tmpfs varlock /var/lock
mkdir -p /var/run/network
touch /var/run/utmp
chmod 664 /var/run/utmp
chown root.utmp /var/run/utmp
if [ "$(find /etc/network/ -name upstart -type f)" ]; then
chmod -x /etc/network/*/upstart || true
fi
end script

script
start networking
initctl emit filesystem --no-wait
initctl emit local-filesystems --no-wait
initctl emit virtual-filesystems --no-wait
init 2
end script
EOF
mkdir -p $ROOTFS/var/run/network
touch $ROOTFS/var/run/network/ifstate
mkdir -p $ROOTFS/var/run/sshd
}

# Disables services a container doesn't need
disable_ubuntu_services() {
    chroot $ROOTFS /usr/sbin/update-rc.d -f umountfs remove
    chroot $ROOTFS /usr/sbin/update-rc.d -f hwclock.sh remove
    chroot $ROOTFS /usr/sbin/update-rc.d -f hwclockfirst.sh remove
}


################################################################################
# lxc configuration files
################################################################################

write_lxc_configuration() {
cat <<EOF > $CONFFILE
lxc.utsname = $UTSNAME
lxc.tty = 6
lxc.pts = 1024
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.mtu = $MTU
lxc.rootfs = $ROOTFS
#lxc.mount = $TMPMNTFILE
lxc.cgroup.devices.deny = a
# /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
EOF
}

write_lxc_mounts() {

    TMPMNTFILE=$(mktemp lxc.XXXXXXXXXX)

    if [ ! -z "$MNTFILE" ]; then
cp $MNTFILE $TMPMNTFILE
    fi
}


collect_information() {

    # choose a hostname, default is the container name
    echo -n "What hostname do you wish for this container ? [$NAME] "
    read _UTSNAME_

    if [ ! -z "$_UTSNAME_" ]; then
UTSNAME=$_UTSNAME_
    else
UTSNAME=$NAME
    fi

}

collect_btrfs_information() {
    echo Available snapshots:
    ls $TEMPLATES
    
    echo -n "Which snapshot would you like to clone? [$SOURCE_SNAPSHOT] "
    read _SOURCE_SNAPSHOT_

    if [ ! -z "$_SOURCE_SNAPSHOT_" ]; then
SOURCE_SNAPSHOT=$_SOURCE_SNAPSHOT_
    else
        echo "Must specify a snapshot"
        exit 1
    fi

    btrfs subvolume list $TEMPLATES$SOURCE_SNAPSHOT 2>/dev/null | grep $SOURCE_SNAPSHOT > /dev/null 2>&1
    RES=$?
    if [ "$RES" != "0" ]; then
        echo "Invalid snapshot, please use the full name"
        exit 1
    fi
}

run_pre_create_hook() {
    # source a hook that's run before the container is created
    # sourced so it has access to our variables, like $NAME
    test -x /etc/lxc-ubuntu/host-pre-create && . /etc/lxc-ubuntu/host-pre-create
}

run_post_create_hooks() {
    # source host and guest hooks for after the container is created
    test -x /etc/lxc-ubuntu/host-post-create && . /etc/lxc-ubuntu/host-post-create

    if [ -x /etc/lxc-ubuntu/guest-post-create ]; then
cp /etc/lxc-ubuntu/guest-post-create $ROOTFS/guest-post-create
        chroot $ROOTFS /bin/bash /guest-post-create
        chroot $ROOTFS /bin/rm /guest-post-create
    fi
}

clone_btrfs_snapshot() {
    if [ -d "$FSBASE/$UTSNAME" ]; then
        echo "Container already exists!"
        lxc-destroy -n $NAME
        exit 1
    fi
    btrfs subvolume snapshot "$TEMPLATES/$SOURCE_SNAPSHOT" "$FSBASE/$UTSNAME"
    RES=$?
    if [ "$RES" != "0" ]; then
        echo "Clone snapshot failed!"
        lxc-destroy -n $NAME
        exit 1
    fi
}

create() {

    collect_information
collect_btrfs_information
    write_lxc_mounts
    write_lxc_configuration

    /usr/bin/lxc-create -n $NAME -f $CONFFILE
    RES=$?

    # remove the configuration files
    rm -f $CONFFILE
    rm -f $TMPMNTFILE

    if [ "$RES" != "0" ]; then
echo "Failed to create '$NAME'"
        exit 1
    fi

clone_btrfs_snapshot
    write_ubuntu_hostname
    write_ubuntu_fstab
    write_ubuntu_network
    write_ubuntu_sshd_config
    write_ubuntu_selinux
    write_ubuntu_console
    disable_ubuntu_services
    run_pre_create_hook
    run_post_create_hooks
    disable_upstart_mounts
    modify_upstart_sysinit
    write_upstart_lxc
 

    echo "Done."
    echo -e "\nYou can run your container with the 'lxc-start -n $NAME'\n"
}

destroy() {

    echo -n "This will PERMANENTLY DESTROY the container $NAME. Are you sure? [y/N] ? "
    read
if [ "$REPLY" != "y" ]; then
echo "Abort."
        exit
fi

halt=$(which lxc-halt)
    if lxc-info -n $NAME | grep RUNNING > /dev/null; then
if [ -n "$halt" ]; then
lxc-halt -n $NAME
        else
lxc-stop -n $NAME
        fi
fi
sleep .5

    btrfs subvolume delete "$FSBASE/$NAME"
    /usr/bin/lxc-destroy -n "$NAME"
    RETVAL=$?
    if [ ! $RETVAL -eq 0 ]; then
echo "Failed to destroyed '$NAME'"
        return $RETVAL
    fi

return 0
}

help() {
    cat <<EOF

This script is a helper to create ubuntu system containers.

The script will create the container configuration file following
the informations submitted interactively with 'lxc-ubuntu create'

Have fun :)

EOF
}

usage() {
    echo "Usage: $(readlink -f $0) {create|destroy} -n NAME"
    exit 0
}



# Main program

if [ $# = 0 ]; then
usage
fi

case "$1" in
    -h|-help|--help) help;;
esac

if [ "$(id -u)" != "0" ]; then
echo "This script should be run as 'root'"
    exit 1
fi

NAME=""
COMMAND=$1
shift
while getopts "n:" flag
do
case $flag in
        n) NAME="$OPTARG" ;;
        *)
            echo "Unknown argument: $flag"
            exit 1
            ;;
    esac
done

if [ -z "$NAME" ]; then
echo "lxc-ubuntu: missing container name, use -n option"
    exit 1
fi

CONFFILE="/var/lib/lxc/$NAME.conf"
ROOTFS="$FSBASE/$NAME/rootfs"

case "$COMMAND" in
    create) create;;
    destroy) destroy;;
    *) usage;;
esac
