diff --git a/setup/firstuser.sh b/setup/firstuser.sh index d24fc37..71264f3 100644 --- a/setup/firstuser.sh +++ b/setup/firstuser.sh @@ -6,7 +6,7 @@ if [ -z "`tools/mail.py user`" ]; then # If we didn't ask for an email address at the start, do so now. if [ -z "$EMAIL_ADDR" ]; then # In an interactive shell, ask the user for an email address. - if [ -z "$NONINTERACTIVE" ]; then + if [ -z "${NONINTERACTIVE:-}" ]; then input_box "Mail Account" \ "Let's create your first mail account. \n\nWhat email address do you want?" \ diff --git a/setup/functions.sh b/setup/functions.sh index 75c6821..1a74edf 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -1,3 +1,9 @@ +# Turn on "strict mode." See http://redsymbol.net/articles/unofficial-bash-strict-mode/. +# -e: exit if any command unexpectedly fails. +# -u: exit if we have a variable typo. +# -o pipefail: don't ignore errors in the non-last command in a pipeline +set -euo pipefail + function hide_output { # This function hides the output of a command unless the command fails # and returns a non-zero exit code. @@ -5,11 +11,14 @@ function hide_output { # Get a temporary file. OUTPUT=$(tempfile) - # Execute command, redirecting stderr/stdout to the temporary file. + # Execute command, redirecting stderr/stdout to the temporary file. Since we + # check the return code ourselves, disable 'set -e' temporarily. + set +e $@ &> $OUTPUT + E=$? + set -e # If the command failed, show the output that was captured in the temporary file. - E=$? if [ $E != 0 ]; then # Something failed. echo @@ -75,7 +84,7 @@ function get_publicip_from_web_service { # # Pass '4' or '6' as an argument to this function to specify # what type of address to get (IPv4, IPv6). - curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null + curl -$1 --fail --silent --max-time 15 icanhazip.com 2>/dev/null || /bin/true } function get_default_privateip { @@ -131,11 +140,10 @@ function get_default_privateip { fi echo $address - } function ufw_allow { - if [ -z "$DISABLE_FIREWALL" ]; then + if [ -z "${DISABLE_FIREWALL:-}" ]; then # ufw has completely unhelpful output ufw allow $1 > /dev/null; fi @@ -154,10 +162,13 @@ function input_box { # input_box "title" "prompt" "defaultvalue" VARIABLE # The user's input will be stored in the variable VARIABLE. # The exit code from dialog will be stored in VARIABLE_EXITCODE. + # Temporarily turn off 'set -e' because we need the dialog return code. declare -n result=$4 declare -n result_code=$4_EXITCODE + set +e result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3") result_code=$? + set -e } function input_menu { @@ -167,8 +178,10 @@ function input_menu { declare -n result=$4 declare -n result_code=$4_EXITCODE local IFS=^$'\n' + set +e result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3) result_code=$? + set -e } function wget_verify { diff --git a/setup/munin.sh b/setup/munin.sh index 2529ba4..c581c6d 100755 --- a/setup/munin.sh +++ b/setup/munin.sh @@ -29,7 +29,7 @@ address 127.0.0.1 # send alerts to the following address contacts admin -contact.admin.command mail -s "Munin notification ${var:host}" administrator@$PRIMARY_HOSTNAME +contact.admin.command mail -s "Munin notification \${var:host}" administrator@$PRIMARY_HOSTNAME contact.admin.always_send warning critical EOF diff --git a/setup/preflight.sh b/setup/preflight.sh index 7466b85..d087efe 100644 --- a/setup/preflight.sh +++ b/setup/preflight.sh @@ -41,7 +41,7 @@ if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then fi # Check that tempfs is mounted with exec -MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts) +MOUNTED_TMP_AS_NO_EXEC=$(grep "/tmp.*noexec" /proc/mounts || /bin/true) if [ -n "$MOUNTED_TMP_AS_NO_EXEC" ]; then echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec" exit diff --git a/setup/questions.sh b/setup/questions.sh index 3d227d8..3ad7eea 100644 --- a/setup/questions.sh +++ b/setup/questions.sh @@ -1,4 +1,4 @@ -if [ -z "$NONINTERACTIVE" ]; then +if [ -z "${NONINTERACTIVE:-}" ]; then # Install 'dialog' so we can ask the user questions. The original motivation for # this was being able to ask the user for input even if stdin has been redirected, # e.g. if we piped a bootstrapping install script to bash to get started. In that @@ -25,8 +25,8 @@ if [ -z "$NONINTERACTIVE" ]; then fi # The box needs a name. -if [ -z "$PRIMARY_HOSTNAME" ]; then - if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then +if [ -z "${PRIMARY_HOSTNAME:-}" ]; then + if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then # We recommend to use box.example.com as this hosts name. The # domain the user possibly wants to use is example.com then. # We strip the string "box." from the hostname to get the mail @@ -86,7 +86,7 @@ fi # If the machine is behind a NAT, inside a VM, etc., it may not know # its IP address on the public network / the Internet. Ask the Internet # and possibly confirm with user. -if [ -z "$PUBLIC_IP" ]; then +if [ -z "${PUBLIC_IP:-}" ]; then # Ask the Internet. GUESSED_IP=$(get_publicip_from_web_service 4) @@ -105,11 +105,11 @@ if [ -z "$PUBLIC_IP" ]; then PUBLIC_IP=$GUESSED_IP fi - if [ -z "$PUBLIC_IP" ]; then + if [ -z "${PUBLIC_IP:-}" ]; then input_box "Public IP Address" \ "Enter the public IP address of this machine, as given to you by your ISP. \n\nPublic IP address:" \ - $DEFAULT_PUBLIC_IP \ + ${DEFAULT_PUBLIC_IP:-} \ PUBLIC_IP if [ -z "$PUBLIC_IP" ]; then @@ -121,27 +121,27 @@ fi # Same for IPv6. But it's optional. Also, if it looks like the system # doesn't have an IPv6, don't ask for one. -if [ -z "$PUBLIC_IPV6" ]; then +if [ -z "${PUBLIC_IPV6:-}" ]; then # Ask the Internet. GUESSED_IP=$(get_publicip_from_web_service 6) MATCHED=0 - if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then + if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then PUBLIC_IPV6=$GUESSED_IP - elif [[ "$DEFAULT_PUBLIC_IPV6" == "$GUESSED_IP" ]]; then + elif [[ "${DEFAULT_PUBLIC_IPV6:-}" == "$GUESSED_IP" ]]; then # No IPv6 entered and machine seems to have none, or what # the user entered matches what the Internet tells us. PUBLIC_IPV6=$GUESSED_IP MATCHED=1 - elif [[ -z "$DEFAULT_PUBLIC_IPV6" ]]; then + elif [[ -z "${DEFAULT_PUBLIC_IPV6:-}" ]]; then DEFAULT_PUBLIC_IP=$(get_default_privateip 6) fi - if [[ -z "$PUBLIC_IPV6" && $MATCHED == 0 ]]; then + if [[ -z "${PUBLIC_IPV6:-}" && $MATCHED == 0 ]]; then input_box "IPv6 Address (Optional)" \ "Enter the public IPv6 address of this machine, as given to you by your ISP. \n\nLeave blank if the machine does not have an IPv6 address. \n\nPublic IPv6 address:" \ - $DEFAULT_PUBLIC_IPV6 \ + ${DEFAULT_PUBLIC_IPV6:-} \ PUBLIC_IPV6 if [ ! $PUBLIC_IPV6_EXITCODE ]; then @@ -154,10 +154,10 @@ fi # Get the IP addresses of the local network interface(s) that are connected # to the Internet. We need these when we want to have services bind only to # the public network interfaces (not loopback, not tunnel interfaces). -if [ -z "$PRIVATE_IP" ]; then +if [ -z "${PRIVATE_IP:-}" ]; then PRIVATE_IP=$(get_default_privateip 4) fi -if [ -z "$PRIVATE_IPV6" ]; then +if [ -z "${PRIVATE_IPV6:-}" ]; then PRIVATE_IPV6=$(get_default_privateip 6) fi if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then @@ -186,11 +186,11 @@ fi # Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless # we've already got those values from a previous run. -if [ -z "$STORAGE_USER" ]; then - STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER") +if [ -z "${STORAGE_USER:-}" ]; then + STORAGE_USER=$([[ -z "${DEFAULT_STORAGE_USER:-}" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER") fi -if [ -z "$STORAGE_ROOT" ]; then - STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT") +if [ -z "${STORAGE_ROOT:-}" ]; then + STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT") fi # Show the configuration, since the user may have not entered it manually. diff --git a/setup/start.sh b/setup/start.sh index 671f444..0b14502 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -60,8 +60,8 @@ source setup/questions.sh # Run some network checks to make sure setup on this machine makes sense. # Skip on existing installs since we don't want this to block the ability to # upgrade, and these checks are also in the control panel status checks. -if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then -if [ -z "$SKIP_NETWORK_CHECKS" ]; then +if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then +if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then source setup/network-checks.sh fi fi diff --git a/setup/system.sh b/setup/system.sh index 2fecac8..2db4829 100755 --- a/setup/system.sh +++ b/setup/system.sh @@ -37,9 +37,9 @@ hostname $PRIMARY_HOSTNAME # for reference SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2) -SWAP_IN_FSTAB=$(grep "swap" /etc/fstab) -ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts) -TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}') +SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true) +ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true) +TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true) AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1) if [ -z "$SWAP_MOUNTED" ] && @@ -143,8 +143,8 @@ fi # section) and syslog (see #328). There might be other issues, and it's # not likely the user will want to change this, so we only ask on first # setup. -if [ -z "$NONINTERACTIVE" ]; then - if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then +if [ -z "${NONINTERACTIVE:-}" ]; then + if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then # If the file is missing or this is the user's first time running # Mail-in-a-Box setup, run the interactive timezone configuration # tool. @@ -239,7 +239,7 @@ EOF # Various virtualized environments like Docker and some VPSs don't provide #NODOC # a kernel that supports iptables. To avoid error-like output in these cases, #NODOC # we skip this if the user sets DISABLE_FIREWALL=1. #NODOC -if [ -z "$DISABLE_FIREWALL" ]; then +if [ -z "${DISABLE_FIREWALL:-}" ]; then # Install `ufw` which provides a simple firewall configuration. apt_install ufw