#!/bin/bash

## Copyright (C) 2012 - 2023 ENCRYPTED SUPPORT LP <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

set -x
set -e

true "INFO: Currently running script: $BASH_SOURCE $@"

MYDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

cd "$MYDIR"
cd ..
cd help-steps

source pre
source colors
source variables

cd "$MYDIR"
cd ..

build_machine_setup() {
   ## https://github.com/QubesOS/qubes-issues/issues/1066
   $SUDO_TO_ROOT systemctl stop qubes-update-check.timer || true
   $SUDO_TO_ROOT systemctl stop qubes-update-check.service || true
   ## https://github.com/QubesOS/qubes-issues/issues/1066#issuecomment-1987239106
   $SUDO_TO_ROOT rm -f /etc/apt/apt.conf.d/00notify-hook

   if [ "$dist_build_fast1" = "1" ]; then
      true "${bold}${cyan}INFO: run with '--fast 1' switch, skipping $BASH_SOURCE. ${reset}"
      exit 0
   fi

   ## {{ users and groups

   ## TODO: Still needed?
   ## Check if user "$user_name" already exist.
   local id_exit_code
   id_exit_code="0"
   id "$user_name" || { id_exit_code="$?" ; true; };
   if [ "$id_exit_code" = "1" ]; then
      true 'INFO: Creating user "$user_name" with password "changeme", because some things have to be run as "$user_name".'
      if command -v qubesdb-read >/dev/null 2>&1 ; then
         password=""
      else
         ## setting password of user clearnet to changeme
         ##
         ## How this password was created:
         ## sudo apt-get install whois
         ## mkpasswd
         ## changeme
         ## Resulted in: aTayYxVyw5kDo
         password="aTayYxVyw5kDo"
      fi
      $SUDO_TO_ROOT useradd --password "$password" --user-group --create-home --shell /bin/bash "$user_name"
   elif [ "$id_exit_code" = "0" ]; then
      true "${cyan}INFO: Not creating user \"$user_name\" with password \"changeme\", because it already exists.${reset}"
   else
      true "${red}${bold}ERROR: id_exit_code was neither 1 or 0, it was: ${id_exit_code}${reset}"
      error "See above!"
   fi

   ## Add user to sudo group.
   ## TODO: Still needed?
   $SUDO_TO_ROOT adduser "$user_name" sudo

   ## }}

   ## Debugging.
   true 'INFO: Benchmarking "$SUDO_TO_ROOT echo "This is a test echo." using "time"...'
   time $SUDO_TO_ROOT echo "This is a test echo."

   true "INFO: Updating git sub modules..."

   git submodule sync --recursive

   #git submodule update --init --recursive --jobs=200
   git -c merge.verifySignatures=true submodule update --init --recursive --jobs=200 --merge

   true "INFO: Updated git sub modules."

   $SUDO_TO_ROOT \
      apt-get \
         ${APTGETOPT[@]} \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_primary" \
         -o Dir::Etc::sourceparts="-" \
         update

   if [ ! "$dist_build_upgrade_build_machine" = "0" ]; then
      ## Update package lists and upgrade.
      $SUDO_TO_ROOT \
         apt-get \
            ${APTGETOPT[@]} \
            -o Dir::Etc::sourcelist="$dist_build_sources_list_primary" \
            -o Dir::Etc::sourceparts="-" \
            $apt_unattended_opts \
            --no-install-recommends \
            --yes \
               dist-upgrade
   fi

   ###############################################
   ## Build Dependencies for Whonix Build Script #
   ###############################################
   local packages_to_be_installed
   packages_to_be_installed+=" $dist_build_script_build_dependency "

   if [ "$eatmydata_install" = "true" ]; then
      true "INFO: Installing eatmydata, because using '--unsafe-io true'."
      packages_to_be_installed+=" $eatmydata "
   else
      true "INFO: Not installing eatmydata, because not using '--unsafe-io true'."
   fi

   if [ "$dist_build_iso" = "true" ]; then
      true "INFO: host_architecture: $host_architecture"
      packages_to_be_installed+=" mokutil "
      packages_to_be_installed+=" keyutils "
      packages_to_be_installed+=" grub2-common "
      packages_to_be_installed+=" efibootmgr "

      ## The following grub packages are (partially) build dependencies by Debian live-build.
      ## Certainly required for amd64 ISO images booted with shim and grub.
      if [ "${host_architecture}" = "amd64" ]; then
         ## These packages are all available for the amd64 platform.
         ## "grub-mkrescue will automatically include every platform it finds." [1]
         ## [1] https://lists.gnu.org/archive/html/grub-devel/2014-03/msg00009.html
         ## Install them all for best compatibility and reproducible builds.
         ## Some might be unnecessary and waste a bit space.
         ## Maybe this can be optimized later.
         packages_to_be_installed+=" grub-efi-amd64-bin grub-pc-bin grub-coreboot-bin grub-efi-ia32-bin grub-xen-bin grub-ieee1275-bin "
         packages_to_be_installed+=" grub-efi-amd64-signed "
         packages_to_be_installed+=" shim-unsigned shim-signed shim-signed-common "
         packages_to_be_installed+=" shim-helpers-amd64-signed "
      elif [ "${host_architecture}" = "i386" ]; then
         packages_to_be_installed+=" grub-efi-amd64-bin grub-pc-bin grub-coreboot-bin grub-efi-ia32-bin grub-xen-bin grub-ieee1275-bin "
         packages_to_be_installed+=" grub-efi-ia32-signed "
         packages_to_be_installed+=" shim-unsigned shim-signed shim-signed-common "
         packages_to_be_installed+=" shim-helpers-i386-signed "
      elif [ "${host_architecture}" = "ppc64el" ]; then
         packages_to_be_installed+=" grub-ieee1275-bin  "
      elif [ "${host_architecture}" = "ppc64" ]; then
         packages_to_be_installed+=" grub-ieee1275-bin  "
      elif [ "${host_architecture}" = "sparc64" ]; then
         packages_to_be_installed+=" grub-ieee1275-bin  "
      elif [ "${host_architecture}" = "arm64" ]; then
         packages_to_be_installed+=" grub-efi-arm64-bin "
         packages_to_be_installed+=" shim-unsigned shim-signed shim-signed-common "
      elif [ "${host_architecture}" = "riscv64" ]; then
         packages_to_be_installed+=" grub-efi-riscv64-bin  "
      else
         true "${red}${bold}WARNING:${reset} ${under}The ISO to be build might be unbootable!${eunder}
- This is because bootloader support is not implemented when building on this
  systems's host_architecture.
- Either the build script does not know how to install the required grub '-bin'
  package for this architecture or the package is simply unavailable.
- Therefore ISO cross builds are unsupported. Patches welcome.
  Might be possible to implement this by running image-to-iso using qemu.
- There is also a small chance that host_architecture detection failed. (Using multiarch, wine?)"
      fi
   fi

   if [ "$dist_build_install_to_root" = "true" ]; then
      ###########################################
      ## Build Dependency for Bare Metal Builds #
      ###########################################
      local bare_metal_basic_package_list
      bare_metal_basic_package_list="$(grep --invert-match --extended-regexp "^\s*#" -- "$source_code_folder_dist/grml_packages" | tr "\n" " ")"
      packages_to_be_installed+=" $bare_metal_basic_package_list "
   else
      if [ "$dist_build_virtualbox" = "true" ]; then
         #######################################################################
         ## Build Dependencies for creating VirtualBox Images (.vdi and .ova)  #
         #######################################################################
         ## uname -r returns on Qubes:
         ## 4.4.31-11.pvops.qubes.x86_64
         local linux_headers
         if command -v qubesdb-read >/dev/null 2>&1 ; then
            linux_headers="linux-headers-amd64"
         else
            linux_headers="linux-headers-${host_architecture}"
         fi
         packages_to_be_installed+=" $linux_headers "
      fi

   fi

   $SUDO_TO_ROOT \
      apt-get \
         ${APTGETOPT[@]} \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_primary" \
         -o Dir::Etc::sourceparts="-" \
         $apt_unattended_opts \
         --no-install-recommends \
         --yes \
         install \
         $packages_to_be_installed

   if [ "$dist_build_internal_run" = "true" ]; then
      true
   else
      if [ "$dist_build_virtualbox" = "true" ]; then
         ## VirtualBox will be be installed at this point.
         "$dist_source_help_steps_folder/vm-exists-test" "$@"
      fi
   fi

   if [ "$dist_build_iso" = "true" ]; then
      pushd "$source_code_folder_dist/grml-debootstraptest" >/dev/null
      ## Using sudo because some dependencies might only be available for root.
      $SUDO_TO_ROOT "$source_code_folder_dist/grml-debootstraptest/image-to-iso" check_dependencies
      popd >/dev/null
   fi

   ## Debugging.
   $SUDO_TO_ROOT cat /usr/sbin/policy-rc.d || true

   ## Debugging.
   #$SUDO_TO_ROOT cat /proc/devices
}

check-unicode() {
   local check_unicode_tool
   check_unicode_tool="${dist_developer_meta_files_folder}/usr/bin/dm-check-unicode"
   ## https://github.com/grml/grml-debootstrap/issues/219
   ## overwrite with '|| true' because `grep` exits non-zero if no match was found.
   ## TODO: dm-check-unicode currently hardcoded. Does not use source_code_folder_dist.

   test -x "$check_unicode_tool"

   grep_find_unicode_wrapper_output="$("$check_unicode_tool" "$source_code_folder_dist" 2>&1)" || true

   if [ "$grep_find_unicode_wrapper_output" = "" ]; then
      true "INFO: grep_find_unicode_wrapper_output empty, good, OK."
   else
      error "$0: ERROR: Unicode found!

See also:
https://forums.whonix.org/t/detecting-malicious-unicode-in-source-code-and-pull-requests/13754"
   fi

   true
}

check-git-folder() {
   if ! test -e "$source_code_folder_dist/packages/kicksecure/genmkfile/.git" ; then
      error "$source_code_folder_dist/packages/kicksecure/genmkfile/.git does not exist."
   fi
}

repo_proxy_setup() {
   if [ "$REPO_PROXY" = "" ]; then
      return 0
   fi
   if [ "$REPO_PROXY" = "none" ]; then
      return 0
   fi
   if [ ! "$REPO_PROXY" = "http://127.0.0.1:3142" ]; then
      return 0
   fi

   if apt-cacher-ng_config_check firstcheck ; then
      true "INFO: apt-cacher-ng_config_check ok."
      return 0
   fi

   true "INFO: Adding 'AllowUserPorts: 0' to /etc/apt-cacher-ng/acng.conf"

   ## Not possible because `append-once` requires `str_match`.
   #$SUDO_TO_ROOT "$source_code_folder_dist/packages/kicksecure/helper-scripts/usr/bin/append-once" "/etc/apt-cacher-ng/acng.conf" "AllowUserPorts: 0"

   echo "AllowUserPorts: 0" | $SUDO_TO_ROOT tee -a "/etc/apt-cacher-ng/acng.conf" >/dev/null

   ## Enable apt-cacher-ng HTTPS tunneling.
   ## Temporary workaround for live-build versus sources.list build issue.
   ## No longer needed.
   ## This would be needed if using any build_sources without 'http://HTTPS///' syntax.
   #echo "PassThroughPattern: .*" | $SUDO_TO_ROOT tee -a "/etc/apt-cacher-ng/acng.conf" >/dev/null

   $SUDO_TO_ROOT systemctl restart apt-cacher-ng.service

   true
}

repo_proxy_test() {
   if [ "$REPO_PROXY" = "" ]; then
      return 0
   fi
   if [ "$REPO_PROXY" = "none" ]; then
      return 0
   fi

   true "INFO: Testing REPO_PROXY $REPO_PROXY (most likely apt-cacher-ng, since default)..."
   local curl_exit_code=0
   curl --fail --silent "$REPO_PROXY" || { curl_exit_code="$?" ; true; };
   if [ "$curl_exit_code" = "22" ]; then
      true "INFO: apt-cacher-ng functional..."
      return 0
   fi

   true "${red}${bold}ERROR: REPO_PROXY curl curl_exit_code: $curl_exit_code. REPO_PROXY $REPO_PROXY unreachable! Does a local firewall block connections to REPO_PROXY?${reset}"
   error "See above!"
}

virtualbox-installation() {
   if [ ! "$dist_build_virtualbox" = "true" ]; then
      return 0
   fi

   true "INFO: Checking if VirtualBox is already installed..."
   if virtualbox_version_installed="$(dpkg-query --show --showformat='${Version}' "virtualbox")" ; then
      true "INFO: virtualbox is already installed."
   elif virtualbox_version_installed="$(dpkg-query --show --showformat='${Version}' "virtualbox-7.0")" ; then
      true "INFO: virtualbox-7.0 is already installed."
   else
      "$binary_image_installer_dist_source" --non-interactive --virtualbox-only --log-level=debug --ci
   fi

   true
}

check-virtualbox-installed() {
   if [ "$dist_build_internal_run" = "true" ]; then
      true "INFO: dist_build_internal_run set to true, skipping $FUNCNAME, ok."
      return 0
   fi
   if [ ! "$dist_build_virtualbox" = "true" ]; then
      true "INFO: dist_build_virtualbox not yet to true, skipping $FUNCNAME, ok."
      return 0
   fi

   if command -v VBoxManage >/dev/null ; then
      true "INFO: VBoxManage available, ok."
      return 0
   fi

   error "VirtualBox not installed yet. VBoxManage command unavailable."

   true
}

check-virtualbox-vm-exists() {
   ## No longer required since using dedicated Linux user account.
   return 0

   if [ "$dist_build_internal_run" = "true" ]; then
      return 0
   fi
   if [ ! "$dist_build_virtualbox" = "true" ]; then
      return 0
   fi

   ## VirtualBox might not be installed at this point.
   ## Trying anyway, in case this script has already been run and installed
   ## VirtualBox beforehand.

   local command_v_exit_code="0"
   command -v VBoxManage >/dev/null || { command_v_exit_code="$?" ; true; }

   if [ ! "$command_v_exit_code" = "0" ]; then
      true "${cyan}INFO: VBoxManage command not yet available, ok.${reset}"
      return 0
   fi

   "$dist_source_help_steps_folder/vm-exists-test" "$@"

   true
}

create-virtualbox-temporary-user() {
   if [ ! "$dist_build_virtualbox" = "true" ]; then
      return 0
   fi

   ## Debugging. Because SUDO_TO_VBOX_TEMP is failing on CI.
   if [ "$CI" = "true" ]; then
      whoami || true
      ## ansible
      groups ansible || true
      ## ansible : ansible sudo vboxusers

      $SUDO_TO_ROOT -- cat /etc/sudoers || true
      $SUDO_TO_ROOT -- ls -la /etc/sudoers.d || true
      $SUDO_TO_ROOT -- cat /etc/sudoers.d/90-cloud-init-users || true
      $SUDO_TO_ROOT -- cat /etc/sudoers.d/ansible-passwordless-sudo || true
      $SUDO_TO_ROOT -- cat /etc/sudoers.d/README || true
   fi

   #ansible ALL=NOPASSWD: ALL
   #root ALL=(ALL) NOPASSWD:ALL
   #%sudo   ALL=(ALL:ALL) NOPASSWD:ALL

   ## TODO: add --quiet
   $SUDO_TO_ROOT -- adduser --system --group --home "$HOMEVAR_VBOX_TEMP" "dm-vbox-temp"
   $SUDO_TO_ROOT -- mkdir --parents "$HOMEVAR_VBOX_TEMP"
   $SUDO_TO_ROOT -- chown --recursive "dm-vbox-temp:dm-vbox-temp" "$HOMEVAR_VBOX_TEMP"

   ## Debugging.
   $SUDO_TO_ROOT -- groups dm-vbox-temp || true
   $SUDO_TO_ROOT -- ls -la "$HOMEVAR_VBOX_TEMP"

   ## Sanity test.
   $SUDO_TO_VBOX_TEMP -- test -d /
   $SUDO_TO_VBOX_TEMP -- ls -la "$HOMEVAR_VBOX_TEMP"

   true
}

grml-debootstrap_installation() {
   pushd "$source_code_folder_dist/grml-debootstrap"
   $SUDO_TO_ROOT make install
   popd
   true
}

live-build_installation() {
   local restore_uncommitted_state

   restore_uncommitted_state='y'

   ## live-build's manpages must be built first, or it will fail to
   ## install. However, building the manpages modifies the source tree,
   ## and we don't want to overwrite any uncommitted changes the user may
   ## have. We therefore make a "checkpoint" commit that we almost
   ## immediately undo, preserving any uncommitted changes. This does
   ## result in all modified files becoming unstaged, which isn't perfect
   ## but isn't horrible either. Preserving which files are staged and
   ## which ones are not is probably difficult.

   pushd "$source_code_folder_dist/live-build"

   ## Do not proceed if the working tree is unclean.
   ## provided by: help-steps/pre
   ## Actually not needed, ./build-steps.d/1100_sanity-tests should cover this already.
   #nothing_to_commit_test

   ## Preserve uncommitted changes
   #git add -A
   #git commit -m 'checkpoint' || {
      #restore_uncommitted_state='n'
      #true
   #}

   ## Build live-build man pages.
   ## This makes the ./manpages/ folder dirty.
   make -C manpages update
   make -C manpages build

   ## If live-build is installed, "$SUDO_TO_ROOT make install" would fail.
   if [ "$(dpkg-query --show --showformat='${Status}\n' live-build)" = 'install ok installed' ]; then
      error "Package live-build must not be installed."
   fi

   ## Install live-build.
   $SUDO_TO_ROOT make install

   ## Revert back to the checkpoint
   #git add -A

   ## git status
   ## > modified:   manpages/
   ## Back to pristine source code.
   ## Delete auto generated manpages.
   ## Doing it for all of the live-build source code might make things more cumbersome for developers.
   #git reset --hard HEAD
   git checkout HEAD -- ./manpages/

   ## Restore uncommitted source tree state
   #if [ "$restore_uncommitted_state" = 'y' ]; then
      #git reset HEAD^
   #fi

   ## Sanity test.
   #nothing_to_commit_test

   popd

   true
}

packages_installation_from_newer_check() {
   local package_item
   for package_item in $dist_build_script_build_dependency_windows_installer_debian_testing ; do
      dpkg --list | grep --fixed-strings -- "$package_item"
   done
}

packages_installation_from_newer_repository() {
   if [ ! "$dist_build_windows_installer" = "true" ]; then
      true "INFO: No packages from testing are currently required if not building Windows Installer."
      return 0
   fi

   ## Hardcoding the versioned package name fp-units-win-rtl-3.2.2 is being avoided.
   ## fp-units-win-rtl is a virtual package.
   ## It is not possible to directly check if a virtual package is already installed.
   ## This is because virtual package are not actually installed.
   local real_fp_units_win_rtl_package
   #real_fp_units_win_rtl_package=$(grep-status --field=Provides,Package --show-field=Package,Provides,Status "fp-units-win-rtl" | grep --fixed-strings -- "Package:" | cut --delimiter=" " -f2) || true
   real_fp_units_win_rtl_package=$(grep-status --field=Package --show-field=Package "fp-units-win-rtl" | grep --fixed-strings -- "Package:" | cut --delimiter=" " -f2) || true
   ## example real_fp_units_win_rtl_package:
   ## fp-units-win-rtl-3.2.2

   if [ "$real_fp_units_win_rtl_package" = "" ]; then
      ## Fallback. This will result in the 'dpkg --status' command to fail,
      ## which will then result in package installation.
      real_fp_units_win_rtl_package="fp-units-win-rtl"
   fi

   local without_virtual_rtl
   without_virtual_rtl=$(echo "$dist_build_script_build_dependency_windows_installer_debian_testing" | $str_replace_tool "fp-units-win-rtl" "")

   local with_real_package_rtl
   with_real_package_rtl="$without_virtual_rtl $real_fp_units_win_rtl_package"

   ## Avoid running "apt-get update" again from testing repository if all dependencies are already installed.
   if dpkg --status $with_real_package_rtl >/dev/null ; then
      true "INFO: All packages required for building Windows Installer are already installed."
      packages_installation_from_newer_check
      return 0
   fi

   $SUDO_TO_ROOT \
      apt-get \
         ${APTGETOPT[@]} \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_newer" \
         -o Dir::Etc::sourceparts="-" \
         update

   $SUDO_TO_ROOT \
      apt-get \
         ${APTGETOPT[@]} \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_newer" \
         -o Dir::Etc::sourceparts="-" \
         $apt_unattended_opts \
         --no-install-recommends \
         --yes \
         install \
         $dist_build_script_build_dependency_windows_installer_debian_testing

   packages_installation_from_newer_check
}

signing_key() {
   true "INFO: GPG_AGENT_INFO: $GPG_AGENT_INFO"
   if [ "$GPG_AGENT_INFO" = "" ]; then
      true "${cyan}${bold}INFO: Environment variable ${under}GPG_AGENT_INFO${eunder} is not set. gnupg-agent will not be available.${reset}"
   fi

   if [ "$CI" = "true" ]; then
      true "INFO: Create signing keys if none exist yet because CI detected..."
      "$dist_source_help_steps_folder/signing-key-create" "$@"
   elif [ "$dist_build_redistributable" = "true" ]; then
      true "INFO: dist_build_redistributable=true, therefore skipping $dist_source_help_steps_folder/signing-key-create, ok.
Not creating signing key when building redistributable builds.
(There is no risk of overwriting signing keys because signing-key-create is checking if signing keys already exist and never overwrites.)
However, if signing keys are missing for redistributable builds, then the keys should be manually put in place to avoid signing redistributable builds using auto-generated keys."
      #"$dist_source_help_steps_folder/signing-key-create" "$@"
   else
      true "INFO: Create signing keys if none exist yet..."
      "$dist_source_help_steps_folder/signing-key-create" "$@"
   fi

   ## Check if signing keys exists and is functional.
   ##
   ## Letting a builder using a gpg key password cache its passwords early,
   ## so we do not pause the build process later when reprepro creates the
   ## local apt repository or when signing redistributable images.
   "$dist_source_help_steps_folder/signing-key-test" "$@"
}

main() {
   repo_proxy_setup "$@"
   repo_proxy_test "$@"
   build_machine_setup "$@"
   check-unicode "$@"
   check-git-folder "$@"
   virtualbox-installation
   check-virtualbox-installed "$@"
   check-virtualbox-vm-exists "$@"
   create-virtualbox-temporary-user "$@"
   grml-debootstrap_installation "$@"
   live-build_installation "$@"
   packages_installation_from_newer_repository "$@"
   signing_key "$@"
}

main "$@"
