#!/bin/sh
# rikkiti-boot-health — the un-brickable boot (docs/47 Layer A).
#
# Runs once per boot (systemd oneshot, Before=boot-complete.target). It answers one
# question: did graphics acceleration actually come up? A kernel/driver update that
# leaves the GPU with no working driver — the classic NVIDIA-DKMS-fail black screen —
# is caught here and AUTOMATICALLY reverted to the last-good boot. Repeated failure
# drops to a guaranteed-usable software-render "safe graphics" entry, so a user is
# never stranded at a black screen.
#
#   healthy -> reset the failure counter, bless this boot, exit 0.
#   broken  -> bump the counter and reboot to fall back:
#                < MAX : one-shot the last-good entry (a consumed candidate one-shot
#                        already lands on the good default; this covers the case where
#                        the *default itself* regressed), then reboot.
#                >= MAX: one-shot the SAFE GRAPHICS entry (software render, always
#                        usable) so recovery is guaranteed, then reboot.
#
# The revert is EXPLICIT (systemd-boot's implicit "pick the next good entry" does NOT
# fire on its own — verified on real hardware, twiglet 2026-07-12). We choose the
# target with `bootctl set-oneshot` and reboot. GPU-accel detection also drives the
# soft "you're on software rendering" warning (comp-side); RDP/virtual/VM boots and an
# intentional `rikkiti.safe=1` boot are never treated as broken.
set -eu

ESP=/boot/efi
TRIES_FILE="$ESP/rikkiti/health-tries"
LASTGOOD_FILE="$ESP/rikkiti/last-good"      # entry id (with .conf) written by the kernel hook
SAFE_ENTRY="rikkiti-safe.conf"
MAX_TRIES=3
DRY=0; [ "${1:-}" = "--dry-run" ] && DRY=1

HLOG=/var/log/rikkiti-boot-health.log   # persistent forensic trail (survives the revert reboot)
log(){
	echo "rikkiti-boot-health: $*"
	[ "$DRY" = 1 ] && return 0
	logger -t rikkiti-health "$*" 2>/dev/null || true
	{ mkdir -p /var/log 2>/dev/null; printf '%s %s\n' "$(date -Iseconds 2>/dev/null || date)" "$*" >> "$HLOG"; } 2>/dev/null || true
}

# ★ SAFETY GATE — recovery reverts by driving systemd-boot (`bootctl set-oneshot`). On
# ANY other bootloader (e.g. the legacy Calamares/GRUB installs) this MUST do nothing —
# never reboot, or it could loop a box it cannot actually revert. Recovery there is the
# bootloader's own previous-kernel menu. This makes the deb safe to push to every box.
is_systemd_boot(){
	command -v bootctl >/dev/null 2>&1 && bootctl is-installed >/dev/null 2>&1 && [ -d /boot/efi/loader/entries ]
}
if ! is_systemd_boot; then
	log "not a systemd-boot install — boot-health is inert here (recovery = the bootloader's own kernel menu)"
	[ "$DRY" = 1 ] && echo "DRY: non-systemd-boot host -> no-op (safe on legacy GRUB boxes)"
	exit 0
fi

# --- brick detection (DRM level, runs early, no compositor needed) ---
# The ONLY thing that counts as "broken" for an auto-revert is: there is NO working
# display driver at all (the NVIDIA-DKMS-fail black screen). Everything that yields a
# usable display — a hardware GPU, a virtual GPU, a software/framebuffer driver — is OK.
# We deliberately do NOT whitelist driver names: that would miss VM GPUs (vboxvideo,
# bochs, qxl, hyperv_drm, cirrus, ast, mgag200, simpledrm, vkms, …) and reboot-loop a
# tester's VM. "Is it hardware-ACCELERATED?" is a separate, notify-only question.
gpu_ok(){
	# Intentional software/safe boot is never a failure.
	grep -q 'rikkiti.safe=1' /proc/cmdline 2>/dev/null && return 0
	# ANY DRM card with a driver bound = a working display path (hardware OR virtual/software).
	for dev in /sys/class/drm/card[0-9]*/device/driver; do [ -e "$dev" ] && return 0; done
	# A DRM card node or a framebuffer also means there IS a display.
	for c in /dev/dri/card[0-9]*; do [ -e "$c" ] && return 0; done
	[ -e /dev/fb0 ] && return 0
	# In a VM/container, software rendering with no DRM is normal — NEVER auto-revert a VM
	# (reboot-looping a tester's VM is unacceptable, and a revert can't summon a virtual
	# display anyway). This is what makes Rikkiti safe to evaluate in VirtualBox/QEMU/etc.
	if command -v systemd-detect-virt >/dev/null 2>&1 && systemd-detect-virt --quiet 2>/dev/null; then return 0; fi
	# Bare metal, a display controller EXISTS in hardware, but no driver bound anywhere
	# -> genuinely BRICKED (the case we exist to recover from).
	if command -v lspci >/dev/null 2>&1 && \
	   lspci 2>/dev/null | grep -qiE 'VGA compatible controller|3D controller|Display controller'; then
		return 1
	fi
	return 0
}

# --- diagnostics (shown in --dry-run) ---
gpu_report(){
	echo "  cmdline: $(cat /proc/cmdline 2>/dev/null)"
	echo "  bound DRM drivers:"
	for dev in /sys/class/drm/card[0-9]*/device/driver; do
		[ -e "$dev" ] || continue
		echo "    $(dirname "$(dirname "$dev")" | sed 's#.*/##') -> $(basename "$(readlink -f "$dev" 2>/dev/null)")"
	done
	command -v lspci >/dev/null 2>&1 && echo "  display controllers:" && \
		lspci 2>/dev/null | grep -iE 'VGA compatible controller|3D controller|Display controller' | sed 's/^/    /'
}

# Give a slow-loading driver (DKMS module, late modeset) up to ~16s to bind before
# judging — a not-yet-ready GPU must not be mistaken for a broken one.
healthy=0
if [ "$DRY" = 1 ]; then
	gpu_ok && healthy=1
else
	i=0
	while [ "$i" -lt 8 ]; do
		if gpu_ok; then healthy=1; break; fi
		sleep 2; i=$((i + 1))
	done
fi

if [ "$healthy" = 1 ]; then
	log "graphics OK — accelerated GPU driver bound (or software-safe boot)"
	if [ "$DRY" = 1 ]; then echo "DRY: healthy -> would reset $TRIES_FILE to 0 and bless this boot"; gpu_report; exit 0; fi
	echo 0 > "$TRIES_FILE" 2>/dev/null || true
	mkdir -p /run/rikkiti 2>/dev/null || true; touch /run/rikkiti/graphics-healthy 2>/dev/null || true
	exit 0
fi

# --- broken ---
tries=$(cat "$TRIES_FILE" 2>/dev/null || echo 0); tries=$((tries + 1))
target="$SAFE_ENTRY"
lg=$(cat "$LASTGOOD_FILE" 2>/dev/null || echo "")
if [ "$tries" -lt "$MAX_TRIES" ] && [ -n "$lg" ]; then target="$lg"; fi
log "GRAPHICS BROKEN — no GPU driver bound (failure #$tries); reverting via one-shot to '$target'"
if [ "$DRY" = 1 ]; then echo "DRY: broken -> tries would become $tries; would 'bootctl set-oneshot $target' then reboot"; gpu_report; exit 1; fi
echo "$tries" > "$TRIES_FILE" 2>/dev/null || true
bootctl set-oneshot "$target" 2>/dev/null || true
sync; sleep 2
systemctl reboot
exit 1
