#!/bin/bash
PATH=/bin:/usr/bin:/sbin:/usr/sbin

set -e

PKGDIR="/opt/isu"
RUNDIR="/run/isu"
ISUCFG="isu.cfg"

# Public key will be checked only if below variable is set
#PUBKEY="/path/to/publickey.pem"

MY_NAME=$(basename "$0")

if [ "$MY_NAME" = "isu-system-generator" ]; then
	ISU_SERVICES_DIR=system-services
	SERVICES_DIR=/usr/lib/systemd/system
	INSTANCE=system
elif [ "$MY_NAME" = "isu-user-generator" ]; then
	ISU_SERVICES_DIR=user-services
	SERVICES_DIR=/usr/lib/systemd/user
	INSTANCE=user
else
	echo "This generator must be named isu-system-generator or isu-user-generator"
	exit 1
fi

log()
{
	echo "$MY_NAME: $*" >&2
}

install_units()
{
	local srv_path="$1"
	local isu_pkg_name="$2"
	local srv_fname=$(basename $srv_path)
	local new_srv_path="$UNITDIR/$srv_fname"

	rm -f "${new_srv_path}" "${new_srv_path}.tmp"

	cat "$srv_path" > "$new_srv_path".tmp || return 1
	local mount_unit="run-isu-$(systemd-escape ${isu_pkg_name})-rootfs.mount"
	local confd="${new_srv_path}.d"
	mkdir -p "${confd}"

	if [ "$INSTANCE" = "system" ]; then
		cat <<EOF >> "$confd/isu.conf.tmp" || return 1
# Automatically generated by $0
[Unit]
After=${mount_unit}
BindsTo=${mount_unit}
EOF
	else
# User session unit can not depend on system session units, thus we can't really use After=/etc. here.
# However, given that we guarantee that isu .mount units are mounted in local-fs.target, which is part
# of basic.target, it should be enough to depend on the DefaultDependencies logic.
		cat <<EOF >> "$confd/isu.conf.tmp" || return 1
# Automatically generated by $0
[Unit]
DefaultDependencies=yes
EOF
	fi

	# make new unit visible
	mv "${confd}/isu.conf.tmp" "${confd}/isu.conf" || return 1
	if ! mv "${new_srv_path}.tmp" "${new_srv_path}"; then
		mv "${confd}/isu.conf" "${confd}/isu.conf.revert" || :
	fi
	log "Installed $INSTANCE service $srv_path for package $isu_pkg_name"

	return 0
}

setup_isu_run_dir()
{
	local isu_pkg_dir="$1"
	local isu_pkg_name="$2"
	local isu_pkg_run="$RUNDIR/$isu_pkg_name"

	if ! mkdir -p "$isu_pkg_run/rootfs"; then
		log "Unable to create needed directory hierarchy at $isu_pkg_run/rootfs - skipping ISU package"
		return 1
	fi

	if ! install -m0644 -o root -g root --context=_ "$isu_pkg_dir/$ISUCFG" "$isu_pkg_run/$ISUCFG"; then
		log "Unable to setup essential ISU configuration - skipping ISU package"
		return 1
	fi
}

install_mount_unit()
{
	local isu_pkg_name="$1"

	if ! test -d "$RUNDIR/$isu_pkg_name/rootfs"; then return 1; fi

	local mount_unit="run-isu-$(systemd-escape ${isu_pkg_name})-rootfs.mount"
	if [ ! -r "$UNITDIR/$mount_unit" ]; then

		if ! test -r "$i"/rootfs.img; then
			log "Can not access rootfs.img. Skipping $isu_pkg_name"
			return 1
		fi

		# generate mount unit for ISU image and extend the service file to use it
		# if mount unit already exists, it means it's been generated by previous
		# install_mount_unit() invocation - for the same ISU package, but different
		# .service file
		cat <<EOF >> "$UNITDIR/$mount_unit" || return 1
# Automatically generated by $0
[Unit]
DefaultDependencies=no
Before=local-fs.target

[Mount]
What=${PKGDIR}/${isu_pkg_name}/rootfs.img
Where=${RUNDIR}/${isu_pkg_name}/rootfs
EOF
		mkdir -p "$UNITDIR/local-fs.target.wants"
		ln -s "../$mount_unit" "$UNITDIR/local-fs.target.wants/"
		log "Installed $UNITDIR/$mount_unit for package $isu_pkg_name"

	fi

	return 0
}

isu_prepare_system()
{
	if [ "$INSTANCE" != "system" ]; then return 0; fi

	local isu_pkg_dir="$1"
	local isu_pkg_name="$2"

	# verify signature and checksum before considering ISU package for application on the system
	cksum_sign_path="$isu_pkg_dir/checksum.sha256.sign"
	cksum_path="${cksum_sign_path%.sign}"
	if [ "$PUBKEY" ]; then
		if ! openssl dgst -sha256 -verify "$PUBKEY" -signature "$cksum_sign_path" "$cksum_path"; then
			log "Public key verification failed for $cksum_path"
			return 1
		fi
		log "Public key verification succeeded for $isu_pkg_dir"
	fi

	if [ -s "$cksum_path" ]; then
		pushd "$isu_pkg_dir"
		if ! sha256sum -c --status "$cksum_path"; then
			popd
			log "Checksum verification failed for $isu_pkg_dir - skipping ISU package"
			return 1
		fi
		popd
	else
		log "Missing or broken checksum file: $cksum_path - skipping ISU package"
		return 1
	fi

	setup_isu_run_dir "$isu_pkg_dir" "$isu_pkg_name"
	install_mount_unit "$isu_pkg_name"
}

isu_prepare()
{
	local isu_pkg_dir="$1"
	local isu_pkg_name=$(basename "$isu_pkg_dir")

	if ! isu_prepare_system "$isu_pkg_dir" "$isu_pkg_name"; then
		return 1
	fi

	for srv_path in $(compgen -G "${isu_pkg_dir}/${ISU_SERVICES_DIR}/*.service"); do

		if ! test -r "$srv_path"; then
			log "Service file $srv_path not readable. Skipping"
			continue
		fi

		install_units "$srv_path" "$isu_pkg_name"
	done

	for unit_path in $(compgen -G "${isu_pkg_dir}/${ISU_SERVICES_DIR}/*.mount"); do

		if ! test -r "$unit_path"; then
			log "Unit file $unit_path not readable. Skipping"
			continue
		fi
		cp -a "$unit_path" "$UNITDIR/"
	done
}


# Entry point

if [ "$PUBKEY" -a ! -r "$PUBKEY" ]; then
	echo "Public key specified but not readable: $PUBKEY"
	exit 1
fi

if [ -z "$1" ]; then
	echo "Please specify unitdir(s) as decribed in systemd.generator (1 or 3 arguments)"
	exit 1
fi
UNITDIR="$1"

for i in $(compgen -G "$PKGDIR/*"); do
	if ! test -d "$i" -a -r "$i"/isu.cfg; then
		log "Can not access essential ISU package data. Skipping $PKGDIR"
		continue
	fi

	isu_prepare "$i" || :
done
