#!/bin/bash

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

: "${tpo_downloader_debug:=""}"
if [ ! "$tpo_downloader_debug" = "" ]; then
   if [ "$tpo_downloader_debug" -ge "1" ]; then
      set -x
   fi
fi

: "${BASH_SOURCE:=""}"

if [ "${BASH_SOURCE}" = "${0}" ]; then
  ## Script was executed.
  tb_updater_was_sourced="false"
elif [ "${BASH_SOURCE}" = "$(command -v "${0}")" ]; then
  ## Script was executed probably with 'bash -x'.
  tb_updater_was_sourced="false"
else
  ## Script was sourced.
  ## This is useful for other programs / scripts to be able to `source` the
  ## functions of this script for code re-use. dist-installer-gui will do this.
  tb_updater_was_sourced="true"
fi

if [ "$tb_updater_was_sourced" = "false" ]; then
  # shellcheck disable=SC2317
  set -o pipefail
  set -o errtrace
fi

source /usr/libexec/helper-scripts/strings.bsh
source /usr/libexec/gpg-bash-lib/source_all

[[ -v SCRIPTNAME ]] || SCRIPTNAME="$(basename "$BASH_SOURCE")"
[[ -v ICON ]] || ICON="/usr/share/icons/icon-pack-dist/tbupdate.ico"

## Developer comment on this script:
## I would be very happy if this script would not be required.
## Unfortunately, it is required. It works around a lot bugs:
## - There is no deb/rpm/whatever package with Tor Browser.
##   https://trac.torproject.org/projects/tor/ticket/5236
##   https://trac.torproject.org/projects/tor/ticket/3994
## - There is no way to add the Documentation page well visible to Tor Browser:
##   https://trac.torproject.org/projects/tor/ticket/6025
##   https://trac.torproject.org/projects/tor/ticket/6053
## - Tor Browsers support for Isolating and Transparent Proxies is very limited:
##   https://trac.torproject.org/projects/tor/ticket/5611
## - RecommendedTBBVersions format is not finalized
##   https://trac.torproject.org/projects/tor/ticket/14383
## - counter downgrade / stale mirror attacks on RecommendedTBBVersions - sign / verify tbb versions file
##   https://trac.torproject.org/projects/tor/ticket/13065
## - GETINFO consensus/packages exists but not yet populated.
##   https://trac.torproject.org/projects/tor/ticket/10395
## - Using torbrowser-launcher instead of tb-updater in Whonix
##   https://forums.whonix.org/t/using-torbrowser-launcher-instead-of-tb-updater-in-whonix/385

tb_exit_function() {
   trap "" ERR
   local exit_code
   exit_code="$1"

   if [ "$exit_code" = "0" ]; then
      exit 0
   fi

   if [ "$anon_shared_inst_tb" = "open" ]; then
      if [ "$tb_postinst" = "true" ]; then
         printf '%s\n' "\
INFO: Failing open. More info:
$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Update:_Technical_Details"
      fi
      exit 0
   else
      if [ "$tb_postinst" = "true" ]; then
         printf '%s\n' "\
INFO: Failing closed. More info:
$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Update:_Technical_Details"
      fi
      exit "$exit_code"
   fi
}

tb_error_handler() {
   local exit_code="$?"

   local MSG="<p>###########################################################
<br></br>## $SCRIPTNAME script bug.
<br></br>## No panic. Nothing is broken. Just some rare condition
<br></br>## has been hit. Try again later. There is likely a
<br></br>## solution for this problem. Please see the News,
<br></br>## User Help Forum and Documentation.
<br></br>## <a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki>$tb_documentation_base_url_clearnet/wiki/$tb_wiki</a>
<br></br>## Please report this bug!
<br></br>##
<br></br>## BASH_COMMAND: <code>$BASH_COMMAND</code>
<br></br>## exit_code: <code>$exit_code</code>
<br></br>##
<br></br>## output_opts: <code>${output_opts[@]}</code>
<br></br>## progressbaridx: <code>$progressbaridx</code>
<br></br>##
<br></br>## For debugging, run:
<br></br>## <code>$SCRIPTNAME</code> --debug
<br></br>###########################################################</p>"

   [ -n "$tb_user_home" ] || tb_user_home=~
   [ -n "$tb_install_folder" ] || tb_install_folder="tb"

   if test -w "$tb_user_home/.cache/$tb_install_folder" ; then
      mkdir --parents -- "$tb_user_home/.cache/$tb_install_folder"
   fi
   if test -w "$tb_user_home/.cache/$tb_install_folder/torbrowser_updater_error.log" ; then
      append "$tb_user_home/.cache/$tb_install_folder/torbrowser_updater_error.log" "$MSG"
   fi

   ## In case function output_gui does not exist yet.
   if [ ! "$(type -t "output_gui")" = "function" ]; then
      output_gui="stecho"
   fi

   if [ "$progressbaridx" = "" ]; then
      true
   else
      output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   output_gui ${output_opts[@]} --messagex --titlex "$TITLE" --typex "error" --message "$MSG" --done
   output_gui ${output_opts[@]} --messagecli --titlecli "$TITLE" --typecli "error" --message "$MSG" --done
   tb_exit_function 1
}

trap "tb_error_handler" ERR

download_fail_help_set() {
DOWNLOAD_FAIL_HELP="<p>Possible reasons for download failure:</p>
<ul>
  <li>Internet connectivity issue.</li>
  <li>The download server is down.</li>
  <li>File size exceeded (endless data attack triggered).</li>
  <li>Other software update attack (rollback, indefinite freeze).</li>
  <li><code>$tb_title</code> Downloader (by <code>$tb_downloader_developers</code> developers) has been broken due to upstream changes.</li>
</ul>
<p>Recommendations:</p>
<ul>
  <li>Re-run <code>$SCRIPTNAME</code> with or without <code>--resume<c/ode> in terminal. For example: <code>$SCRIPTNAME --resume</code></li>
  <li>For more information, see the <a href='$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Troubleshooting'>$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Troubleshooting</a>.</li>
</ul>"
}

tb_run_function() {
   case $tb_skip_functions in
   *"$@"*) printf '%s\n' "INFO: Skipping '$@', because tb_skip_functions includes it."
                  return 0
                  ;;
   esac

   true "INFO: Running '$@', because tb_skip_functions does not include it."
   "$@"
}

root_check() {
   if [ "$(id -u)" = "0" ]; then
      tb_running_as_root=true
      ## Runs as root during build process (chroot), where msgcollector is not running.
      ## Under Wayland cannot use gui applications as root.
      export TB_USE_MSGCOLLECTOR="false"
   else
      tb_running_as_root=false
   fi

   if [ "$tb_running_as_root" = "false" ]; then
      true "INFO: $FUNCNAME: running as user (non-root)."
      return 0
   fi

   if printf '%s\n' "$@" | grep -- "--postinst" >/dev/null 2>/dev/null ; then
      true "INFO: $FUNCNAME: Allow running as root because using --postinst."
      return 0
   fi

   printf '%s\n' "ERROR: Do not run '$SCRIPTNAME' as root (sudo)!"
   tb_exit_function 2
}

tb_sanity_tests() {
   command -v id >/dev/null
   command -v basename >/dev/null
   command -v touch >/dev/null
   command -v uname >/dev/null
   command -v mkdir >/dev/null
   command -v pidof >/dev/null
   command -v chmod >/dev/null
   command -v cp >/dev/null
   command -v mv >/dev/null
   command -v killall >/dev/null
   command -v safe-rm >/dev/null
   command -v head >/dev/null
   command -v tar >/dev/null
   command -v date >/dev/null
   command -v /usr/libexec/msgcollector/msgcollector >/dev/null
   command -v /usr/libexec/msgcollector/pv_wrapper >/dev/null
   ## tbbversion function
   command -v grep >/dev/null
   ## tbbversion function
   command -v sed >/dev/null
   command -v sort >/dev/null
   command -v pv >/dev/null
   command -v mkfifo >/dev/null
   command -v jq >/dev/null
   command -v sanitize-string >/dev/null
}

tb_ex_funct() {
   local MSG
   MSG="$0: INFO: $FUNCNAME: Signal '$SIGNAL_TYPE' received. Cleaning up..."

   if [ "$last_pid_list" = "" ]; then
      true
   else
      local childpids_list childpid_item
      for pid in $last_pid_list; do
         childpids_list=$(pgrep --parent "$pid")
         for childpid_item in $childpids_list ; do
            pkill --signal sigkill --parent "$childpid_item" &>/dev/null || true
         done
      done
   fi

   if [ "$progressbaridx" = "" ]; then
      true
   else
      output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   output_gui ${output_opts[@]} --messagecli --typecli "info" --message "$MSG" --done

   #MSG="Aborted."
   #output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done
   #printf '%s\n' "ERROR: $MSG"

   MSG="$0: INFO: $FUNCNAME: Signal '$SIGNAL_TYPE' received. Exiting."
   output_gui ${output_opts[@]} --messagecli --typecli "info" --message "$MSG" --done
}

tb_signal_sigterm() {
   SIGNAL_TYPE="sigterm"
   tb_ex_funct
   tb_exit_function 143
}

trap "tb_signal_sigterm" SIGTERM

tb_signal_sigint() {
   SIGNAL_TYPE="sigint"
   tb_ex_funct
   tb_exit_function 130
}

trap "tb_signal_sigint" SIGINT ## ctrl + c

trap_sigusr2() {
   SIGNAL_TYPE="SIGUSR2"
   tb_ex_funct
   tb_exit_function 3
}

trap "trap_sigusr2" SIGUSR2 ## msgcollector, yad cancel button

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

   while :
   do
       case $1 in
           --debug)
               set -x
               shift
               ;;
           --ordinary)
               ordinary="true"
               shift
               ;;
           --update)
               true ## legacy
               shift
               ;;
           --noask)
               TB_FORCE_INSTALL="1"
               shift
               ;;
           --only-if-newer)
               UPDATE_ONLY_IF_NEWER="1"
               shift
               ;;
           --nokilltb)
               NOKILLTB="1"
               shift
               ;;
           --devbuildpassthrough)
               TB_FORCE_INSTALL="1"
               DEV_BUILD_PASSTHROUGH="1"
               DEV_PASSTHROUGH="1"
               export TB_USE_MSGCOLLECTOR="false"
               shift
               ;;
           --postinst)
               tb_postinst="true"
               UPDATE_ONLY_IF_NEWER="1"
               shift
               ;;
           --is-chroot)
               is_chroot="true"
               shift
               ;;
           --noaskstart)
               noaskstart="true"
               shift
               ;;
           --alpha)
               tbb_download_alpha_version="true"
               shift
               ;;
           --no-tor-con-check)
               TB_NO_TOR_CON_CHECK="1"
               shift 1
               ;;
           --input)
               TB_INPUT="$2"
               shift 2
               ;;
           --no-install)
               TB_NO_INSTALL="true"
               shift
               ;;
           --resume)
               CURL_RESUME="--continue-at -"
               shift
               ;;
           --onion)
               [ -n "$tb_onion" ] || tb_onion="true"
               shift
               ;;
           --reset)
               [ -n "$tb_hard_reset" ] || tb_hard_reset="true"
               [ -n "$TB_NO_CLEANUP" ] || TB_NO_CLEANUP="true"
               shift
               ;;
           --)
               shift
               break
               ;;
           -*)
               printf '%s\n' "$SCRIPTNAME: unknown option: '$1'"
               printf '%s\n' "See:"
               printf '%s\n' "    man $SCRIPTNAME"
               tb_exit_function 4
               ;;
           *)
               break
               ;;
       esac
   done

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

tb_stdin() {
   if [ ! "$TB_INPUT" = "" ]; then
      true "INFO: TB_INPUT is already set to '$TB_INPUT', skipping auto detection, ok."
      return 0
   fi
   if [ "$TB_FORCE_INSTALL" = "1" ]; then
      printf '%s\n' "INFO: TB_FORCE_INSTALL is set to '1', therefore setting TB_INPUT to 'none'."
      TB_INPUT="none"
      export TB_USE_MSGCOLLECTOR="false"
      return 0
   fi
   if [ -t "0" ]; then
      #printf '%s\n' "INFO: stdin connected to terminal, setting \
#TB_INPUT to 'stdin', will use terminal for input, ok."
      #printf '%s\n' "INFO: Alternatively, if want to run from \
#command line, but still use the graphical user interface for input, \
#you could add to command line: --input gui"
      TB_INPUT="stdin"
      export TB_USE_MSGCOLLECTOR="false"
      return 0
   fi
   printf '%s\n' "INFO: stdin not connected to terminal, probably run in graphical environment, ok."
}

tb_qubes_dvm_template() {
   if printf '%s\n' "$qubes_vm_name" | grep --invert-match -- "-dvm" >/dev/null 2>/dev/null ; then
      printf '%s\n' "INFO: Not running inside Qubes Disposable Template, ok."
      return 0
   fi

   local MSG="\
<p>Do not run <code>$TITLE</code> in Qubes Disposable Template!<br></br>
<br></br>
More info: <a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Running_Tor_Browser_in_Qubes_Template_or_Disposable_Template>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Running_Tor_Browser_in_Qubes_Template_or_Disposable_Template</a></p>"

   output_gui ${output_opts[@]} --messagex --titlex "$TITLE" --typex "error" --message "$MSG" --done

   output_cli "ERROR: $MSG"

   tb_exit_function 1
}

output_cli() {
   local MSG
   MSG="$(sanitize-string -- nolimit "$@")"
   printf '%s\n' "$MSG"
}

output_gui() {
   local MSG output_tool
   true "$FUNCNAME: START"
   true "$FUNCNAME: args: ${1+$@}"
   output_tool="/usr/libexec/msgcollector/msgcollector"
   ## Variable initialization required because 'curl-prgrs' uses "set -o nounset".
   [[ -v TB_USE_MSGCOLLECTOR ]] || TB_USE_MSGCOLLECTOR=""
   [[ -v outputfunc_verbose ]] || outputfunc_verbose=""
   if [ "$TB_USE_MSGCOLLECTOR" = "false" ]; then
      while true; do
         case $1 in
            --messagex)
               ## Only show --messagecli. Skip --messagex.
               true "$FUNCNAME: --messagex END"
               return 0
               ;;
            --message)
               MSG="$2"
               shift 2
               break
               ;;
            *)
               break
               ;;
         esac
      done
      ## MSG will be unset for '--progressx'.
      [[ -v MSG ]] || MSG=""
      if [ ! "$MSG" = "" ]; then
         MSG="$(sanitize-string -- nolimit "$MSG")"
         printf '%s\n' "$MSG"
      fi
   elif [ "$outputfunc_verbose" = "true" ]; then
      printf '%s\n' "Running: bash -x $output_tool --identifier $IDENTIFIER ${1+$@}"
      bash -x $output_tool --identifier "$IDENTIFIER" "$@"
   else
      $output_tool --identifier "$IDENTIFIER" "$@"
   fi
   true "$FUNCNAME: END END"
}

tb_config_folder_parser() {
   true "tb_settings_folder: $tb_settings_folder"
   [ -n "$tb_settings_folder" ] || tb_settings_folder="torbrowser.d"
   shopt -s nullglob
   local i
   for i in \
      /etc/${tb_settings_folder}/*.conf \
      /usr/local/etc/${tb_settings_folder}/*.conf \
      ; do
         bash -n "$i"
         source "$i"
   done
}

tb_resolv_conf_debugging() {
   printf '%s\n' "INFO: tb_resolv_conf_debugging: start"

   printf '%s\n' "ls -la /etc/resolv.conf"
   ls -la -- /etc/resolv.conf || true

   printf '%s\n' "cat -- /etc/resolv.conf"
   cat -- /etc/resolv.conf || true

   printf '%s\n' "cat -- /etc/resolv.whonix"
   cat -- /etc/resolv.whonix || true

   printf '%s\n' "cat -- /etc/resolv.conf.whonix-orig"
   cat -- /etc/resolv.conf.whonix-orig || true

   printf '%s\n' "INFO: tb_resolv_conf_debugging: end"
}

tb_anon_ws_dns_conf() {
   true "tb_disable_anon_ws_dnf_conf: $tb_disable_anon_ws_dnf_conf"
   true "is_chroot: $is_chroot"

   if [ "$is_chroot" = "true" ]; then
      ## Debugging.
      tb_resolv_conf_debugging
   fi

   if [ "$tb_disable_anon_ws_dnf_conf" = "true" ]; then
      printf '%s\n' "INFO: tb_anon_ws_dns_conf: tb_disable_anon_ws_dnf_conf=true"
      printf '%s\n' "INFO: Checking if /etc/resolv.conf is a symlink..."
      if test -h /etc/resolv.conf ; then
         ## This is only the case when whonix-ws-network-conf is already installed.
         printf '%s\n' "INFO: Yes, /etc/resolv.conf is a symlink..."
         printf '%s\n' "INFO: Checking if /var/lib/dpkg/info/whonix-ws-network-conf.prerm is executable..."
         if test -x /var/lib/dpkg/info/whonix-ws-network-conf.prerm ; then
            printf '%s\n' "INFO: Yes, /var/lib/dpkg/info/whonix-ws-network-conf.prerm is a executable."
            [ -n "$tb_reenable_anon_ws_dns_conf" ] || tb_reenable_anon_ws_dns_conf=true
            tb_resolv_conf_debugging

            printf '%s\n' "INFO: tb_anon_ws_dns_conf: Restore original /etc/resolv.conf by running /usr/libexec/tb-updater/chroot-pre ..."

            true "tb_running_as_root: $tb_running_as_root"
            if [ "$tb_running_as_root" = "false" ]; then
               ## TODO: port to privleap - but privleap may not be running inside chroot
               sudo --non-interactive /usr/libexec/tb-updater/chroot-pre
            else
               /usr/libexec/tb-updater/chroot-pre
            fi
            tb_resolv_conf_debugging
         fi
      fi
   fi
}

tb_settings_chroot_common() {
   ## Do not run in chroot if tb_install_in_chroot=false.
   if [ ! "$tb_install_in_chroot" = "false" ]; then
      [ -n "$tb_updater_run" ] || tb_updater_run=true
   fi
   ## Fail open, if there was an error.
   [ -n "$anon_shared_inst_tb" ] || anon_shared_inst_tb=open
   ## Skip Tor connectivity check when running inside chroot.
   [ -n "$TB_NO_TOR_CON_CHECK" ] || TB_NO_TOR_CON_CHECK="1"
   ## Hack to disable using proxy settings when running inside chroot.
   ## We are using --fail anyhow. No problem to duplicate it.
   [ -n "$CURL_PROXY" ] || CURL_PROXY="--fail"

   ## Careful as it would like information to logs:
   ## Connection #0 to host 10.137.0.82 left intact
   #[ -n "$CURL_OPTS" ] || CURL_OPTS="--verbose"

   [ -n "$tb_disable_anon_ws_dnf_conf" ] || tb_disable_anon_ws_dnf_conf=true
   ## Debugging.
   [ -n "$pv_wrapper_debug" ] || pv_wrapper_debug=true
}

tb_settings_postinst_common() {
   ## Skip installation confirmation messages when running with
   ## --postinst, because we will be using hardcoded version numbers to verify
   ## upstream's version info.
   ## Downgrade / freeze attacks should not be possible, because file names
   ## that include the version number will be verified using gpg.
   [ -n "$tb_confirm_installation_skip" ] || tb_confirm_installation_skip=true

   [ -n "$NOKILLTB" ] || NOKILLTB="1"
   [ -n "$noaskstart" ] || noaskstart="true"
   [ -n "$TB_INPUT" ] || TB_INPUT="none"
   export TB_USE_MSGCOLLECTOR="false"

   ## Fail open, if there was an error.
   [ -n "$anon_shared_inst_tb" ] || anon_shared_inst_tb=open

   ## Using curl instead of curl-prgrs to debug server issue.
   ## --ordinary
   #[ -n "$ordinary" ] || ordinary="true"
   ## Careful as it would like information to logs:
   ## TODO: out comment this
   ## Connection #0 to host 10.137.0.82 left intact
   #[ -n "$CURL_OPTS" ] || CURL_OPTS="--verbose"
}

tb_settings_qubes_common_templatevm() {
   ## Skip Tor connectivity check when running inside Qubes Template,
   ## because since Qubes R4 Templates are non-networked by default.
   [ -n "$TB_NO_TOR_CON_CHECK" ] || TB_NO_TOR_CON_CHECK="1"
   [ -n "$tb_qubes_template_detected" ] || tb_qubes_template_detected=true
}

tb_settings_not_tempaltevm() {
   [ -n "$tb_qubes_template_detected" ] || tb_qubes_template_detected=false
}

tb_settings_qubes_postinst_templatevm() {
   ## Do not run during Qubes Template postinst if tb_install_follow=false.
   if [ ! "$tb_install_follow" = "false" ]; then
      [ -n "$tb_updater_run" ] || tb_updater_run=true
   fi
   if [ "$tb_user_home" = "" ]; then
      tb_user_home="/var/cache/tb-binary"
      tb_auto_set_user_home_msg="Automatically setting download folder to '$tb_user_home', because running inside Qubes Template and from postinst. \
This is useful so you get up to date versions of $tb_title in newly created App Qubes inherited from updated Templates.
More info: $tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Qubes-specific"
   fi
}

tb_settings_qubes_manual_run_templatevm() {
   [ -n "$tb_updater_run" ] || tb_updater_run=true
   if [ "$tb_user_home" = "" ]; then
      tb_user_home="/var/cache/tb-binary"
      tb_auto_set_user_home_msg="Automatically setting download folder to '$tb_user_home', because running inside Qubes Template but not run from postinst. \
This is useful so you get up to date versions of '$tb_title' in newly created App Qubes inherited from updated Templates.
More info: $tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Qubes-specific"
   fi
}

tb_settings_manual_run_common() {
   [ -n "$tb_updater_run" ] || tb_updater_run=true
}

tb_fix_permissions() {
   ## This function is duplicated in tb-updater, tb-starter.
   ## /usr/libexec/tb-updater/tb-permission-fix fixes permissions in folder /var/cache/tb-binary
   ## Save to do in Qubes Template as well as in App Qube.
   if [ "$tb_running_as_root" = "true" ]; then
      ## Already running as root. No need to use sudo or leaprun.
      ## Fix build issue.
      ## /usr/bin/update-torbrowser: line 591: /usr/bin/sudo: Operation not permitted
      /usr/libexec/tb-updater/tb-permission-fix
      return 0
   fi
   ## Running as user, not as root.
   ## Requires root, therefore running this with leaprun.
   leaprun tb-permission-fix
}

tb_preparation() {
   [ -n "$tb_wiki" ] || tb_wiki="Tor_Browser"
   [ -n "$tb_title" ] || tb_title="Tor Browser"
   [ -n "$IDENTIFIER" ] || IDENTIFIER="torbrowser-downloader"
   [ -n "$tb_install_folder" ] || tb_install_folder="tb"
   [ -n "$tb_install_folder_dot" ] || tb_install_folder_dot=".tb"
   [ -n "$tb_browser_name" ] || tb_browser_name="tor-browser"
   [ -n "$tb_bin" ] || tb_bin="torbrowser"
   [ -n "$tb_downloader_developers" ] || tb_downloader_developers="Whonix"
   [ -n "$tb_documentation_base_url_clearnet" ] || tb_documentation_base_url_clearnet="https://www.whonix.org"

   if [ "$tb_postinst" = "true" ]; then
      [ -n "$tb_manual_run" ] || tb_manual_run=false
   else
      [ -n "$tb_manual_run" ] || tb_manual_run=true
   fi

   if command -v qubesdb-read >/dev/null 2>&1 ; then
      [ -n "$is_qubes" ] || is_qubes=true

      ## qubesdb-read fails
      ## - inside chroot,
      ## - perhaps during upgrades
      ##   - https://github.com/QubesOS/qubes-issues/issues/2497
      ##   - https://github.com/QubesOS/qubes-issues/issues/2509
      ## therefore overwriting with '|| true'.
      [ -n "$qubes_vm_name" ] || qubes_vm_name="$(qubesdb-read /name)" || true
   else
      [ -n "$is_qubes" ] || is_qubes=false
   fi

   if [ "$is_chroot" = "true" ]; then
      printf '%s\n' "INFO: chroot: is_chroot=true"
      tb_settings_chroot_common
   else
      true "INFO: chroot: is_chroot=true is not set, ok."
   fi

   if test -f /run/qubes/this-is-templatevm ; then
      [ -n "$running_inside_qubes_template_vm" ] || running_inside_qubes_template_vm=true
   else
      [ -n "$running_inside_qubes_template_vm" ] || running_inside_qubes_template_vm=false
   fi

   if [ "$running_inside_qubes_template_vm" = "true" ] ; then
      tb_settings_qubes_common_templatevm
   else
      tb_settings_not_tempaltevm
   fi

   if [ "$tb_postinst" = "true" ]; then
      tb_settings_postinst_common
      if [ "$running_inside_qubes_template_vm" = "true" ] ; then
         tb_settings_qubes_postinst_templatevm
      fi
   fi

   true "tb_manual_run: $tb_manual_run"
   if [ "$tb_manual_run" = "true" ]; then
      tb_settings_manual_run_common
      if [ "$running_inside_qubes_template_vm" = "true" ] ; then
         tb_settings_qubes_manual_run_templatevm
      fi
   fi

   if [ "$tb_user_home" = "" ]; then
      tb_user_home=~
      ## In case of running as root from postinst.
      if [ "$tb_user_home" = "/root" ] || [ "$(whoami)" = "root" ]; then
         tb_user_home="/var/cache/tb-binary"
         tb_auto_set_user_home_msg="Automatically setting download folder to '$tb_user_home', because running as root."
      fi
   fi

   [ -n "$tb_home_folder" ] || tb_home_folder="$tb_user_home/$tb_install_folder_dot"
   [ -n "$tb_browser_folder" ] || tb_browser_folder="$tb_home_folder/$tb_browser_name"
   [ -n "$tb_cache_folder" ] || tb_cache_folder="$tb_user_home/.cache/$tb_install_folder"
   [ -n "$tb_temp_folder" ] || tb_temp_folder="$tb_cache_folder/temp"
   [ -n "$tb_downloaded_files_folder" ] || tb_downloaded_files_folder="$tb_cache_folder/files"
   [ -n "$tb_gpg_tmp_dir" ] || tb_gpg_tmp_dir="$tb_cache_folder/gpgtmpdir"
   [ -n "$tb_extract_temp_folder" ] || tb_extract_temp_folder="$tb_cache_folder/$tb_browser_name"

   [ -n "$tb_local_version_filename" ] || tb_local_version_filename="tbb_version.json"
   [ -n "$tb_local_version_file" ] || tb_local_version_file="$tb_browser_folder/Browser/$tb_local_version_filename"
   [ -n "$tbb_download_alpha_version" ] || tbb_download_alpha_version="false"

   [ -n "$TB_KEEP_OLD_VERSIONS_COUNT" ] || TB_KEEP_OLD_VERSIONS_COUNT="0"

   if [ ! "$tb_updater_run" = "true" ]; then
      true "tb_updater_run is set to: '$tb_updater_run'"
      printf '%s\n' "INFO: Using '--postinst' option but outside of Qubes Template, skipping, ok."
      tb_exit_function 0
   fi

   ## Fortunately, on linux-image-486 kernel, while "uname --all" returns for example
   ## "Linux host 3.2.0-4-486 #1 Debian 3.2.41-2 i686 GNU/Linux",
   ## "uname --machine" returns "i686" and Tor Browser works fine with linux-image-486 kernel.
   ## (There are no 486 downloads for Tor Browser.)
   ##ARCH="x86_64"
   ##ARCH="i686"
   if [ "$ARCH" = "" ]; then
      true "INFO: Auto detecting ARCH..."
      ARCH="$(uname --machine)"
      printf '%s\n' "INFO: ARCH '$ARCH' detected."
   else
      printf '%s\n' "INFO: Skipping auto detecting ARCH because already set."
      printf '%s\n' "INFO: ARCH is set to '$ARCH'."
   fi

   if [ "$ARCH_DOWNLOAD" = "" ]; then
      true "INFO: Auto detecting ARCH_DOWNLOAD..."
      if [ "$ARCH" = "i386" ]; then
         ARCH="i686"
      fi
      if printf '%s\n' "$ARCH" | grep -- "aarch64" >/dev/null 2>/dev/null ; then
         ARCH="arm64"
      fi

      if [ "$ARCH" = "x86_64" ]; then
         [ -n "$ARCH_DOWNLOAD" ] || ARCH_DOWNLOAD="linux-x86_64"
      elif [ "$ARCH" = "i686" ]; then
         [ -n "$ARCH_DOWNLOAD" ] || ARCH_DOWNLOAD="linux-i686"
      elif [ "$ARCH" = "arm64" ]; then
         [ -n "$ARCH_DOWNLOAD" ] || ARCH_DOWNLOAD="linux-arm64"
      else
         [ -n "$ARCH_DOWNLOAD" ] || ARCH_DOWNLOAD="linux-$ARCH"
         printf '%s\n' "WARNING: Guessing. Setting ARCH_DOWNLOAD to '$ARCH_DOWNLOAD'. If functional, this is probably OK."
      fi

      printf '%s\n' "INFO: ARCH_DOWNLOAD '$ARCH_DOWNLOAD' detected."
   else
      printf '%s\n' "INFO: Skipping auto detecting ARCH_DOWNLOAD because already set."
      printf '%s\n' "INFO: ARCH_DOWNLOAD is set to '$ARCH_DOWNLOAD'."
   fi

   ## provides tbbversion function
   source /usr/libexec/tb-updater/version-validator

   if [ "$my_tty" = "" ]; then
      local my_tty_exit_code
      my_tty_exit_code="0"
      my_tty="$(tty)" || { my_tty_exit_code="$?" ; true; };
      if [ ! "$my_tty_exit_code" = "0" ]; then
         my_tty="none"
      fi
      ## Just in case.
      if [ "$my_tty" = "" ]; then
         my_tty="none"
      fi
   fi

   who_ami="$(whoami)"

   TITLE="$tb_title Downloader (by $tb_downloader_developers developers)"

   output_gui --icon "$ICON"
   output_gui --parenttty "$my_tty"
   output_gui --titlex "$TITLE"
   output_gui --titlecli "$TITLE"

   ## Make visible to curl-prgrs.
   export IDENTIFIER
   export who_ami

   ret="0"
   command -v curl.anondist-orig >/dev/null || { ret="$?" ; true; };

   if [ "$ret" = "0" ]; then
      ## using the non-uwt-wrapped version, if the uwt wrapper is installed,
      ## which is the case on a default Whonix installation
      CURL=curl.anondist-orig
   else
      ret="0"
      command -v curl >/dev/null || { ret="$?" ; true; };
      if [ "$ret" = "0" ]; then
         ## falling back to real curl, if the uwt wrapper has been uninstalled
         CURL=curl
      else
         local MSG="uwt_tool: Can not find curl. Please report this bug!"
         output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG"
         printf '%s\n' "ERROR: $MSG"
         EXIT_CODE="1"
         return 0
      fi
   fi

   if [ "$CURL_PRGRS" = "" ]; then
      if [ "$ordinary" = "true" ]; then
         CURL_PRGRS="$CURL"
      elif [ -x /usr/libexec/helper-scripts/curl-prgrs ]; then
         ## Curl progress wrapper.
         CURL_PRGRS="/usr/libexec/helper-scripts/curl-prgrs"
      else
         true "/usr/libexec/helper-scripts/curl-prgrs not available, skipping."
         true "Setting CURL_PRGRS to $CURL."
         CURL_PRGRS="$CURL"
      fi
   fi

   ## Debugging CURL_PRGRS.
   #CURL_PRGRS="bash -x /usr/libexec/helper-scripts/curl-prgrs"

   ## Debugging / disabling CURL_PRGRS.
   #CURL_PRGRS="$CURL"

   ## Export CURL variable, so it can be read by $CURL_PRGRS.
   export CURL

   if [ "$tbb_download_alpha_version" = "true" ]; then
      [ -n "$TBB_RELEASE_CHANNEL" ] || TBB_RELEASE_CHANNEL="alpha"
   else
      [ -n "$TBB_RELEASE_CHANNEL" ] || TBB_RELEASE_CHANNEL="release"
   fi

   ## https://dist.torproject.org/torbrowser/10.0.17/tor-browser-linux64-10.0.17_en-US.tar.xz
   [ -n "$TBB_DOWNLOAD_APPENDIX" ] || TBB_DOWNLOAD_APPENDIX=""
   [ -n "$tbb_versions_base_folder" ] || tbb_versions_base_folder="torbrowser/update_3"

   ## https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions/ file was abolished by upstream
   ## https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/33521
   ## https://web.archive.org/web/20201210123314/https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions/
   ## https://forums.whonix.org/t/update-torbrowser-does-not-see-version-10-0-6/10711
   ##
   ## example:
   ##    https://www.torproject.org/dist/torbrowser/10.0.6/tor-browser-linux64-10.0.6_en-US.tar.xz
   ## http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/dist/torbrowser/10.0.6/tor-browser-linux64-10.0.6_en-US.tar.xz

   if [ "$tb_onion" = "true" ]; then
      ## dist.torproject.org has no corresponding onion address at time of writing.
      ## https://securityheaders.com/?q=https%3A%2F%2Fdist.torproject.org%2F&followRedirects=on
      ## https://web.archive.org/web/20210424113513/https://securityheaders.com/?q=https%3A%2F%2Fdist.torproject.org%2F&followRedirects=on
      ##
      ## https://onion.torproject.org/
      ## https://web.archive.org/web/20210424113401/https://onion.torproject.org/
      ##
      ## https://securityheaders.com/?q=https%3A%2F%2Fwww.torproject.org&followRedirects=on
      ## https://web.archive.org/web/20210424113646/https://securityheaders.com/?q=https%3A%2F%2Fwww.torproject.org&followRedirects=on
      [ -n "$tbb_download_base_url" ] || tbb_download_base_url="http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"
      [ -n "$TBB_REMOTE_FOLDER" ] || TBB_REMOTE_FOLDER="http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/dist/torbrowser"

      ## https://securityheaders.com/?q=https%3A%2F%2Faus1.torproject.org%2Ftorbrowser%2Fupdate_3%2Frelease%2Fdownloads.json&followRedirects=off
      ## https://web.archive.org/web/20210424113759/https://securityheaders.com/?q=https%3A%2F%2Faus1.torproject.org%2Ftorbrowser%2Fupdate_3%2Frelease%2Fdownloads.json&followRedirects=off
      ## Onion-Location	http://ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid.onion/torbrowser/update_3/release/downloads.json
      [ -n "$tbb_versions_base_url" ] || tbb_versions_base_url="http://ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid.onion"
      [ -n "$TBB_VERSIONS_FILE_LINK" ] || TBB_VERSIONS_FILE_LINK="${tbb_versions_base_url}/${tbb_versions_base_folder}/${TBB_RELEASE_CHANNEL}/download-${ARCH_DOWNLOAD}.json"
   else
      ## Download from the torproject.org clearnet by default.
      [ -n "$tbb_download_base_url" ] || tbb_download_base_url="https://www.torproject.org"
      [ -n "$TBB_REMOTE_FOLDER" ] || TBB_REMOTE_FOLDER="https://www.torproject.org/dist/torbrowser"
      [ -n "$CURL_FORCE_SSL" ] || CURL_FORCE_SSL="--tlsv1.3 --proto =https"

      [ -n "$tbb_versions_base_url" ] || tbb_versions_base_url="https://aus1.torproject.org"
      [ -n "$TBB_VERSIONS_FILE_LINK" ] || TBB_VERSIONS_FILE_LINK="${tbb_versions_base_url}/${tbb_versions_base_folder}/${TBB_RELEASE_CHANNEL}/download-${ARCH_DOWNLOAD}.json"
   fi

   if [ "$DEV_PASSTHROUGH" = "1" ]; then
      [ -n "$CURL_PROXY" ] || CURL_PROXY=()
   elif [ "$running_inside_qubes_template_vm" = "true" ] ; then
      ## Use Qubes updates proxy.
      if [ "$CURL_PROXY" = "" ]; then
         PROXY_SERVER='http://127.0.0.1:8082/'
         CURL_PROXY=( "--proxy" "$PROXY_SERVER" )
      fi
   elif [ -f /usr/share/whonix/marker ]; then
      ## sets: GATEWAY_IP
      eval $(/usr/libexec/helper-scripts/settings_echo)
      [ -n "$SOCKS_PORT_TBB_DOWNLOAD" ] || SOCKS_PORT_TBB_DOWNLOAD="9115"
      local random_socks_user_name
      random_socks_user_name="$(random_alpha_numeric 43)"
      ## TODO: https://spec.torproject.org/proposals/351-socks-auth-extensions.html
      [ -n "$CURL_PROXY" ] || CURL_PROXY=(
         '--proxy' "socks5h://$GATEWAY_IP:$SOCKS_PORT_TBB_DOWNLOAD"
         '--proxy-user' "tb_updater_${random_socks_user_name}:password"
      )
   else
      [ -n "$CURL_PROXY" ] || CURL_PROXY=()
   fi

   printf '%s\n' "INFO: CURL_PROXY: '${CURL_PROXY[@]}'"

   ## Also used by function tbbversion.
   [ -n "$RecommendedTBBVersions" ] || RecommendedTBBVersions="$tb_cache_folder/RecommendedTBBVersions"

   [ -n "$tbb_version_last_downloaded_save_file" ] || tbb_version_last_downloaded_save_file="$tb_cache_folder/tbb_version_last_downloaded_save_file"

   if [ -f "$tbb_version_last_downloaded_save_file" ]; then
      [ -n "$tbb_version_previous_downloaded_version" ] || tbb_version_previous_downloaded_version="$(stcat "$tbb_version_last_downloaded_save_file")"
   fi
   [ -n "$tbb_version_previous_downloaded_version" ] || tbb_version_previous_downloaded_version="none"
   tbb_version_previous_downloaded_version_stripped="$(sanitize-string -- 20 "$tbb_version_previous_downloaded_version")"

   if ! test -f /usr/share/whonix/marker ; then
      [ -n "$TB_NO_TOR_CON_CHECK" ] || TB_NO_TOR_CON_CHECK="1"
   fi

   if [ ! "$tb_auto_set_user_home_msg" = "" ]; then
      printf '%s\n' "INFO: $tb_auto_set_user_home_msg"
   fi
}

tb_create_folders() {
   mkdir --parents -- "$tb_home_folder"
   mkdir --parents -- "$tb_cache_folder"
   mkdir --parents -- "$tb_downloaded_files_folder"

   ## Required for /usr/libexec/helper-scripts/tor_bootstrap_check.bsh.
   TEMP_DIR="$tb_temp_folder"
   export TEMP_DIR
   safe-rm --recursive --force -- "$TEMP_DIR"
   mkdir --parents -- "$TEMP_DIR"
}

# tb_skip_if_higher_or_equal_version_already_downloaded() {
#    ## Do this only when using --postinst.
#    if [ ! "$tb_postinst" = "true" ]; then
#       true "INFO: Not using --postinst, therefore skipping $FUNCNAME."
#       return 0
#    fi
#    if [ "$tbb_version_previous_downloaded_version_stripped" = "none" ]; then
#       true "INFO: Could not determine tbb_version_previous_downloaded_version_stripped (set to 'none'), therefore skipping $FUNCNAME."
#       return 0
#    fi
#    if [ "$tbb_hardcoded_version" = "" ]; then
#       true "INFO: tbb_hardcoded_version is empty, therefore skipping $FUNCNAME."
#       return 0
#    fi
#    if dpkg --compare-versions -- "$tbb_version_previous_downloaded_version_stripped" ge "$tbb_hardcoded_version" ; then
#       printf '%s\n' "INFO: Previously downloaded version: '$tbb_version_previous_downloaded_version_stripped'"
#       printf '%s\n' "INFO: Previously downloaded version is greater or equal tbb_hardcoded_version, good, ok."
#    fi
#
#    ## TODO: Does not belong here?
#    ## Being careful with deletion.
# #    if [ "$tb_downloaded_files_folder" = "/var/cache/tb-binary/.cache/$tb_install_folder/files" ]; then
# #       printf '%s\n' "INFO: No need to keep older versions in Qubes root image. Saving disk space. Therefore deleting..."
# #       printf '%s\n' "safe-rm -r -f -- '$tb_downloaded_files_folder'"
# #       safe-rm -r -f -- "$tb_downloaded_files_folder"
# #       mkdir --parents -- "$tb_downloaded_files_folder"
# #    fi
# }

tb_connectivity_checks_interfaces() {
   if [ "$running_inside_qubes_template_vm" = "true" ] ; then
      ## Qubes Templates are non-networked as per Qubes default.
      return 0
   fi

   printf '%s' "INFO: Running network interface available check... "
   if /usr/libexec/helper-scripts/check-network-access; then
      printf '%s\n' "Done."
      return 0
   fi

   MSG="<p><b>No network access.</b></p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
         <br></br>or in Terminal: systemcheck
         <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>Run systemcheck on Whonix-Gateway as well.</p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title still fails, please report a bug!</p>

<p>Debugging information:<br/>
/usr/libexec/helper-scripts/check-network-access non-zero exit code.</p>"

   output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

   output_cli "ERROR: $MSG"

   tb_exit_function 18
}

tb_connectivity_checks_tor() {
   if [ "$DEV_BUILD_PASSTHROUGH" = "1" ]; then
      return 0
   fi

   if [ "$TB_NO_TOR_CON_CHECK" = "1" ]; then
      return 0
   fi

   if [ "$CURL_PROXY" = "" ]; then
      return 0
   fi

   if [ -x /usr/libexec/helper-scripts/tor_enabled_check ]; then
      printf '%s' "INFO: Running Tor enabled check... "

      source /usr/libexec/helper-scripts/tor_enabled_check
      ## sets: TOR_ENABLED
      check_tor_enabled_do

      if [ "$TOR_ENABLED" = "0" ]; then
         MSG="<p><b>Tor not enabled yet.</b></p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
              <br></br>or in Terminal: systemcheck
              <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>Run systemcheck on Whonix-Gateway as well.</p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title still fails, please report a bug!</p>

<p>Debugging information:<br />
You could use <code>--no-tor-con-check</code> if you think function <code>$FUNCNAME</code> should be skipped.</p>"

         output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

         output_cli "ERROR: $MSG"

         tb_exit_function 5
      fi
      printf '%s\n' "Done."
   else
      true "/usr/libexec/helper-scripts/tor_enabled_check does not exist, skipping."
   fi

   if [ -x /usr/libexec/helper-scripts/tor_bootstrap_check.bsh ]; then
      printf '%s' "INFO: Running Tor bootstrap check... "
      source /usr/libexec/helper-scripts/tor_bootstrap_check.bsh
      ## sets: tor_bootstrap_percent
      tb_run_function check_tor_circuit_established

      if [ ! "$tor_circuit_established" = "1" ]; then
         MSG="<p></b>Tor not fully bootstrapped.</b></p>

<p>Possible reasons:
<br></br>- no internet connectivity</p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
              <br></br>or in Terminal: systemcheck
              <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>Run systemcheck on Whonix-Gateway as well.</p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title still fails, please report a bug!</p>"

         output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

         output_cli "ERROR: $MSG"

         tb_exit_function 6
      fi
      printf '%s\n' "Done."
   else
      true "/usr/libexec/helper-scripts/tor_bootstrap_check.bsh not available, skipping."
   fi

   true "Tor fully bootstrapped."
}

tb_connectivity_checks_curl() {
   if [ "$DEV_BUILD_PASSTHROUGH" = "1" ]; then
      return 0
   fi

   tb_notify_details="Checking connectivity... Will take a moment..."
   printf '%s\n' "INFO: Running connectivity check...  Downloading...: '$tbb_download_base_url'"

   [ -n "$timeout_connectivity_checks_curl" ] || timeout_connectivity_checks_curl=180
   ## 1 MB = 1048576 bytes
   ## 2 MB = 2097152 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="2097152"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$tb_temp_folder/tbb_remote_folder"
   safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_connectivity_checks_curl"
   curl_download_target_url="$tbb_download_base_url"
   tb_download_common_exit_on_fail="false"
   tb_download_common

   ## Check if curl failed.
   if [ ! "$curl_exit_code" = "0" ]; then
      MSG="<p>$curl_download_target_url could not be reached.</p>

<p>Possible reasons:
<br></br>- $TBB_REMOTE_FOLDER is down
<br></br>- download location changed</p>

<p>Please check: <blockquote>Start menu -> System -> systemcheck
              <br></br>or in Terminal: systemcheck
              <br></br>or in Terminal with debugging: systemcheck -v</blockquote></p>

<p>If systemcheck reports no problems with internet activity and downloading $tb_title keeps failing, please report a bug!</p>

<p>(Debugging information: curl_status_message: $curl_status_message)</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 7
   fi

   printf '%s\n' "INFO: Connectivity check succeeded."
}

tb_update_check() {
   if [ ! "$tbb_version_stripped" = "" ]; then
      true "INFO: tbb_version already set to '$tbb_version_stripped'. Skipping $FUNCNAME, ok."
      return 0
   fi

   ## do not re-download Tor Browser if a previous build already did
   if [ "$DEV_BUILD_PASSTHROUGH" = "1" ]; then
      if [ -d "$tb_browser_folder" ]; then
         printf '%s\n' "$SCRIPTNAME: Not downloading '$tb_title' again, because folder '$tb_browser_folder' already exists."
         tb_exit_function 0
      fi
   fi

   tb_notify_details="Checking $tb_title version... Will take a moment..."
   printf '%s\n' "INFO: Find out latest version... Downloading...: '$TBB_VERSIONS_FILE_LINK'"

   [ -n "$timeout_version_file_download" ] || timeout_version_file_download=300
   ## 1 MB = 1048576 bytes
   ## 2 MB = 2097152 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="2097152"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$RecommendedTBBVersions"
   safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_version_file_download"
   curl_download_target_url="$TBB_VERSIONS_FILE_LINK"
   tb_download_common_exit_on_fail="8"
   tb_download_common

   test -f "$RecommendedTBBVersions"
}

tb_remote_version_parser() {
   if [ ! "$tbb_version_stripped" = "" ]; then
      true "INFO: tbb_version_stripped already set to '$tbb_version_stripped'. Simplified $FUNCNAME, ok."
      [ -n "$tbb_recommended_versions_error" ] || tbb_recommended_versions_error=""
      return 0
   fi

   ## needs: $RecommendedTBBVersions
   ## sets: tbb_version_raw
   ## sets: tbb_version_stripped
   ## sets: tbb_recommended_versions_error
   tb_run_function tbbversion
}

tb_remote_version_sanity_test() {
   if [ "$tbb_version_stripped" = "UNKNOWN" ]; then
      download_fail_help_set
      local MSG="<p>$installed_or_not_text</p>

<p>Could not find out latest <code>$tb_title</code> version! <code>$tbb_recommended_versions_error</code></p>

<p>Either <code>$TBB_VERSIONS_FILE_LINK</code> is invalid or this is a <code>$SCRIPTNAME</code> (by <code>$tb_downloader_developers</code> developers) bug.</p>

<p>$DOWNLOAD_FAIL_HELP</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 9
   fi

   if dpkg --compare-versions -- "$tbb_version_stripped" lt "$tbb_hardcoded_version" ; then
      download_fail_help_set
      local MSG="<p>$installed_or_not_text</p>

<p>Detected latest <code>$tb_title</code> version <code>$tbb_version_stripped</code> is older than hardcoded known-good version <code>$tbb_hardcoded_version</code>!</p>

<p>Either <code>$TBB_VERSIONS_FILE_LINK</code> is invalid or this is a <code>$SCRIPTNAME</code> (by <code>$tb_downloader_developers</code> developers) bug.</p>

<p>$DOWNLOAD_FAIL_HELP</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 9
   elif dpkg --compare-versions -- "$tbb_version_stripped" gt "$tbb_hardcoded_version" ; then
       true "OK"
   elif dpkg --compare-versions -- "$tbb_version_stripped" eq "$tbb_hardcoded_version" ; then
       true "OK"
   else
      download_fail_help_set
      local MSG="<p>$installed_or_not_text</p>

<p>Detected latest <code>$tb_title</code> version: <code>$tbb_version_stripped</code>
hardcoded known-good version: <code>$tbb_hardcoded_version</code>
dpkg --compare-versions failed!</p>

<p>Either <code>$TBB_VERSIONS_FILE_LINK</code> is invalid or this is a <code>$SCRIPTNAME</code> (by <code>$tb_downloader_developers</code> developers) bug.</p>

<p>$DOWNLOAD_FAIL_HELP</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 9
   fi
}

tb_local_version_detection() {
   ## sets: tbb_hardcoded_version
   true "INFO: tbb_download_alpha_version: $tbb_download_alpha_version"
   if [ "$tbb_download_alpha_version" = "true" ]; then
      ## sets: tbb_hardcoded_version
      source /usr/share/tb-updater/tbb_hardcoded_version_alpha
      printf '%s\n' "INFO: Using alpha version. See:
$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Alpha"
   else
      ## sets: tbb_hardcoded_version
      source /usr/share/tb-updater/tbb_hardcoded_version
      printf '%s\n' "INFO: Using stable version. For alpha version, see: $tb_documentation_base_url_clearnet/wiki/$tb_wiki#Alpha"
   fi

   ## Used by function tbbversion_installed
   [ -n "$tbb_folder" ] || tbb_folder="$tb_browser_folder"

   ## sets: tbb_locally_installed_version
   ## sets: tbb_locally_installed_version_detect_success
   tb_run_function tbbversion_installed
   tbb_locally_installed_version_stripped="$(sanitize-string -- 20 "$tbb_locally_installed_version")"

   if [ -d "$tb_browser_folder" ]; then
      installed_or_not_result="true"
      installed_or_not_text=""
   else
      installed_or_not_result="false"
      installed_or_not_text="$tb_title is currently not installed.
(Folder $tb_browser_folder does not exist.)"
   fi
}

tb_confirm_update() {
   if [ "$running_inside_qubes_template_vm" = "true" ] ; then
      local folder_name found
      for folder_name in "/home/$who_ami/$tb_install_folder_dot" "/home/$who_ami/.cache/${tb_install_folder}" ; do
         if [ -d "$folder_name" ]; then
            found=true
            break
         fi
      done
      if [ "$found" = "true" ]; then
         local MSG="\
<p>Obsolete folders exist. It is recommended to delete them. \
Otherwise updating <code>$tb_title</code> in this Template will not result in newly created App Qubes to inherit the updated version of <code>$tb_title</code>.
<br />
<br />To delete these obsolete folders, please run:
<br />
<br /><code>safe-rm -r -f -- '/home/$who_ami/$tb_install_folder_dot'</code>
<br /><code>safe-rm -r -f -- '/home/$who_ami/.cache/${tb_install_folder}'</code>
<br />
<br />(Or delete them using a file manager.)
<br />
<br />More info: <a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Qubes-specific>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Qubes-specific</a></p>"
         output_gui ${output_opts[@]} --messagex --typex "info" --message "$MSG" --done

         output_cli "INFO: $MSG"
      fi
   fi

   printf '%s\n' "INFO: Previously downloaded version: '$tbb_version_previous_downloaded_version_stripped'"
   printf '%s\n' "INFO: Currently installed version  : '$tbb_locally_installed_version_stripped'"
   printf '%s\n' "INFO: Online detected version      : '$tbb_version_stripped'"

   local MSG question button type
   button="yesno"
   question="Download now?"

   local updater_vs_downloader_msg confirmation_screen_learn_more_link

   updater_vs_downloader_msg="\
If you would like to keep your browser profile and update rather than re-downloading $tb_title, \
you must use \
<a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Internal_Updater>$tb_title's internal updater</a>. \
In that case, say no now.<br></br>
<br></br>
This program \
(<a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Downloader>$tb_title Downloader (by $tb_downloader_developers developers)</a>) \
is incapable of keeping user data.<br></br>
<br></br>
YOUR BROWSER WILL BE KILLED.<br></br>
YOUR OLD BROWSER PROFILE INCLUDING BOOKMARKS AND PASSWORDS WILL GET DELETED."

   confirmation_screen_learn_more_link="\
<a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Download_Confirmation_Notification>Learn more about this Download Confirmation Notification.</a>"

   if [ "$running_inside_qubes_template_vm" = "true" ] ; then
      qubes_running_inside_template_vm_msg="\
<u>Qubes specific notice</u>: Currently running inside a Template.
Due to <a href=$tb_documentation_base_url_clearnet/wiki/Dev/Qubes#tb-updater_vs_Template>technical limitations</a>,
running this program in this Template will not update $tb_title in App Qubes, which are based on this Template.
Only newly created App Qubes, which are based on this Template will benefit from this.
To update $tb_title inside existing App Qubes, please update $tb_title inside App Qubes."
   fi

   if [ "$installed_or_not_result" = "true" ]; then
      type="warning"
      if [ "$tbb_locally_installed_version_detect_success" = "0" ]; then
         ## installed, but local version number unknown
         MSG="\
<p>$updater_vs_downloader_msg</p>
<p>$confirmation_screen_learn_more_link</p>
<p>$qubes_running_inside_template_vm_msg</p>"
      else
         ## installed, local version number known
         if dpkg --compare-versions -- "$tbb_version_stripped" eq "$tbb_locally_installed_version"; then
            up_to_date_or_not_text="Looks like $tb_title is already up to date."
            re_install_or_install_text="Please close $tb_title if you want to (re-)install!"
	          if [ "$UPDATE_ONLY_IF_NEWER" = "1" ]; then
               true "INFO: UPDATE_ONLY_IF_NEWER is set 1."
               printf '%s\n' "INFO: Skip downloading: current '$tb_title' '$tbb_locally_installed_version' is already up-to-date."
               tb_exit_function 0
	          fi
         elif dpkg --compare-versions -- "$tbb_version_stripped" gt "$tbb_locally_installed_version"; then
            up_to_date_or_not_text="Looks like there is an upgrade for $tb_title."
            re_install_or_install_text="Please close $tb_title if you want to (re-)install!"
         elif dpkg --compare-versions -- "$tbb_version_stripped" lt "$tbb_locally_installed_version"; then
            up_to_date_or_not_text="<b><u>WARNING: Looks like a downgrade attack!</u></b>"
            re_install_or_install_text="Please close $tb_title if you want to (re-)install!"
         else
            error "Failed to compare versions using dpkg.
<br></br>
<br></br>tbb_locally_installed_version_detect_success: $tbb_locally_installed_version_detect_success"
            tb_exit_function 1
         fi
         MSG="\
<p>$up_to_date_or_not_text</p>

<p>$re_install_or_install_text</p>

<p>If your currently installed version is:<blockquote>
   - higher: you are likely target of a downgrade attack, SAY NO NOW.<br></br>
   - equal : only proceed, if you want to create a new browser profile.<br></br>
   - lower : you should upgrade.</blockquote></p>

<p>$updater_vs_downloader_msg</p>
<p>$confirmation_screen_learn_more_link</p>"
      fi

      output_cli "$MSG"
   else
      ## not installed
      type="info"
      MSG="\
<p>$confirmation_screen_learn_more_link</p>"

      if ! [ "$tb_postinst" = "true" ]; then
         output_cli "INFO: $MSG"
      fi
   fi

   if ! [ "$tb_postinst" = "true" ]; then
      printf '%s\n' "$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Download_Confirmation_Notification"
   fi

   if [ "$TB_INPUT" = "none" ]; then
      true "INFO: TB_INPUT set to none, ok."
   else
      local answer
      if [ "$TB_INPUT" = "stdin" ]; then
         printf '%s\n' "QUESTION: $question
y/n?"
         read -r answer
         if [ ! "$answer" = "y" ]; then
            printf '%s\n' "INFO: Canceled. Exit."
            tb_exit_function 10
         fi
      else
         #printf '%s\n' "/usr/libexec/msgcollector/tb_updater_gui \"$type\" \"$TITLE\" \"$tbb_locally_installed_version_stripped\" \"$tbb_recommended_versions_comma_separated\" \"$MSG\" \"$question\" \"$button\")"

         answer="$("/usr/libexec/msgcollector/tb_updater_gui" "$type" "$TITLE" "$tbb_locally_installed_version_stripped" "$tbb_version_stripped" "$MSG" "$question" "$button")"
         if [ "$answer" = "65536" ]; then ## Button 'Yes' has not been pressed.
            printf '%s\n' "INFO: Canceled. Exit."
            tb_exit_function 10
         fi
      fi
   fi
}

tb_version_processing() {
   if [ "$tb_hard_reset" == "true" ]; then
      tbb_version_stripped="$tbb_version_previous_downloaded_version_stripped"
   fi

   if [ "$tbb_version_stripped" = "" ]; then
      printf '%s\n' "ERROR: in function tb_version_processing: variable $tbb_version_stripped not set!"
      tb_exit_function 17
   fi

   [ -n "$tbb_version_folder" ] || tbb_version_folder="$tbb_version_stripped"
   [ -n "$tb_browser_download_name" ] || tb_browser_download_name="tor-browser"
   [ -n "$TBB_EXTRA_FOLDER" ] || TBB_EXTRA_FOLDER="${tbb_version_folder}"

   [ -n "$TBB_SIG_FILENAME" ] || TBB_SIG_FILENAME="${tb_browser_download_name}-${ARCH_DOWNLOAD}-${tbb_version_stripped}.tar.xz.asc"
   [ -n "$TBB_PACKAGE_FILENAME" ] || TBB_PACKAGE_FILENAME="${tb_browser_download_name}-${ARCH_DOWNLOAD}-${tbb_version_stripped}.tar.xz"

   [ -n "$TBB_SIG_LINK" ] || TBB_SIG_LINK="${TBB_REMOTE_FOLDER}/${TBB_EXTRA_FOLDER}/${TBB_SIG_FILENAME}${TBB_DOWNLOAD_APPENDIX}"
   [ -n "$TBB_SIG_FULL_PATH" ] || TBB_SIG_FULL_PATH="$tb_downloaded_files_folder/$TBB_SIG_FILENAME"

   [ -n "$TBB_PACKAGE_LINK" ] || TBB_PACKAGE_LINK="$TBB_REMOTE_FOLDER/${TBB_EXTRA_FOLDER}/$TBB_PACKAGE_FILENAME${TBB_DOWNLOAD_APPENDIX}"
   [ -n "$TBB_PACKAGE_FULL_PATH" ] || TBB_PACKAGE_FULL_PATH="$tb_downloaded_files_folder/$TBB_PACKAGE_FILENAME"
}

tb_reset() {
   if [ ! "$tb_hard_reset" == "true" ]; then
      return 0
   fi

   printf '%s\n' "INFO: --reset. Performing hard reset rather than download. See: $tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset"

   if ! test -f "$TBB_PACKAGE_FULL_PATH" ; then
      local MSG="\
<p><b>Tor Browser archive file <code>$TBB_PACKAGE_FULL_PATH</code> not yet downloaded. Run without <code>--reset</code> is required.</b>
<br/>
<br/>See:
<br/><a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset>$tb_documentation_base_url_clearnet/wiki/$tb_wiki/Advanced_Users#Tor_Browser_Hard_Reset</a></p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 16
   fi
}

tb_kill_already_running_tb_maybe() {
   if [ ! "$NOKILLTB" = "1" ]; then
      [ -n "$tb_process_name" ] || tb_process_name="firefox.real"

      printf '%s\n' "INFO: Because you are not using --nokilltb, now killing potentially still running instances of '$tb_title'..."
      killall "$tb_process_name" || true
   fi
}

tb_download_common() {
   if [ "$TB_FORCE_INSTALL" = "1" ]; then
      true
   else
      progressbaridx="$(cat -- "/proc/sys/kernel/random/uuid")"
      tb_notify_msg="Download
----------------------------------------------------------------------
$tb_notify_details"
      if [ "$TB_INPUT" = "" ] || [ "$TB_INPUT" = "gui" ]; then
         output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressbarx --parentpid "$$" --typex "info" --progressbartitlex "$TITLE" --message "$tb_notify_msg" --parentpid "$$" --done
         ## $CURL_PRGRS honors the $CURL and the $CURL_PRGRS_EXEC environment
         ## variables. (See above.)
         ## Define what CURL_PRGRS is supposed to eval.
         ## ($percent is a local variable provided by $CURL_PRGRS.)
         export -f output_gui
         CURL_PRGRS_EXEC="output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressx"
         export CURL_PRGRS_EXEC
      fi
   fi

   printf '%s\n' "INFO: CURL_OUT_FILE: '$CURL_OUT_FILE'"

   ## Debugging: Simulating endless data attack.
   #export CURL_PRGRS_MAX_FILE_SIZE_BYTES="1048576"

   curl_exit_code="0"
   $CURL_PRGRS \
      $CURL_RESUME \
      --fail \
      "${CURL_PROXY[@]}" \
      $CURL_FORCE_SSL \
      --retry-connrefused \
      --retry 10 \
      --retry-delay 60 \
      --max-time "$curl_download_max_time" \
      --location \
      $CURL_OPTS \
      --output "$CURL_OUT_FILE" \
      "$curl_download_target_url" \
      &

   last_pid_list="$!"
   wait "$last_pid_list" || { curl_exit_code="$?" ; true; };
   last_pid_list=""

   if [ "$progressbaridx" = "" ]; then
      true
   else
      output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   if [ -x "/usr/libexec/helper-scripts/curl_exit_codes" ]; then
      curl_status_message="$(/usr/libexec/helper-scripts/curl_exit_codes "$curl_exit_code")" || true
   else
      curl_status_message="$curl_exit_code"
   fi

   if [ "$tb_download_common_exit_on_fail" = "false" ]; then
      true "$FUNCNAME is not supposed to exit, because tb_download_common_exit_on_fail is set to $tb_download_common_exit_on_fail."
      return 0
   fi

   ## Check if curl failed.
   if [ ! "$curl_exit_code" = "0" ]; then
      download_fail_help_set
      MSG="<p>Failed to download: $curl_download_target_url</p>

<p>$DOWNLOAD_FAIL_HELP</p>

<p>(Debugging information: curl_status_message: $curl_status_message)</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function "$tb_download_common_exit_on_fail"
   fi

   true "INFO: Done, downloaded: $curl_download_target_url"
}

tb_download_files() {
   ### signature ###

   tb_notify_details="Downloading $tb_title digital signature (GPG)... Will take a moment..."
   printf '%s\n' "INFO: Digital signature (GPG) download... Will take a moment..."
   printf '%s\n' "INFO: Downloading...: '$TBB_SIG_LINK'"

   [ -n "$timeout_signature_file_download" ] || timeout_signature_file_download=300
   ## 1 MB = 1048576 bytes
   ## 2 MB = 2097152 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="2097152"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$TBB_SIG_FULL_PATH"
   safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_signature_file_download"
   curl_download_target_url="$TBB_SIG_LINK"
   tb_download_common_exit_on_fail="8"
   tb_download_common

   ### archive file ###

   tb_notify_details="Downloading $tb_title... Will take a while..."
   printf '%s\n' "INFO: Downloading '$tb_title'..."
   printf '%s\n' "INFO: Downloading...: '$TBB_PACKAGE_LINK'"

   [ -n "$timeout_archive_file_download" ] || timeout_archive_file_download=3600
   ## 1 MB = 1048576 bytes
   ## 200 MB = 209715200 bytes
   ## Export CURL_PRGRS_MAX_FILE_SIZE_BYTES, so $CURL_PRGRS can read it.
   export CURL_PRGRS_MAX_FILE_SIZE_BYTES="209715200"
   ## Export CURL_OUT_FILE, so $CURL_PRGRS can read it.
   export CURL_OUT_FILE="$TBB_PACKAGE_FULL_PATH"
   ## Not deleting to support resume.
   #safe-rm --force -- "$CURL_OUT_FILE"
   curl_download_max_time="$timeout_archive_file_download"
   curl_download_target_url="$TBB_PACKAGE_LINK"
   tb_download_common_exit_on_fail="17"
   tb_download_common

   tb_download_attempt_success="true"
}

tb_gpg_verify() {
   printf '%s\n' "INFO: Digital signature (GPG) verification... This will take a moment..."

   printf '%s\n' "INFO: Using digital signature signing key by The Tor Project."
   gpg_bash_lib_input_key_import_dir="/usr/share/torbrowser-updater-keys.d"

   gpg_bash_lib_input_verify_timeout_after="60"
   gpg_bash_lib_input_verify_kill_after="10"
   gpg_bash_lib_input_temp_folder="$tb_gpg_tmp_dir"
   gpg_bash_lib_input_data_file="$TBB_PACKAGE_FULL_PATH"
   gpg_bash_lib_input_sig_file="$TBB_SIG_FULL_PATH"
   gpg_bash_lib_input_file_name_enforce="false"
   gpg_bash_lib_input_cleanup="false"
   gpg_bash_lib_input_error_handler_extra='tb_error_handler "$gpg_bash_lib_output_error_handler_message"'
   ## One month has 2592000 seconds.
   ## (60 [seconds] * 60 [minutes] * 24 [hours] * 30 [days])
   ## Setting this to 3 months. (777600 seconds)
   ## (2592000 * 3 [months])
   [ -n "$gpg_bash_lib_input_maximum_age_in_seconds" ] || gpg_bash_lib_input_maximum_age_in_seconds="2592000"

   gpg_bash_lib_function_main_verify

   gpg_bash_lib_output_diagnostic_message="$(/usr/libexec/msgcollector/br_add "$gpg_bash_lib_output_diagnostic_message")"

   ## Not checking for gpg_bash_lib_output_alright_status, because then there
   ## would be no way to accept outdated signatures. It is not clear if it is
   ## guaranteed to have a new stable TBB release every three months.
   if [ ! "$gpg_bash_lib_output_validsig_status" = "true" ] || [ "$gpg_bash_lib_output_failure" = "true" ] ; then
      local MSG="<p><b>Digital signature (GPG) could NOT be verified.</b>
<br></br>$tb_title update failed! Try again later.</p>

<p>gpg_bash_lib_output_alright_status: <code>$gpg_bash_lib_output_alright_status</code>
<br></br>gpg_bash_lib_output_failure: <code>$gpg_bash_lib_output_failure</code></p>

<p>gpg_bash_lib_output_diagnostic_message:</p>

<p>$gpg_bash_lib_output_diagnostic_message</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 12
   fi

   printf '%s\n' "INFO: Digital signature (GPG) verification ok."
}

tb_confirm_install() {
   if [ "$tb_confirm_installation_skip" = "true" ]; then
      return 0
   fi

   local MSG question button answer type clock_hint
   local signature_freshness_msg signature_creation_msg
   local last_used_gpg_bash_lib_output_signed_on_date
   local last_used_gpg_bash_lib_output_signed_on_unixtime

   if [ -f "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_date" ]; then
      last_used_gpg_bash_lib_output_signed_on_date="$(cat "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_date")" || true
   fi
   if [ "$last_used_gpg_bash_lib_output_signed_on_date" = "" ]; then
      last_used_gpg_bash_lib_output_signed_on_date="Unknown. Probably never downloaded a signature before."
   fi
   if [ -f "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_unixtime" ]; then
      last_used_gpg_bash_lib_output_signed_on_unixtime="$(cat -- "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_unixtime")" || true
   fi
   if [ "$last_used_gpg_bash_lib_output_signed_on_unixtime" = "" ]; then
      last_used_gpg_bash_lib_output_signed_on_unixtime="unknown"
   fi

   ## Thanks to:
   ## http://mywiki.wooledge.org/BashFAQ/054
   if [[ "$last_used_gpg_bash_lib_output_signed_on_unixtime" != *[!0-9]* ]]; then
      true "'$last_used_gpg_bash_lib_output_signed_on_unixtime' is strictly numeric."
      if [ "$last_used_gpg_bash_lib_output_signed_on_unixtime" -lt "$gpg_bash_lib_output_signed_on_unixtime" ]; then
         signature_freshness_msg="The downloaded signature is newer than the last known signature as expected."
         type="info"
      elif [ "$last_used_gpg_bash_lib_output_signed_on_unixtime" -gt "$gpg_bash_lib_output_signed_on_unixtime" ]; then
         signature_freshness_msg="<b>You are likely target of a downgrade attack</b>, SAY NO NOW! The downloaded signature is older than the last known signature. \
Unless you are downgrading, you should abort now and try again later!"
         type="warning"
      elif [ "$last_used_gpg_bash_lib_output_signed_on_unixtime" = "$gpg_bash_lib_output_signed_on_unixtime" ]; then
         signature_freshness_msg="<b>You could be target of an indefinite freeze attack!</b> The downloaded signature has the same creation date as the last known signature. \
Unless you are re-installing the same version, you should abort now and try again later!"
         type="warning"
      else
         error "last_used_gpg_bash_lib_output_signed_on_unixtime $last_used_gpg_bash_lib_output_signed_on_unixtime \
neither -lt, -gt nor equals gpg_bash_lib_output_signed_on_unixtime $gpg_bash_lib_output_signed_on_unixtime"
         type="error"
      fi
   else
      signature_freshness_msg="We have not previously accepted a signature yet. Therefore assisted check for downgrade \
or indefinite freeze attacks skipped. Please check the Current Signature Creation Date looks sane."
      type="info"
   fi

   if [ -f /usr/share/whonix/marker ]; then
      clock_hint="In that case, please check your clock is correct."
   fi

   case "$gpg_bash_lib_output_freshness_detail" in
      "lenient")
         signature_creation_msg="<b>Your clock might be slow.</b> $clock_hint
<br></br>
<br></br>According to your system clock, the signature was created $gpg_bash_lib_output_signed_on_unixtime_minus_current_unixtime_pretty before current time.
You can probably ignore this, because it still is within range. (Okay up to $gpg_bash_lib_output_maximum_age_pretty_output before.)"
         ;;
      "slow")
         signature_creation_msg="<b>Your clock might be slow.</b> $clock_hint
<br></br>
<br></br>According to your system clock, the signature was created $gpg_bash_lib_output_signed_on_unixtime_minus_current_unixtime_pretty before current time."
         ;;
      "outdated")
         signature_creation_msg="<b>The signature looks quite old already.</b>
<br></br>
<br></br>Either,
<br></br>- your clock might be fast (at least $gpg_bash_lib_output_in_future_pretty_output fast). $clock_hint
<br></br>- there is really no newer signature yet. The signature is really older than $gpg_bash_lib_output_maximum_age_pretty_output already. (Older than $gpg_bash_lib_output_in_future_pretty_output already.)
<br></br>- this is a $SCRIPTNAME bug
<br></br>- this is an attack"
         ;;
      "current")
         signature_creation_msg="According to your system clock, the signature was created $gpg_bash_lib_output_current_unixtime_minus_signed_on_unixtime_pretty ago."
         ;;
      *)
         error "gpg_bash_lib_output_freshness_detail is neither lenient, nor slow, nor outdated, nor current, it is: $gpg_bash_lib_output_freshness_detail"
         return 0
         ;;
   esac

   gpg_bash_lib_output_gpg_verify_output_br_added="$(/usr/libexec/msgcollector/br_add "$gpg_bash_lib_output_gpg_verify_output")"

   MSG="<p><b>Installation confirmation</b></p><p><table><tr>
<td>Currently installed version:</td> <td><tt> <code>$tbb_locally_installed_version_stripped</code></tt></td></tr><tr>
<td>Downloaded version         :</td> <td><tt> <code>$tbb_version_stripped</code></tt></td></tr></table></p>
<p>$signature_freshness_msg</p><p><table><tr>
<td>Previous Signature Creation Date:</td> <td><tt> <code>$last_used_gpg_bash_lib_output_signed_on_date</code></tt></td></tr><tr>
<td>Last Signature Creation Date    :</td> <td><tt> <code>$gpg_bash_lib_output_signed_on_date</code></tt></td></tr></table></p>
<p>$signature_creation_msg</p>
<p><u>gpg reports</u>:<br></br>
$gpg_bash_lib_output_gpg_verify_output_br_added</p>
<p><a href=$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Installation_Confirmation_Notification>Learn more about this Installation Confirmation Notification.</a></p>"

   output_cli "INFO: $MSG"

   printf '%s\n' "$tb_documentation_base_url_clearnet/wiki/$tb_wiki#Installation_Confirmation_Notification"

   button="yesno"
   question="Install now?"

   if [ "$TB_INPUT" = "none" ]; then
      true "INFO: TB_INPUT is set to '$TB_INPUT', continuing without asking."
   else
      if [ "$TB_INPUT" = "stdin" ]; then
         printf '%s\n' "QUESTION: $question
y/n?"
         read -r answer
         if [ ! "$answer" = "y" ]; then
            printf '%s\n' "INFO: Canceled. Exit."
            tb_exit_function 14
         fi
      else
         answer="$(/usr/libexec/msgcollector/generic_gui_message "$type" "$TITLE" "$MSG" "$question" "$button")"
         if [ ! "$answer" = "16384" ]; then ## Button 'Yes' has not been pressed.
            printf '%s\n' "INFO: Canceled. Exit."
            tb_exit_function 14
         fi
      fi
   fi

   overwrite "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_date" "$gpg_bash_lib_output_signed_on_date"
   overwrite "$tb_cache_folder/last_used_gpg_bash_lib_output_signed_on_unixtime" "$gpg_bash_lib_output_signed_on_unixtime"
}

tb_extract() {
   if [ "$TB_NO_EXTRACT" = "true" ]; then
      true "INFO: Skipping $FUNCNAME, because TB_NO_EXTRACT is 'true', ok."
      return 0
   fi

   if [ "$TB_FORCE_INSTALL" = "1" ]; then
      true
   else
      progressbaridx="$(cat -- "/proc/sys/kernel/random/uuid")"
      tb_notify_msg="Extraction
----------------------------------------------------------------------
Extracting $tb_title... This could take a moment..."
      if [ "$TB_INPUT" = "" ] || [ "$TB_INPUT" = "gui" ]; then
         output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressbarx --parentpid "$$" --typex "info" --progressbartitlex "$TITLE" --message "$tb_notify_msg" --parentpid "$$" --done
      fi
   fi

   local timeout_after kill_after file_name
   timeout_after="180"
   kill_after="30"
   file_name="$TBB_PACKAGE_FULL_PATH"

   printf '%s\n' "INFO: Extracting '$file_name'..."

   ## Folder must be already exist, otherwise `tar` would fail.
   mkdir --parents -- "$tb_extract_temp_folder"

   ## Debugging.
   #timeout_after="0.001"
   #kill_after="0.001"

   ## `tar` vs `bsdtar`
   ##
   ## Using `bsdtar` because it can auto detect the archive type.
   ## When using `tar` in a pipe, we would need to explicitly set '--xz'.
   ##
   ## Using '--strip-components 1':
   ## - Because we are also using '--directory "$tb_extract_temp_folder"'
   ## - To remove language specific default folder names such as tor-browser_en-US.

   tb_tar_exit_code="0"

   if [ "$TB_FORCE_INSTALL" = "1" ]; then
      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            bsdtar \
               --strip-components 1 \
               --extract \
               --directory "$tb_extract_temp_folder" \
               --file "$file_name" \
               &

      lastpid="$!"
      true "INFO: wait bsdtar lastpid"
      wait "$lastpid" || { tb_tar_exit_code="$?" ; true; };
   else
      pv_echo_command='echo "extraction percent done: "$percent" / 100" >&2'
      export pv_echo_command
      export -f output_gui
      pv_wrapper_command="output_gui ${output_opts[@]} --progressbaridx $progressbaridx --progressx \"\$percent\""
      export pv_wrapper_command

      local tar_fifo tar_pid pv_wrapper_fifo pv_wrapper_fifo
      local pv_exit_code tar_exit_code pv_wrapper_exit_code
      tar_fifo="$TEMP_DIR/tar_fifo"
      pv_wrapper_fifo="$TEMP_DIR/pv_wrapper_fifo"
      pv_exit_code="0"
      tar_exit_code="0"
      pv_wrapper_exit_code="0"

      safe-rm --force -- "$tar_fifo"
      safe-rm --force -- "$pv_wrapper_fifo"
      mkfifo -- "$tar_fifo"
      mkfifo -- "$pv_wrapper_fifo"

      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            bsdtar \
               --strip-components 1 \
               --extract \
               --directory "$tb_extract_temp_folder" \
               --file \
               - \
               < "$tar_fifo" \
               &
      tar_pid="$!"
      last_pid_list+=" $tar_pid"

      ## Debugging.
      #export pv_wrapper_debug=true

      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            bash \
               /usr/libexec/msgcollector/pv_wrapper \
               < "$pv_wrapper_fifo" \
               &
      pv_wrapper_pid="$!"
      last_pid_list+=" $pv_wrapper_pid"

      timeout \
         --kill-after="$kill_after" \
         "$timeout_after" \
            pv \
               --numeric \
               "$file_name" \
               1> "$tar_fifo" \
               2> "$pv_wrapper_fifo" \
               &
      pv_pid="$!"
      last_pid_list+=" $pv_pid"

      true "INFO: wait pv_pid"
      wait "$pv_pid" || { pv_exit_code="$?" ; true; };
      true "INFO: wait tar_pid"
      wait "$tar_pid" || { tar_exit_code="$?" ; true; };
      true "INFO: wait pv_wrapper_pid"
      wait "$pv_wrapper_pid" || { pv_wrapper_exit_code="$?" ; true; };

      if [ ! "$pv_exit_code" = "0" ]; then
         tb_tar_exit_code="1"
      fi
      if [ ! "$tar_exit_code" = "0" ]; then
         tb_tar_exit_code="1"
      fi
      if [ ! "$pv_wrapper_exit_code" = "0" ]; then
         tb_tar_exit_code="1"
      fi
   fi

   ## `timeout` returns:
   ## - 124 if sigterm was sufficient
   ## - 137 if needed to use kill.

   if [ "$progressbaridx" = "" ]; then
      true
   else
      output_gui ${output_opts[@]} --progressbaridx "$progressbaridx" --progressx "100" || true
      progressbaridx=""
   fi

   if [ ! "$tb_tar_exit_code" = "0" ]; then
      ## Debugging.
      ls -la "$tb_extract_temp_folder" || true

      local MSG="<p><b>Could not extract <code>$file_name</code>!</b>
<br></br>
<br></br>(Debugging information:
<br></br>tb_tar_exit_code: <code>$tb_tar_exit_code</code>
<br></br>pv_exit_code: <code>$pv_exit_code</code>
<br></br>tar_exit_code: <code>$tar_exit_code</code>
<br></br>pv_wrapper_exit_code: <code>$pv_wrapper_exit_code</code>)
<br></br>
<br></br>Please report this bug!</p>"
      output_gui ${output_opts[@]} --messagex --typex "error" --message "$MSG" --done

      output_cli "ERROR: $MSG"

      tb_exit_function 15
   fi

   printf '%s\n' "INFO: Extraction of '$file_name' has been completed."
}

tb_patch_download_folder_create() {
   mkdir --parents -- "$tb_extract_temp_folder/Browser/Downloads"
}

tb_patch() {
   if [ "$TB_NO_PATCH" = "true" ]; then
      true "INFO: Skipping $FUNCNAME, because TB_NO_PATCH is 'true', ok."
      return 0
   fi

   tb_run_function tb_patch_download_folder_create
}

tb_install() {
   if [ "$TB_NO_INSTALL" = "true" ]; then
      true "INFO: Skipping $FUNCNAME, because TB_NO_INSTALL is 'true', ok."
      return 0
   fi

   printf '%s\n' "INFO: Deleting old folder '$tb_browser_folder'."
   safe-rm --recursive --force -- "$tb_browser_folder"

   printf '%s\n' "INFO: Moving temporary folder '$tb_extract_temp_folder' to '$tb_browser_folder'."
   mv -- "$tb_extract_temp_folder" "$tb_browser_folder"
}

tb_cleanup() {
   if [ "$TB_NO_INSTALL" = "true" ]; then
      true "INFO: Skipping $FUNCNAME, because TB_NO_INSTALL is 'true', ok."
      return 0
   fi
   if [ "$TB_NO_CLEANUP" = "true" ]; then
      printf '%s\n' "INFO: Skipping $FUNCNAME, because TB_NO_CLEANUP is 'true', ok."
      return 0
   fi

   printf '%s\n' "INFO: Deleting no longer required file '$TBB_PACKAGE_FULL_PATH' to save space."
   safe-rm --force -- "$TBB_PACKAGE_FULL_PATH"
}

tb_chroot_pre_exit() {
   if [ "$tb_reenable_anon_ws_dns_conf" = "true" ]; then
      printf '%s\n' "INFO: tb_chroot_pre_exit: tb_reenable_anon_ws_dns_conf=true"

      printf '%s\n' "INFO: tb_chroot_pre_exit: Restore whonix-ws-network-conf /etc/resolv.conf by running /usr/libexec/tb-updater/chroot-post ..."

      true "tb_running_as_root: $tb_running_as_root"
      if [ "$tb_running_as_root" = "false" ]; then
         ## TODO: port to privleap - but privleap may not be running inside chroot
         sudo --non-interactive /usr/libexec/tb-updater/chroot-post
      else
         /usr/libexec/tb-updater/chroot-post
      fi

      tb_resolv_conf_debugging
   fi
}

tb_hardcoded_version_last_downloaded_save() {
   overwrite "$tbb_version_last_downloaded_save_file" "$tbb_version_stripped"
   printf '%s\n' "INFO: Saved '$tbb_version_stripped' in '$tbb_version_last_downloaded_save_file'."
}

tb_start_ask() {
   if [ "$noaskstart" = "true" ]; then
      true "INFO: noaskstart is set to 'true'. Skipping question if '$tb_title' should be started."
      return 0
   fi

   if [ "$TB_FORCE_INSTALL" = "1" ]; then
      printf '%s\n' "INFO: Finished installing '$tb_title'. Can be found in folder '$tb_browser_folder'."
      printf '%s\n' "INFO: Not starting '$tb_title', because variable TB_FORCE_INSTALL is set to '1'."
      return 0
   fi

   local command_v_torbrowser_exit_code="0"
   command -v $tb_bin || { command_v_torbrowser_exit_code="$?" ; true; };

   if [ ! "$command_v_torbrowser_exit_code" = "0" ]; then
      printf '%s\n' "INFO: '$tb_bin' binary not found in path. tb-starter is probably not installed. \
Skipping question to start '$tb_title'."
      return 0
   fi

   if [ "$noaskstart" = "false" ]; then
      true "noaskstart is 'false'"
   else
      if [ "$running_inside_qubes_template_vm" = "true" ] ; then
         MSG="<p>Finished installing '$tb_title'. Can be found in '$tb_browser_folder'.</p>

<p>Not asking to start $tb_title, because running in a Template.</p>

<p>To run '$tb_title', start an App Qube, Standalone or Disposable, then run:<br />
<code>$tb_bin</code></p>"
         output_gui ${output_opts[@]} --messagex --typex "info" --message "$MSG" --done

         output_cli "INFO: $MSG"

         return 0
      fi
   fi

   MSG="<p>Finished installing $tb_title. Can be found in $tb_browser_folder.</p>

<p>Please donate!
<br></br><a href=$tb_documentation_base_url_clearnet/wiki/Donate>$tb_documentation_base_url_clearnet/wiki/Donate</a></p>"
   local question="Start $tb_title?"
   local button="yesno"
   local answer

   if [ "$TB_INPUT" = "none" ]; then
      true "INFO: TB_INPUT is set to '$TB_INPUT'. Skipping question if $tb_title should be started."
      return 0
   fi

   if [ "$TB_INPUT" = "stdin" ]; then
      printf '%s\n' "QUESTION: $question
y/n?"
      read -r answer
      if [ ! "$answer" = "y" ]; then
         printf '%s\n' "INFO: Canceled starting '$tb_title', ok."
      else
         $tb_bin
      fi
      return 0
   fi

   printf '%s\n' "INFO: GUI is asking to start $tb_title."
   answer="$(/usr/libexec/msgcollector/generic_gui_message "info" "$TITLE" "$MSG" "$question" "$button")"
   if [ ! "$answer" = "16384" ]; then ## Button 'Yes' has not been pressed.
      printf '%s\n' "INFO: Canceled starting '$tb_title', ok."
      return 0
   else
      printf '%s\n' "INFO: Running '$tb_title'."
      $tb_bin
      printf '%s\n' "INFO: '$tb_title' was terminated, ok."
      return 0
   fi
}

main_function() {
   tb_run_function root_check "$@"
   tb_run_function tb_sanity_tests
   tb_run_function tb_config_folder_parser
   tb_run_function tb_parse_cmd_options "$@"
   tb_run_function tb_preparation
   tb_run_function tb_stdin
   tb_run_function tb_fix_permissions
   tb_run_function tb_create_folders
   tb_run_function tb_qubes_dvm_template
   tb_run_function tb_anon_ws_dns_conf
   tb_run_function tb_local_version_detection

   if [ "$tb_hard_reset" == "true" ]; then
      tb_run_function tb_kill_already_running_tb_maybe
      tb_run_function tb_version_processing
      tb_run_function tb_reset
      tb_run_function tb_gpg_verify
      tb_run_function tb_extract
      tb_run_function tb_patch
      tb_run_function tb_install
      tb_run_function tb_cleanup
      tb_run_function tb_start_ask
      return 0
   fi

   #tb_run_function tb_skip_if_higher_or_equal_version_already_downloaded
   tb_run_function tb_connectivity_checks_interfaces
   tb_run_function tb_connectivity_checks_tor
   tb_run_function tb_connectivity_checks_curl
   tb_run_function tb_update_check
   tb_run_function tb_remote_version_parser
   tb_run_function tb_remote_version_sanity_test

   tb_run_function tb_confirm_update
   tb_run_function tb_version_processing
   tb_run_function tb_kill_already_running_tb_maybe
   tb_run_function tb_download_files
   tb_run_function tb_gpg_verify
   tb_run_function tb_confirm_install
   tb_run_function tb_extract
   tb_run_function tb_patch
   tb_run_function tb_install
   tb_run_function tb_cleanup
   tb_run_function tb_hardcoded_version_last_downloaded_save
   tb_run_function tb_fix_permissions
   tb_run_function tb_chroot_pre_exit
   tb_run_function tb_start_ask
}

if [ "$tb_updater_was_sourced" = "true" ]; then
  true "INFO: Not running because it was sourced by another script."
else
  true "INFO: Running because it was executed."
  tb_run_function main_function "$@"
fi
