#!/bin/bash ## vim:sw=2:sts=2:ts=2:et:spell: ## ## Copyright (C) 2022 ENCRYPTED SUPPORT LP ## See the file COPYING for copying conditions. ########################## ## BEGIN DEFAULT VALUES ## ########################## set -o errexit set -o nounset 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. " ## Version is commit based: https://github.com/Whonix/usability-misc version="14.0-1" me="${0##*/}" # shellcheck disable=SC2034 all_args="${*}" start_time="$(date +%s)" ## colors get_colors(){ ## Disable colors if some environment variables are present. if test -n "${NO_COLOR:-}" || test -n "${ANSI_COLORS_DISABLED:-}"; then # shellcheck disable=SC2034 nocolor="" bold="" nobold="" underline="" nounderline="" red="" green="" yellow="" #blue="" magenta="" cyan="" return 0 fi # shellcheck disable=SC2034 nocolor="\033[0m" bold="\033[1m" nobold="\033[22m" underline="\033[4m" nounderline="\033[24m" red="\033[31m" green="\033[32m" yellow="\033[33m" #blue="\033[34m" magenta="\033[35m" cyan="\033[36m" } ## 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" ######################## ## END DEFAULT VALUES ## ######################## ################ ## BEGIN MISC ## ################ ## 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 } dirname() { ## Usage: dirname "path" ## If '$1' is empty set 'dir' to '.', else '$1'. dir=${1:-.} ## Strip all trailing forward-slashes '/' from ## the end of the string. # ## "${dir##*[!/]}": Remove all non-forward-slashes ## from the start of the string, leaving us with only ## the trailing slashes. ## "${dir%%"${}"}": Remove the result of the above ## substitution (a string of forward slashes) from the ## end of the original string. dir=${dir%%"${dir##*[!/]}"} ## If the variable *does not* contain any forward slashes ## set its value to '.'. [ "${dir##*/*}" ] && dir=. ## Remove everything *after* the last forward-slash '/'. dir=${dir%/*} ## Again, strip all trailing forward-slashes '/' from ## the end of the string (see above). dir=${dir%%"${dir##*[!/]}"} ## Print the resulting string and if it is empty, ## print '/'. printf '%s\n' "${dir:-/}" } ## Capitalize only the first char of a string. capitalize_first_char(){ echo "${1:-}" | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1' } ## Block running as root. not_as_root(){ test "$(id -u)" = "0" && die 1 "Running as root." return 0 } ## Wrapper that supports su, sudo, doas root_cmd(){ test -z "${1:-}" && die 1 "Failed to pass arguments to root_cmd." 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 su root -s "${cmd}" -- "${@}" ;; sudo) log_run sudo -- "${@}" ;; doas) log_run doas -u root -- "${@}" ;; *) die 1 "Function root_cmd does not support sucmd '${sucmd}'." ;; esac } ## 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 if [ "$(echo "${addr_port}" | tr -cd "." | wc -c)" != 3 ] || [ "$(echo "${addr_port}" | tr -cd ":" | wc -c)" != 1 ] || [ "${port}" = "${addr}" ]; then die 2 "Invalid address:port assignment: ${addr_port}" fi is_integer "${port}" || die 2 "Invalid port '${port}', not an integer." if [ "${port}" -gt 0 ] && [ "${port}" -le 65535 ]; then true "Valid port '${port}'" else die 2 "Invalid port '${port}', not within range: 0-65535." fi for quad in $(printf '%s\n' "${addr}" | tr "." " "); do is_integer "${quad}" || die 2 "Invalid address '${addr}', '${quad}' is not an integer." if [ "${quad}" -ge 0 ] && [ "${quad}" -le 255 ]; then true "Valid quad '${quad}'" else die 2 "Invalid address '${addr}', '${quad}' not within range: 0-255." fi done } ## 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="" case ${os} in Linux*) if test -f /usr/share/kicksecure/marker; then distro="Kicksecure" distro_version=$(cat /etc/kicksecure_version) elif test -f /usr/share/whonix/marker; then distro="Whonix" distro_version=$(cat /etc/whonix_version) elif has lsb_release; then distro=$(lsb_release --short --description || lsb_release -sd) distro_version=$(lsb_release --short --release || lsb_release -sr) 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} ;; 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 log notice "Detected architecture: ${arch}." log notice "Detected system: ${os}." log notice "Detected distribution: ${distro}." log notice "Detected distribution version: ${distro_version}." ## This at last so the user can hopefully post his system info from the ## logs before the error below. if [ -z "${distro_version}" ]; then die 101 "Failed to find ditribution 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="$(echo "${distro_version}" | tr -d ".")" is_integer "${distro_version_without_dot}" || die 101 "Distro version without dot is still not a number: '${distro_version_without_dot}'" } ############## ## END MISC ## ############## ########################## ## BEGIN OPTION PARSING ## ########################## ## Begin parsing options. ## function should be called before the case statement to assign the options ## to a temporary variable 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 } ## Get arguments from options that require them. ## if option requires argument, check if it was provided, if true, assign the ## arg to the opt. If $arg was already assigned, and if valid, will use it for ## the key value ## usage: get_arg key get_arg(){ true "BEGIN 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 set_arg "${1}" "${arg}" ## 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 true "END get_arg(): '${*}'" } ## Single source to set opts, can later be used to print the options parsed ## usage: set_arg variable value set_arg(){ ## Check if variable had already a value assigned. Expanding to empty value ## is necessary to avoid eval failing because of unset parameter if variable ## didn't have a value assigned before. # shellcheck disable=SC2016 eval previous_value="$(printf '${%s:-}' "${1}")" ## Escaping quotes is needed because else it fails if the argument is quoted # shellcheck disable=SC2140 eval "${1}"="\"${2}\"" ## variable used for --getopt if test -z "${arg_saved:-}"; then arg_saved="${1}=\"${2}\"" else if test -z "${previous_value:-}"; then ## If didn't add to the end of the list arg_saved="${arg_saved}\n${1}=\"${2}\"" else ## If had, replace existing value. arg_saved="$(printf %s"${arg_saved}" | sed "s|^${1}=.*$|${1}=\"${2}\"|")" fi fi true "END set_arg(): '${*}'" } ## 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 } ## Check if argument is within range ## usage: ## $ range_arg key "1" "2" "3" "4" "5" ## $ range_arg key "a" "b" "c" "A" "B" "C" range_arg(){ key="${1:-}" eval var='$'"${key}" shift 1 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 } ## check if option has value, if not, error out ## this is intended to be used with required options check_opt_filled(){ key="${1}" eval val='$'"${key:-}" ! test -n "${val}" && die 2 "${key} is missing." } ######################## ## END OPTION PARSING ## ######################## ################### ## BEGIN LOGGING ## ################### ## 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." log(){ ## Avoid clogging output if log() is working alright. if test "${xtrace}" = "1"; then true "Removing xtrace for log() function." set +o xtrace fi log_type="${1}" ## capitalize log level log_type_up="$(echo "${log_type}" | tr "[:lower:]" "[:upper:]")" shift 1 ## escape printf reserved char '%' # shellcheck disable=SC2001 log_content="$(echo "${*}" | sed "s/%/%%/g")" ## 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}" ;; *) log bug "Unsupported log type: '${log_type}'." die 1 "Please report this bug." esac ## uniform log format log_color="${bold}${log_color}" log_full="${me}: [${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 case "${log_type}" in bug) #printf %s"${log_full:+$log_full }Please report this bug.\n" 1>&2 printf %s"${log_full}" 1>&2 return 0 ;; error) printf %s"${log_full}\n" 1>&2 return 0 ;; esac ## reverse importance order is required, excluding 'error' all_log_levels="warn notice info debug" if echo " ${all_log_levels} " | grep -o ".* ${log_level} " \ | grep -q " ${log_type}" then case "${log_type}" in warn) ## send warning to stdout and stderr printf %s"${log_full}\n" 1>&2 ;; *) printf %s"${log_full}\n" ;; esac fi if test "${xtrace}" = "1"; then set -o xtrace fi } ## 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 exiting now with code '${1}' because allow_errors is set." return 0 fi case "${1}" in 106|107) true ;; *) log error "Aborting installer." ;; esac exit "${1}" } ## Wrapper to log command before running to avoid duplication of code log_run(){ ## Extra spaces appearing when breaking log_run on multiple lines. command_without_extrarenous_spaces="$(echo "${@}" | tr -s " ")" if test "${dry_run}" = "1"; then log notice "Skipping: $ ${command_without_extrarenous_spaces}" return 0 fi if test "${ci}" != "1"; then ## CI expects no output from root_cmd() which calls log_run(). log notice "Executing: $ ${command_without_extrarenous_spaces}" fi "${@}" || return 1 } ## Useful to get runtime mid run to log easily get_elapsed_time(){ printf '%s\n' "$(($(date +%s) - start_time))" } ## Log elapsed time, the name explains itself. log_time(){ log info "Elapsed time: $(get_elapsed_time)s." } ## wrappet to end the exit trap. end_exit(){ ## Reset exit trap. trap - EXIT HUP INT QUIT ABRT ALRM TERM ## Kill tail PID. if test -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 # shellcheck disable=SC2086 kill -9 ${tail_pid} fi ## Exit with desired exit code. exit "${last_exit}" } ## Handle exit trap with line it failed and its exit code. handle_exit(){ true "BEGIN handle_exit() with args: $*" last_exit="${1}" line_number="${2:-0}" log_time ## Exit without errors. test "${last_exit}" = "0" && end_exit ## Virtual Machine expected start issues. test "${last_exit}" = "106" && end_exit ## Virtual Machine unexpected start issues. test "${last_exit}" = "107" && end_exit ## Exit with errors. log notice "Current script: ${0}" # shellcheck disable=SC3028 if test -n "${BASH_COMMAND:-}"; then # shellcheck disable=SC2039,3028,3054 log notice "Function executed: ${FUNCNAME[1]}" # shellcheck disable=SC2039,3028,3054 log notice "Command executed: ${BASH_COMMAND}" fi ## some shells have a bug that displays line 1 as LINENO if test "${line_number}" -gt 2; then log error "Error detected. Installer aborted." log error "No panic. Nothing is broken. Just some rare condition has been hit." log error "There is likely a solution for this problem." log error "Try again. If this issue is transient (not happening again) it can be safely ignored." log error "Please see ${guest_pretty} News and ${guest_pretty} User Help Forum." log error "If not already reported, please report this bug!" echo "" log error "Error occurred in 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 echo "" 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.)" echo "" else if [ "${last_exit}" -gt 128 ] && [ "${last_exit}" -lt 193 ]; then signal_code=$((last_exit-128)) signal_caught="$(kill -l "${signal_code}")" log error "Received ${signal_caught} signal." fi fi ## Print exit code. log error "Exit code: ${last_exit}." end_exit } ################# ## END LOGGING ## ################# ########################### ## BEGIN SCRIPT SPECIFIC ## ########################### ## Get necessary packages for your host system to be able to set the guest. get_host_pkgs(){ case "${os}" in Linux*) case "${distro}" in "Debian"*|"Tails"*|"Kicksecure"|"Whonix") true "${distro}" install_package_debian_common install_virtualbox_debian if test "${virtualbox_only}" = "1"; then exit 0 fi ;; "Linux Mint"*|"LinuxMint"*|"mint"*) true "${distro}" install_package_debian_common install_virtualbox_ubuntu if test "${virtualbox_only}" = "1"; then exit 0 fi ;; *"buntu"*) true "${distro}" if [ "${distro_version_without_dot}" -lt 2204 ]; then die 101 "Minimal ${distro} required version is 22.04, yours is ${distro_version}." fi install_package_debian_common install_virtualbox_ubuntu ;; "Arch"*|"Artix"*|"ArcoLinux"*) die 101 "Unsupported system." ;; "Fedora"*|"CentOS"*|"rhel"*|"Redhat"*|"Red hat") die 101 "Unsupported system." ;; *) die 101 "Unsupported system." ;; esac ;; "OpenBSD"*) die 101 "Unsupported system." ;; "NetBSD"*) die 101 "Unsupported system." ;; "FreeBSD"*|"HardenedBSD"*|"DragonFly"*) die 101 "Unsupported system." ;; *) die 101 "Unsupported system." ;; esac } get_independent_host_pkgs(){ ## Platform independent packages if has signify-openbsd; then ## fix Debian unconventional naming signify(){ signify-openbsd "${@}"; } fi has timeout || die 1 "Timeout utility is missing." has curl || install_pkg curl has rsync || install_pkg rsync ## 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 test -z "${nested_virt_tool:-}"; then ## No hard fail, not a requirement, good to have only. log warn "${underline}Nested Virtualization Test:${nounderline} Program to detect nested virtualization not found." else ## Check if we are a guest of virtualization. if root_cmd "${nested_virt_tool:-}" >/dev/null 2>&1; then nested_virtualization_detected=true log warn "${underline}Nested Virtualization Test:${nounderline} Nested virtualization detected. - Possibly a user mistake. - 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 see: ${url_version_domain}/wiki/Nested_Virtualization" fi fi if test -f /usr/share/qubes/marker-vm; then nested_virtualization_detected=true log warn "${underline}QubesOS Detection Test:${nounderline} Qubes detected. - The installer is not supposed to be run inside Qubes. - Useful only for development purposes. - It is recommended to use Qubes-Whonix instead." fi } ## Install package only if not installed already. install_pkg(){ pkgs="${*}" pkg_not_installed="" for pkg in ${pkgs}; do ## Test if package exists as a binary or a library, using different tools. # shellcheck disable=SC2086 if ${pkg_mngr_check_installed} "${pkg}"; then log info "${pkg} package already installed." continue fi if has "${pkg}"; then log info "${pkg} program already installed." continue fi pkg_not_installed="${pkg_not_installed} ${pkg}" done if test -n "${pkg_not_installed}"; then if test "${dry_run}" = "1"; then log notice "Skipping installing packages because dry_run is set:${pkg_not_installed}" return 0 fi log notice "Installing package(s):${pkg_not_installed}" log notice "Updating package list." # shellcheck disable=SC2086 root_cmd ${pkg_mngr_update} || die 1 "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: $command_without_extrarenous_spaces The user is advised to attempt to debug this with the following steps: 1. Run above command. 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." log notice "Installing package(s):${pkg_not_installed}." # shellcheck disable=SC2086 root_cmd ${pkg_mngr_install} ${pkg_not_installed} || die 1 "Failed to install package${pkg_not_installed}." ## Test if installation worked. 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(){ 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_not_installed} ${pkg}" fi done if test -n "${pkg_not_installed}"; then if test "${dry_run}" = "1"; then log error "Failed to locate package(s):${pkg_not_installed}. - ignoring because dry_run is set." else log error "Failed to locate package(s):${pkg_not_installed}." return 1 fi fi } ## Check if VM exists on VirtualBox check_vm_exists_virtualbox(){ case "${guest}" in whonix) ## Test if machine exists. workstation_exists=0 gateway_exists=0 if vboxmanage showvminfo \ "${guest_pretty}-Gateway-${interface_all_caps}" >/dev/null 2>&1 then gateway_exists=1 fi if vboxmanage showvminfo \ "${guest_pretty}-Workstation-${interface_all_caps}" >/dev/null 2>&1 then workstation_exists=1 fi ## Find discrepancies. if test "${workstation_exists}" = "0" && test "${gateway_exists}" = "1" then log warn "Gateway exists but Workstation doesn't." fi if test "${workstation_exists}" = "1" && test "${gateway_exists}" = "0" then log warn "Workstation exists but Gateway doesn't." fi ## If either one of the Guests exists, procede. if test "${workstation_exists}" = "1" || test "${gateway_exists}" = "1" then log notice "${underline}Existing VM Check:${nounderline} Virtual Machine(s) have been imported previously." if test "${reimport}" = "1"; then ## Remove Gateway if it exists. if test "${gateway_exists}" = "1"; then ## Do not remove gateway if import_only is set to workstation if test "${import_only}" = "workstation"; then log info "Although reimport is set, not deleting previously imported gateway because import_only is set to workstation." log info "If you wish to reimport both machines, do not specify import_only." else log warn "Deleting previously imported gateway because reimport is set." log_run vboxmanage unregistervm \ "${guest_pretty}-Gateway-${interface_all_caps}" --delete fi fi ## Remove Workstation if it exists. if test "${workstation_exists}" = "1"; then ## Do not remove workstation if import_only is set to gateway if test "${import_only}" = "gateway"; then log info "Although reimport is set, not deleting previously imported workstation because import_only is set to gateway." log info "If you wish to reimport both machines, do not specify import_only." else log warn "Deleting previously imported workstation because reimport is set." log_run vboxmanage unregistervm \ "${guest_pretty}-Workstation-${interface_all_caps}" --delete fi fi elif test -n "${import_only}"; then ## reimport was not set and the import_only is set to the same ## machine that already exists. if test "${gateway_exists}" = "1" && test "${import_only}" = "gateway" then die 1 "import_only was set to 'gateway', but it already exists and reimport was not set." elif test "${workstation_exists}" = "1" && test "${import_only}" = "workstation" then die 1 "import_only was set to 'workstation', but it already exists and reimport was not set." fi else ## One of the guests doesn't exist, but neither reimport nor ## import-only was not set. if test "${workstation_exists}" = "0" || test "${gateway_exists}" = "0" then test "${workstation_exists}" = "0" && die 1 "Workstation cannot be started because it doesn't exist, try '--import-only=workstation'." test "${gateway_exists}" = "0" && die 1 "Gateway cannot be started because it doesn't exist, try '--import-only=gateway'." fi ## VMs already exist, check if user wants to start them. log info "Checking if user wants to start Virtual Machine(s) now." check_guest_boot fi fi ;; kicksecure) if vboxmanage showvminfo \ "${guest_pretty}-${interface_all_caps}" >/dev/null 2>&1 then log notice "${underline}Existing VM Check:${nounderline} Virtual Machine(s) were imported previously." if test "${reimport}" = "1"; then ## If VMs exists and reimport is set, remove VMs as they are gonna ## be imported later by main. log warn "Deleting previously imported Virtual Machine(s) because reimport is set." log_run vboxmanage unregistervm \ "${guest_pretty}-${interface_all_caps}" --delete else ## VMs already exist, check if user wants to start them. log info "Checking if user wants to start Virtual Machine(s) now." check_guest_boot fi fi ;; esac } ## Check if VM exists using hypervisor tools. check_vm_exists(){ log info "Checking if Virtual Machine(s) have been already imported." case "${hypervisor}" in virtualbox) check_vm_exists_virtualbox ;; kvm) return 0 ;; esac } ## Check if guest should start or not check_guest_boot(){ log info "Virtual Machine(s) already exist." log notice "If you would like to redownload the image, read about --redownload (safe)." log notice "If you would like to reimport the image, read about --reimport (danger)." ## Skip guest boot if test "${no_boot}" = "1"; then log info "no_boot is set." log notice "${underline}VM Startup Check:${nounderline} User declined to start Virtual Machine(s) via command line." log notice "Virtual Machine(s) can be started manually." exit fi ## Default to start guest without interaction if test "${non_interactive}" = "1"; then log notice "${underline}VM Startup Check:${nounderline} VM start agreed by the user by setting non_interactive." start_guest exit fi ## Ask user to start guest or not case "${guest}" in whonix) log notice "Available guest: ${guest_pretty}-Gateway-${interface_all_caps}" log notice "Available guest: ${guest_pretty}-Workstation-${interface_all_caps}" ;; kicksecure) log notice "Available guest: ${guest_pretty}-${interface_all_caps}" ;; esac log notice "${bold}Question:${nobold} Do you want to start the ${guest_pretty} Virtual Machine(s) now? [y/n] (default: yes): " printf '%s' "Your answer: " read -r response log notice "User replied '${response}'." case ${response} in ""|[Yy]|[Yy][Ee][Ss]) log notice "${underline}VM Startup Check:${nounderline} User accepted to start Virtual Machine(s)." start_guest ;; *) log notice "${underline}VM Startup Check:${nounderline} User declined to start Virtual Machine(s)." log notice "The ${guest_pretty} Virtual Machine(s) can be started manually." ;; esac ## Last phase, should exit here every time. exit } import_guest(){ if test "${no_import}" = "1"; then log notice "Not importing guest because no_import is set." exit 0 fi check_vm_exists case "${hypervisor}" in virtualbox) import_virtualbox ;; kvm) import_kvm ;; esac } ## Import VirtualBox images import_virtualbox(){ log notice "Importing Virtual Machine(s)." ## Check how many systems to import. ## vsys 0: gateway ## vsys 1: workstation vm_purge="purgeme" case "${import_only}" in workstation) vbox_arg="--vsys 0 --eula accept --vmname ${vm_purge} --vsys 1 --eula accept" ;; gateway) vbox_arg="--vsys 0 --eula accept --vsys 1 --eula accept --vmname ${vm_purge}" ;; "") case "${guest}" in whonix) ## if importing whonix, import 2 virtual systems vbox_arg="--vsys 0 --eula accept --vsys 1 --eula accept" ;; kicksecure) vbox_arg="--vsys 0 --eula accept" ;; esac ;; esac ## import VirtualBox image # shellcheck disable=SC2086 log_run vboxmanage import \ "${directory_prefix}/${guest_file}.${guest_file_ext}" ${vbox_arg} || die 105 "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 test -n "${import_only}"; then log_run vboxmanage unregistervm ${vm_purge} --delete || die 1 "Failed to remove extraneous virtual system." fi log notice "You can now open the VirtualBox application to use ${guest_pretty}." } ## Import KVM images import_kvm(){ ## placeholder log notice "KVM import feature does not exist. Ending run." exit 0 } start_guest(){ case "${hypervisor}" in virtualbox) start_virtualbox ;; kvm) start_kvm ;; esac } ## Start the hypervisor with the desired guest start_virtualbox(){ log notice "Starting Virtual Machine(s)." case "${guest}" in whonix) log_run vboxmanage startvm \ "${guest_pretty}-Gateway-${interface_all_caps}" || virtualbox_start_failed log_run vboxmanage startvm \ "${guest_pretty}-Workstation-${interface_all_caps}" || virtualbox_start_failed ;; kicksecure) log_run vboxmanage startvm \ "${guest_pretty}-${interface_all_caps}" || virtualbox_start_failed ;; esac log notice "${underline}Virtual Machine Startup Result:${nounderline} ${green}${bold}SUCCESS${nobold}${nocolor}" } virtualbox_start_failed() { if [ "$nested_virtualization_detected" = "true" ] || [ "$virt_detection_success" = "false" ]; then die 106 "${underline}Virtual Machine Startup Result:${nounderline} ${red}${bold}FAIL${nobold}${nocolor} - The installer succeeded with download and import, but - failed to start the virtual machines. - This is likely happening due to the virtualization related warnings that have been reported above." fi die 107 "${underline}Virtual Machine Startup Result:${nounderline} ${red}${bold}FAIL${nobold}${nocolor} - The installer succeeded with download and import, but - failed to start the virtual machines. - See user documentation: ${url_version_domain}/wiki/VirtualBox/Troubleshooting" } ## https://stackoverflow.com/a/54239534 check_installed_debian(){ status="$(dpkg-query --show --showformat='${db:Status-Status}' "$1" 2>&1)" if test "$?" != 0 || test "$status" != "installed"; then return 1 fi return 0 } install_package_debian_common(){ pkg_mngr="apt-get" pkg_mngr_install="${pkg_mngr} install --yes --no-install-recommends" pkg_mngr_update="${pkg_mngr} update --yes --error-on=any" pkg_mngr_check_installed="check_installed_debian" ## 'dpkg --audit' does not return non-zero exit code on failure. dpkg_audit_output="$(dpkg --audit 2>&1)" if test -n "${dpkg_audit_output}"; then log error "Auditing dpkg database returned non-zero exit code." log error "Learn how to fix this issue at:" log error " https://www.kicksecure.com/wiki/Operating_System_Software_and_Updates#Broken_APT" return 1 fi if test "${virtualbox_only}" = "1"; then return 0 fi install_pkg torsocks install_signify signify-openbsd } ## End installation of VirtualBox on Debian and derived systems. install_virtualbox_debian_common_end(){ if ! has vboxmanage ; then if test "${dry_run}" = "1"; then log error "Failed to locate 'vboxmanage' program. - ignoring because dry_run is set." else die 1 "Failed to locate 'vboxmanage' program." fi fi virtualbox_linux_user_group="vboxusers" id_of_user="$(id --name --user)" || die 1 "Failed to run 'id --name --user'." if id -nG "$id_of_user" | grep -qw "${virtualbox_linux_user_group}\$"; then log info "'$id_of_user' is already a member of the Linux group 'vboxusers'." return 0 fi root_cmd adduser "$id_of_user" "${virtualbox_linux_user_group}" || die 1 "Failed to add user '$id_of_user' to group '${virtualbox_linux_user_group}'." } install_backports_and_fasttrack_repository_debian(){ backports_found="" backports_clearnet="deb.debian.org" backports_onion="2s4yqjx5ul6okpp3f2gaunr2syex5jgbfpfvhxxbbjwnrsvbk5v3qbid.onion" fasttrack_clearnet="fasttrack.debian.net" fasttrack_onion="5phjdr2nmprmhdhw4fdqfxvpvt363jyoeppewju2oqllec7ymnolieyd.onion" fasttrack_found="" apt_torified="" apt_onion="" for file in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do test -f "${file}" || continue if grep -v "#" "${file}" | grep -q \ -e "backports" then backports_found=1 fi if grep -v "#" "${file}" | grep -q \ -e "://${fasttrack_clearnet}" \ -e "://${fasttrack_onion}" then fasttrack_found=1 fi if grep -v "#" "${file}" | grep -q -F -e "tor://" -e "tor+"; then apt_torified=1 fi if grep -v "#" "${file}" | grep -q -F ".onion"; then apt_onion=1 fi done if test "${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. Not sure when this ## could happen in reality, but a hard fail is necessary as explained ## per the points above. log info "APT is supposed to be torified, checking if package apt-transport-tor is installed." test_pkg apt-transport-tor || die 1 "APT is supposed to be torified, but package apt-transport-tor is not installed. Please install." fi log info "Installing packages required for backports and fasttrack repository." install_pkg apt-transport-https ca-certificates fasttrack-archive-keyring if [ "${backports_found}" = "1" ] && [ "${fasttrack_found}" = "1" ]; then log info "Skipped adding additional APT repositories because backports and fasttrack was already found." return 0 fi if test "${backports_found}" = "1"; then log info "Skipped adding Backports because it was already found." else ## If user has onion repositories configured, prefer it. if test "${apt_onion}"; then log notice "Adding Backports onion repository." backports_url="tor+http://${backports_onion}" ## If user has torified repositories configured, prefer it. elif test "${apt_torified}"; then log notice "Adding Backports torified clearnet repository." backports_url="tor+https://${backports_clearnet}" ## If user doesn't have torified repositories, use clearnet one. else log notice "Adding Backports clearnet repository." backports_url="https://${backports_clearnet}" fi log notice "backports URL: ${backports_url}" log notice "Adding backports repository to /etc/apt/sources.list.d/backports.list" echo "deb ${backports_url}/debian/ bullseye-backports main contrib non-free" | \ root_cmd tee /etc/apt/sources.list.d/backports.list || die 1 "Failed to write to /etc/apt/sources.list.d/backports.list." fi if test "${fasttrack_found}" = "1"; then log info "Skipped adding Fasttrack because it was already found." else ## If user has onion repositories configured, prefer it. if test "${apt_onion}"; then log notice "Adding Fasttrack's onion repository." fasttrack_url="tor+http://${fasttrack_onion}" ## If user has torified repositories configured, prefer it. elif test "${apt_torified}"; then log notice "Adding Fasttrack's torified clearnet repository." fasttrack_url="tor+https://${fasttrack_clearnet}" ## If user doesn't have torified repositories, use clearnet one. else log notice "Adding Fasttrack's clearnet repository." fasttrack_url="https://${fasttrack_clearnet}" fi log notice "Fasttrack URL: ${fasttrack_url}" log notice "Adding Fasttrack repository to /etc/apt/sources.list.d/fasttrack.list" echo "deb ${fasttrack_url}/debian/ bullseye-fasttrack main contrib non-free" | \ root_cmd tee /etc/apt/sources.list.d/fasttrack.list || die 1 "Failed to write to /etc/apt/sources.list.d/fasttrack.list." fi } ## Install VirtualBox on Debian install_virtualbox_debian(){ has_vboxmanage=0 has_linux_headers=0 has_virtualbox_qt=0 linux_headers="linux-headers-$(dpkg --print-architecture)" has vboxmanage && has_vboxmanage=1 test_pkg "${linux_headers}" 2>/dev/null && has_linux_headers=1 has virtualboxvm && has_virtualbox_qt=1 if test "${has_vboxmanage}" = "1" && test "${has_linux_headers}" = "1"; then log info "vboxmanage and ${linux_headers} are already installed." install_virtualbox_debian_common_end return 0 fi if test "${has_linux_headers}" != "1"; then log notice "Preparing to install ${linux_headers}" install_pkg "${linux_headers}" fi if test "${has_virtualbox_qt}" != "1"; then log notice "Preparing to install VirtualBox-QT" install_backports_and_fasttrack_repository_debian install_pkg virtualbox-qt fi install_virtualbox_debian_common_end } ## Install VirtualBox on Ubuntu install_virtualbox_ubuntu(){ if has vboxmanage && test_pkg linux-headers-generic; then install_virtualbox_debian_common_end return 0 fi install_pkg virtualbox linux-headers-generic install_virtualbox_debian_common_end } ## Helper to install signify on different systems. install_signify(){ pkg_name="${1:-signify}" has "${pkg_name}" && return 0 install_pkg "${pkg_name}" } ## Test if user accepts the license, if not, abort. check_license(){ if [ "${non_interactive}" = "1" ]; then log notice "License Check: Agreed by the user by setting non_interactive." return 0 fi log notice "The license will be shown in 5 seconds." log notice "(Use -n or --non-interactive for non-interactive mode.)" test "${dry_run}" != "1" && sleep 5 ## 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} User agreed with 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} User agreed with the license." ;; *) printf '%s\n' "${license}" printf '%s' "Do you agree with the license(s)? (yes/no): " read -r license_agreement case "${license_agreement}" in [yY][eE][sS]) log notice "${underline}License Check:${nounderline} User agreed with the license." ;; *) log warn "${underline}License Check:${nounderline} User replied '${license_agreement}'." return 1 ;; esac ;; esac } get_su_cmd(){ while true; do has sudo && sucmd=sudo && break has doas && sucmd=doas && break has su && sucmd=su && break test -z "${sucmd}" && { die 1 "Failed to find program to run as another user." } case "${sucmd}" in sudo) :;; *) log warn "Using sucmd '$sucmd'. Consider installing sudo instead to cache your passwords instead of typing them every time.";; esac done log info "Testing root_cmd function" root_cmd echo "test" || die 1 "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 "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 info "User system is using credential caching: No" return 0 fi log info "User system is using credential caching: 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 "Unexpected non-empty output for sudo test in normal mode." fi fi } get_checkhash_cmd(){ while true; do has sha512sum && checkhash="sha512sum" && break has shasum && checkhash="shasum -a 512" && break has sha512 && checkhash="sha512" && break has openssl && checkhash="openssl dgst -sha512 -r" && break has digest && checkhash="digest -a sha512" && break test -z "${checkhash}" && { die 1 "Failed to find program that checks SHA512 hash sum." } done } get_transfer_cmd(){ ## curl|rsync transfer_utility=rsync case "${transfer_utility}" in rsync) rsync=1 ;; curl) 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_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 } ## Get utilities from a pool of known utilities and use the first one found. get_utilities(){ get_su_cmd if test "${virtualbox_only}" = "1"; then return 0 fi get_checkhash_cmd get_transfer_cmd } ## Set default traps set_trap(){ log info "Current PATH: ${PATH}" ## Sometimes ps is not available, default to sh. curr_shell="$(cat /proc/$$/comm)" ## Get current shell from current process ## If the process if the file name, get its shell from shebang ## sometimes the process name is the base name of the script with some ## missing letters. case "${0##*/}" in ${curr_shell}*) ## necessary glob because /bin/sh makes the file name ## appear with one letter less shebang="$(head -1 "${0}")" curr_shell="${shebang##*/}" ;; esac log info "Current shell: '${curr_shell}'" case "${curr_shell}" in *bash|*ksh|*zsh) # shellcheck disable=SC2039 if test "${curr_shell}" = "bash"; then # shellcheck disable=SC2039,3040 set -o errtrace fi # shellcheck disable=SC2039,3047 trap 'handle_exit $? ${LINENO:-}' ERR ;; esac trap 'handle_exit $? ${LINENO:-}' EXIT HUP INT QUIT ABRT ALRM TERM } ## Check if system status is supported get_system_stat(){ if [ "${arch}" != "x86_64" ]; then die 101 "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 test "${virtualbox_only}" = "1"; then # shellcheck disable=SC2034 min_ram_mb="1024" fi ## 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 log warn "${underline}Minimum RAM Check:${nounderline} Your systems has a low amount of total RAM: ${total_mem} MB. See:" if test "${virtualbox_only}" != "1"; then log warn " ${url_version_domain}/wiki/RAM" fi fi if test "${virtualbox_only}" = "1"; then return 0 fi df_output="$(df --output=avail -BG "${directory_prefix}")" free_space_available="$(echo "$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 test "${dev}" = "1"; then free_space_required="1" fi if [ "${free_space_available}" -lt "$free_space_required" ]; then die 101 "\ ${underline}Free Disk Space Check:${nounderline} Failed! Insufficient free disk space! - available: ${free_space_available}G - required : ${free_space_required}G 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 } ## Sanity checks that should be called before execution of main pre_check(){ get_os get_system_stat get_host_pkgs get_independent_host_pkgs nested_virtualization_test ## onion=1 -- Always torify onion connections. ## onion=* -- Only torify clearnet if SOCKS proxy is specified. case "${onion}" in 1) torify_conn;; *) test -n "${socks_proxy}" && torify_conn;; esac ## Functions below are difficult to emulate if test "${dry_run}" = "1"; then log info "Skipping rest or pre_check() because dry_run is set." return 0 fi get_virtualization } ## Generate SOCKS credentials for stream isolation get_proxy_cred(){ test "${transfer_utility}" != "curl" && return 0 test -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(){ 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="UTW_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" # shellcheck disable=SC2086 actual_response_header="$(eval ${cmd_check_proxy} 2>&1 | head -1 | 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 "${actual_response_header}" in *"${expected_response_header}"*) log info "Received header:" log info "'${actual_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' - Received header: '${actual_response_header}'" return 1 esac } ## Set transference proxy depending on transfer utility. ## usage: set_transfer_proxy ${proxy} set_transfer_proxy(){ 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_prefix="" transfer_proxy_suffix="--proxy socks5h://${1}" ;; 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(){ 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 mail 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 installing 'tor' 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 test -n "${socks_proxy:-}"; then proxy="${socks_proxy}" set_transfer_proxy "${proxy}" elif test -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 "\ Cannot connect to Tor SOCKS proxy. - This issue is most likely not caused by this installer. - This issue is likely caused by a missing software, configuration or network issues. Note, that for torification of connections an already functional Tor connection is required. - A) An already installed and running system Tor (little-t-tor). Or, - B) An already installed and running TBB (Tor Browser Bundle). This installer cannot help to set up a functional Tor connection. This must be done by the system administrator. When manually running above cmd_check_proxy it needs to include the expected_response_header." } ## Set version by user input or by querying the API get_version(){ log notice "Detecting guest version..." if test -n "${guest_version:-}"; then log notice "User defined, dry_run or dev version already configured. Autodetection form API not required." return 0 fi log info "Acquiring guest version from API..." log info "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}" ## this is necessary because we log will not be printed as the command is ## assigned to a variable at 'raw_version=$()'. if test "${dry_run}" = "1"; then # shellcheck disable=SC2086 log_run ${cmd_raw_version} return 0 fi # shellcheck disable=SC2086 raw_version="$(${cmd_raw_version})" ## First line only. guest_version="$(echo "$raw_version" | head -n 1)" # shellcheck disable=SC2046,SC2086 guest_version="$(printf '%s\n' "${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 "Invalid guest version: contains unexpected characters." ## block string containing more than 12 chars [ "${#guest_version}" -le 12 ] || die 1 "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(){ size="${1}" url="${2}" ## Round is only used to get a different password every time. test -z "${round:-}" && round=10 round=$((round+1)) 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}" download_opt="${transfer_speed_optimization_opt} ${transfer_size_opt} ${transfer_size_large_file}" ;; *) log bug "Missing size option for get_file()." ;; esac # shellcheck disable=SC2046,SC2086 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}" log notice "Downloading ${url:-}." # shellcheck disable=SC2086 log_run ${download_opt_full} || return 1 } ## 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 "Downloads 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}.sha512sums.sig" || return 1 get_file small "${url_guest_file}.sha512sums" || return 1 log notice "Checking if files exists locally." if test "${dry_run}" = "1" || { test -f "${directory_prefix}/${guest_file}.${guest_file_ext}" && test -f "${directory_prefix}/${guest_file}.sha512sums.sig" && test -f "${directory_prefix}/${guest_file}.sha512sums" } then log_time log_run touch "${download_flag}" else die 103 "Failed to download files." fi } ## https://en.wikipedia.org/wiki/X86_virtualization get_virtualization(){ ## 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:=}" in vmx) brand=intel;; svm) brand=amd;; 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 "${underline}Virtualization Support Test:${nounderline} Your CPU supports virtualization: ${brand}: ${virt_flag}." virt_detection_success=true return 0 ;; "") log warn "${underline}Virtualization Support Test:${nounderline} No virtualization flag found." virt_detection_success=false ;; *) log warn "${underline}Virtualization Support Test:${nounderline} Unknown virtualization flag: ${virt_flag}." virt_detection_success=false ;; esac if [ "$virt_detection_success" = "false" ]; then log warn "(The virtualization detection is imperfect and might show a false negative warning.)" if [ "${hypervisor}" = "virtualbox" ]; then log warn "See user documentation on how to enable virtualization:" log warn " ${url_version_domain}/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 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) 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) 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 test -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 test -e /dev/kvm; then log notice "Device /dev/kvm exists" verdict 0 else 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 test ! -r "${msr}"; then root_cmd modprobe msr || die 1 "Could not add module 'msr' to the kernel." fi if root_cmd test ! -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 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(){ if test "${virtualbox_only}" = "1"; then return 0 fi ## 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}" ;; esac ## ${variable+string} means use string as value if variable is not empty. ## get download links by mirror of choice. case "${mirror}" in "") ## no mirror chosen, use default values. ## Force mirror to be used. site_download_clearnet="mirrors.dotsrc.org/${guest}" site_download_onion="${curl+download.}${site_onion}${rsync+/${guest}}" ;; 0) site_download_clearnet="${curl+download.}${site_clearnet}${rsync+/${guest}}" site_download_onion="${curl+download.}${site_onion}${rsync+/${guest}}" ;; 1) site_download_clearnet="mirrors.dotsrc.org/${guest}" site_download_onion="dotsrccccbidkzg7oc7oj4ugxrlfbt64qebyunxbrgqhxiwj3nl6vcad.onion/${guest}" ;; 2) site_download_clearnet="mirror.koljasagorski.de/${guest}" site_download_onion="" ;; 3) site_download_clearnet="quantum-mirror.hu/mirrors/pub/${guest}" site_download_onion="" ;; 4) ## does not have TLS for Rsync ## When using rsync-ssl, this message appears: ## rsync: did not see server greeting die 1 "Mirror ${mirror} doesn't support TLS for rsync." site_download_clearnet="ftp.icm.edu.pl/pub/Linux/dist/${guest}" site_download_onion="" ;; 5) die 1 "Mirror ${mirror} is currently using an invalid certificate." site_download_clearnet="mirror.gigenet.com/${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. test -n "${site_download_onion}" || die 1 "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." test "${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 test "${testers}" = "1"; then url_version_template="VersionTesters" else url_version_template="VersionNew" fi ## image signer signify_key="${adrelanos_signify}" ## url directory to find files of the selected hypervisor url_domain="${url_download}/ova" ## image file extension guest_file_ext="ova" ## function to call when importing guest ;; kvm) if test "${testers}" = "1"; then die 1 "KVM does not have testers version." #url_version_template="" else url_version_template="Version_KVM" fi ## image signer signify_key="${hulahoop_signify}" ## 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(){ if test "${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. test "${download_msg_done:-}" = "1" && return 0 download_msg_done=1 ## Download if redownload option is set. log notice "Downloading files because redownload is set." return 0 elif test -f "${download_flag:-}"; then ## Do not download if flag exists. log notice "Skipping download because flag exists: ${download_flag}." return 1 fi ## Download as no obstacles prohibit it. return 0 } ## Check signature of signed checksum. check_signature(){ log notice "Signify signature:\n${signify_key}" log notice "Verifying file: ${directory_prefix}/${guest_file}.sha512sums." echo "${signify_key}" | signify -V -p - \ -m "${directory_prefix}/${guest_file}.sha512sums" || return 1 log notice "Signature matches." } ## Check hash sum. check_hash(){ log notice "Checking SHA512 checksum: ${directory_prefix}/${guest_file}.${guest_file_ext}" ${checkhash} "${directory_prefix}/${guest_file}.sha512sums" || return 1 log notice "Checksum matches." } ## Check integrity of files check_integrity(){ if test "${dry_run}" = "1"; then log notice "Skipping integrity checks because dry_run is set." return 0 fi log notice "Doing integrity checks." check_signature || die 104 "Failed to verify signature." check_hash || die 104 "Failed hash checking." } ## Self explanatory name, make everything after option parsing. main(){ ############### ## BEGIN PRE ## ############### log notice "Saving user log to: '${log_file_user}'." if test -f "${log_file_debug}"; then log notice "Saving debug log to: '${log_file_debug}'." fi log info "Starting main function." guest_pretty="$(capitalize_first_char "${guest}")" interface_pretty="$(capitalize_first_char "${interface}")" interface_all_caps="$(echo "${interface}" | tr "[:lower:]" "[:upper:]")" case "${hypervisor}" in virtualbox) hypervisor_pretty="VirtualBox" ;; kvm) hypervisor_pretty="KVM" ;; *) hypervisor_pretty="${hypervisor}" ;; esac log info "Parsed options:" for item in ${arg_saved}; do log info " ${item}" done if test "${virtualbox_only}" = "1"; then log notice "${underline}Installer:${nounderline} ${bold}VirtualBox Installer${nobold}" else log notice "${underline}Installer:${nounderline} ${bold}${guest_pretty} ${interface_pretty} for ${hypervisor_pretty} Installer${nobold}" fi if test "${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 "User disagreed with the license." pre_check log_time ## Check if VM were already imported, if true then ask to start if test "${redownload}" != "1"; then check_vm_exists fi ############# ## 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 test -n "${guest_version}"; then guest_file="${guest_pretty}-${interface_all_caps}-${guest_version}" download_flag="${directory_prefix}/${guest_file}.${guest_file_ext}.flag" fi if should_download; then if test "${dry_run}" != "1"; then log notice "Testing internet connection..." 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}" log info "Executing: $ ${cmd_check_internet}" ${cmd_check_internet} >/dev/null || die $? "Cannot connect to ${url_origin}, perhaps no internet?" log notice "Connection to ${url_origin} succeeded." fi get_version "${url_version}" log notice "Version: ${guest_version}." guest_file="${guest_pretty}-${interface_all_caps}-${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 "Failed to download files." fi else log notice "Version: ${guest_version}." fi ################## ## END DOWNLOAD ## ################## ########################################## ## BEGIN VERIFICATION, IMPORT AND START ## ########################################## check_integrity 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...] -g, --guest= Set guest. Options: kicksecure, whonix (default) -u, --guest-version= Set guest version, else query from API. -i, --interface= Set interface. Options: cli, xfce (default) -m, --hypervisor= Set virtualization. Options: kvm, virtualbox (default) -o, --onion Download files over onion. -s, --socks-proxy= Set TCP SOCKS proxy for onion client connections. (default: TOR_SOCKS_HOST:TOR_SOCKS_PORT, if not set, try TBB (Tor Browser Bundle) proxy at port 9150, else try system Tor (little-t-tor) proxy at port 9050.) -l, --log-level= Set log level. Options: debug, info, notice (default), warn, error. -V, --version Show version and quit. -h, --help Show help for commands and quit. Developer options: --no-show-errors Don't print errors. --allow-errors Don't exit on errors. Dirty mode. --mirror= Set mirror according to number index. Choose closest mirror to you if not using onion for faster downloads. Not that if not choosing mirror, defaults to whonix.org for plainnet and dotsrc.org correspondent for onion. Mirrors index: 0 [DE] download.whonix.org (onion available) 1 [DK] mirrors.dotsrc.org (onion available) 2 [DE] mirror.koljasagorski.de 3 [HU] quantum-mirror.hu 4 [PL] ftp.icm.edu.pl 5 [US] mirrors.gigenet.com --redownload Redo the download of the guest instead of stopping if it was already successfully downloaded. --import-only= Choose the missing VM to import when the guest is whonix. Options: workstation and gateway'. --no-import Don't import guest. Default is to import. --reimport Redo the import of the guest to the hypervisor. Will try to import already existing files if found. This option requires --destroy-existing-guest as pledge. -k, --no-boot Don't boot guest at the end of run. Default is to start. --destroy-existing-guest Required pledge for --reimport. -P, --directory-prefix= Set absolute path to directory prefix to save files to. The directory must already exist and have read and write capabilities for the calling user. If you change this directory, but the previously downloaded files are not changed to the new directory, the download start again. \$HOME/installer-dist-download (default). -n, --non-interactive Set non-interactive mode, license will be accepted. -D, --dev Set development mode. Empty image to download --ci Set CI mode. Test running in CI. --testers Set testers version to be downloaded. -d, --dry-run Simulated run, log commands to info level without executing. --virtualbox-only Only downloads and installs VirtualBox. -t, --getopt Show parsed options and quit. File name: The default file name is installer-dist. Some basic options can be set as the file name if they follow the format 'guest-installer-interface'. Anything different than the default name or the allowed format is rejected. Command line options override the file name definition. " exit "${1:-0}" } ## Set default values for variables. set_default(){ guest_pretty="" get_colors xtrace="" directory_prefix="" set_arg directory_prefix "${HOME}/installer-dist-download" guest="" set_arg guest whonix hypervisor="" set_arg hypervisor virtualbox interface="" set_arg interface xfce log_level="" set_arg log_level notice guest_version="" socks_proxy="" onion="" non_interactive="" dev="" dry_run="" getversion="" getopt="" ci="" no_import="" no_boot="" redownload="" reimport="" import_only="" destroy_existing_guest="" testers="" allow_errors="" no_show_errors="" mirror="" virtualbox_only="" } ## Parse script name. parse_name(){ ## if using default file name, ignore the rest test "${me}" = "installer-dist" && return 0 ## check if file name is valid case "${me}" in whonix-installer-xfce | whonix-installer-cli | \ kicksecure-installer-xfce | kicksecure-installer-cli ) log info "Valid script name '${me}', using its name to set options." ;; virtualbox-installer ) log info "Valid script name '${me}', using its name to set options." set_arg virtualbox_only 1 set_arg 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 " installer-dist and use command-line options instead." return 2 esac ## assign values according to script name set_arg guest "$(echo "${me}" | cut -d "-" -f1)" set_arg interface "$(echo "${me}" | cut -d "-" -f3)" #set_arg hypervisor "$(echo "${me}" | cut -d "-" -f3)" log info "Assigned guest and interface according to script name ${me}." return 0 } ## Parse command-line options. parse_opt(){ #test -z "${1:-}" && usage 2 while true; do begin_optparse "${1:-}" "${2:-}" || break # shellcheck disable=SC2034 case "${opt}" in P|directory-prefix) get_arg directory_prefix ;; o|onion) set_arg onion 1 ;; s|socks-proxy) get_arg socks_proxy ;; l|log-level) get_arg log_level ;; g|guest) get_arg guest ;; u|guest-version) get_arg guest_version ;; i|interface) get_arg interface ;; m|hypervisor) get_arg hypervisor ;; mirror) get_arg mirror ;; import-only) get_arg import_only ;; allow-errors) set_arg allow_errors 1 ;; no-show-errors) set_arg no_show_errors 1 ;; redownload) set_arg redownload 1 ;; reimport) set_arg reimport 1 ;; destroy-existing-guest) set_arg destroy_existing_guest 1 ;; n|non-interactive) set_arg non_interactive 1 ;; k|no-boot) set_arg no_boot 1 ;; no-import) set_arg no_import 1 ;; D|dev) set_arg dev 1 ;; testers) set_arg testers 1 ;; t|getopt) set_arg getopt 1 ;; ci) set_arg ci 1 ;; d|dry-run) set_arg dry_run 1 ;; virtualbox-only) set_arg virtualbox_only 1 set_arg hypervisor virtualbox ;; V|version) set_arg getversion 1 ;; h|help) usage 0 ;; *) die 2 "Invalid option: '${opt_orig}'." ;; esac shift "${shift_n:-1}" done ## Put last newline after last argument of arg_saved. arg_saved="$(printf %s"${arg_saved}\n")" ## Test if options are valid range_arg log_level error warn notice info debug if [ "${log_level}" = "debug" ]; then xtrace=1 set -o xtrace fi range_arg guest whonix kicksecure range_arg interface cli xfce range_arg hypervisor kvm virtualbox range_arg import_only workstation gateway if test "${guest}" != "whonix" && test -n "${import_only}"; then die 1 "The option import_only can only be set when the guest is whonix." fi test -n "${mirror}" && range_arg mirror 0 1 2 3 4 5 test -n "${socks_proxy}" && is_addr_port "${socks_proxy}" if test "${reimport}" = "1"; then if test -z "${destroy_existing_guest}"; then log error "The option --reimport requires --destroy-existing-guest." log error "The option --destroy-existing-guest was not set." die 1 "User mistake, read the documentation to understand how to use the options." fi fi if test -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 -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}'." mkdir -p "${directory_prefix}" || die 1 "Failed to created directory: '${directory_prefix}'." test -w "${directory_prefix}" || die 1 "Directory isn't writable: '${directory_prefix}'." test -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 -d "${log_dir_main}/1"; then log_dir_cur="${log_dir_main}/1" else last_run_integer="$(echo "${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. mkdir -p "${log_dir_cur}" cp "${0}" "${log_dir_cur}" touch "${log_file_user}" fi # shellcheck disable=SC2194 if test "${getopt}" = "1"; then printf '%s\n' "${arg_saved}" exit 0 fi if test "${getversion}" = "1"; then printf '%s\n' "${me} ${version}" exit 0 fi if test "${dev}" = "1"; then if test -z "${guest_version}"; then log notice "Setting dev software version." guest_version="16.0.4.2" fi fi if test "${dry_run}" = "1"; then if test -z "${guest_version}"; then log notice "dry_run set, commands will be printed but not executed." log notice "Using simulated software version because of dry_run." guest_version="16.0.4.2" fi fi if test "${allow_errors}" = "1"; then set +o errexit # shellcheck disable=SC2039,3040 test "${curr_shell}" = "bash" && set +o errtrace fi log info "Option parsing ended." } ## Logging mechanism. log_term_and_file(){ ## Discover if terminal is attached to stdout if ! test -t 1; then log warn "Output is not being sent to the terminal because terminal is not connected to stdout." return 0 fi ## Send fd3 to the terminal true "exec 3>$(tty)" exec 3>>"$(tty)" ## Current problem is that command-line option is necessary to hide stderr ## by sending it to /dev/null, while the desired method would be to avoid ## this workaround and simply be able to redirect with 2>/dev/null. ## Send f21/stdout to log file. true "exec 1>>${log_file_user}" exec 1>>"${log_file_user}" ## Send fd2/stderr to log file by default. true "exec 2>>${log_file_user}" exec 2>>"${log_file_user}" ## If no_show_errors is set, send fd2/stderr to /dev/null. if test "${no_show_errors}" = "1"; then true "exec 2>/dev/null" exec 2>/dev/null fi ## Bash has built-in feature to redirect xtrace to the specified file. if test "${curr_shell}" = "bash"; then # shellcheck disable=SC2039 true "exec 9>>${log_file_debug}" exec 9>>"${log_file_debug}" export BASH_XTRACEFD=9 set -o xtrace xtrace=1 touch "${log_file_debug}" fi ## Copy output of log file to fd3 and send process to the background. if test -f "${log_file_debug}" && test "${log_level}" = "debug"; then true "tail -qf ${log_file_debug} ${log_file_user} >&3 &" tail -qf "${log_file_debug}" "${log_file_user}" >&3 & else true "tail -f ${log_file_user} >&3 &" tail -f "${log_file_user}" >&3 & fi ## Get background job PID. tail_pid="$!" } ## Wrapper to call all necessary functions in one. run_installer(){ ## Set default values. set_default not_as_root ## Set trap for common signals. set_trap ## Parse script name for wanted values. parse_name ## Parse command-line options. parse_opt "${@}" ## Logging mechanism. log_term_and_file get_utilities get_download_links ## Start main function. main } ## Run the install script. run_installer "${@}"