#!/bin/sh

PODMAN_ROOT=
PUB_KEY=
SHOW_DF=
SHOW_DU=
if [ -z "$SWUPDATE_USB_SWU" ]; then
	ARCHIVE_DIR=/mnt
else
	ARCHIVE_DIR="${SWUPDATE_USB_SWU%/*}"
	[ -n "$ARCHIVE_DIR" ] || ARCHIVE_DIR=/
fi

[ -z "${CONTAINERS_CONF+x}" ] && \
	[ -e /etc/atmark/containers.conf ] && \
	export CONTAINERS_CONF=/etc/atmark/containers.conf
[ -z "${CONTAINERS_STORAGE_CONF+x}" ] && \
	[ -e /etc/atmark/containers_storage.conf ] && \
	export CONTAINERS_STORAGE_CONF=/etc/atmark/containers_storage.conf
[ -z "${CONTAINERS_REGISTRIES_CONF+x}" ] && \
	[ -e /etc/atmark/containers_registries.conf ] && \
	export CONTAINERS_REGISTRIES_CONF=/etc/atmark/containers_registries.conf

error() {
	printf -- "----------------------------------------------\n" >&2
	printf -- "/!\ %s\n" "$@" >&2
	printf -- "----------------------------------------------\n" >&2
	cleanup
	if [ -n "$SHOW_DF" ]; then
		# btrfs only updates df in background or on sync
		sync -f /var/app/volumes
		info "Free space available:"
		stdout_info df -h /var/app/volumes
	fi
	if [ -n "$SHOW_DU" ]; then
		info "Installing this file:"
		stdout_info du -h "$SHOW_DU"
	fi
	exit 1
}

warning() {
	case "$SWUPDATE_WARN_FD" in
	4) printf "%s\n" "$@" >&4;;
	*) printf "%s\n" "$@" >&2;;
	esac
}

# info commands duplicated from scripts/common
stdout_info() {
	# this one keeps stdout if unset
	case "$SWUPDATE_INFO_FD" in
	3) "$@" >&3;;
	*) "$@";;
	esac
}

info() {
	stdout_info printf "%s\n" "$@"
}

# options:
# FILTER: run grep -vE on filter and only print non-matches
# NOSTDOUT: drop stdout and only consider stderr (for openssl cms-verify)
info_if_not_empty() {
	local output="${TMPDIR:-/var/tmp}/cmd_output.$$"
	local ret

	if [ -n "$NOSTDOUT" ]; then
		"$@" 2> "$output" >/dev/null
	else
		"$@" > "$output" 2>&1
	fi
	ret=$?

	if [ -n "$FILTER" ]; then
		# can't check for grep error redirecting file as it returns
		# non-zero if no match... hopefully mv will fail then?
		grep -vE "$FILTER" < "$output" > "$output.filter"
		if ! mv "$output.filter" "$output"; then
			echo "Could not filter '$*' output" >&2
			ret=1
		fi
	fi

	if [ -s "$output" ]; then
		info "Command '$*' output:"
		stdout_info cat "$output"
	fi
	rm -f "$output"
	return "$ret"
}

podman_in_root_noredir() {
	command podman --root "$PODMAN_ROOT" \
		--storage-opt additionalimagestore="" \
		"$@"
}

podman_in_root() {
	info_if_not_empty podman_in_root_noredir "$@"
}

usage() {
	echo "Usage: $0 [options] [-l [archive...]|uri...]"
	echo
	echo "Helper to update containers"
	echo
	echo "  -l    Instead of podman pull, use podman load to install update"
	echo "        In this case if no argument is provided tries to load from stdin"
	echo
	echo "Options:"
	echo " --storage <path> Path to podman storage to update"
	echo " --pubkey <key>   Public key used to verify signed image (load with parameter only)"
	echo "                  Setting to empty string disables the check"
}

podman_pull() {
	local uri="$1"
	local image

	image=$(podman_in_root_noredir pull -q "$uri") \
		|| SHOW_DF=1 error "Could not pull $uri"

	if [ -z "$image" ]; then
		info "No image pulled from $uri ?"
		return
	fi

	if [ -n "$force_tag" ]; then
		podman_in_root tag "$image" "$force_tag" \
			|| error "Could not tag $image to $force_tag"
		info "$uri: tagged $image as $force_tag"
	else
		info "$uri: pulled $image"
	fi
}

check_sig() {
	local file="$1"

	FILTER="Verification successful" NOSTDOUT=1 info_if_not_empty \
		openssl cms -verify -inform DER -in "$file.sig" \
				-content "$file" -nosmimecap -no_check_time \
				-binary -CAfile "$PUB_KEY" ||
			error "Signature verification failed"
}

podman_load() {
	local archive

	if [ $# -eq 0 ]; then
		podman_in_root load \
			|| SHOW_DF=1 error "Could not load archive"
	else for archive; do
		case "$archive" in
		/*) ;;
		*) archive="$ARCHIVE_DIR/$archive";;
		esac
		if [ -n "$PUB_KEY" ]; then
			check_sig "$archive"
		fi
		podman_in_root load -i "$archive" \
			|| SHOW_DF=1 SHOW_DU="$archive" \
				error "Could not load $archive"
	done; fi
}

cleanup() {
	if [ -n "$PODMAN_ROOT" ]; then
		# cleanup again afterwards for whoever comes next
		rm -f "$PODMAN_ROOT/libpod/bolt_state.db" "$PODMAN_ROOT/db.sql"
	fi
	if [ -n "$TMPDIR" ] && [ "$TMPDIR" != "/var/tmp" ]; then
		rm -rf "$TMPDIR"
	fi
	trap - EXIT INT QUIT TERM
}

update_containers() {
	local uri tmp

	# find which db to use: we prefer persistent development storage
	# over readonly one as images in ro storage won't be visible
	if [ -z "$PODMAN_ROOT" ]; then
		if grep -q 'containers/storage_readonly' /etc/containers/storage.conf; then
			PODMAN_ROOT=/target/var/lib/containers/storage_readonly
		else
			# note we don't use /target bind: they're identical, but bolt_sate.db
			# has the canonical path
			PODMAN_ROOT=/var/lib/containers/storage
			warning "Updating images on development storage, do not use this mode for production!" \
			       "(Old images removal is conservative, not safe on power loss...)"
		fi
		mountpoint -q "$PODMAN_ROOT" \
			|| error "Trying to use $PODMAN_ROOT which is not mounted"
	fi

	# cleanup podman silly location-binding db before/after using directory.
	[ -z "$(podman ps --root "$PODMAN_ROOT" -qa)" ] \
		|| error "podman state not clean"
	rm -f "$PODMAN_ROOT/libpod/bolt_state.db" "$PODMAN_ROOT/db.sql"

	# podman sometimes leaves lots of data in /var/tmp on errors:
	# force a different TMPDIR and clean up after podman
	mkdir -p "${TMPDIR:-/var/tmp}/scripts"
	tmp=$(mktemp -d "${TMPDIR:-/var/tmp}/scripts/mkswu_podman_target.XXXXXX") \
		|| error "Could not create tmpdir in ${TMPDIR:-/var/tmp}"
	export TMPDIR="$tmp"
	trap cleanup EXIT INT QUIT TERM

	local CMD="$1"
	shift
	case "$CMD" in
	load)
		podman_load "$@"
		;;
	pull)
		local force_tag=""
		if [ "$1" = "--tag" ]; then
			[ $# = 3 ] || error "Usage: pull --tag tag image (single image only)"
			force_tag="$2"
			shift 2
		fi
		for uri do
			podman_pull "$uri"
		done
		;;
	run)
		podman_in_root "$CMD" "$@" \
			|| error "podman $CMD $* failed"
		;;
	*)
		error "Unhandled command $CMD"
		;;
	esac

	cleanup
}

[ $# -lt 1 ] && usage && exit 1

while [ $# -ge 1 ]; do
	case "$1" in
	"--pubkey")
		[ $# -ge 2 ] || error "$1 needs an argument"
		PUB_KEY="$2"
		shift 2
		;;
	"--storage")
		[ $# -ge 2 ] || error "$1 needs an argument"
		PODMAN_ROOT="$2"
		shift 2
		;;
	"--")
		shift
		break
		;;
	"-h"|"--help"|"-"*)
		usage
		exit 0
		;;
	*)
		break
		;;
	esac
done

update_containers "$@"

