#!/bin/sh
# Не выпускать браузер, пока показывать некуда и нечего.
#
# Два ожидания, и оба нужны:
#
#  1. Подключённый экран. Композитору нужен CRTC; если телевизор ещё не воткнут
#     или не ответил по HDMI, ядро пишет «Cannot find any crtc or sizes», и
#     запуск проваливается. Плату включают раньше телевизора чаще, чем кажется.
#
#  2. Агент. Иначе первое, что нарисует браузер, — ошибка соединения, а не
#     страница, и человек перед телевизором ничего с этим сделать не может.
#
# Выходим с ошибкой, а не ждём вечно: юнит перезапустится и попробует снова.
# Лимит перезапусков у него снят, так что попытки не кончатся.
set -eu

PORT="${SCREEN_PORT:-3000}"
TIMEOUT="${DASHDASH_WAIT_TIMEOUT:-120}"
BOOT_PAGE="${DASHDASH_BOOT_PAGE:-/usr/local/share/dashdash/boot.html}"
BOOT_PID=""

display_connected() {
	for status in /sys/class/drm/card*-*/status; do
		[ -f "$status" ] || continue
		[ "$(cat "$status" 2>/dev/null)" = "connected" ] && return 0
	done
	return 1
}

i=0
while [ "$i" -lt "$TIMEOUT" ]; do
	if display_connected; then
		break
	fi
	i=$((i + 1))
	sleep 1
done
if ! display_connected; then
	echo "dashdash: за ${TIMEOUT}с не появилось ни одного подключённого экрана" >&2
	exit 1
fi

# Экран есть, агента ещё нет — и это та самая пара минут, в которые человек
# перед телевизором видит чёрный прямоугольник и решает, что устройство
# сломано. Показываем заставку тем же киоском, только со страницы с диска:
# сети на этом этапе может не быть вовсе.
#
# Запускаем через setsid, отдельной группой процессов, и это не мелочь. Раньше
# здесь был обычный запуск, а гасили заставку через `kill "$BOOT_PID"` — то
# есть убивали оболочку, но не её детей. Веб-процесс браузера при этом
# оставался жив, держал открытыми дескрипторы /dev/dri (их у него восемь), и
# настоящий киоск не мог получить DRM master: «Device or resource busy».
# systemd писал «Found left-over process … Ignoring» и спокойно запускал
# композитор, которому уже не досталось экрана. Отказ тихий: юнит «работает»,
# на телевизоре пусто.
show_boot_screen() {
	[ -f "$BOOT_PAGE" ] || return 0
	command -v cage >/dev/null 2>&1 || return 0
	command -v cog >/dev/null 2>&1 || return 0
	setsid cage -- cog --platform=wl "file://${BOOT_PAGE}" >/dev/null 2>&1 &
	BOOT_PID=$!
}

# Жив ли процесс заставки.
boot_screen_alive() {
	[ -n "$BOOT_PID" ] || return 1
	kill -0 "$BOOT_PID" 2>/dev/null
}

# Никто больше не держит видеокарту?
#
# Проверяем по факту, а не по таймеру: ядро отпускает мастера не в момент
# сигнала, а когда закрыт последний дескриптор, и «подождать секунду» —
# это гадание, которое однажды не угадает.
drm_is_free() {
	for proc in /proc/[0-9]*; do
		ls -l "$proc/fd" 2>/dev/null | grep -q 'dri/card' && return 1
	done
	return 0
}

# Заставку надо не просто убить, а дождаться, пока карта освободится.
hide_boot_screen() {
	[ -n "$BOOT_PID" ] || return 0
	# Минус перед номером — вся группа: оболочка, композитор и все процессы
	# браузера. Именно ради этого заставка и запускалась через setsid.
	kill -TERM "-$BOOT_PID" 2>/dev/null || true
	wait "$BOOT_PID" 2>/dev/null || true
	waited=0
	while [ "$waited" -lt 10 ]; do
		drm_is_free && break
		waited=$((waited + 1))
		sleep 1
	done
	if ! drm_is_free; then
		# Не ушли по-хорошему — добиваем. Экран важнее вежливости.
		kill -KILL "-$BOOT_PID" 2>/dev/null || true
		sleep 1
	fi
	BOOT_PID=""
}

trap hide_boot_screen EXIT INT TERM

show_boot_screen

i=0
while [ "$i" -lt "$TIMEOUT" ]; do
	if curl -fsS -o /dev/null "http://127.0.0.1:${PORT}/health"; then
		hide_boot_screen
		exit 0
	fi

	# При холодном старте cog часто не переживает первую попытку: ядро только
	# что подняло DRM, разъём ещё «дребезжит», и процесс умирает молча. Один
	# запуск на этом и заканчивался — заставка появлялась только при
	# перезапуске сервисов вручную, когда всё уже прогрето. Поэтому пробуем
	# снова, не чаще раза в пять секунд, чтобы не устроить круг перезапусков.
	if ! boot_screen_alive && [ $((i % 5)) -eq 0 ]; then
		BOOT_PID=""
		show_boot_screen
	fi

	i=$((i + 1))
	sleep 1
done

echo "dashdash: агент не ответил на :${PORT} за ${TIMEOUT}с" >&2
exit 1
