#!/bin/bash
#
# vserver-new - Create a new virtual server
# Copyright (C) 2005 Benedikt Boehm <hollow@gentoo.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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 -x
trap "exit 1" INT

#------------------------------------------------------------------------------#
# Constants                                                                    #
#------------------------------------------------------------------------------#

TARGETS="stage3 clone template"
VDIR="`readlink /etc/vservers/.defaults/vdirbase`"
APP="${0##*/}"


#------------------------------------------------------------------------------#
# Helper functions                                                             #
#------------------------------------------------------------------------------#

die() {
	eerror ${1:-<no error message>}
	einfo "Type ${APP} --help for details"
	
	if [ -n "${target}" ]; then
		echo
		${target}_clean ${2:-1}
	fi
	
	exit ${2:-1}
}

unpack() {
	local x
	local y
	local myfail
	local tarvars
	
	[ -z "$*" ] && die "Nothing passed to the 'unpack' command"
	
	for x in "$@"; do
		myfail="unpacking ${x} failed"
		y="${x%.*}"
		y="${y##*.}"
		
		case "${x##*.}" in
			tar)
				tar xf "${DISTDIR}/${x}" ${tarvars} || die "$myfail"
				;;
			tgz)
				tar xzf "${DISTDIR}/${x}" ${tarvars} || die "$myfail"
				;;
			tbz2)
				bzip2 -dc "${DISTDIR}/${x}" | tar xf - ${tarvars} || die "$myfail"
				;;
			ZIP|zip)
				unzip -qo "${DISTDIR}/${x}" || die "$myfail"
				;;
			gz|Z|z)
				if [ "${y}" == "tar" ]; then
						tar xzf "${DISTDIR}/${x}" ${tarvars} || die "$myfail"
				else
						gzip -dc "${DISTDIR}/${x}" > ${x%.*} || die "$myfail"
				fi
				;;
			bz2)
				if [ "${y}" == "tar" ]; then
						bzip2 -dc "${DISTDIR}/${x}" | tar xf - ${tarvars} || die "$myfail"
				else
						bzip2 -dc "${DISTDIR}/${x}" > ${x%.*} || die "$myfail"
				fi
				;;
			*)
				die "unpack ${x}: file format not recognized."
				;;
		esac
	done
}


#------------------------------------------------------------------------------#
# Generic target helpers                                                       #
#------------------------------------------------------------------------------#

generic_build() {
	local interfaceopts=
	
	for interface in ${dst_ifaces}; do
		interfaceopts="${interfaceopts} --interface ${interface}"
	done
	
	if [ $dst_vdir_mount -eq 0 ]; then
		ebegin "Automagically unmounting ${dst_vdir}"
		umount ${dst_vdir}
		eend $? || exit 2
		rm -rf ${dst_vdir} || die "failed to remove ${dst_vdir}"
	fi
	
	ebegin "Building skeleton configuration"
	vserver ${dst} build -m skeleton \
		--context ${dst_xid} \
		--hostname ${dst_hostname:-${dst}} \
		${interfaceopts} \
		--initstyle plain
	eend $? || ${target}_clean 2
	
	rm -rf ${dst_vdir}/*
	
	if [ $dst_vdir_mount -eq 0 ]; then
		ebegin "Automagically mounting ${dst_vdir}"
		mount ${dst_vdir}
		eend $? || exit 2
	fi
	
	pushd $dst_vdir &>/dev/null || die "cd ${dst_vdir} failed"
	
}

generic_clean() {
	popd &>/dev/null
	
	if [ -n "$1" ]; then
		echo
		einfo "Error detected; removing all generated files ..."
		
		ebegin "/etc/vservers/${dst}"
		rm -rf /etc/vservers/${dst}
		eend $?
		
		if [ ${dst_vdir_mount} -eq 0 ]; then
			ebegin "${dst_vdir}/*"
			rm -rf "${dst_vdir}"/*
			eend $?
		fi
		
		if [ ${dst_vdir_mount} -eq 1 ]; then
			ebegin "${dst_vdir}"
			rm -rf "${dst_vdir}"
			eend $?
		fi
		
		exit $1
	else
		local clean="/var/tmp /tmp /var/lib/init.d"
		
		for dir in ${clean}; do
			ebegin "Cleaning ${dir}"
			rm -rf ${dst_vdir}${dir}/*
			eend $?
		done
	fi
	
	echo
	ewarn
	ewarn "Please remember to change required settings inside your vserver"
	ewarn "(hostname, hosts, ip, etc)"
	ewarn
}

generic_boot() {
	if [ -n "${dst_start}" ]; then
		ebegin "Starting vserver ${dst}"
		vserver ${dst} start
		eend $? || exit 2
	fi
}



#------------------------------------------------------------------------------#
# Target: stage3                                                               #
#------------------------------------------------------------------------------#

STAGE3_DEFAULTTAR="stage3-i686-latest.tar.bz2"
STAGE3_DEFAULTURL="http://dev.gentoo.org/~hollow/vserver/stages/${STAGE3_DEFAULTTAR}"

stage3_usage() {
	echo "Usage: vserver-new <name> <globalopts> stage3 [<tar>]"
	echo
	echo "    <tar>    Location of the stage3 tarball"
	echo "             (default: download automagically)"
	echo
}

stage3_init() {
	tarball=
	
	if [ "$1" == "--help" ]; then
		stage3_usage
		exit 0
	fi
	
	if [ -n "$1" ]; then
		tarball=$1
		[ "${tarball#/}" == "${tarball}" ] && tarball="`pwd`/${tarball}"
	fi
	
	stage3_build
}

stage3_build() {
	generic_build
	
	if [ -z "${tarball}" ]; then
		tarball="`pwd`/${STAGE3_DEFAULTTAR}"
		
		einfo "Downloading stage3 tarball ..."
		echo
		wget ${STAGE3_DEFAULTURL} || die "download failed"
		echo
	fi
	
	[ ! -r ${tarball} ] && die "${tarball} is not readable"
	
	ebegin "Unpacking ${tarball}"
	unpack ${tarball}
	eend $? || stage3_clean 2
	
	mkdir usr/portage &>/dev/null || die "failed to create portdir"
	
	local portdir="`portageq portdir`"
	
	[ -z "${portdir}" ] && die "host has no portage tree!"
	
	ebegin "Using shared portage tree ${portdir}"
	mount -o bind ${portdir} usr/portage &>/dev/null
	eend $? || stage3_clean 2
	
	cp -L /etc/resolv.conf etc/resolv.conf || die "failed to copy DNS information"
	
	mount -t proc none proc/ || die "failed to mount procfs"
	
	cat <<EOF > tmp/vserver-new-stage3.sh
#!/bin/bash
if [ -r /sbin/functions.sh ]; then
	source /sbin/functions.sh
else
	echo "/sbin/functions.sh missing. Are you running Gentoo?"
fi

die() {
	eerror \${1:-<no error message>}
	einfo "Type \${APP} --help for details"
	exit \${2:-1}
}

env-update || die "failed to update environment"
source /etc/profile || die "failed to source /etc/profile"

pushd /etc &>/dev/null || die "failed to cd /etc"
rm make.profile

ebegin "Switching to vserver/x86 portage profile"
ln -s ../usr/portage/profiles/vserver/x86 make.profile
eend \$? || exit 1

echo

emerge syslog-ng

echo

pushd syslog-ng &>/dev/null || die "failed to cd /etc/syslog-ng"
sed -i -e 's:pipe("/proc/kmsg"); ::' syslog-ng.conf
sed -i -r 's:(.*console_all.*):#\1:g' syslog-ng.conf

rc-update add syslog-ng default

popd &>/dev/null
popd &>/dev/null
EOF
	
	chmod +x tmp/vserver-new-stage3.sh
	
	echo
	einfo "We're chrooting into the unpacked stage3 now!"
	ewarn "It is not advisable to hit Ctrl+C during this step!"
	ewarn "You have been warned! Continuing in 10 seconds ..."
	echo
	sleep 10
	
	chroot . /tmp/vserver-new-stage3.sh || stage3_clean 2
	
	stage3_clean
}

stage3_clean() {
	rm tmp/vserver-new-stage3.sh &>/dev/null
	
	umount usr/portage &>/dev/null
	umount proc/ &>/dev/null
	
	generic_clean $1
	stage3_boot
}

stage3_boot() {
	generic_boot
}

#------------------------------------------------------------------------------#
# Target: clone                                                                #
#------------------------------------------------------------------------------#

clone_usage() {
	echo "Usage: vserver-new <name> <globalopts> clone <src>"
	echo
	echo "    <src>    Name of the vserver to copy from"
	echo
}

clone_init() {
	src=
	
	if [ "$1" == "--help" ]; then
		clone_usage
		exit 0
	fi
	
	[ -z "$1" ] && die "Missing required argument <src>"
	
	src=$1 && shift
	src_vdir="${VDIR}/${src}"
	src_xid=$(</etc/vservers/${src}/context)
	
	[ ! -d $src_vdir ] && die "<src> does not exist"
	
	clone_build
}

clone_build() {
	stopped=
	if [ -d /etc/vservers/.defaults/run.rev/${src_xid} ]; then
		ebegin "Stopping vserver ${src}"
		vserver ${src} stop &>/dev/null
		eend $? || exit 2
		stopped=1
	fi
	
	generic_build
	
	ebegin "Running rsync -a $src_vdir/ $dst_vdir"
	rsync -a $src_vdir/ $dst_vdir
	eend $? || clone_clean 2
	
	clone_clean
}

clone_clean() {
	generic_clean $1
	clone_boot
}

clone_boot() {
	if [ -n "${stopped}" ] || [ -n "${start}" ]; then
		ebegin "Starting vserver ${src}"
		vserver ${src} start
		eend $? || exit 2
	fi
	
	generic_boot
}


#------------------------------------------------------------------------------#
# Target: template                                                             #
#------------------------------------------------------------------------------#

template_usage() {
	echo "Usage: vserver-new <name> <globalopts> template <archive>"
	echo
	echo "    <archive>    Path to the template tarball"
	echo
	echo "To build a template, go to your exisiting vserver"
	echo "installation and tar the whole rootfs:"
	echo
	echo "$ cd /vservers/foo"
	echo "$ tar cvpf ../foo-template.tar ./"
}

template_init() {
	archive=
	
	if [ "$1" == "--help" ]; then
		template_usage
		exit 0
	fi
	
	if [ -n "$1" ]; then
		archive=$1 && shift
		[ "${archive#/}" == "${archive}" ] && archive="$(pwd)/${archive}"
	else
		die "Missing required argument <archive>"
	fi
	
	template_build
}

template_build() {
	generic_build
	
	ebegin "Unpacking $(basename ${archive}) to ${dst_vdir}"
	unpack $archive
	eend $? || template_clean 2
	
	template_clean
}

template_clean() {
	generic_clean $1
	template_boot
}

template_boot() {
	generic_boot
}


#------------------------------------------------------------------------------#
# Main app                                                                     #
#------------------------------------------------------------------------------#
if [ -r /sbin/functions.sh ]; then
	source /sbin/functions.sh
else
	echo "/sbin/functions.sh missing. Are you running Gentoo?"
fi

usage() {
	echo "Usage: vserver-new <name> <globalopts> <target> [localopts]"
	echo
	echo "  <name>        Name of the new vserver"
	echo "  <globalopts>  See Global options below"
	echo "  <target>      See Supported targets below"
	echo "  [localopts]   Target specific options"
	echo
	echo "Global options:"
	echo "    --context <id>         Static context ID of <name> (required)"
	echo "    --hostname <name>      Hostname of <name>"
	echo "    --interface [<name-suffix>=][<device>:]<ip>[/<mask|prefixlen>]"
	echo "                           Declare a new network-interface for <name> (this"
	echo "                           option can be specified multiple times)"
	echo "    --start                Start the new vserver"
	echo "    --destroy              Destroy exisiting vserve installation"
	echo "                            (Use this option if you have seperate"
	echo "                            fs mounts for each vserver)"
	echo
	echo "Supported targets:"
	echo "    stage3    Build a new vserver using a stage3 tarball"
	echo "    clone     Clone an already exisiting vserver"
	echo "    template  Create a new vserver using a template tarball"
	echo
	echo "Type vserver-new <target> --help for local options"
}

# 1. Check for "vserver-new <target> --help" construct
if [ "$2" == "--help" ]; then
	for i in $TARGETS; do
		if [ "$i" == "$1" ]; then
			${i}_usage
			exit 0
		fi
	done
	
	die "Selected target not supported"
fi

# 2. Check for "vserver-new --help" construct
if [ "$1" == "--help" ]; then
	usage
	exit 0
fi

# 3. Checking <name>
[ -z "$1" ] && die "Missing argument <name>"

# 4. Parsing opts
dst=$1 && shift
opts=$(POSIXLY_CORRECT=1 getopt -o h --longoptions help,context:,hostname:,interface:,start,destroy -n $0 -- "$@")

[ "$?" != "0" ] && die "Wrong number of options"

eval set -- "$opts"

dst_vdir="${VDIR}/${dst}"
dst_xid=
dst_hostname=
dst_ifaces=
dst_start=
destroy=
dst_vdir_mount=1

while true; do
	case "$1" in
		-h|--help)
			usage
			exit 0
			;;
		--context)
			dst_xid=$2
			shift 2
			;;
		--hostname)
			dst_hostname=$2
			shift 2
			;;
		--interface)
			dst_ifaces="${dst_ifaces} $2"
			shift 2
			;;
		--start)
			dst_start=1
			shift
			;;
		--destroy)
			destroy=1
			shift
			;;
		--)
			shift
			break
			;;
	esac
done

# 5. Checking environment
if [ -d $dst_vdir ]; then
	grep ${dst_vdir} /etc/fstab &>/dev/null
	dst_vdir_mount=$?
	
	if [ -n "${destroy}" ]; then
		ewarn "Existing vserver installation will be destroyed!"
		ewarn "Proceeding in 10 seconds ..."
		sleep 10
		
		einfo "Removing existing installation ..."
		
		if [ ${dst_vdir_mount} -eq 0 ]; then
			ebegin "  ${dst_vdir}/*"
			rm -rf ${dst_vdir}/*
			eend $? || exit 1
		else
			ebegin "  ${dst_vdir}"
			rm -rf ${dst_vdir}
			eend $? || exit 1
		fi
		
		ebegin "  /etc/vservers/${dst}"
		rm -rf /etc/vservers/${dst}
		eend $?
	else
		eerror "Exisiting vserver installation found. Try --destroy"
		exit 1
	fi
fi

[ -d /etc/vservers/${dst} ] && die "vserver config dir already exists"

# 6. Checking required option --context
[ -z "$dst_xid" ] && die "Missing required global option --context"

# 7. Checking <target>
[ -z "$1" ] && die "Missing argument <target>"

target=
for i in $TARGETS; do
	[ "$i" == "$1" ] && target=$i
done
shift

[ -z "$target" ] && die "Selected target not supported"

# 8. Start target
trap "${target}_clean 1" INT
${target}_init "$@"
