#!/bin/bash

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

## Intentional, throughout this file: a leading '$' on variables already in
## arithmetic context (e.g. $(( ${x} + 1 ))) is a harmless style choice.
# shellcheck disable=SC2004

# shellcheck source=../packages/kicksecure/helper-scripts/usr/libexec/helper-scripts/check_runtime.bsh

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

HELPER_SCRIPTS_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/../packages/kicksecure/helper-scripts"
export HELPER_SCRIPTS_PATH
source "${HELPER_SCRIPTS_PATH}/usr/libexec/helper-scripts/check_runtime.bsh"

if was_executed "${BASH_SOURCE[0]}"; then
   parse_cmd_was_sourced='false'
else
   parse_cmd_was_sourced='true'
fi

implicit_dist_type_vm() {
   if printf '%s\n' "${args[@]}" | grep --fixed-strings -- "--type" &>/dev/null; then
      return 0
   fi
   if [ ! "${dist_build_type:-}" = "" ]; then
      return 0
   fi
   printf '%s\n' "INFO: implicitly setting '--type vm' because using '${1:-}'."
   dist_build_type="vm"
}

implicit_dist_type_host() {
   if printf '%s\n' "${args[@]}" | grep --fixed-strings -- "--type" &>/dev/null; then
      return 0
   fi
   if [ ! "${dist_build_type:-}" = "" ]; then
      return 0
   fi
   printf '%s\n' "INFO: implicitly setting '--type host' because using '${1:-}'."
   dist_build_type="host"
}

parse_cmd_type_error() {
   printf '%s\n' "${red}${bold}ERROR: --type must be either 'vm' or 'host'.${reset}"
   exit 1
}

parse_cmd_target_error() {
   printf '%s\n' "${red}${bold}ERROR: --target must be either virtualbox, qcow2, utm, iso, raw, dist-installer-cli, windows or root and can be used multiple times.${reset}"
   exit 1
}

parse_cmd_flavor_error() {
   printf '%s\n' "${red}${bold}You must add either:
--flavor whonix-gateway-lxqt
--flavor whonix-gateway-rpi
--flavor whonix-gateway-cli
--flavor whonix-workstation-lxqt
--flavor whonix-workstation-cli
--flavor whonix-custom-workstation
--flavor whonix-host-lxqt
--flavor whonix-host-cli
--flavor kicksecure-cli
--flavor kicksecure-lxqt
--flavor dist-installer-cli
--flavor source
${reset}"
   exit 1
}

parse_cmd_freedom_versus_nonfreedom_firmware_choice_check() {
   if [ "${build_freedom_only:-}" = "true" ]; then
      true "${BASH_SOURCE[0]} INFO: --freedom true, therefore omitting package firmware-nonfreedom."
   elif [ "${build_freedom_only:-}" = "false" ]; then
      true "${BASH_SOURCE[0]} INFO: --freedom false, therefore adding package firmware-nonfreedom, ok. See also: https://www.kicksecure.com/wiki/Dev/nonfree"
   else
      error "${red}${bold}When building '--arch amd64', you must select firmware. Add either '--freedom true' or '--freedom false'!${reset}"
   fi
}

dist_build_one_script_help() {
   local cmd_name

   cmd_name="${1:-}";
   if [ -n "${cmd_name}" ]; then
      cmd_name="$(basename "$(realpath "${cmd_name}")")"
   fi

   if [ "${cmd_name}" = 'derivative-maker' ]; then
      printf '%s\n' "\
derivative-maker

Syntax:
  --flavor [flavor] --target [target]

Description:
  Creates a separate build folder at \$HOMEVAR/derivative-binary.

  For detailed information on build configuration parameters, please refer to the full build documentation.

Flavors:
  --flavor [flavor_option]
  Options:
    whonix-gateway-lxqt         : Builds Whonix-Gateway LXQt VM.
    whonix-gateway-rpi          : Builds Whonix-Gateway CLI RPi 3 VM.
    whonix-gateway-cli          : Builds Whonix-Gateway CLI VM.
    whonix-workstation-lxqt     : Builds Whonix-Workstation LXQt VM.
    whonix-workstation-cli      : Builds Whonix-Workstation CLI VM.
    whonix-custom-workstation   : Builds Whonix-Custom-Workstation VM.
    whonix-host-cli             : Builds Whonix-Host CLI.
    whonix-host-lxqt            : Builds Whonix-Host LXQt.
    kicksecure-cli              : Builds Kicksecure VM CLI VM.
    kicksecure-lxqt             : Builds Kicksecure VM LXQt VM.
    source                      : Build helpers (e.g. cowbuilder chroot, source packages) without targeting a specific VM flavor.

Targets:
  --target [target_option]
  Options:
    virtualbox              : Builds VirtualBox .ova files.
    qcow2                   : Builds qcow2 images.
    utm                     : Builds UTM images.
    iso                     : Builds ISO images.
    raw                     : Builds raw disk images.
    dist-installer-cli      : Builds dist-installer-cli.
    windows                 : Builds the Windows Installer.
    root                    : Builds for physical installations.
    source                  : Builds a xz source archive.

Types:
  --type [type_option]
  Options:
    host                    : Specifies that the build is for a host system.
    vm                      : Specifies that the build is for a virtual machine.

Optional Parameters:
  --vmram [size]           : Set VM RAM size (e.g., --vmram 128).
  --vram [size]            : Set VM video RAM size (e.g., --vram 12).
  --vmsize [size]          : Set VM disk size (e.g., --vmsize 200G).

  --freshness [option]     : Choose between 'frozen' (frozen sources) or 'current' (current sources).
  --connection [option]    : Select 'clearnet' for clearnet apt sources or 'onion' for onion apt sources.
  --repo [true|false]      : Enable or disable derivative remote repository (default: false).

Environment Variables:
  - flavor_meta_packages_to_install: Define meta packages to be installed.
    Examples:
      flavor_meta_packages_to_install='none'
      flavor_meta_packages_to_install='kicksecure-baremetal-gui-lxqt kicksecure-baremetal-server'

  - install_package_list: Specify additional custom packages for installation.
    Examples:
      install_package_list='gparted'
      install_package_list='gparted gedit'

  - DERIVATIVE_APT_REPOSITORY_OPTS: Set options for the Derivative APT Repository.
    Examples:
      DERIVATIVE_APT_REPOSITORY_OPTS='--enable --repository stable'
      DERIVATIVE_APT_REPOSITORY_OPTS='--enable --repository testers'
      DERIVATIVE_APT_REPOSITORY_OPTS='--enable --repository developers'
      DERIVATIVE_APT_REPOSITORY_OPTS='--enable --codename trixie'

   - vm_names_to_be_exported: Specify for unified images which images to combine.
    Default:
      For CLI:
        vm_names_to_be_exported='Whonix-Gateway-CLI Whonix-Workstation-CLI'
      For LXQt:
        vm_names_to_be_exported='Whonix-Gateway-LXQt Whonix-Workstation-LXQt'
    Examples mixing CLI gateway with LXQt workstation:
        vm_names_to_be_exported='Whonix-Gateway-CLI Whonix-Workstation-LXQt'

Advanced Options:
  --report [true|false]           : Enable or disable build reports (default: false).
  --verifiable [true|false]       : Toggle file deletion in cleanup script for verifiable builds (default: false).
  --sanity-tests [true|false]     : Enable or disable chroot script sanity tests for faster build speed (default: false).
  --retry-max [attempts]          : Set maximum retry attempts. (default: 2)
  --retry-wait [seconds]          : Set wait time between retry attempts.
  --retry-before [script]         : Specify a script to run before retry. [default: none)
  --retry-after [script]          : Specify a script to run after retry. [default: none)
  --allow-uncommitted [true|false]: Permit builds with uncommitted changes (default: false).
  --allow-untagged [true|false]   : Permit builds from non-tagged sources (default: false).
  --allow-unsigned [true|false]   : Skip git_sanity_test commit-signature verification (default: false).
  --sign-and-tag [true|false]     : Sign and tag HEAD and submodules (default: true).
  --kernel [packages]             : Specify kernel packages (e.g., 'linux-image-amd64' or 'none').
  --headers [packages]            : Specify kernel header packages.
  --remote-derivative-packages    : Choose to use remote derivative packages instead of building derivative packages from source code. (default: false).
  --release [unsupported_option]  : Set release option (unsupported). (trixie|bookworm|xenial|bionic)
  --arch [architecture]           : Set architecture (pick one of the following)
    ${architecture_all_list[*]}
    (Note: amd64 also works with most Intel CPUs.)
  --debug                         : Does not disable verbose/debug (xtrace) during help-steps pre, variables and parse-cmd.
  --dry-run [true|false]          : Does not actually build real images. Just an empty text file. Useful for debugging dm-prepare-release script.
  --unsupported-os [true|false]   : Skip the build-host operating system / codename sanity check. NOT for release builds; for dev / AI / CI smoke tests on non-Debian hosts only.

For VMs only:
  --initramfs [packages]          : Specify initramfs packages. (default: \$BUILD_INITRAMFS_PKGS)

Configuration Files:
  --confdir [/path/to/config/dir] : Specify an additional configuration directory.
  --conffile [/path/to/config/file]: Specify an additional configuration file.
  --grmlbin [/path/to/grml-debootstrap]: Set the grml-debootstrap path (default: grml-debootstrap).

Miscellaneous:
  --unsafe-io [true|false]        : Toggle unsafe IO options (default: false).
  --interactive [true|false]      : Force interactive (continue/retry menus) or non-interactive mode. Default: auto-detect (TTY on stdin and stdout, and not CI).
  --freedom [true|false]          : Choose between pure or impure builds (required for host builds).
  --tb [none|closed|open]         : Configure Tor Browser installation options (default: open).
  none: Do not install Tor Browser.
  closed: Abort build, fail closed if Tor Browser cannot be installed.
  open: Do not abort build, fail open if Tor Browser cannot be installed.

Please use the options as per your requirements. For any assistance, refer to the full documentation.
"
   elif [ "${cmd_name}" = 'derivative-update' ]; then
      printf '%s\n' "\
derivative-update

Syntax:
  (-t|--tag|-r|--ref ref)

Description:
  Updates the derivative-maker repository to the specified ref in a secure
  fashion. Imports the derivative-maker signing keys if they are not already
  imported into GPG. Fetches new commits and refs from the remote repository.

Parameters:
  -t|--tag                   : Check out the specified tag. Specify 'latest'
                               to check out the most recently created tag in
                               the repository.
  -r|--ref                   : Check out the specified branch.
  -u|--update-only           : No check out. Update git submodules only.
"
   fi
   exit 0
}

dist_build_one_parse_cmd() {
   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/035

   args=("$@")

   if ! [ "${dist_build_source_run:-}" = "true" ]; then
      if [ "${#args[@]}" -eq 0 ]; then
         printf '%s\n' "${red}${bold}No option chosen! Use '--help'.${reset}"
         exit 1
      fi
   fi

   local build_machines_counter="0"
   local build_target_counter="0"

   ## Using export, so scripts run by run-parts (run by derivative-maker) can read
   ## these variables.

   ## Note, short and combined options such as '-tpu' are unsupported and not planned.
   while :
   do
       case "${1:-}" in
           -h | --help | -\?)
               if [ "${parse_cmd_was_sourced}" = 'true' ]; then
                   ## Script was sourced.
                   dist_build_one_script_help "$0"
               else
                   dist_build_one_script_help "derivative-maker"
               fi
               ;;
           --flavor)
               true "${cyan}INFO: --flavor ${2:-} chosen.${reset}"
               dist_build_flavor="${2:-}"
               export dist_build_flavor
               if [ "${dist_build_flavor:-}" = "whonix-gateway-lxqt" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-gateway-rpi" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-gateway-cli" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-workstation-lxqt" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-workstation-cli" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-custom-workstation" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-host-cli" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "whonix-host-lxqt" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "kicksecure-cli" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "kicksecure-lxqt" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
               elif [ "${dist_build_flavor:-}" = "dist-installer-cli" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
                  export dist_build_installer_dist="true"
                  dist_build_image_upload_supported="true"
               elif [ "${dist_build_flavor:-}" = "source" ]; then
                  build_machines_counter="$(( $build_machines_counter + 1 ))"
                  export dist_build_source_run="true"
               else
                  parse_cmd_flavor_error
               fi
               shift 2
               ;;
           --type)
               true "${cyan}INFO: --type ${2:-} chosen.${reset}"
               if [ "${2:-}" = "host" ]; then
                  dist_build_type="host"
               elif [ "${2:-}" = "vm" ]; then
                  dist_build_type="vm"
               else
                  parse_cmd_type_error
               fi
               shift 2
               ;;
           --target)
               true "${cyan}INFO: --target ${2:-} chosen.${reset}"
               if [ "${2:-}" = "virtualbox" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_virtualbox="true"
                  dist_build_image_upload_supported="true"
                  implicit_dist_type_vm "--target ${2:-}"
               elif [ "${2:-}" = "qcow2" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_qcow2="true"
                  dist_build_image_upload_supported="true"
                  implicit_dist_type_vm "--target ${2:-}"
               elif [ "${2:-}" = "utm" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_raw="true"
                  export dist_build_utm="true"
                  dist_build_image_upload_supported="false"
                  implicit_dist_type_vm "--target ${2:-}"
               elif [ "${2:-}" = "iso" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_iso="true"
                  dist_build_image_upload_supported="true"
                  implicit_dist_type_host "--target ${2:-}"
               elif [ "${2:-}" = "raw" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_raw="true"
                  dist_build_image_upload_supported="false"
               elif [ "${2:-}" = "root" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_install_to_root="true"
                  dist_build_image_upload_supported="false"
               elif [ "${2:-}" = "windows" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_windows_installer="true"
                  dist_build_image_upload_supported="true"
                  implicit_dist_type_vm "--target ${2:-}"
               elif [ "${2:-}" = "source" ]; then
                  build_target_counter="$(( $build_target_counter + 1 ))"
                  export dist_build_source_archive="true"
                  dist_build_image_upload_supported="true"
               else
                  parse_cmd_target_error
               fi
               shift 2
               ;;
           --fast)
               true "${cyan}INFO: --fast ${2:-} chosen.${reset}"
               if [ "${2:-}" = "1" ]; then
                  export dist_build_fast1="1"
               elif [ "${2:-}" = "2" ]; then
                  export dist_build_fast1="1"
                  export dist_build_fast2="1"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --fast are '1' and '2'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --vmram)
               export VMRAM="${2:-}"
               shift 2
               if [ "${VMRAM:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: You forgot to specify how much MB to use for --vmram.${reset}"
                  exit 1
               fi
               ;;
           --vram)
               export VRAM="${2:-}"
               shift 2
               if [ "${VRAM:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: You forgot to specify how much MB to use for --vram.${reset}"
                  exit 1
               fi
               ;;
           --vmsize)
               export VMSIZE="${2:-}"
               shift 2
               if [ "${VMSIZE:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: You forgot to specify how much GB to use for --vmsize.${reset}"
                  exit 1
               fi
               ;;
           --freshness)
               if [ "${2:-}" = "frozen" ]; then
                  printf '%s\n' "${red}${bold}ERROR: Using frozen sources is currently not implemented!${reset}"
                  exit 1
               elif [ "${2:-}" = "current" ]; then
                  ## default
                  true "${cyan}INFO: Using current sources.${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --freshness are 'frozen' or 'current'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --connection)
               if [ "${2:-}" = "clearnet" ]; then
                  true "${cyan}INFO: Using clearnet apt sources.${reset}"
                  dist_build_sources_clearnet_or_onion="clearnet"
               elif [ "${2:-}" = "onion" ]; then
                  true "${cyan}INFO: Using onion apt sources.${reset}"
                  dist_build_sources_clearnet_or_onion="onion"
                  ## tb-updater
                  ## https://phabricator.whonix.org/T678
                  tb_onion=true
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --connection are 'clearnet' or 'onion'.${reset}"
                  exit 1
               fi
               export tb_onion
               export dist_build_sources_clearnet_or_onion
               shift 2
               ;;
           --release)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --release must not be empty.${reset}"
                  exit 1
               else
                  export BUILD_RELEASE="${2:-}"
                  true "${cyan}BUILD_RELEASE set to $BUILD_RELEASE.${reset}"
               fi
               shift 2
               ;;
           ## TODO
           --testing-frozen-sources)
               export dist_build_sources_list_primary="build_sources/debian_testing_frozen.sources"
               shift
               ;;
           --debug)
               ## Implemented in help-steps/pre. Cannot be implemented here, because it runs too late.
               shift
               ;;
           --arch)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --arch must not be empty.${reset}"
                  exit 1
               else
                  dist_build_target_arch="${2:-}"
                  true "${cyan}dist_build_target_arch set to $dist_build_target_arch.${reset}"
               fi
               shift 2
               ;;
           --policy-file)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --policy-file must not be empty.${reset}"
                  exit 1
               fi
               export sq_git_policy_file="${2:-}"
               true "${cyan}sq_git_policy_file set to ${sq_git_policy_file}.${reset}"
               shift 2
               ;;
           --cert-file)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --cert-file must not be empty.${reset}"
                  exit 1
               fi
               export openpgp_cert_file_user="${2:-}"
               true "${cyan}openpgp_cert_file_user set to ${openpgp_cert_file_user}.${reset}"
               shift 2
               ;;
           --initramfs)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --initramfs must not be empty.${reset}"
                  exit 1
               elif [ "${2:-}" = "none" ]; then
                  export BUILD_INITRAMFS_PKGS="none"
                  true "${cyan}BUILD_INITRAMFS_PKGS set to $BUILD_INITRAMFS_PKGS.${reset}"
               else
                  export BUILD_INITRAMFS_PKGS="$BUILD_INITRAMFS_PKGS ${2:-}"
                  true "${cyan}BUILD_INITRAMFS_PKGS set to $BUILD_INITRAMFS_PKGS.${reset}"
               fi
               shift 2
               ;;
           --kernel)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --kernel must not be empty.${reset}"
                  exit 1
               elif [ "${2:-}" = "none" ]; then
                  export BUILD_KERNEL_PKGS="none"
                  true "${cyan}BUILD_KERNEL_PKGS set to $BUILD_KERNEL_PKGS.${reset}"
               else
                  export BUILD_KERNEL_PKGS="$BUILD_KERNEL_PKGS ${2:-}"
                  true "${cyan}BUILD_KERNEL_PKGS set to $BUILD_KERNEL_PKGS.${reset}"
               fi
               shift 2
               ;;
           --headers)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --headers must not be empty.${reset}"
                  exit 1
               elif [ "${2:-}" = "none" ]; then
                  export BUILD_HEADER_PKGS="none"
                  true "${cyan}BUILD_HEADER_PKGS set to $BUILD_HEADER_PKGS.${reset}"
               else
                  export BUILD_HEADER_PKGS="$BUILD_HEADER_PKGS ${2:-}"
                  true "${cyan}BUILD_HEADER_PKGS set to $BUILD_HEADER_PKGS.${reset}"
               fi
               shift 2
               ;;
           --report)
               if [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Full report.${reset}"
                  export dist_build_script_create_report="true"
               elif [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: No report.${reset}"
                  export dist_build_script_create_report="false"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --report are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --verifiable)
               if [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Building verifiable.${reset}"
                  export dist_build_script_verifiable="true"
               elif [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: Not building verifiable.${reset}"
                  export dist_build_script_verifiable="false"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --verifiable are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --sanity-tests)
               if [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Sanity tests true.${reset}"
               elif [ "${2:-}" = "false" ]; then
                  ## TODO: opt in rather than opt out.
                  true "${cyan}INFO: Sanity tests false.${reset}"
                  export SKIP_SCRIPTS+=" 20_sanity_checks "
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --sanity-tests are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --file-system)
               export dist_build_file_system="${2:-}"
               shift 2
               ;;
           --hostname)
               export dist_build_hostname="${2:-}"
               shift 2
               ;;
           --retry-max)
               export dist_build_auto_retry="${2:-}"
               shift 2
               ;;
           --retry-wait)
               export dist_build_wait_auto_retry="${2:-}"
               shift 2
               ;;
           --retry-before)
               export dist_build_dispatch_before_retry="${2:-}"
               shift 2
               ;;
           --retry-after)
               export dist_build_dispatch_after_retry="${2:-}"
               shift 2
               ;;
           --allow-untagged)
               if [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: Would stop if building from untagged commits.${reset}"
               elif [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Would build from untagged commits.${reset}"
                  export dist_build_ignore_untagged="true"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --allow-untagged are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --allow-uncommitted)
               if [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: Would stop if uncommitted changes detected.${reset}"
               elif [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Would ignore if uncommitted changes detected.${reset}"
                  export dist_build_ignore_uncommitted="true"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --allow-uncommitted are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --allow-unsigned)
               if [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: Would verify commit signatures (git_sanity_test).${reset}"
                  export dist_build_ignore_unsigned="false"
               elif [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Would skip commit signature verification (git_sanity_test).${reset}"
                  export dist_build_ignore_unsigned="true"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --allow-unsigned are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --sign-and-tag)
               if [ "${2:-}" = "true" ]; then
                  true "${cyan}INFO: Would sign and tag HEAD and submodules.${reset}"
                  export dist_build_sign_and_tag="true"
               elif [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: Would skip signing and tagging (sign-and-tag no-op).${reset}"
                  export dist_build_sign_and_tag="false"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --sign-and-tag are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --confdir)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --confdir may not be empty.${reset}"
                  exit 1
               else
                  export dist_build_custom_config_dir="${2:-}"
                  true "${cyan}INFO: --confdir set to: $dist_build_custom_config_dir${reset}"
                  if [ -d "$dist_build_custom_config_dir" ]; then
                     true "${cyan}INFO: --confdir $dist_build_custom_config_dir exists.${reset}"
                  else
                     printf '%s\n' "${red}${bold}ERROR: --confdir $dist_build_custom_config_dir does not exist!${reset}"
                     exit 1
                  fi
               fi
               shift 2
               ;;
           --conffile)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --conffile may not be empty.${reset}"
                  exit 1
               else
                  export dist_build_conf_file="${2:-}"
                  true "${cyan}INFO: --conffile set to: $dist_build_conf_file${reset}"
                  if [ -f "$dist_build_conf_file" ]; then
                     true "${cyan}INFO: --conffile $dist_build_conf_file exists.${reset}"
                  else
                     printf '%s\n' "${red}${bold}ERROR: --conffile $dist_build_conf_file does not exist!${reset}"
                     exit 1
                  fi
               fi
               shift 2
               ;;
           --grmlbin)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --grmlbin may not be empty.${reset}"
                  exit 1
               else
                  export dist_build_grml_bin="${2:-}"
                  true "${cyan}INFO: --grmlbin set to: $dist_build_grml_bin${reset}"
                  if [ -x "$dist_build_grml_bin" ]; then
                     true "${cyan}INFO: --grmlbin $dist_build_grml_bin exists.${reset}"
                  else
                     printf '%s\n' "${red}${bold}ERROR: --grmlbin $dist_build_grml_bin is not executable!${reset}"
                     exit 1
                  fi
               fi
               shift 2
               ;;
           --tb)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --tb may not be empty.${reset}"
                  exit 1
               else
                  export anon_shared_inst_tb="${2:-}"
                  true "${cyan}INFO: --tb set to: $anon_shared_inst_tb${reset}"
               fi
               shift 2
               ;;
           --unsafe-io)
               if [ "${2:-}" = "false" ]; then
                  true "${cyan}INFO: Not using unsafe io.${reset}"
               elif [ "${2:-}" = "true" ]; then
                  dist_build_unsafe_io="true"
                  export dist_build_unsafe_io
                  true "${cyan}INFO: Using unsafe io.${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --unsafe-io are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --interactive)
               ## Also implemented in help-steps/pre, as this code runs too late.
               if [ "${2:-}" = "true" ]; then
                  dist_build_interactive="true"
                  export dist_build_interactive
                  true "${cyan}INFO: Interactive mode enabled.${reset}"
               elif [ "${2:-}" = "false" ]; then
                  dist_build_interactive="false"
                  export dist_build_interactive
                  true "${cyan}INFO: Interactive mode disabled.${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --interactive are 'true' or 'false'.${reset}"
                  exit 1
               fi
               shift 2
               ;;
           --repo)
               if [ "${2:-}" = "false" ]; then
                  build_remote_repo_enable="false"
                  true "${cyan}INFO: will ${under}not${eunder} enable remote repository.${reset}"
               elif [ "${2:-}" = "true" ]; then
                  build_remote_repo_enable="true"
                  true "${cyan}INFO: will ${under}enable${eunder} remote repository.${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --repo are 'true' or 'false'.${reset}"
                  exit 1
               fi
               export build_remote_repo_enable
               shift 2
               ;;
           --remote-derivative-packages)
               if [ "${2:-}" = "false" ]; then
                  build_remote_derivative_pkgs="false"
                  true "${cyan}INFO: will ${under}not${eunder} use remote derivative packages.${reset}"
               elif [ "${2:-}" = "true" ]; then
                  build_remote_derivative_pkgs="true"
                  true "${cyan}INFO: will ${under}use${eunder} remote derivative packages.${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --remote-derivative-packages are 'true' or 'false'.${reset}"
                  exit 1
               fi
               export build_remote_derivative_pkgs
               shift 2
               ;;
           --dry-run)
               if [ "${2:-}" = "false" ]; then
                  build_dry_run="false"
                  true "${cyan}INFO: ${under}not${eunder} --dry-run${reset}"
               elif [ "${2:-}" = "true" ]; then
                  build_dry_run="true"
                  true "${cyan}INFO: ${under}--dry-run${eunder}${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --dry-run are 'true' or 'false'.${reset}"
                  exit 1
               fi
               export build_dry_run
               shift 2
               ;;
           --unsupported-os)
               ## Bypass the build-steps.d/1100_sanity-tests
               ## check-operating-system-version error. Useful when
               ## smoke-testing the build pipeline on a non-Debian host
               ## (developer laptops, AI dev sandboxes, GitHub
               ## ubuntu-latest CI runners, ...) where the rest of the
               ## stack still works. Real builds should never set this:
               ## the package versions and APT codename will not match
               ## and the resulting image will be subtly broken.
               if [ "${2:-}" = "true" ]; then
                  build_unsupported_os="true"
                  true "${cyan}INFO: --unsupported-os true: bypassing OS-version sanity check (dev / AI / CI use only).${reset}"
               elif [ "${2:-}" = "false" ]; then
                  build_unsupported_os="false"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --unsupported-os are 'true' or 'false'.${reset}"
                  exit 1
               fi
               export build_unsupported_os
               shift 2
               ;;
           --freedom)
               if [ "${2:-}" = "false" ]; then
                  build_freedom_only="false"
                  true "${cyan}INFO: will include nonfreedom software packages.${reset}"
               elif [ "${2:-}" = "true" ]; then
                  build_freedom_only="true"
                  true "${cyan}INFO: will include Freedom Software packages only.${reset}"
               else
                  printf '%s\n' "${red}${bold}ERROR: supported options for --freedom are 'true' or 'false'.${reset}"
                  exit 1
               fi
               export build_freedom_only
               shift 2
               ;;
            -t|--tag)
               target_tag="${2:-}"
               shift 2
               ;;
            -r|--ref)
               target_ref="${2:-}"
               shift 2
               ;;
            -u|--update-only)
               update_only="true"
               shift 1
               ;;
           --function)
               if [ "${2:-}" = "" ]; then
                  printf '%s\n' "${red}${bold}ERROR: --function may not be empty.${reset}"
                  exit 1
               else
                  FUNCTION="${2:-}"
                  true "${cyan}INFO: --function set to $FUNCTION${reset}"
               fi
               shift 2
               ;;
           --)
               shift
               break
               ;;
           -*)
               if ! [ "${dist_build_source_run:-}" = "true" ]; then
                  printf '%s\n' "${red}${bold}unknown option (1): '${1:-}'${reset}"
                  exit 1
               fi
               break
               ;;
           *)
               if [ "${1:-}" = "" ]; then
                  true
               else
                  if ! [ "${dist_build_source_run:-}" = "true" ]; then
                     printf '%s\n' "${red}${bold}unknown option (2): '${1:-}'${reset}"
                     exit 1
                  fi
               fi
               break
               ;;
       esac
   done

   [[ -v target_tag ]] || target_tag=""
   [[ -v target_ref ]] || target_ref=""
   [[ -v update_only ]] || update_only=""
   [[ -v architecture_valid ]] || architecture_valid=""

   if [ "${dist_build_target_arch:-}" = "" ]; then
      if [ "${dist_build_source_archive:-}" = "true" ]; then
         architecture_valid=true
      elif [ "${dist_build_source_run:-}" = "true" ]; then
         architecture_valid=true
      else
         error "\
${red}${bold}Missing '--arch' option!

Choosing a target architecture is mandatory! Pick one of the following.${reset}
${architecture_all_list[*]}

For example for Intel CPUs (yes, this is correct!):
${bold}--arch amd64${reset}

For example for AMD CPUs:
${bold}--arch amd64${reset}

For ARM CPUs:
${bold}--arch arm64${reset}
${reset}"
      fi
   elif [ "${dist_build_target_arch:-}" = "i386" ]; then
      error "\
${red}${bold}Architecture 'i386' is no longer supported!

Debian Trixie and higher dropped support for full i386 installations.
See: https://www.debian.org/releases/bookworm/i386/release-notes/ch-information.en.html
Build for '--arch amd64' or '--arch arm64' instead.${reset}"
   elif [ "${dist_build_target_arch:-}" = "amd64" ] || [ "${dist_build_target_arch:-}" = "arm64" ]; then
      [ -n "${virtualbox_supported_architecture:-}" ] || virtualbox_supported_architecture="true"
   else
      [ -n "${virtualbox_supported_architecture:-}" ] || virtualbox_supported_architecture="false"
   fi

   if [ "${dist_build_virtualbox:-}" = "true" ]; then
      if [ "${virtualbox_supported_architecture:-}" = "false" ]; then
         error "\
${red}${bold}VirtualBox architecture support test failed!
virtualbox_supported_architecture: '$virtualbox_supported_architecture'
dist_build_target_arch is not 'amd64' or 'arm64'.
dist_build_target_arch is '${dist_build_target_arch:-}'.
You cannot build '--target virtualbox' if not using '--arch amd64' or '--arch arm64'.
This is because at time of writing this check, VirtualBox supports 'amd64' and 'arm64' only.
Should VirtualBox meanwhile support other architectures such as 'ppc64el', then this check can simply be removed in the source code.${reset}"
      fi
   fi

   for architecture_all_item in "${architecture_all_list[@]}"; do
      if [ "${dist_build_target_arch:-}" = "$architecture_all_item" ]; then
        architecture_valid=true
        break
      fi
   done

   if ! [ "${architecture_valid:-}" = "true" ]; then
      error "\
${red}${bold}Invalid '--arch' option!${reset}

Chosen architecture ${red}${under}'$dist_build_target_arch'${reset} is invalid!

Choosing a target architecture is mandatory! Pick one of the following.${reset}
${architecture_all_list[*]}
${reset}"
   fi

   export BUILD_INITRAMFS_PKGS

   true "${cyan}INFO: dist_build_target_arch    (--arch): ${dist_build_target_arch:-}${reset}"
   true "${cyan}INFO: BUILD_KERNEL_PKGS       (--kernel): ${BUILD_KERNEL_PKGS:-}${reset}"
   true "${cyan}INFO: BUILD_HEADER_PKGS      (--headers): ${BUILD_HEADER_PKGS:-}${reset}"

   if [ "${dist_build_sources_clearnet_or_onion:-}" = "" ]; then
      true "${cyan}${bold}INFO: No --connection type 'clearnet' or 'onion' has been chosen. \
Using default dist_build_sources_clearnet_or_onion=${under}clearnet${eunder}.
(Alternative value would be 'onion'.)${reset}"
      export dist_build_sources_clearnet_or_onion="clearnet"
   fi

   ## If there are input files (for example) that follow the options, they
   ## will remain in the "$@" positional parameters.

   if [ "${dist_build_source_run:-}" = "true" ]; then
      true
   else
      if [ "${build_machines_counter:-0}" -gt "1" ]; then
         printf '%s\n' "${red}${bold}You cannot use --flavor multiple times!${reset}"
         exit 1
      fi
   fi

   if [ "${build_machines_counter:-0}" -le "0" ]; then
      if [ "${dist_build_one_parsed:-}" = "true" ]; then
         true
      elif [ "${dist_build_source_run:-}" = "true" ] ; then
         true
      else
         parse_cmd_flavor_error
      fi
   fi

   if [ "${dist_build_install_to_root:-}" = "true" ]; then
      true
   elif [ "${dist_build_virtualbox:-}" = "true" ]; then
      true
   elif [ "${dist_build_qcow2:-}" = "true" ]; then
      true
   elif [ "${dist_build_utm:-}" = "true" ]; then
      true
   elif [ "${dist_build_raw:-}" = "true" ]; then
      true
   elif [ "${dist_build_installer_dist:-}" = "true" ]; then
      true
   elif [ "${dist_build_windows_installer:-}" = "true" ]; then
      true
   elif [ "${dist_build_iso:-}" = "true" ]; then
      true
   elif [ "${dist_build_source_run:-}" = "true" ]; then
      true
   elif [ "${dist_build_source_archive:-}" = "true" ]; then
      true
   else
      parse_cmd_target_error
   fi

   if [ "${dist_build_install_to_root:-}" = "true" ]; then
      if [ "${build_target_counter:-0}" -gt "1" ]; then
         printf '%s\n' "${red}${bold}You can not combine --target root with other targets.${reset}"
         exit 1
      fi
   fi

   if [ "${dist_build_type:-}" = "vm" ]; then
      true
   elif [ "${dist_build_type:-}" = "host" ]; then
      true
   elif [ "${dist_build_source_run:-}" = "true" ]; then
      true
   elif [ "${dist_build_source_archive:-}" = "true" ]; then
      true
   elif [ "${dist_build_installer_dist:-}" = "true" ]; then
      true
   else
      printf '%s\n' "${red}${bold}You must add either:
'--type vm'
'--type host'
${reset}"
      exit 1
   fi

   if [ "${dist_build_iso:-}" = "true" ]; then
      if [ "${dist_build_target_arch:-}" = "amd64" ]; then
         parse_cmd_freedom_versus_nonfreedom_firmware_choice_check
      fi
   fi

   if [ "${CI:-}" = "true" ]; then
      if ! [ "${dist_build_flavor:-}" = "kicksecure-cli" ]; then
         ## TODO: cross-building is too slow. Hits GitHub 6 hours maximum time.
         ## Therefore, build fewer flavors on CI to save build time.
         ## At this time, build flavor 'kicksecure-cli' only on CI.
         ## - Specifically cross-builds (using AMD64 to build ARM64) is hitting GitHub CI timeout.
         ## - It is unlikely that building an LXQt flavor would succeed while the CLI flavor would fail.
         ## equivalent of '--dry-run true'
         build_dry_run="true"
         export build_dry_run
         true "${BASH_SOURCE[0]} INFO: CI='${CI:-}' and dist_build_flavor='${dist_build_flavor:-}' is NOT kicksecure-cli, therefore setting: build_dry_run='${build_dry_run:-}'"
      fi
   fi
}

if [ "${parse_cmd_was_sourced}" = 'true' ]; then
   true "INFO $0: script was sourced."
else
   true "INFO $0: script was executed."
   dist_build_one_parse_cmd "$@"
fi
