#!/bin/bash

# NOTE: to be able to run this script standalone, /ngcp-fallback/ needs to be properly
# setup up, this can be done e.g. via (adjust 'grub-efi,efibootmgr' with 'grub-pc' for non-EFI systems):
#   mount -o remount,rw /ngcp-fallback/
#   mmdebstrap --no-merged-usr --variant=minbase --include=grub-efi,efibootmgr $(lsb_release -c -s) /ngcp-fallback/
#   mkdir /ngcp-fallback/ngcp-data /ngcp-fallback/run/udev
#   steps/common/mr9.5/update_grub_config  # run this script, then take care of cleanup:
#   umount /ngcp-fallback/proc /ngcp-fallback/sys /ngcp-fallback/dev/pts /ngcp-fallback/dev /ngcp-fallback/run/udev /ngcp-fallback/boot/efi /ngcp-fallback/ngcp-data

set -eEu -o pipefail

die() {
  echo "ERROR: $*" >&2
  exit 1
}

last_record="$(tail -1 /etc/sipwise_ngcp_version)"
if [[ ! "${last_record}" =~ ^First\ stage\ completed ]] ; then
  echo "This script should be run only after first run of ngcp-upgrade" >&2
  echo "before the reboot" >&2
  if [[ "${FORCE_UPGRADE:-}" = "true" ]] ; then
    echo "Forcing as requested via environment variable FORCE_UPGRADE."
  else
    exit 1
  fi
fi

if ! mountpoint /ngcp-fallback/proc; then
  mount -t proc   none                  /ngcp-fallback/proc
fi
if ! mountpoint /ngcp-fallback/sys; then
  mount -t sysfs  none                  /ngcp-fallback/sys
fi
if ! mountpoint /ngcp-fallback/dev; then
  mount --bind    /dev                  /ngcp-fallback/dev
fi
if ! mountpoint /ngcp-fallback/dev/pts; then
  mount -t devpts none                  /ngcp-fallback/dev/pts
fi
if ! mountpoint /ngcp-fallback/run/udev; then
  mount --bind    /run/udev             /ngcp-fallback/run/udev
fi
if ! mountpoint /ngcp-fallback/ngcp-data; then
  mount           /dev/mapper/ngcp-data /ngcp-fallback/ngcp-data
fi

if mountpoint /boot; then
  umount /ngcp-fallback/boot || true
  cp -a -t /boot/ /ngcp-fallback/boot/{config-,initrd.,System.map,vmlinuz-}*
  boot_part=$(mount | awk '/on \/boot type/ {print $1}')
  mount "${boot_part}" /ngcp-fallback/boot
fi

if mountpoint /boot/efi; then
  umount /ngcp-fallback/boot/efi || true
  mkdir -p /ngcp-fallback/boot/efi

  if [[ "$(mount | grep -c ' on /boot/efi type')" -ne 1 ]] ; then
    echo "ERROR: Current mount point /boot/efi looks unexpected, make sure only *one* partition is mounted there" >&2
    echo "NOTE: Check output of 'mount' command, and if it's mounted more than once, unmount one partition on /boot/efi"
    exit 1
  fi

  efi_part=$(mount | awk '/on \/boot\/efi type/ {print $1}')
  mount "${efi_part}" /ngcp-fallback/boot/efi
fi

# The next code was copied from mr7.5/fix_grub_AKA mr7.5/run_grub_install with some changes

# ensure we don't get locale warnings from perl
export LC_ALL=C
export LANG=C

collect_system_information() {
  echo "### date ###"
  date || true

  echo "### fdisk -l ###"
  fdisk -l || true

  echo "### lsblk ###"
  lsblk || true

  echo "### ls -la /dev/disk/by-id/ ###"
  ls -la /dev/disk/by-id/ || true

  echo "### mount ###"
  mount || true

  echo "### pvs --noheadings --separator : ###"
  pvs --noheadings --separator : || true

  echo "### cat /proc/cmdline ###"
  cat /proc/cmdline || true

  echo "### cat /proc/mdstat ###"
  cat /proc/mdstat || true

  echo "### efibootmgr -v ###"
  efibootmgr -v || true

  echo "### chroot /ngcp-fallback dpkg -l | grep grub ###"
  chroot /ngcp-fallback dpkg -l | grep grub || true

  echo "### chroot /ngcp-fallback debconf-show show ${GRUB_PACKAGE:-grub-pc} ###"
  chroot /ngcp-fallback debconf-show "${GRUB_PACKAGE:-grub-pc}" || true
}

usage_information() {
  local filename
  filename="/var/log/ngcp-upgrade-grub-information-$(date +%Y-%m-%d).txt"

  cat << EOF

NOTE: this script ($0) failed to execute for some reason.

Collecting system information in ${filename}
Please report all of this output as well as ${filename} to the Sipwise team.
EOF

  # append if file should exist already
  collect_system_information >> "${filename}" 2>&1 || true
}

get_grub_package() {
  if chroot /ngcp-fallback dpkg-query -s grub-pc &>/dev/null; then
    GRUB_PACKAGE=grub-pc
  elif chroot /ngcp-fallback dpkg-query -s grub-efi-amd64 &>/dev/null; then
    GRUB_PACKAGE=grub-efi-amd64
  else
    die "couldn't identify /ngcp-fallback grub package"
  fi
}

get_orig_debconf_settings() {
  local orig_setting

  if chroot /ngcp-fallback debconf-show "${GRUB_PACKAGE}" | grep -q '/install_devices: '; then
    orig_setting=$(debconf-show "${GRUB_PACKAGE}" | awk -F: '/\/install_devices: / {print $2}' | sed 's/^\s\+//')
    echo "Debconf configuration install_devices for ${GRUB_PACKAGE} currently set to '${orig_setting}'"
  else
    echo "Debconf configuration install_devices for ${GRUB_PACKAGE} seems to be unset"
  fi

  if chroot /ngcp-fallback debconf-show "${GRUB_PACKAGE}" | grep -q '/install_devices_disks_changed: '; then
    orig_setting=$(debconf-show "${GRUB_PACKAGE}" | awk -F: '/\/install_devices_disks_changed: / {print $2}' | sed 's/^\s\+//')
    echo "Debconf configuration install_devices_disks_changed for ${GRUB_PACKAGE} currently set to '${orig_setting}'"
  else
    echo "Debconf configuration install_devices_disks_changed for ${GRUB_PACKAGE} seems to be unset"
  fi
}

get_block_device() {
  local device="$1"

  local part_info
  part_info="$(udevadm info -n "${device}" -q path)"  # e.g. "/devices/pci0000:00/0000:00:0a.0/virtio1/block/vda/vda1"

  case "${part_info}" in
    /devices/virtual/block/md*)
      local md_device="/dev/${part_info##*/}"  # e.g. "/dev/md0"
      local swraid_devices
      swraid_devices=$(mdadm --detail --export "${md_device}" | awk -F= '/MD_DEVICE_[d]?ev_.*_DEV/ {print $2}')  # MD_DEVICE_ev_sdb3_DEV=/dev/sdb3 or MD_DEVICE_dev_sdb3_DEV=/dev/sdb3
      for dev in ${swraid_devices} ; do
        local parent_device
        parent_device=$(dirname "$(udevadm info -n "${dev}" -q path)")  # e.g. /devices/pci0000:00/0000:00:05.0/0000:01:02.0/virtio4/host3/target3:0:0/3:0:0:1/block/sdb
        echo "/dev/${parent_device##*/}"  # e.g. "/dev/sdb"
      done
    ;;
    *)
      local disk="${part_info%/*}"  # e.g. "/devices/pci0000:00/0000:00:0a.0/virtio1/block/vda"
      echo "/dev/${disk##*/}"       # e.g. "/dev/vda"
    ;;
  esac
}

efi_support() {
  # Absence of an EFI runtime does not prevent loading efivarfs since commit
  # 301de9a2055357375a4e1053d9df0f8f3467ff00 which landed in Linux v6.3.
  # Unconditionally load it, in case nothing else attempted it.
  modprobe efivarfs &>/dev/null || true

  if [[ -d /sys/firmware/efi ]] ; then
    echo "NOTE: EFI support detected."
    return 0
  fi

  return 1
}

update_efi() {
  if ! [ -f /boot/efi/EFI/debian/grubx64.efi ] ; then
    echo "WARN: no /boot/efi/EFI/debian/grubx64.efi present, skipping update_efi."
    return 0
  fi

  # identify underlying block device of where /boot/efi/EFI/debian/grubx64.efi is stored
  local source_partition
  source_partition="$(df --local --output=source /boot/efi/EFI/debian/grubx64.efi | tail -1)"

  # identify EFI system partitions, that aren't mounted
  for disk in $(lsblk --list -o PATH,FSTYPE,PARTTYPE,MOUNTPOINT | \
    awk '$2 == "vfat" && $3 == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" && $4 == "" {print $1}') ; do

    echo "Checking EFI partition '${disk}' for /EFI/debian/grubx64.efi."

    if ! [[ -b "${disk}" ]] ; then
      echo "WARN: '${disk}' doesn't look like a valid block device, ignoring."
      continue
    fi

    local mount_dir
    mount_dir=$(mktemp -d)
    if ! mount "${disk}" "${mount_dir}" ; then
      echo "WARN: failed to mount '${disk}', ignoring."
      continue
    fi

    if ! [[ -f "${mount_dir}/EFI/debian/grubx64.efi" ]] ; then
      echo "WARN: no /EFI/debian on '${disk}' present, ignoring."
    else
      if cmp -s /boot/efi/EFI/debian/grubx64.efi "${mount_dir}/EFI/debian/grubx64.efi" ; then
        echo "File /EFI/debian/grubx64.efi on '${disk}' matches /boot/efi/EFI/debian/grubx64.efi on '${source_partition:-}', nothing to do."
      else
        echo "File /EFI/debian/grubx64.efi on '${disk}' differs from /boot/efi/EFI/debian/grubx64.efi on '${source_partition:-}', updating."
        cp /boot/efi/EFI/debian/grubx64.efi "${mount_dir}/EFI/debian/grubx64.efi"
      fi
    fi

    umount "${mount_dir}"
    rmdir "${mount_dir}"
  done
}

get_grub_devices() {
  local boot_disk
  if grep -q 'root=/dev/mapper/ngcp-' /proc/cmdline; then  # running on LVM
    boot_disk="$(pvs --noheadings --separator : | awk -F: '/:ngcp:/ {print $1; exit}' | tr -d ' ')"  # e.g. "/dev/sda1"; limit to first match only
  else
    local uuid
    uuid="$(sed 's/.*root=UUID=\([0-9a-z-]*\) .*/\1/' /proc/cmdline)" # e.g. "d0454f66-40cc-4903-920e-6c724b72bb89"
    boot_disk="$(blkid -U "${uuid}")"                                 # e.g. /dev/vda1
  fi

  get_block_device "${boot_disk}"
}

validate_bootoption_root() {
  if grep -q 'root=/dev/mapper/ngcp-' /proc/cmdline; then
    echo "NOTE: NGCP system running with LVM detected"
  elif grep -qE 'root=UUID=[0-9a-z-]+' /proc/cmdline; then
    echo "NOTE: NGCP system without LVM detected"
  else
    usage_information
    die "system not running from /dev/mapper/ngcp-* nor root=UUID=…, please report this problem"
  fi
}

validate_grub_disk() {
  local grub_disk="$1"

  if ! [[ -b "${grub_disk}" ]]; then
    usage_information
    die "GRUB disk ${grub_disk} doesn't seem to be a valid block device"
  fi

  local signature
  signature="$(dd if="${grub_disk}" bs=1024 count=1 2>/dev/null| tr -d '\0')"
  if ! echo "${signature}" | grep -aEq 'EFI PART|GRUB'; then
    return 1
  fi

  return 0
}


get_install_device() {
  local grub_disk="$1"
  local install_device

  # GRUB expects a disk like /dev/disk/by-id/ata-VBOX_HARDDISK_VBa0d04d8f-ae7cc8ec
  # if a corresponding device is present, otherwise it accepts the fallback to
  # something like /dev/vda (e.g. happening when upgrading from 2.8 LTS systems
  # where disks aren't listed in /dev/disk/by-id/ yet)
  for disk in /dev/disk/by-id/*; do
    case "$(realpath "${disk}")" in
      "${grub_disk}")
        install_device="${disk}"
      ;;
    esac
  done

  if [[ -n "${install_device:-}" ]]; then
    echo "NOTE: Identified ${install_device} matching ${grub_disk} as GRUB disk" >&2
  else
    install_device="${grub_disk}"
    echo "WARN: couldn't find a corresponding /dev/disk/by-id/* device for ${grub_disk}, falling back to ${install_device} instead" >&2
  fi

  echo "${install_device}"
}

apply_debconf_settings() {
  local grub_setting

  if [[ "$#" -eq 1 ]]; then  # one single device
    grub_setting="$1"
  else  # multiple devices
    # debconf expects a list like "/dev/disk/by-id/scsi-foo, /dev/disk/by-id/scsi-bar"
    # without any further extra white space characters
    declare -a __grub=()
    for i in "$@" ; do
      __grub+=( "$i,")
    done
    grub_setting="${__grub[*]}"
    grub_setting="${grub_setting%,}"  # strip trailing "," for debconf
    unset __grub
  fi

  echo "Setting debconf configuration for '${GRUB_PACKAGE}/install_devices' to '${grub_setting}'"
  echo "${GRUB_PACKAGE} ${GRUB_PACKAGE}/install_devices multiselect ${grub_setting}" | \
    chroot /ngcp-fallback debconf-set-selections

  if chroot /ngcp-fallback debconf-show "${GRUB_PACKAGE}" | grep -q '/install_devices_disks_changed: ' ; then
    echo "Setting debconf configuration for '${GRUB_PACKAGE}/install_devices_disks_changed' to '${grub_setting}'"
    echo "${GRUB_PACKAGE} ${GRUB_PACKAGE}/install_devices_disks_changed multiselect ${grub_setting}" | \
      chroot /ngcp-fallback debconf-set-selections
  else
    echo "debconf configuration for '${GRUB_PACKAGE}/install_devices_disks_changed' was unset, not setting therefore"
  fi

  if efi_support && ! ls /ngcp-fallback/sys/firmware/efi/efivars/* &>/dev/null ; then
    echo "Mounting efivarfs on /ngcp-fallback/sys/firmware/efi/efivars for dpkg-reconfigure usage"
    mount -t efivarfs efivarfs /ngcp-fallback/sys/firmware/efi/efivars
  fi

  echo "Executing 'chroot /ngcp-fallback dpkg-reconfigure ${GRUB_PACKAGE}' now:"
  DEBIAN_FRONTEND='noninteractive' chroot /ngcp-fallback dpkg-reconfigure "${GRUB_PACKAGE}"

  umount /ngcp-fallback/sys/firmware/efi/efivars &>/dev/null || true
}

grub_install() {
  local devices="$*"

  if efi_support && ! ls /ngcp-fallback/sys/firmware/efi/efivars/* &>/dev/null ; then
    echo "Mounting efivarfs on /ngcp-fallback/sys/firmware/efi/efivars for grub-install usage"
    mount -t efivarfs efivarfs /ngcp-fallback/sys/firmware/efi/efivars
  fi

  for dev in ${devices}; do
    echo "Invoking 'chroot /ngcp-fallback grub-install ${dev}' now:"
    chroot /ngcp-fallback grub-install "${dev}"
  done

  umount /ngcp-fallback/sys/firmware/efi/efivars &>/dev/null || true
}

# main execution
validate_bootoption_root
get_grub_package
get_orig_debconf_settings

GRUB_FOUND=false
declare -a INSTALL_DEVICES=()
for dev in $(get_grub_devices); do
  if ! validate_grub_disk "${dev}"; then
    echo "Disk '${dev}' doesn't have GRUB or EFI signature, skipping it..."
    continue
  fi

  install_device="$(get_install_device "${dev}")"
  INSTALL_DEVICES+=( "${install_device}" )

  grub_install "${dev}"
  GRUB_FOUND=true
done

if ! "${GRUB_FOUND}"; then
  usage_information
  die "Can't find any device with installed GRUB"
fi

update_efi

apply_debconf_settings "${INSTALL_DEVICES[@]}"

chroot /ngcp-fallback update-grub

echo "Finished reconfiguration of /ngcp-fallback ${GRUB_PACKAGE}"
