#!/bin/bash ## vim:sw=2:sts=2:ts=2:et:spell: ## DO NOT MODIFY, AUTOGENERATED CODE, MODIFY ../../bin/dist-installer-cli and the functions sourced ## ## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ########################## ## BEGIN DEFAULT VALUES ## ########################## ## Declare global variables declare version me start_time dialog_title license adrelanos_signify \ hulahoop_signify oracle_pgp user_home_dir directory_prefix guest \ hypervisor interface oracle_repo log_level guest_version socks_proxy onion \ non_interactive dev noupdate noupgrade dry_run getversion getopt ci no_import \ no_boot redownload import_only destroy_existing_guest testers allow_errors \ mirror virtualbox_only target_user last_exit guest_pretty xtrace sucmd \ url_version_domain run_background background_pid \ debian_derivative_detected ubuntu_derivative_detected \ kali_derivative_detected fedora_derivative_detected \ virtualbox_linux_user_group qubes_template_detected \ install_pkg_fasttrack_extra_args_maybe user_warned_potential_startup_issue \ vboxmanage_locale_english dist_installer_version_pretty \ log_file_user log_file_debug hypervisor_pretty nested_virt_tool \ fasttrack_backports_staging_suites_debsource secure_boot_dkms_key_enrolled \ secure_boot_status_pretty secure_boot_status_short \ secure_boot_mokutil_dkms_test_key kernel_modules_can_be_loaded \ kernel_module_has_been_loaded kernel_module_signer \ kernel_module_modprobe_output modules_signed_only console_write_command \ package_manager_issue_extra_help_text pkg_mngr pkg_mngr_install \ pkg_mngr_update pkg_mngr_check_installed pkg_mngr_upgrade_check \ pkg_mngr_upgrade_install guest_full_vm_name_gateway \ guest_full_vm_name_workstation guest_full_vm_name_kicksecure \ workstation_exists gateway_exists vm_or_vms_already_existing_test_result \ guest_file guest_file_ext debian_testing_or_unstable_detected \ distro_codename_common_use use_deb822_sources oracle_found \ oracle_file_debsource oracle_file_deb822source unstable_found \ unstable_found_with_contrib unstable_file_debsource \ unstable_file_deb822source backports_found backports_file_debsource \ backports_file_deb822source fasttrack_found fasttrack_file_debsource \ fasttrack_file_deb822source fasttrack_backports_staging_found \ fasttrack_backports_staging_file_debsource \ fasttrack_backports_staging_file_deb822source kicksecure_found \ kicksecure_file_debsource kicksecure_file_deb822source kali_found \ kali_found_with_contrib kali_file_debsource kali_file_deb822source \ connection_type_debsource kicksecure_oneline_source \ unstable_oneline_source fasttrack_oneline_source \ fasttrack_backports_staging_oneline_source backports_oneline_source \ kali_oneline_source kicksecure_deb822_source unstable_deb822_source \ fasttrack_deb822_source fasttrack_backports_staging_deb822_source \ backports_deb822_source kali_deb822_source oracle_oneline_source \ oracle_deb822_source log_dir_cur virtualbox_qt_package_name \ virtualbox_cache_version_cmd virtualbox_guest_additions_iso_package_name \ use_rsync use_curl transfer_utility transfer_max_time_large_file \ transfer_max_time_small_file transfer_size_test_connection \ transfer_size_small_file transfer_size_large_file transfer_io_timeout_opt \ transfer_connect_timeout_opt transfer_size_opt transfer_dryrun_opt \ transfer_output_dir_opt transfer_output_file_opt transfer_verbosity_opt \ transfer_speed_optimization_opt transfer_proxy_prefix \ transfer_proxy_suffix proxy curl_transfer_proxy curl_opt_ssl \ url_guest_file url_origin signify_key signify_signer url_domain \ url_version download_flag checkhash interface_name \ dist_installer_cli_was_sourced can_boot_virtualbox_guest_vms APTGETOPT \ APTGETOPT_SERIALIZED if [[ -n "${BASH_SOURCE[0]-}" && "${BASH_SOURCE[0]}" != "${0}" ]]; then ## 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. dist_installer_cli_was_sourced="true" else ## Script was executed. dist_installer_cli_was_sourced="false" set -o errexit set -o errtrace set -o nounset set -o pipefail fi set_globals() { local all_args ## Version is commit based: https://github.com/Whonix/usability-misc version="31.8-1" me="${0##*/}" # shellcheck disable=SC2034 all_args="${*}" start_time="$(date +%s)" dialog_title="License agreement (scroll with arrows)" license=" Please do NOT continue unless you understand everything! DISCLAIMER OF WARRANTY. . THE PROGRAM IS PROVIDED WITHOUT ANY WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE AND MERCHANTABILITY. THE PROGRAM IS BEING DELIVERED OR MADE AVAILABLE 'AS IS', 'WITH ALL FAULTS' AND WITHOUT WARRANTY OR REPRESENTATION. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. . LIMITATION OF LIABILITY. . UNDER NO CIRCUMSTANCES SHALL ANY COPYRIGHT HOLDER OR ITS AFFILIATES, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, FOR ANY DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES ARISING FROM, OUT OF OR IN CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM OR OTHER DEALINGS WITH THE PROGRAM(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), WHETHER OR NOT ANY COPYRIGHT HOLDER OR SUCH OTHER PARTY RECEIVES NOTICE OF ANY SUCH DAMAGES AND WHETHER OR NOT SUCH DAMAGES COULD HAVE BEEN FORESEEN. . INDEMNIFICATION. . IF YOU CONVEY A COVERED WORK AND AGREE WITH ANY RECIPIENT OF THAT COVERED WORK THAT YOU WILL ASSUME ANY LIABILITY FOR THAT COVERED WORK, YOU HEREBY AGREE TO INDEMNIFY, DEFEND AND HOLD HARMLESS THE OTHER LICENSORS AND AUTHORS OF THAT COVERED WORK FOR ANY DAMAGES, DEMANDS, CLAIMS, LOSSES, CAUSES OF ACTION, LAWSUITS, JUDGMENTS EXPENSES (INCLUDING WITHOUT LIMITATION REASONABLE ATTORNEYS' FEES AND EXPENSES) OR ANY OTHER LIABILITY ARISING FROM, RELATED TO OR IN CONNECTION WITH YOUR ASSUMPTIONS OF LIABILITY. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. " ## https://www.whonix.org/wiki/Main/Project_Signing_Key#cite_note-7 adrelanos_signify="untrusted comment: Patrick Schleizer adrelanos@whonix.org signify public key RWQ6KRormNEETq+M8IysxRe/HAWlqZRlO8u7ACIiv5poAW0ztsirOjCQ" ## https://www.whonix.org/wiki/KVM/Project_Signing_Key#cite_note-4 hulahoop_signify="untrusted comment: signify public key RWT2GZDQkp1NtTAC1IoQHUsyb/AQ2LIQF82cygQU+riOpPWSq730A/rq" ## https://www.virtualbox.org/wiki/Linux_Downloads oracle_pgp="-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) mQINBFcZ9OEBEACSvycoAEIKJnyyIpZ9cZLCWa+rHjXJzPymndnPOwZr9lksZVYs 12YnsEy7Uj48rTB6mipbIuDDH9VBybJzpu3YjY7PFTkYAeW6WAPeJ8RcSGXsDvc0 fQ8c+7/2V1bpNofc9vDSdvcM/U8ULQcNeEa6DI4/wgy2sWLXpi1DYhuUOSU10I97 KHPwmpWQPsLtLHEeodeOTvnmSvLX1RRl32TPFLpLdjTpkEGS7XrOEXelqzMBQXau VUwanUzQ2VkzKnh4WecmKFT7iekOFVHiW0355ErL2RZvEDfjMjeIOOa/lPmW7y4F fHMU3a3sT3EzpD9ST/JGhrmaZ+r5yQD4s4hn1FheYFUtUN0dqHe9KgPDecUGgh4w rGnm0nUQsmQLKGSFXskqt26IiERdRt1eXpR9C5yufCVZfYpSsoG/mIHAt9opXFqi ryJqzx5pfQkOLTz9WErThHK1399jyXJwYGKLyddHFQEdy3u3ELM8Kfp7SZD/ERVq t2oA8jsr24IOyL16cydzfSP2kAV1r30bsF/1Q4qq6ii/KfDLaI0KEliBLQuB9jrA 6XQ69VLtkNPgiWzVMclg+qW1pA8ptXqXLMxi4h5EmE5GOhsihuwkwhhBmFqGT1RJ EGlc/uiHWQskOW3nhQ3Epd6xhCUImy8Eu83YRxS6QriH6K8z5LgRSdg9nwARAQAB tElPcmFjbGUgQ29ycG9yYXRpb24gKFZpcnR1YWxCb3ggYXJjaGl2ZSBzaWduaW5n IGtleSkgPGluZm9AdmlydHVhbGJveC5vcmc+iQI3BBMBCgAhBQJXGfThAhsDBQsJ CAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEKL2g8UpgK7P49QP/39dH+lFqlD9ruCV apBKVPmWTiwWbqmjxAV35PzG9reO7zHeZHil7vQ6UCb6FGMgZaYzcj4Sl9xVxfbH Zk7lMgyLDuNMTTG4c6WUxQV9UH4i75E1IBm9lOJw64bpbpfEezUF/60PAFIiFBvD 34qUAoVKe49PbvuTy98er5Kw6Kea880emWxU6I1Q1ZA80+o2dFEEtQc+KCgfWFgd O757WrqbTj6gjQjBAD5B4z5SwBYMg1/TiAYF0oa+a32LNhQIza/5H3Y+ufMfO3tY B/z1jLj8ee5lhjrv0jWvvfUUeIlq5pNoOmtNYFS+TdkO0rsqEC6AD0JRTKsRHOBu eSj7SLt8gmqy7eEzRCMlYIvoQEzt0/JuTQNJjHCuxH1scV13Q3bK6SmxqlY46tf5 Ljni9Z4lLJ7MB1BF2MkHuwQ7OcaEgUQBZSudzPkpRnY0AktiQYYP4Q1uDp+vfvFp GTkY1pqz3z2XD66fLz0ea5WIBBb3X/uq9zdHu8BTwDCiZlWLaDR5eQoZWWe+u+5J NUx1wcBpC1Hr2AnmuXBCRq+bzd8iaB8qxWfpCAFZBksSIW2aGhigSeYdx1jpjOob xog4qbuo5w1IUh8YLHwQ6uM12CqwC1nZadLxG0Fj4KoYbvp0T5ryBM3XD+TVGjKB m/QHLqabxZBbuJT0Cw2dRtW/ty5ZuQINBFcZ9OEBEADEY+YveerQjzzy5nA1FjQG XSaPcjy4JlloRxrUyqlATA0AIuK7cwc7PVrpstV8mR9qb38fdeIoY1z1dD3wnQzJ lbDfZhS5nGMzk9AANd6eJ2KcWI3qLeB//4fr2pPS0piOG4qyW4IhY4KeuCwusE6d IyDBg2XEdpG1IesSDaqNsvLZjPFEBNiCIkqrC7XSmoPNwHkKGj5LeD1wAE914cn2 a04IlbXiT46V9jjJFnNem/Co0u+2e2J3oReNmHvbb62OC57rqeBxqBplXg9tvJk/ w0A3bXxUrfz83tY6vDYoFdwJDudaJJWQjvqpYnySXMJYT6KoE4Xgl5fNcbNIVUpU k74BcWD9PZVadSMN7FWZzMfVsbTMmUA22tuDKD6hrF6ysCelex4YO44kSH7dhXu5 ANtZ2BFfRZvdjTQoblOI8C9cy/iX74vvG8OZarFG+u/kon3+xcAgY5KceUVbostO 0n3V8iK0gMQWH8sR8vXH+oV4GqHUEQURax2XM2Tt7Ra5XGcQaYDIkNPKSVVVtTk5 3OU/bNoBofAbwd4eOZOf9ag5ZVIIaoubMOEiveGYde4AEVE7krSNcYh/C48iCVKr eOyS26AVA15dAvnKTAqxJqICUSQ9zjGfTp1obhXCkMAy0m+AxNVEfSzFznQLHtWK zEGr+zCsvj1R8/qlMpHBXQARAQABiQIfBBgBCgAJBQJXGfThAhsMAAoJEKL2g8Up gK7PKpQP+wY9zLgnJeqrvNowmd70afd8SVge9BvhLh60cdG+piM5ZuEV5ZmfTFoX XPHzOo2dgt6VYTE9JO72Jv7MyzJj3zw3G/IcJQ6VuQwzfKkFTD+IeOiXX2I2lX1y nFv24rs1MTZ4Px1NJai7fdyXLiCl3ToYBmLafFpfbsVEwJ8U9bCDrHE4KTVc9IXO KQ5/86JaIPN+JJLHJoO2EBQC08Cw3oxTDFVcWZ/IWvEFeqyqRSyoFMoDkjLYsqHS we1kEoMmM2qN20otpKYq8R+bIEI5KKuJvAts/1xKE2cHeRvwl5kcFw/S3QQjKj+b LCVTSRZ6EgcDDmsAPKt7o01wmu+P3IjDoiyMZJQZpZIA2pYDxruY+OLXpcmw78Gq lTXb4Q9Vf47sAE8HmHfkh/wrdDeEiY9TQErzCBCufYbQj7sgttGoxAt12N+pUepM MBceAsnqkF6aEa4n8dUTdS2/nijnyUZ2rDVzikmKc0JlrZEKaw8orDzg8fXzfHIc pTrXCmFLX5BzNQ4ezAlw0NZG/qvhSBCuAkFiibfQUal8KLYwswvGJFghuQHsVTkf gF8Op7Br7loTNnp3yiI0jo2D+7DBFqtiSHCq1fIgktmKQoVLCfd3wlBJ/o9cguT4 Y3B83Y34PxuSIq2kokIGo8JhqfqPB/ohtTLHg/o9RhP8xmfvALRD =Rv7/ -----END PGP PUBLIC KEY BLOCK-----" } # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/wc-test.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/wc-test.sh #!/bin/bash ## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. if ! printf '%s\n' "" | wc -l >/dev/null ; then printf '%s\n' "\ $0: ERROR: command 'wc' test failed! Do not ignore this! 'wc' can core dump. Example: zsh: illegal hardware instruction (core dumped) wc -l https://github.com/rspamd/rspamd/issues/5137" >&2 exit 1 fi ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/wc-test.sh # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/get_colors.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/get_colors.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## colors # shellcheck disable=SC2034 get_colors(){ get_colors_sourced=1 ## Disable colors if some environment variables are present. if test -n "${NO_COLOR:-}" || test -n "${ANSI_COLORS_DISABLED:-}" || \ test -z "${TERM:-}" || test "${TERM:-}" = "dumb" || test "${TERM:-}" = "unknown"; then nocolor="" bold="" nobold="" underline="" nounderline="" red="" green="" yellow="" blue="" magenta="" cyan="" return 0 fi printf -v nocolor '%b' "\033[0m" printf -v bold '%b' "\033[1m" printf -v nobold '%b' "\033[22m" printf -v underline '%b' "\033[4m" printf -v nounderline '%b' "\033[24m" printf -v red '%b' "\033[31m" printf -v green '%b' "\033[32m" printf -v yellow '%b' "\033[33m" printf -v blue '%b' "\033[34m" printf -v magenta '%b' "\033[35m" printf -v cyan '%b' "\033[36m" } if test "${get_colors_sourced:-}" != "1"; then get_colors fi ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/get_colors.sh ######################## ## END DEFAULT VALUES ## ######################## ################ ## BEGIN MISC ## ################ # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/has.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/has.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## This is just a simple wrapper around 'command -v' to avoid ## spamming '>/dev/null' throughout this function. This also guards ## against aliases and functions. ## https://github.com/dylanaraps/pfetch/blob/pfetch#L53 has(){ _cmd="$(command -v "${1}")" 2>/dev/null || return 1 [ -x "${_cmd}" ] || return 1 } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/has.sh # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/capitalize_first_char.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/capitalize_first_char.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## Capitalize only the first char of a string. capitalize_first_char(){ printf '%s' "${1:-}" | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1' } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/capitalize_first_char.sh # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/not_as_root.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/not_as_root.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## Block running as root. not_as_root(){ test "$(id -u)" = "0" && die 1 "\ ${underline}Non-Root Check:${nounderline} Running as root. - You are currently running this script with root privileges. - This script should not be run as root. - Please run as normal user." return 0 } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/not_as_root.sh # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/root_cmd.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/root_cmd.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## Wrapper that supports su, sudo, doas root_cmd(){ local cmdarr test -z "${1:-}" && die 1 "${underline}root_cmd function:${nounderline} Failed to pass arguments to function 'root_cmd'." if test -z "${sucmd:-}"; then get_su_cmd fi : "${root_cmd_loglevel:=echo}" case "${sucmd}" in su) ## Thanks to Congelli501 for su to not mess with quotes. ## https://stackoverflow.com/a/32966744/2605155 cmd="$(which -- "${1}")" shift log_run "$root_cmd_loglevel" su root -s "${cmd}" -- "${@}" ;; sudo) cmdarr=( 'log_run' "$root_cmd_loglevel" 'sudo' ) if [ -n "$ROOT_CMD_TARGET_USER" ]; then cmdarr+=( '--user' "$ROOT_CMD_TARGET_USER" ); fi if [ -n "$ROOT_CMD_TARGET_DIR" ]; then cmdarr+=( '--chdir' "$ROOT_CMD_TARGET_DIR" ); fi cmdarr+=( '--' "${@}" ) "${cmdarr[@]}" ;; doas) log_run "$root_cmd_loglevel" doas -u root -- "${@}" ;; *) die 1 "${underline}root_cmd function:${nounderline} 'root_cmd' does not support sucmd: '${sucmd}'" ;; esac } get_su_cmd(){ export ROOT_CMD_TARGET_USER='' export ROOT_CMD_TARGET_DIR='' while true; do has sudo && sucmd=sudo && break has doas && sucmd=doas && break has su && sucmd=su && break [ -z "${sucmd:-}" ] && sucmd='' test -z "${sucmd}" && { die 1 "${underline}get_su_cmd:${nounderline} Failed to find program to run commands with administrative ('root') privileges. This program requires either one of the following programs to be installed: - sudo (recommended) - doas - su" } case "${sucmd}" in sudo) :;; *) log warn "Using sucmd '$sucmd'. Consider installation of 'sudo' instead to cache your passwords instead of typing them every time.";; esac done log info "Testing 'root_cmd' function..." root_cmd printf '%s\n' "test" >/dev/null || die 1 "${underline}get_su_cmd:${nounderline} Failed to run test command as 'root'." if test "${ci:-}" = "1"; then root_output="$(timeout --kill-after 5 5 sudo --non-interactive -- test -d /usr 2>&1)" if test -n "${root_output}"; then log error "'sudo' output: '${root_output}'" die 1 "${underline}get_su_cmd:${nounderline} Unexpected non-empty output for 'sudo' test in CI mode." fi return 0 fi ## Other su cmds do not have an option that does the same. if test "${sucmd}" = "sudo"; then if ! timeout --kill-after 5 5 sudo --non-interactive -- test -d /usr; then log warn "Credential Caching Status: 'No' - Without credential caching, this program will prompt for 'sudo' authorization multiple times. Consider configuring 'sudo' credential caching to streamline the installation process." return 0 fi ## Used by dist-installer-gui. # shellcheck disable=SC2034 credential_caching_status=yes log info "Credential Caching Status: 'Yes'" root_output="$(timeout --kill-after 5 5 sudo -- test -d /usr 2>&1)" if test -n "${root_output}"; then log error "'sudo' output: '${root_output}'" die 1 "${underline}get_su_cmd:${nounderline} Unexpected non-empty output for 'sudo' test in normal mode." fi fi } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/root_cmd.sh # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/ip_syntax.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/ip_syntax.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## Check if variable is integer is_integer(){ printf %d "${1}" >/dev/null 2>&1 || return 1 } ## Checks if the target is valid. ## Address range from 0.0.0.0 to 255.255.255.255. Port ranges from 0 to 65535 ## this is not perfect but it is better than nothing is_addr_port(){ addr_port="${1}" port="${addr_port##*:}" addr="${addr_port%%:*}" ## Support only IPv4 x.x.x.x:y # shellcheck disable=SC2154 if [ "$(printf '%s' "${addr_port}" | tr -cd "." | wc -c)" != 3 ] || [ "$(printf '%s' "${addr_port}" | tr -cd ":" | wc -c)" != 1 ] || [ "${port}" = "${addr}" ] then die 2 "${underline}is_addr_port test:${nounderline} Invalid address:port assignment: ${addr_port}" fi is_integer "${port}" || die 2 "${underline}is_addr_port test:${nounderline} Invalid port '${port}', not an integer." if [ "${port}" -gt 0 ] && [ "${port}" -le 65535 ]; then true "is_addr_port test: Valid port: '${port}'" else die 2 "${underline}is_addr_port test:${nounderline} Invalid port '${port}', not within range: 0-65535." fi for quad in $(printf '%s\n' "${addr}" | tr "." " "); do is_integer "${quad}" || die 2 "${underline}is_addr_port test:${nounderline} Invalid address '${addr}', '${quad}' is not an integer." if [ "${quad}" -ge 0 ] && [ "${quad}" -le 255 ]; then true "Valid quad '${quad}'" else die 2 "${underline}is_addr_port test:${nounderline} Invalid address '${addr}', '${quad}' not within range: 0-255." fi done } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/ip_syntax.sh # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/get_os.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/get_os.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## TODO: how to handle installer specific code? ## Get host os and other necessary information. get_os(){ ## Source: pfetch: https://github.com/dylanaraps/pfetch/blob/master/pfetch os="$(uname -s)" kernel="$(uname -r)" arch="$(uname -m)" distro="" distro_version="" debian_testing_or_unstable_detected="" distro_codename="" case "${os}" in Linux*) if has lsb_release; then distro="$(lsb_release --short --description || lsb_release -sd)" distro_version="$(lsb_release --short --release || lsb_release -sr)" distro_codename="$(lsb_release --short --codename || lsb_release -sc)" elif test -f /etc/os-release; then while IFS='=' read -r key val; do case "${key}" in (PRETTY_NAME) distro="${val}" ;; (VERSION_ID) distro_version="${val}" ;; (VERSION_CODENAME) distro_codename="${val}" ;; esac done < /etc/os-release else has crux && distro="$(crux)" has guix && distro='Guix System' fi distro="${distro##[\"\']}" distro="${distro%%[\"\']}" case "${PATH}" in (*/bedrock/cross/*) distro='Bedrock Linux' ;; esac if [ "${WSLENV:-}" ]; then distro="${distro}${WSLENV+ on Windows 10 [WSL2]}" elif [ -z "${kernel%%*-Microsoft}" ]; then distro="${distro} on Windows 10 [WSL1]" fi ;; Haiku) distro=$(uname -sv);; Minix|DragonFly) distro="${os} ${kernel}";; SunOS) IFS='(' read -r distro _ < /etc/release;; OpenBSD*) distro="$(uname -sr)";; FreeBSD) distro="${os} $(freebsd-version)";; *) distro="${os} ${kernel}";; esac ## Debian 'testing' /etc/os-release does not contain VERSION_ID. if printf '%s' "${distro}" | grep "/sid" &>/dev/null ; then log info "Debian 'testing' or 'unstable' detection: '/sid' matched" debian_testing_or_unstable_detected=1 fi ## TODO: Debian 'forky' - change this from 'forky' to 'duke'. if [ "$distro_codename" = "forky" ]; then log info "Debian 'testing' or 'unstable' detection: 'forky' still considered 'testing' (hardcoded in this program)" debian_testing_or_unstable_detected=1 fi distro_derivative_name_pretty="(No derivative detected.)" distro_derivative_version="(No derivative detected.)" if test -f /usr/share/kicksecure/marker; then distro_derivative_name_pretty="Kicksecure" distro_derivative_version="$(cat -- /etc/kicksecure_version)" elif test -f /usr/share/whonix/marker; then distro_derivative_name_pretty="Whonix" distro_derivative_version="$(cat -- /etc/whonix_version)" fi log notice "Architecture detected: '${arch}'" log notice "System detected: '${os}'" log notice "Distribution/Derivative name detected: '${distro}' / '${distro_derivative_name_pretty}'" log notice "Distribution/Derivative version detected: '${distro_version}' / '${distro_derivative_version}'" if [ "$debian_testing_or_unstable_detected" = "1" ]; then log notice "Debian 'testing' or 'unstable' detection: 'yes', detected" if test "${oracle_repo}" = "1"; then log error "You are attempting to use '--oracle-repo' on Debian 'testing' or 'unstable'. This is impossible." if test "${ci}" = "1"; then die 0 "${underline}Distribution Test Result:${nounderline} Oracle doesn't provide a Debian 'testing' or 'unstable' repository. Skipped on CI to avoid breaking the CI 'testing' or 'unstable'." else die 101 "${underline}Distribution Test Result:${nounderline} Oracle doesn't provide a Debian 'testing' or 'unstable' repository." fi fi log info "Not attempting to use '--oracle-repo' on Debian 'testing' or 'unstable', good." ## In Debian 'testing' distro_version was previously observed as 'n/a' or empty, because ## Debian 'testing' '/etc/os-release' does not contain VERSION_ID. return 0 fi log info "Debian 'testing' or 'unstable' detection: 'no', not detected" ## This at last so the user can hopefully post his system info from the ## logs before the error below. if [ -z "${distro_version}" ]; then if test -f /etc/os-release; then log notice "Contents of '/etc/os-release' file:" cat -- /etc/os-release || true else log notice "'/etc/os-release' file not found." fi die 101 "${underline}Distribution Check:${nounderline} Failed to find distribution version." ## it will fail later on get_host_pkgs if the system is not supported. ## but distro version needs to be checked here because it can occur ## frequently when the release of the distribution is still unstable. ## Also because we check for distribution version to abort if necessary. fi distro_version_without_dot="$(printf '%s' "${distro_version}" | tr -d ".")" is_integer "${distro_version_without_dot}" || die 101 "${underline}Distribution Check:${nounderline} Distribution version without dot is still not a number: '${distro_version_without_dot}'" } get_distro() { true "distro: ${distro}" case "${os}" in Linux*) case "${distro}" in [Dd]"ebian"*|[Tt]"ails"*|[Kk]"icksecure"|[Ww]"honix") debian_derivative_detected=1 ;; [Kk]"ali"*) debian_derivative_detected=1 kali_derivative_detected=1 ;; "linux mint"*|"linuxmint"*|"Linux Mint"*|"LinuxMint"*|"mint"*) ubuntu_derivative_detected=1 debian_derivative_detected=1 ;; *"buntu"*) ubuntu_derivative_detected=1 debian_derivative_detected=1 if [ "${distro_version_without_dot}" -lt 2204 ]; then die 101 "${underline}Distribution Check:${nounderline} Minimal '${distro}' required version is '22.04', yours is '${distro_version}'." fi ;; [Ff]"edora"*|"centos"*|"CentOS"*|"rhel"*|"red hat"|"redhat"*|"Redhat"*|"Red hat") fedora_derivative_detected=1 ;; [Aa]"rch"*|[Aa]"rtix"*|"arcolinux"*|"ArcoLinux"*) claim_unsupported_distro known "${distro}" ;; *) claim_unsupported_distro unknown "${distro}" ;; esac ;; "openbsd"*|"OpenBSD"*) claim_unsupported_distro known "${distro}" ;; "netbsd"*|"NetBSD"*) claim_unsupported_distro known "${distro}" ;; "freebsd"*|"hardenedbsd"*|"dragonfly"*|"FreeBSD"*|"HardenedBSD"*|"DragonFly"*) claim_unsupported_distro known "${distro}" ;; *) claim_unsupported_distro unknown "${distro}" ;; esac if test "${oracle_repo:-}" = "1" && test "${kali_derivative_detected:-}"; then die 1 "Distribution Extended Check: Oracle repository does not work with Kali." fi if test ! "${fedora_derivative_detected:-}" = "1" || test ! "${ci-}" = "1" || test ! "${onion-}" = "1" then return 0 fi die 0 "${underline}Distribution Test Result:${nounderline} Fedora on CI does not run the Tor systemd service. Skipped on CI to avoid breaking the CI testing." } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/get_os.sh get_specifics() { distro_codename_real=$(lsb_release --short --codename) distro_codename_common_use="${distro_codename_real}" ## TODO: How to handle the default value for use_deb822_sources? If we ## default to 'yes', old releases might end up with deb822 sources ## incorrectly. If we default to 'no', new releases might end up with ## one-line sources incorrectly. Defaulting to 'no' for now. if [ "${ubuntu_derivative_detected}" = "1" ]; then if grep --quiet -- "^UBUNTU_CODENAME=\S\+=" -- /etc/os-release; then distro_codename_ubuntu="$(awk -F'=' '/^UBUNTU_CODENAME=/{print $2}' /etc/os-release)" else distro_codename_ubuntu="noble" fi case "${distro_codename_ubuntu}" in noble|plucky|questing) use_deb822_sources='yes' ;; *) use_deb822_sources='no' ;; esac elif [ "${debian_derivative_detected}" = "1" ]; then if [ "${kali_derivative_detected}" = "1" ]; then distro_codename_debian="forky" else distro_codename_debian="${distro_codename_common_use}" fi case "${distro_codename_debian}" in trixie|forky|sid) use_deb822_sources='yes' ;; *) use_deb822_sources='no' ;; esac case "${distro_codename_debian}" in trixie) log info "Install VirtualBox from Oracle repository because at the time of writing VirtualBox is not yet in Debian 'fasttrack' and Debian 'unstable' ('sid') is no longer compatible with 'trixie'." oracle_repo=1 ;; *) ;; esac fi } get_installer_version() { dist_installer_version_pretty="${me} ${version}" get_installer_package_version log notice "dist-installer-cli version: '${dist_installer_version_pretty}'" } get_installer_package_version() { local dist_installer_package if [ ! "${debian_derivative_detected}" = "1" ]; then ## usability-misc package only available for Debian derivatives. ## Check using dpkg-query implemented below only available on Debian and derivatives. true "INFO: No debian derivative detected." return 0 fi ## Do not use commit-hash replace-me as search string to avoid replacement script replacing this. if ! printf '%s' "${version}" | grep --fixed-strings -- "replace-me" >/dev/null ; then true "INFO: version variable does not match replace-me string." return 0 fi if ! printf '%s' "${0}" | grep -- '^/usr/bin' >/dev/null ; then true "INFO: variable 0 does not match ^/usr/bin string. This means not for example /usr/bin/dist-installer-cli. So not packages dist-installer-cli version." return 0 fi if ! printf '%s' "${me}" | grep --fixed-strings -- "installer-cli" >/dev/null ; then true "INFO: program name variable does not match installer-cli string." return 0 fi dist_installer_package="usability-misc" ## Overwrite with '|| true' as this is not important enough to hard fail here. version="$(dpkg-query --show --showformat='${Version}' "${dist_installer_package}")" || true dist_installer_version_pretty="${dist_installer_package} ${version}" return 0 } check_not_qubes_template() { if [ ! -f /run/qubes/this-is-templatevm ]; then true "INFO: Not running inside a Qubes Template." return 0 fi qubes_template_detected=true user_warned_potential_startup_issue=true log warn "${underline}QubesOS Template Detection Test:${nounderline} 'Template detected' - The installer has detected being run inside a Qubes Template." if [ "${virtualbox_only}" = "1" ]; then true "INFO: Running inside a Qubes Template. Continuing via '--virtualbox-only' option." return 0 fi ## Downloading non-Qubes VM images in Qubes Template makes no sense. Not even for development purposes. ## Installing VirtualBox inside Qubes however makes sense for development purposes. ## For example, this permits building non-Qubes VM images inside Qubes VMs. die 1 "${underline}QubesOS Template Detection Test:${nounderline} Only virtualbox-installer-cli ('--virtualbox-only') allowed in Qubes Template." } ############## ## END MISC ## ############## ########################## ## BEGIN OPTION PARSING ## ########################## # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/parse_opt.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/parse_opt.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. ## ------------------- ## ## Usage of parse_opt.sh ## ## usage(){ ## printf '%s\n' "Usage: ${0##*/} [OPTIONS] ## -o, --opt-wo-arg option without argument ## -w, --opt-with-arg option with argument ## " ## exit "${1}" ## } ## ## opt_wo_arg="" ## opt_with_arg="" ## ## while true; do ## begin_optparse "${1:-}" "${2:-}" || break ## case "${opt}" in ## o|opt-wo-arg) opt_wo_arg=1;; ## w|opt-with-arg) get_arg; opt_with_arg="${arg}";; ## h|help) usage 0;; ## "") break;; ## *) die 2 "Invalid option: '${opt_orig}'" ## esac ## shift "${shift_n:-1}" ## done ## ## range_arg opt_with_arg "${opt_with_arg}" p1 p2 ## ## log info "opt_wo_arg=$opt_wo_arg" ## log info "opt_with_arg=$opt_with_arg" ## ------------------- ## ## Begin parsing options. ## function should be called before the case statement to assign the options ## to a temporary variable ## Usage: begin_optparse "${1:-}" "${2:-}" || break begin_optparse(){ ## options ended test -z "${1:-}" && return 1 shift_n="" ## save opt orig for error message to understand which opt failed opt_orig="${1}" # shellcheck disable=SC2034 ## need to pass the second positional parameter cause maybe it is an argument arg_possible="${2}" clean_opt "${1}" || return 1 } ## Clean options. ## '--option=value' should shift once and '--option value' should shift twice ## but at this point it is not possible to be sure if option requires an ## argument, reset shift to zero, at the end, if it is still 0, it will be ## assigned to one, has to be zero here so we can check later if option ## argument is separated by space ' ' or equal sign '=' clean_opt(){ case "${opt_orig}" in "") ## options ended return 1 ;; --) ## stop option parsing shift 1 return 1 ;; --*=*) ## long option '--sleep=1' opt="${opt_orig%=*}" opt="${opt#*--}" arg="${opt_orig#*=}" shift_n=1 ;; -*=*) ## short option '-s=1' opt="${opt_orig%=*}" opt="${opt#*-}" arg="${opt_orig#*=}" shift_n=1 ;; --*) ## long option '--sleep 1' opt="${opt_orig#*--}" arg="${arg_possible}" ;; -*) ## short option '-s 1' opt="${opt_orig#*-}" arg="${arg_possible}" ;; *) ## not an option usage 2 ;; esac } get_arg(){ ## if argument is empty or starts with '-', fail as it possibly is an option case "${arg:-}" in ""|-*) die 2 "Option '${opt_orig}' requires an argument." ;; esac ## shift positional argument two times, as this option demands argument, ## unless they are separated by equal sign '=' ## shift_n default value was assigned when trimming dashes '--' from the ## options. If shift_n is equal to zero, '--option arg', if shift_n is not ## equal to zero, '--option=arg' if test -z "${shift_n}"; then shift_n=2 fi } ## Check if argument is within range ## usage: ## $ range_arg key "${key}" "1" "2" "3" "4" "5" ## $ range_arg key "${key}" "a" "b" "c" "A" "B" "C" range_arg(){ key="${1}" var="${2}" shift 2 list="${*:-}" #range="${list#"${1} "}" if [ -n "${var:-}" ]; then success=0 for tests in ${list:-}; do ## only evaluate if matches all chars [ "${var:-}" = "${tests}" ] && success=1 && break done ## if not within range, fail and show the fixed range that can be used if [ ${success} -eq 0 ]; then die 2 "Option '${key}' cannot be '${var:-}'. Possible values: '${list}'" fi fi } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/parse_opt.sh ######################## ## END OPTION PARSING ## ######################## ################### ## BEGIN LOGGING ## ################### # shellcheck source=../../../helper-scripts/usr/libexec/helper-scripts/log_run_die.sh ##### BEGIN pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/log_run_die.sh #!/bin/bash ## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC ## See the file COPYING for copying conditions. if ! command -v stecho &>/dev/null ; then ## Fallback to 'printf' in case 'stecho' is unavailable. stecho() { printf "%s\n" "$*" } fi re_enable_xtrace_maybe() { if test "${xtrace:-}" = "1"; then set -o xtrace fi } ## Logging mechanism with easy customization of message format as well as ## standardization on how the messages are delivered. ## usage: log [info|notice|warn|error] "X occurred." ## Variable to define by calling script: log_level log(){ : "${log_level:="notice"}" ## Avoid clogging output if log() is working alright. if test "${xtrace:-}" = "1"; then set +o xtrace else case "${-}" in *x*) xtrace=1 set +o xtrace ;; esac fi log_type="${1:-notice}" ## capitalize log level log_type_up="$(printf '%s' "${log_type}" | tr "[:lower:]" "[:upper:]")" shift 1 ## set formatting based on log level case "${log_type}" in bug) log_color="${yellow}" ;; error) log_color="${red}" ;; warn) log_color="${magenta}" ;; info) log_color="${cyan}" ;; notice) log_color="${green}" ;; echo) log_color="" true ;; null) log_color="" true ;; *) log bug "Unsupported log type specified: '${log_type}'" re_enable_xtrace_maybe die 1 "Please report this bug." esac ## uniform log format log_color="${bold}${log_color}" log_source_script="${0##*/}: " log_level_colorized="[${log_color}${log_type_up}${nocolor}]: " log_content="${*}" ## error logs are the minimum and should always be printed, even if ## failing to assign a correct log type ## send bugs and error to stdout and stderr log_full="${log_source_script}${log_level_colorized}${log_content}" case "${log_type}" in bug|error) stecho "${log_full}" >&2 re_enable_xtrace_maybe return 0 ;; null) true ;; esac ## reverse importance order is required, excluding 'error' all_log_levels="warn notice info debug echo null" # shellcheck disable=SC2154 if printf '%s' " ${all_log_levels} " | grep -o ".* ${log_level} " \ | grep " ${log_type}" &>/dev/null then case "${log_type}" in null) true ;; *) stecho "${log_full}" >&2 ;; esac fi #sleep 0.1 re_enable_xtrace_maybe } ## For one liners 'log error; die' ## 'log' should not handle exits, because then it would not be possible ## to log consecutive errors on multiple lines, making die more suitable ## usage: die # "msg" ## where '#' is the exit code. die(){ log error "${2}" if test "${allow_errors:-}" = "1"; then log warn "Skipping termination because of with code '${1}' due to 'allow_errors' setting." return 0 fi case "${1}" in 106|107) true ;; *) log error "Aborting." ;; esac exit "${1}" } ## Wrapper to log command before running to avoid duplication of code log_run(){ local level command_without_extraneous_spaces_temp command_without_extraneous_spaces level="${1}" shift ## Extra spaces appearing when breaking log_run on multiple lines. ## bug: removes all spaces #command_without_extraneous_spaces="$(printf '%s' "${@}" | tr -s " ")" printf -v command_without_extraneous_spaces_temp '%q ' "${@}" command_without_extraneous_spaces="$(printf "%s\n" "${command_without_extraneous_spaces_temp}")" if test "${dry_run:-}" = "1"; then log "${level}" "Skipping command (dry-run): $ ${command_without_extraneous_spaces}" return 0 fi ## TODO: Still an issue? CI expects no output from root_cmd() which calls log_run(). if test "${run_background:-}" = "1"; then log "${level}" "Background command starting: $ ${command_without_extraneous_spaces} &" "${@}" & background_pid="$!" disown "$background_pid" else log "${level}" "Command executing: $ ${command_without_extraneous_spaces}" "${@}" || return 1 fi } ## Useful to get runtime mid run to log easily ## Variable to define outside: start_time # shellcheck disable=SC2154 get_elapsed_time(){ printf '%s\n' "$(($(date +%s) - start_time))" } ## Log elapsed time, the name explains itself. log_time(){ log info "Time elapsed: $(get_elapsed_time)s." } ##### END pasted by build-dist-installer-cli from file /usr/libexec/helper-scripts/log_run_die.sh ## Wrapper to end the exit trap. end_exit() { ## Reset exit trap. trap - EXIT HUP INT QUIT ABRT ALRM TERM ## Kill tail PID. if [ -n "${tail_pid:-}" ]; then ## Sleep less than a second so the file descriptors have enough time to ## output all the logs to the screen before the background job is killed. sleep 0.3 if kill -0 -- "${tail_pid}"; then ## TODO: Check if this is really necessary. # shellcheck disable=SC2086 kill -s sigterm -- "${tail_pid}" || true fi fi true "INFO: Show log file locations at end of xtrace." true "${log_file_user:-}" true "${log_file_debug:-}" ## Exit with desired exit code. exit "${last_exit}" } ## Handle exit trap with line it failed and its exit code. handle_exit() { local line_number line_above line_error line_below signal_code signal_caught true "BEGIN handle_exit() with args: $*" last_exit="${1}" line_number="${2:-0}" log_time ## Exit without errors. [ "${last_exit}" = "0" ] && end_exit ## Virtual Machine expected start issues. [ "${last_exit}" = "106" ] && end_exit ## Virtual Machine unexpected start issues. [ "${last_exit}" = "107" ] && end_exit ## Exit with errors. if [ -n "${BASH_COMMAND:-}" ]; then log notice "Executed script, function, command executed: '${0}' '${FUNCNAME[1]}' '${BASH_COMMAND}'" else log notice "Executed script: '${0}'" fi ## some shells have a bug that displays line 1 as LINENO if [ "${line_number}" -gt 2 ]; then log error "Installer aborted due to an error." log error "No need to panic. Nothing is broken. Just some rare condition has been hit." log error "A solution likely exists for this issue." log error "If the issue does not recur on retry, it can be safely ignored as transient." log error "Consult ${guest_pretty} News and the User Help Forum for assistance." log error "Please report this bug if it has not been already." printf '\n' log error "An error occurred at line: '${line_number}'" ## ideas from https://unix.stackexchange.com/questions/39623/trap-err-and-echoing-the-error-line ## Simple version that doesn't indicate the error line. # pr -tn "${0}" | tail -n+$((line_number - 3)) | head -n3 ## Easy version for wasting resources and better readability. line_above="$(pr -tn "${0}" | tail -n+$((line_number - 4)) | head -n4)" line_error="$(pr -tn "${0}" | tail -n+$((line_number)) | head -n1)" line_below="$(pr -tn "${0}" | tail -n+$((line_number + 1)) | head -n4)" printf '%s\n*%s\n%s\n' "${line_above}" "${line_error}" "${line_below}" ## Too complex. # awk 'NR>L-4 && NR>>":""),$0 }' L="${line_number}" "${0}" >&2 printf '\n' log error "Please include the user log and the debug log in your bug report." log error "(For file locations where to find these logs, see above.)" printf '\n' else if [ "${last_exit}" -gt 128 ] && [ "${last_exit}" -lt 193 ]; then signal_code=$((last_exit-128)) ## 'kill -l': Use short option name because 'kill' is a built-in and does not support long option name '--list'. ## 'kill -l' does not support end-of-options. signal_caught="$(kill -l "${signal_code}")" log error "Signal received: '${signal_caught}'" fi fi ## Print exit code. log error "Installer exited with code: '${last_exit}'" end_exit } ################# ## END LOGGING ## ################# ########################### ## BEGIN SCRIPT SPECIFIC ## ########################### claim_unsupported_distro() { local status distro status="${1}" distro="${2}" log error "At this time, your Operating System is unsupported by the '${guest_pretty}' Installer." log error "Visit the following URL to check support for manual installation:" log error " ${url_version_domain}/wiki/Virtualbox" die 101 "${underline}Distribution Check:${nounderline} Unsupported ('${status}' '${distro}') system." } ## Get necessary packages for your host system to be able to set the guest. get_host_virtualizer_pkgs() { log notice "$hypervisor_pretty Installation: Required system virtualization packages are being installed... Please wait, this process may take a few minutes..." if [ "$ubuntu_derivative_detected" = "1" ]; then install_package_debian_common install_virtualbox_ubuntu elif [ "$debian_derivative_detected" = "1" ]; then install_package_debian_common install_virtualbox_debian elif [ "$fedora_derivative_detected" = "1" ]; then install_package_fedora_common install_virtualbox_fedora else die 1 "Operating system not found in 'get_host_virtualizer_pkgs'. Please report this bug. The debug log is required." fi } get_independent_host_pkgs() { ## Platform independent packages if has signify-openbsd; then ## fix Debian unconventional naming run_signify() { ## Debian places in signify-openbsd in folder '/bin' and not '/usr/bin'. run_as_target_user signify-openbsd "${@}" } else run_signify() { run_as_target_user signify "${@}" } fi has timeout || die 1 "${underline}Packages Installed Check:${nounderline} Timeout utility is missing." has curl || install_pkg curl has rsync || install_pkg rsync has mokutil || install_pkg mokutil ## Install openssl and ca-certificates. ## openssl is required: ## Otherwise there will be an error message by rsync-ssl: ## Failed to find on your path: openssl stunnel4 stunnel ## ca-certificates is required: ## Otherwise rsync-ssl will fail TLS verification. ## XXX: Strictly speaking packages openssl ca-certificates are only required ## if using clearnet. I.e. not required when using --onion. install_pkg openssl ca-certificates while true; do has systemd-detect-virt && nested_virt_tool="systemd-detect-virt" && break test_pkg virt-what && nested_virt_tool="virt-what" && break install_pkg virt-what && nested_virt_tool="virt-what" && break break done } nested_virtualization_test() { #nested_virtualization_detected="" if [ -z "${nested_virt_tool:-}" ]; then ## No hard fail, not a requirement, good to have only. user_warned_potential_startup_issue=true log warn "${underline}Nested Virtualization Test:${nounderline} Detection tool for nested virtualization is missing." else if [ "${dry_run}" = "1" ]; then log notice "Nested Virtualization Test: Skipping test due to dry_run mode." else ## Check if we are a guest of virtualization. if root_cmd "${nested_virt_tool:-}" >/dev/null 2>&1; then #nested_virtualization_detected=true user_warned_potential_startup_issue=true log warn "${underline}Nested Virtualization Test:${nounderline} Nested virtualization detected. - This might be a user error. - This installer is designed to run on the host operating system. - This installer is not designed to be run inside virtual machines. - For more information about nested virtualization, refer to: ${url_version_domain}/wiki/Nested_Virtualization" fi fi fi if [ -f /usr/share/qubes/marker-vm ]; then #nested_virtualization_detected=true user_warned_potential_startup_issue=true log warn "${underline}QubesOS Detection Test:${nounderline} 'Qubes detected' - The installer is not designed for execution within Qubes. - Useful only for development purposes. - It is recommended to use Qubes-Whonix instead." fi } secure_boot_test() { local mokutil_output_sb_state secure_boot_dkms_key_enrolled="" if mokutil_output_sb_state=$(root_cmd mokutil --sb-state 2>&1) ; then if printf '%s' "$mokutil_output_sb_state" | grep --ignore-case --fixed-strings -- "enabled" >/dev/null; then secure_boot_status_pretty="'enabled' (mokutil_output_sb_state: '$mokutil_output_sb_state')" secure_boot_status_short='true' ## sudo mokutil --test-key /var/lib/dkms/mok.pub ## > /var/lib/dkms/mok.pub is already enrolled ## > zsh: exit 1 sudo mokutil --test-key /var/lib/dkms/mok.pub# ## Added "|| true" to handle the non-zero exit code. ## Redirection '2>&1' not necessary yet but that could be considered a minor bug in mokutil to write to stdout instead ## of writing to stderr. secure_boot_mokutil_dkms_test_key="$(root_cmd mokutil --test-key "/var/lib/dkms/mok.pub" 2>&1)" || true if printf '%s' "$secure_boot_mokutil_dkms_test_key" | grep --ignore-case --fixed-strings -- "already enrolled" >/dev/null ; then secure_boot_dkms_key_enrolled=true fi elif printf '%s' "$mokutil_output_sb_state" | grep --ignore-case --fixed-strings -- "disabled" >/dev/null ; then secure_boot_status_short='false' secure_boot_status_pretty="'disabled' (mokutil_output_sb_state: '$mokutil_output_sb_state')" else secure_boot_status_short='false' secure_boot_status_pretty="'unknown-please-report-this-minor-bug' (mokutil_output_sb_state: '$mokutil_output_sb_state')" fi else secure_boot_status_short='false' secure_boot_status_pretty="'disabled' (mokutil_output_sb_state: '$mokutil_output_sb_state')" fi log info "Secure Boot Check Result: $secure_boot_status_pretty Ok." } dkms_signing_key_enrollment() { if [ ! "$secure_boot_status_short" = "true" ]; then log info "Secure Boot DKMS Signing Key Enrollment Check Result: 'success' - Secure Boot is disabled, therefore DKMS signing key enrollment is unnecessary, ok." return 0 fi if [ "$secure_boot_dkms_key_enrolled" = "true" ]; then log info "Secure Boot DKMS Signing Key Enrollment Check Result: 'success' - Secure Boot is enabled and DKMS signing key has already been enrolled, ok." return 0 fi if ! [ "${hypervisor}" = "virtualbox" ]; then log info "Secure Boot DKMS Signing Key Enrollment Check Result: 'success' - Secure Boot is enabled, but the DKMS signing key is not enrolled. This is however unnecessary for hypervisor '${hypervisor}'." return 0 fi log notice "secure_boot_mokutil_dkms_test_key: '$secure_boot_mokutil_dkms_test_key'" ## Not simple to automate, because it requires a password prompt. ## https://git.launchpad.net/~ubuntu-core-dev/shim/+git/shim-signed/tree/update-secureboot-policy #root_cmd mokutil --import /var/lib/dkms/mok.pub ## ## sudo mokutil --import /var/lib/dkms/mok.pub ## if already enrolled: ## > SKIP: /var/lib/dkms/mok.pub is already enrolled ## ## sudo mokutil --test-key /var/lib/dkms/mok.pub ## > /var/lib/dkms/mok.pub is not enrolled ## With exit code 0. ## ## sudo mokutil --root-pw --import /var/lib/dkms/mok.pub ## This can fail if the root account is locked / has no password such as in Kicksecure. ## > Failed to get root password hash ## ## This is a Debian DKMS / shim-signed issue: ## - dkms Debian: enroll DKMS signing key / automate running "sudo mokutil --import /var/lib/dkms/mok.pub" ## https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1076269 ## - shim-signed Debian: please update update-secureboot-policy / add '--new-key' option ## https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1076278 ## - dkms upstream: automate running "`sudo mokutil --import /var/lib/dkms/mok.pub`" ## https://github.com/dell/dkms/issues/429 die 1 "${underline}Secure Boot DKMS Signing Key Enrollment Check Result:${nounderline} ${red}${bold}'FAIL'${nobold}${nocolor} - Secure Boot is enabled, but the DKMS signing key is not enrolled. - VirtualBox kernel modules cannot be loaded due to this issue. - For instructions on DKMS Signing Key Enrollment, visit: https://www.kicksecure.com/wiki/Secure_Boot " } kernel_modules_load() { local modules_disabled ## Does usually not require root but might on some hardened systems. if ! modules_disabled=$(root_cmd_loglevel=null root_cmd cat -- /proc/sys/kernel/modules_disabled) ; then kernel_modules_can_be_loaded=maybe user_warned_potential_startup_issue=true log warn "${underline}Kernel Module Load Check Result:${nounderline} 'failed' (failed to cat -- /proc/sys/kernel/modules_disabled)" return 0 fi if [ "$modules_disabled" = "0" ]; then kernel_modules_can_be_loaded=true log info "Kernel Module Load Check Result: 'yes' - modules can be load. (sysctl kernel.modules_disabled=0 is set.)" return 0 fi kernel_modules_can_be_loaded=false log info "${underline}Kernel Module Load Check Result:${nounderline} 'no' - modules cannot be load. (sysctl kernel.modules_disabled=1 is set.)" } kernel_modules_signed_only() { local sig_enforce ## Does usually not require root but might on some hardened systems. if ! sig_enforce=$(root_cmd_loglevel="echo" root_cmd cat -- /sys/module/module/parameters/sig_enforce) ; then modules_signed_only=maybe user_warned_potential_startup_issue=true log warn "${underline}Kernel Module Signature Enforcement Check Result:${nounderline} 'failed' (failed to cat -- /sys/module/module/parameters/sig_enforce)" return 0 fi if [ "$sig_enforce" = "Y" ]; then modules_signed_only=true log info "Kernel Module Signature Enforcement Check Result: 'yes' - only signed modules can be load. (kernel parameter module.sig_enforce=1 might be set.)" return 0 fi modules_signed_only=false log info "Kernel Module Signature Enforcement Check Result: 'no' - unsigned modules can be load." } need_reboot_check_first() { ## Debian: /var/run/reboot-required ## Fedora: Does not have this. if [ ! -f /var/run/reboot-required ]; then log info "Reboot Check Result: File /var/run/reboot-required does not exist, good." return 0 fi if [ "${ci}" = "1" ]; then true "INFO: Ignore need_reboot_check_first because running on CI." return 0 fi ## Building for an old kernel might result in missing kernel headers for that kernel ## In result, VirtualBox would fail to start. die 1 "${underline}Reboot Check Result:${nounderline} Your system reports that a reboot is required. Please reboot your system and restart this installer. Debugging information: file /var/run/reboot-required exists." } need_reboot_check_second() { true "fedora_derivative_detected: $fedora_derivative_detected" if [ ! "$fedora_derivative_detected" = "1" ]; then return 0 fi ## Fedora 38: Part of dnf-utils. Not installed by default (on CI). ## Fedora 39: Part of dnf-plugins-core. Not installed by default (on CI). ## Therefore this can only run after install_package_fedora_common. if root_cmd /usr/bin/needs-restarting --reboothint ; then true "INFO: No reboot required." return 0 fi if [ "${ci}" = "1" ]; then true "INFO: Ignore need_reboot_check_second because running on CI." return 0 fi die 1 "${underline}Reboot Check Result:${nounderline} Your system reports that a reboot is required. Please reboot your system and restart this installer. Debugging information: needs-restarting reported that a reboot is required." } update_sources() { local update_output update_cmd log notice "Updating package list..." ## global console_write_command if tty >/dev/null >&1 ; then console_write_command=( 'tee' '/dev/tty' ) else ## github actions CI issue: ## tee: /dev/tty: No such device or address ## https://github.com/actions/runner/issues/241 console_write_command=( 'cat' ) fi #root_cmd "${pkg_mngr_update[@]}" 2>&1 || true #update_sources_error ## result: OK: command_without_extraneous_spaces="sudo -- apt-get update --yes --error-on=any" ## But if root_cmd is run in a subshell using $(root_cmd ...) then command_without_extraneous_spaces ## won't be updated. if [ "${noupdate}" = "1" ]; then ## Too slow to run over and over again during testing. update_cmd=( 'true' "${pkg_mngr_update[@]}" ) log warn "Package List Update: Simulate only, via '--noupdate' option." else update_cmd=( "${pkg_mngr_update[@]}" ) fi if update_output=$(root_cmd_loglevel=notice root_cmd "${update_cmd[@]}" 2>&1 | "${console_write_command[@]}") ; then true "INFO: Exit code is zero but that does not guarantee in case of dnf that there is no error." if printf '%s' "$update_output" | grep --ignore-case -- "Error:" >/dev/null ; then log error "${underline}Package List Update:${nounderline} Exit code was 0 but 'Error:' found in output." if printf '%s' "$update_output" | grep --ignore-case -- "GPG signature verification error: Signing key not" >/dev/null; then log warn "${underline}Package List Update:${nounderline} Signing key not found. Skipping due to 'rpm --import' failing to import keys, but '--assumeyes' being used will import keys." return 0 fi update_sources_error else true "INFO: No error found in update output, ok." return 0 fi else log error "${underline}Package List Update:${nounderline} Non-zero exit code. Stop." update_sources_error fi } package_manager_issue_extra_help_text="The user is advised to attempt to debug this with the following steps: 1. Run above command as root (with sudo). 2. If there is an issue, use search engines, documentation and if needed contact the support of your operating system. 3. Once this has been fixed fixed, re-run this installer." update_sources_error() { die 1 "${underline}Package List Update:${nounderline} Could not update package lists. - This issue is most likely not caused by this installer. - This is most likely a package manager configuration or network issue. This is the command which the installer has just run that failed: ${sucmd} ${pkg_mngr_update[*]} ${package_manager_issue_extra_help_text}" } check_upgrades_simulation() { local upgrade_simulate_output packages_upgradable upgrade_simulate_cmd upgrade_simulate_cmd=( "${pkg_mngr_upgrade_check[@]}" ) [ "${#install_pkg_fasttrack_extra_args_maybe[@]}" != '0' ] \ && upgrade_simulate_cmd+=( "${install_pkg_fasttrack_extra_args_maybe[@]}" ) if upgrade_simulate_output=$(root_cmd_loglevel=notice root_cmd "${upgrade_simulate_cmd[@]}" 2>&1 | "${console_write_command[@]}") ; then true "INFO: Exit code is zero but that does not guarantee in case of dnf that there is no error." if printf '%s' "$upgrade_simulate_output" | grep --ignore-case -- "Error:" >/dev/null ; then log error "${underline}Package Upgrade Simulation:${nounderline} Exit code was 0 but 'Error:' found in output." upgrade_simulate_sources_error fi else log error "${underline}Package Upgrade Simulation:${nounderline} Non-zero exit code. Stop." upgrade_simulate_sources_error fi true "INFO: No error found in upgrade_simulate_output output, ok." if [ "${#install_pkg_fasttrack_extra_args_maybe[@]}" = '0' ]; then if pkg_mngr_upgradable_check >/dev/null ; then packages_upgradable=false else packages_upgradable=true fi else if pkg_mngr_upgradable_check "${install_pkg_fasttrack_extra_args_maybe[@]}" >/dev/null ; then packages_upgradable=false else packages_upgradable=true fi fi if [ "${dry_run}" = "1" ]; then packages_upgradable=false fi if [ "$packages_upgradable" = "false" ]; then log info "Package Upgrade Simulation: No packages require upgrading, ok." return 0 fi if [ "${noupgrade}" = "1" ]; then log warn "Package Upgrade Simulation: Package upgrades available but proceeding anyhow via '--noupgrade' option." return 0 fi die 1 "${underline}Package Upgrade Simulation:${nounderline} Package upgrades available. Please upgrade your operating system first. Otherwise this installer cannot proceed. ${sucmd} ${pkg_mngr_upgrade_install[*]} ${install_pkg_fasttrack_extra_args_maybe[*]} ${package_manager_issue_extra_help_text}" } upgrade_simulate_sources_error() { die 1 "${underline}Package Upgrade Simulation:${nounderline} Failed. - This issue is most likely not caused by this installer. - This is most likely a package manager configuration or network issue. This is the command which the installer has just run that failed: ${sucmd} ${pkg_mngr_upgrade_check[*]} ${install_pkg_fasttrack_extra_args_maybe[*]} ${package_manager_issue_extra_help_text}" } ## Install package only if not installed already. install_pkg() { local pkgs arg special_args pkg_not_installed pkgs="${*}" special_args=() pkg_not_installed=() for arg in ${pkgs}; do case "${arg}" in --target-release=*) special_args+=( "${arg}" ) ;; *) ## Test if package exists as a binary or a library, using different tools. if "${pkg_mngr_check_installed[@]}" "${arg}" >/dev/null 2>&1; then log info "Package already installed: '${arg}'" continue fi if has "${arg}"; then log info "Program already installed: '${arg}'" continue fi pkg_not_installed+=( "${arg}" ) ;; esac done if [ "${#pkg_not_installed[@]}" != '0' ]; then if [ "${dry_run}" = "1" ]; then log notice "Skipping installation of the following packages via '--dry-run' option: '${pkg_not_installed[*]}'" return 0 fi log notice "Installing package(s): '${pkg_not_installed[*]}'" log info "special_args: '${special_args[*]}'" if ! root_cmd "${pkg_mngr_install[@]}" "${special_args[@]}" "${pkg_not_installed[@]}"; then if printf '%s' "${pkg_not_installed[*]}" | grep --ignore-case -- "virtualbox" >/dev/null; then virtualbox_installation_failure_debug fi die 1 "${underline}Package Installation:${nounderline} Failed to install package: '${pkg_not_installed[*]}'" fi ## Test if installation succeeded. test_pkg "${pkg_not_installed[@]}" fi } ## Used to test for a 2nd time if packages exist or not, if not, ## install_pkg() failed above and best thing to do is abort because of missing ## dependencies. test_pkg() { local pkgs pkg pkg_not_installed pkgs=( "$@" ) pkg_not_installed=() for pkg in "${pkgs[@]}"; do if ! has "${pkg}" && ! "${pkg_mngr_check_installed[@]}" "${pkg}" >/dev/null 2>&1 then pkg_not_installed+=( "${pkg}" ) fi done if [ "${#pkg_not_installed[@]}" != '0' ]; then if [ "${dry_run}" = "1" ]; then log info "Failed to locate package(s) and ignoring via '--dry-run' option: '${pkg_not_installed[*]}'" else log info "Failed to locate package(s): '${pkg_not_installed[*]}'" return 1 fi fi } check_vm_running_general() { case "${guest}" in whonix) check_vm_running_virtualbox "${guest_full_vm_name_gateway}" check_vm_running_virtualbox "${guest_full_vm_name_workstation}" ;; kicksecure) check_vm_running_virtualbox "${guest_full_vm_name_kicksecure}" ;; esac } ## Abort if user wants to reimport a VM that is running. ## This function is called before attempting to reimport an image. check_vm_running_virtualbox() { local vm vm="${1}" ## Paused state should be considered as running. Instead of grepping ## possible states, grep VM from list of running VMs. # vboxmanage list runningvms | grep -- "^\"${vm}\" " >/dev/null if run_as_target_user "${vboxmanage_locale_english[@]}" showvminfo "${vm}" 2>&1 | \ grep -E -- "^State:[[:space:]]+(running|paused)" >/dev/null then log error "Cannot proceed. You have the following VM running: ${vm}" die 1 "${underline}VM Running Check:${nounderline} Please turn it off before re-running this installer." fi } ## Check if VM exists on VirtualBox check_vm_registered_virtualbox() { local extra_message extra_message="$1" case "${guest}" in whonix) ## Test if machine exists. workstation_exists=0 gateway_exists=0 if run_as_target_user "${vboxmanage_locale_english[@]}" showvminfo "${guest_full_vm_name_gateway}" >/dev/null 2>&1 ; then gateway_exists=1 log info "Existing VM Check Result $extra_message: guest '${guest_full_vm_name_gateway}' exists." fi if run_as_target_user "${vboxmanage_locale_english[@]}" showvminfo "${guest_full_vm_name_workstation}" >/dev/null 2>&1 ; then workstation_exists=1 log info "Existing VM Check Result $extra_message: guest '${guest_full_vm_name_workstation}' exists." fi ## Find discrepancies. if [ "${workstation_exists}" = "0" ] && [ "${gateway_exists}" = "1" ]; then log info "Existing VM Check Result $extra_message: Gateway exists but Workstation doesn't." fi if [ "${workstation_exists}" = "1" ] && [ "${gateway_exists}" = "0" ]; then log info "Existing VM Check Result $extra_message: Workstation exists but Gateway doesn't." fi vm_or_vms_already_existing_test_result=false if [ "${workstation_exists}" = "1" ] || [ "${gateway_exists}" = "1" ]; then ## If either one of the guests exists, proceed. vm_or_vms_already_existing_test_result=true fi if [ "${vm_or_vms_already_existing_test_result}" = "false" ]; then ## Both guests are still non-existing. Therefore return from this function. log info "Existing VM Check Result $extra_message: None existing yet, ok." return 0 fi log notice "Existing VM Check Result $extra_message: Virtual Machine(s) have been imported previously." ;; kicksecure) vm_or_vms_already_existing_test_result=false if ! run_as_target_user "${vboxmanage_locale_english[@]}" showvminfo "${guest_full_vm_name_kicksecure}" >/dev/null 2>&1 ; then log info "Existing VM Check Result $extra_message: None existing yet, ok." return 0 fi log notice "Existing VM Check Result $extra_message: Virtual Machine(s) were imported previously." vm_or_vms_already_existing_test_result=true ;; esac } ## Check if VM exists using hypervisor tools. check_vm_exists_general() { if [ "${virtualbox_only}" = "1" ]; then return 0 fi if [ "${hypervisor}" = "kvm" ]; then return 0 fi check_vm_registered_virtualbox "$1" } check_vm_file_exists_virtualbox_general() { local file_name_list file_name_item ## '/home/user/VirtualBox VMs/Kicksecure-Xfce/Kicksecure-Xfce.vbox' ## '/home/user/VirtualBox VMs/Whonix-Gateway-Xfce/Whonix-Gateway-Xfce.vbox' ## '/home/user/VirtualBox VMs/Whonix-Workstation-Xfce/Whonix-Workstation-Xfce.vbox' if [ -z "${user_home_dir+x}" ]; then log warn "VM Import Check: Skip testing if there is an extraneous '.vbox' file because environment variable user_home_dir is unset." return 0 fi file_name_list=() case "${guest}" in whonix) if [ "${import_only}" = "gateway" ]; then file_name_list+=("${user_home_dir}/VirtualBox VMs/${guest_full_vm_name_gateway}/${guest_full_vm_name_gateway}.vbox") elif [ "${import_only}" = "workstation" ]; then file_name_list+=("${user_home_dir}/VirtualBox VMs/${guest_full_vm_name_workstation}/${guest_full_vm_name_workstation}.vbox") else file_name_list+=("${user_home_dir}/VirtualBox VMs/${guest_full_vm_name_gateway}/${guest_full_vm_name_gateway}.vbox") file_name_list+=("${user_home_dir}/VirtualBox VMs/${guest_full_vm_name_workstation}/${guest_full_vm_name_workstation}.vbox") fi ;; kicksecure) file_name_list+=("${user_home_dir}/VirtualBox VMs/${guest_full_vm_name_kicksecure}/${guest_full_vm_name_kicksecure}.vbox") ;; esac for file_name_item in "${file_name_list[@]}" ; do if test_file -e "$file_name_item" ; then log warn "VM Import Check: Inconsistent state. This might have happened by previously using VirtualBox GUI 'Remove...' with 'Remove only' instead of 'Delete all files'. File '$file_name_item' exists but there is no associated VM registered in VirtualBox." if [ -z "${import_only}" ]; then die 1 "VM Import Check: Aborting because of inconsistent state and missing '--import-only' option." fi if [ "${destroy_existing_guest}" != "1" ]; then die 1 "VM Import Check: Aborting because of inconsistent state and missing '--destroy-existing-guest' option." fi log warn "VM superfluous '.vbox' File Deletion: Removing file '$file_name_item' via '--import-only=${import_only}' and '--destroy-existing-guest' option." log_run warn rm -f -- "${file_name_item}" fi done } vm_delete_kicksecure() { if [ "${vm_or_vms_already_existing_test_result}" = "true" ]; then log_run notice run_as_target_user vboxmanage unregistervm "${guest_full_vm_name_kicksecure}" --delete else log notice "VM Deletion: Kicksecure VM does not exist, no need to delete, ok." fi } vm_delete_gateway() { if [ "${gateway_exists}" = "1" ]; then log_run notice run_as_target_user vboxmanage unregistervm "${guest_full_vm_name_gateway}" --delete else log notice "VM Deletion: Gateway VM does not exist, no need to delete, ok." fi } vm_delete_workstation() { if [ "${workstation_exists}" = "1" ]; then log_run notice run_as_target_user vboxmanage unregistervm "${guest_full_vm_name_workstation}" --delete else log notice "VM Deletion: Workstation VM does not exist, no need to delete, ok." fi } vm_delete_maybe() { case "${guest}" in whonix) if [ "${destroy_existing_guest}" = "1" ]; then ## '--destroy-existing-guest' option is set. if [ "${import_only}" = "gateway" ]; then log warn "VM Deletion: 'yes' - Deleting previously imported gateway via '--import-only=gateway' option... (If it exists.)" vm_delete_gateway elif [ "${import_only}" = "workstation" ]; then log warn "VM Deletion: 'yes' - Deleting previously imported workstation via '--import-only=workstation' option... (If it exists.)" vm_delete_workstation elif [ "${import_only}" = "both" ]; then log warn "VM Deletion: 'yes' - Deleting previously imported gateway via '--import-only=both' option... (If it exists.)" vm_delete_gateway log warn "VM Deletion: 'yes' - Deleting previously imported workstation via '--import-only=both' option... (If it exists.)" vm_delete_workstation else die 1 "VM Deletion: 'no' - Not deleting any previously imported VMs because '--import-only' option is not set..." fi else ## '--destroy-existing-guest' option not yet. if [ -n "${import_only}" ]; then if [ "${gateway_exists}" = "1" ] && [ "${import_only}" = "gateway" ]; then die 1 "${underline}Existing VM Check Result:${nounderline} '--import-only' option was set to 'gateway', but it already exists and '--destroy-existing-guest' option was not set." elif [ "${workstation_exists}" = "1" ] && [ "${import_only}" = "workstation" ] ; then die 1 "${underline}Existing VM Check Result:${nounderline} '--import-only' option was set to 'workstation', but it already exists and '--destroy-existing-guest' option was not set." fi ## FIXME: Shouldn't import_only=both be handled here? else log info "Existing VM Check: Neither '--destroy-existing-guest' nor '--import-only' option was set, ok." fi fi ;; kicksecure) if [ "${destroy_existing_guest}" = "1" ]; then ## If VMs exists and '--destroy-existing-guest' is set, remove VMs as they are gonna ## be imported later by main. log warn "VM Deletion: 'yes' - Deleting previously imported Virtual Machine(s) via '--destroy-existing-guest' option. (If it exists.)" vm_delete_kicksecure else log notice "VM Deletion: 'no' - Not deleting previously any imported VMs because neither using '--destroy-existing-guest' option." fi ;; esac } ## Check if guest should start or not check_guest_boot() { if [ "${virtualbox_only}" = "1" ]; then return 0 fi if [ "${hypervisor}" = "kvm" ]; then return 0 fi if [ "${can_boot_virtualbox_guest_vms}" = 'false' ]; then log warn "A reboot may be required before the installed virtual machine(s) can be launched." fi ## Refresh variables vm_or_vms_already_existing_test_result, gateway_exists and workstation_exists. check_vm_exists_general "(check after import)" case "${guest}" in whonix) if [ "${gateway_exists}" = "1" ]; then log notice "Available guest: '${guest_full_vm_name_gateway}'" fi if [ "${workstation_exists}" = "1" ]; then log notice "Available guest: '${guest_full_vm_name_workstation}'" fi ;; kicksecure) log notice "Available guest: '${guest_full_vm_name_kicksecure}'" ;; esac if [ "$kernel_module_has_been_loaded" = "false" ]; then ## Kernel modules have not been load. user_warned_potential_startup_issue=true log warn "Debugging information: Secure Boot Status: '$secure_boot_status_pretty' Kernel module has been loaded: '$kernel_module_has_been_loaded' Kernel modules can be loaded: '$kernel_modules_can_be_loaded' (modules can be load. (sysctl kernel.modules_disabled=0 is set.) Only signed kernel modules can be load: '$modules_signed_only' Kernel module vboxdrv signer: '$kernel_module_signer' kernel_module_modprobe_output ('$sucmd modprobe vboxdrv'): '$kernel_module_modprobe_output'" if [ "$kernel_modules_can_be_loaded" = "true" ]; then log error "${underline}VirtualBox Installation Result:${nounderline} ${red}${bold}'FAIL'${nobold}${nocolor} - because kernel modules have not been load yet. - Kernel modules can be load in theory, should be loaded by now, but are not. - This is a VirtualBox installation issue." if [ "${ci}" != "1" ]; then virtualbox_start_failed fi else log error "${underline}VirtualBox Installation Result:${nounderline} ${red}${bold}'REBOOT REQUIRED'${nobold}${nocolor} - because kernel module loading is disabled on your system: - The system administrator might have disabled module loading. - Module loading might have been disabled by Kicksecure package security-misc-shared. - You can re-run this installer again after reboot." if [ "${ci}" != "1" ]; then virtualbox_start_failed fi fi fi log notice "${underline}VirtualBox Installation Result:${nounderline} 'success'" log info "VM Start: Checking if user wants to start Virtual Machine(s) now..." if [ "${qubes_template_detected}" = "true" ]; then log notice "${underline}VirtualBox Startup Check:${nounderline} Not starting VirtualBox because running inside a Qubes Template." ## Avoid needlessly starting VirtualBox inside a Qubes Template. ## That would not be useful, would create unnecessary files inside the Template's home folder and might be confusing. ## What instead might be useful for development purposes is to install VirtualBox inside a Qubes Template for the purpose ## of using a Qubes App Qube for building VirtualBox VM images. ## Run end_installer here, because no VMs were downloaded anyhow if a Qubes Template was detected. end_installer elif [ -n "${target_user}" ]; then log notice "${underline}VirtualBox Startup Check:${nounderline} Not starting VirtualBox because user is installing VMs as another user." ## Starting VMs installed under a different user account is difficult and could cause serious problems. end_installer elif [ "${no_boot}" = "1" ]; then log notice "${underline}VirtualBox Startup Check:${nounderline} User declined to start VirtualBox via '--no-boot' option." log notice "VirtualBox can be started manually." ## Not running end_installer here, because that happens below and we want the following output messages. #end_installer elif [ "${non_interactive}" = "1" ]; then log notice "${underline}VirtualBox Startup Check:${nounderline} Starting VirtualBox automatically via '--non-interactive' option." if [ "${virtualbox_only}" = "1" ]; then start_virtualbox_gui end_installer fi else if [ "${virtualbox_only}" = "1" ]; then log notice "${bold}Question:${nobold} Do you want to start VirtualBox now? [y/n] (default: yes): " else log notice "${bold}Question:${nobold} Do you want to start VirtualBox and the ${guest_pretty} Virtual Machine(s) now? [y/n] (default: yes): " fi printf '%s' "Your answer: " local response read -r response log notice "User replied: '${response}'" case ${response} in ""|[Yy]|[Yy][Ee][Ss]) log notice "${underline}VirtualBox Startup Check:${nounderline} User accepted to start VirtualBox." if [ "${virtualbox_only}" = "1" ]; then start_virtualbox_gui end_installer fi ;; *) log notice "${underline}VirtualBox Startup Check:${nounderline} User declined to start VirtualBox." end_installer ;; esac fi log info "Virtual Machine(s) already exist." if [ "${redownload}" != "1" ]; then log notice "Hint: If you would like to redownload the image, read about '--redownload' option (safe)." fi if [ "${destroy_existing_guest}" != "1" ]; then log notice "Hint: If you would like to delete a VM and re-import, read about '--destroy-existing-guest' option (danger)." fi if [ "${no_boot}" = "1" ]; then ## Skip guest boot log notice "${underline}VM Startup Check:${nounderline} User declined to start Virtual Machine(s) via '--no-boot' option." log notice "Virtual Machine(s) can be started manually." end_installer fi if [ "${non_interactive}" = "1" ]; then ## start guest without interaction log notice "${underline}VM Startup Check:${nounderline} VM start agreed by the user via '--non-interactive' setting." ## Try to start VMs before trying to start VirtualBox Manager, ## because if it fails, it is less confusing to avoid starting VirtualBox Manager. start_guest start_virtualbox_gui end_installer fi case ${response} in ""|[Yy]|[Yy][Ee][Ss]) log notice "${underline}VM Startup Check:${nounderline} User accepted to start Virtual Machine(s)." start_guest start_virtualbox_gui ;; *) log notice "${underline}VM Startup Check:${nounderline} User declined to start Virtual Machine(s)." log notice "The following Virtual Machine(s) can be started manually: '${guest_pretty}'" ;; esac end_installer } import_guest() { if [ "${virtualbox_only}" = "1" ]; then return 0 fi if [ "${no_import}" = "1" ]; then log notice "VM Import: Not importing guest via '--import-only' option." end_installer fi case "${hypervisor}" in virtualbox) import_virtualbox ;; kvm) import_kvm ;; esac } extract_vm_name_from_virtualbox_ova() { local output system_number a b c d has awk || die 1 "${underline}extract_vm_name_from_virtualbox_ova:${nounderline} Package 'gawk' is missing. Please install." output="$1" system_number="$2" a=$(printf '%s' "$output" | grep --max-count=1 --after-context=4 -- "Virtual system $system_number:") b=$(printf '%s' "$a" | grep -- "Suggested VM name") c=$(printf '%s' "$b" | grep -oP -- '"\K[^"]+') ## Take first word only because VirtualBox would suggest name "Whonix-Gateway-Xfce 1" in case, ## a VM "Whonix-Gateway-Xfce" is already registered in VirtualBox. d=$(printf '%s' "$c" | awk '{print $1}') printf '%s' "$d" } ## Import VirtualBox images import_virtualbox() { local vbox_arg_normal vbox_arg_general vm_purge vbox_arg_importonly \ vsys_0_actual vsys_1_actual do_continue output ## Check how many systems to import. ## vsys 0: gateway or Kicksecure ## vsys 1: workstation case "${guest}" in whonix) ## if importing whonix, import 2 virtual systems vbox_arg_normal=( '--vsys' '0' '--eula' 'accept' '--vsys' '1' '--eula' 'accept' ) ;; kicksecure) vbox_arg_normal=( '--vsys' '0' '--eula' 'accept' ) ;; esac vbox_arg_general=( "${vbox_arg_normal[@]}" ) vm_purge="purgeme" case "${import_only}" in workstation) vbox_arg_importonly=( '--vsys' '0' '--eula' 'accept' '--vmname' "${vm_purge}" '--vsys' '1' '--eula' 'accept' ) vbox_arg_general=( "${vbox_arg_importonly[@]}" ) ;; gateway) vbox_arg_importonly=( '--vsys' '0' '--eula' 'accept' '--vsys' '1' '--eula' 'accept' '--vmname' "${vm_purge}" ) vbox_arg_general=( "${vbox_arg_importonly[@]}" ) ;; "") ;; esac if [ "${vm_or_vms_already_existing_test_result}" = "true" ]; then if [ -n "${import_only}" ]; then do_continue=yes log info "VM Import Check Result: Continue via '--import-only' option." else do_continue=no log info "VM Import Check Result: Skipping import because at least 1 VM already exists but '--import-only' option is not set." fi else do_continue=yes log info "VM Import Check Result: Continue because no VM(s) exist yet." fi if [ "$do_continue" = "no" ]; then log notice "VM Import Check Result: Skipping to import, because Virtual Machine(s) have been imported previously and not using '--import-only' option." return 0 fi check_vm_file_exists_virtualbox_general output=$(log_run notice run_as_target_user "${vboxmanage_locale_english[@]}" import "${directory_prefix}/${guest_file}.${guest_file_ext}" "${vbox_arg_normal[@]}" --dry-run) || \ die 105 "${underline}VM Import:${nounderline} Failed to import virtual machines." if [ "${dry_run}" = "1" ]; then log info "VM import: Skip vsys test via '--dry-run'." else case "${guest}" in whonix) vsys_0_actual=$(extract_vm_name_from_virtualbox_ova "$output" 0) || true vsys_1_actual=$(extract_vm_name_from_virtualbox_ova "$output" 1) || true if [ ! "${guest_full_vm_name_gateway}" = "${vsys_0_actual}" ]; then die 105 "${underline}VM Import:${nounderline} BUG: vsys0 is actually '${vsys_0_actual}', not '${guest_full_vm_name_gateway}'." fi if [ ! "${guest_full_vm_name_workstation}" = "$vsys_1_actual" ]; then die 105 "${underline}VM Import:${nounderline} BUG: vsys1 is actually '${vsys_1_actual}', not '${guest_full_vm_name_workstation}'." fi log info "vsys_0_actual: ${vsys_0_actual}" log info "vsys_1_actual: ${vsys_1_actual}" ;; kicksecure) vsys_0_actual=$(extract_vm_name_from_virtualbox_ova "$output" 0) || true if [ ! "${guest_full_vm_name_kicksecure}" = "${vsys_0_actual}" ]; then die 105 "${underline}VM Import:${nounderline} BUG: vsys0 is actually '${vsys_0_actual}', not '${guest_full_vm_name_kicksecure}'." fi log info "vsys_0_actual: ${vsys_0_actual}" ;; esac fi log notice "VM Import: Importing Virtual Machine(s)..." ## import VirtualBox image log_run notice run_as_target_user "${vboxmanage_locale_english[@]}" import "${directory_prefix}/${guest_file}.${guest_file_ext}" "${vbox_arg_general[@]}" || \ die 105 "${underline}VM Import:${nounderline} Failed to import virtual machines." ## VirtualBox does not accept any command to import a single virtual system ## out from an ova with multiple ones. ## https://forums.virtualbox.org/viewtopic.php?f=1&t=107965 if [ -n "${import_only}" ] && [ "${import_only}" != "both" ]; then log_run notice run_as_target_user "${vboxmanage_locale_english[@]}" unregistervm "${vm_purge}" --delete || die 1 "${underline}VM Import:${nounderline} Failed to remove extraneous VM '${vm_purge}'." fi log notice "VM Import: 'success'" log notice "You can now open the VirtualBox application and start using: '${guest_pretty}'" } ## Import KVM images import_kvm() { ## placeholder log notice "KVM import feature does not exist. Ending run." exit 0 } start_guest() { if [ "${virtualbox_only}" = "1" ]; then return 0 fi case "${hypervisor}" in virtualbox) start_virtualbox_vm ;; kvm) start_kvm_vm ;; esac } ## Start the hypervisor with the desired guest start_virtualbox_vm() { log notice "Virtual Machine Startup: Starting Virtual Machine(s)..." ## 'virtualbox' is called after 'vboxmanage' because it hangs till the app ## is closed, while 'vboxmanage startvm' exits after starting the VMs. case "${guest}" in whonix) if [ "${gateway_exists}" = "1" ]; then log_run notice vboxmanage startvm "${guest_full_vm_name_gateway}" || virtualbox_start_failed fi if [ "${workstation_exists}" = "1" ]; then log_run notice vboxmanage startvm "${guest_full_vm_name_workstation}" || virtualbox_start_failed fi ;; kicksecure) log_run notice vboxmanage startvm "${guest_full_vm_name_kicksecure}" || virtualbox_start_failed ;; esac log notice "${underline}Virtual Machine Startup Result:${nounderline} 'success'" } start_virtualbox_gui() { if pgrep -- 'VirtualBox$' >/dev/null ; then log notice "VirtualBox Manager Startup Result: Not starting duplicate VirtualBox GUI because already running." return 0 fi run_background=1 log_run notice virtualbox if [ "${dry_run}" != "1" ]; then log notice "VirtualBox Manager Startup Result: Launched VirtualBox GUI into the background. (pid: '$background_pid')" fi run_background="" } virtualbox_start_failed() { local likely_cause likely_cause="" if [ "$user_warned_potential_startup_issue" = "true" ]; then likely_cause="- This is likely happening due to the warnings that have been reported above." fi die 106 "${underline}Virtual Machine Startup Result:${nounderline} ${red}${bold}FAIL${nobold}${nocolor} $likely_cause - The installer succeeded with download and import, but - failed to start the virtual machines (VMs). - The root cause for this issue is likely not the installer. - This issue would likely also happen if the user tried to manually start the VMs. - Resolving these issues (as per documentation hyperlinks above) would likely resolve this issue. https://www.kicksecure.com/wiki/VirtualBox/Troubleshooting" } ## Detect if repository is configured in the one-line format sources list. get_pattern_sources_debian() { local file pattern file="${1}" pattern="${2}" grep -v "^\s*#" -- "${file}" | grep -E -- "${pattern}" >/dev/null || return 1 } ## Detect if repository is configured in the deb822 format sources. get_pattern_sources_deb822_debian() { local file types_pattern uris_pattern suites_pattern components_pattern \ current_types current_uris current_suites current_components match_hit \ line file="${1:-}" types_pattern="${2:-}" uris_pattern="${3:-}" suites_pattern="${4:-}" components_pattern="${5:-}" current_types="" current_uris="" current_suites="" current_components="" while read -r line; do if [[ "${line}" =~ ^\s*# ]]; then continue fi line="$(sed 's/^\s*//; s/\s*$//;' <<< "${line}")" if [ -z "${line}" ]; then match_hit='yes' if [ -n "${types_pattern}" ] \ && ! [[ "${current_types}" =~ ${types_pattern} ]]; then match_hit='no' elif [ -n "${uris_pattern}" ] \ && ! [[ "${current_uris}" =~ ${uris_pattern} ]]; then match_hit='no' elif [ -n "${suites_pattern}" ] \ && ! [[ "${current_suites}" =~ ${suites_pattern} ]]; then match_hit='no' elif [ -n "${current_components}" ] \ && ! [[ "${current_components}" =~ ${components_pattern} ]]; then match_hit='no' fi if [ "${match_hit}" = 'yes' ]; then return 0 fi current_types="" current_uris="" current_suites="" current_components="" fi if [[ "${line}" =~ ^Types: ]]; then current_types="$(cut -d' ' -f2-)" elif [[ "${line}" =~ ^URIs: ]]; then current_uris="$(cut -d' ' -f2-)" elif [[ "${line}" =~ ^Suites: ]]; then current_suites="$(cut -d' ' -f2-)" elif [[ "${line}" =~ ^Components: ]]; then current_components="$(cut -d' ' -f2-)" fi done < "${file}" return 1 } write_sources_debian() { local url file signed_by_key url="${1}" file="${2}" signed_by_key="${3}" test -e "${signed_by_key}" || die 1 "${underline}Sources List Writer:${nounderline} signed-by key missing: '${signed_by_key}'" printf '%s\n' "${url}" | root_cmd tee -- "${file}" || die 1 "${underline}Sources List Writer:${nounderline} Failed to write to file: '${file}'" } ## https://stackoverflow.com/a/54239534 ## "apt list --installed $pkg" does not fail if package is not installed. check_installed_debian() { local status status="$(dpkg-query --show --showformat='${db:Status-Status}' "$1" 2>&1)" # shellcheck disable=SC2181 if [ "$?" != 0 ] || [ "$status" != "installed" ]; then return 1 fi return 0 } disable_kvm_virt_at_load() { local module_list_str kvm_variant kvm_variant_unload_failed \ kvm_core_unload_failed kvm_reload_failed module_list_str='' kvm_variant='' kvm_variant_unload_failed='false' kvm_core_unload_failed='false' kvm_reload_failed='false' ## KVM's 'enable_virt_at_load' feature renders VirtualBox unusable on systems ## using Linux 6.12 or higher. ## ## VirtualBox can be fixed by setting 'enable_virt_at_load=0' in the KVM ## driver. This does not break KVM, it simply prevents it from conflicting ## with VirtualBox (at the expense of making the first KVM VM launch and the ## last KVM VM shutdown take slightly longer). This is safe to set even on ## kernels older than 6.12, it will act as a no-op there. ## ## Using modprobe.d for setting this parameter as recommended by ## https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1082157#29 ## ## Using same method documented at ## https://www.kicksecure.com/wiki/Template:VirtualBox_Host_Software_Installation#Ubuntu log notice "Disabling KVM virt-at-load feature to enable VirtualBox to function. This will not break the ability to use KVM virtual machines..." printf '%s\n' "\ ## Prevent VirtualBox and KVM from conflicting. This does not break KVM. options kvm enable_virt_at_load=0" \ | root_cmd tee /etc/modprobe.d/disable-kvm-virt-at-load.conf >/dev/null log notice "KVM virt-at-load feature disabled." ## Hot-reloading KVM kernel module if possible. This is necessary for VM ## autostart to work later. log notice "Attempting hot reload of KVM kernel modules to allow VirtualBox VM auto-start later..." module_list_str="$(lsmod | cut -d' ' -f1)" if grep '^kvm_intel$' <<< "${module_list_str}" ; then kvm_variant='intel' elif grep '^kvm_amd$' <<< "${module_list_str}" ; then kvm_variant='amd' elif grep '^kvm$' <<< "${module_list_str}" ; then ## KVM hot-reload will almost certainly fail if we can't detect the KVM ## variant, as the core KVM module requires a helper module for the ## specific CPU architecture. Warn, but try to reload anyway. log warn "Could not detect KVM variant. KVM hot reload will probably fail." kvm_variant='unknown' fi if [ "${kvm_variant}" != 'unknown' ]; then root_cmd modprobe -r "kvm_${kvm_variant}" || kvm_variant_unload_failed='true' fi root_cmd modprobe -r 'kvm' || kvm_core_unload_failed='true' if [ "${kvm_variant_unload_failed}" = 'true' ]; then log warn "Failed to unload KVM kernel modules. A reboot will possibly be required before VirtualBox VMs will function properly." can_boot_virtualbox_guest_vms='false' elif [ "${kvm_core_unload_failed}" = 'true' ]; then ## Try to put the variant kernel module back. It is not fatal if this ## fails. root_cmd modprobe "kvm_${kvm_variant}" || true log warn "Failed to unload KVM kernel modules. A reboot will possibly be required before VirtualBox VMs will function properly." can_boot_virtualbox_guest_vms='false' fi if [ "${kvm_variant}" != 'unknown' ]; then root_cmd modprobe "kvm_${kvm_variant}" || kvm_reload_failed='true' else root_cmd modprobe 'kvm' || kvm_reload_failed='true' fi if [ "${kvm_reload_failed}" = 'true' ]; then log warn "Failed to reload KVM kernel modules. A reboot will possibly be required before either VirtualBox or KVM VMs will function properly." can_boot_virtualbox_guest_vms='false' return 0 fi log notice "Successfully reloaded KVM kernel modules. VirtualBox VMs should be functional." } install_package_fedora_common() { pkg_mngr="dnf" pkg_mngr_install=( "${pkg_mngr}" 'install' '--assumeyes' '--setopt=install_weak_deps=False' ) ## Fedora lacks an equivalent to Debian's '--error-on=any'. #pkg_mngr_update=( "${pkg_mngr}" 'update' '--assumeyes' ) ## 'dnf makecache' does exit non-zero if a repository is unavailable. ## --assumeyes good or bad idea? For now not needed. So not using. #pkg_mngr_update( "${pkg_mngr}" 'makecache' ) pkg_mngr_update=( "${pkg_mngr}" '--assumeyes' 'update' ) pkg_mngr_check_installed=( "${pkg_mngr}" 'list' '--installed' ) pkg_mngr_upgrade_check=( '/bin/true' 'dnf does not seem to support an alternative to apt-get full-upgrade --simulate, skipping.' ) pkg_mngr_upgrade_install=( "${pkg_mngr}" 'upgrade' ) pkg_mngr_upgradable_check() { /bin/true 'dnf does not seem to support an alternative to apt-get full-upgrade --simulate, skipping.' return 0 } update_sources #check_upgrades_simulation if [ "${virtualbox_only}" = "1" ]; then return 0 fi install_pkg torsocks dnf-plugins-core procps-ng ## Conflicting packages requires '--allowerasing'. if "${pkg_mngr_check_installed[@]}" lsb_release >/dev/null 2>&1 || "${pkg_mngr_check_installed[@]}" redhat-lsb-core >/dev/null 2>&1 then true else install_pkg lsb_release >/dev/null 2>&1 fi ## Requires lsb_release / lsb-release. get_specifics install_signify signify } install_package_debian_common() { local dpkg_audit_output sources_list_d_file if [[ -v APTGETOPT_SERIALIZED ]]; then # shellcheck disable=SC2128 mapfile -t APTGETOPT <<< "$APTGETOPT_SERIALIZED" else APTGETOPT_SERIALIZED=() if [[ -v APTGETOPT ]]; then APTGETOPT=() fi fi # shellcheck disable=SC2145 log info "APTGETOPT_SERIALIZED: '${APTGETOPT_SERIALIZED[@]}'" # shellcheck disable=SC2145 log info "APTGETOPT: '${APTGETOPT[@]}'" pkg_mngr="apt-get" pkg_mngr_install=( "${pkg_mngr}" '--yes' '--no-install-recommends' "${APTGETOPT[@]}" 'install' ) pkg_mngr_update=( "${pkg_mngr}" '--yes' '--error-on=any' "${APTGETOPT[@]}" 'update' ) pkg_mngr_check_installed=( "check_installed_debian" ) pkg_mngr_upgrade_check=( "${pkg_mngr}" '--simulate' "${APTGETOPT[@]}" 'full-upgrade' ) pkg_mngr_upgrade_install=( "${pkg_mngr}" "${APTGETOPT[@]}" 'full-upgrade' ) # shellcheck disable=SC2145 log info "pkg_mngr_update: '${pkg_mngr_update[@]}'" pkg_mngr_upgradable_check() { local upgrade_check_output upgrade_check_grep_output number_upgradable ## Not using 'apt list --upgradable', because it is not the same as ## 'sudo apt-get dist-upgrade --target-release=trixie-fasttrack'. ## It misses out on packages from "trixie-fasttrack". upgrade_check_output="$(root_cmd "${pkg_mngr_upgrade_check[@]}" "$@" 2>&1)" || true ## Counting both 'Inst' and 'Conf' is not an accurate counting. Will double count. But will also mean that ## APT has things to do. upgrade_check_grep_output="$(printf '%s' "${upgrade_check_output}" | grep -e '^Inst' -e '^Conf')" || true number_upgradable=0 if [ -n "$upgrade_check_grep_output" ]; then number_upgradable="$(printf '%s' "${upgrade_check_grep_output}" | wc -l)" fi if [ "$number_upgradable" -gt "0" ]; then return 1 fi return 0 } dpkg_audit_output="$(dpkg --audit 2>&1)" || true if [ -n "${dpkg_audit_output}" ]; then die 1 "${underline}DPKG Audit Test Result:${nounderline} ${red}${bold}'FAIL'${nobold}${nocolor} - What happened in simple terms? This installer has performed a check to ensure it's sane to proceed, which has failed. - What exactly happened? The command 'dpkg --audit' generated output, signaling a system issue. - What was the expected behavior? The 'dpkg --audit' command should execute without producing any output. - Is this installer to blame for the issue? It's unlikely that this installer is the root cause of the issue. - So, what might have caused it? The issue likely originates from pre-existing DPKG / APT package management system issues on your machine. - What steps should you take now? Start by running the following command yourself: dpkg --audit - What should you do after that? Analyze the output of the command to identify and address the underlying problem. Once resolved, you can re-run this installer. - Do you have any suggestions? The following link might offer helpful insights: https://www.kicksecure.com/wiki/Operating_System_Software_and_Updates#Broken_APT If that doesn't resolve the issue, consider reaching out to your operating system's support team for assistance." fi ## Dumping all sources.list files could have privacy implications. ## Hence only doing it for '--dev'. if [ "${dev}" = "1" ]; then for sources_list_d_file in /etc/apt/sources.list /etc/apt/sources.sources /etc/apt/sources.list.d/*; do if [ -f "$sources_list_d_file" ]; then cat -- "$sources_list_d_file" fi done fi update_sources check_upgrades_simulation if [ "$debian_derivative_detected" = "1" ]; then install_pkg lsb-release procps fi ## Requires lsb_release / lsb-release. get_specifics if [ "${virtualbox_only}" = "1" ]; then return 0 fi true "debian_testing_or_unstable_detected: $debian_testing_or_unstable_detected" true "kali_derivative_detected: $kali_derivative_detected" ## https://bugs.debian.org/1066313 #if [ "${debian_testing_or_unstable_detected}" != "1" ] && [ "${kali_derivative_detected}" != "1" ]; then #true "INFO: either debian_testing_or_unstable_detected or kali_derivative_detected = 1" #install_pkg torsocks #fi install_signify signify-openbsd torsocks } add_user_to_vbox_group() { local id_of_user id_of_user="$(id --name --user)" || die 1 "${underline}Linux user ID check:${nounderline} Failed to run: 'id --name --user'" if id -nG "${id_of_user}" | grep -w -- "${virtualbox_linux_user_group}\$" >/dev/null; then log info "Linux Group Configuration: Account '${id_of_user}' is already a member of the Linux group 'vboxusers'." return 0 fi if [ "${debian_derivative_detected}" = "1" ]; then root_cmd adduser "${id_of_user}" "${virtualbox_linux_user_group}" || \ die 1 "${underline}Linux Group Configuration: adduser to Linux user group virtualbox:${nounderline} Failed to add user '$id_of_user' to group '${virtualbox_linux_user_group}'." else root_cmd usermod --append --groups "${virtualbox_linux_user_group}" "${id_of_user}" || \ die 1 "${underline}Linux Group Configuration: adduser to Linux user group virtualbox:${nounderline} Failed to add user '$id_of_user' to group '${virtualbox_linux_user_group}'." fi } ## End installation of VirtualBox on Fedora and derived systems. install_virtualbox_fedora_common_end() { if ! has vboxmanage ; then if [ "${dry_run}" = "1" ]; then log error "Failed to locate 'vboxmanage' program. Ignoring via '--dry-run' option." else die 1 "${underline}vboxmanage test:${nounderline} Failed to locate 'vboxmanage' program." fi fi add_user_to_vbox_group disable_kvm_virt_at_load } ## End installation of VirtualBox on Debian and derived systems. install_virtualbox_debian_common_end() { if ! has vboxmanage ; then if [ "${dry_run}" = "1" ]; then log error "Failed to locate 'vboxmanage' program. - Ignoring via '--dry-run' option." else die 1 "${underline}vboxmanage test:${nounderline} Failed to locate 'vboxmanage' program." fi fi add_user_to_vbox_group disable_kvm_virt_at_load } kernel_modules_check() { if ! [ "${hypervisor}" = "virtualbox" ]; then return 0 fi if [ ! -e /dev/vboxdrv ]; then kernel_module_has_been_loaded=false user_warned_potential_startup_issue=true log warn "${underline}VirtualBox Kernel Module Loaded Test Result${nounderline}: 'no' - file '/dev/vboxdrv' does not exist. This probably means that the 'vboxdrv' kernel module has not been loaded yet." else kernel_module_has_been_loaded=true log info "VirtualBox Kernel Module Loaded Test Result: 'yes' - file '/dev/vboxdrv' exists." fi ## Debugging. log info "Command executing: $ $sucmd -- modinfo --field signer vboxdrv" if kernel_module_signer=$(root_cmd_loglevel="echo" root_cmd modinfo --field signer vboxdrv 2>&1) ; then log info "VirtualBox Kernel Module Signer: '$kernel_module_signer'" else user_warned_potential_startup_issue=true log warn "${underline}VirtualBox Kernel Module Signer${nounderline}: '$kernel_module_signer'" fi ## Debugging. ## modprobe should be automatic but if it fails, figure out why. log notice "Command executing: $ $sucmd -- modprobe vboxdrv" if kernel_module_modprobe_output=$(root_cmd_loglevel="echo" root_cmd modprobe vboxdrv 2>&1) ; then log info "VirtualBox Kernel Module modprobe output: '$kernel_module_modprobe_output'" else user_warned_potential_startup_issue=true log warn "${underline}VirtualBox Kernel modprobe output${nounderline}: '$kernel_module_modprobe_output'" fi } install_repositories_for_virtualbox_on_debian() { local \ oracle_clearnet oracle_clearnet_uri_base \ oracle_types_debsource \ oracle_components_debsource oracle_suites_debsource unstable_clearnet \ unstable_onion unstable_clearnet_uri_base unstable_onion_uri_base \ unstable_types_debsource unstable_signedby_debsource \ unstable_components_debsource unstable_suites_debsource \ backports_clearnet backports_onion backports_clearnet_uri_base \ backports_onion_uri_base backports_types_debsource \ backports_components_debsource \ backports_suites_debsource fasttrack_clearnet fasttrack_onion \ fasttrack_clearnet_uri_base fasttrack_onion_uri_base \ fasttrack_types_debsource \ fasttrack_components_debsource fasttrack_components_debsource \ fasttrack_suites_debsource fasttrack_components_debsource_regex \ fasttrack_dir_regex fasttrack_suffix_debsource_regex \ fasttrack_backports_staging_suites_debsource \ fasttrack_backports_staging_suffix_debsource_regex kicksecure_clearnet \ kicksecure_onion kicksecure_clearnet_uri_base kicksecure_onion_uri_base \ kicksecure_types_debsource \ kicksecure_components_debsource kicksecure_suites_debsource \ kali_clearnet kali_clearnet_uri_base kali_types_debsource \ kali_components_debsource kali_suites_debsource \ apt_torified apt_onion oracle_uri_debsource kicksecure_uri_debsource \ unstable_uri_debsource fasttrack_uri_debsource backports_uri_debsource \ kali_uri_debsource sources_list_file sources_deb822_file if [ "${dev}" = "1" ]; then distro_codename_kicksecure_use="${distro_codename_common_use}-developers" else distro_codename_kicksecure_use="${distro_codename_common_use}" fi oracle_found="" oracle_clearnet="download.virtualbox.org" #oracle_onion="" ## non-existing oracle_clearnet_uri_base="${oracle_clearnet}/virtualbox/debian/" #oracle_onion_uri_base="" ## non-existing oracle_file_debsource="/etc/apt/sources.list.d/oracle.list" oracle_file_deb822source="/etc/apt/sources.list.d/oracle.sources" oracle_types_debsource="deb" oracle_signedby_debsource="/usr/share/keyrings/oracle-virtualbox-2016.asc" oracle_components_debsource="contrib" ## Oracle does not have a Kali dist section, use Debian stable. if [ "${ubuntu_derivative_detected}" = "1" ]; then oracle_suites_debsource="${distro_codename_ubuntu}" elif [ "${debian_derivative_detected}" = "1" ]; then oracle_suites_debsource="${distro_codename_debian}" else oracle_suites_debsource="${distro_codename_common_use}" fi unstable_found="" unstable_clearnet="deb.debian.org" unstable_onion="2s4yqjx5ul6okpp3f2gaunr2syex5jgbfpfvhxxbbjwnrsvbk5v3qbid.onion" unstable_clearnet_uri_base="${unstable_clearnet}/debian/" unstable_onion_uri_base="${unstable_onion}/debian/" unstable_file_debsource="/etc/apt/sources.list.d/unstable.list" unstable_file_deb822source="/etc/apt/sources.list.d/unstable.sources" unstable_types_debsource="deb" ## CI /etc/apt/sources.list.d/debian.sources uses: ## Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg ## So we have to use signed-by here too, otherwise we get an apt-get error: ## E: Conflicting values set for option Signed-By regarding source ## Also needed to avoid an apt notice when using deb822 format sources. if test -e "/usr/share/keyrings/debian-archive-keyring.pgp"; then ## new unstable_signedby_debsource="/usr/share/keyrings/debian-archive-keyring.pgp" elif test -e "/usr/share/keyrings/debian-archive-keyring.gpg"; then ## old unstable_signedby_debsource="/usr/share/keyrings/debian-archive-keyring.gpg" else unstable_signedby_debsource="/usr/share/keyrings/unknown-please-report-this-bug" fi unstable_components_debsource="main contrib non-free" unstable_suites_debsource="unstable" backports_found="" backports_clearnet="deb.debian.org" backports_onion="2s4yqjx5ul6okpp3f2gaunr2syex5jgbfpfvhxxbbjwnrsvbk5v3qbid.onion" backports_clearnet_uri_base="${backports_clearnet}/debian/" backports_onion_uri_base="${backports_onion}/debian/" backports_file_debsource="/etc/apt/sources.list.d/backports.list" backports_file_deb822source="/etc/apt/sources.list.d/backports.sources" backports_types_debsource="deb" backports_signedby_debsource="/usr/share/keyrings/debian-archive-keyring.gpg" backports_components_debsource="main contrib non-free" backports_suites_debsource="${distro_codename_common_use}-backports" fasttrack_found="" fasttrack_clearnet="fasttrack.debian.net" fasttrack_onion="5phjdr2nmprmhdhw4fdqfxvpvt363jyoeppewju2oqllec7ymnolieyd.onion" fasttrack_clearnet_uri_base="${fasttrack_clearnet}/debian-fasttrack/" fasttrack_onion_uri_base="${fasttrack_onion}/debian-fasttrack/" fasttrack_file_debsource="/etc/apt/sources.list.d/fasttrack.list" fasttrack_file_deb822source="/etc/apt/sources.list.d/fasttrack.sources" fasttrack_types_debsource="deb" case "${distro_codename_real}" in bullseye|bookworm) fasttrack_signedby_debsource="/etc/apt/trusted.gpg.d/fasttrack-archive-keyring.gpg" ;; trixie|forky|sid) fasttrack_signedby_debsource="/usr/share/keyrings/fasttrack-archive-keyring.gpg" ;; *) fasttrack_signedby_debsource="/usr/share/keyrings/unknown-please-report-this-bug" ;; esac fasttrack_components_debsource="main contrib non-free" fasttrack_suites_debsource="${distro_codename_common_use}-fasttrack" fasttrack_components_debsource_regex="main\s+contrib\s+non-free" fasttrack_dir_regex="/debian(-fasttrack)?/?\s" fasttrack_suffix_debsource_regex="${fasttrack_dir_regex}\s*${fasttrack_suites_debsource}\s*${fasttrack_components_debsource_regex}" fasttrack_backports_staging_found="" ## same clearnet as fasttrack ## same onion as fasttrack ## same clearnet_uri_base as fasttrack ## same onion_uri_base as fasttrack fasttrack_backports_staging_file_debsource="/etc/apt/sources.list.d/fasttrack-backports-staging.list" fasttrack_backports_staging_file_deb822source="/etc/apt/sources.list.d/fasttrack-backports-staging.sources" ## same types as fasttrack ## same signedby as fasttrack ## same components as fasttrack fasttrack_backports_staging_suites_debsource="${distro_codename_common_use}-backports-staging" ## same components_debsource_regex as fasttrack ## same dir_regex as fasttrack fasttrack_backports_staging_suffix_debsource_regex="${fasttrack_dir_regex}\s*${fasttrack_backports_staging_suites_debsource}\s+${fasttrack_components_debsource_regex}" kicksecure_found="" kicksecure_clearnet="deb.kicksecure.com" kicksecure_onion="deb.w5j6stm77zs6652pgsij4awcjeel3eco7kvipheu6mtr623eyyehj4yd.onion" kicksecure_clearnet_uri_base="${kicksecure_clearnet}/" kicksecure_onion_uri_base="${kicksecure_onion}/" kicksecure_file_debsource="/etc/apt/sources.list.d/derivative.list" kicksecure_file_deb822source="/etc/apt/sources.list.d/derivative.sources" kicksecure_types_debsource="deb" kicksecure_signedby_debsource="/usr/share/keyrings/derivative.asc" kicksecure_components_debsource="main contrib non-free" kicksecure_suites_debsource="${distro_codename_kicksecure_use}" kali_found="" kali_clearnet="http.kali.org" #kali_onion="" ## non-existing kali_clearnet_uri_base="${kali_clearnet}/" #kali_onion_uri_base="" ## non-existing kali_file_debsource="/etc/apt/sources.list.d/kali.list" kali_file_deb822source="/etc/apt/sources.list.d/kali.sources" kali_types_debsource="deb" ## TODO: Test. kali_signedby_debsource="/usr/share/keyrings/kali-archive-keyring.gpg" kali_components_debsource="main contrib non-free non-free-firmware" kali_suites_debsource="${distro_codename_common_use}" apt_torified="" apt_onion="" ## TODO: ## - handle case if using Debian mirrors ## - unit tests for sources_deb822_file in /etc/apt/sources.list.d/*.sources; do [ -f "${sources_deb822_file}" ] || continue if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "${oracle_clearnet}" "" "" then oracle_found=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "${kicksecure_clearnet}|${kicksecure_onion}" "" "" then kicksecure_found=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "(${unstable_clearnet}|${unstable_onion})/debian.*" "(unstable|sid)" "" then unstable_found=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "(${unstable_clearnet}|${unstable_onion})/debian.*" "(unstable|sid)" "contrib" then unstable_found_with_contrib=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "(${backports_clearnet}|${backports_onion})/debian/?" "${distro_codename_common_use}-backports" "" then backports_found=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "(${fasttrack_clearnet}|${fasttrack_onion})${fasttrack_dir_regex}" "${fasttrack_suites_debsource}" "${fasttrack_components_debsource_regex}" then fasttrack_found=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "(${fasttrack_clearnet}|${fasttrack_onion})${fasttrack_dir_regex}" "${fasttrack_backports_staging_suites_debsource}" "${fasttrack_components_debsource_regex}" then fasttrack_backports_staging_found=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "${kali_clearnet}" "" "" then kali_found=1 fi ## Kali VirtualBox is in 'contrib'. if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "${kali_clearnet}/kali" "" "contrib" then kali_found_with_contrib=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "tor://|tor\+" "" ""; then apt_torified=1 fi if get_pattern_sources_deb822_debian "${sources_deb822_file}" "" "\.onion" "" ""; then apt_onion=1 fi done for sources_list_file in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do [ -f "${sources_list_file}" ] || continue if get_pattern_sources_debian "${sources_list_file}" "${oracle_clearnet}" then oracle_found=1 fi if get_pattern_sources_debian "${sources_list_file}" "${kicksecure_clearnet}|${kicksecure_onion}" then kicksecure_found=1 fi if get_pattern_sources_debian "${sources_list_file}" "(${unstable_clearnet}|${unstable_onion})/debian.* (unstable|sid)" then unstable_found=1 fi ## Need to check if not only unstable repository or sid codename is enabled but also check if the 'contrib' suite is enabled, ## because VirtualBox is only available from 'contrib'. if get_pattern_sources_debian "${sources_list_file}" "(${unstable_clearnet}|${unstable_onion})/debian.*\s(unstable|sid).*\scontrib" then unstable_found_with_contrib=1 fi if get_pattern_sources_debian "${sources_list_file}" "(${backports_clearnet}|${backports_onion})/debian/?\s*${distro_codename_common_use}-backports" then backports_found=1 fi if get_pattern_sources_debian "${sources_list_file}" "(${fasttrack_clearnet}|${fasttrack_onion})${fasttrack_suffix_debsource_regex}" then fasttrack_found=1 fi if get_pattern_sources_debian "${sources_list_file}" "(${fasttrack_clearnet}|${fasttrack_onion})${fasttrack_backports_staging_suffix_debsource_regex}" then fasttrack_backports_staging_found=1 fi if get_pattern_sources_debian "${sources_list_file}" "${kali_clearnet}" then kali_found=1 fi ## Kali VirtualBox is in 'contrib'. if get_pattern_sources_debian "${sources_list_file}" "${kali_clearnet}/kali.*\scontrib" then kali_found_with_contrib=1 fi if get_pattern_sources_debian "${sources_list_file}" "tor://|tor\+"; then apt_torified=1 fi if get_pattern_sources_debian "${sources_list_file}" "\.onion"; then apt_onion=1 fi done if [ "${apt_torified}" = "1" ]; then ## If apt-transport-tor is not installed, we shouldn't, because we got ## a false positive that updates should be torified. This can happen if ## user configured the sources list to be torified but hasn't installed ## apt-transport-tor. It is better to abort, the user should configure ## tor by other means to ensure a working connection (bridges, proxy etc). log info "APT is supposed to be torified, checking if package apt-transport-tor is installed." test_pkg apt-transport-tor || die 1 "${underline}Torified APT Test Result:${nounderline} APT is supposed to be torified, but package apt-transport-tor is not installed. Please install." fi ## If user has onion repositories configured, prefer it. if [ "${apt_onion}" = '1' ]; then connection_type_debsource="onion" if [ "${oracle_repo}" = "1" ] || [ "${kali_derivative_detected}" = "1" ]; then if [ "${oracle_repo}" = "1" ]; then log warn "Oracle doesn't provide onion repositories." fi if [ "${kali_derivative_detected}" = "1" ]; then log warn "Kali doesn't provide onion repositories." fi if [ "${apt_torified}" = '1' ]; then if [ "${oracle_repo}" = "1" ]; then log warn "Fallback Oracle repository to torified clearnet." fi if [ "${kali_derivative_detected}" = "1" ]; then log warn "Fallback Kali repository to torified clearnet." fi kali_uri_debsource="tor+https://${kali_clearnet_uri_base}" oracle_uri_debsource="tor+https://${oracle_clearnet_uri_base}" else if [ "${oracle_repo}" = "1" ]; then log warn "Fallback Oracle repository to clearnet." fi if [ "${kali_derivative_detected}" = "1" ]; then log warn "Fallback Kali repository to clearnet." fi kali_uri_debsource="https://${kali_clearnet_uri_base}" oracle_uri_debsource="https://${oracle_clearnet_uri_base}" fi else if [ "${apt_torified}" = '1' ]; then kali_uri_debsource="tor+https://${kali_clearnet_uri_base}" oracle_uri_debsource="tor+https://${oracle_clearnet_uri_base}" else kali_uri_debsource="https://${kali_clearnet_uri_base}" oracle_uri_debsource="https://${oracle_clearnet_uri_base}" fi fi fasttrack_uri_debsource="http://${fasttrack_onion_uri_base}" kicksecure_uri_debsource="http://${kicksecure_onion_uri_base}" unstable_uri_debsource="http://${unstable_onion_uri_base}" backports_uri_debsource="http://${backports_onion_uri_base}" ## If user has torified repositories configured, prefer it. elif [ "${apt_torified}" = '1' ]; then connection_type_debsource="torified clearnet" oracle_uri_debsource="tor+https://${oracle_clearnet_uri_base}" kicksecure_uri_debsource="tor+https://${kicksecure_clearnet_uri_base}" unstable_uri_debsource="tor+https://${unstable_clearnet_uri_base}" fasttrack_uri_debsource="tor+https://${fasttrack_clearnet_uri_base}" backports_uri_debsource="tor+https://${backports_clearnet_uri_base}" kali_uri_debsource="tor+https://${kali_clearnet_uri_base}" ## If user doesn't have torified repositories, use clearnet one. else connection_type_debsource="clearnet" oracle_uri_debsource="https://${oracle_clearnet_uri_base}" kicksecure_uri_debsource="https://${kicksecure_clearnet_uri_base}" unstable_uri_debsource="https://${unstable_clearnet_uri_base}" fasttrack_uri_debsource="https://${fasttrack_clearnet_uri_base}" backports_uri_debsource="https://${backports_clearnet_uri_base}" kali_uri_debsource="https://${kali_clearnet_uri_base}" fi kicksecure_oneline_source="${kicksecure_types_debsource} [signed-by=${kicksecure_signedby_debsource}] ${kicksecure_uri_debsource} ${kicksecure_suites_debsource} ${kicksecure_components_debsource}" unstable_oneline_source="${unstable_types_debsource} [signed-by=${unstable_signedby_debsource}] ${unstable_uri_debsource} ${unstable_suites_debsource} ${unstable_components_debsource}" fasttrack_oneline_source="${fasttrack_types_debsource} [signed-by=${fasttrack_signedby_debsource}] ${fasttrack_uri_debsource} ${fasttrack_suites_debsource} ${fasttrack_components_debsource}" fasttrack_backports_staging_oneline_source="${fasttrack_types_debsource} [signed-by=${fasttrack_signedby_debsource}] ${fasttrack_uri_debsource} ${fasttrack_backports_staging_suites_debsource} ${fasttrack_components_debsource}" backports_oneline_source="${backports_types_debsource} [signed-by=${backports_signedby_debsource}] ${backports_uri_debsource} ${backports_suites_debsource} ${backports_components_debsource}" kali_oneline_source="${kali_types_debsource} [signed-by=${kali_signedby_debsource}] ${kali_uri_debsource} ${kali_suites_debsource} ${kali_components_debsource}" kicksecure_deb822_source="\ Types: ${kicksecure_types_debsource} URIs: ${kicksecure_uri_debsource} Suites: ${kicksecure_suites_debsource} Components: ${kicksecure_components_debsource} Signed-By: ${kicksecure_signedby_debsource}" unstable_deb822_source="\ Types: ${unstable_types_debsource} URIs: ${unstable_uri_debsource} Suites: ${unstable_suites_debsource} Components: ${unstable_components_debsource} Signed-By: ${unstable_signedby_debsource}" fasttrack_deb822_source="\ Types: ${fasttrack_types_debsource} URIs: ${fasttrack_uri_debsource} Suites: ${fasttrack_suites_debsource} Components: ${fasttrack_components_debsource} Signed-By: ${fasttrack_signedby_debsource}" fasttrack_backports_staging_deb822_source="\ Types: ${fasttrack_types_debsource} URIs: ${fasttrack_uri_debsource} Suites: ${fasttrack_backports_staging_suites_debsource} Components: ${fasttrack_components_debsource} Signed-By: ${fasttrack_signedby_debsource}" backports_deb822_source="\ Types: ${backports_types_debsource} URIs: ${backports_uri_debsource} Suites: ${backports_suites_debsource} Components: ${backports_components_debsource} Signed-By: ${backports_signedby_debsource}" kali_deb822_source="\ Types: ${kali_types_debsource} URIs: ${kali_uri_debsource} Suites: ${kali_suites_debsource} Components: ${kali_components_debsource} Signed-By: ${kali_signedby_debsource}" oracle_oneline_source="${oracle_types_debsource} [signed-by=${oracle_signedby_debsource}] ${oracle_uri_debsource} ${oracle_suites_debsource} ${oracle_components_debsource}" oracle_deb822_source="\ Types: ${oracle_types_debsource} URIs: ${oracle_uri_debsource} Suites: ${oracle_suites_debsource} Components: ${oracle_components_debsource} Signed-By: ${oracle_signedby_debsource}" if [ "${oracle_repo}" = "1" ]; then install_oracle_repository_debian return 0 fi case "${distro_codename_real}" in bullseye|bookworm) install_backports_and_fasttrack_repository_debian return 0 ;; trixie) ## Please upload VirtualBox to trixie-fasttrack ## https://salsa.debian.org/fasttrack-team/support/-/issues/74 #install_backports_and_fasttrack_repository_debian ## ## 'trixie' is no longer compatible with 'unstable'. #install_unstable_repository_debian ## Therefore, fall back to Oracle repository. ## No need, because checking 'if [ "${oracle_repo}" = "1" ]; then' above. #install_oracle_repository_debian return 0 ;; forky) install_unstable_repository_debian return 0 ;; kali-rolling) install_kali_repository_debian return 0 ;; *) return 0 ;; esac ## Out-commented. Unreachable code. #if test "${ubuntu_derivative_detected}" = "1"; then # ## Doesn't require extra repository, but this function is used to get # ## Oracle repository information and protocol (onion, https, tor+http etc) # return 0 #fi # #die 1 "${underline}Repository Add:${nounderline} Unsupported distribution codename: '${distro_codename_real}'!" } install_oracle_repository_fedora() { oracle_found="" if dnf repolist --all virtualbox | grep -- "." >/dev/null; then oracle_found="1" fi if dnf repolist --disabled virtualbox | grep -- "." >/dev/null; then dnf config-manager --set-enabled virtualbox fi if [ "${oracle_found}" = "1" ]; then log info "Oracle Repository: Skipped adding Oracle repository because it was already found." else log notice "Oracle Repository: Adding Oracle's clearnet repository to /etc/yum.repos.d/oracle.repo" if [ -f /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle ]; then log info "Oracle Repository: Key /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle already exists." else printf '%s\n' "${oracle_pgp}" | \ run_as_target_user tee -- "${log_dir_cur}/oracle-virtualbox-2016.asc" >/dev/null printf '%s\n' "${oracle_pgp}" | \ root_cmd tee -- /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle >/dev/null root_cmd rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle fi ## Based on: ## https://download.virtualbox.org/virtualbox/rpm/fedora/virtualbox.repo # shellcheck disable=SC2016 printf '%s\n' '[virtualbox] name=Fedora $releasever - $basearch - VirtualBox baseurl=https://download.virtualbox.org/virtualbox/rpm/fedora/$releasever/$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle ' | root_cmd tee -- /etc/yum.repos.d/oracle.repo ## dnf does not have a command to accept add the key to its database. ## https://bugzilla.redhat.com/show_bug.cgi?id=1768206 ## Workaround to accept the key with --assumeyes without accepting other things. ## Does not work. #root_cmd dnf --assumeyes --cacheonly search VirtualBox #root_cmd dnf --assumeyes module list fi } install_oracle_repository_debian() { if [ "${oracle_found}" = "1" ]; then log info "Oracle Repository: Skipped adding Oracle repository because it was already found." else if [ -f /usr/share/keyrings/oracle.asc ]; then log info "Oracle Repository: Key /usr/share/keyrings/oracle.asc already exists." else printf '%s\n' "${oracle_pgp}" | \ run_as_target_user tee -- "${log_dir_cur}/oracle-virtualbox-2016.asc" >/dev/null printf '%s\n' "${oracle_pgp}" | \ root_cmd tee -- /usr/share/keyrings/oracle-virtualbox-2016.asc >/dev/null fi if [ "${use_deb822_sources}" = 'yes' ]; then log notice "Oracle Repository: Adding Oracle's ${connection_type_debsource} repository to ${oracle_file_deb822source}" write_sources_debian "${oracle_deb822_source}" "${oracle_file_deb822source}" "${oracle_signedby_debsource}" else log notice "Oracle Repository: Adding Oracle's ${connection_type_debsource} repository to ${oracle_file_debsource}" write_sources_debian "${oracle_oneline_source}" "${oracle_file_debsource}" "${oracle_signedby_debsource}" fi fi } install_kicksecure_repository_debian() { ## Contains file: ## /usr/share/extrepo/offline-data/debian/bullseye/whonix.asc ## Same as: kicksecure.asc ## (Which is not yet in the extrepo-offline-data package as of Debian 12.0.) install_pkg extrepo-offline-data ## Not using extrepo directly because it does not support torified and/or onion repositories: ## https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1037254 if [ -f /usr/share/keyrings/derivative.asc ]; then log info "Kicksecure Repository: Key /usr/share/keyrings/derivative.asc already exists." log info "Kicksecure Repository: Not copying /usr/share/extrepo/offline-data/debian/bullseye/whonix.asc to /usr/share/keyrings/derivative.asc." else root_cmd cp --verbose /usr/share/extrepo/offline-data/debian/bullseye/whonix.asc /usr/share/keyrings/derivative.asc fi if [ "${kicksecure_found}" = "1" ]; then log info "Kicksecure Repository: Skipped adding Kicksecure repository because it was already found." else if [ "${use_deb822_sources}" = 'yes' ]; then log notice "Kicksecure Repository: Adding Kicksecure's ${connection_type_debsource} repository to ${kicksecure_file_deb822source}" write_sources_debian "${kicksecure_deb822_source}" "${kicksecure_file_deb822source}" "${kicksecure_signedby_debsource}" else log notice "Kicksecure Repository: Adding Kicksecure's ${connection_type_debsource} repository to ${kicksecure_file_debsource}" write_sources_debian "${kicksecure_oneline_source}" "${kicksecure_file_debsource}" "${kicksecure_signedby_debsource}" fi fi } install_kali_repository_debian() { if [ "${kali_found}" = "1" ] && [ "${kali_found_with_contrib}" = "1" ]; then log info "APT Repository Configuration: Skipped adding additional APT repositories because 'kali' (with 'contrib') were already found." return 0 fi if [ "${use_deb822_sources}" = 'yes' ]; then log notice "APT Repository Configuration: Adding 'kali' ${connection_type_debsource} repository to ${kali_file_deb822source}" write_sources_debian "${kali_deb822_source}" "${kali_file_deb822source}" "${kali_signedby_debsource}" else log notice "APT Repository Configuration: Adding 'kali' ${connection_type_debsource} repository to ${kali_file_debsource}" write_sources_debian "${kali_oneline_source}" "${kali_file_debsource}" "${kali_signedby_debsource}" fi } install_unstable_repository_debian() { if [ "${unstable_found}" = "1" ] && [ "${unstable_found_with_contrib}" = "1" ]; then log info "APT Repository Configuration: Skipped adding additional APT repositories because 'unstable' (with 'contrib') were already found." return 0 fi log notice "APT Preferences Configuration Writer: Adding APT pinning configuration to prefer 'stable' ('trixie') over 'unstable'." file="/etc/apt/preferences.d/40-installer-dist-pinning" printf '%s\n' "\ ## This file was created by: $0 ## It can be safely deleted if you know what you are doing. ## Allow package installation from 'unstable' only if explicitly requested ## (never chosen automatically by APT). Package: * Pin: release a=unstable Pin-Priority: 25 ## In case using specific codename 'sid' instead of generic suite 'unstable'. Package: * Pin: release n=sid Pin-Priority: 25 " | root_cmd tee -- "$file" || die 1 "${underline}APT Preferences Configuration Writer:${nounderline} Failed to write to file: '$file'" if [ "${use_deb822_sources}" = 'yes' ]; then log notice "APT Repository Configuration: Adding 'unstable' ${connection_type_debsource} repository to ${unstable_file_deb822source}" write_sources_debian "${unstable_deb822_source}" "${unstable_file_deb822source}" "${unstable_signedby_debsource}" else log notice "APT Repository Configuration: Adding 'unstable' ${connection_type_debsource} repository to ${unstable_file_debsource}" write_sources_debian "${unstable_oneline_source}" "${unstable_file_debsource}" "${unstable_signedby_debsource}" fi } install_backports_and_fasttrack_repository_debian() { log info "Installing packages required for 'backports' and 'fasttrack' repository..." install_pkg ca-certificates fasttrack-archive-keyring if [ "${backports_found}" = "1" ]; then log info "APT Repository Configuration: Skipped adding 'backports' repository because it was already found." else if [ "${use_deb822_sources}" = 'yes' ]; then log notice "APT Repository Configuration: Adding 'backports' ${connection_type_debsource} repository to ${backports_file_deb822source}" write_sources_debian "${backports_deb822_source}" "${backports_file_deb822source}" "${backports_signedby_debsource}" else log notice "APT Repository Configuration: Adding 'backports' ${connection_type_debsource} repository to ${backports_file_debsource}" write_sources_debian "${backports_oneline_source}" "${backports_file_debsource}" "${backports_signedby_debsource}" fi fi if [ "${fasttrack_found}" = "1" ]; then log info "APT Repository Configuration: Skipped adding 'fasttrack' repository because it was already found." else if [ "${use_deb822_sources}" = 'yes' ]; then log notice "APT Repository Configuration: Adding 'fasttrack' ${connection_type_debsource} repository to ${fasttrack_file_deb822source}" write_sources_debian "${fasttrack_deb822_source}" "${fasttrack_file_deb822source}" "${fasttrack_signedby_debsource}" else log notice "APT Repository Configuration: Adding 'fasttrack' ${connection_type_debsource} repository to ${fasttrack_file_debsource}" write_sources_debian "${fasttrack_oneline_source}" "${fasttrack_file_debsource}" "${fasttrack_signedby_debsource}" fi fi if [ "${fasttrack_backports_staging_found}" = "1" ]; then log info "APT Repository Configuration: Skipped adding 'fasttrack' with suite '${fasttrack_backports_staging_suites_debsource}' repository because it was already found." else if [ "${use_deb822_sources}" = 'yes' ]; then log notice "APT Repository Configuration: Adding 'fasttrack' ${connection_type_debsource} repository with suite '${fasttrack_backports_staging_suites_debsource}' to ${fasttrack_backports_staging_file_deb822source}" write_sources_debian "${fasttrack_backports_staging_deb822_source}" "${fasttrack_backports_staging_file_deb822source}" "${fasttrack_signedby_debsource}" else log notice "APT Repository Configuration: Adding 'fasttrack' ${connection_type_debsource} repository with suite '${fasttrack_backports_staging_suites_debsource}' to ${fasttrack_backports_staging_file_debsource}" write_sources_debian "${fasttrack_backports_staging_oneline_source}" "${fasttrack_backports_staging_file_debsource}" "${fasttrack_signedby_debsource}" fi fi ## virtualbox-guest-additions-iso is available from Debian stable and fasttrack. ## Debian stable version however is outdated. ## If not using APT with option '--target-release=trixie-fasttrack' then ## APT would download an outdated version from Debian stable instead the ## newer version from Debian fasttrack. install_pkg_fasttrack_extra_args_maybe=( "--target-release=${distro_codename_common_use}-fasttrack" ) } virtualbox_installation_failure_debug() { local virtualbox_dkms_main_folder latest_folder \ virtualbox_dkms_latest_folder make_log virtualbox_dkms_main_folder=/var/lib/dkms/virtualbox if [ ! -d "$virtualbox_dkms_main_folder" ]; then log notice "VirtualBox Installation Debug: folder '$virtualbox_dkms_main_folder' does not exist." return 0 fi ## TODO: review log_run notice ls --format=single-column --sort=time "$virtualbox_dkms_main_folder" # shellcheck disable=SC2012 latest_folder=$(ls --format=single-column --sort=time "$virtualbox_dkms_main_folder" | head --lines=1) if [ "$latest_folder" = "" ]; then log notice "VirtualBox Installation Debug: latest_folder in '$virtualbox_dkms_main_folder' is empty, could not be determined." log_run notice ls -la "$virtualbox_dkms_main_folder" || true return 0 fi log notice "VirtualBox Installation Debug: latest_folder: '$latest_folder'" virtualbox_dkms_latest_folder="${virtualbox_dkms_main_folder}/${latest_folder}" if [ ! -d "$virtualbox_dkms_latest_folder" ]; then log notice "VirtualBox Installation Debug: virtualbox_dkms_latest_folder '$virtualbox_dkms_latest_folder' is not a directory." log_run notice ls -la "$virtualbox_dkms_latest_folder" || true return 0 fi make_log="${virtualbox_dkms_latest_folder}/build/make.log" if [ ! -f "$make_log" ]; then log notice "VirtualBox Installation Debug: make_log '$make_log' does not exist." log_run notice ls -la "$virtualbox_dkms_latest_folder" || true return 0 fi if [ ! -r "$make_log" ]; then log notice "VirtualBox Installation Debug: make_log '$make_log' not readable." log_run notice ls -la "$virtualbox_dkms_latest_folder" || true log_run notice ls -la "$make_log" || true return 0 fi log_run notice cat -- "$make_log" || true } ## Install VirtualBox on Fedora install_virtualbox_fedora() { local has_virtualbox_qt install_pkg kernel-headers kernel-devel dkms get_virtualbox_version_fedora() { ## Too many issues with auto detection. Therefore hardcoded. virtualbox_qt_package_name="VirtualBox-7.2" return 0 # log info "VirtualBox Version Detection: Running 'dnf search VirtualBox-*'. This can take a while..." # ## --cacheonly does not work. # ## Maybe dnf would need to be run with root_cmd. # virtualbox_version=$(dnf search "VirtualBox-*") # virtualbox_version=$(printf '%s' "$virtualbox_version" | grep -- "^VirtualBox-[[:digit:]]\{1,2\}\.[[:digit:]]\{1,2\}\.") # virtualbox_version=$(printf '%s' "$virtualbox_version" | tail -1) # virtualbox_version=$(printf '%s' "$virtualbox_version" | cut -d " " -f1) # virtualbox_version=$(printf '%s' "$virtualbox_version" | cut -d "-" -f2-) # virtualbox_version=$(printf '%s' "$virtualbox_version" | cut -d "." -f1) # virtualbox_qt_package_name=VirtualBox-"${virtualbox_version}" # if test -z "$virtualbox_version"; then # ## return non-zero late to avoid unbound variable virtualbox_qt_package_name. # return 1 # fi } has_virtualbox_qt=0 if get_virtualbox_version_fedora ; then test_pkg "${virtualbox_qt_package_name}" 2>/dev/null && has_virtualbox_qt=1 else true "INFO: get_virtualbox_version_fedora failed the first time which is normal as the repository has not been enabled yet." fi ## Guard against adding extraneous repositories. if [ "${has_virtualbox_qt}" != "1" ]; then log notice "VirtualBox Installation: Preparing to install VirtualBox..." install_oracle_repository_fedora ## Does not work. #root_cmd dnf makecache --assumeyes '--disablerepo=*' --enablerepo=virtualbox update_sources #check_upgrades_simulation get_virtualbox_version_fedora || die 2 "\ ${underline}VirtualBox Package Version Detection:${nounderline} 'FAIL'" fi ## This is to make sure loglevel info message is shown: ## Package already installed: $virtualbox_qt_package_name install_pkg "$virtualbox_qt_package_name" install_virtualbox_fedora_common_end } ## Discover the VirtualBox version to install in Debian and derivatives. get_virtualbox_version_debian() { local virtualbox_version if [ "${oracle_repo}" = "1" ]; then virtualbox_version=$("${virtualbox_cache_version_cmd[@]}" | tail -1) virtualbox_version=${virtualbox_version#virtualbox-} virtualbox_version=${virtualbox_version%% *} virtualbox_qt_package_name=virtualbox-"${virtualbox_version}" ## Package virtualbox-guest-additions-iso is unavailable from Oracle repository. virtualbox_guest_additions_iso_package_name="" if [ -z "$virtualbox_version" ]; then ## return non-zero late to avoid unbound variable virtualbox_qt_package_name. return 1 fi else virtualbox_qt_package_name=virtualbox-qt ## virtualbox-guest-additions-iso is Freedom Software: ## https://www.kicksecure.com/wiki/Dev/VirtualBox#VirtualBox_Guest_Additions_ISO_Freedom_vs_Non-Freedom ## Provides file: /usr/share/virtualbox/VBoxGuestAdditions.iso ## Useful to easily install VirtualBox guest additions in custom VirtualBox VMs. virtualbox_guest_additions_iso_package_name="virtualbox-guest-additions-iso" fi } ## Begin installation of VirtualBox on Debian and derived systems. install_virtualbox_debian_common_begin() { local pkg log notice "VirtualBox Installation: Preparing to install VirtualBox..." if [ "${oracle_repo}" = "1" ]; then ## Workaround for undeclared dependencies bug by virtualbox.org (Oracle) repository. ## ## udev: ## https://www.virtualbox.org/ticket/21804 ## ## gcc: ## virtualbox-"${virtualbox_version}" by virtualbox.org (Oracle) `Recommends:` `gcc` ## Otherwise kernel modules will not be compiled during installation. install_pkg gcc udev fi virtualbox_cache_version_cmd=('apt-cache' 'search' '--names-only' '--quiet' '^virtualbox-[[:digit:]]{1,2}\.[[:digit:]]{1,2}$') ## Dealing with installation from tarball is not possible, we can identify ## if 'vboxmanage' is in the PATH while no related package was found, but ## then there is not a way to clearly explain to the user that they should ## remove TODO if [ "${oracle_repo}" != '1' ]; then for pkg in $("${virtualbox_cache_version_cmd[@]}" | cut -d " " -f1); do if "${pkg_mngr_check_installed[@]}" "${pkg}" >/dev/null 2>&1; then die 108 "Found Oracle VirtualBox package but option '--oracle-repo' was not set! Run with the option '--oracle-repo' or uninstall the package '$pkg' and then rerun this script." fi done fi if has vboxmanage && ! dpkg --search "$(command -v vboxmanage)"; then die 109 "Found Oracle VirtualBox installed from tarball/source. Using a package manager can conflict with your already manually installed VirtualBox. Uninstall VirtualBox first and then rerun this script. Use the option '--oracle-repo' if you want to install from the Oracle repository." fi install_repositories_for_virtualbox_on_debian update_sources check_upgrades_simulation get_virtualbox_version_debian || die 2 "${underline}VirtualBox Package Version Detection:${nounderline} 'FAIL'" } ## Install VirtualBox on Debian ## See also comments for install_virtualbox_fedora. install_virtualbox_debian() { local linux_image linux_headers install_virtualbox_args linux_image="linux-image-$(dpkg --print-architecture)" linux_headers="linux-headers-$(dpkg --print-architecture)" ## Doing here and now below because we $install_pkg_fasttrack_extra_args_maybe should not be used here. install_pkg "${linux_image}" "${linux_headers}" install_virtualbox_debian_common_begin install_virtualbox_args=() [ "${#install_pkg_fasttrack_extra_args_maybe[@]}" != '0' ] \ && install_virtualbox_args+=( "${install_pkg_fasttrack_extra_args_maybe[@]}" ) install_virtualbox_args+=( "${virtualbox_qt_package_name}" ) [ -n "${virtualbox_guest_additions_iso_package_name}" ] \ && install_virtualbox_args+=( "${virtualbox_guest_additions_iso_package_name}" ) install_pkg "${install_virtualbox_args[@]}" install_virtualbox_debian_common_end } ## Install VirtualBox on Ubuntu install_virtualbox_ubuntu() { local install_virtualbox_args install_virtualbox_debian_common_begin install_virtualbox_args=() [ "${#install_pkg_fasttrack_extra_args_maybe[@]}" != '0' ] \ && install_virtualbox_args+=( "${install_pkg_fasttrack_extra_args_maybe[@]}" ) install_virtualbox_args+=( "${virtualbox_qt_package_name}" ) [ -n "${virtualbox_guest_additions_iso_package_name}" ] \ && install_virtualbox_args+=( "${virtualbox_guest_additions_iso_package_name}" ) install_pkg "${install_virtualbox_args[@]}" linux-image-generic linux-headers-generic install_virtualbox_debian_common_end } ## Helper to install signify on different systems. install_signify() { local pkg_name pkg_name="${1:-signify}" has "${pkg_name}" && return 0 install_pkg "${pkg_name}" } ## Test if user accepts the license, if not, abort. check_license() { local license_agreement dialog_box if [ "${non_interactive}" = "1" ]; then log notice "License Check: 'success' - User agreement confirmed via '--non-interactive' option." return 0 fi log notice "The license will be shown in 5 seconds." log notice "(Use '-n' or '--non-interactive' for non-interactive mode.)" [ "${dry_run}" != "1" ] && sleep 5 dialog_box="" ## 'whiptail' is the Debian version of Dialog with much less features. ## Dialog types problems: ## - whiptail does not allow to set default option with scrolltext on. ## - dialog leaves empty lines on exit. while true; do has dialog && dialog_box="dialog" && break has whiptail && dialog_box="whiptail" && break break done case "${dialog_box}" in dialog) ## output-fd to stdout because currently 'main 2>&1 | tee file' if used ## and makes the dialog fail to recognize the characters as it is ## receiving from stderr and writing to stdout. dialog --erase-on-exit --no-shadow \ --title "${dialog_title}" \ --yes-label "Agree" \ --no-label "Disagree" \ --output-fd 1 \ --yesno "${license}" 640 480 || return 1 log notice "${underline}License Check:${nounderline} 'success' - User has accepted the license." ;; whiptail) ## When text is too long and scrolltext is needed, the yesno box ## does not display a default item. (note: --default-item is for items ## in the box to be selected as menu for example, not for buttons). whiptail \ --scrolltext \ --title "${dialog_title}" \ --yes-button "Agree" \ --no-button "Disagree" \ --yesno "${license}" 24 80 || return 1 log notice "${underline}License Check:${nounderline} 'success' - User has accepted the license." ;; *) printf '%s\n' "${license}" printf '%s' "Do you accept the license(s)? (yes/no): " read -r license_agreement case "${license_agreement}" in [yY][eE][sS]) log notice "${underline}License Check:${nounderline} 'success' - User has accepted the license." ;; *) log warn "${underline}License Check:${nounderline} User replied: '${license_agreement}'" return 1 ;; esac ;; esac } get_checkhash_cmd() { if [ "${virtualbox_only}" = "1" ]; then return 0 fi while true; do has sha512sum && checkhash=( 'sha512sum' '--check' '--strict' '--warn' ) && break has shasum && checkhash=( 'shasum' '--algorithm' '512' '--check' '--strict' '--warn' ) && break ## TODO: Does it have an equivalent to --check? Compatible file format? #has sha512 && checkhash=( 'sha512' '-c' ) && break #has digest && checkhash=( 'digest' '-a' 'sha512' '-c' ) && break ## TODO: How to make openssl check the file without workarounds? #has openssl && checkhash=( 'openssl' 'dgst' '-sha512' '-r' ) && break [ -z "${checkhash[0]}" ] && { die 1 "${underline}get_checkhash_cmd:${nounderline} Failed to find program that checks SHA512 hash sum." } done } get_transfer_cmd() { local transfer_io_timeout transfer_connect_timeout ## curl|rsync ## rsync is always better to retry failed downloads, but some distributions ## might not have torsocks installed, which is necessary for rsync as it ## doesn't support SOCKS5. ## https://pkg.kali.org/news/583693/torsocks-240-1-removed-from-kali-rolling/ #if [ "${debian_testing_or_unstable_detected}" = "1" ] || [ "${kali_derivative_detected}" = "1" ]; then #transfer_utility=curl #else #transfer_utility=rsync #fi transfer_utility=rsync case "${transfer_utility}" in rsync) use_rsync=1 ;; curl) use_curl=1 ;; esac ## 45m transfer_max_time_large_file="2700" ## 3m transfer_max_time_small_file="180" ## 10m transfer_io_timeout="600" ## 3m transfer_connect_timeout="180" transfer_size_test_connection="200K" transfer_size_small_file="2K" transfer_size_large_file="3G" case ${transfer_utility} in curl) ## Maximum time in seconds that we allow the whole operation to take. ## Option works but as rsync doesn't have it, we are using timeout ## utility from coreutils. #transfer_max_time_opt="--max-time" ## Curl does not have I/O timeout. transfer_io_timeout_opt=() ## Maximum time in seconds that we allow curl's connection to take. ## This only limits the connection phase, so if curl's connect in the ## given period, it will continue. transfer_connect_timeout_opt=( '--connect-timeout' "${transfer_connect_timeout}" ) ## curl max-filesize is not a definitive barrier: ## The file size is not always known prior to download, and for ## such files this option has no effect even if the file transfer ends ## up being larger than this given limit. transfer_size_opt="--max-filesize" transfer_dryrun_opt="" transfer_output_dir_opt="--output-dir" transfer_output_file_opt="--remote-name" transfer_verbosity_opt=() transfer_speed_optimization_opt=() ;; rsync*) ## Rsync does not have an option to set maximum time for of operation. ## Option works but as rsync doesn't have it, we are using timeout ## utility from coreutils. #transfer_max_time_opt="" ## If no data is transferred in the specified time, rsync will exit. transfer_io_timeout_opt=( '--timeout' "${transfer_io_timeout}" ) ## Amount of time the client will wait for its connection to a server ## to succeed. ## Error when using this option: ## The --contimeout option may only be used when connecting to an ## rsync daemon. #transfer_connect_timeout_opt=( '--contimeout' "${transfer_connect_timeout}" ) transfer_connect_timeout_opt=() transfer_size_opt="--max-size" transfer_dryrun_opt="--dry-run" transfer_output_dir_opt="" transfer_output_file_opt="" transfer_verbosity_opt=( '--no-motd' '--progress' '--verbose' '--verbose' ) transfer_speed_optimization_opt=( '--compress' '--partial' ) ;; esac } ## Set default traps set_trap() { trap 'handle_exit $? ${LINENO:-}' ERR EXIT HUP INT QUIT ABRT ALRM TERM } ## Check if system status is supported get_system_stat() { local min_ram_mb total_mem_kB total_mem df_output free_space_available \ free_space_required live_status_detected live_status_extra_message if [ "${arch}" != "x86_64" ]; then die 101 "${underline}Architecture Check:${nounderline} Only supported architecture is 'x86_64', yours is: '${arch}'." fi ## https://www.whonix.org/wiki/RAM#Whonix_RAM_and_VRAM_Defaults ## TODO ## min_ram_mb not used currently because less than total 4GB is too low ## already # shellcheck disable=SC2034 case "${interface}" in xfce) min_ram_mb="3328" ;; cli) min_ram_mb="1024" ;; esac if [ "${virtualbox_only}" = "1" ]; then # shellcheck disable=SC2034 min_ram_mb="1024" fi has awk || die 1 "${underline}Architecture Check:${nounderline} Package 'gawk' is missing. Please install." ## 4GB RAM machine reports 3844Mi and 4031MB ## /proc/meminfo replies in kB ## https://ux.stackexchange.com/a/13850 total_mem_kB="$(awk '/MemTotal/{print $2}' /proc/meminfo)" ## convert kB to MB total_mem="$((total_mem_kB/1000))" ## capped to 4200MB to report that 4GB RAM on the host is too little if [ "${total_mem}" -lt "4200" ]; then user_warned_potential_startup_issue=true log warn "${underline}Minimum RAM Check:${nounderline} Your systems has a low amount of total RAM: '${total_mem} MB'" if [ "${virtualbox_only}" != "1" ]; then log warn " - For more information, refer to:" log warn " ${url_version_domain}/wiki/RAM" fi fi df_output="$(run_as_target_user df --output=avail -BG "${directory_prefix}")" free_space_available="$(printf '%s' "$df_output" | awk '/G$/{print substr($1, 1, length($1)-1)}')" ## TODO: do not hardcode 10 GB - Kicksecure vs Whonix free_space_required="10" ## Free space require set to approximately double the image size (2023-03-21) case "${guest}" in whonix) case "${interface}" in xfce) free_space_required="5" ;; cli) free_space_required="3" ;; esac ;; kicksecure) case "${interface}" in xfce) free_space_required="2" ;; cli) free_space_required="1" ;; esac ;; esac if [ "${virtualbox_only}" = "1" ]; then free_space_required="1" fi if [ "${dev}" = "1" ]; then free_space_required="1" fi ## live-status-detected is provided only if package helper-scripts is installed. if has live-status-detected; then live_status_detected="$(live-status-detected)" || true if [ "$live_status_detected" = "true" ]; then log warn "Live Mode Detected: Yes, live mode detected. Unusual. Most users use this installer in persistent mode. But can be OK." live_status_extra_message=" - ${underline}Live Mode detected:${nounderline} Yes. NOTE: Running this installer in Live Mode will usually not work unless the system has a huge amount of RAM. Booting into persistent mode is recommended for using this installer. - If this is an ISO: Consider installing the operating system to an internal or external hard drive first. For more information, refer to: ${url_version_domain}/wiki/USB_Installation - If this is grub-live mode: Boot into persistent mode. For more information, refer to: ${url_version_domain}/wiki/Persistent_Mode" elif [ "$live_status_detected" = "false" ]; then live_status_extra_message="" log info "Live Mode Detected: No, live mode not detected, ok." else log info "Live Mode Detected: Detection failed." live_status_extra_message=" - ${underline}Live Mode detected:${nounderline} 'Detection failed.'" fi else log info "Live Mode Detected: Skipped, because live-status-detected (from helper-scripts) is unavailable, ok." live_status_extra_message="" fi if [ "${free_space_available}" -lt "$free_space_required" ]; then die 101 "\ ${underline}Free Disk Space Check Result:${nounderline} 'FAIL.' - Insufficient free disk space! - ${underline}available:${nounderline} '${free_space_available}G' - ${underline}required :${nounderline} '${free_space_required}G' ${live_status_extra_message} Debugging information: - Command to test the available free space on ${directory_prefix}: df --output=avail -BG \"${directory_prefix}\" - Available free space result: $df_output" fi log info "Free Disk Space Check Result: Success." } ## Generate SOCKS credentials for stream isolation get_proxy_cred() { local proxy_user proxy_pass [ "${transfer_utility}" != "curl" ] && return 0 [ -z "${transfer_proxy_suffix:-}" ] && return 0 proxy_user="anonym" proxy_pass="${1:?}" printf '%s' "--proxy-user ${proxy_user}:${proxy_pass}" } ## Test if can connect to SOCKS proxy and expect the correct Tor reply. check_tor_proxy() { local expected_response_header cmd_check_proxy actual_response_header \ parsed_response_header log notice "Testing SOCKS proxy: '${proxy}'" expected_response_header="HTTP/1.0 501 Tor is not an HTTP Proxy" log info "Expected response header:" log info "'$expected_response_header'" cmd_check_proxy=( 'env' 'UWT_DEV_PASSTHROUGH=1' 'curl' '--silent' '--show-error' '--max-time' '3' '--head' "http://${proxy}" ) log info "Command used to check if proxy is functional:" log info "${cmd_check_proxy[*]}" actual_response_header="$("${cmd_check_proxy[@]}" 2>&1)" parsed_response_header=$(printf '%s' "${actual_response_header}" | head -1) parsed_response_header=$(printf '%s' "${parsed_response_header}" | tr -d "\r") ## Globs are necessary to match patterns in the event the header has more ## characters them expected but still has the expected string. ## Rsync header response example: ## bad response from proxy -- HTTP/1.0 501 Tor is not an HTTP Proxy case "${parsed_response_header}" in *"${expected_response_header}"*) log info "Received header:" log info "'${parsed_response_header}'" log notice "Connected to Tor SOCKS proxy successfully." return 0 ;; *) log error "\ Unexpected proxy response, maybe not a Tor proxy? Debugging information: - Command used to check if proxy is functional: '${cmd_check_proxy[*]}' - Expected response header: '${expected_response_header}' - Actual received header: '${actual_response_header}' - Parsed received header: '${parsed_response_header}'" return 1 esac } ## Set transference proxy depending on transfer utility. ## usage: set_transfer_proxy ${proxy} set_transfer_proxy() { local proxy_port proxy_addr proxy_port="${1##*:}" proxy_addr="${1%%:*}" ## Used for transfers that only curl can do. curl_transfer_proxy=( '--proxy' "socks5h://${1}" ) ## Set transfer proxy per utility. case "${transfer_utility}" in curl) transfer_proxy_suffix=( '--proxy' "socks5h://${1}" ) transfer_proxy_prefix=() ;; rsync*) transfer_proxy_suffix=() transfer_proxy_prefix=( 'torsocks' '--isolate' '--address' "${proxy_addr}" '--port' "${proxy_port}" ) ;; esac } ## Useful to test if it is a SOCKS proxy before attempting to make requests. ## If connection to proxy fails, abort to avoid leaks. torify_conn_maybe() { ## onion=1 -- Always torify onion connections. ## onion=* -- Only torify clearnet if SOCKS proxy is specified. if [ ! "${onion}" = "1" ]; then if [ -z "${socks_proxy:-}" ]; then ## TODO: lower log level log notice "Not torifying connection, because the 'socks_proxy' environment variable is unset nor using '--onion'." return 0 fi fi if ! has tor; then log warn "System 'tor' binary ('little-t-tor') was not found on the system." log warn "Unless your SOCKS connection is made available by the Tor Browser" log warn " or by your uplink network, the proxy check may fail." log warn "The installer with torified connections depends on a working SOCKS proxy," log warn " it won't configure the proxy, only establish the connection." log warn "If the proxy connection fails, try installation of the 'tor' package on your system." fi ## curl and many other viable applications do not support SOCKS proxy to ## connect with Unix Domain Socket: ## https://curl.se/mail/archive-2021-03/0013.html if [ -n "${socks_proxy:-}" ]; then proxy="${socks_proxy}" set_transfer_proxy "${proxy}" elif [ -n "${TOR_SOCKS_PORT:-}" ]; then proxy="${TOR_SOCKS_HOST:-127.0.0.1}:${TOR_SOCKS_PORT}" set_transfer_proxy "${proxy}" else ## Stream Isolation will be enforced get_proxy_cred() log warn "Missing SOCKS proxy for torified connections." log warn "Trying Tor defaults: system Tor ('little-t-tor') (port: '9050') and TBB (Tor Browser Bundle) (port: '9150')." proxy="127.0.0.1:9050" set_transfer_proxy ${proxy} if ! check_tor_proxy; then proxy="127.0.0.1:9150" set_transfer_proxy ${proxy} else return 0 fi fi check_tor_proxy || die 2 "\ ${underline}Check Tor Proxy:${nounderline} 'FAIL' Unable to connect to Tor SOCKS proxy. - This issue is unlikely caused by this installer. - It is more probable that the problem stems from absent software, improper configuration, or network issues. Please note that in order to torify connections, an already functional Tor connection is needed: - A) A pre-installed and running system Tor ('little-t-tor'), or, - B) A pre-installed and running TBB (Tor Browser Bundle). Additional details: - This installer does not support setting up a functional Tor connection. This task needs to be performed by the system administrator. - When running the above cmd_check_proxy manually, ensure it includes the expected_response_header." } ## Set version by user input or by querying the API get_version() { local cmd_raw_version raw_version cmd_raw_version_len cmd_raw_version_idx log notice "Version Detection: Detecting guest version..." if [ -n "${guest_version:-}" ]; then log notice "Version Detection: 'skipped' - Autodetection using API not required via '--guest-version', '--dry-run' or '--dev' option." return 0 fi log info "Version Detection: Acquiring guest version using API..." log info "Version Detection: API host: ${1}" cmd_raw_version=( 'curl' "${curl_transfer_proxy[@]}" "$(get_proxy_cred version)" "${curl_opt_ssl[@]}" '--max-time' "${transfer_max_time_small_file}" '--max-filesize' "${transfer_size_small_file}" '--url' "${1}" ) cmd_raw_version_len="${#cmd_raw_version[@]}" for (( cmd_raw_version_idx=0; cmd_raw_version_idx < cmd_raw_version_len; cmd_raw_version_idx++ )); do if [ -z "${cmd_raw_version[cmd_raw_version_idx]}" ]; then unset "cmd_raw_version[${cmd_raw_version_idx}]" fi done ## this is necessary because we log will not be printed as the command is ## assigned to a variable at 'raw_version=$()'. if [ "${dry_run}" = "1" ]; then log_run notice "${cmd_raw_version[@]}" return 0 fi raw_version="$("${cmd_raw_version[@]}")" ## First line only. guest_version="$(printf '%s' "$raw_version" | head -n 1)" guest_version="$(printf '%s' "${guest_version}" | sed "s/<.*//")" ## Distrust the API version ## Block anything that is not made purely out of numbers and dots ## Not printing queried version to avoid it showing a very long version ## that could inhibit the user from seeing the error message. ## The user would still see a failed exit code. [ "${guest_version%%*[^0-9.]*}" ] || die 1 "${underline}Version Check Result:${nounderline} Invalid guest version: contains unexpected characters." ## block string containing more than 12 chars [ "${#guest_version}" -le 12 ] || die 1 "${underline}Version Check Result:${nounderline} Invalid guest version: contains more than 12 characters." } ## Helper for download_files() to make it less repetitive. ## usage: get_file small|large $url get_file() { local size url download_opt_prefix download_opt download_opt_full round \ download_opt_full_len download_opt_full_idx size="${1}" url="${2}" ## Round is only used to get a different password every time. [ -z "${round:-}" ] && round=10 round=$((round+1)) download_opt=() case "${size}" in small) download_opt_prefix=( 'timeout' '--foreground' "${transfer_max_time_small_file}" ) download_opt+=( "${transfer_size_opt}" "${transfer_size_small_file}" ) ;; large) download_opt_prefix=( 'timeout' '--foreground' "${transfer_max_time_large_file}" ) [ "${#transfer_speed_optimization_opt[@]}" != '0' ] && download_opt+=( "${transfer_speed_optimization_opt[@]}" ) download_opt+=( "${transfer_size_opt}" "${transfer_size_large_file}" ) ;; *) log bug "Missing size option for get_file()." ;; esac download_opt_full=( "${download_opt_prefix[@]}" "${transfer_proxy_prefix[@]}" "${transfer_utility}" "${transfer_verbosity_opt[@]}" "${transfer_proxy_suffix[@]}" "$(get_proxy_cred ${round})" "${transfer_connect_timeout_opt[@]}" "${transfer_io_timeout_opt[@]}" "${download_opt[@]}" "${transfer_output_file_opt}" "${url}" "${transfer_output_dir_opt}" "${directory_prefix}" ) download_opt_full_len="${#download_opt_full[@]}" for (( download_opt_full_idx=0; download_opt_full_idx < download_opt_full_len; download_opt_full_idx++ )); do if [ -z "${download_opt_full[download_opt_full_idx]}" ]; then unset "download_opt_full[${download_opt_full_idx}]" fi done download_opt_full=( "${download_opt_full[@]}" ) log_run notice run_as_target_user "${download_opt_full[@]}" || return 1 } test_file() { local mode file real_file retcode mode="${1}" file="${2}" ## realpath needs to be able to access the path it checks real_file="$(root_cmd_loglevel="echo" dry_run=0 root_cmd realpath -- "${file}")" ## 'find' needs to start execution in a directory it can access pushd / >/dev/null if [ "${mode}" = '-f' ]; then if [ -z "$(run_as_target_user find "${real_file}" -maxdepth 0 -follow -type f 2>/dev/null)" ]; then popd >/dev/null return 1 else popd >/dev/null return 0 fi elif [ "${mode}" = '-d' ]; then if [ -z "$(run_as_target_user find "${real_file}" -maxdepth 0 -follow -type d 2>/dev/null)" ]; then popd >/dev/null return 1 else popd >/dev/null return 0 fi elif [ "${mode}" = '-w' ]; then run_as_target_user test -w "${real_file}" retcode="$?" popd >/dev/null return "${retcode}" elif [ "${mode}" = '-r' ]; then run_as_target_user test -r "${real_file}" retcode="$?" popd >/dev/null return "${retcode}" elif [ "${mode}" = '-e' ]; then if [ -z "$(run_as_target_user find "${real_file}" -maxdepth 0 2>/dev/null)" ]; then popd >/dev/null return 1 else popd >/dev/null return 0 fi else popd >/dev/null return 1 fi } files_already_downloaded_check() { if [ "${dry_run}" = "1" ]; then log notice "files_already_downloaded_check: Creating image file '${directory_prefix}/${guest_file}.${guest_file_ext}' (sha512sums.sig and sha512sums) via '--dry-run' option." touch -- "${directory_prefix}/${guest_file}.${guest_file_ext}" touch -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.sig" touch -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums" fi test_file -f "${directory_prefix}/${guest_file}.${guest_file_ext}" test_file -f "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.sig" test_file -f "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums" } ## Check if files were already downloaded, if not, try to download everything ## and only if succeeds, set download flag. download_files() { log_time log notice "Download: Files will be stored in the directory: '${directory_prefix}'" get_file large "${url_guest_file}.${guest_file_ext}" || return 1 get_file small "${url_guest_file}.${guest_file_ext}.sha512sums.sig" || return 1 get_file small "${url_guest_file}.${guest_file_ext}.sha512sums" || return 1 log info "Download: Checking if files exists locally..." if ! files_already_downloaded_check ; then die 103 "${underline}Download:${nounderline} Failed to download files." fi log_time } ## https://en.wikipedia.org/wiki/X86_virtualization get_virtualization() { local virt_flag brand virt_detection_success virt msr virt_disabled virt_bit ## Check if virtualization is enabled. ## Check CPU flags for capability virt_flag="$(root_cmd grep -m1 -w -- '^flags[[:blank:]]*:' /proc/cpuinfo | grep -wo -E -- '(vmx|svm)' || true)" case "${virt_flag:=none}" in vmx) brand=intel;; svm) brand=amd;; *) brand=unknown;; esac # if compgen -G "/sys/kernel/iommu_groups/*/devices/*" > /dev/null; then # log notice "${brand}'s I/O Virtualization Technology is enabled in the BIOS/UEFI" # else # log warn "${brand}'s I/O Virtualization Technology is not enabled in the BIOS/UEFI" # fi case "${virt_flag:=}" in vmx|svm) log notice "Virtualization Support Test Result: 'success'" log info "cpu_brand: '${brand}' virt_flag: '${virt_flag}'" virt_detection_success=true return 0 ;; none) user_warned_potential_startup_issue=true log warn "${underline}Virtualization Support Test:${nounderline} ${red}${bold}'FAIL'${nobold}${nocolor} - No virtualization flag found." virt_detection_success=false ;; *) user_warned_potential_startup_issue=true log warn "${underline}Virtualization Support Test:${nounderline} ${red}${bold}'FAIL'${nobold}${nocolor} - Unknown virtualization flag. (Use '--log-level=info' for potentially privacy sensitive details.)" log info "cpu_brand: '${brand}' virt_flag: '${virt_flag}'" virt_detection_success=false ;; esac if [ "$virt_detection_success" = "false" ]; then user_warned_potential_startup_issue=true log warn " - The virtualization detection feature of this installer may not be flawless and could potentially fail to detect virtualization support (this is known as a 'false negative')." if [ "${hypervisor}" = "virtualbox" ]; then log warn " - Refer to user documentation on how to enable virtualization:" log warn " https://www.kicksecure.com/wiki/VirtualBox/Troubleshooting#Enable_VT-x_in_BIOS" fi return 0 ## Let's not hard fail here, let the user do it later. #return 101 fi ## msr is blocked by security-misc. If no other solution is found, ## remove the rest of this function. ## $ modprobe msr ## /bin/disabled-msr-by-security-misc: ERROR: This CPU MSR kernel module is disabled by package security-misc by default. See the configuration file /etc/modprobe.d/30_security-misc.conf | args: ## modprobe: ERROR: ../libkmod/libkmod-module.c:990 command_do() Error running install command '/bin/disabled-msr-by-security-misc' for module msr: retcode 1 ## modprobe: ERROR: could not insert 'msr': Invalid argument install_pkg msr-tools # https://bazaar.launchpad.net/~cpu-checker-dev/cpu-checker/trunk/view/head:/kvm-ok # kvm-ok - check whether the CPU we're running on supports KVM acceleration # Copyright (C) 2008-2010 Canonical Ltd. # # Authors: # Dustin Kirkland # Kees Cook # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Print verdict verdict() { case "${1}" in 0) log notice "Virtualization can be used." log warn "Virtualization availability can be a false negative." return 0 ;; 1) user_warned_potential_startup_issue=true log warn "Virtualization can NOT be used." log warn "Virtualization availability can be a false negative." return 0 ## let's not hard fail here, let the user do it later. #return 1 ;; 2) user_warned_potential_startup_issue=true log warn "Virtualization can be used, but not enabled." log warn "Virtualization availability can be a false negative." return 0 ## let's not hard fail here, let the user do it later. #return 1 ;; esac } ## Check CPU flags for capability virt=$(root_cmd grep -m1 -w -- '^flags[[:blank:]]*:' /proc/cpuinfo | grep -wo -E -- '(vmx|svm)') || true if [ -z "${virt}" ]; then log error "Your CPU does not support Virtualization." verdict 1 fi [ "${virt}" = "vmx" ] && brand="intel" [ "${virt}" = "svm" ] && brand="amd" ## Now, check that the device exists if [ -e /dev/kvm ]; then log notice "Device /dev/kvm exists" verdict 0 else user_warned_potential_startup_issue=true log warn "Device /dev/kvm does not exist" log warn "hint: '${sucmd} modprobe kvm_$brand'" fi ## Prepare MSR access msr="/dev/cpu/0/msr" if root_cmd [ ! -r "${msr}" ]; then root_cmd modprobe msr || die 1 "${underline}modprobe:${nounderline} Could not add module 'msr' to the kernel." fi if root_cmd [ ! -r "${msr}" ]; then log error "Cannot read: '${msr}'" return 1 fi log notice "Your CPU supports Virtualization extensions." virt_disabled=0 ## check brand-specific registers if [ "${virt}" = "vmx" ]; then virt_bit=$(root_cmd rdmsr --bitfield 0:0 0x3a 2>/dev/null || true) if [ "${virt_bit}" = "1" ]; then ## and FEATURE_CONTROL_VMXON_ENABLED_OUTSIDE_SMX clear (no tboot) virt_bit=$(root_cmd rdmsr --bitfield 2:2 0x3a 2>/dev/null || true) [ "${virt_bit}" = "0" ] && virt_disabled=1 fi elif [ "${virt}" = "svm" ]; then virt_bit=$(root_cmd rdmsr --bitfield 4:4 0xc0010114 2>/dev/null || true) [ "${virt_bit}" = "1" ] && virt_disabled=1 else log error "Unknown virtualization extension: '${virt}'" verdict 1 fi if [ "${virt_disabled}" -eq 1 ]; then user_warned_potential_startup_issue=true log warn "'${virt}' is disabled by your BIOS" log warn "Enter your BIOS setup and enable Virtualization Technology (VT)," log warn " and then reboot your system." verdict 2 fi verdict 0 } ######################### ## END SCRIPT SPECIFIC ## ######################### ################ ## BEGIN MAIN ## ################ get_download_links() { local site_clearnet_whonix site_onion_whonix site_clearnet_kicksecure \ site_onion_kicksecure site_clearnet site_onion site_download_clearnet \ site_download_onion protocol_prefix_clearnet protocol_prefix_onion \ url_download_clearnet url_download_onion url_download url_version_template \ url_version_prefix url_version_suffix ## Set upstream links as base, especially for API. ## clearnet project domain site_clearnet_whonix="whonix.org" ## onion project domain site_onion_whonix="dds6qkxpwdeubwucdiaord2xgbbeyds25rbsgr73tbfpqpt4a6vjwsyd.onion" ## clearnet project domain site_clearnet_kicksecure="kicksecure.com" ## onion project domain site_onion_kicksecure="w5j6stm77zs6652pgsij4awcjeel3eco7kvipheu6mtr623eyyehj4yd.onion" case "${guest}" in whonix) site_clearnet="${site_clearnet_whonix}" site_onion="${site_onion_whonix}" ;; kicksecure) site_clearnet="${site_clearnet_kicksecure}" site_onion="${site_onion_kicksecure}" ;; *) ## Variables need to be set for --virtualbox-only. site_clearnet="${site_onion_kicksecure}" site_onion="${site_clearnet_kicksecure}" ;; esac if [ -z "${mirror}" ]; then ## No mirror chosen, use default values. mirror=0 fi ## ${variable+string} means use string as value if variable is not empty. ## get download links by mirror of choice. case "${mirror}" in 0) site_download_clearnet="${use_curl+download.}${site_clearnet}${use_rsync+/${guest}}" site_download_onion="${use_curl+download.}${site_onion}${use_rsync+/${guest}}" ;; 1) site_download_clearnet="mirrors.dotsrc.org/${guest}" site_download_onion="dotsrccccbidkzg7oc7oj4ugxrlfbt64qebyunxbrgqhxiwj3nl6vcad.onion/${guest}" ;; 2) site_download_clearnet="quantum-mirror.hu/mirrors/pub/${guest}" site_download_onion="" ;; *) ## range_arg should have catch this error before, just safeguarding. log bug "Invalid mirror number: '${mirror}'" ;; esac case "${transfer_utility}" in curl) protocol_prefix_clearnet="https" protocol_prefix_onion="http" ;; rsync) protocol_prefix_clearnet="rsync" protocol_prefix_onion="rsync" ;; esac ## clearnet download url url_download_clearnet="${protocol_prefix_clearnet}://${site_download_clearnet}" ## onion download url url_download_onion="${protocol_prefix_onion}://${site_download_onion}" case "${onion}" in 1) log info "Onion preferred." curl_opt_ssl=() ## Used to test internet connection. url_origin="${protocol_prefix_onion}://www.${site_onion}" ## URL to download files from. [ -n "${site_download_onion}" ] || die 1 "${underline}Mirror Selection:${nounderline} Mirror ${mirror} doesn't provide an onion service." url_download="${url_download_onion}" ## Used to query version number. url_version_domain="http://www.${site_onion}" ;; *) log info "Clearnet preferred." [ "${transfer_utility}" = "rsync" ] && transfer_utility="rsync-ssl" curl_opt_ssl=( '--tlsv1.3' '--proto' '=https' ) ## Used to test internet connection. url_origin="${protocol_prefix_clearnet}://www.${site_clearnet}" ## URL to download files from. url_download="${url_download_clearnet}" ## Used to query version number. url_version_domain="https://www.${site_clearnet}" ;; esac case "${hypervisor}" in virtualbox) if [ "${testers}" = "1" ]; then url_version_template="VersionTesters" else url_version_template="VersionNew" fi ## image signer signify_key="${adrelanos_signify}" signify_signer="adrelanos" ## url directory to find files of the selected hypervisor url_domain="${url_download}/ova" ## image file extension guest_file_ext="Intel_AMD64.ova" ## function to call when importing guest ;; kvm) if [ "${testers}" = "1" ]; then die 1 "${underline}Version Selection:${nounderline} KVM does not have testers version." #url_version_template="" else url_version_template="Version_KVM" fi ## image signer signify_key="${hulahoop_signify}" signify_signer="hulahoop" ## url directory to find files of the selected hypervisor url_domain="${url_download}/libvirt" ## image file extension guest_file_ext="Intel_AMD64.qcow2.libvirt.xz" ## function to call when importing guest ## TODO ;; esac url_version_prefix="w/index.php?title=Template:" url_version_suffix="&stable=0&action=raw" url_version="${url_version_domain}/${url_version_prefix}${url_version_template}${url_version_suffix}" } ## Test if files should be downloaded should_download() { local download_msg_done if [ "${virtualbox_only}" = "1" ]; then ## 'return 1' so the result of should_download is "no". return 1 fi if [ "${dry_run}" = "1" ]; then log notice "Download: Creating download flag via '--dry-run' option." dry_run=0 log_run notice run_as_target_user touch -- "${download_flag}" return 0 fi if [ "${redownload}" = "1" ]; then ## Do not print further messages as it was already printed before. ## Occurs if the should_download() function was called more than once. [ "${download_msg_done:-}" = "1" ] && return 0 # shellcheck disable=SC2034 download_msg_done=1 ## Download if redownload option is set. log notice "Download: Re-downloading files via '--redownload' option." return 0 elif test_file -f "${download_flag:-}"; then ## Do not download if flag exists. log notice "Download: Skipping download because download and integrity check previously succeeded." return 1 fi ## Download as no obstacles prohibit it. return 0 } ## Check signature of signed checksum. check_signature() { local signify_checksum_file signify_pub_file signify_checksum_file="${1}" log info "Signify key:\n${signify_key}" log info "Verifying file: '${signify_checksum_file}'" signify_pub_file="${log_dir_cur}/${signify_signer}.pub" ## Newline '\n' at the end needed. Otherwise error: #signify-openbsd: missing new line after base64 in /home/user/dist-installer-cli-download/logs/1/adrelanos.pub printf '%s\n' "${signify_key}" | run_as_target_user tee -- "${signify_pub_file}" >/dev/null log_run info run_signify -V -p "${signify_pub_file}" \ -m "${signify_checksum_file}" || return 1 log info "Signify Signature Verification: 'success'" } ## Check hash sum. check_hash() { local shafile dir shafile="${1}" dir="$(dirname "${shafile}")" log info "Checking SHA512 checksum: '${shafile}" ## $checkhash needs to be executed on the same folder as the compared file. log info "Changing to directory: '${dir}'" if [ -n "${target_user}" ]; then run_as_target_user_and_dir "${dir}" "${checkhash[@]}" "${shafile}" || return 1 else cd "${dir}" log_run info run_as_target_user "${checkhash[@]}" "${shafile}" || return 1 fi log info "SHA512 Hash Verification: 'success'" } check_signature_test() { log info "Unit testing signature, expecting non-zero exit code." run_as_target_user rm -f -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" run_as_target_user cp -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums" "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" printf '\n' | run_as_target_user tee -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" >/dev/null if ! check_signature "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" 2>/dev/null; then run_as_target_user rm -f -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" log info "Received expected non-zero exit code from unit test." else run_as_target_user rm -f -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" die 104 "${underline}SHA512 Hash Verification (unit test):${nounderline} 'FAIL' - received a zero as exit code, expected non-zero." fi } check_hash_test() { log info "Unit testing checksum, expecting non-zero exit code." run_as_target_user rm -f -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" run_as_target_user cp -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums" "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" printf '\n' | run_as_target_user tee -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" >/dev/null if ! check_hash "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" 2>/dev/null; then run_as_target_user rm -f -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" log info "Received expected non-zero exit code from unit test." else run_as_target_user rm -f -- "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums.test" die 104 "${underline}SHA512 Hash Verification (unit test):${nounderline} 'FAIL' - received a zero as exit code, expected non-zero." fi } ## Check integrity of files check_integrity() { if [ "${virtualbox_only}" = "1" ]; then return 0 fi if [ "${dry_run}" = "1" ]; then log notice "Integrity Check: Skipping integrity checks via '--dry-run' option." return 0 fi log notice "Integrity Check: Performing integrity checks..." check_signature "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums" || die 104 "${underline}Signify Signature Verification:${nounderline} 'FAIL'" check_hash "${directory_prefix}/${guest_file}.${guest_file_ext}.sha512sums" \ || die 104 "${underline}SHA512 Hash Verification:${nounderline} 'FAIL'" check_signature_test check_hash_test log_run info run_as_target_user touch -- "${download_flag}" log notice "Integrity Check Result: 'success'" } ## Self explanatory name, make everything after option parsing. main() { local item cmd_check_internet cmd_check_internet_idx cmd_check_internet_len ############### ## BEGIN PRE ## ############### log info "Starting main function." case "${guest}" in whonix) guest_full_vm_name_gateway="${guest_pretty}-Gateway-${interface_name}" guest_full_vm_name_workstation="${guest_pretty}-Workstation-${interface_name}" ;; kicksecure) guest_full_vm_name_kicksecure="${guest_pretty}-${interface_name}" ;; esac log info "Parsed options:" for item in $(print_getopt); do log info " ${item}" done if [ "${non_interactive}" != "1" ]; then log notice "If you wish to cancel installation, press Ctrl+C." fi ## The license function sleeps for some seconds to give time to abort check_license || die 100 "${underline}License Check:${nounderline} User declined the license." log_time ############# ## END PRE ## ############# #################### ## BEGIN DOWNLOAD ## #################### ## Skip making internet requests if flag already exists and user ## specified the desired version. ## If version is set, use it now to set the download flag path. if [ -n "${guest_version}" ]; then guest_file="${guest_pretty}-${interface_name}-${guest_version}" download_flag="${directory_prefix}/${guest_file}.${guest_file_ext}.flag" fi if should_download; then if [ "${dry_run}" != "1" ]; then log notice "Connectivity Test: Testing internet connection to '${url_origin}'..." cmd_check_internet=( 'timeout' '--foreground' "${transfer_max_time_small_file}" "${transfer_proxy_prefix[@]}" "${transfer_utility}" "${transfer_proxy_suffix[@]}" "${transfer_dryrun_opt}" "${transfer_size_opt}" "${transfer_size_test_connection}" "${url_origin}" ) cmd_check_internet_len="${#cmd_check_internet[@]}" for (( cmd_check_internet_idx=0; cmd_check_internet_idx < cmd_check_internet_len; cmd_check_internet_idx++ )); do if [ -z "${cmd_check_internet[cmd_check_internet_idx]}" ]; then unset "cmd_check_internet[${cmd_check_internet_idx}]" fi done log info "Executing: $ ${cmd_check_internet[*]}" "${cmd_check_internet[@]}" >/dev/null || die $? "${underline}Connectivity Test Result:${nounderline} 'FAIL' - Cannot connect, perhaps no internet?" log notice "Connectivity Test Result: 'success'" fi get_version "${url_version}" log notice "Version Detection Result: '${guest_version}'" guest_file="${guest_pretty}-${interface_name}-${guest_version}" download_flag="${directory_prefix}/${guest_file}.${guest_file_ext}.flag" url_domain="${url_domain}/${guest_version:?}" url_guest_file="${url_domain}/${guest_file}" ## Check again for download flag after version was queried. if should_download; then download_files || die 103 "${underline}Download:${nounderline} Failed to download files." fi else if [ "${virtualbox_only}" = "1" ]; then true "INFO: Skip showing version via '--virtualbox-only' option." else log notice "Version Detection Result: '${guest_version}'" fi fi ################## ## END DOWNLOAD ## ################## ########################################## ## BEGIN VERIFICATION, IMPORT AND START ## ########################################## check_integrity check_vm_running_general check_vm_exists_general "(check before maybe deletion)" vm_delete_maybe check_vm_exists_general "(check before import)" import_guest check_guest_boot ######################################## ## END VERIFICATION, IMPORT AND START ## ######################################## } ## Print usage message and exit with set exit code, depending if usage was ## called by [-h|--help] or because user tried and invalid option. usage() { printf '%s' "Usage: ${me} [options...] User Options: -g, --guest= Specify the guest. Options: kicksecure, whonix (default) -u, --guest-version= Specify guest version, or query from API if not provided. -i, --interface= Choose the interface. Options: cli, xfce (default) -m, --hypervisor= Select the virtualization platform. Options: kvm, virtualbox (default) --oracle-repo Use Oracle's repository for VirtualBox -o, --onion Enable downloading files via onion. -s, --socks-proxy= Set TCP SOCKS proxy for onion client connections. (Defaults to TOR_SOCKS_HOST:TOR_SOCKS_PORT; if unset, attempts to use TBB (Tor Browser Bundle) proxy at port '9150', or system Tor ('little-t-tor') proxy at port '9050'.) -l, --log-level= Choose log verbosity. Options: debug, info, notice (default), warn, error. -V, --version Display version information and exit. -h, --help Show this help message and exit. Developer options: --no-show-errors Suppress error messages. --allow-errors Continue execution despite errors. Dirty mode. Use with caution. --mirror= Choose a download mirror by index. Defaults to mirror 0 for clearnet and mirror 0 for onion if unspecified. Mirror indices: 0 [DE] download.whonix.org (onion available) 1 [DK] mirrors.dotsrc.org (onion available) 2 [HU] quantum-mirror.hu --redownload Re-download the guest image, even if previously successful. --import-only= Select specific VM to import. Only if guest is Whonix. Options: workstation, gateway. --no-import Skip guest import. Default behavior is to import. --destroy-existing-guest Deletes any existing virtual machine(s) and re-imports them. Warning: This action poses a risk of data loss as it involves a complete reinstallation of the VM(s). Proceed with caution. -k, --no-boot Do not boot the guest after setup. Default is to start. -P, --directory-prefix= Specify the absolute path for the directory where files will be saved. Ensure that the directory already exists and is readable and writable by the user. If this directory is changed and previously downloaded files are not moved to the new directory, the download will restart. Default: \$HOME/dist-installer-cli-download. -n, --non-interactive Enable non-interactive mode; license will be accepted. -D, --dev Activate development mode. Downloads an empty image. --noupdate Skip package manager list update. For development only. --noupgrade Ignore pending upgrades. For development only. --ci Enable Continuous Integration (CI) mode. --testers Download the tester's version. -d, --dry-run Simulate execution; log commands without executing. --virtualbox-only Restrict actions to downloading and installing VirtualBox. -t, --getopt Display parsed options and exit. --user=username Specify the user to install the distribution VM under. File name: The default file name is dist-installer-cli. Basic options can be set by using file name following the format 'guest-installer-interface'. Names not adhering to this format or the default are rejected. Command-line options take precedence over file name settings. " exit "${1:-0}" } ## Set default values for variables. set_default() { ## Options user_home_dir="${HOME}" directory_prefix="${HOME}/dist-installer-cli-download" guest=whonix hypervisor=virtualbox interface=xfce oracle_repo="" log_level=notice guest_version="" socks_proxy="" onion="" non_interactive="" dev="" noupdate="" noupgrade="" dry_run="" getversion="" getopt="" ci="" no_import="" no_boot="" redownload="" import_only="" destroy_existing_guest="" testers="" allow_errors="" mirror="" virtualbox_only="" target_user="" ## Runtime variables. last_exit=0 guest_pretty="" xtrace="" url_version_domain="" run_background="" background_pid="" debian_derivative_detected="" ubuntu_derivative_detected="" kali_derivative_detected="" fedora_derivative_detected="" virtualbox_linux_user_group="vboxusers" # shellcheck disable=SC2034 qubes_template_detected="" install_pkg_fasttrack_extra_args_maybe=() user_warned_potential_startup_issue="" can_boot_virtualbox_guest_vms="true" ## 'vboxmanage' locale needs to be "C" (default, English) so output can be grepped. vboxmanage_locale_english=( 'env' 'LC_ALL=C.UTF-8' 'LANG=C.UTF-8' 'LANGUAGE=C' 'vboxmanage' ) } set_target_user_account() { target_user="${1:-}" if [ "${sucmd}" != 'sudo' ]; then log warn "Privilege escalation utilities other than 'sudo' for installing to an alternate user account or running under account 'sysmaint' is untested." fi ## 'getent' may fail if the account doesn't exist, however we ignore it for now. ## The reason is because if this is run as a user account `sysmaint` on a ## system with no account 'user', this will fail, but the account may have passed ## their own --user and --directory-prefix flags which will fix it. user_home_dir="$(getent passwd "${target_user}" | cut -d':' -f6)" || true } run_as_target_user() { if [ -z "${target_user}" ]; then "${@}" else ROOT_CMD_TARGET_USER="${target_user}" root_cmd "${@}" fi } run_as_target_user_and_dir() { local target_dir target_dir="${1:-}" [ -z "${target_dir}" ] && die 1 "Failed to provide directory name to 'run_as_target_user_and_dir'!" [ -z "${target_user}" ] && die 1 "Cannot use 'run_as_target_user_and_dir' if 'set_target_user_account' is not used first!" shift ROOT_CMD_TARGET_USER="${target_user}" ROOT_CMD_TARGET_DIR="${target_dir}" root_cmd "${@}" } adjust_default_for_sysmaint_maybe() { if getent passwd sysmaint >/dev/null; then log info "Account 'sysmaint' exists: 'Yes'" if [ "$(id -un)" = 'sysmaint' ]; then log info "Running under account 'sysmaint': 'Yes', adjusting behavior." set_target_user_account 'user' directory_prefix="${user_home_dir}/dist-installer-cli-download" else log info "Running under account 'sysmaint': 'No'" fi else log info "Account 'sysmaint' exists: 'No'" fi } ## Print parsed options print_getopt() { printf '%s\n' "directory_prefix=${directory_prefix} guest=${guest} hypervisor=${hypervisor} interface=${interface} oracle_repo=${oracle_repo} log_level=${log_level} guest_version=${guest_version} socks_proxy=${socks_proxy} onion=${onion} non_interactive=${non_interactive} dev=${dev} noupdate=${noupdate} noupgrade=${noupgrade} dry_run=${dry_run} getversion=${getversion} getopt=${getopt} ci=${ci} no_import=${no_import} no_boot=${no_boot} redownload=${redownload} import_only=${import_only} destroy_existing_guest=${destroy_existing_guest} testers=${testers} allow_errors=${allow_errors} mirror=${mirror} virtualbox_only=${virtualbox_only}" } ## Parse script name. parse_name() { ## if using default file name, ignore the rest [ "${me}" = "dist-installer-cli" ] && return 0 [ "${me}" = "dist-installer-cli-standalone" ] && return 0 ## check if file name is valid case "${me}" in whonix-xfce-installer-cli | whonix-cli-installer-cli | \ kicksecure-xfce-installer-cli | kicksecure-cli-installer-cli ) log info "Valid script name to set options: '${me}'" ;; virtualbox-installer-cli ) log info "Valid script name to set options: '${me}'" virtualbox_only=1 hypervisor=virtualbox return 0 ;; *) log error "Invalid script name: '${me}'" log error "If you don't know why this happened, rename this script to" log error " dist-installer-cli and use command-line options instead." return 2 esac ## assign values according to script name guest="$(printf '%s' "${me}" | cut -d "-" -f1)" interface="$(printf '%s' "${me}" | cut -d "-" -f2)" log info "Assigned guest and interface according to script name: '${me}'" return 0 } copy_thru_barrier() { local source dest source_basename dest_realpath dest_file source="${1}" dest="${2}" source_basename="$(basename -- "${source}")" dest_realpath="$(root_cmd_loglevel="echo" dry_run=0 root_cmd realpath -- "${dest}")" if test_file -d "${dest_realpath}"; then dest_file="${dest_realpath}/${source_basename}" else dest_file="${dest_realpath}" fi run_as_target_user tee -- "${dest_file}" >/dev/null < "${source}" } ## Parse command-line options. parse_opt() { local directory_prefix_parent last_run_integer cur_run_integer log_dir_main #[ -z "${1:-}" ] && usage 2 while true; do begin_optparse "${1:-}" "${2:-}" || break ## SC2154: Variable ${opt} gets set in file: /usr/libexec/helper-scripts/parse_opt.sh case "${opt}" in P|directory-prefix) get_arg directory_prefix="${arg}" ;; o|onion) onion=1 ;; s|socks-proxy) get_arg socks_proxy="${arg}" ;; l|log-level) get_arg log_level="${arg}" ;; g|guest) get_arg guest="${arg}" ;; u|guest-version) get_arg guest_version="${arg}" ;; i|interface) get_arg interface="${arg}" ;; m|hypervisor) get_arg hypervisor="${arg}" ;; oracle-repo) oracle_repo=1 ;; mirror) get_arg mirror="${arg}" ;; import-only) get_arg import_only="${arg}" ;; allow-errors) allow_errors=1 ;; redownload) redownload=1 ;; destroy-existing-guest) destroy_existing_guest=1 ;; n|non-interactive) non_interactive=1 ;; k|no-boot) no_boot=1 ;; no-import) no_import=1 ;; D|dev) dev=1 ;; noupdate) noupdate=1 ;; noupgrade) noupgrade=1 ;; testers) testers=1 ;; t|getopt) getopt=1 ;; ci) ci=1 ;; d|dry-run) dry_run=1 ;; virtualbox-only) virtualbox_only=1 hypervisor=virtualbox guest=none ;; user) get_arg set_target_user_account "${arg}" ;; V|version) getversion=1 ;; h|help) usage 0 ;; "") break ;; *) die 2 "Invalid option: '${opt_orig}'" ;; esac shift "${shift_n:-1}" done ## Test if options are valid range_arg log_level "${log_level}" error warn notice info debug if [ "${log_level}" = "debug" ]; then xtrace=1 set -o xtrace fi range_arg guest "${guest}" none whonix kicksecure guest_pretty="$(capitalize_first_char "${guest}")" range_arg interface "${interface}" cli xfce range_arg hypervisor "${hypervisor}" kvm virtualbox range_arg import_only "${import_only}" workstation gateway both case "${interface}" in xfce) ## Whonix 17 and above uses Xfce instead of XFCE. interface_name="Xfce" ;; cli) interface_name="CLI" ;; esac if [ "${guest}" != "whonix" ]; then ## Guest is not 'whonix'. I.e. is 'kicksecure' if [ -n "${import_only}" ]; then die 1 "The option '--import-only' option can only be set when the guest is 'whonix'." fi fi [ -n "${mirror}" ] && range_arg mirror "${mirror}" 0 1 2 [ -n "${socks_proxy}" ] && is_addr_port "${socks_proxy}" if [ -n "${directory_prefix}" ]; then ## Remove trailing slash from directory. directory_prefix="${directory_prefix%*/}" ## Only accept an absolute path. if [ "${directory_prefix}" = "${directory_prefix#/}" ]; then log error "Invalid directory prefix: '${directory_prefix}'" die 1 "Directory prefix can not be a relative path, must be an absolute path." fi ## Test if parent directory exists. directory_prefix_parent="$(dirname "${directory_prefix}")" if ! test_file -d "${directory_prefix_parent}"; then die 1 "Directory doesn't exist: '${directory_prefix_parent}'" fi ## Not possible to check if parent dir is writable because if the prefix ## is set to '~/', the parent '/home' is not writable. log info "Creating directory: '${directory_prefix}'" run_as_target_user mkdir -p -- "${directory_prefix}" || die 1 "Failed to created directory: '${directory_prefix}'" test_file -w "${directory_prefix}" || die 1 "Directory isn't writable: '${directory_prefix}'" test_file -r "${directory_prefix}" || die 1 "Directory isn't readable: '${directory_prefix}'" log_dir_main="${directory_prefix}/logs" ## Log to incrementing integer to avoid leaking other information such ## as PID or date (even if UTC). if ! test_file -d "${log_dir_main}/1"; then log_dir_cur="${log_dir_main}/1" else has awk || die 1 "${underline}Parse options:${nounderline} Package 'gawk' is missing. Please install." last_run_integer="$(printf '%s ' "${log_dir_main}"/* | awk '{print NF}')" cur_run_integer=$((last_run_integer+1)) log_dir_cur="${log_dir_main}/${cur_run_integer}" fi log_file_user="${log_dir_cur}/user.log" log_file_debug="${log_dir_cur}/debug.log" ## If the commands below fail, it should have failed earlier for the ## parent directory permissions, not below. run_as_target_user mkdir -p -- "${log_dir_cur}" copy_thru_barrier "${0}" "${log_dir_cur}"; run_as_target_user touch -- "${log_file_user}" fi if [ "${getopt}" = "1" ]; then print_getopt exit 0 fi if [ "${getversion}" = "1" ]; then printf '%s\n' "${me} ${version}" exit 0 fi if [ "${dev}" = "1" ]; then if [ -z "${guest_version}" ]; then log notice "Version Detection: Setting development testing empty software version via '--dev' option." guest_version="17.0.3.4" fi fi if [ "${dry_run}" = "1" ]; then if [ -z "${guest_version}" ]; then log notice "Simulation: commands will be printed but not executed via '--dry-run' option." log notice "Version Detection: Using simulated software version via '--dry-run' option." guest_version="17.0.3.4" fi fi if [ "${allow_errors}" = "1" ]; then set +o errexit set +o errtrace fi case "${hypervisor}" in virtualbox) hypervisor_pretty="VirtualBox" ;; kvm) hypervisor_pretty="KVM" ;; *) hypervisor_pretty="${hypervisor}" ;; esac log info "Option Parsing: 'success'" } ## Logging mechanism. ## Bash supports process substitution and saving xtrace to a file, which is a ## simpler way to log to file and console. log_term_and_file() { local temp_folder xtrace_fifo ## Discover if terminal is attached to stdout if [ ! -t 1 ]; then log warn "Terminal: Output is not being sent to the terminal because terminal is not connected to stdout." return 0 fi run_as_target_user touch -- "${log_file_user}" run_as_target_user touch -- "${log_file_debug}" ## By appending '&' at the end, log_file_user would remain empty. true "\ exec > >(run_as_target_user tee -a -- \"${log_file_user}\") \ 2> >(run_as_target_user tee -a \"${log_file_debug}\" >&2)" exec > >(run_as_target_user tee -a -- "${log_file_user}") \ 2> >(run_as_target_user tee -a -- "${log_file_debug}" >&2) temp_folder="$(mktemp --directory)" xtrace_fifo="/${temp_folder}/xtrace_fifo" mkfifo -- "$xtrace_fifo" if [ "${log_level}" = "debug" ] || test -o xtrace; then ## log_level is debug or xtrace is already enabled. ## Therefore keep send xtrace to both console and debug file. run_as_target_user tee -a -- "${log_file_debug}" < "$xtrace_fifo" >&2 & else run_as_target_user tee -a -- "${log_file_debug}" < "$xtrace_fifo" > /dev/null & fi ## Automatically exists at the end of this script. #xtrace_pid="$!" exec 9> "$xtrace_fifo" export BASH_XTRACEFD=9 set -o xtrace xtrace=1 } welcome() { if [ "${virtualbox_only}" = "1" ]; then log notice "${underline}Installer:${nounderline} ${bold}'VirtualBox Installer'${nobold}" else log notice "${underline}Installer:${nounderline} ${bold}'${guest_pretty} ${interface_name} for ${hypervisor_pretty} Installer'${nobold}" fi log notice "Saving user log to: '${log_file_user}'" if test_file -f "${log_file_debug}"; then log notice "Saving debug log to: '${log_file_debug}'" fi } end_installer() { log notice "Installer Result: ${green}${bold}'SUCCESS'${nobold}${nocolor}" end_exit } ## Wrapper to call all necessary functions in one. run_installer() { set_globals "${@}" ## Set default values. set_default get_su_cmd not_as_root ## Set trap for common signals. set_trap ## Parse script name for wanted values. parse_name ## If account 'sysmaint' is in use and the user hasn't configured their own directory ## prefix and target user, default to target account 'user'. adjust_default_for_sysmaint_maybe ## Parse command-line options. parse_opt "${@}" ## Logging mechanism. log_term_and_file welcome get_os get_distro get_installer_version check_not_qubes_template get_system_stat need_reboot_check_first get_host_virtualizer_pkgs need_reboot_check_second get_independent_host_pkgs kernel_modules_check nested_virtualization_test secure_boot_test dkms_signing_key_enrollment kernel_modules_load kernel_modules_signed_only get_transfer_cmd torify_conn_maybe get_virtualization get_checkhash_cmd get_download_links main } if [ "$dist_installer_cli_was_sourced" = "true" ]; then true "INFO: Not running the install script because it was sourced by another script." else true "INFO: Running the install script because it was executed." run_installer "${@}" fi