#! /usr/bin/env bash
#
# Copyright 2020 Northern.tech AS
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

# Default that can be overridden by providing this method in a
# configuration file passed with '--config'
function platform_modify() {
  true
}
PLATFORM_MODIFY_HOOKS=(platform_modify)

function user_local_modify() {
  true
}
USER_LOCAL_MODIFY_HOOKS=(user_local_modify)

function overlay_modify() {
  true
}
OVERLAY_MODIFY_HOOKS=(overlay_modify)

function trap_exit() {
  echo "mender-convert-modify has finished. Cleaning up..."
  sudo umount -f work/boot
  sudo umount -f work/rootfs
}

function trap_term() {
  true
}

trap trap_term INT TERM
trap trap_exit EXIT

echo "Running $(basename $0): $@"

source modules/bootstrap.sh
source modules/disk.sh
source modules/probe.sh
source modules/deb.sh

# The mender_convert_config is always used and provides all the defaults
declare -a configs=("configs/mender_convert_config")

while (( "$#" )); do
  case "$1" in
    -o | --overlay)
      overlays+=("${2}")
      shift 2
      ;;
    -c | --config)
      configs+=("${2}")
      shift 2
      ;;
    -d | --disk-image)
      disk_image="${2}"
      shift 2
      ;;
    *)
      log_fatal "Sorry, but the provided option is not supported: $1"
      ;;
  esac
done

# Note the use of %q formatting here. This is a bash feature to add
# proper quoting to the strings so that spaces and special characters
# will be treated properly.  Primarily for supporting spaces in
# pathnames and avoid splitting those into multiple parameters.
source modules/config.sh $(printf "%q " "${configs[@]}")

boot_part=$(disk_boot_part)
root_part=$(disk_root_part)

# Sysfs device paths
boot_part_device=$(disk_boot_part_device)
data_part_device=$(disk_data_part_device)
root_part_a_device=$(disk_root_part_a_device)
root_part_b_device=$(disk_root_part_b_device)

# Create mount points
mkdir -p work/boot
mkdir -p work/rootfs

sudo mount ${boot_part} work/boot
sudo mount ${root_part} work/rootfs

mkdir -p work/mender-client-deb/files

log_info "Installing Mender client and related files"

deb_arch=$(probe_debian_arch_name)

if [ "${MENDER_CLIENT_VERSION}" = "latest" ]; then
  deb_name=$(deb_from_repo_get "work/mender-client-deb" ${MENDER_APT_REPO_URL} ${deb_arch} "stable" "mender-client" "latest")
elif [ "${MENDER_CLIENT_VERSION}" = "master" ]; then
  deb_name=$(deb_from_repo_get "work/mender-client-deb" ${MENDER_APT_REPO_URL} ${deb_arch} "experimental" "mender-client" "latest")
else
  DEBIAN_REVISION="-1"
  deb_name=$(deb_from_repo_get "work/mender-client-deb" ${MENDER_APT_REPO_URL} ${deb_arch} "experimental" "mender-client" "${MENDER_CLIENT_VERSION}${DEBIAN_REVISION}")
fi

cd work/mender-client-deb
run_and_log_cmd "ar -xv ${deb_name}"
run_and_log_cmd "sudo tar xJf data.tar.xz -C files"
cd - > /dev/null 2>&1

run_and_log_cmd "sudo rsync --archive --keep-dirlinks --verbose work/mender-client-deb/files/ work/rootfs/"

if [ "${MENDER_ENABLE_SYSTEMD}" == "y" ]; then
  run_and_log_cmd "sudo ln -sf /lib/systemd/system/mender-client.service \
        work/rootfs/etc/systemd/system/multi-user.target.wants/mender-client.service"
fi

if [ "${MENDER_GRUB_EFI_INTEGRATION}" == "y" ]; then
  # Check for known U-Boot problems in all files on the boot partition.
  check_for_broken_uboot_uefi_support work/boot

  run_and_log_cmd "wget -Nq '${MENDER_GRUBENV_URL}' -P work/"
  run_and_log_cmd "tar xzvf work/${MENDER_GRUBENV_VERSION}.tar.gz -C work/"

  if [ -z "${MENDER_GRUB_KERNEL_IMAGETYPE}" ]; then
    kernel_imagetype=$(probe_kernel_in_boot_and_root)
  else
    kernel_imagetype="${MENDER_GRUB_KERNEL_IMAGETYPE}"
  fi

  if [ -z "${MENDER_GRUB_INITRD_IMAGETYPE}" ]; then
    initrd_imagetype=$(probe_initrd_in_boot_and_root)
  else
    initrd_imagetype="${MENDER_GRUB_INITRD_IMAGETYPE}"
  fi

  cat <<- EOF > work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}/mender_grubenv_defines
mender_rootfsa_part=${MENDER_ROOTFS_PART_A_NUMBER}
mender_rootfsb_part=${MENDER_ROOTFS_PART_B_NUMBER}
mender_grub_storage_device=${MENDER_GRUB_STORAGE_DEVICE}
kernel_imagetype=kernel
initrd_imagetype=initrd
EOF

  run_and_log_cmd "ln -s ${kernel_imagetype} work/rootfs/boot/kernel"
  if [ "${initrd_imagetype}" != "" ]; then
    run_and_log_cmd "ln -s ${initrd_imagetype} work/rootfs/boot/initrd"
  fi

  # For partuuid support grub.cfg expects dedicated variables to be added
  if [ "${MENDER_ENABLE_PARTUUID}" == "y" ]; then
    rootfsa_partuuid=$(disk_get_partuuid_from_device "${root_part_a_device}")
    rootfsb_partuuid=$(disk_get_partuuid_from_device "${root_part_b_device}")
    log_info "Using root partition A partuuid in grubenv: $rootfsa_partuuid"
    log_info "Using root partition B partuuid in grubenv: $rootfsb_partuuid"
    cat <<- EOF >> work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}/mender_grubenv_defines
mender_rootfsa_uuid=${rootfsa_partuuid}
mender_rootfsb_uuid=${rootfsb_partuuid}
EOF
  else
    cat <<- EOF >> work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}/mender_grubenv_defines
mender_kernel_root_base=${MENDER_STORAGE_DEVICE_BASE}
EOF
  fi

  if [ -n "${MENDER_GRUB_KERNEL_BOOT_ARGS}" ]; then
    cat <<- EOF > work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}/11_bootargs_grub.cfg
set bootargs="${MENDER_GRUB_KERNEL_BOOT_ARGS}"
EOF
  fi

  cd work/grub-mender-grubenv-${MENDER_GRUBENV_VERSION}
  run_and_log_cmd "make 2>&1"
  run_and_log_cmd "sudo make DESTDIR=../ BOOT_DIR=boot install-boot-files"
  run_and_log_cmd "sudo make DESTDIR=../rootfs install-tools"
  cd - > /dev/null 2>&1

  # Remove conflicting boot files. These files do not necessarily effect the
  # functionality, but lets get rid of them to avoid confusion.
  #
  # There is no Mender integration for EFI boot or systemd-boot.
  sudo rm -rf work/boot/loader work/rootfs/boot/loader
  sudo rm -rf work/boot/EFI/Linux
  sudo rm -rf work/boot/EFI/systemd
  sudo rm -rf work/boot/NvVars
  for empty_dir in $(cd work/boot; find . -maxdepth 1 -type d -empty); do
    sudo rmdir work/boot/$empty_dir
    sudo rm work/rootfs/boot/$empty_dir
  done

  log_info "Installing GRUB"

  arch=$(probe_arch)
  efi_name=$(probe_grub_efi_name)
  efi_target_name=$(probe_grub_efi_target_name)

  log_info "GRUB EFI: ${efi_target_name}"

  run_and_log_cmd "wget -Nq ${MENDER_GRUB_BINARY_STORAGE_URL}/${arch}/${efi_name} -P work/"
  run_and_log_cmd "wget -Nq ${MENDER_GRUB_BINARY_STORAGE_URL}/${arch}/grub-editenv -P work/"

  run_and_log_cmd "sudo install -m 751 work/grub-editenv work/rootfs/usr/bin/"

  run_and_log_cmd "sudo mkdir -p work/boot/EFI/BOOT"
  run_and_log_cmd "sudo cp work/${efi_name} -P work/boot/EFI/BOOT/${efi_target_name}"
fi

# Copy dtb directory to the boot partition for use by the bootloader.
if [ -d work/rootfs/boot/dtbs ]; then
  # Look for the first directory that has dtb files. First check the base
  # folder, then any subfolders in versioned order.
  for candidate in work/rootfs/boot/dtbs $(find work/rootfs/boot/dtbs/ -maxdepth 1 -type d | sort -V -r); do
    if [ $(find $candidate -maxdepth 1 -name '*.dtb' | wc -l) -gt 0 ]; then
      run_and_log_cmd "sudo cp -r $candidate work/boot/dtb"
      break
    fi
  done
fi

run_and_log_cmd "sudo mkdir -p work/rootfs/data/mender"
run_and_log_cmd "sudo ln -sf /data/mender work/rootfs/var/lib/mender"

log_info "Using root device A in mender.conf: $root_part_a_device"
log_info "Using root device B in mender.conf: $root_part_b_device"

cat <<- EOF > work/mender.conf.data
{
  "RootfsPartA": "${root_part_a_device}",
  "RootfsPartB": "${root_part_b_device}"
}
EOF

run_and_log_cmd "sudo cp work/mender.conf.data work/rootfs/data/mender/mender.conf"
run_and_log_cmd "sudo chmod 600 work/rootfs/data/mender/mender.conf"

if [ -z "${MENDER_DEVICE_TYPE}" ]; then
  # Observed systems who do not have this file, e.g images generated with mkosi
  if [ -f work/rootfs/etc/hostname ]; then
    device_type=$(cat work/rootfs/etc/hostname)
  else
    device_type="default"
  fi
else
  device_type="${MENDER_DEVICE_TYPE}"
fi

run_and_log_cmd "echo 'device_type=${device_type}' > work/device_type"
run_and_log_cmd "sudo install -m 0444 work/device_type work/rootfs/data/mender/"
run_and_log_cmd "echo 'artifact_name=${MENDER_ARTIFACT_NAME}' \
  | sudo tee work/rootfs/etc/mender/artifact_info"

log_info "Creating state scripts version file."
case "${MENDER_CLIENT_VERSION}" in
    1* ) VERSION_STRING='2';;
    *  ) VERSION_STRING='3';;
esac
run_and_log_cmd "sudo mkdir -p work/rootfs/etc/mender/scripts/"
run_and_log_cmd "echo -n ${VERSION_STRING} | sudo tee work/rootfs/etc/mender/scripts/version"

log_info "Installing a custom /etc/fstab (see work/convert.log for more info)"

if [ "${MENDER_GRUB_EFI_INTEGRATION}" == "y" ]; then
  boot_part_mountpoint="/boot/efi"
else
  boot_part_mountpoint="/uboot"
fi

run_and_log_cmd "sudo mkdir -p work/rootfs/${boot_part_mountpoint}"

if [ "${MENDER_DATA_PART_GROWFS}" == "y" ]; then
  MENDER_DATA_PART_FSTAB_OPTS="${MENDER_DATA_PART_FSTAB_OPTS},x-systemd.growfs"
fi

if echo ${MENDER_ROOT_PART_FSTAB_OPTS} | tr ',' '\n' | egrep -q "^ro$"; then
  if ! echo "${MENDER_ROOT_PART_MKFS_OPTS}" | fgrep -q -- "-O ^64bit"; then
    log_warn "EXT4 64bits feature is known to create unstable checksums on read-only file systems, add \"-O ^64bit\" to MENDER_ROOT_PART_MKFS_OPTS to remove it"
  fi
  if ! echo "${MENDER_ROOT_PART_MKFS_OPTS}" | fgrep -q -- "-O ^has_journal"; then
    log_warn "EXT4 journal feature is known to create unstable checksums on read-only file systems, add \"-O ^has_journal\" to MENDER_ROOT_PART_MKFS_OPTS to remove it"
  fi
fi

log_info "Using boot partition device in fstab: $boot_part_device"
log_info "Using data partition device in fstab: $data_part_device"

sudo bash -c "cat <<- EOF > work/rootfs/etc/fstab
# stock fstab - you probably want to override this with a machine specific one
/dev/root            /                    auto       ${MENDER_ROOT_PART_FSTAB_OPTS} 1  ${MENDER_ROOT_PART_FS_PASSNO}
proc                 /proc                proc       defaults              0  0

${boot_part_device}   ${boot_part_mountpoint}          auto       defaults,sync    0  0
${data_part_device}   /data          auto       ${MENDER_DATA_PART_FSTAB_OPTS}      0  0
EOF"

#
# Make sure to re-label rootfs when selinux is in enforcing mode
# e.g. CentOS8 after conversion cannot start login shell due selinux
# inspired by: https://forums.centos.org/viewtopic.php?t=48714
#
if [ -f work/rootfs/etc/selinux/config ]; then
  grep -r 'SELINUX=Enforcing' work/rootfs/etc/selinux/config || true
  if [ $? -eq 0 ]; then
    log_info "Selinux is in enforcing mode. Enable autorelabel"
    touch work/rootfs/.autorelabel
  fi
fi

log_info "Performing platform specific modifications (if any)"
for hook in "${PLATFORM_MODIFY_HOOKS[@]}"; do
  log_info "Running hook: $hook"
  eval $hook
done

log_info "Performing user/local specific modifications (if any)"
for hook in "${USER_LOCAL_MODIFY_HOOKS[@]}"; do
  log_info "Running hook: $hook"
  eval $hook
done

for overlay in "${overlays[@]}"; do
  log_info "Applying rootfs overlay: ${overlay}"
  run_and_log_cmd "sudo rsync --archive --keep-dirlinks --verbose ${overlay}/ work/rootfs/"
done

log_info "Performing overlay specific modifications (if any)"
for hook in "${OVERLAY_MODIFY_HOOKS[@]}"; do
  log_info "Running hook: $hook"
  eval $hook
done