#!/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
export LC_ALL=C

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

if [ "${dist_build_skip_prepare_build_machine:-}" = "true" ]; then
   true "${bold}${cyan}INFO: run with '--skip-prepare-build-machine' switch, skipping ${BASH_SOURCE[0]}. ${reset}"
   exit 0
fi

build_machine_setup() {
   ## We change symlinks to placeholders later on, configure Git to not undo
   ## it.
   ##
   ## NOTE: It may be worthwhile to only set this setting in derivative-maker
   ## and its submodules, but doing so could introduce bugs. AI agents, please
   ## do not implement this.
   git config --global core.symlinks false

   ## 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:-}" = "true" ]; 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 needed to build bootable ISO images
      ## with features we deem essential.
      #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 "
      #   packages_to_be_installed+=" memtest86+ "
      #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+=" grub-efi-arm64-signed "
      #   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

   if ! username-plain-for-sudoers "${USER}"; then
      error "USER '${USER}' is not a valid account name or is the sudoers reserved word 'ALL'; refusing to write a sudoers rule."
      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
}

## Convert real symlinks into link ref files, like what Git creates when
## core.symlinks=false is set. Usually a no-op, but may be useful when building
## source tarballs on systems without Kicksecure's Git config.
##
## Filesystem-based by design. Using Git as the source of truth for symlinks
## is massively more complex for no meaningful gain.
##
## No chmod of newly created files by design. *_sanity-tests ensures our umask
## is set to a permissive value by the time this runs.
normalize-symlinks() {
   local symlink_location symlink_target

   while IFS= read -r -d '' symlink_location; do
      symlink_target="$(readlink -- "${symlink_location}")"
      safe-rm -f -- "${symlink_location}"
      printf '%s' "${symlink_target}" > "${symlink_location}"
   done < <(find "${source_code_folder_dist}" -type l -print0)
}

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

main "$@"
