#!/bin/sh

# container-tools - Manage systemd-nspawn containers
# Copyright (C) 2014-2016 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

COMMAND="$(basename ${0})"

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

START="false"
SYSTEMCTL="true"

Parameters ()
{
	LONG_OPTIONS="name:,nspawn,start,"
	OPTIONS="n:,"

	PARAMETERS="$(getopt --longoptions ${LONG_OPTIONS} --name=${COMMAND} --options ${OPTIONS} --shell sh -- ${@})"

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

	eval set -- "${PARAMETERS}"

	while true
	do
		case "${1}" in
			-n|--name)
				NAME="${2}"
				shift 2
				;;

			--nspawn)
				# internal option
				SYSTEMCTL="false"
				shift 1
				;;

			--start)
				# internal option
				START="true"
				SYSTEMCTL="false"
				shift 1
				;;

			--)
				shift 1
				break
				;;

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

Usage ()
{
	echo "Usage: container ${COMMAND} -n|--name NAME" >&2
	exit 1
}

Parameters "${@}"

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

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

case "${START}" in
	false)
		STATE="$(machinectl show ${NAME} 2>&1 | awk -F= '/^State=/ { print $2 }')"

		case "${STATE}" in
			running)
				echo "'${NAME}': container is already started" >&2
				exit 1
				;;
		esac
		;;
esac

HOST_ARCHITECTURE="$(dpkg --print-architecture)"
MACHINE_ARCHITECTURE="$(chroot ${MACHINES}/${NAME} dpkg --print-architecture)"

case "${HOST_ARCHITECTURE}" in
	amd64)
		case "${MACHINE_ARCHITECTURE}" in
			i386)
				SETARCH="setarch i686"
				;;

			*)
				SETARCH=""
				;;
		esac
		;;

	arm64)
		case "${MACHINE_ARCHITECTURE}" in
			armel|armhf)
				SETARCH="setarch armv7l"
				;;

			*)
				SETARCH=""
				;;
		esac
		;;
esac

# config
if [ -e "${CONFIG}/${NAME}.conf" ]
then
	BIND="$(awk -F= '/^bind=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

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

		for BIND in ${BINDS}
		do
			DIRECTORY="$(echo ${BIND} | awk -F: '{ print $1 }')"

			mkdir -p "${DIRECTORY}"
		done

		BIND=""

		for DIRECTORIES in ${BINDS}
		do
			BIND="${BIND} --bind ${DIRECTORIES}"
		done
	fi

	BOOT="$(awk -F= '/^boot=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo yes)"

	case "${BOOT}" in
		yes)
			BOOT="--boot"
			;;

		*)
			BOOT=""
			;;
	esac

	CAPABILITY="$(awk -F= '/^capability=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	case "${CAPABILITY}" in
		"")
			CAPABILITY=""
			;;

		*)
			CAPABILITY="--capability=${CAPABILITY}"
			;;
	esac

	DIRECTORY="$(awk -F= '/^directory=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo ${MACHINES}/${NAMES})"
	DIRECTORY="--directory ${DIRECTORY}"

	DROP_CAPABILITY="$(awk -F= '/^drop-capability=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	case "${DROP_CAPABILITY}" in
		"")
			DROP_CAPABILITY=""
			;;

		*)
			DROP_CAPABILITY="--drop-capability=${DROP_CAPABILITY}"
			;;
	esac

	MACHINE="--machine=${NAME}"

	NETWORK_VETH_EXTRA_CONF="$(awk -F= '/^network-veth-extra=/ { print $2 }' ${CONFIG}/${NAME}.conf)"
	NETWORK_VETH_EXTRA=""

	case "${NETWORK_VETH_EXTRA_CONF}" in
		"")
			;;

		*)
			for VETH in ${NETWORK_VETH_EXTRA_CONF}
			do
				NETWORK_VETH_EXTRA="${NETWORK_VETH_EXTRA} --network-veth-extra=${VETH}"
				INTERFACE="$(echo ${VETH} | awk -F: '{ print $1 }')"

cat > "/etc/network/interfaces.d/${INTERFACE}" << EOF
allow-hotplug ${INTERFACE}
iface ${INTERFACE} inet manual
	pre-up ifconfig ${INTERFACE} up
	post-down ifconfig ${INTERFACE} down
EOF

			done
			;;
	esac

	NETWORK_BRIDGES="$(awk -F= '/^cnt.network-bridge=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	case "${NETWORK_BRIDGES}" in
		"")
			;;

		*)
			for BRIDGE_DEFINITION in ${NETWORK_BRIDGES}
			do
				INTERFACE="$(echo ${BRIDGE_DEFINITION} | awk -F: '{ print $1 }')"
				BRIDGE="$(echo ${BRIDGE_DEFINITION} | awk -F: '{ print $2 }')"

				if [ -n "${BRIDGE}" ] && [ -n "${INTERFACE}" ]
				then

cat > "/etc/network/interfaces.d/${INTERFACE}" << EOF
allow-hotplug ${INTERFACE}
iface ${INTERFACE} inet manual
	pre-up ifconfig ${INTERFACE} up
	post-up brctl addif ${BRIDGE} ${INTERFACE}
	pre-down brctl delif ${BRIDGE} ${INTERFACE}
	post-down ifconfig ${INTERFACE} down
EOF

				else
					echo "Warning bridge definition '${BRIDGE_DEFINITION}' not recognized (expected <bridge>:<interface>): Ignoring"
				fi
			done
			;;
	esac

	LINK_JOURNAL="$(awk -F= '/^link-journal=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo no)"

	case "${LINK_JOURNAL}" in
		yes)
			LINK_JOURNAL="--link-journal=yes"
			;;

		*)
			LINK_JOURNAL="--link-journal=no"
			;;
	esac

	REGISTER="$(awk -F= '/^register=/ { print $2 }' ${CONFIG}/${NAME}.conf || echo yes)"

	case "${REGISTER}" in
		yes)
			REGISTER="--register=yes"
			;;

		*)
			REGISTER="--register=no"
			;;
	esac

	BLOCK_IO_DEVICE_WEIGHT="$(awk -F= '/^BlockIODeviceWeight=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${BLOCK_IO_DEVICE_WEIGHT}" ]
	then
		BLOCK_IO_DEVICE_WEIGHT="BlockIODeviceWeight=${BLOCK_IO_DEVICE_WEIGHT}"
		SET_PROPERTY="true"
	fi

	BLOCK_IO_READ_BANDWITH="$(awk -F= '/^BlockIOReadBandwith=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${BLOCK_IO_READ_BANDWITH}" ]
	then
		BLOCK_IO_READ_BANDWITH="BlockIOReadBandwith=${BLOCK_IO_READ_BANDWITH}"
		SET_PROPERTY="true"
	fi

	BLOCK_IO_WEIGHT="$(awk -F= '/^BlockIOWeight=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${BLOCK_IO_WEIGHT}" ]
	then
		BLOCK_IO_WEIGHT="BlockIOWeight=${BLOCK_IO_WEIGHT}"
		SET_PROPERTY="true"
	fi

	BLOCK_IO_WRITE_BANDWITH="$(awk -F= '/^BlockIOWriteBandwith=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${BLOCK_IO_WRITE_BANDWITH}" ]
	then
		BLOCK_IO_WRITE_BANDWITH="BlockIOWriteBandwith=${BLOCK_IO_WRITE_BANDWITH}"
		SET_PROPERTY="true"
	fi

	CPU_QUOTA="$(awk -F= '/^CPUQuota=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${CPU_QUOTA}" ]
	then
		CPU_QUOTA="CPUQuota=${CPU_QUOTA}"
		SET_PROPERTY="true"
	fi

	CPU_SHARES="$(awk -F= '/^CPUShares=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${CPU_SHARES}" ]
	then
		CPU_SHARES="CPUShares=${CPU_SHARES}"
		SET_PROPERTY="true"
	fi

	MEMORY_LIMIT="$(awk -F= '/^MemoryLimit=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${MEMORY_LIMIT}" ]
	then
		MEMORY_LIMIT="MemoryLimit=${MEMORY_LIMIT}"
		SET_PROPERTY="true"
	fi

	TASKS_MAX="$(awk -F= '/^TasksMax=/ { print $2 }' ${CONFIG}/${NAME}.conf)"

	if [ -n "${TASKS_MAX}" ]
	then
		TASKS_MAX="TasksMax=${TASKS_MAX}"
		SET_PROPERTY="true"
	fi
fi

case "${SYSTEMCTL}" in
	true)
		systemctl start container@${NAME}.service
		# FIXME start console .. after sleep? + configuration option
		exit 0
		;;
esac

case "${START}" in
	true)
		case "${SET_PROPERTY}" in
			true)
				systemctl --runtime set-property ${NAME} ${BLOCK_IO_DEVICE_WEIGHT} ${BLOCK_IO_READ_BANDWITH} ${BLOCK_IO_WEIGHT} ${BLOCK_IO_WRITE_BANDWITH} ${CPU_QUOTA} ${CPU_SHARES} ${MEMORY_LIMIT} ${TASKS_MAX}
				;;
		esac
		;;

	*)
		# Run
		${SETARCH} systemd-nspawn --keep-unit ${BIND} ${BOOT} ${CAPABILITY} ${DIRECTORY} ${DROP_CAPABILITY} ${MACHINE} ${NETWORK_VETH_EXTRA} ${LINK_JOURNAL} ${REGISTER}
		;;
esac