#!/bin/sh

# container-tools - Manage systemd-nspawn containers
# Copyright (C) 2014-2017 Daniel Baumann <daniel.baumann@open-infrastructure.net>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

set -e

SCRIPT="${0}"

CONFIG="/etc/container-tools/config"
HOOKS="/etc/container-tools/hooks"
MACHINES="/var/lib/machines"

Parameters ()
{
	GETOPT_LONGOPTIONS="bind:,bind-ro:,script:,name:,preseed-file:"
	GETOPT_OPTIONS="b:,s:,n:,p:"

	PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${SCRIPT} --options ${GETOPT_OPTIONS} --shell sh -- ${@})"

	if [ "${?}" != "0" ]
	then
		echo "'${SCRIPT}': getopt exit" >&2
		exit 1
	fi

	eval set -- "${PARAMETERS}"

	while true
	do
		case "${1}" in
			-b|--bind)
				# ignore
				shift 2
				;;

			--bind-ro)
				# ignore
				shift 2
				;;

			--cnt.auto)
				# ignore
				shift 2
				;;

			-s|--script)
				# ignore
				shift 2
				;;

			-n|--name)
				NAME="${2}"
				shift 2
				;;

			--preseed-file)
				PRESEED_FILE="${2}"
				shift 2
				;;

			--)
				shift 1
				break
				;;

			*)
				echo "'${SCRIPT}': getopt error" >&2
				exit 1
				;;
		esac
	done
}

Usage ()
{
	echo "Usage: container create -n|--name NAME -s|--script ${SCRIPT} -- [-p|--preseed-file FILE]" >&2
	exit 1
}

Parameters "${@}"

if [ -z "${NAME}" ]
then
	Usage
fi

if [ -e "${MACHINES}/${NAME}" ]
then
	echo "'${NAME}': container already exists" >&2
	exit 1
fi

if [ ! -x /usr/sbin/debootstrap ]
then
	echo "'${NAME}': /usr/sbin/debootstrap - no such file." >&2
	exit 1
fi

if [ "$(id -u)" -ne 0 ]
then
	echo "'${NAME}': need root privileges" >&2
	exit 1
fi

Chroot ()
{
	CHROOT="${1}"
	shift

	chroot "${CHROOT}" /usr/bin/env -i \
		LC_ALL="C" PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games" TERM="${TERM}" \
		DEBIAN_FRONTEND="${DEBCONF_FRONTEND}" DEBIAN_PRIORITY="${DEBCONF_PRIORITY}" \
		DEBCONF_NONINTERACTIVE_SEEN="true" DEBCONF_NOWARNINGS="true" \
		ftp_proxy="${ftp_proxy}" http_proxy="${http_proxy}" \
		${@}
}

CIDR ()
{
	x=${1##*255.}
	set -- 0^^^128^192^224^240^248^252^254^ $(( (${#1} - ${#x})*2 )) ${x%%.*}

	x=${1%%$3*}
	echo $(( $2 + (${#x}/4) ))
}

Upgrade_system ()
{
	DIRECTORY="${1}"

	# Mount pseudo-filesystems
	mount -o bind /dev/pts "${DIRECTORY}/dev/pts"
	mount -o bind /proc "${DIRECTORY}/proc"
	mount -o bind /sys "${DIRECTORY}/sys"

	# Bind mount systems apt cache
	mount -o bind /var/cache/apt/archives "${DIRECTORY}/var/cache/apt/archives"

	# Disable dpkg syncing

cat > "${DIRECTORY}/etc/dpkg/dpkg.cfg.d/container-tools" << EOF
force-unsafe-io
EOF

	# Create policy-rc.d file

cat > "${DIRECTORY}/usr/sbin/policy-rc.d" << EOF
#!/bin/sh
echo "All runlevel operations denied by policy" >&2
exit 101
EOF

	chmod 0755 "${DIRECTORY}/usr/sbin/policy-rc.d"

	# Upgrade system
	Chroot "${DIRECTORY}" "apt-get update"
	Chroot "${DIRECTORY}" "apt-get --yes --option APT::Force-LoopBreak=true --option Dpkg::Options::=--force-confnew upgrade"
	Chroot "${DIRECTORY}" "apt-get --yes --option APT::Force-LoopBreak=true --option Dpkg::Options::=--force-confnew dist-upgrade"

	# Install systemd support packages
	Chroot "${DIRECTORY}" "apt-get --yes install dbus libpam-systemd systemd-sysv"

	# Unmount systems apt cache
	umount "${DIRECTORY}/var/cache/apt/archives"

	Chroot "${DIRECTORY}" "apt-get clean"
}

Cleanup_system ()
{
	DIRECTORY="${1}"

	Chroot "${DIRECTORY}" "apt-get --yes autoremove"
	Chroot "${DIRECTORY}" "apt-get clean"

	# Cleanup
	rm -f "${DIRECTORY}/etc/dpkg/dpkg.cfg.d/container-tools"
	rm -f "${DIRECTORY}/usr/sbin/policy-rc.d"

	# Unmount pseudo-filesystems
	umount "${DIRECTORY}/dev/pts"
	umount "${DIRECTORY}/proc"
	umount "${DIRECTORY}/sys"
}

Debconf ()
{
	# Configure local debconf
	mkdir -p /tmp/container-tools
	DEBCONF_TMPDIR="$(mktemp -d -p /tmp/container-tools -t $(basename ${0}).XXXX)"
	export DEBCONF_TMPDIR

	mkdir -p "${DEBCONF_TMPDIR}/debconf"

cat > "${DEBCONF_TMPDIR}/debconf.systemrc" << EOF
Config: configdb
Templates: templatedb

Name: config
Driver: File
Mode: 644
Reject-Type: password
Filename: ${DEBCONF_TMPDIR}/debconf/config.dat

Name: passwords
Driver: File
Mode: 600
Backup: false
Required: false
Accept-Type: password
Filename: ${DEBCONF_TMPDIR}/debconf/passwords.dat

Name: configdb
Driver: Stack
Stack: config, passwords

Name: templatedb
Driver: File
Mode: 644
Filename: ${DEBCONF_TMPDIR}/debconf/templates.dat
EOF

	DEBCONF_SYSTEMRC="${DEBCONF_TMPDIR}/debconf.systemrc"
	export DEBCONF_SYSTEMRC
}

Debootstrap ()
{
	DIRECTORY="${1}"

	INCLUDE="dbus"

	if echo "${MIRROR}" | grep -qs '^https' || \
	   echo "{PARENT_MIRROR}" | grep -qs '^https'
	then
		INCLUDE="${INCLUDE},apt-transport-https,ca-certificates"
	fi

	mkdir -p "$(dirname ${DIRECTORY})"
	debootstrap --verbose --arch=${ARCHITECTURE} --components=${PARENT_ARCHIVE_AREAS} --include=${INCLUDE} \
		${PARENT_DISTRIBUTION} "${DIRECTORY}" ${PARENT_MIRROR}
}

Configure_apt ()
{
	DIRECTORY="${1}"

	# Configure apt
	rm -f "${DIRECTORY}/etc/apt/sources.list"

	PARENT_AREA="$(echo ${PARENT_ARCHIVE_AREAS} | sed -e 's|,| |g')"
	PARENT_DIST="$(echo ${PARENT_DISTRIBUTION} | sed -e 's|-backports||')"

	echo "deb ${PARENT_MIRROR} ${PARENT_DIST} ${PARENT_AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/debian.list"

	case "${MODE}" in
		progress-linux)
			AREA="$(echo ${ARCHIVE_AREAS} | sed -e 's|,| |g')"
			DIST="$(echo ${DISTRIBUTION} | sed -e 's|-backports||')"

			echo "deb ${MIRROR} ${DIST} ${AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"

			case "${DISTRIBUTION}" in
				*-backports)
					echo "deb ${MIRROR} ${DIST}-backports ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
					;;
			esac

cat > "${DIRECTORY}/etc/apt/preferences.d/progress-linux.pref" << EOF
Package: *
Pin: release n=${DIST}
Pin-Priority: 999

Package: *
Pin: release n=${DIST}-security
Pin-Priority: 999

Package: *
Pin: release n=${DIST}-updates
Pin-Priority: 999

Package: *
Pin: release n=${DIST}-lts
Pin-Priority: 999

Package: *
Pin: release n=${DIST}-extras
Pin-Priority: 999
EOF
			case "${DISTRIBUTION}" in
				*-backports)

cat >> "${DIRECTORY}/etc/apt/preferences.d/progress-linux.pref" << EOF

Package: *
Pin: release n=${DIST}-backports
Pin-Priority: 999

Package: *
Pin: release n=${DIST}-backports-extras
Pin-Priority: 999
EOF

					;;

				*)

cat >> "${DIRECTORY}/etc/apt/preferences.d/progress-linux.pref" << EOF

#Package: *
#Pin: release n=${DIST}-backports
#Pin-Priority: 999

#Package: *
#Pin: release n=${DIST}-backports-extras
#Pin-Priority: 999
EOF

					;;
			esac

			# Import archive keys
			KEYS="project/pgp/archive-key-${DIST}.asc"

			for KEY in ${KEYS}
			do
				KEY_NAME="$(basename ${KEY})"
				echo "P: Fetching archive-key ${KEY_NAME}..."

				wget -q "${MIRROR}/${KEY}" -O "${DIRECTORY}/key.asc"
				wget -q "${MIRROR}/${KEY}.gpg" -O "${DIRECTORY}/key.asc.gpg"

				if [ -e /usr/bin/gpgv ]
				then
					if [ -e /usr/share/keyrings/debian-keyring.gpg ] || [ -e /usr/share/keyrings/debian-maintainers.gpg ]
					then
						KEY_VALID=""

						for KEYRING in /usr/share/keyrings/debian-keyring.gpg /usr/share/keyrings/debian-maintainers.gpg
						do
							if [ -e "${KEYRING}" ]
							then
								echo -n "I: Verifying archive-key ${KEY_NAME} against $(basename ${KEYRING} .gpg | sed -e 's|-keyring||') keyring..."

								# FIXME: doesn't work anymore when the template eventually be run with 'set -e'
								/usr/bin/gpgv --quiet --keyring ${KEYRING} "${DIRECTORY}/key.asc.gpg" "${DIRECTORY}/key.asc" > /dev/null 2>&1 && KEY_VALID="true" && break
							fi
						done

						case "${KEY_VALID}" in
							true)
								echo " successful."
								;;

							*)
								echo " failed."
								return 1
								;;
						esac
					else
						echo "W: Skipping archive-key ${KEY_NAME} verification, debian-keyring not available..."
					fi
				else
					echo "W: Skipping archive-key ${KEY_NAME} verification, gpgv not available..."
				fi

				echo "P: Importing archive-key ${KEY_NAME}..."

				Chroot "${DIRECTORY}" "apt-key add key.asc"
				rm -f "${DIRECTORY}/key.asc"
				rm -f "${DIRECTORY}/key.asc.gpg"
			done

			Chroot "${DIRECTORY}" "apt-get update"
			;;
	esac
}

Deconfigure_system ()
{
	DIRECTORY="${1}"

	# Configure fstab

cat > "${DIRECTORY}/etc/fstab" << EOF
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>

EOF

	# Fix /etc/mtab
	rm -f "${DIRECTORY}/etc/mtab"
	ln -s /proc/self/mounts "${DIRECTORY}/etc/mtab"

	# Temporary hack for dpkg
	if [ -e "${DIRECTORY}/etc/dpkg/origins/default" ]
	then
		rm -f "${DIRECTORY}/etc/dpkg/origins/default"
		Chroot "${DIRECTORY}" "dpkg-reconfigure base-files"
	fi

	# Temporary hack for base-files
	for FILE in motd.tail motd profile
	do
		if [ -e "${DIRECTORY}/usr/share/base-files/${FILE}" ]
		then
			rm -f "${DIRECTORY}/etc/${FILE}"
			cp "${DIRECTORY}/usr/share/base-files/${FILE}" "${DIRECTORY}/etc"
		fi
	done

	# Temporary hack for readline
	if [ -e "${DIRECTORY}/etc/inputrc" ]
	then
		rm -f "${DIRECTORY}/etc/inputrc"
		cp "${DIRECTORY}/usr/share/readline/inputrc" "${DIRECTORY}/etc"
	fi

	# Temporary hack for sysvinit
	if [ -e "${DIRECTORY}/etc/inittab" ]
	then
		rm -f "${DIRECTORY}/etc/inittab"
		cp "${DIRECTORY}/usr/share/sysvinit/inittab" "${DIRECTORY}/etc/inittab"
	fi

	# Removing resolv.conf
	#rm -f "${DIRECTORY}/etc/resolv.conf"
	# FIXME: needs to stay for the moment
	rm -f "${DIRECTORY}/etc/resolv.conf"
	cp /etc/resolv.conf "${DIRECTORY}/etc"

	# Removing hosts/hostname
	rm -f "${DIRECTORY}"/etc/hosts
	rm -f "${DIRECTORY}"/etc/hostname

	# Removing openssh-server host keys
	rm -f "${DIRECTORY}"/etc/ssh/ssh_host_*_key
	rm -f "${DIRECTORY}"/etc/ssh/ssh_host_*_key.pub
}

Configure_system ()
{
	DIRECTORY="${1}"

	# Overwrite resolv.conf from cache with hosts resolv.conf
	rm -f "${DIRECTORY}/etc/resolv.conf"
	cp /etc/resolv.conf "${DIRECTORY}/etc"

	echo "${NAME}" > "${DIRECTORY}/etc/hostname"

	# Configure apt
	rm -f "${DIRECTORY}/etc/apt/sources.list"

	PARENT_AREA="$(echo ${PARENT_ARCHIVE_AREAS} | sed -e 's|,| |g')"
	PARENT_DIST="$(echo ${PARENT_DISTRIBUTION} | sed -e 's|-backports||')"

	echo "deb ${PARENT_MIRROR} ${PARENT_DIST} ${PARENT_AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/debian.list"

	for PARENT_REPO in ${PARENT_ARCHIVES}
	do
		case "${PARENT_REPO}" in
			${PARENT_DIST}-security)
				echo "deb ${PARENT_MIRROR_SECURITY} ${PARENT_DIST}/updates ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list"
				;;

			${PARENT_DIST}-updates)
				echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-updates ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list"
				;;

			${PARENT_DIST}-lts)
				echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-lts ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list"
				;;

			${PARENT_DIST}-backports)
				echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-backports ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list"
				;;

			${PARENT_DIST}-proposed-updates)
				echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-proposed-updates ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list"
				;;

			experimental)
				echo "deb ${PARENT_MIRROR} experimental ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list"
				;;
		esac
	done

	case "${MODE}" in
		progress-linux)
			AREA="$(echo ${ARCHIVE_AREAS} | sed -e 's|,| |g')"
			DIST="$(echo ${DISTRIBUTION} | sed -e 's|-backports||')"

			echo "deb ${MIRROR} ${DIST} ${AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"

			for REPO in ${ARCHIVES}
			do
				case "${REPO}" in
					${DIST}-staging)
						echo "deb ${MIRROR} ${DIST}-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-security)
						echo "deb ${MIRROR_SECURITY} ${DIST}-security ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-security-staging)
						echo "deb ${MIRROR_SECURITY} ${DIST}-security-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-updates)
						echo "deb ${MIRROR} ${DIST}-updates ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-updates-staging)
						echo "deb ${MIRROR} ${DIST}-updates-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-lts)
						echo "deb ${MIRROR} ${DIST}-lts ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-lts-staging)
						echo "deb ${MIRROR} ${DIST}-lts-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-extras)
						if echo "${AREA}" | grep -qs non-free
						then
							echo "deb ${MIRROR} ${DIST}-extras ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						else
							echo "deb ${MIRROR} ${DIST}-extras ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						fi
						;;

					${DIST}-extras-staging)
						if echo "${AREA}" | grep -qs non-free
						then
							echo "deb ${MIRROR} ${DIST}-extras-staging ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						else
							echo "deb ${MIRROR} ${DIST}-extras-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						fi
						;;

					${DIST}-backports)
						echo "deb ${MIRROR} ${DIST}-backports ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-backports-staging)
						echo "deb ${MIRROR} ${DIST}-backports-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						;;

					${DIST}-backports-extras)
						if echo "${AREA}" | grep -qs non-free
						then
							echo "deb ${MIRROR} ${DIST}-backports-extras ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						else
							echo "deb ${MIRROR} ${DIST}-backports-extras ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						fi
						;;

					${DIST}-backports-extras-staging)
						if echo "${AREA}" | grep -qs non-free
						then
							echo "deb ${MIRROR} ${DIST}-backports-extras-staging ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						else
							echo "deb ${MIRROR} ${DIST}-backports-extras-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list"
						fi
						;;
				esac
			done
			;;
	esac

	if [ "${APT_RECOMMENDS}" = "false" ]
	then

cat > "${DIRECTORY}/etc/apt/apt.conf.d/recommends.conf" << EOF
APT::Install-Recommends "false";
EOF

	fi

	# Add local archives configured from preseed file
	if ls "${DEBCONF_TMPDIR}/apt"/*.list > /dev/null 2>&1
	then
		cp "${DEBCONF_TMPDIR}/apt"/*.list "${DIRECTORY}/etc/apt/sources.list.d"

		if ls "${DEBCONF_TMPDIR}/apt"/*.key > /dev/null 2>&1
		then
			for KEY in "${DEBCONF_TMPDIR}/apt"/*.key
			do
				cp "${KEY}" "${DIRECTORY}"
				Chroot "${DIRECTORY}" "apt-key add $(basename ${KEY})"
				rm -f "${DIRECTORY}/$(basename ${KEY})"
			done
		fi

		if ls "${DEBCONF_TMPDIR}/apt"/*.pref > /dev/null 2>&1
		then
			cp "${DEBCONF_TMPDIR}/apt"/*.pref "${DIRECTORY}/etc/apt/preferences.d"
		fi
	fi

	Upgrade_system "${DIRECTORY}"

	# Preseed system
	if [ -n "${PRESEED_FILE}" ]
	then
		for FILE in ${PRESEED_FILE}
		do
			sed -e "s|@NAME@|${NAME}|g" "${FILE}" >> "${DIRECTORY}/preseed.cfg"
		done

		Chroot "${DIRECTORY}" "debconf-set-selections preseed.cfg"

		rm -f "${DIRECTORY}/preseed.cfg"
	fi

	# FIXME: All packages of priority of essential need to be reconfigured to reflect choices from preseeding
	# -> fix: use two-stage bootstrap (foreign) and inject preseeds in between

	# Manual hack to workaround broken preseeding in locales package
	if [ -n "${PRESEED_FILE}" ]
	then
		for FILE in ${PRESEED_FILE}
		do
			if grep -qs locales "${FILE}"
			then
				if [ -e "${DIRECTORY}/var/lib/dpkg/info/locales.list" ]
				then
					rm -f "${DIRECTORY}/etc/default/locale" "${DIRECTORY}/etc/locale.gen"
					Chroot "${DIRECTORY}" "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=criticial dpkg-reconfigure locales"

					break
				fi
			fi
		done
	fi

	# Manual hack to create conffiles when using locales-all instead of locales
	if [ ! -e "${DIRECTORY}/etc/environment" ]
	then
		echo "LANG=C.UTF-8" >> "${DIRECTORY}/etc/environment"
	fi

	if [ ! -e "${DIRECTORY}/etc/default/locale" ]
	then
		echo "LANG=C.UTF-8" >> "${DIRECTORY}/etc/default/locale"
	fi

	# Manual hack to workaround broken preseeding in tzdata package
	if [ -n "${PRESEED_FILE}" ]
	then
		for FILE in ${PRESEED_FILE}
		do
			if grep -qs tzdata "${FILE}"
			then
				rm -f "${DIRECTORY}/etc/timezone"
				Chroot "${DIRECTORY}" "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=criticial dpkg-reconfigure tzdata"

				break
			fi
		done
	fi

	# Temporary hack for base-files (base-files gets upgraded, so run it second time)
	for FILE in motd.tail motd profile
	do
		if [ -e "${DIRECTORY}/usr/share/base-files/${FILE}" ]
		then
			rm -f "${DIRECTORY}/etc/${FILE}"
			cp "${DIRECTORY}/usr/share/base-files/${FILE}" "${DIRECTORY}/etc"
		fi
	done

	# FIXME: Install additional packages after lxc-support has been run.
	# This is suboptimal, ideally we should install all packages but not run the maintainer scripts,
	# then run lxc-support, and run the maintainer scripts. This way, lxc-support would see
	# all the installed packages and could skip those scripts entirely when a certain package is not
	# installed. Unfortunately, that is not possible in any reasonable way with apt-get.
	# FTR: The only known workaround for now would be to first apt-get install --download-only all
	# packages, then unpack them with dpkg, run lxc-support, and dpkg --configure them.
	# For the time being, it's better to have lxc-support see no packages at all and be run before
	# packages are installed, than the other way around.
	# Workaround: We're running lxc-support at the end of the template again.
	if [ -n "${PACKAGES}" ]
	then
		Chroot "${DIRECTORY}" "apt-get --option APT::Force-LoopBreak=true --option Dpkg::Options::=--force-confnew --yes install ${PACKAGES}"
	fi

	# Manual hack to regenerate ssh keys
	if [ -e "${DIRECTORY}/var/lib/dpkg/info/openssh-server.postinst" ] && \
	   ! ls "${DIRECTORY}"/etc/ssh/ssh_host_*_key > /dev/null 2>&1
	then
		Chroot "${DIRECTORY}" "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=criticial dpkg-reconfigure openssh-server"
	fi

	# container command
	if [ -n "${CONTAINER_COMMAND}" ]
	then
		echo "${CONTAINER_COMMAND}" > "${DIRECTORY}/.container-command"

		chmod 0755 "${DIRECTORY}/.container-command"
		Chroot "${DIRECTORY}" "sh /.container-command"

		rm -f "${DIRECTORY}/.container-command"
	fi

}

Configure_network ()
{
	DIRECTORY="${1}"

	# Create /etc/resolv.conf
	rm -f "${DIRECTORY}/etc/resolv.conf.tmp"

	if [ -n "${NAMESERVER_DOMAIN}" ]
	then
		echo "domain ${NAMESERVER_DOMAIN}" >> "${DIRECTORY}/etc/resolv.conf.tmp"
	fi

	if [ -n "${NAMESERVER_SEARCH}" ]
	then
		echo "search ${NAMESERVER_SEARCH}" >> "${DIRECTORY}/etc/resolv.conf.tmp"
	fi

	if [ -n "${NAMESERVER_SERVER}" ]
	then
		if [ -e "${DIRECTORY}/etc/resolv.conf.tmp" ]
		then
			echo "" >> "${DIRECTORY}/etc/resolv.conf.tmp"
		fi

		for NAMESERVER in $(echo ${NAMESERVER_SERVER} | sed -e 's|,| |g')
		do
			echo "nameserver ${NAMESERVER}" >> "${DIRECTORY}/etc/resolv.conf.tmp"
		done
	fi

	if [ -n "${NAMESERVER_OPTIONS}" ]
	then
		if [ -e "${DIRECTORY}/etc/resolv.conf.tmp" ]
		then
			echo "" >> "${DIRECTORY}/etc/resolv.conf.tmp"
		fi

		echo "options ${NAMESERVER_OPTIONS}" >> "${DIRECTORY}/etc/resolv.conf.tmp"
	fi

	if [ -e "${DIRECTORY}/etc/resolv.conf.tmp" ]
	then
		mv "${DIRECTORY}/etc/resolv.conf.tmp" "${DIRECTORY}/etc/resolv.conf"
	fi

	# Create /etc/hosts
	case "${NETWORK0_IPV4_METHOD}" in
		none|dhcp)

cat > "${DIRECTORY}/etc/hosts.tmp" << EOF
127.0.0.1	localhost	${NAME}

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

			;;

		static)

cat > "${DIRECTORY}/etc/hosts.tmp" << EOF
127.0.0.1	localhost
${NETWORK0_IPV4_ADDRESS}	${NAME}

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

			;;
	esac

	mv "${DIRECTORY}/etc/hosts.tmp" "${DIRECTORY}/etc/hosts"
}

Configure_systemd_networkd ()
{
	# FIXME: postup/postdown: multi-interface route

	DIRECTORY="${1}"

	# Enable systemd-networkd
	chroot "${DIRECTORY}" apt purge --yes ifupdown || true

	rm -f "${DIRECTORY}/etc/network/interfaces"
	rmdir --ignore-fail-on-non-empty --parents "${DIRECTORY}"/etc/network/* > /dev/null 2>&1 || true

	chroot "${DIRECTORY}" systemctl enable systemd-networkd

	for NUMBER in $(seq 0 ${NETWORK_NUMBER})
	do
		eval IPV4_COMMENT="$`echo NETWORK${NUMBER}_IPV4_COMMENT`"
		eval IPV4_METHOD="$`echo NETWORK${NUMBER}_IPV4_METHOD`"
		eval IPV4_ADDRESS="$`echo NETWORK${NUMBER}_IPV4_ADDRESS`"
		eval IPV4_GATEWAY="$`echo NETWORK${NUMBER}_IPV4_GATEWAY`"
		eval IPV4_NETMASK="$`echo NETWORK${NUMBER}_IPV4_NETMASK`"
#		eval IPV4_POST_UP="$`echo NETWORK${NUMBER}_IPV4_POST_UP`"
#		eval IPV4_POST_DOWN="$`echo NETWORK${NUMBER}_IPV4_POST_DOWN`"

		if [ -z "${IPV4_METHOD}" ]
		then
			continue
		fi

		IPV4_SUFFIX="$(CIDR ${IPV4_NETMASK})"
		IPV4_CIDR="${IPV4_ADDRESS}/${IPV4_SUFFIX}"

cat > "${DIRECTORY}/etc/systemd/network/eth${NUMBER}.network" << EOF
[Match]
Name=eth${NUMBER}

[Network]
EOF

		if [ -n "${IPV4_COMMENT}" ]
		then
			echo "Description=${IPV4_COMMENT}" >> "${DIRECTORY}/etc/systemd/network/eth${NUMBER}.network"
		fi

		case "${IPV4_METHOD}" in
			dhcp)

cat >> "${DIRECTORY}/etc/systemd/network/eth${NUMBER}.network" << EOF
DHCP=ipv4
EOF

				;;

			static)

cat >> "${DIRECTORY}/etc/systemd/network/eth${NUMBER}.network" << EOF
DHCP=no
Address=${IPV4_CIDR}
EOF

				if [ -n "${IPV4_GATEWAY}" ]
				then

cat >> "${DIRECTORY}/etc/systemd/network/eth${NUMBER}.network" << EOF
Gateway=${IPV4_GATEWAY}
EOF

				fi
		esac

		NUMBER="$((${NUMBER} + 1))"
	done
}

Commands ()
{
	DIRECTORY="${1}"

	if [ -n "${HOST_COMMAND}" ]
	then
		echo "${HOST_COMMAND}" > "${DIRECTORY}/.host-command"

		cd "${DIRECTORY}"
		sh "${DIRECTORY}/.host-command"
		cd "${OLDPWD}"

		rm -f "${DIRECTORY}/.host-command"
	fi

	# config (FIXME)

	# maximum of 15 characters, prefix is 'veth-'
	HOSTNAME_SHORT="$(echo ${NAME} | cut -c-8)"
	HOST_INTERFACE_NAME="$(echo ${NETWORK0_VETH:-veth-${HOSTNAME_SHORT}-0})"

	sed -i -e "s|^cnt.auto=.*|cnt.auto=${CNT_AUTO}|" "${CONFIG}/${NAME}.conf"
	sed -i -e "s|^cnt.network-bridge=.*|cnt.network-bridge=${HOST_INTERFACE_NAME}:${NETWORK0_BRIDGE:-br0}|g" "${CONFIG}/${NAME}.conf"
	sed -i -e "s|^cnt.overlay=.*|cnt.overlay=${CNT_OVERLAY}|g" "${CONFIG}/${NAME}.conf"
	sed -i -e "s|^bind=.*|bind=${BIND}|" "${CONFIG}/${NAME}.conf"
	sed -i -e "s|^bind-ro=.*|bind-ro=${BIND_RO}|" "${CONFIG}/${NAME}.conf"
	sed -i -e "s|^network-veth-extra=.*|network-veth-extra=${HOST_INTERFACE_NAME}:eth0|g" "${CONFIG}/${NAME}.conf"

	for NUMBER in $(seq 1 ${NETWORK_NUMBER})
	do
		eval IPV4_METHOD="$`echo NETWORK${NUMBER}_IPV4_METHOD`"

		if [ -z "${IPV4_METHOD}" ]
		then
			continue
		fi

		eval HOST_INTERFACE_NAME="$`echo NETWORK${NUMBER}_VETH`"

		HOST_INTERFACE_NAME="$(echo ${HOST_INTERFACE_NAME:-veth-${HOSTNAME_SHORT}-${NUMBER}})"
		CONTAINER_INTERFACE_NAME="eth${NUMBER}"

		sed -i -e "/^register=.*/ a network-veth-extra=${HOST_INTERFACE_NAME}:${CONTAINER_INTERFACE_NAME}" "${CONFIG}/${NAME}.conf"

		eval BRIDGE="$`echo NETWORK${NUMBER}_BRIDGE`"
		sed -i -e "/^register=.*/ a cnt.network-bridge=${HOST_INTERFACE_NAME}:${BRIDGE:-br${NUMBER}}" "${CONFIG}/${NAME}.conf"
	done

	# Setting root password
	echo root:${ROOT_PASSWORD} | chroot "${DIRECTORY}" chpasswd

	case "${ROOT_RANDOM_PASSWORD}" in
		true)
			echo "${NAME}: root password set to '${ROOT_PASSWORD}'."
			;;
	esac
}

umask 0022

# Get distributor from template filename
MODE="$(basename ${SCRIPT})"

case "${MODE}" in
	debconf)
		MODE="debian"
		;;
esac

export MODE

CACHE="/var/cache/container-tools/${MODE}"
SYSTEM="${MACHINES}/${NAME}"

Debconf

export NAME

# Pre hooks
for FILE in "${HOOKS}/pre-${SCRIPT}".* "${HOOKS}/${NAME}.pre-${SCRIPT}"
do
	if [ -x "${FILE}" ]
	then
		"${FILE}"
	fi
done

# Run debconf parts
for SCRIPT in /usr/share/container-tools/scripts/debconf.d/*
do
	if [ -x "${SCRIPT}" ]
	then
		# FIXME
		# debconf -ocontainer-tools "${SCRIPT}"
		"${SCRIPT}"
	fi
done

# Read-in configuration from debconf
. "${DEBCONF_TMPDIR}/debconf.default"

## Generic parts
if [ ! -e "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" ]
then
	Debootstrap "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp"
	Configure_apt "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp"
	Deconfigure_system "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp"

	mv "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp" "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}"
fi

Upgrade_system "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" || echo "W: If upgrading the system failed, try removing the cache for your distribution in /var/cache/container-tools"
Cleanup_system "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}"

## Specific parts
mkdir -p "${MACHINES}"
cp -a "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" "${MACHINES}/${NAME}"

# Mounting rw bind mounts
if [ -n "${BIND}" ]
then
	BINDS="$(echo ${BIND} | sed -e 's|;| |g')"

	for ENTRY in ${BINDS}
	do
		SOURCE="$(echo ${ENTRY} | awk -F: '{ print $1 }')"
		TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')"

		mkdir -p "${SOURCE}"
		mkdir -p "${MACHINES}/${NAME}/${TARGET}"

		mount -o bind "${SOURCE}" "${MACHINES}/${NAME}/${TARGET}"
	done
fi

# Mounting ro bind mounts
if [ -n "${BIND_RO}" ]
then
	BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')"

	for ENTRY in ${BINDS_RO}
	do
		SOURCE="$(echo ${ENTRY} | awk -F: '{ print $1 }')"
		TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')"

		mkdir -p "${SOURCE}"
		mkdir -p "${MACHINES}/${NAME}/${TARGET}"

		mount -o rbind "${SOURCE}" "${MACHINES}/${NAME}/${TARGET}"
	done
fi

# Mounting overlay mounts
if [ -n "${CNT_OVERLAY}" ]
then
	CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')"

	for CNT_OVERLAY in ${CNT_OVERLAYS}
	do
		DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')"
		DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')"
		DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')"
		DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')"

		for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}"
		do
			mkdir -p "${DIRECTORY}"
		done

		mount -t overlay overlay-${NAME} -olowerdir="${DIRECTORY_LOWER}",upperdir="${DIRECTORY_UPPER}",workdir="${DIRECTORY_WORK}" "${DIRECTORY_MERGED}"
	done
fi

Configure_system "${MACHINES}/${NAME}"
Configure_network "${MACHINES}/${NAME}"
Configure_systemd_networkd "${MACHINES}/${NAME}" # FIXME
Cleanup_system "${MACHINES}/${NAME}"

Commands "${MACHINES}/${NAME}"

# Unmounting overlay mounts
if [ -n "${CNT_OVERLAY}" ]
then
	CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')"

	for CNT_OVERLAY in ${CNT_OVERLAYS}
	do
		DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')"
		DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')"
		DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')"
		DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')"

		umount -f "${DIRECTORY_MERGED}"

		for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}"
		do
			rmdir --ignore-fail-on-non-empty --parents ${DIRECTORY} > /dev/null 2>&1 || true
		done
	done
fi

# Unmounting ro bind mounts
if [ -n "${BIND_RO}" ]
then
	BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')"

	for ENTRY in ${BINDS_RO}
	do
		TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')"

		umount "${MACHINES}/${NAME}/${TARGET}"
	done
fi

# Unmounting rw bind mounts
if [ -n "${BIND}" ]
then
	BINDS="$(echo ${BIND} | sed -e 's|;| |g')"

	for ENTRY in ${BINDS}
	do
		TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')"

		umount "${MACHINES}/${NAME}/${TARGET}"
	done
fi

# remove debconf temporary files
rm --preserve-root --one-file-system -rf "${DEBCONF_TMPDIR}"
rmdir --ignore-fail-on-non-empty /tmp/container-tools 2>&1 || true

# Post hooks
for FILE in "${HOOKS}/post-${SCRIPT}".* "${HOOKS}/${NAME}.post-${SCRIPT}"
do
	if [ -x "${FILE}" ]
	then
		"${FILE}"
	fi
done