#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2023 SUSE LLC
set -e
shopt -s nullglob

# set to --keep-title to use altenate screen. Better for debugging but causes flicker
dialog_altenate_screen=
dialog_backtitle="Systemd-boot"
interactive=
verbose=
nl=$'\n'
shimdir="/usr/share/efi/$(uname -m)"
sdboot_vendor="systemd"
sdboot_dst="/EFI/$sdboot_vendor"
arg_esp_path=
arg_entry_token=
arg_arch=

tmpdir=$(mktemp -d -t sdboot.XXXXXX)
cleanup()
{
	rm -rf "$tmpdir"
}
trap cleanup EXIT

entryfile="$tmpdir/entries.json"
snapperfile="$tmpdir/snapper.json"
tmpfile="$tmpdir/tmp"

helpandquit()
{
	cat <<-EOF
		Usage: $0 [OPTIONS] [COMMAND]
		OPTIONS:
		  --verbose  verbose
		  -h         help screen

		COMMAND:
		add-kernel VERSION [SUBVOL]
			   create boot entry for specified kernel
		remove-kernel VERSION [SUBVOL]
			   remove boot entry for specified kernel
		kernels    open kernel menu
		snapshots  open snapshots menu
		entries    open entry menu

	EOF
	exit 0
}

log_info()
{
	[ "${verbose:-0}" -gt 0 ] || return 0
	echo "$@"
}

d(){
	local retval=0
	# Bash makes it a bit annoying to read the output of a different FD into a variable, it
	# only supports reading stdout by itself. So redirect 3 to stdout and 1 to the real stdout.
	exec {stdoutfd}>&1
	result="$(dialog $dialog_altenate_screen --backtitle "$dialog_backtitle" --output-fd 3 "$@" 3>&1 1>&${stdoutfd})" || retval=$?
	# Word splitting makes it necessary to use eval here.
	eval "exec ${stdoutfd}>&-"
	return "$retval"
}

err()
{
	if [ "$interactive" = 1 ]; then
		d --title 'Error' --ok-label "Quit" --colors --aspect 60 --msgbox "\Z1Error:\Zn $*" 0 0
	else
		echo "Error: $*" >&2
	fi
	exit 1
}

warn()
{
	if [ "$interactive" = 1 ]; then
		d --title 'Warning' --ok-label "Continue" --colors --aspect 60 --msgbox "\Z1Warning:\Zn $*" 0 0
	else
		echo "Warning: $*" >&2
	fi
	exit 1
}



run_command_live_output()
{
	if [ "$interactive" = 1 ]; then
		"$@" 2>&1 | dialog $dialog_altenate_screen --backtitle "$dialog_backtitle" --title "$1" --aspect 60 --progressbox 0 0
	else
		"$@"
	fi
}

run_command_output()
{
	if [ "$interactive" = 1 ]; then
		"$@" > "$tmpfile" 2>&1
		[ -s "$tmpfile" ] && d --textbox "$tmpfile" 0 0
	else
		"$@"
	fi
}

# Given the number of total item pairs, outputs the number of items to display at once
menuheight() {
	local height=$(($1 / 2))
	[ "$height" -le "$dh_menu" ] || height="$dh_menu"
	echo "$height"
}

stty_size() {
	set -- $(stty size)
	LINES="$1"
	COLUMNS="$2"
	# stty size can return zero when not ready or
	# its a serial console
	if [ "$COLUMNS" = "0" ] || [ "$LINES" = "0" ]; then
		LINES=24
		COLUMNS=80
	fi

	dh_menu=$((LINES-15))
	dh_text=$((LINES-5))
}

subvol_is_ro()
{
	local subvol="${1:?}"
	while read -r line; do
		[ "$line" = "ro=true" ] && return 0
	done < <(btrfs prop get -t s "${subvol#@}" ro)
	return 1
}

detect_parent() {
	local subvol="$1"
	parent_uuid="$(btrfs subvol show "${subvol#@}" | sed -ne 's/\s*Parent UUID:\s*//p')"
	[ "$parent_uuid" != '-' ] || parent_uuid=
	[ -n "$parent_uuid" ] || return 0
	parent_subvol="$(/sbin/btrfs subvol show -u "$parent_uuid" "${subvol#@}" | head -1)"
	parent_snapshot="${parent_subvol#@/.snapshots/}"
	if [ "$parent_subvol" = "$parent_snapshot" ]; then
		unset parent_subvol parent_snapshot
	else
		parent_snapshot="${parent_snapshot%/snapshot}"
	fi
}

sedrootflags()
{
	local subvol="${1:?}"
	sed -e "s/[ \t]\+/ /g;s/\<\(BOOT_IMAGE\|initrd\)=[^ ]* \?//;s,rootflags=subvol=[^ ]*,rootflags=subvol=$subvol,;tx; s,\$, rootflags=subvol=$subvol,;:x"
}

remove_kernel()
{
	local subvol="$1"
	local kernel_version="$2"
	local snapshot="${subvol#@/.snapshots/}"
	snapshot="${snapshot%/*}"
	local id="$entry_token-$kernel_version-$snapshot.conf"
	run_command_output bootctl unlink "$id"
}

install_kernel()
{
	local subvol="$1"
	local kernel_version="$2"
	local src="${subvol#@}/lib/modules/$kernel_version/vmlinuz"
	test -e "$src" || err "Can't find $src"

	calc_chksum "$src"
	dst="/$entry_token/$kernel_version/linux-$chksum"

	local snapshot="${subvol#@/.snapshots/}"
	snapshot="${snapshot%/*}"

	local initrd="${src%/*}/initrd"

	mkdir -p "$boot_root${dst%/*}"

	if [ -e "$initrd" ]; then
		ln -s "$initrd" "$tmpdir/initrd"
	else
		# check if we can reuse the initrd from the parent
		# to avoid expensive regeneration
		detect_parent "$subvol"
		parent_conf="$boot_root/loader/entries/$entry_token-$kernel_version-$parent_snapshot.conf"
		if [ -n "$parent_subvol" ] && [ -e "$parent_conf" ]; then
			#subvol_is_ro "$parent_subvol" || err "Parent snapshot $parent_snapshot is not read-only, can't reuse initrd"
			local k
			local v
			while read -r k v; do
				[ "$k" = 'initrd' ] || continue
				log_info "found previous initrd $v"
				dstinitrd="$v"
				break
			done < "$parent_conf"
			[ -n "$dstinitrd" ] || err "no initrd for kernel $kernel_version in parent snapshot $parent_snapshot found"
		elif [ "$subvol" != "$root_subvol" ]; then
			err "Can't call dracut for snapshots that have no initrd yet"
		else
			run_command_live_output dracut --reproducible "$tmpdir/initrd" "$kernel_version"
		fi
	fi

	local boot_options=
	for i in /etc/kernel/cmdline /usr/lib/kernel/cmdline /proc/cmdline; do
		[ -f "$i" ] || continue
		boot_options="$(sedrootflags "$subvol" < "$i")"
		break
	done

	if [ -z "$dstinitrd" ] && [ -e "$tmpdir/initrd" ]; then
		calc_chksum "$tmpdir/initrd"
		dstinitrd="${dst%/*}/initrd-$chksum"
	fi

	sort_key="$ID"
	entry_machine_id=
	[ "$entry_token" = "$machine_id" ] && entry_machine_id="$machine_id"

	cat > "$tmpdir/entry.conf" <<-EOF
	# Boot Loader Specification type#1 entry
	title      ${os_release_PRETTY_NAME:-Linux $kernel_version}
	version    $snapshot@$kernel_version${entry_machine_id:+${nl}machine-id $entry_machine_id}${sort_key:+${nl}sort-key   $sort_key}
	options    $boot_options
	linux      $dst
	initrd     $dstinitrd
	EOF

	local failed=
	if [ ! -e "$boot_root$dst" ]; then
		install -m 0644 "$src" "$boot_root$dst" || failed=kernel
		chown root:root "$boot_root$dst" 2>/dev/null || :
		log_info "installed $boot_root$dst"
	else
		log_info "reusing $boot_root$dst"
	fi
	if [ -z "$failed" ] && [ -e "$tmpdir/initrd" ] && [ ! -e "$boot_root$dstinitrd" ]; then
		install -m 0644 "$tmpdir/initrd" "$boot_root$dstinitrd" || failed=initrd
		chown root:root "$boot_root$dstinitrd" 2>/dev/null || :
		log_info "installed $boot_root$dstinitrd"
	fi
	if [ -z "$failed" ]; then
		loader_entry="$boot_root/loader/entries/$entry_token-$kernel_version-$snapshot.conf"
		install -D -m 0644 "$tmpdir/entry.conf" "$loader_entry" || failed="bootloader entry"
		chown root:root "$boot_root$dstinitrd" 2>/dev/null || :
		log_info "installed $loader_entry"
	fi
	rm -f "$tmpdir/initrd"
	rm -f "$tmpdir/entry.conf"
	if [ -n "$failed" ]; then
		rm -f "$boot_root$dst" "$boot_root$dstinitrd"
		err "Failed to install $failed"
	fi
}

install_all_kernels()
{
	local subvol="@/.snapshots/${1:?}/snapshot"
	find_kernels "$subvol"
	for kv in "${!found_kernels[@]}"; do
		log_info "installing $kv"
		install_kernel "${subvol}" "$kv"
	done

}

remove_all_kernels()
{
	local subvol="@/.snapshots/${1:?}/snapshot"
	find_kernels "$subvol"
	for kv in "${!found_kernels[@]}"; do
		remove_kernel "${subvol}" "$kv"
	done

}

entry_filter=("cat")
update_entries()
{
	[ -z "$1" ] || entry_filter=("$@")
	bootctl list --json=short | "${entry_filter[@]}" > "$entryfile"
}

update_entries_for_subvol()
{
	local subvol="$1"
	update_entries jq "[.[]|select(has(\"options\"))|select(.options|match(\"root=UUID=$root_uuid .*rootflags=subvol=$subvol\"))]"
}



update_entries_for_snapshot()
{
	local n="$1"
	update_entries_for_subvol "@/.snapshots/$n/snapshot"
}

show_entries()
{
	local dialogtitle="${1:-Entries}"

	[ -s "$entryfile" ] || update_entries

	while true; do
		local list=()
		local n=0
		local default=
		while read -r isdefault isreported type title; do
			color=
			if [ "$isdefault" = "true" ]; then
				default="$n"
				color="\\Zb\Zu"
			fi
			if [ "$isreported" = "false" ]; then
				color="$color\\Z2"
			fi
			if [ "$type" = "loader" ]; then
				color="$color\\Z5"
			fi
			list+=("$n" "$color$title\\Zn")
			n=$((++n))
		done < <(jq '.[]|[.isDefault, if has("isReported") then .isReported else 0 end, if has("type") then .type else "unknown" end, .showTitle]|join(" ")' -r < "$entryfile")
		if [ "${#list}" = 0 ]; then
			d --msgbox "No entries" 0 0
			return 0
		fi
		d --no-hot-list --colors --ok-label "Options" --cancel-label "Back" --menu "$dialogtitle" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || return 0
		n="$result"

		show_entry ".[$n]"
	done
}

show_entry()
{
	local filter="$1"
	local type
	local isreported
	local isdefault
	local new=

	read -r isdefault isreported type title < <(jq "$filter"'|[.isDefault, if has("isReported") then .isReported else 0 end, if has("type") then .type else "unknown" end, .showTitle]|join(" ")' -r < "$entryfile")

	[ "$isdefault" = true ] || isdefault=
	[ "$isreported" = true ] || new=1

	if [ -n "$isdefault$new" ]; then
		title="$title ["
		[ -z "$isdefault" ] || title="${title}default"
		[ -z "$new" ] || title="${title}${isdefault:+,}new"
		title="$title]"
	fi

	while true; do
		local list=(show json)
		if [ "$type" = "type1" ]; then
			list+=(cat Raw edit Edit)
		fi
		if [ -z "$isdefault" ]; then
			list+=(set-default "set as default" oneshot "set as one-shot")
			if [ "$type" != "loader" ]; then
				list+=(delete delete)
			fi
		fi
		d --no-tags --menu "Entry #$title" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || break
		action="$result"

		case "$action" in
			show)
				jq "$filter" < "$entryfile" > "$tmpfile"
				d --textbox "$tmpfile" 0 0
				;;
			cat)
				read -r fn < <(jq -r "$filter|.path" < "$entryfile")
				d --textbox "$fn" 0 0
				;;
			edit)
				read -r fn < <(jq -r "$filter|.path" < "$entryfile")
				${EDITOR:-vim} "$fn"
				update_entries
				;;
			delete)
				read -r id < <(jq -r "$filter|.id" < "$entryfile")
				bootctl unlink "$id" > "$tmpfile" 2>&1
				[ -s "$tmpfile" ] && d --textbox "$tmpfile" 0 0
				update_entries
				break
				;;
			set-default)
				read -r id < <(jq -r "$filter|.id" < "$entryfile")
				bootctl set-default "$id" > "$tmpfile" 2>&1
				[ -s "$tmpfile" ] && d --textbox "$tmpfile" 0 0
				update_entries
				break
				;;
			oneshot)
				read -r id < <(jq -r "$filter|.id" < "$entryfile")
				bootctl set-oneshot "$id" > "$tmpfile" 2>&1
				[ -s "$tmpfile" ] && d --textbox "$tmpfile" 0 0
				update_entries
				break
				;;
		esac
	done
}

update_snapper()
{
	 snapper --jsonout list > "$snapperfile"
}

show_snapper()
{
	if ! update_snapper 2>"$tmpfile"; then
		d --title "Error" --textbox "$tmpfile" 0 0
		exit 1
	fi

	while true; do
		local list=()
		local n=0
		local default=
		while read -r n isdefault title; do
			[ "$n" != "0" ] || continue
			if [ "$isdefault" = "true" ]; then
				default="$n"
				title="\\Zb\Zu$title\\Zn"
			fi
			list+=("$n" "$title")
		done < <(jq '.root|.[]|[.number, .default, .description]|join(" ")' -r < "$snapperfile")
		if [ "${#list}" = 0 ]; then
			d --msgbox "No snapshots" 0 0
			return 0
		fi
		d --no-hot-list --colors --ok-label "Options" --cancel-label "Back" --menu "Snapshots" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || return 0
		n="$result"

		while true; do
			list=(kernels kernels entries entries show json)
			if [ "$n" != "$default" ]; then
				list+=(delete delete)
			fi
			d --no-tags --menu "Snapshot #$n" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || break
			action="$result"

			case "$action" in
				show)
					jq ".root|.[]|select(.number==$n)" < "$snapperfile" > "$tmpfile"
					d --textbox "$tmpfile" 0 0
					;;
				entries)
					#read -r MACHINE_ID < /etc/machine-id
					#update_entries jq "[.[]|select(.machineId==\"$MACHINE_ID\")|select(has(\"options\"))|select(.options|match(\"@/.snapshots/$n/snapshot\"))]"
					update_entries_for_snapshot "$n"
					show_entries "Entries for Snapshot $n"
					;;
				kernels)
					show_kernels "@/.snapshots/$n/snapshot"
					;;
			esac
		done
	done
}

calc_chksum() {
    # shellcheck disable=SC2046
    set -- $(sha1sum "$1")
    chksum="$1"
}

# map with kernel version as key and checksum as value
declare -A found_kernels
find_kernels()
{
	local subvol="${1:?}"
	found_kernels=()

	for fn in "${subvol#@}"/usr/lib/modules/*/vmlinuz; do
		kv="${fn%/*}"
		kv="${kv##*/}"
		calc_chksum "$fn"
		found_kernels["$kv"]="$chksum"
		log_info "found kernel $kv = $chksum"
	done
}

# map that uses expected path on the ESP for each kernel. The value indicates
# whether that kernel is installed, stale or missing in the ESP
declare -A known_kernels
update_kernels()
{
	local subvol="${1:?}"
	known_kernels=()
	find_kernels "$subvol"
	for kv in "${!found_kernels[@]}"; do
		known_kernels["/$entry_token/$kv/linux-${found_kernels[$kv]}"]=missing
	done
	update_entries_for_subvol "$subvol"

	while read -r k id; do
		# kernel in ESP that is not installed
		if [ -z "${known_kernels[$k]}" ]; then
			known_kernels["$k"]="!$id"
		elif [ "${known_kernels[$k]}" = missing ]; then
			known_kernels["$k"]="$id"
		fi
	done < <(jq -r '.[]|select(has("linux"))|[.linux,.id]|join(" ")'< "$entryfile")
}

show_kernels()
{
	subvol="${1:?}"
	while true; do
		update_kernels "$subvol"
		local list=()
		local n=0
		local default=
		local kernelfiles=("${!known_kernels[@]}")
		local states=()
		for k in "${kernelfiles[@]}"; do
			state="${known_kernels[$k]}"
			if [ "$state" != 'missing' ]; then
				if [ "${state:0:1}" = '!' ]; then
					state="stale"
				else
					state="ok"
				fi
			fi
			states+=("$state")
			s="${k#/*/}"
			list+=("$n" "$(printf "%-10s %s" "$state" "$s")")
			n=$((++n))
		done
		if [ "${#list}" = 0 ]; then
			d --msgbox "No kernels" 0 0
			return 1
		fi
		d --no-tags --no-hot-list --colors --ok-label "Options" --cancel-label "Back" --menu "Kernels associated with $subvol" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || return 0
		n="$result"

		local id="${known_kernels[${kernelfiles[$n]}]}"
		list=()
		if [ "$id" = "missing" ]; then
			list+=(install "Install")
		else
			[ "${id:0:1}" = '!' ] && id="${id:1}" # stale
			list+=(show "Entry")
		fi
		list+=(entries "Other Entries")

		local kv="${kernelfiles[$n]%/*}"
		kv="${kv##*/}"
		local title="Kernel $kv"
		while true; do
			d --no-tags --no-hot-list --colors --ok-label "Ok" --cancel-label "Back" --menu "$title" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || break
			action="$result"

			case "$action" in
				entries)
					update_entries jq "[.[]|select(has(\"linux\"))|select(.linux|match(\"${kernelfiles[$n]}\"))]"
					show_entries "Entries for kernel ${kernelfiles[$n]#/*/}"
					;;
				show)
					show_entry ".[]|select(.id|match(\"$id\"))"
					break # might have selected delete so refresh
					;;
				install)
					install_kernel "$subvol" "$kv"
					break
					;;
			esac
		done
	done
}

sdboot_is_installed()
{
	[ -e "$boot_root$sdboot_dst/systemd-boot$firmware_arch.efi" ] && \
		grep -q '#### LoaderInfo: systemd-boot' "$boot_root$sdboot_dst/systemd-boot$firmware_arch.efi"
}

# TODO: need to create a boot variable if it's not the first installation
install_sdboot()
{
	# XXX: this is a hack in case we need to inject a signed
	# systemd-boot from a separate package
	local sdboot="/usr/lib/systemd-boot/systemd-boot$firmware_arch.efi"
	[ -e "$sdboot" ] || sdboot="/usr/lib/systemd/boot/efi/systemd-boot$firmware_arch.efi"
	[ -e "$sdboot" ] || err "missing $sdboot"

	mkdir -p "$boot_root/loader/entries"

	if [ -e "$shimdir/shim.efi" ]; then
		entry="$sdboot_dst/shim.efi"
		for i in MokManager shim; do
			install -D "$shimdir/$i.efi" "$boot_root$sdboot_dst/$i.efi"
		done
		install -D "$sdboot" "$boot_root$sdboot_dst/grub.efi"
		echo "shim.efi,openSUSE Boot Manager" | iconv -f ascii -t ucs2 > "$boot_root/$sdboot_dst/boot.csv"
		# boot entry point
		for i in MokManager fallback; do
			install -D "$shimdir/$i.efi" "$boot_root/EFI/BOOT/$i.efi"
		done
		install -D "$shimdir/shim.efi" "$boot_root/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
	else
		entry="$sdboot_dst/${sdboot##*/}"
		install -D "$sdboot" "$boot_root$entry"
		echo "${sdboot##*/},openSUSE Boot Manager" | iconv -f ascii -t ucs2 > "$boot_root/$sdboot_dst/boot.csv"
	fi
	mkdir -p "$boot_root/$entry_token"
	[ -s /etc/kernel/entry-token ] || echo "$entry_token" > /etc/kernel/entry-token
	update_random_seed

	[ -s "$boot_root/loader/entries.srel" ] || echo type1 > "$boot_root/loader/entries.srel"
}

settle_entry_token()
{
	case "$arg_entry_token" in
		"") [ -n "$entry_token" ] || entry_token="$machine_id" ;;
		auto)
			if [ -s '/etc/kernel/entry-token' ]; then
				read -r entry_token < '/etc/kernel/entry-token'
			else
				entry_token="$machine_id"
				# bootctl has more here in case the machine id
				# is random falls back to trying IMAGE_ID and
				# ID, only them machine id. We assume there is
				# a valid machine-id
			fi
			;;
		machine-id) entry_token="$machine_id" ;;
		os-id)
			# shellcheck disable=SC2154
			entry_token="$os_release_ID"
			[ -n "$entry_token" ] || err "Missing ID"
			;;
		os-image)
			# shellcheck disable=SC2154
			entry_token="$os_release_IMAGE_ID"
			[ -n "$entry_token" ] || err "Missing IMAGE_ID"
			;;
		*) entry_token="$arg_entry_token" ;;
	esac
	return 0
}

hex_to_binary()
{
	local s="$1"
	local i
	for ((i=0;i<${#s};i+=2)); do eval echo -n "\$'\x${s:$i:2}'"; done
}

update_random_seed()
{
	local s _p
	read -r s _p < <({ dd if=/dev/urandom bs=32 count=1 status=none; [ -e "$boot_root/loader/random-seed" ] && dd if="$boot_root/loader/random-seed" bs=32 count=1 status=none; } | sha256sum)
	[ "${#s}" = 64 ] || { warn "Invalid random seed"; return 0; }
	hex_to_binary "$s" > "$boot_root/loader/random-seed.new"
	mv "$boot_root/loader/random-seed.new" "$boot_root/loader/random-seed"
}

install_sdboot_interactive()
{
	d --aspect 60 --yesno "Are you sure you want to install systemd-boot into $boot_root?\n
This will overwrite any existing bootloaders" 0 0 || return 0
	install_sdboot
	d --aspect 60 --msgbox "Installed into $boot_root" 0 0
}

set_default_snapshot()
{
	local num="${1:?}"
	local configs
	update_entries_for_snapshot "$num"
	mapfile configs < <(jq '.[]|[.id]|join(" ")' -r < "$entryfile")
	configs=("${configs[@]%$nl}")
	if [ -z "${configs[0]}" ]; then
		log_info "snapshot $num has no configs, trying to create them..."
		install_all_kernels "$num"
		update_entries_for_snapshot "$num"
		mapfile configs < <(jq '.[]|[.id]|join(" ")' -r < "$entryfile")
		configs=("${configs[@]%$nl}")
		if [ -z "${configs[0]}" ]; then
			err "snapshot $num has no kernels"
		fi
	fi
	log_info "setting default entry ${configs[0]}"
	bootctl set-default "${configs[0]}"
}

main_menu()
{
	while true; do
		list=(kernels Kernels snapper Snapshots sd-boot Entries install "Install/Update")
		d --no-tags --cancel-label "Quit"  --menu "Main Menu" 0 0 "$(menuheight ${#list[@]})" "${list[@]}" || return 0
		action="$result"

		case "$action" in
			snapper) show_snapper ;;
			sd-boot) update_entries cat; show_entries ;;
			kernels) show_kernels "$root_subvol";;
			install) install_sdboot_interactive ;;
		esac
	done
}

####### main #######

stty_size

getopttmp=$(getopt -o hc:v --long help,flicker,verbose,esp-path:,entry-token:,arch: -n "${0##*/}" -- "$@")
eval set -- "$getopttmp"

while true ; do
        case "$1" in
                -h|--help) helpandquit ;;
		--flicker) dialog_altenate_screen=--keep-tite; shift ;;
		-v|--verbose) verbose=$((++verbose)); shift ;;
		--esp-path) arg_esp_path="$2"; shift 2 ;;
		--arch) arg_arch="$2"; shift 2 ;;
		--entry-token) arg_entry_token="$2"; shift 2 ;;
                --) shift ; break ;;
                *) echo "Internal error!" ; exit 1 ;;
        esac
done

case "$1" in
	install|add-kernel|remove-kernel|set-default-snapshot|add-all-kernels|remove-all-kernels) ;;
	*) interactive=1 ;;
esac

for i in /etc/os-release /usr/lib/os-release; do
	[ -f "$i" ] || continue
	eval $(sed -ne '/^[A-Z_]\+=/s/^/os_release_/p' < "$i")
	break
done

# XXX: bootctl should have json output for that too
eval "$(bootctl 2>/dev/null | sed -ne 's/Firmware Arch: *\(\w\+\)/firmware_arch="\1"/p;s/ *token: *\(\w\+\)/entry_token="\1"/p;s, *\$BOOT: *\([^ ]\+\).*,boot_root="\1",p')"
root_uuid=$(findmnt / -r -n -o UUID)
root_subvol=$(btrfs subvol show /|head -1)
root_snapshot="${root_subvol#@/.snapshots/}"
root_snapshot="${root_snapshot%/snapshot}"
[ -s /etc/machine-id ] && read -r machine_id < /etc/machine-id

[ -n "$arg_esp_path" ] && boot_root="$arg_esp_path"
[ -n "$arg_arch" ] && firmware_arch="$arg_arch"
settle_entry_token

[ -n "$boot_root" ] || err "No ESP detected. Legacy system?"
[ -n "$root_uuid" ] || err "Can't determine root UUID"
[ -n "$root_subvol" ] || err "Can't determine root subvolume"
[ -n "$entry_token" ] || err "No entry token. sd-boot not installed?"
[ -n "$firmware_arch" ] || err "Can't determine firmware arch"

if [ "$1" = "install" ]; then
	install_sdboot
elif [ "$1" = "add-kernel" ]; then
	install_kernel "${3:-$root_subvol}" "$2"
elif [ "$1" = "add-all-kernels" ]; then
	install_all_kernels "${2:-$root_snapshot}"
elif [ "$1" = "remove-kernel" ]; then
	remove_kernel "${3:-$root_subvol}" "$2"
elif [ "$1" = "remove-all-kernels" ]; then
	remove_all_kernels "${2:-$root_snapshot}"
elif [ "$1" = "set-default-snapshot" ]; then
	set_default_snapshot "$2"
elif [ "$1" = "kernels" ]; then
	show_kernels "$root_subvol"
elif [ "$1" = "snapshots" ]; then
	show_snapper
elif [ "$1" = "entries" ]; then
	show_entries
else
	main_menu
fi
