#!/bin/bash

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

## Intentional word-splitting of cowbuilder command arguments (e.g. the
## optional COWBUILDER_PREFIX); quoting them would break the call.
# shellcheck disable=SC2086

## example usage:

#make_cross_build_platform_list="armel armhf amd64 arm64" ./build-steps.d/*_cowbuilder-setup --allow-untagged true --allow-uncommitted true --flavor source --target root

set -x
set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

true "INFO: Currently running script: ${BASH_SOURCE[0]} $*"

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

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

source pre
source variables

cd "$MYDIR"
cd ..

source_date_epoch_test() {
   if [ "${SOURCE_DATE_EPOCH:-}" = "" ]; then
      error "SOURCE_DATE_EPOCH is still unset!"
   fi
   true "INFO: SOURCE_DATE_EPOCH: $SOURCE_DATE_EPOCH"
}

cowbuilder_setup() {
   if cowbuilder_setup_do ; then
      true "${FUNCNAME[0]}: success"
   else
      true "${FUNCNAME[0]}: try again"
      cowbuilder_setup_do
   fi
}

cowbuilder_setup_do() {
   true "${bold}${green}INFO${reset}: Setting up cowbuilder..."

   dist_build_sources_list_primary_contents="$(cat -- "$dist_build_sources_list_primary")"
   export dist_build_sources_list_primary_contents

   if [ "${dist_build_unsafe_io:-}" = "true" ]; then
      eatmydata_maybe_install="eatmydata"
   fi

   if [ "${dist_build_unsafe_io:-}" = "true" ]; then
      if [ "${DOCKER:-}" = "true" ]; then
         ## https://forums.whonix.org/t/docker-container-that-builds-whonix-images/17494/21
         true "INFO: Skip re-mounting '$cowbuilder_cache_dir' as tmpfs because DOCKER=true, ok."
      else
         ## Create the mount point first: a relocated (non-default)
         ## '$cowbuilder_cache_dir' may not exist yet, and 'mount' fails on a
         ## missing target. Harmless for the default '/var/cache/pbuilder'
         ## (already created by the 'pbuilder' package).
         $SUDO_TO_ROOT mkdir --parents -- "$cowbuilder_cache_dir"
         if $SUDO_TO_ROOT mount | grep -- "$cowbuilder_cache_dir" | grep -- tmpfs ; then
            true "INFO: '$cowbuilder_cache_dir' already tmpfs."
         else
            $SUDO_TO_ROOT mount -t tmpfs -o size=2G none "$cowbuilder_cache_dir"
         fi
      fi
   fi

   ## Debugging.
   $SUDO_TO_ROOT cat -- "$dist_build_pbuilder_config_file"

   make_cross_build_platform_list_including_host_architecture+=" $make_cross_build_platform_list "

   ## VirtualBox on Linux ships an amd64 build only; there is no VirtualBox for
   ## Linux/arm64 (ARM64 VirtualBox exists solely on macOS). So an '--arch arm64
   ## --target virtualbox' build cannot install VirtualBox into an arm64 chroot
   ## and must build an additional amd64 chroot to produce the VirtualBox image.
   ## On an arm64 host that amd64 chroot is emulated (qemu-user); see the
   ## foreign-architecture handling in the per-architecture loop below.
   if [ "${dist_build_virtualbox:-}" = "true" ]; then
      if [ "${dist_build_target_arch:-}" = "arm64" ]; then
         true "${cyan}INFO: '--target virtualbox' and '--arch arm64', therefore also building a AMD64 chroot, because at time of writing VirtualBox is available only on ARM64 macOS but not for ARM64 Linux.${reset}"
         make_cross_build_platform_list_including_host_architecture+=" amd64 "
      fi
   fi

   true "INFO: Also adding host_architecture to variable make_cross_build_platform_list_including_host_architecture, so local dependencies can be built."
   make_cross_build_platform_list_including_host_architecture+=" $host_architecture "

   true "INFO: Removing potential duplicates in variable make_cross_build_platform_list_including_host_architecture."
   make_cross_build_platform_list_including_host_architecture=$(printf "%s\n" "$make_cross_build_platform_list_including_host_architecture" | tr ' ' '\n' | sort | uniq | tr '\n' ' ')

   for dist_build_multiarch_package_item in $make_cross_build_platform_list_including_host_architecture ; do
      true "dist_build_multiarch_package_item: $dist_build_multiarch_package_item"
      export dist_build_multiarch_package_item

      ## Foreign-architecture chroot: qemu-user emulation and setuid.
      ##
      ## When the chroot architecture differs from the build host architecture
      ## (for example the amd64 VirtualBox chroot on an arm64 host, see above),
      ## its binaries run under qemu-user emulation via binfmt_misc. setuid
      ## binaries such as 'sudo' only gain root under emulation when the qemu
      ## handler carries the 'C' (credentials) flag; without it the kernel
      ## derives credentials from the unprivileged qemu interpreter rather than
      ## the setuid target, and 'sudo' fails later, deep inside
      ## dist-installer-cli's get_su_cmd, with a misleading 'sudo: effective uid
      ## is not 0 ... nosuid' error.
      ##
      ## This affects any cross-architecture build (CI and developer hosts
      ## alike): the build installs 'qemu-user-binfmt', whose systemd-binfmt
      ## registration ships flags 'OPF' (no credentials). Rather than abort and
      ## ask the operator to reconfigure binfmt_misc by hand, add the flag
      ## automatically via help-steps/binfmt-credential-setup (an /etc/binfmt.d
      ## override reusing the vendor spec, re-applied with systemd-binfmt). Runs
      ## after the qemu-user-binfmt install in 1100_sanity-tests and before this
      ## chroot is bootstrapped.
      if [ "${dist_build_multiarch_package_item}" != "${host_architecture}" ]; then
         ## '|| true' so an unmapped architecture (function returns non-zero,
         ## empty output) does not trip errexit; handled by the check below.
         qemu_emulation_machine="$(dist_build_arch_to_qemu_machine "${dist_build_multiarch_package_item}")" || true
         if [ -n "${qemu_emulation_machine}" ]; then
            true "${cyan}INFO: Foreign-architecture chroot '${dist_build_multiarch_package_item}' on host '${host_architecture}' runs under qemu-user emulation; ensuring the qemu binfmt handler carries the 'C' (credentials) flag so setuid (sudo) works inside it.${reset}"
            $SUDO_TO_ROOT "$dist_source_help_steps_folder/binfmt-credential-setup" "qemu-${qemu_emulation_machine}"
         else
            true "${cyan}INFO: No qemu machine mapping for architecture '${dist_build_multiarch_package_item}'; skipping binfmt credentials setup.${reset}"
         fi
      fi

      ## Implemented in help-steps/variables.
      ## sets:
      ## cow_folder
      ## base_folder
      set_cowbuilder_folders

      ## For development. Skipping base cowbuilder chroot creation during cowbuilder_execute_script development.
      #skip=true

      if [ "${skip:-}" = "true" ]; then
         true "${cyan}INFO: Skipping cowbuilder chroot creation.${reset}"
      else
         if [ -d "$base_folder" ]; then
            $SUDO_TO_ROOT "$dist_source_help_steps_folder/umount_kill.sh" "$base_folder"
            $SUDO_TO_ROOT safe-rm --recursive --force -- "$base_folder"
         fi

         local cowbuilder_exit_code
         cowbuilder_exit_code=0

         ## '--mirror "$dist_build_apt_sources_mirror"' should be only cosmetic,
         ## because of mmdebstrap wrapper and hooks.
         ##
         ## Duplicate --extrapackages to prevent removal by pbuilder.
         $SUDO_TO_ROOT \
            ${COWBUILDER_PREFIX:-} \
               cowbuilder \
                  --architecture "$dist_build_multiarch_package_item" \
                  --configfile "$dist_build_pbuilder_config_file" \
                  --create \
                  --basepath "$base_folder" \
                  --buildplace "$cow_folder" \
                  --distribution "$dist_build_apt_stable_release" \
                  --mirror "$dist_build_apt_sources_mirror" \
                  --debootstrap "$dist_source_help_steps_folder/mmdebstrap" \
                  --hookdir "$dist_source_help_steps_folder/pbuilder-hooks" \
                  --extrapackages "sudo devscripts debhelper strip-nondeterminism fakeroot apt-transport-tor eatmydata aptitude cowdancer fasttrack-archive-keyring adduser sq qemu-utils safe-rm" \
                  || { cowbuilder_exit_code="$?" ; true; };

         if [ -d "$base_folder" ]; then
            $SUDO_TO_ROOT "$dist_source_help_steps_folder/umount_kill.sh" "$base_folder"
         fi

         if [ ! "${cowbuilder_exit_code:-}" = "0" ]; then
            return "$cowbuilder_exit_code"
         fi
      fi

      true "INFO: Disable needless initramfs builds inside cowbuilder chroot (not supposed to get booted)."
      $SUDO_TO_ROOT mkdir --parents -- "$base_folder/etc/dracut.conf.d"
      $SUDO_TO_ROOT -- cp --verbose -- "$source_dracut_disable_config_snippet" "$base_folder/etc/dracut.conf.d"

      ## help-steps/pbuilder-hooks/G10sources_list_restore.bsh should have restored it already.
      ## `pbuilder` component `/usr/lib/pbuilder/pbuilder-createbuildenv`
      ## unfortunately runs function `installaptlines` after `${DEBOOTSTRAP}`,
      ## which uses a different sources list. This pbuilder hook script restores
      ## derivative-maker build APT sources list.
      ##
      ## Out-commented. No longer necessary in Trixie as we now use deb822
      ## format sources files and delete sources.list.
      #true "INFO: Sanity test. Security critical. Comparing derivative-maker build sources list with chroot sources list."
      #$SUDO_TO_ROOT diff -- "$dist_build_sources_list_primary" "$base_folder/etc/apt/sources.list.d/derivative.sources"
      #$SUDO_TO_ROOT cp -- "$dist_build_sources_list_primary" "$base_folder/etc/apt/sources.list.d/derivative.sources"

      $SUDO_TO_ROOT mkdir --parents -- "$base_folder/home/$user_name"
      $SUDO_TO_ROOT cp -- "$dist_build_pbuilder_config_file" "$base_folder/home/$user_name/pbuilder_config_file"
      ## retry_run helper so the in-chroot pbuilder-chroot-script-* can wrap
      ## their apt calls too (they cannot source help-steps/pre).
      $SUDO_TO_ROOT cp -- "$dist_source_help_steps_folder/retry-run" "$base_folder/home/$user_name/retry-run"

      ## virtualbox-installer / dist-installer-cli
      dist_installer_cli_file_name="$(basename -- "$binary_image_installer_dist_source")"
      ## example dist_installer_cli_file_name:
      ## dist-installer-cli-standalone

      ## copy dist-installer-cli-standalone into chroot home folder
      ## Might not be required for all platforms but depending on '"$dist_build_virtualbox" = "true"'
      ## might cause issues with reproducibility. Has no effect if not used.
      ## Copy unconditionally for simplicity.
      $SUDO_TO_ROOT cp -- "$binary_image_installer_dist_source" "$base_folder/usr/bin/$dist_installer_cli_file_name"
      $SUDO_TO_ROOT chmod o+rx -- "$base_folder/usr/bin/$dist_installer_cli_file_name"
      $SUDO_TO_ROOT chown --recursive -- "$user_name:$user_name" "$base_folder/home/$user_name"

      cowbuilder_script_list=()
      cowbuilder_script_list+=("$dist_build_pbuilder_main_chroot_script")

      if [ "${dist_build_virtualbox:-}" = "true" ]; then
         ## Install VirtualBox inside the chroot using 'dist-installer-cli'.
         ## Only install into the amd64 chroot:
         ## - VirtualBox binaries are available for amd64 only (no Linux arm64
         ##   build exists at time of writing), so installing into any
         ##   non-amd64 chroot would fail.
         ## - When '--target virtualbox --arch arm64' (for Mac M1/M2 .ova
         ##   output), an amd64 chroot is explicitly added above; the
         ##   arm64 .vdi is wrapped into an .ova using amd64 'VBoxManage'.
         if [ "$dist_build_multiarch_package_item" = "amd64" ]; then
            cowbuilder_script_list+=("$dist_build_pbuilder_virtualbox_chroot_script")
         else
            true "INFO: Skipping VirtualBox install in '$dist_build_multiarch_package_item' chroot; VirtualBox is installed into the amd64 chroot only."
         fi
      fi

      for cowbuilder_execute_script in "${cowbuilder_script_list[@]}"; do
         cowbuilder_exit_code=0
         $SUDO_TO_ROOT \
            ${COWBUILDER_PREFIX:-} \
               cowbuilder \
                  --architecture "$dist_build_multiarch_package_item" \
                  --configfile "$dist_build_pbuilder_config_file" \
                  --execute \
                  --basepath "$base_folder" \
                  --buildplace "$cow_folder" \
                  --save-after-login \
                  -- \
                  "$cowbuilder_execute_script" \
                  || { cowbuilder_exit_code="$?" ; true; };

         if [ -d "$base_folder" ]; then
            $SUDO_TO_ROOT "$dist_source_help_steps_folder/umount_kill.sh" "$base_folder"
         fi

         if [ ! "${cowbuilder_exit_code:-}" = "0" ]; then
            break
         fi
      done

      if [ ! "${cowbuilder_exit_code:-}" = "0" ]; then
         return "$cowbuilder_exit_code"
      fi
   done

   true "${bold}${green}INFO${reset}: cowbuilder setup done."
}

main() {
   source_date_epoch_test "$@"

   ## Not possible because we need *_local-dependencies build step.
   #if [ "${build_dry_run:-}" = "true" ]; then
      #true "${bold}${cyan}INFO: dry-run, skipping ${BASH_SOURCE[0]}. ${reset}"
      #return 0
   #fi

   cowbuilder_setup "$@"
}

main "$@"
