#! /bin/bash

##- Creates a new vServer using debootstrap
##: vserver release[:arch][:mirror] ipaddress [size]
##+ vserver      Name for the new vServer
##+ release      Name of the release to instal (e.g. "sid")
##+ arch         Architecture, if not given uses value from config file
##+ mirror       Mirror URL, if not given uses value from config file
##+ ipaddress    Network address in [iface:]A.B.C.D[/BB] format
##+ size         Size of the created logical volume

[ "${__vsa_bindir}" ] || exec "$(dirname "$0")/vserver-admin" bootstrap "$@"
[[ $# -lt 3 ]] && __vsa_usage_and_exit

__vsa_run_locked
__vsa_config "$1" dst


vnet=$(__vsa_guess_vnet "$3")
size=${4:-${dst_lvm_default_size}}


# This is a list of arches for which the combo of debootstrap+vserver
# would (hopefully) work, assuming that having an util-vserver package
# exists suffices, the list of arches is the same as for this package:
# http://packages.debian.org/squeeze/util-vserver
declare -ar valid_arches=( i386 amd64 ia64 powerpc s390 sparc mips mipsel )


is_valid_debian_vserver_arch ()
{
	local i
	for i in "${valid_arches[@]}" ; do
		if [[ ${i} == $1 ]] ; then
			return 0
		fi
	done
	return 1
}


what_to_install ()
{
	local release arch mirror

	release=${1%%:*}

	if [[ ${release} = $1 ]] ; then
		# No "arch" and "mirror" parts given: get them from the configuration
		arch=${dst_bootstrap_arch}
		mirror=${dst_bootstrap_mirror}
	else
		# Try to extract architecture from the string
		arch=${1#${release}:}
		arch=${arch%%:*}

		if [[ ${arch} = ${release} ]] ; then
			# We were not given the mirror part, pick it from the configuration.
			arch=${1#${release}:}
			mirror=${dst_bootstrap_mirror}
		else
			mirror=${1#${release}:${arch}:}
			if [ "${mirror}" = "${release}:${arch}" ] ; then
				mirror=${dst_bootstrap_mirror}
			fi
		fi
	fi

	# Expand mirror alias, if possible
	local varname="dst_bootstrap_mirror_${mirror}"
	if [ "${!varname}" ] ; then
		mirror=${!varname}
	fi

	echo "release='${release}'"
	echo "mirror='${mirror}'"
	echo "arch='${arch}'"
}


eval "$(what_to_install "$2")"



add-fstab-entry ()
{
	local entry="/dev/${dst_lvm_group}/${dst_lvm_volume}" # device
	entry="${entry} ${__vsa_vdir}/${dst_vserver}"         # mount point
	entry="${entry} ${dst_lvm_fstab_filesystem}"          # filesystem
	entry="${entry} ${dst_lvm_fstab_options} 0 3"         # options

	# Now put the entry in place
	echo "${entry}" >> "${dst_lvm_fstab}"
}


fixup_i386_in_amd64 ()
{
	echo linux_32bit > "/etc/vservers/${dst_vserver}/personality"
	echo i686        > "/etc/vservers/${dst_vserver}/uts/machine"
}

add_in_guest_fstab_entry ()
{
	echo "${1}" >> "/etc/vservers/${dst_vserver}/fstab"
}

# Some times the bootstrap scripts fail horribly to set up the machine name.
# This is know to happen in Debian/Sid and Ubuntu/{Karmic,Lucid}. It is
# better to stay safe and always call this.
#
fixup_guest_hostname ()
{
	if [ -r "${dst_fsroot}/etc/hostname" ] ; then
		local tmp=$(< "${dst_fsroot}/etc/hostname")
		if [[ ${tmp} != ${dst_vserver} && ${tmp} = $(uname -n) ]] ; then
			# Host name differs from the desired one -- most likely it was set to
			# the name of the host due to missing customization script for the
			# installed Debian/Ubuntu release. We zap it here so it gets recreated
			# below.
			rm "${dst_fsroot}/etc/hostname"
		fi
	fi

	if [ ! -r "${dst_fsroot}/etc/hostname" ] ; then
		echo "${dst_vserver}" > "${dst_fsroot}/etc/hostname"
	fi
}


# It is utterly unacceptable to have a hosts file without the localhost
# and own machine entries. This will fix that up, if needed.
#
fixup_guest_hosts_file ()
{
	# Ensure that the file exists
	touch "${dst_fsroot}/etc/hosts"

	# Check, and add if needed, an entry for localhost
	if ! grep -qE '^127\.0\.0\.1[[:space:]]' "${dst_fsroot}/etc/hosts"
	then
		echo '127.0.0.1 localhost' >> "${dst_fsroot}/etc/hosts"
	fi

	# Same as above, for the main vServer IP address, which we get from the
	# vServer configuration directory.
	#
	if [ -r "/etc/vservers/${dst_vserver}/interfaces/0/ip" ] ; then
		local ipaddr=$(< "/etc/vservers/${dst_vserver}/interfaces/0/ip")

		if ! grep -qE "^${ipaddr//./\\.}[[:space:]]" "${dst_fsroot}/etc/hosts"
		then
			echo "${ipaddr} ${dst_vserver}" >> "${dst_fsroot}/etc/hosts"
		fi
	fi
}


# List of Upstart services to disable on Ubuntu by
# fixup_ubuntu_upstart_services() below.
#
declare -ar ubuntu_upstart_rm_services=(
	dbus
	procps
	hostname
	dmesg rsyslog-kmsg
	hwclock-save hwclock
	mountall-net mountall-reboot mountall-shell mountall
	network-interface networking
	tty1 tty2 tty3 tty4 tty5 tty6 console-setup
	upstart-udev-bridge
)

# Upstart scriptlet that will be dropped in /etc/init for Ubuntu
# guests. This fakes up some of the events needed for services to
# run. Those events are originally emitted by the services listed
# in ubuntu_upstart_rm_services to be removed.
#
ubuntu_upstart_scriptlet='
description "vServer init"
start on startup
task

pre-start script
	touch           /var/run/utmp
	chown root:utmp /var/run/utmp
	chmod 664       /var/run/utmp
	mkdir -p        /run/lock
	chmod 777       /run/lock
	chmod +t        /run/lock
end script

script
	initctl emit virtual-filesystems --no-wait
	initctl emit local-filesystems --no-wait
	initctl emit remote-filesystems --no-wait
	initctl emit all-swaps --no-wait
	initctl emit filesystem --no-wait
	initctl emit mounting --no-wait
	initctl emit mounted MOUNTPOINT=/dev --no-wait
	initctl emit mounted MOUNTPOINT=/var/run --no-wait
	initctl emit mounted MOUNTPOINT=/tmp --no-wait
	initctl emit net-device-up IFACE=lo --no-wait
	initctl emit net-device-up --no-wait
	initctl emit static-network-up --no-wait
end script
'


fixup_ubuntu_upstart_services ()
{
	# Step 1.) Remove services listed in ubuntu_upstart_rm_services
	#
	local svcname svcfile
	for svcname in "${ubuntu_upstart_rm_services[@]}" ; do
		# Note that Upstart scans all subdirectories of /etc/init, so it is
		# safer to do the same and search in the whole tree for service files.
		svcfile=$(find "${dst_fsroot}/etc/init/" -name "${svcname}.conf" -type f -print)
		if [[ -n ${svcfile} ]] ; then
			rm -f "${svcfile}"
		fi
	done

	# Step 2.) Drop-in a vserver.conf scriptlet that synthetizes events
	#          for other services to start properly.
	#
	echo "${ubuntu_upstart_scriptlet}" > "${dst_fsroot}/etc/init/vserver.conf"

	# Step 3.) Disable console output in Upstart scriptlets
	#
	sed -i 's/^\(console output\)$/#\1/1' "${dst_fsroot}"/etc/init/*

	# Step 4.) Fix OOM-disablers in all Upstart scriptlets. Adjusting the
	#          OOM killer inside vServer guests is not allowed.
	#
	while read -r svcfile ; do
		sed -i '/^[[:space:]]*oom[[:space:]]/s/^/#/' "${svcfile}"
	done < <( find "${dst_fsroot}/etc/init" -name '*.conf' -type f )
}


fixup_guest_dev ()
{
	# Certain guests using Upstart (mainly Ubuntu) could chew up CPU cycles
	# when /dev/console is not present. This ensures that the device node
	# exists.
	#
	if [[ ! -c ${dst_fsroot}/dev/console ]] ; then
		mknod -m 0600 "${dst_fsroot}/dev/console" c 5 1
	fi
	# Same for /dev/kmsg
	if [[ ! -c ${dst_fsroot}/dev/kmsg ]] ; then
		mknod -m 0600 "${dst_fsroot}/dev/kmsg" c 1 11
	fi
}


build_opts=(
	--interface "${vnet}"
	--hostname  "${dst_vserver}"
	--force
	--
	-d "${release}"
)

if [[ -d ${dst_fsroot} || -d ${__vsa_vdir}/${dst_vserver} ]] ; then
	__vsa_die "A vServer named '${dst_vserver}' already exists!"
fi

if [[ ! -r /etc/debian_version ]] ; then
	__vsa_die "This command will run properly only on Debian systems."
fi

if [[ ! -d /usr/share/debootstrap ]] ; then
	__vsa_die "Directory /usr/share/debootstrap missing. Hint: install debootstrap."
fi

if [[ ! -r /usr/share/debootstrap/scripts/${release} ]] ; then
	__vsa_die "Invalid release '${release}', debootstrap does not know about it."
fi


# Check whether Ubuntu is being debootstrapped. In this case, some fixups
# are to be done later to make Upstart work inside the vServer.
#
# More info: http://linux-vserver.org/Upstart_issues
#
is_ubuntu=false
if grep -qE 'default_mirror[[:space:]]+http://[^[:space:]]*\.ubuntu.com/' \
	          "/usr/share/debootstrap/scripts/${release}"
then
	is_ubuntu=true
fi

if ${is_ubuntu} ; then
	build_opts=( --initstyle plain "${build_opts[@]}" )
fi


if [ "${mirror}" ] ; then
	build_opts=( "${build_opts[@]}" -m "${mirror}" )
fi
if [ "${arch}" ] ; then
	if is_valid_debian_vserver_arch "${arch}" ; then
		build_opts=( "${build_opts[@]}" -- --arch "${arch}" )
	else
		__vsa_die "Unrecognized architecture, valid ones: ${valid_arches[*]}"
	fi
fi


if [ "${dst_lvm_group}" ] ; then
	__vsa_do mkdir "${__vsa_vdir}/${dst_vserver}"
	__vsa_do lvcreate -L "${size}" -n "${dst_lvm_volume}" "${dst_lvm_group}"
	__vsa_do __vsa_wait_fs "/dev/${dst_lvm_group}/${dst_lvm_volume}"
	if [ "${dst_lvm_volume_create_hook}" ] ; then
		eval __vsa_do "${dst_lvm_volume_create_hook}"
	fi
	__vsa_do add-fstab-entry
	__vsa_do mount "${__vsa_vdir}/${dst_vserver}"
fi

__vsa_do vserver --silent "${dst_vserver}" build -m debootstrap "${build_opts[@]}"

machine="${arch}:$(uname -m)"
case ${machine} in
	i386:x86_64) __vsa_do fixup_i386_in_amd64 ;;
esac

__vsa_do fixup_guest_hostname
__vsa_do fixup_guest_hosts_file
__vsa_do fixup_guest_dev

if ${is_ubuntu} ; then
	__vsa_do fixup_ubuntu_upstart_services
	__vsa_do add_in_guest_fstab_entry \
		"none    /run            tmpfs   size=32m,nosuid,mode=0755       0 0"
fi