#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <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 ..

test-wc() {
   if ! printf '%s\n' "" | wc -l >/dev/null ; then
      error "\
command 'wc' test failed! Do not ignore this!

'wc' can core dump. Example:
zsh: illegal hardware instruction (core dumped) wc -l
https://github.com/rspamd/rspamd/issues/5137"
   fi
}

check-redistributable-builds-requirements() {
   if [ "$dist_build_redistributable" != "true" ]; then
      return 0
   fi
   if [ "$CI" = "true" ]; then
      return 0
   fi
   local folder_list
   folder_list=(
      "$HOME/buildconfig.d"
      "$HOME/.ssh"
      "$HOME/.gnupg"
      "$HOME/.signify"
   )
   for folder_item in "${folder_list[@]}"; do
      if [ ! -d "$folder_item" ]; then
         error "dist_build_redistributable=true, missing required folder: $folder_item"
      fi
   done
}

check-git-folder() {
   ## A few places in the source code use "git clean" or "git describe".
   if ! test -e "$source_code_folder_dist/.git" ; then
      error "$source_code_folder_dist/.git does not exist."
   fi
}

check-operating-system-version() {
   if ! test -r /etc/os-release ; then
      error "file /etc/os-release does not exist!

Is package base-files installed? It is required for this check.
sudo apt install base-files

Are you building on Debian? Only building on Debian is supported."
   fi

   local codename
   codename=$(cat /etc/os-release)
   codename=$(echo "$codename" | grep VERSION_CODENAME)
   codename=$(echo "$codename" | cut -d= -f2)

   ## TODO: remove after stable release of trixie based release
   if [ "bookworm" = "$codename" ]; then
      true "INFO: legacy codename OK."
      return 0
   fi

   if [ "$dist_build_apt_stable_release" = "$codename" ]; then
      true "INFO: up-to-date codename OK."
      return 0
   fi

   error "\
Wrong operating system!

You are attempting to build on an unsupported operating system or version.

Either your version is older or newer than expected. See build documentation
to learn which operating system is suggested.

detected operating system codename: '$codename'
expected operating system codename: '$dist_build_apt_stable_release'"

   true
}

check-sudo() {
   local sudo_test_output
   sudo_test_output="$($SUDO_TO_ROOT test -d /usr 2>&1)"
   if [ "$sudo_test_output" = "" ]; then
      true "INFO: sudo_test_output is empty as expected, ok."
      return 0
   fi
   error "\
sudo_test_output not empty! In other words, the output of command

sudo dpkg test -d /usr

is expected to be empty but was non-empty.

The user must fix this issue before proceeding. These issue is most likely not caused by derivative-issue. This is most likely a general system configuration issue."
   true
}

check-dpkg() {
   ## dpkg --audit does not return anything, if everything is fine.
   ## Therefore we see if dpkg has to say something, and if yes, the system is
   ## broken and we abort.
   dpkg_audit_output="$($SUDO_TO_ROOT dpkg --audit 2>&1)" || true
   if [ "$dpkg_audit_output" = "" ]; then
      true "INFO: dpkg_audit_output is empty as expected, ok."
      return 0
   fi
   error "\
dpkg_audit_output not empty! In other words, the output of command

sudo dpkg --audit

is expected to be empty but was non-empty. Meaning that dpkg found a system configuration issue.

modified quote of the dpkg man page:

> Performs database sanity and consistency checks for [...] all packages. For example, searches for packages that have been installed only partially on your system or that have missing, wrong or obsolete control data or files. dpkg will suggest what to do with them to get them fixed.

The user must fix this issue before proceeding. These issue is most likely not caused by derivative-issue. This is most likely a general system configuration issue."
   true
}

check-hostname() {
   ## mmdebstrap requires file '/etc/hostname' to exist.
   if ! test -f /etc/hostname ; then
      local local_hostname
      if ! local_hostname="$(hostname)" ; then
         error "${bold}${red}ERROR $BASH_SOURCE: Running command 'hostname' failed. See above.${reset}"
      fi
      if [ "$local_hostname" = "" ]; then
         error "${bold}${red}ERROR $BASH_SOURCE: Running command 'hostname' returned empty output. See above.${reset}"
      fi
      true "${bold}${cyan}INFO: File /etc/hostname did not exist. Writing $local_hostname to /etc/hostname, ok.${reset}"
      echo "$local_hostname" | $SUDO_TO_ROOT tee -- /etc/hostname >/dev/null
   fi
   if ! test -r /etc/hostname ; then
      error "${bold}${red}ERROR $BASH_SOURCE: /etc/hostname unreadable on the build system!${reset}"
   fi
   local build_system_hostname
   if ! build_system_hostname=$(cat -- /etc/hostname) ; then
      error "${bold}${red}ERROR $BASH_SOURCE: /etc/hostname reading failed the build system!${reset}"
   fi
   if [ "$build_system_hostname" = "" ]; then
      error "${bold}${red}ERROR $BASH_SOURCE: /etc/hostname file is empty on build system!${reset}"
   fi
   true "INFO: /etc/hostname check ok."
}

check-mailname() {
   ## Silence noisy pbuilder warning:
   ## W: No local /etc/mailname to copy, relying on /var/cache/pbuilder/cow.cow_amd64/cow.950654/etc/mailname to be correct
   if ! test -f /etc/mailname ; then
      true "INFO: File /etc/mailname did not exist. Creating empty one...."
      $SUDO_TO_ROOT touch /etc/mailname
   fi
}

check-source-folder-permissions() {
   ## Debugging.
   true "${bold}${cyan}INFO: PWD: $PWD ${reset}"

   ## Checking if derivative-maker source folder has been obtained (git cloned) as user or root.

   stat_output_owner="$(stat -c %U "$BASH_SOURCE")"
   stat_output_group="$(stat -c %G "$BASH_SOURCE")"

   if [ "$stat_output_owner" = "root" ]; then
      error "${bold}${red}ERROR $BASH_SOURCE: Is owned by root user! Instructions say you should get derivative-maker source code as user, not root! \
Please delete derivative-maker source code folder and get it again as user, not root!${reset}"
   fi

   if [ "$stat_output_group" = "root" ]; then
      error "${bold}${red}ERROR $BASH_SOURCE: Is owned by root group! Instructions say you should get derivative-maker source code as user, not root! \
Please delete derivative-maker source code folder and get it again as user, not root!${reset}"
   fi

   true
}

check-stray-loop-devices() {
   true "INFO: Checking for stray loop devices..."

   local losetup_output
   losetup_output=$($SUDO_TO_ROOT losetup --all)

   if [ "$losetup_output" = "" ]; then
      true "INFO: Output of losetup_output is empty. No stray loop devices, OK."
      return 0
   fi

   ## TODO: ignore /var/swapfile loop device
   ## losetup_output:
   ## /dev/loop0: [64769]:2097454 (/var/swapfile)

   true "INFO: Stray loop devices detected!

losetup_output: '$losetup_output'

This has been detected by the following command... To reproduce this manually, run...

sudo losetup --all ; echo $?

expected result:
0

In other words, this build script as currently implemented expects that there are no open loop devices.
'sudo losetup --all' should not have any output and exit with exit code 0.

If there is a legitimate loop device, this message can be ignored.

Potential causes:
- A previously broken or aborted build might result in a stray loop device.

Recommendation:
- Reboot. Often a reboot is required to get rid of the stray loop device."

   true
}

check-stray-mounts() {
   true "INFO: Checking for mounts..."

   local chroot_folder_base_name proc_mounts_grep_result

   if [ "$CHROOT_FOLDER" = "" ]; then
      true "INFO: CHROOT_FOLDER variable is empty (probably --target root). Skip test for stray mounts, OK."
      return 0
   fi

   chroot_folder_base_name="$(basename "$CHROOT_FOLDER")"

   proc_mounts_grep_result=$($SUDO_TO_ROOT cat /proc/mounts | grep -i -- "$chroot_folder_base_name") || true

   if [ "$proc_mounts_grep_result" = "" ]; then
      true "INFO: Output of proc_mounts_grep_result is empty. No stray mounts, OK."
      return 0
   fi

   error "Stray mounts detected!

proc_mounts_grep_result: '$proc_mounts_grep_result'

This has been detected by the following command... To reproduce this manually, run...

sudo cat /proc/mounts | grep '$CHROOT_FOLDER' ; echo $?

expected result:
1

Potential causes:
- A previously broken or aborted build might result in a stray mount.

Recommendation:
- Reboot. Often a reboot is required to get rid of the stray mount."

   true
}

mount-test() {
   ## TODO
   return 0

   ## Debugging.

   $SUDO_TO_ROOT mount

   mkdir --parents /home/user/whonix_binary

   $SUDO_TO_ROOT kpartx -av /home/user/whonix_binary/Whonix-Gateway.img

   $SUDO_TO_ROOT mkdir --parents /home/user/whonix_binary/Whonix-Gateway_image

   $SUDO_TO_ROOT mount --verbose /dev/mapper/loop0p1 /home/user/whonix_binary/Whonix-Gateway_image

   ## TODO
   $SUDO_TO_ROOT umount --verbose /home/user/whonix_binary/Whonix-Gateway_image/etc/resolv.conf || true

   $SUDO_TO_ROOT umount --verbose /home/user/whonix_binary/Whonix-Gateway_image

   $SUDO_TO_ROOT kpartx -dv /home/user/whonix_binary/Whonix-Gateway.img

   $SUDO_TO_ROOT rmdir -- /home/user/whonix_binary/Whonix-Gateway_image

   $SUDO_TO_ROOT mount

   true
}

check-copy-vms-into-raw() {
   if [ "$dist_build_internal_run" = "true" ]; then
      return 0
   fi

   if [ "$dist_build_type_short" = "kicksecure" ]; then
      true "INFO: kicksecure does not copy VM images into the build, ok."
      return 0
   fi

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

   ## TODO: VirtualBox support

   local help_text
   help_text="A much later build step would try to copy these into the raw image and fail. Therefore we test it already here and fail early.

##########
If you want to do a debug build, perhaps create empty files?

qemu-img create -f qcow2 $copy_vms_into_raw_file_one 1M
qemu-img create -f qcow2 $copy_vms_into_raw_file_two 1M
##########
If you want to do a debug build, manually set which files you like to copy into the raw image using build configuration variables?

copy_vms_into_raw_file_one=$binary_build_folder_dist/Whonix-Gateway.qcow2 copy_vms_into_raw_file_two=$binary_build_folder_dist/Whonix-Workstation.qcow2
##########"

   if [ ! -f "$copy_vms_into_raw_file_one" ]; then
      error "Whonix VMs need to be build first before Whonix host can be build.

copy_vms_into_raw_file_one '$copy_vms_into_raw_file_one' does not exist!

$help_text"
   fi
   if [ ! -f "$copy_vms_into_raw_file_two" ]; then
      error "Whonix VMs need to be build first before Whonix host can be build

copy_vms_into_raw_file_two '$copy_vms_into_raw_file_two' does not exist!

$help_text"
   fi

   true
}

install_required_packages() {
   ## XXX: Ideally this would happen in a different script without
   ##       "sanity-tests" in its name.

   ## Using the build system's already exiting APT sources list.
   ## As configured by the local system administrator.
   ##
   ## Not using the following command line options:
   #-o Dir::Etc::sourcelist="$dist_build_sources_list_primary" \
   #-o Dir::Etc::sourceparts="-" \
   ## Because dist_build_sources_list_primary requires approx.

   $SUDO_TO_ROOT \
      apt-get \
         "${APTGETOPT_WITHOUT_APT_CACHE[@]}" \
         update

   $SUDO_TO_ROOT \
      apt-get \
         "${APTGETOPT_WITHOUT_APT_CACHE[@]}" \
         $apt_unattended_opts \
         --no-install-recommends \
         --yes \
         install \
         $required_packages_list
}

check_required_packages_installed() {
   required_packages_list="git time curl approx lsb-release fakeroot fasttrack-archive-keyring safe-rm"
   local required_package_item

   for required_package_item in $required_packages_list ; do
      ## dpkg-query does not exit non-zero if package is absent on the system.
      if [ "$(dpkg-query --show --showformat='${Version}' "$required_package_item")" = "" ] ; then
         true "INFO: Required package '$required_package_item' missing."
         install_required_packages
         break
      fi
   done
}

main() {
   test-wc "$@"
   check-redistributable-builds-requirements "$@"
   check-git-folder "$@"
   check-operating-system-version "$@"
   check-sudo "$@"
   check-dpkg "$@"
   check-hostname "$@"
   check-mailname "$@"
   check-source-folder-permissions "$@"
   check-stray-loop-devices "$@"
   check-stray-mounts "$@"
   mount-test "$@"
   check-copy-vms-into-raw "$@"
   check_required_packages_installed "$@"

   source "$dist_source_help_steps_folder/git_sanity_test"
   git_sanity_test_main "$@"

   true
}

main "$@"
