#!/bin/bash

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

## Static-analysis waivers for this legacy build script:
## - intentional word-splitting of command arguments (apt options, package
##   lists) -- quoting them would pass a single argument and break the call;
## - 'command -v' for dependency probing during early bootstrap, before the
##   'has' helper is guaranteed present;
## - 'rm' of specific, known files (not user-supplied paths).
# shellcheck disable=SC2086
## style-ok: no-has
## style-ok: no-safe-rm

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 ..

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
   ## 'safe-rm' not installed yet.
   ## 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[0]}. ${reset}"
      exit 0
   fi

   ## Debugging.
   true "INFO: Benchmarking \"$SUDO_TO_ROOT printf '%s\\n' 'This is a test printf.'\" using \"time\"..."
   time $SUDO_TO_ROOT printf '%s\n' "This is a test printf."

   ## Nowadays done using: ./derivative-update
   #true "INFO: Updating git sub modules..."
   ## Safe.
   ## Ensures submodules' remote URL configuration matches the values specified in .gitmodules.
   #git submodule sync --recursive
   ## Caution.
   ## This command updates Git submodules to the commit recorded in the parent repository. (derivative-maker)
   ## It modifies the submodule's Git HEAD, potentially overriding local changes.
   #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."

   ## Might be useful during port to Debian forky.
   #$SUDO_TO_ROOT repository-dist --disable || true
   #$SUDO_TO_ROOT safe-rm -f -- /etc/apt/sources.list.d/extrepo_kicksecure.sources
   #$SUDO_TO_ROOT "$str_replace_many_tool" trixie forky /etc/apt/sources.list.d/* || true

   retry_run --tries 4 --delay 15 -- \
   $SUDO_TO_ROOT \
      apt-get \
         "${DIST_APTGETOPT[@]}" \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_primary" \
         -o Dir::Etc::sourceparts="-" \
         update

   true "dist_build_upgrade_build_machine: $dist_build_upgrade_build_machine"
   if [ ! "${dist_build_upgrade_build_machine:-}" = "0" ]; then
      ## Update package lists and upgrade.
      retry_run --tries 4 --delay 15 -- \
      $SUDO_TO_ROOT \
         apt-get \
            "${DIST_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}" = "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.
- 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 "
   fi
   ## No host-side VirtualBox build-dependencies here: VirtualBox is now
   ## installed into the amd64 cowbuilder chroot (see
   ## 'help-steps/pbuilder-chroot-script-virtualbox'), not on the build
   ## host. This keeps the host clean and is required when building
   ## arm64 VirtualBox OVAs ('Mac M1/M2') from an arm64 Linux host.

   retry_run --tries 4 --delay 15 -- \
   $SUDO_TO_ROOT \
      apt-get \
         "${DIST_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

   ## 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"

   ## dm-check-unicode is silent on a clean run, so it passes only on a
   ## zero exit AND no output; fail closed on anything else.
   local check_unicode_rc
   check_unicode_rc=0
   grep_find_unicode_wrapper_output="$("$check_unicode_tool" "$source_code_folder_dist" 2>&1)" || check_unicode_rc="$?"

   if [ "$check_unicode_rc" = "0" ] && [ -z "${grep_find_unicode_wrapper_output:-}" ]; then
      true "INFO: dm-check-unicode passed cleanly (exit 0, no output), good, OK."
      return 0
   fi

   printf '%s\n' "${grep_find_unicode_wrapper_output:-}" >&2
   error "$0: ERROR: dm-check-unicode did not pass cleanly (exit code ${check_unicode_rc}). See its output above. An exit code other than 1 means the tool could not run (e.g. a missing dependency). See also:
https://forums.whonix.org/t/detecting-malicious-unicode-in-source-code-and-pull-requests/13754"
}

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
}

approx_proxy_setup() {
   if [ ! "${APPROX_PROXY_ENABLE:-}" = "yes" ]; then
      return 0
   fi

   ## Install approx proxy configuration
   $SUDO_TO_ROOT mkdir --parents -- '/etc/approx-derivative-maker'
   $SUDO_TO_ROOT mkdir --parents -- '/var/cache/approx-derivative-maker'
   $SUDO_TO_ROOT mkdir --parents -- '/etc/approx-derivative-maker/curl-home'
   $SUDO_TO_ROOT chown --recursive -- approx:approx '/var/cache/approx-derivative-maker'

   ## Clear out empty files from the cache, approx uses these to mark files
   ## that could not be fetched, which could cause problems for later builds
   ## if the fetch failure was transient.
   $SUDO_TO_ROOT find '/var/cache/approx-derivative-maker' -type f -empty -delete

   ## Clear out repository metadata from the cache, approx handles it poorly.
   ## Debian bug report:
   ## Unable to detect when local and remote metadata no longer matches
   ## https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1118031
   #$SUDO_TO_ROOT find '/var/cache/approx-derivative-maker' -type f -name InRelease -delete
   local repo_dir
   for repo_dir in /var/cache/approx-derivative-maker/*; do
     if [ -d "${repo_dir}/dists" ]; then
       $SUDO_TO_ROOT safe-rm --recursive --force -- "${repo_dir}/dists"
     fi
   done

   $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/approx-derivative-maker.socket" '/usr/lib/systemd/system/approx-derivative-maker.socket'
   if [ "${dist_build_sources_clearnet_or_onion:-}" = "clearnet" ]; then
      $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/approx-derivative-maker@.service" '/usr/lib/systemd/system/approx-derivative-maker@.service'
      $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/approx.conf" '/etc/approx-derivative-maker/approx.conf'
      $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/curlrc" '/etc/approx-derivative-maker/curl-home/.curlrc'
   else
      $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/approx-derivative-maker-tor@.service" '/usr/lib/systemd/system/approx-derivative-maker@.service'
      $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/approx-tor.conf" '/etc/approx-derivative-maker/approx.conf'
      $SUDO_TO_ROOT cp --no-preserve=mode -- "$source_code_folder_dist/approx/curlrc-tor" '/etc/approx-derivative-maker/curl-home/.curlrc'
   fi

   $SUDO_TO_ROOT systemctl daemon-reload
   $SUDO_TO_ROOT systemctl restart approx-derivative-maker.socket

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

   ## When building redistributable builds, add '/var/cache/approx' to Qubes bind-dirs on build machine.
   $SUDO_TO_ROOT mkdir --parents -- /rw/config/qubes-bind-dirs.d
   printf '%s\n' "binds+=( '/var/cache/approx-derivative-maker' )" | $SUDO_TO_ROOT tee -- /rw/config/qubes-bind-dirs.d/40_derivative-maker_approx.conf >/dev/null

   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 approx, since default)..."
   local curl_exit_code=0
   curl --fail --silent "$REPO_PROXY" || { curl_exit_code="$?" ; true; };
   if [ "${curl_exit_code:-}" = "0" ]; then
      true "INFO: approx 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 is no longer installed on the host. It is installed into
## the amd64 cowbuilder chroot only; see
## 'help-steps/pbuilder-chroot-script-virtualbox' (invoked by
## 'build-steps.d/*_cowbuilder-setup') and
## 'help-steps/pbuilder-chroot-script-create-vbox-vm' (invoked by
## 'build-steps.d/*_create-vbox-vm').

check-vm-exists() {
   if [ "${dist_build_source_run:-}" = "true" ]; then
      return 0
   fi

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

   ## When using:
   ## SKIP_SCRIPTS+= " prepare-release "
   ## then skip this function.
   local skip_script
   for skip_script in $SKIP_SCRIPTS; do
      if matched_word=$(printf '%s\n' "$skip_script" | grep --fixed-strings -- "prepare-release") ; then
         printf '%s\n' "${bold}${green}${BASH_SOURCE[0]} INFO: Skipping ${FUNCNAME[0]}, because SKIP_SCRIPTS matches 'prepare-release'. matched_word: '$matched_word'${reset}"
         return 0
      fi
   done

   if [ "${dist_build_raw:-}" = "true" ]; then
      if ! test -f "$binary_image_raw_file_for_unified" ; then
         error "\
Trying to build...
VMNAME: $VMNAME
vm_names_to_be_exported: $vm_names_to_be_exported
dist_build_desktop: $dist_build_desktop
dist_build_raw: $dist_build_raw
missing other VM for unified builds: $binary_image_raw_file_for_unified

This means dm-prepare-release would fail later.

Did you build a CLI build first and now intent to mix with a LXQt build? In this case, set environment variable, for example:
binary_image_raw_file_for_unified=/path/to/raw ./derivative-maker"
      fi
   fi

   if [ "${dist_build_qcow2:-}" = "true" ]; then
      if ! test -f "$binary_image_qcow2_file_for_unified" ; then
         error "\
Trying to build...
VMNAME: $VMNAME
vm_names_to_be_exported: $vm_names_to_be_exported
dist_build_desktop: $dist_build_desktop
dist_build_qcow2: $dist_build_qcow2
missing other VM for unified builds: $binary_image_qcow2_file_for_unified

This means dm-prepare-release would fail later.

Did you build a CLI build first and now intent to mix with a LXQt build? In this case, set environment variable, for example:
binary_image_qcow2_file_for_unified=/path/to/qcow ./derivative-maker"
      fi
   fi

   ## VirtualBox VM existence check for unified builds is no longer done
   ## here: the VirtualBox registry lives inside the amd64 cowbuilder
   ## chroot (see 'build-steps.d/*_create-vbox-vm'), not on the host.
   ## A missing sibling VM will surface as a 'VBoxManage export' error
   ## when 'build-steps.d/*_prepare-release' runs.

   true
}

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

packages_installation_from_backports_repository() {
   if [ "${dist_build_script_build_dependency_debian_backports:-}" = "" ]; then
      return 0
   fi

   retry_run --tries 4 --delay 15 -- \
   $SUDO_TO_ROOT \
      apt-get \
         "${DIST_APTGETOPT[@]}" \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_debian_forky_backports" \
         -o Dir::Etc::sourceparts="-" \
         update

   retry_run --tries 4 --delay 15 -- \
   $SUDO_TO_ROOT \
      apt-get \
         "${DIST_APTGETOPT[@]}" \
         -o Dir::Etc::sourcelist="$dist_build_sources_list_debian_forky_backports" \
         -o Dir::Etc::sourceparts="-" \
         $apt_unattended_opts \
         --no-install-recommends \
         --yes \
         install \
         $dist_build_script_build_dependency_debian_backports

   true
}

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" "$@"
   elif [ "${build_dry_run:-}" = "true" ]; then
      true "INFO: Create signing keys if none exist yet because build_dry_run=true (exercise dm-prepare-release signing path)..."
      "$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 an OpenPGP 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" "$@"
}

sudo_setup() {
   if [ "${USER:-}" = "" ]; then
      error "Environment variable USER cannot be empty! Function sudo_setup failed!"
      return 0
   fi

   ## Fix 'sudo' error:
   ## > sudo: you are not permitted to use the -D option
   ## When using '$SUDO_TO_VBOX_TEMP' because it uses
   ## 'sudo' with '-D $HOMEVAR_VBOX_TEMP' ('--chdir') to avoid 'VBoxManage' error.
   ## > VBoxManage: error: Could not create the directory '.' (VERR_ACCESS_DENIED)

   local file_content
   file_content="\
## This file has been automatically created by derivative-maker.
## Feel free to delete this file after using derivative-maker.
##
## file: ${BASH_SOURCE[0]}
## function: ${FUNCNAME[0]}

## Enable passwordless sudo for '$USER'.
## Avoid 'sudo: a password is required' errors during the build process.
$USER ALL=(ALL:ALL) NOPASSWD:ALL

Defaults:%$USER runcwd=*

## Does not work:
#Defaults:%$USER runcwd=$HOMEVAR_VBOX_TEMP
#Defaults:%$USER runcwd=$HOMEVAR_VBOX_TEMP/
#Defaults:%$USER runcwd=$HOMEVAR_VBOX_TEMP/*
"

   printf "%s" "$file_content" | $SUDO_TO_ROOT SUDO_EDITOR="" VISUAL="" EDITOR=sponge -- visudo -f /etc/sudoers.d/derivative-maker >/dev/null

   ## Debugging.
   $SUDO_TO_ROOT cat /etc/sudoers.d/derivative-maker

   ## Sanity test.
   $SUDO_TO_ROOT visudo --strict --check /etc/sudoers.d/derivative-maker
}

main() {
   approx_proxy_setup "$@"
   repo_proxy_test "$@"
   build_machine_setup "$@"
   check-unicode "$@"
   check-git-folder "$@"
   check-vm-exists "$@"
   grml-debootstrap_installation "$@"
   packages_installation_from_backports_repository "$@"
   signing_key "$@"
   sudo_setup "$@"
}

main "$@"
