#!/bin/sh
# scraplinux-boot-generations - put the recent generations in the boot menu
#
#   scraplinux-boot-generations              rewrite the generation entries
#   scraplinux-boot-generations --keep N     show the newest N (default from config)
#
# Writes one boot entry per recent generation, so a configuration that will
# not boot can be escaped from the boot menu rather than a rescue USB:
#
#   ScrapLinux
#   ScrapLinux (generation 4)
#   ScrapLinux (generation 3)
#   ...
#
# Booting a generation entry passes scraplinux.generation=N on the kernel command
# line. The generation service sees it and reconciles the system to that generation
# before anything else starts, so choosing it from the menu is the same as
# having run `scraplinux-generation switch N` beforehand.
#
# This only rewrites the block between the two marker comments below, so
# whatever else is in limine.conf - other operating systems, hand-added
# entries - is left exactly as it was.
#
# shellcheck shell=sh disable=SC2039

set -u
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

GENDIR=/var/lib/scraplinux/generations
BEGIN="### scraplinux-generations begin"
END="### scraplinux-generations end"

KEEP=""
case "${1:-}" in
--keep) KEEP=${2:-5} ;;
-h|--help) sed -n '2,20p' "$0" | sed 's/^# \?//'; exit 0 ;;
esac

# A_GENERATIONS in install.conf decides how many appear; 0 turns them off.
if [ -z "$KEEP" ]; then
	KEEP=5
	[ -f /etc/scraplinux/install.conf ] && \
		KEEP=$(awk -F= '/^[[:space:]]*A_GENERATIONS=/{gsub(/[^0-9]/,"",$2); print $2}' \
			/etc/scraplinux/install.conf | tail -1)
	[ -n "$KEEP" ] || KEEP=5
fi

find_bootdir() {
	for d in /boot/efi /boot/EFI /boot; do
		[ -f "$d/limine.conf" ] && { echo "$d"; return 0; }
	done
	for d in /boot/efi /boot; do
		[ -d "$d" ] && { echo "$d"; return 0; }
	done
	return 1
}

BOOTDIR=$(find_bootdir) || { echo "scraplinux-boot-generations: no boot directory found" >&2; exit 1; }
CONF="$BOOTDIR/limine.conf"
[ -f "$CONF" ] || { echo "scraplinux-boot-generations: no $CONF - run scraplinux-rebuild first" >&2; exit 1; }

# Reuse the exact kernel path and command line the main entry already uses,
# so a generation entry differs from it only by scraplinux.generation=N. Parsing
# it back out of limine.conf rather than recomputing it means these cannot
# drift apart from whatever scraplinux-rebuild decided.
kpath=$(awk '/^[[:space:]]*kernel_path:/{sub(/^[^:]*:[[:space:]]*/,""); print; exit}' "$CONF")
kcmd=$(awk '/^[[:space:]]*kernel_cmdline:/{sub(/^[^:]*:[[:space:]]*/,""); print; exit}' "$CONF")
# Only the FIRST entry's module_path line(s) - not a bare pattern scan of the
# whole file, which would also match lines already written into the
# generations block below and re-multiply them every time this runs.
mods=$(awk '/^\//{if(e)exit;e=1;next} e&&/^$/{exit} e&&/^[[:space:]]*module_path:/{print}' "$CONF")

[ -n "$kpath" ] || { echo "scraplinux-boot-generations: could not read kernel_path from $CONF" >&2; exit 1; }

ids=""
if [ -d "$GENDIR" ]; then
	for d in "$GENDIR"/[0-9]*; do
		[ -d "$d" ] || continue
		n=$(basename "$d")
		case "$n" in *[!0-9]*) continue ;; esac
		ids="$ids $n"
	done
fi
ids=$(printf '%s\n' $ids | sort -rn | head -n "$KEEP")

tmp="$CONF.scraplinux-gen.$$"
# Everything before the marker block, unchanged.
awk -v b="$BEGIN" '$0==b{exit} {print}' "$CONF" >"$tmp"

if [ "$KEEP" -gt 0 ] && [ -n "$ids" ]; then
	{
		printf '%s\n' "$BEGIN"
		printf '# Rewritten by scraplinux-boot-generations - edits here are lost.\n'
		for id in $ids; do
			desc=$(cat "$GENDIR/$id/description" 2>/dev/null)
			date=$(cat "$GENDIR/$id/date" 2>/dev/null)
			printf '\n/ScrapLinux (generation %s)\n' "$id"
			printf '    comment: %s  %s\n' "$date" "$desc"
			printf '    protocol: linux\n'
			printf '    kernel_path: %s\n' "$kpath"
			printf '    kernel_cmdline: %s scraplinux.generation=%s\n' "$kcmd" "$id"
			[ -n "$mods" ] && printf '%s\n' "$mods"
		done
		printf '%s\n' "$END"
	} >>"$tmp"
fi

# Everything after the marker block, unchanged. If there was no block before,
# this prints nothing and the file simply gains one.
awk -v e="$END" 'f{print} $0==e{f=1}' "$CONF" >>"$tmp"

mv -f "$tmp" "$CONF"

# Older Limine reads limine.cfg; keep the two in step the same way
# scraplinux-rebuild does.
if [ -f "$BOOTDIR/limine.cfg" ]; then
	sed 's|^/ScrapLinux|:ScrapLinux|; s|^    \([a-z_]*\): |    \1=|' "$CONF" \
		>"$BOOTDIR/limine.cfg" 2>/dev/null || :
fi

n=$(printf '%s\n' $ids | grep -c . 2>/dev/null || echo 0)
printf 'boot menu updated: %s generation entries\n' "$n"
