#!/bin/sh
# SPDX-License-Identifier: GPL-3.0+
# Copyright 2025 Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>

set -eu

usage() {
  echo "Set up eMMC with a /boot partition which loads the OS from the SSD." >&2
  echo "This tool is usually run from a rescue system on an SD-card to" >&2
  echo "migrate a system from one SoM to another." >&2
  echo "WARNING: this tool will erase the contents of your eMMC." >&2
  echo >&2
  echo "Usage: $0 [--help]" >&2
  echo >&2
  echo "Options:" >&2
  echo "  --help           Display this help and exit." >&2
  echo >&2
  echo "This utility is not able to create a working /boot partition on" >&2
  echo "eMMC for arbitrary installations. It currently understands" >&2
  echo >&2
  echo " - single partition on SSD with the rootfs" >&2
  echo " - two partitions on SSD, one with the rootfs the other swap" >&2
  echo " - the luks/lvm setup created by reform-setup-encrypted-disk" >&2
}

is_current_root() {
  FSTAB_ROOT="$(findmnt --fstab --noheadings --evaluate --mountpoint / --output SOURCE)"
  [ "${1}" = "${FSTAB_ROOT}" ]
}

if [ "$#" -gt 0 ]; then
  case $1 in --help)
    usage
    exit 0
    ;;
  *)
    echo "E: unknown option: $1" >&2
    ;;
  esac
fi

# shellcheck source=/dev/null
if [ -e "./machines/$(cat /proc/device-tree/model).conf" ]; then
  . "./machines/$(cat /proc/device-tree/model).conf"
elif [ -e "/usr/share/reform-tools/machines/$(cat /proc/device-tree/model).conf" ]; then
  . "/usr/share/reform-tools/machines/$(cat /proc/device-tree/model).conf"
else
  echo "E: unable to find config for $(cat /proc/device-tree/model)" >&2
  exit 1
fi

# eMMC device is being used (case 1): mount points are known, show them. Includes swap.
if [ -n "$(lsblk --noheadings --output=MOUNTPOINT "/dev/${DEV_MMC}")" ]; then
  echo "E: eMMC has the following mounted volumes, unmount them before running this tool" >&2
  lsblk --noheadings --output=MOUNTPOINT "/dev/${DEV_MMC}" | xargs --no-run-if-empty -I '{}' echo "E:   {}" >&2
  exit 1
fi

# eMMC device is being used (case 2): there are not file systems directly mounted on the block device
# but it is opened by consumers like device-mapper, raid or luks, to name some examples. In this situation
# it is not trivial to locate the consumer.
# reform-boot-config does the same thing (could share code?)
get_exclusive_write_lock() {
  ret=0
  python3 - "$1" <<EOF || ret=$?
import errno, os, sys

try:
    os.open(sys.argv[1], os.O_WRONLY | os.O_EXCL)
except OSError as e:
    if e.errno == errno.EBUSY:
        sys.exit(1)
    raise
EOF
  return $ret
}

if ! get_exclusive_write_lock "/dev/${DEV_MMC}"; then
  echo "E: device /dev/${DEV_MMC} (eMMC) is still in use" >&2
  exit 1
fi

# This script has two modes: either it is executed from a system on SD-card
# or from a system on SSD. In the former case, it will run reform-rescue-shell
# and run the setup code from within a chroot on the SSD. In the latter case,
# it latter case the setup code will run directly.
case $(findmnt --noheadings --evaluate --mountpoint / --output SOURCE) in
  "/dev/${DEV_SD}"*) : ;;
  *)
    echo "I: Running without chroot isolation of reform-rescue-shell" >&2
    echo
    if mountpoint --quiet "/boot"; then
      echo "E: /boot still has something mounted on it" >&2
      exit 1
    fi
    # we leave this mounted even after the script has finished, no need for an exit trap
    mount "/dev/${DEV_MMC}p1" /boot
    ;;
esac

cat <<END

WARNING: This script will destroy the contents of your eMMC drive and replace
it with a /boot partition which is able to boot your system on SSD.

WARNING: This operation needs internet access to your Debian apt mirror. Make
sure that your system is online before proceeding.

END
printf "Are you sure you want to proceed? [y/N] "
read -r response
if [ "$response" != "y" ]; then
  echo "Exiting."
  exit
fi

# reform-boot-config does the same thing (could share code?)
parted --script --machine "/dev/$DEV_MMC" "mklabel msdos"
# create two partitions as expected by reform-check (but we only format /boot)
# make /boot as big as mkimage.sh would
parted --script --machine "/dev/$DEV_MMC" "mkpart primary ext4 16MiB $((16 + 488))MiB"
parted --script --machine "/dev/$DEV_MMC" "mkpart primary ext4 $((16 + 488))MiB 100%"
udevadm settle
partprobe "/dev/$DEV_MMC"
mkfs.ext4 -F "/dev/${DEV_MMC}p1"
# create extlinux directory to make reform-rescue-shell recognize this as a
# /boot partiton
debugfs -w "/dev/${DEV_MMC}p1" -R "mkdir /extlinux"

set --

# If this is run from an SD-card, run with the isolation from reform-rescue-shell
case $(findmnt --noheadings --evaluate --mountpoint / --output SOURCE) in
  "/dev/${DEV_SD}"*) set -- "reform-rescue-shell" ;;
esac

# Pass script as string argument with -c instead of sending it to standard input
# of sh because we need stdin of sh connected to the current interactive tty
# or otherwise the user will be unable to send keys to interactive prompts.
# shellcheck disable=SC2016
"$@" sh -c '
set -exuo pipefail
apt-get update --error-on=any
if ! dpkg-query --search "/boot/vmlinuz*" >/dev/null; then
  echo "I: No kernel package installed." >&2
  printf "Do you want this script to install linux-image-mnt-reform-arm64 for you? [y/N] "
  read -r response
  if [ "$response" != "y" ]; then
    echo "E: no kernel packages installed, exiting." >&2
    exit 1
  fi
  apt-get install --yes linux-image-mnt-reform-arm64
else
  # upgrade linux-image-mnt-reform-arm64 in case that the installed kernel is
  # not downloadable anymore
  apt-get install --yes --only-upgrade linux-image-mnt-reform-arm64
fi
# also regenerate everything for other installed kernel packages
dpkg-query --search "/boot/vmlinuz*" | sed "s/:.*//" | while read -r pkg; do
  # if a package cannot be downloaded, apt will still exit
  # successfully
  apt-get install --yes --reinstall "$pkg"
done
if [ -z "$(find "/boot" -name "vmlinuz-*")" ]; then
  echo "E: no kernel images were installed into /boot" >&2
  exit 1
fi
reform-boot-config --emmc "$(findmnt --noheadings --evaluate --mountpoint / --output SOURCE)"
'

if [ "$EMMC_BOOT" != false ]; then
  reform-flash-bootloader emmc
fi
