#!/bin/sh
# Writes Petitboot's boot menu, /etc/kboot.conf (the ps3-boot package; run by the kernel packages
# when they're installed or removed). One entry per kernel in /boot, the newest release first and
# the default, so Petitboot boots it after its countdown and lists the others to pick from.
#
# PS3_DEFAULT in /etc/default/ps3-boot (a release) pins the default to that kernel while it is
# installed, e.g. a known-good one until a new kernel has booted once; PS3_DEFAULT_FLAVOUR picks
# the newest kernel of a flavour: release (a release without a -debug suffix, e.g. 6.12.111+ps3.N)
# or debug (6.12.111+ps3.N-debug<stamp>). The flavour names before 2026-10-16 still work:
# ps3 (then the debug kernel) as debug, ps3-release as release.
#
# An entry's command line: this installation's root and extras (/etc/default/ps3-boot) plus the
# kernel's own arguments, which its package ships as /usr/lib/ps3/boot/<release>.args (kernels
# without that file get PS3_LEGACY_ARGS).
#
# Extra entries (e.g. the SSD root's, docs/GAMEOS-HDD.md "Phase B"): the "label='image args'" lines
# of every /etc/kboot.d/*.conf, in file name order, come after the kernels' entries as written, so a
# kernel install or removal keeps them. Left out, as a comment in kboot.conf and a message: a label
# that is already taken (Petitboot keys its default on the label), a line that isn't an entry, and an
# entry whose image or initrd= is missing on this disk (paths without "device:" are looked up here).
# Petitboot ignores the default= line (docs/GAMEOS-HDD.md "Petitboot's behaviour"); it is kept for
# the record. PS3_BOOT_ROOT: a prefix for every file (host tests only).
set -e
R=${PS3_BOOT_ROOT:-}
DEFAULTS=$R/etc/default/ps3-boot
CONF=$R/etc/kboot.conf

# First run on an installation from before ps3-boot: keep what the running kernel booted with
if [ ! -e "$DEFAULTS" ]; then
	root=$(tr ' ' '\n' < $R/proc/cmdline | grep -E '^(root|rootfstype|nfsroot)=|^(ro|rw|rootwait)$' | xargs)
	extra=$(tr ' ' '\n' < $R/proc/cmdline | grep -E '^(ip|netconsole)=' | xargs)
	cat > "$DEFAULTS" <<EOF
# Petitboot boot menu settings (ps3-update-kboot writes /etc/kboot.conf from them)
PS3_ROOT_ARGS="$root"
PS3_EXTRA_ARGS="$extra"
PS3_LEGACY_ARGS="video=ps3fb:mode:133 consoleblank=0 ps3rsx_gart=32M console=tty1 loglevel=4"
EOF
fi
. "$DEFAULTS"

# newest first, in Debian's kernel version order (linux-base), which knows about ~ and +
# dpkg's temporary names during an upgrade (vmlinux-<release>.dpkg-tmp, .dpkg-new, ...) are not kernels
releases=$(ls $R/boot/vmlinux-* 2>/dev/null | grep -v '\.dpkg-[a-z]*$' | sed "s|^$R/boot/vmlinux-||")
if command -v linux-version >/dev/null; then
	releases=$(echo "$releases" | linux-version sort --reverse)
else
	releases=$(echo "$releases" | sort -rV)
fi
# kernels with their GPU driver (ps3rsx-modules-<release>) first: one without it boots to no desktop
# (2026-10-27: a new kernel published before its module), so it is listed but never the first entry,
# the one Petitboot boots; with no module anywhere (host tests, a system without ps3rsx) the order stays
nogpu=
for r in $releases; do [ -e "$R/lib/modules/$r/updates/ps3rsx.ko" ] || nogpu="$nogpu $r"; done
if [ -n "$nogpu" ] && [ "$(echo $nogpu | wc -w)" -lt "$(echo $releases | wc -w)" ]; then
	releases=$(for r in $releases; do case " $nogpu " in *" $r "*) ;; *) echo "$r" ;; esac; done
		for r in $nogpu; do echo "$r"; done)
	echo "ps3-update-kboot: no GPU driver (ps3rsx) for$nogpu: listed after the kernels that have it"
fi
kernels=$(echo "$releases" | sed '/^$/d; s|^|/boot/vmlinux-|')
if [ -z "$kernels" ]; then
	# no kernel left: no entries pointing at files that are gone
	echo "# Petitboot boot menu, written by ps3-update-kboot: no kernels installed" > "$CONF.new"
	mv "$CONF.new" "$CONF"
	echo "ps3-update-kboot: no kernels in /boot"
	exit 0
fi
tmp=$(mktemp "$CONF.XXXXXX")
echo "# Petitboot boot menu, written by ps3-update-kboot: newest kernel first" > "$tmp"
default=
labels=" "
for k in $kernels; do
	release=${k#/boot/vmlinux-}
	if [ -e "$R/usr/lib/ps3/boot/$release.args" ]; then
		args=$(cat "$R/usr/lib/ps3/boot/$release.args")
	else
		args=$PS3_LEGACY_ARGS
	fi
	[ -n "$default" ] || default=$release
	# one space between the arguments: Petitboot passes an empty one on as "(null)"
	echo "$release='$(printf '%s' "$k $PS3_ROOT_ARGS $PS3_EXTRA_ARGS $args" | tr -s ' ' | sed 's/ $//')'" >> "$tmp"
	labels="$labels$release "
done

# the extra entries from /etc/kboot.d
extras=0
skip() {	# skip FILE WHY: a comment in kboot.conf, a message
	echo "# $1: left out: $2" >> "$tmp"
	echo "ps3-update-kboot: $1: left out: $2" >&2
}
for f in "$R"/etc/kboot.d/*.conf; do
	[ -f "$f" ] || continue
	name=${f#"$R"}
	while IFS= read -r line || [ -n "$line" ]; do
		case $line in ''|'#'*) continue ;; esac
		label=${line%%=*}
		case $label in
		"$line"|''|*[!A-Za-z0-9_.+-]*) skip "$name" "not a label='image args' entry: $line"; continue ;;
		default|delay|message|timeout|initrd|root|video) skip "$name" "a global setting, not an entry: $line"; continue ;;
		esac
		case $labels in *" $label "*) skip "$name" "label $label is already taken"; continue ;; esac
		val=${line#*=}
		val=${val#\'}; val=${val%\'}; val=${val#\"}; val=${val%\"}
		image=${val%% *}
		initrd=$(echo " $val " | sed -n 's/.* initrd=\([^ ]*\) .*/\1/p')
		missing=
		for p in $image $initrd; do
			case $p in *:*) ;; *) [ -e "$R$p" ] || missing="$missing $p" ;; esac
		done
		if [ -n "$missing" ]; then skip "$name" "$label: missing$missing"; continue; fi
		echo "$line" >> "$tmp"
		labels="$labels$label "
		extras=$((extras + 1))
	done < "$f"
done

# PS3_DEFAULT_FLAVOUR (release or debug): the newest kernel of that flavour, if one is installed;
# PS3_DEFAULT (a release) wins over it
flavour=${PS3_DEFAULT_FLAVOUR:-}
case "$flavour" in
	ps3) flavour=debug ;;
	ps3-release) flavour=release ;;
	release|debug|'') ;;
	*) echo "ps3-update-kboot: unknown PS3_DEFAULT_FLAVOUR=$flavour (release or debug), ignored" >&2; flavour= ;;
esac
if [ -n "$flavour" ]; then
	for k in $kernels; do
		case "${k#/boot/vmlinux-}" in
			*-debug*) [ "$flavour" = debug ] || continue ;;
			*) [ "$flavour" = release ] || continue ;;
		esac
		default=${k#/boot/vmlinux-}; break
	done
fi
if [ -n "${PS3_DEFAULT:-}" ] && [ -e "$R/boot/vmlinux-$PS3_DEFAULT" ]; then
	default=$PS3_DEFAULT
fi
echo "default=$default" >> "$tmp"
chmod 644 "$tmp"
mv "$tmp" "$CONF"
echo "ps3-update-kboot: $(echo "$kernels" | wc -l) kernel(s), $extras extra entr$([ $extras = 1 ] && echo y || echo ies) (/etc/kboot.d), default $default"
# Where Petitboot reads this system's menu: PS3_BOOT_TARGET (/etc/default/ps3-boot) if set, else what
# booted it. The generic initramfs (the internal install, booted from GameOS's dev_hdd1) logs
# "initramfs kind=generic" in /run/ps3ssd/init.log: then dev_hdd1 gets the kernels and the menu
# (ps3-hdd1-boot, docs/GAMEOS-HDD.md "The internal install"). Anything else (a USB root, an image
# build, the SSD root whose kernel comes from the USB disk) keeps this root's /etc/kboot.conf only.
target=${PS3_BOOT_TARGET:-}
if [ -z "$target" ]; then
	if grep -q 'initramfs kind=generic' "$R/run/ps3ssd/init.log" 2>/dev/null; then target=hdd1; else target=root; fi
fi
case $target in
hdd1)
	echo "ps3-update-kboot: this system boots from dev_hdd1: its menu there too"
	${PS3_HDD1_BOOT:-$R/usr/sbin/ps3-hdd1-boot} "$default" $releases ;;
root) ;;
*) echo "ps3-update-kboot: unknown PS3_BOOT_TARGET=$target (hdd1 or root), only /etc/kboot.conf written" >&2 ;;
esac
