#!/bin/sh

set -e

PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin

export DEBIAN_FRONTEND=noninteractive

if [ -f /etc/pantin-apply.env ]; then
	. /etc/pantin-apply.env
fi

fatal() {
	echo "$(basename $0): $1" >&2
	exit 1
}

unattended_upgrade_lock() {
	# Steal the lock from the Debian unattended-upgrades utility,
	# which uses fcntl(). This is to avoid having our manifests
	# updates while we're running.
	perl -MFcntl -e \
		'open(LK,">","/var/run/unattended-upgrades.lock") ||
		die("open: $!");
		$p = pack("ssqql", F_WRLCK, SEEK_SET, 0, 0, 0);
		fcntl(LK, F_SETLKW, $p) || die("fcntl: $!\n");
		sleep(3600);' &
	trap "kill $!" EXIT
}

pre_puppet_tasks() {
	if [ "$platform" = "Linux" -a -x /usr/bin/apt-get ]; then
		unattended_upgrade_lock
		apt-get update \
			-o Dir::Etc::sourcelist="sources.list.d/minmaxiste.sources" \
			-o Dir::Etc::sourceparts="-" \
			-o APT::Get::List-Cleanup="0"
		apt-get install -qy pantin-manifests
	else
		pkg_add -xI https://repo-openbsd.minmaxiste.com/pantin-manifests >/dev/null
	fi
}

flock() {
	# Linux has an 'flock' shell utility, but use perl
	# for portability on *BSD.
	# We discard flock() errors since it should mean another instance
	# is running.
	if [ $ignore_lock = "true" ]; then
		shift
		$@
	else
		perl -MFcntl=:flock -e \
			'$lk=shift; open(LK,">",$lk) || die($!);
			flock(LK,LOCK_EX|LOCK_NB) || exit(0);
			system(join(" ",@ARGV));' $@
	fi
}

run_puppet() {
	noop="$1"
	extra_modules="$2"
	modules='$basemodulepath'
	if [ ! -z "$extra_modules" ]; then
		modules="$extra_modules:$modules"
	fi

	if tty -s; then
		[ $skip_manifests_update = "false" ] && pre_puppet_tasks
		flock $apply_lock puppet apply $sitepp --test \
			--modulepath="'$modules'" $extra_args $noop $verbose
	else
		[ $skip_manifests_update = "false" ] && pre_puppet_tasks
		flock $apply_lock puppet apply $sitepp \
			--modulepath="'$modules'" $extra_args $noop $verbose >/dev/null
	fi

	return $?
}

usage() {
	echo "Usage: $(basename $0) [-h] [-f] [-t <interval in minutes>]"
	echo "       -h              Help"
	echo "       -f              Force run, disregarding the minimum interval"
	echo "       -n              Pass --noop to puppet"
	echo "       -v              Pass --verbose and --debug to puppet"
	echo "       -S              Skip updating manifests from repo"
	echo "       -K              Ignore the apply lock and run anyway"
	echo "       -t <interval>   Wait this many minutes before next run"
	echo "       -s <path>       Path to site.pp"
	echo "       -m <modules>    Colon-separate extra module paths"
	echo "       -P <seconds>    Hold the Puppet apply lock"
}

args=`getopt fnvht:s:m:a:P:SK $*`
if [ $? -ne 0 ]; then
	usage
	exit 2
fi

set -- $args
force="false"
extra_modules=""
extra_args=""
noop=""
verbose=""
interval=60
ignore_lock="false"
pause_time=""
skip_manifests_update="false"

while [ $# -ne 0 ]; do
	case "$1" in
		-f)
			force="true"
			shift
			;;
		-h)
			usage
			exit 0
			;;
		-n)
			noop="--noop"
			skip_manifests_update="true"
			shift
			;;
		-S)
			skip_manifests_update="true"
			shift
			;;
		-K)
			ignore_lock="true"
			shift
			;;
		-v)
			verbose="--verbose --debug"
			shift
			;;
		-m)
			extra_modules="$2"
			shift
			shift
			;;
		-a)
			extra_args="$2"
			shift
			shift
			;;
		-t)
			interval="$2"
			shift
			shift
			;;
		-s)
			sitepp="$2"
			shift
			shift
			;;
		-P)
			pause_time="$2"
			shift
			shift
			;;
		--)
			shift
			break
			;;
	esac
done

[ `whoami` = root ] || fatal "must be run as root"

if [ ! -r /var/cache/pantin-apply ]; then
	puppet config print \
		codedir default_manifest lastrunreport | \
		tr -d ' ' > /var/cache/pantin-apply
fi

. /var/cache/pantin-apply

apply_lock=/var/run/pantin-apply.lock
sitepp=$codedir/$default_manifest/site.pp

platform=`uname -s`
if [ "$platform" = "Linux" ]; then
	distro=`lsb_release -si`
fi

if [ ! -z "$pause_time" ]; then
	[ "$ignore_lock" = "true" ] && fatal "cannot hold the lock if we ignore it"
	echo "Holding Puppet apply lock for $pause_time seconds"
	flock $apply_lock /bin/sleep $pause_time
	echo "Releasing Puppet apply lock"
	exit 0
fi

st=0

if [ "$force" = "true" -o ! -f "$lastrunreport" \
    -o -z "$(find $lastrunreport -mmin -${interval})" ]; then
	run_puppet "$noop" "$extra_modules"
	st=$?
fi

exit $st
