#!/bin/bash

set -eu
deviceid_url=""
chosen_preseed=""

GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C='Puavo_Installed_Windows_C'
GPT_PARTTYPE_WINDOWS_RE='de94bba4-06d1-4d40-a16a-bfd50179d6ac'

ask_install_target_hosttype() {
  puavo_install_target_hosttype=laptop
  while true; do
    echo 'Choose hosttype (possible choices are: bootserver laptop)' >&2
    read -p "  Target hosttype? [$puavo_install_target_hosttype]: " answer
    [ -n "$answer" ] && puavo_install_target_hosttype=$answer

    case "$puavo_install_target_hosttype" in
      bootserver|laptop) break                              ;;
      *) echo "'$answer' is not a supported hosttype\n" >&2 ;;
    esac
  done

  echo "$puavo_install_target_hosttype"
}

get_preseed_variable() {
  local chosen_preseed preseed_json preseed_var preseed_value
  preseed_var=$1
  chosen_preseed=$2
  preseed_json=$3

  preseed_value=$(
    printf %s "$preseed_json" \
      | jq -r --arg chosen_preseed "$chosen_preseed" \
              --arg preseed_var "$preseed_var" '
          if (.preseeds[$chosen_preseed] | has($preseed_var)) then
            .preseeds[$chosen_preseed][$preseed_var]
          elif (.preseeds[$chosen_preseed] | has("template"))
            and (.templates[ .preseeds[$chosen_preseed].template ]
                   | has($preseed_var)) then
              .templates[ .preseeds[$chosen_preseed].template ][$preseed_var]
          else
            ""
          end
        ')

  printf %s "$preseed_value"
}

ask_preseed() {
  if ! image_server=$(/usr/lib/puavo-ltsp-client/lookup-image-server-by-dns); then
     echo 'Could not lookup image server, preseed not available.' >&2
     return 1
  fi

  deviceid_url="https://${image_server}/deviceid"
  preseed_url="https://${image_server}/preseeds/index.json"
  if ! preseed_json=$(curl --cacert /etc/puavo-conf/rootca.pem --fail \
                        --show-error --silent "$preseed_url"); then
    echo "Could not fetch preseed from ${preseed_url}" >&2
    return 1
  fi

  if ! preseed_choices=$(printf %s "$preseed_json" \
                           | jq -r '.preseeds | keys | .[]') \
    || [ -z "$preseed_choices" ]; then
      echo "Could not read preseed choices from json" >&2
      return 1
  fi

  if ! forced_preseed=$(printf %s "$preseed_json" \
                          | jq -r '.["force-preseed"]'); then
    echo "Could not read forced preseed from json" >&2
    return 1
  fi
  if [ "$forced_preseed" = 'null' ]; then
    forced_preseed=''
  fi

  echo
  chosen_preseed=''
  if [ -n "$forced_preseed" ]; then
    if printf %s "$preseed_choices" | grep -qw "$forced_preseed"; then
      echo -n "Forcing a preseed: '${forced_preseed}' "
      sleep 1; echo -n .; sleep 1; echo -n .; sleep 1; echo -n .; echo ' GO!'
      chosen_preseed="$forced_preseed"
    else
      echo "Could not force preseed '${forced_preseed}', it does not exist!" >&2
    fi
  fi
  if [ -z "$chosen_preseed" ]; then
    echo 'Choose a preseed:'
    if ! chosen_preseed=$(printf %s "$preseed_choices" \
                            | fzf --height=1 --layout=reverse-list) \
      || [ -z "$chosen_preseed" ]; then
        echo 'Problem when choosing preseed' >&2
        return 1
    fi
    printf "You have chosen preseed: %s\n" "$chosen_preseed"
  fi

  preseed_admin_user=$(get_preseed_variable admin_user "$chosen_preseed" \
                         "$preseed_json")
  preseed_admin_password=$(get_preseed_variable admin_password \
                             "$chosen_preseed" "$preseed_json")
  preseed_devicetype=$(get_preseed_variable devicetype \
                          "$chosen_preseed" "$preseed_json")
  preseed_puavo_server=$(get_preseed_variable server \
                          "$chosen_preseed" "$preseed_json")
  preseed_force_reg_defaults=$(get_preseed_variable force-register-defaults \
                                 "$chosen_preseed" "$preseed_json")

  puavo_preseed_register_args=()
  if [ -n "$preseed_admin_user" ]; then
    puavo_preseed_register_args+=('--username' "$preseed_admin_user")
  fi
  if [ -n "$preseed_admin_password" ]; then
    puavo_preseed_register_args+=('--password' "$preseed_admin_password")
  fi
  if [ -n "$preseed_devicetype" ]; then
    puavo_preseed_register_args+=('--devicetype' "$preseed_devicetype")
  fi
  if [ -n "$preseed_puavo_server" ]; then
    puavo_preseed_register_args+=('--puavoserver' "$preseed_puavo_server")
  fi
  if [ "$preseed_force_reg_defaults" = 'true' ]; then
    puavo_preseed_register_args+=('--force-defaults')
  fi
  puavo_preseed_puavo_key_list=$(
    printf %s "$preseed_json" | jq -r --arg chosen_preseed "$chosen_preseed" \
                                  '.preseeds[$chosen_preseed] | keys | .[]' \
                              | grep '^puavo')
  for puavo_preseed_key in $puavo_preseed_puavo_key_list; do
    puavo_preseed_value=$(get_preseed_variable "$puavo_preseed_key" \
                            "$chosen_preseed" "$preseed_json")
    puavo_preseed_register_args+=("--${puavo_preseed_key}" "$puavo_preseed_value")
  done

  puavo_fs_setup_keys='
    ask-disk-device          force-disk-device
    ask-imageoverlay-size    force-imageoverlay-size
    ask-install-without-raid force-install-without-raid
    ask-partition            force-partition
    ask-setup-windows        force-setup-windows
    ask-unpartitioned-space  force-unpartitioned-space
    ask-wipe-partition       force-wipe-partition
    ask-write-partitions     force-write-partitions
  '
  for puavo_preseed_key in $puavo_fs_setup_keys; do
    puavo_preseed_value=$(get_preseed_variable "$puavo_preseed_key" \
                            "$chosen_preseed" "$preseed_json")
    if [ -n "$puavo_preseed_value" ]; then
      puavo_preseed_setup_filesystems_args+=("--${puavo_preseed_key}" "$puavo_preseed_value")
    fi
  done
}

bootserver_setup_state() {
  if ! puavo-set-root-password; then
    echo 'Could not setup root password for bootservers' >&2
    return 1
  fi

  # this affects setup_krb5kdc and puavo-init-ds-slave
  export PUAVO_BOOTSERVER_INSTALLATION=true

  /etc/puavo-conf/scripts/setup_hostname
  /etc/puavo-conf/scripts/setup_state_partition
  /etc/puavo-conf/scripts/setup_bootserver_persistent_net_rules

  echo 'Wrote /etc/udev/rules.d/70-persistent-net.rules:'
  echo ---
  cat /etc/udev/rules.d/70-persistent-net.rules
  echo ---
  echo 'Spawning shell for you if you want to edit it, type "exit" when ok'
  bash
  echo 'Running puavo-sysmerge, remember to run it if you make changes to'
  echo '/etc/udev/rules.d/70-persistent-net.rules.'

  # XXX this is ugly but puavo-sysmerge needs this for proper networking
  # XXX setup and bootserver profile is not yet effective (or we do
  # XXX networking setup after first boot but that is also ugly)
  puavo-conf puavo.networking.ddns.dhcpd_interfaces 'ltsp*,wlan*'

  puavo-sysmerge --auto

  /etc/puavo-conf/scripts/setup_slapd
  /etc/puavo-conf/scripts/setup_krb5kdc

  puavo-init-ds-slave
}

do_preinstall() {
  puavo_install_hosttype=$1

  if is_preinstalled "$puavo_install_hosttype"; then
    echo 'Can not do a preinstall from a preinstalled host' >&2
    return 1
  fi

  echo
  echo 'Welcome to puavo device preinstallation!'
  echo

  puavo_install_target_hosttype=$(ask_install_target_hosttype)

  echo
  puavo-setup-filesystems --hosttype "$puavo_install_target_hosttype" \
                          ${puavo_preseed_setup_filesystems_args[@]}

  maybe_install_windows

  # Grub needs to be installed before images, because grub
  # configuration is updated during image installation by
  # puavo-image-preinst.
  install_grub --hosttype preinstalled

  # Setup Grub environment for preinstalled so that Windows can be booted
  # even when we have Puavo OS only preinstalled.
  /etc/puavo-conf/scripts/setup_grub_environment || {
    echo 'ERROR: failed to setup grub environment' >&2
    return 1
  }

  install_image "$puavo_install_hosttype" --hosttype preinstalled

  echo "$puavo_install_target_hosttype" > /images/puavo_preinstalled

  do_umounts
}

do_umounts() {
  umounts_status=0

  for mntpoint in /home /imageoverlays /images /state /tmp; do
    if mountpoint -q "$mntpoint"; then
      umount -l "$mntpoint" || umounts_status=1
    fi
  done

  return $umounts_status
}

get_accepted_hosttypes() {
  puavo_install_hosttype=$1

  if [ "$puavo_install_hosttype" = 'preinstalled' ]; then
    get_preinstalled_hosttype
    return 0
  fi

  echo 'bootserver,fatclient,laptop'
}

get_install_hosttype() {
  mkdir -p /run/puavo
  if [ ! -e /run/puavo/install_hosttype ]; then
    cp /etc/puavo/hosttype /run/puavo/install_hosttype
  fi

  puavo_install_hosttype="$(cat /run/puavo/install_hosttype)"

  case "$puavo_install_hosttype" in
    diskinstaller|preinstalled|unregistered)
      echo "$puavo_install_hosttype"
      ;;
    *)
      echo "'${puavo_install_hosttype}' is not a supported hosttype for" \
	   "installation" >&2
      return 1
      ;;
  esac
}

get_preinstalled_hosttype() {
  preinstalled_hosttype=$(cat /images/puavo_preinstalled 2>/dev/null || true)
  case "$preinstalled_hosttype" in
    bootserver|laptop)
      echo "$preinstalled_hosttype"
      ;;
    *)
      echo 'Host is preinstalled, but does not have a supported hosttype' >&2
      return 1
      ;;
  esac
}

# Install Windows if Windows partitions created by us (puavo-setup-filesystems) exist.
maybe_install_windows() {
  local line ptuuid win_c_partition_devpath win_c_partition_devpath_count win_re_partition_devpath win_re_partition_devpath_count

  win_c_partition_devpath_count=0
  win_re_partition_devpath_count=0

  while read -r line; do
    if [ -z "${line}" ]; then
      continue
    fi
    win_c_partition_devpath="${line}"
    win_c_partition_devpath_count=$((win_c_partition_devpath_count + 1))
  done < <(lsblk -J -oPARTLABEL,PATH | jq -r --arg partlabel "${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C}" '.blockdevices[] | select(.partlabel == $partlabel).path') || {
    echo "Failed to check if there are ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions" >&2
    return 1
  }

  if [ ${win_c_partition_devpath_count} -eq 0 ]; then
    echo "INFO: Not installing Windows, ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions not found." >&2
    return 0
  elif [ ${win_c_partition_devpath_count} -gt 1 ]; then
    # This is very unexpected case: it means the partition table
    # contains multiple Windows RE partitions AND partitioning is made
    # by puavo-setup-filesystem. This is certainly a bug in
    # puavo-setup-filesystem!
    echo "ERROR: Not installing Windows, multiple (${win_c_partition_devpath_count}) ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions found!" >&2
    return 1
  fi

  ptuuid=$(lsblk -n -oPTUUID "${win_c_partition_devpath}") || {
    echo "ERROR: Failed to get PTUUID of '${win_c_partition_devpath}'" >&2
    return 1
  }

  while read -r line; do
    if [ -z "${line}" ]; then
      continue
    fi
    win_re_partition_devpath="${line}"
    win_re_partition_devpath_count=$((win_re_partition_devpath_count + 1))
  done < <(lsblk -J -oPTUUID,PARTTYPE,PATH | jq -r --arg ptuuid "${ptuuid}" --arg parttype "${GPT_PARTTYPE_WINDOWS_RE}" '.blockdevices[] | select(.parttype == $parttype and .ptuuid == $ptuuid).path') || {
    echo "Failed to check if there are Windows RE partitions" >&2
    return 1
  }

  if [ ${win_re_partition_devpath_count} -eq 0 ]; then
    echo "ERROR: Not installing Windows, because Windows RE partition is not found. It should exist though, because the Windows C partition '${win_c_partition_devpath}' is Puavo partitioned." >&2
    return 1
  elif [ ${win_re_partition_devpath_count} -gt 1 ]; then
    echo "ERROR: Not installing Windows, because multiple (${win_re_partition_devpath_count}) Windows RE partitions were found! I'm not able to make an informed guess which one to actually use." >&2
    return 1
  fi

  puavo-reset-windows --mode install-c-partition --force "${win_c_partition_devpath}" || return 1
  puavo-reset-windows --mode install-re-partition --force "${win_c_partition_devpath}" "${win_re_partition_devpath}" || return 1
  puavo-manage-efi install-windows "${win_c_partition_devpath}" || return 1

  return 0
}

install_grub() {
  echo 'Starting grub installation...'
  puavo-install-grub "$@"
  sync
  echo 'Finished grub installation.'
}

install_image() {
  puavo_install_hosttype=$1; shift

  if [ "$puavo_install_hosttype" = 'preinstalled' ]; then
    return 0
  fi

  ltspimage_name=$(cat /etc/puavo-image/name 2>/dev/null || true)
  if [ -z "$ltspimage_name" ]; then
    echo 'Could not determine the current ltsp image' >&2
    return 1
  fi

  case "$puavo_install_hosttype" in
    diskinstaller)
      puavo-install-and-update-ltspimages "$@"                 \
        --install-from-file "/installimages/${ltspimage_name}" \
        "$ltspimage_name"
      ;;
    unregistered)
      puavo-install-and-update-ltspimages "$@" \
        --install-from-nbd /dev/nbd0           \
        "$ltspimage_name"
      ;;
    *)
      echo 'I do not know from where to install an ltsp image' >&2
      return 1
      ;;
  esac

  sync
}

start_vpn() {
  puavo_hosttype=$1

  if [ -e /run/puavo/nbd-server ]; then
    # if we have booted from network, we can trust bootserver to handle
    # our special networking needs, so vpn is not necessary
    return 0
  fi

  # at installation time we use the "laptop"-style DNS/VPN configuration
  # even for bootservers
  puavo-conf puavo.service.dnsmasq.enabled false
  puavo-conf puavo.service.puavo-vpn-client-dnsmasq.enabled true

  # start openvpn connection
  echo -n 'Starting vpn connection'
  service puavo-vpn-client-dnsmasq start || true
  service puavo-vpn-client-openvpn start || true

  read ldapmaster < /etc/puavo/ldap/master
  for i in $(seq 10); do
    if host -W 5 "$ldapmaster" >/dev/null; then
      echo ' OK.'
      return 0
    fi
    echo -n .
    sleep "$i"
  done

  echo ' FAILED.'
  return 1
}

install_localbootdevice() {
  puavo_hosttype=$1
  puavo_install_hosttype=$2

  update_configuration_retstatus=0

  if is_preinstalled "$puavo_install_hosttype"; then
    if ! [ "$puavo_hosttype" = 'bootserver' \
             -o "$puavo_hosttype" = 'laptop' ]; then
      echo "Setting up preinstalled '$puavo_hosttype' is not supported" >&2
      return 1
    fi
  else
    puavo-setup-filesystems ${puavo_preseed_setup_filesystems_args[@]}
    maybe_install_windows
  fi

  echo 'Setting up Secure Boot...'
  /usr/lib/puavo-ltsp-install/puavo-setup-secure-boot

  setup_state "$puavo_hosttype"

  if is_preinstalled "$puavo_install_hosttype"; then
    # If host was preinstalled, the system will boot in 'preinstalled' mode
    # until we install new configurations. We also (re)install Grub because
    # it handles the EFI label and it might be missing in case we have been
    # installed from a restoredisk image.
    puavo-update-boot-disks --hosttype "$puavo_hosttype"
    # From now on the host will boot as a fully installed host.
    rm -f /images/puavo_preinstalled
  else
    # Grub needs to be installed before images, because grub
    # configuration is updated during image installation by
    # puavo-image-preinst.
    install_grub
  fi

  # these must work, otherwise we have serious problems
  # these are also required for VPN
  update_configuration update_device_json update_certificate
  # fix this as update_configuration messes this up (from the kernel cmdline):
  puavo-conf puavo.hosttype "$puavo_hosttype"

  # VPN is mandatory only for bootserver installations,
  # others will fail softly (installation should succeed,
  # possibly with some minor configuration issues)
  if ! start_vpn "$puavo_hosttype"; then
    if [ "$puavo_hosttype" = 'bootserver' ]; then
      echo 'VPN connection is required for bootserver install, exiting.' >&2
      return 1
    fi
  fi

  # other configuration update
  update_configuration || update_configuration_retstatus=1
  puavo-conf puavo.hosttype "$puavo_hosttype"

  install_image "$puavo_install_hosttype"
  update_images || update_configuration_retstatus=1

  if [ "$puavo_hosttype" = 'bootserver' ]; then
    bootserver_setup_state
    /usr/local/lib/puavo-handle-image-changes \
      || update_configuration_retstatus=1
  fi

  if [ "$puavo_hosttype" = 'laptop' \
    -a -n "$(puavo-conf puavo.abitti2.version 2>/dev/null || true)" ]; then
      /usr/local/sbin/puavo-update-abitti2-image \
        || update_configuration_retstatus=1
  fi

  if [ "$update_configuration_retstatus" -ne 0 ]; then
    return $update_configuration_retstatus
  fi

  do_umounts
}

is_preinstalled() { [ "$1" = 'preinstalled' ]; }

setup_state() {
  mkdir -p /state
  mountpoint -q /state || mount /dev/mapper/puavo-state /state

  # copy puavo dn/password and other information to state
  mkdir -p /state/etc
  cp -aT /etc/puavo /state/etc/puavo

  # copy benchmark results (if any) to state
  mkdir -p /state/var/lib/puavo
  cp -aT /var/lib/puavo /state/var/lib/puavo
}

update_configuration() {
  echo 'Updating configuration...'
  if /usr/lib/puavo-ltsp-install/update-configuration "$@"; then
    echo '  ... configuration updated.'
    echo
    return 0
  else
    echo '  ... a problem occurred when updating configuration.'
    echo
    return 1
  fi
}

update_images() {
  echo 'Trying image update...'
  if /usr/lib/puavo-ltsp-install/update-images false false; then
    echo '  ... image updated.'
    echo
    return 0
  else
    echo '  ... image update failed (may update it later).'
    echo
    return 1
  fi
}

# main

puavo_install_hosttype=$(get_install_hosttype)

if [ "$puavo_install_hosttype" = "diskinstaller" ]; then
  # Clear the TPM before installation and registeration,
  # because it might require going back to BIOS settings.
  /usr/lib/puavo-ltsp-install/puavo-prepare-tpm-configuration
fi

preinstall_only=false

# these might be filled later by ask_preseed subroutine
puavo_preseed_register_args=()
puavo_preseed_setup_filesystems_args=()

export UKI_MODE=1

install_mode=${1:-normal}

case "$install_mode" in
  normal|preinstall)
    puavo_preseed_setup_filesystems_args+=('--force-imageoverlay-size'   'default')
    puavo_preseed_setup_filesystems_args+=('--force-partition'           'default')
    puavo_preseed_setup_filesystems_args+=('--force-unpartitioned-space' 'default')
    puavo_preseed_setup_filesystems_args+=('--force-wipe-partition'      'default')
    puavo_preseed_setup_filesystems_args+=('--force-encryption'          'none')
    puavo_preseed_setup_filesystems_args+=('--force-recovery-key'   'yes')
    ;;
  crypt*)
    puavo_preseed_setup_filesystems_args+=('--force-imageoverlay-size'   'default')
    puavo_preseed_setup_filesystems_args+=('--force-partition'           'default')
    puavo_preseed_setup_filesystems_args+=('--force-unpartitioned-space' 'default')
    puavo_preseed_setup_filesystems_args+=('--force-wipe-partition'      'default')
    puavo_preseed_setup_filesystems_args+=('--force-encryption'          'tpm-with-recovery-key')
    puavo_preseed_setup_filesystems_args+=('--force-recovery-key'   'no')
    ;;
  preseed-*)
    ask_preseed
    ;;
esac

case "$install_mode" in
  crypt|crypt-install|expert|expert-install|normal|preseed-install)
    ;;
  *-preinstall|preinstall)
    do_preinstall "$puavo_install_hosttype"
    exit 0
    ;;
  *)
    echo "Unknown installation mode: '${install_mode}'" >&2
    exit 1
    ;;
esac

# Check Secure Boot prerequisites such as Setup Mode status before
# registration to avoid wasting the registration process.
/usr/lib/puavo-ltsp-install/puavo-prepare-secure-boot

accepted_hosttypes=$(get_accepted_hosttypes "$puavo_install_hosttype")

puavo-register --accepted-devicetypes "$accepted_hosttypes" \
               "${puavo_preseed_register_args[@]}"

if [ "$chosen_preseed" != "" ]; then
  curl --cacert /etc/puavo-conf/rootca.pem \
    -d "$(jq -n --arg sn "$(facter dmi.product.serial_number)" --arg preseed "$chosen_preseed" '.serialnumber = $sn | .preseed = $preseed')" \
    --fail -H "Content-Type: application/json" --max-time 5 --silent -X POST "$deviceid_url" > /dev/null || echo "Preseed notification send failed."
fi

puavo_hosttype=$(cat /etc/puavo/hosttype)
puavo-conf puavo.hosttype "$puavo_hosttype"

case "$puavo_hosttype" in
  bootserver|laptop)
    install_localbootdevice "$puavo_hosttype" "$puavo_install_hosttype"
    ;;
esac
