#!/bin/sh
# shellcheck disable=SC1111,SC2086 # deutsche Anführungszeichen; echo ohne Anführung fasst Leerzeichen zusammen
# bashclub-zbm-update: ZFSBootMenu aus dem Paket bashclub-zfsbootmenu auf
# jede ESP des Boot-Pools bringen. Läuft nach jeder Installation bzw.
# Aktualisierung des Pakets (postinst) und wenn sich shim oder Debians
# signiertes GRUB ändern (dpkg-Trigger); der Installer ruft es mit --install.
#
# Je ESP:
#   EFI/bashclub/   shim + mmx64 + ZBM (als grubx64.efi, so lädt shim es)
#   EFI/BOOT/       dasselbe als Rückfall ohne NVRAM-Eintrag
#   EFI/memtest/    shim + Debians signiertes GRUB → memtest86+ (Secure Boot)
#   zfsbootmenu/    Kernel und initramfs von ZBM für syslinux (BIOS)
#   syslinux/syslinux.cfg, memtest/   nur, wenn die ESP für BIOS eingerichtet ist
# Unterscheidet sich das ZBM auf der ESP vom neuen, wandert der alte Stand
# vorher nach EFI/bashclub-backup/ bzw. zfsbootmenu/backup/ und bleibt als
# Booteintrag „bashclub <n> (vorige Version)“ bzw. syslinux-Eintrag
# „backup“ wählbar. Dateien werden erst neben dem Ziel geschrieben und dann
# umbenannt, eine ESP hat nie eine halbe Datei.
#
#   bashclub-zbm-update [--list] [--install] [--bios] [--no-efi-entries] [ESP …]
#     ohne ESP: alle ESPs auf den Platten des Pools mit bootfs
#     --list            nur anzeigen (ESPs, Stand, Booteinträge), nichts ändern
#     --install         Ersteinrichtung: Einträge „bashclub <n>“ an den Anfang
#                       der Bootreihenfolge (sonst kommen neue Einträge ans Ende)
#     --bios            syslinux-Konfiguration schreiben, auch wenn es sie noch
#                       nicht gibt (syslinux --install macht der Installer)
#     --no-efi-entries  keine UEFI-Booteinträge anlegen oder ändern
# Rückgabe: 0 alles aktuell, 1 mindestens eine ESP gescheitert, 2 keine ESP
# aktualisiert, 3 kein Boot-Pool gefunden (z. B. im Container, im Live-System).
set -eu

SRC=${BASHCLUB_ZBM_SRC:-/usr/lib/bashclub-zfsbootmenu}
SHIM=${BASHCLUB_ZBM_SHIM:-/usr/lib/shim}
GRUB_SIGNED=${BASHCLUB_ZBM_GRUB:-/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed}
ESP_TYPE=c12a7328-f81f-11d2-ba4b-00a0c93ec93b
MEMTEST_EFI=memtest86+x64.efi
MEMTEST_BIOS=memtest86+x64.bin

list=0 install=0 bios=0 entries=1
while [ $# -gt 0 ]; do
	case $1 in
	--list) list=1 ;;
	--install) install=1 ;;
	--bios) bios=1 ;;
	--no-efi-entries) entries=0 ;;
	-h | --help) sed -n '3,/^set -eu/{/^set -eu/d;s/^# \{0,1\}//;p}' "$0"; exit 0 ;;
	-*) echo "bashclub-zbm-update: unbekannte Option $1" >&2; exit 2 ;;
	*) break ;;
	esac
	shift
done
[ -d /sys/firmware/efi/efivars ] || entries=0
command -v efibootmgr >/dev/null 2>&1 || entries=0

say() { printf '%s\n' "$*"; }
warn() { printf 'bashclub-zbm-update: %s\n' "$*" >&2; }

# ESPs: alle Partitionen vom Typ ESP auf den Platten des Pools mit bootfs
discover() {
	pool=$(zpool list -H -o name,bootfs 2>/dev/null | awk '$2 != "-" {print $1; exit}')
	[ -n "$pool" ] || return 3
	zpool list -vHPL "$pool" | awk '$1 ~ /^\/dev\// {print $1}' | while read -r dev; do
		disk=$(lsblk -dno PKNAME "$dev" 2>/dev/null) || continue
		[ -n "$disk" ] || continue
		lsblk -lnpo NAME,PARTTYPE "/dev/$disk" | awk -v t=$ESP_TYPE 'tolower($2) == t {print $1}'
	done | sort -u
}

if [ $# -gt 0 ]; then
	esps=$(for e in "$@"; do readlink -f "$e"; done)
else
	set +e
	esps=$(discover)
	rc=$?
	set -e
	if [ $rc = 3 ]; then
		say "Kein Boot-Pool (Pool mit bootfs) gefunden, nichts zu tun."
		exit 3
	fi
fi
[ -n "$esps" ] || { warn "keine ESP auf den Platten des Boot-Pools"; exit 2; }

for f in zfsbootmenu.EFI vmlinuz-bootmenu initramfs-bootmenu.img; do
	[ -f "$SRC/$f" ] || { warn "$SRC/$f fehlt"; exit 2; }
done
for f in shimx64.efi.signed mmx64.efi.signed; do
	[ -f "$SHIM/$f" ] || { warn "$SHIM/$f fehlt (Paket shim-signed)"; exit 2; }
done
memtest=0
[ -f "$SRC/$MEMTEST_EFI" ] && [ -f "$GRUB_SIGNED" ] && memtest=1

MEMTEST_GRUB="# bashclub-zbm-update: Speichertest (shim → GRUB → memtest86+), Eintrag „bashclub memtest“
set timeout=0
search --no-floppy --file --set=root /memtest/$MEMTEST_EFI
menuentry \"Speichertest / Memory test (memtest86+)\" {
	linux /memtest/$MEMTEST_EFI
}"

syslinux_cfg() {
	printf '%s\n' "# bashclub-zbm-update: ZFSBootMenu für BIOS; Umschalt halten und memtest bzw." \
		"# backup (vorige Version von ZFSBootMenu) eintippen" \
		"DEFAULT zfsbootmenu" "PROMPT 0" "TIMEOUT 0" "" \
		"LABEL zfsbootmenu" "  KERNEL /zfsbootmenu/vmlinuz-bootmenu" \
		"  INITRD /zfsbootmenu/initramfs-bootmenu.img" "  APPEND ro quiet loglevel=0 zbm.timeout=3 watchdog.stop_on_reboot=0"
	if [ "$1" = 1 ]; then
		printf '%s\n' "" "LABEL backup" "  KERNEL /zfsbootmenu/backup/vmlinuz-bootmenu" \
			"  INITRD /zfsbootmenu/backup/initramfs-bootmenu.img" "  APPEND ro quiet loglevel=0 zbm.timeout=3 watchdog.stop_on_reboot=0"
	fi
	if [ "$2" = 1 ]; then
		printf '%s\n' "" "LABEL memtest" "  LINUX /memtest/$MEMTEST_BIOS"
	fi
}

# Datei neben dem Ziel schreiben, dann umbenennen
# (update_esp läuft in „if“, dort gilt set -e nicht: jeder Schritt prüft selbst)
put() {
	mkdir -p "$(dirname "$2")" || return 1
	if [ -f "$2" ] && cmp -s "$1" "$2"; then return 0; fi
	cp "$1" "$2.new" || { rm -f "$2.new"; return 1; }
	mv -f "$2.new" "$2"
}
put_text() {
	tmp=$(mktemp) || return 1
	printf '%s\n' "$1" >"$tmp" && put "$tmp" "$2"
	rc=$?
	rm -f "$tmp"
	return $rc
}

state() {
	# Stand einer eingehängten ESP: aktuell, veraltet oder fehlt
	if [ ! -f "$1/EFI/bashclub/grubx64.efi" ]; then echo fehlt
	elif cmp -s "$1/EFI/bashclub/grubx64.efi" "$SRC/zfsbootmenu.EFI"; then echo aktuell
	else echo veraltet; fi
}

update_esp() {
	esp=$1 mnt=$2
	if [ "$(state "$mnt")" = veraltet ]; then
		say "$esp: vorige Version nach EFI/bashclub-backup und zfsbootmenu/backup"
		for f in shimx64.efi mmx64.efi grubx64.efi; do
			if [ -f "$mnt/EFI/bashclub/$f" ]; then
				put "$mnt/EFI/bashclub/$f" "$mnt/EFI/bashclub-backup/$f" || return 1
			fi
		done
		for f in vmlinuz-bootmenu initramfs-bootmenu.img; do
			if [ -f "$mnt/zfsbootmenu/$f" ]; then
				put "$mnt/zfsbootmenu/$f" "$mnt/zfsbootmenu/backup/$f" || return 1
			fi
		done
	fi
	# ZBM zuletzt nach EFI/bashclub: bis dahin startet der bisherige Stand
	for dir in EFI/BOOT EFI/bashclub; do
		shim=shimx64.efi
		[ $dir = EFI/BOOT ] && shim=BOOTX64.EFI
		put "$SHIM/shimx64.efi.signed" "$mnt/$dir/$shim" || return 1
		put "$SHIM/mmx64.efi.signed" "$mnt/$dir/mmx64.efi" || return 1
		put "$SRC/zfsbootmenu.EFI" "$mnt/$dir/grubx64.efi" || return 1
	done
	for f in vmlinuz-bootmenu initramfs-bootmenu.img; do
		put "$SRC/$f" "$mnt/zfsbootmenu/$f" || return 1
	done
	if [ $memtest = 1 ]; then
		put "$SHIM/shimx64.efi.signed" "$mnt/EFI/memtest/shimx64.efi" || return 1
		put "$SHIM/mmx64.efi.signed" "$mnt/EFI/memtest/mmx64.efi" || return 1
		put "$GRUB_SIGNED" "$mnt/EFI/memtest/grubx64.efi" || return 1
		put "$SRC/$MEMTEST_EFI" "$mnt/memtest/$MEMTEST_EFI" || return 1
		# Debians GRUB sucht seine Konfiguration unter /EFI/debian, dann
		# neben der eigenen Datei: beide Orte, gleicher Inhalt
		put_text "$MEMTEST_GRUB" "$mnt/EFI/memtest/grub.cfg" || return 1
		put_text "$MEMTEST_GRUB" "$mnt/EFI/debian/grub.cfg" || return 1
	fi
	if [ -d "$mnt/syslinux" ] || [ $bios = 1 ]; then
		bios_memtest=0
		if [ -f "$SRC/$MEMTEST_BIOS" ]; then
			put "$SRC/$MEMTEST_BIOS" "$mnt/memtest/$MEMTEST_BIOS" || return 1
			bios_memtest=1
		fi
		backup=0
		[ -f "$mnt/zfsbootmenu/backup/vmlinuz-bootmenu" ] && backup=1
		put_text "$(syslinux_cfg $backup $bios_memtest)" "$mnt/syslinux/syslinux.cfg" || return 1
	fi
	sync -f "$mnt/EFI" 2>/dev/null || sync
}

# UEFI-Einträge: je ESP „bashclub <n>“ und, wenn es den alten Stand gibt,
# „bashclub <n> (vorige Version)“; „bashclub memtest“ einmal. Neue Einträge
# legt efibootmgr -C an, ohne die Bootreihenfolge zu ändern; sie kommen ans
# Ende, mit --install die bashclub-Einträge an den Anfang.
entry_for() { # <partuuid oder leer> <pfad> → Bootnummer oder nichts
	# Der Pfad allein ist eindeutig: \EFI\bashclub\… ist kein Teil von \EFI\bashclub-backup\…
	efibootmgr -v 2>/dev/null | grep -i -- "$1" | grep -iF -- "$2" |
		sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\).*/\1/p' | head -1
}
ensure_entry() { # <esp> <label> <pfad> [any] → gibt die Bootnummer aus
	uuid=$(lsblk -no PARTUUID "$1")
	match=$uuid
	[ "${4:-}" = any ] && match=""
	num=$(entry_for "$match" "$3")
	if [ -z "$num" ]; then
		disk=/dev/$(lsblk -dno PKNAME "$1")
		part=$(lsblk -no PARTN "$1")
		efibootmgr -q -C -d "$disk" -p "$part" -L "$2" -l "$3" >/dev/null || return 1
		num=$(entry_for "$uuid" "$3")
		[ -n "$num" ] || { warn "$1: Booteintrag „$2“ ließ sich nicht anlegen"; return 1; }
		say "$1: Booteintrag Boot$num „$2“ angelegt" >&2
	fi
	echo "$num"
}

if [ $list = 1 ]; then
	say "Quelle: $SRC ($(dpkg-query -W -f '${Version}' bashclub-zfsbootmenu 2>/dev/null || echo 'ohne Paket'))"
fi

failed=0 done_count=0 n=0 front="" back=""
for esp in $esps; do
	n=$((n + 1))
	mnt=$(mktemp -d)
	if [ $list = 1 ]; then
		if mount -o ro -t vfat "$esp" "$mnt" 2>/dev/null; then
			b=""
			[ -f "$mnt/EFI/bashclub-backup/grubx64.efi" ] && b=", vorige Version vorhanden"
			[ -d "$mnt/syslinux" ] && b="$b, BIOS"
			say "$esp: $(state "$mnt")$b"
			umount "$mnt"
		else
			say "$esp: lässt sich nicht einhängen"
		fi
		rmdir "$mnt"
		continue
	fi
	if ! mount -t vfat "$esp" "$mnt"; then
		warn "$esp: lässt sich nicht einhängen"
		rmdir "$mnt"
		failed=$((failed + 1))
		continue
	fi
	if update_esp "$esp" "$mnt"; then
		done_count=$((done_count + 1))
		has_backup=0
		[ -f "$mnt/EFI/bashclub-backup/grubx64.efi" ] && has_backup=1
		umount "$mnt"
		rmdir "$mnt"
		say "$esp: aktuell"
		if [ $entries = 1 ]; then
			num=$(ensure_entry "$esp" "bashclub $n" '\EFI\bashclub\shimx64.efi') && front="$front $num" || failed=$((failed + 1))
			if [ $has_backup = 1 ]; then
				num=$(ensure_entry "$esp" "bashclub $n (vorige Version)" '\EFI\bashclub-backup\shimx64.efi') \
					&& back="$back $num" || failed=$((failed + 1))
			fi
			if [ $memtest = 1 ] && [ $n = 1 ]; then
				num=$(ensure_entry "$esp" "bashclub memtest" '\EFI\memtest\shimx64.efi' any) \
					&& back="$back $num" || failed=$((failed + 1))
			fi
		fi
	else
		warn "$esp: Aktualisierung gescheitert"
		umount "$mnt" || true
		rmdir "$mnt" || true
		failed=$((failed + 1))
	fi
done
[ $list = 1 ] && { [ $entries = 1 ] && efibootmgr | grep -e '^BootOrder' -e 'bashclub'; exit 0; }

if [ $entries = 1 ] && [ -n "$front$back" ]; then
	order=$(efibootmgr | sed -n 's/^BootOrder: //p' | tr ',' ' ')
	if [ $install = 1 ]; then
		# bashclub <n> vorn, alles andere in bisheriger Reihenfolge, Rückfall und Speichertest hinten
		new="$front"
		for o in $order; do
			case " $front $back " in *" $o "*) ;; *) new="$new $o" ;; esac
		done
		new="$new $back"
	else
		# nur fehlende Einträge hinten anhängen, die Reihenfolge des Admins bleibt
		new="$order"
		for o in $front $back; do
			case " $order " in *" $o "*) ;; *) new="$new $o" ;; esac
		done
	fi
	new=$(echo $new | tr ' ' ',')
	if [ "$new" != "$(echo $order | tr ' ' ',')" ]; then
		efibootmgr -q -o "$new"
		say "Bootreihenfolge: $new"
	fi
fi

[ $done_count = 0 ] && exit 2
[ $failed -gt 0 ] && exit 1
exit 0
