#!/bin/sh

# by Vladimir Kudrya
# https://github.com/Vladimir-csp/
#
# This script 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 2 of the License, or
# (at your option) any later version. See <http://www.gnu.org/licenses/>.

# This is a replacement for original dbus.service script by Thomas Liske <thomas@fiasko-nw.net>
# Dbus dependencies are enumerated dynamically and restarted after dbus and systemd daemon reexec

# delay in semi-interactive mode
DELAY_SECONDS=10

# enable xtrace if we should be verbose
if [ "$NR_VERBOSE" = '1' ]; then
	set -x
fi

# choose interactivity mode
if [ "${DEBIAN_FRONTEND:-other}" = "noninteractive" ]; then
	CONSOLE_MODE=skip
elif [ -t 0 ]; then
	CONSOLE_MODE=interactive
else
	CONSOLE_MODE=delay
fi

if [ "$(id -ru)" != "0" ]; then
	echo "Not root, exiting" >&2
	exit 1
fi

# get init system process name
INIT_EXEC="$(ps -p 1 -o comm=)"

case "$INIT_EXEC" in
systemd | */systemd) true ;;
*)
	echo "Init system is not systemd ($INIT_EXEC), doing nothing"
	exit 0
	;;
esac

# start from the name of the hook, get real unit ID (for example, dbus-broker replaces dbus)
DBUS_SERVICE_NAME=$(
	systemctl show --value -p Id "${0##*/}"
)

# if DM is active, return canonical ID
DISPLAY_MANAGER=$(
	systemctl -q is-active display-manager.service \
		&& systemctl show --value -p Id display-manager.service
)

# get active dependencies
ACTIVE_DEPS=''
while read -r service; do
	case "$service" in
	"$DISPLAY_MANAGER" | dbus.service | "$DBUS_SERVICE_NAME") continue ;;
	*.service)
		case " $ACTIVE_DEPS " in
		*" $service "*) true ;;
		*) ACTIVE_DEPS="${ACTIVE_DEPS}${ACTIVE_DEPS:+ }${service}" ;;
		esac
		;;
	esac
done <<- EOF
	$(
		systemctl list-dependencies -l --reverse --plain --all \
			--type=service \
			--state=active,reloading,failed,activating \
			dbus.socket dbus.service "$DBUS_SERVICE_NAME"
	)
EOF

# get active services with BusName
current_busname=''
current_id=''
while IFS='=' read -r option value; do
	case "${option}" in
	BusName)
		current_busname="$value"
		continue
		;;
	Id)
		current_id="$value"
		continue
		;;
	'')
		if [ -z "$current_busname" ] || [ -z "$current_id" ]; then
			current_busname=''
			current_id=''
			continue
		fi
		service=$current_id
		current_busname=''
		current_id=''
		case "$service" in
		"$DISPLAY_MANAGER" | dbus.service | "$DBUS_SERVICE_NAME") continue ;;
		esac
		case " $ACTIVE_DEPS " in
		*" $service "*) true ;;
		*) ACTIVE_DEPS="${ACTIVE_DEPS}${ACTIVE_DEPS:+ }${service}" ;;
		esac
		;;
	*)
		current_busname=''
		current_id=''
		;;
	esac
done <<- EOF
	$(
		systemctl show --plain --all \
			--type=service \
			--state=active,reloading,failed,activating \
			--property=BusName,Id
	)
	=end
EOF

# get logind users
USERS=''
while read -r _uid user _linger _state; do
	USERS="${USERS}${USERS:+ }${user}"
done <<- EOF
	$(loginctl list-users --no-legend)
EOF

# get current dbus service PIDs
DBUS_PIDS=''
while read -r pid unit; do
	case "$unit" in
	"${DBUS_SERVICE_NAME}") DBUS_PIDS="${DBUS_PIDS}${DBUS_PIDS:+ }${pid}" ;;
	esac
done <<- EOF
	$(ps -eo pid,unit)
EOF

# assemble commands
COMMANDS=''
# terminate users
[ -n "$USERS" ] && COMMANDS="${COMMANDS}${COMMANDS:+ ; }loginctl terminate-user $USERS"
# stop display manager
[ -n "$DISPLAY_MANAGER" ] && COMMANDS="${COMMANDS}${COMMANDS:+ ; }systemctl stop $DISPLAY_MANAGER"
# restart dbus
COMMANDS="${COMMANDS}${COMMANDS:+ ; }sleep 1 ; systemctl restart $DBUS_SERVICE_NAME"
# kill previous dbus pids
[ -n "$DBUS_PIDS" ] && COMMANDS="${COMMANDS}${COMMANDS:+ ; }sleep 1 ; kill $DBUS_PIDS 2>/dev/null"
# restart active dependencies
COMMANDS="${COMMANDS}${COMMANDS:+ ; }sleep 1 ; systemctl restart $ACTIVE_DEPS"
# start display manager
[ -n "$DISPLAY_MANAGER" ] && COMMANDS="${COMMANDS}${COMMANDS:+ ; }systemctl start $DISPLAY_MANAGER"
# normalize to single line
COMMANDS=$(echo "$COMMANDS" | tr '\n' ' ' | tr -s ' ')

shcat() {
	while IFS='' read -r line; do
		printf '%s\n' "$line"
	done
}

# shellcheck disable=SC2086
case "$CONSOLE_MODE" in
interactive) shcat ;;
delay) shcat >&2 ;;
*) shcat > /dev/null ;;
esac <<- EOF

	!!! $DBUS_SERVICE_NAME restart will be performed !!!

	Users to be terminated:
	$(printf '  %s\n' $USERS)

	Services to be restarted:
	$(printf '  %s\n' $ACTIVE_DEPS)

	Display manager to be restarted: ${DISPLAY_MANAGER:-no active DM found}

	Command set to run as transient unit restart-dbus.service:
	$(
		IFS=';'
		# consequent commands have a space after preceding semicolons, compensate for that
		printf '%s' ' '
		printf ' %s\n' $COMMANDS
	)

	Logs can be viewed by: journalctl -u restart-dbus
EOF

# if interactive, ask for continuation
case "$CONSOLE_MODE" in
interactive)
	printf '\n%s' "Press Enter to continue > "
	read -r _PRESSENTER
	;;
delay)
	printf '\n%s\n' "Restart in $DELAY_SECONDS seconds..." >&2
	sleep $DELAY_SECONDS
	echo "Restarting..." >&2
	;;
esac

# run restart sequence as transient unit...
# it is possible to view its output in the log if any:
# journalctl -u restart-dbus
systemd-run -G --unit=restart-dbus --description="Transient dbus restarter" sh -c "$COMMANDS"
