turn on bash strict mode to better catch setup errors

fixes #893
This commit is contained in:
Joshua Tauberer 2018-11-30 10:24:19 -05:00
parent aa52f52d02
commit e5e0c64395
7 changed files with 47 additions and 34 deletions

View file

@ -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 we didn't ask for an email address at the start, do so now.
if [ -z "$EMAIL_ADDR" ]; then if [ -z "$EMAIL_ADDR" ]; then
# In an interactive shell, ask the user for an email address. # In an interactive shell, ask the user for an email address.
if [ -z "$NONINTERACTIVE" ]; then if [ -z "${NONINTERACTIVE:-}" ]; then
input_box "Mail Account" \ input_box "Mail Account" \
"Let's create your first mail account. "Let's create your first mail account.
\n\nWhat email address do you want?" \ \n\nWhat email address do you want?" \

View file

@ -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 { function hide_output {
# This function hides the output of a command unless the command fails # This function hides the output of a command unless the command fails
# and returns a non-zero exit code. # and returns a non-zero exit code.
@ -5,11 +11,14 @@ function hide_output {
# Get a temporary file. # Get a temporary file.
OUTPUT=$(tempfile) 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 $@ &> $OUTPUT
E=$?
set -e
# If the command failed, show the output that was captured in the temporary file. # If the command failed, show the output that was captured in the temporary file.
E=$?
if [ $E != 0 ]; then if [ $E != 0 ]; then
# Something failed. # Something failed.
echo echo
@ -75,7 +84,7 @@ function get_publicip_from_web_service {
# #
# Pass '4' or '6' as an argument to this function to specify # Pass '4' or '6' as an argument to this function to specify
# what type of address to get (IPv4, IPv6). # 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 { function get_default_privateip {
@ -131,11 +140,10 @@ function get_default_privateip {
fi fi
echo $address echo $address
} }
function ufw_allow { function ufw_allow {
if [ -z "$DISABLE_FIREWALL" ]; then if [ -z "${DISABLE_FIREWALL:-}" ]; then
# ufw has completely unhelpful output # ufw has completely unhelpful output
ufw allow $1 > /dev/null; ufw allow $1 > /dev/null;
fi fi
@ -154,10 +162,13 @@ function input_box {
# input_box "title" "prompt" "defaultvalue" VARIABLE # input_box "title" "prompt" "defaultvalue" VARIABLE
# The user's input will be stored in the variable VARIABLE. # The user's input will be stored in the variable VARIABLE.
# The exit code from dialog will be stored in VARIABLE_EXITCODE. # 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=$4
declare -n result_code=$4_EXITCODE declare -n result_code=$4_EXITCODE
set +e
result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3") result=$(dialog --stdout --title "$1" --inputbox "$2" 0 0 "$3")
result_code=$? result_code=$?
set -e
} }
function input_menu { function input_menu {
@ -167,8 +178,10 @@ function input_menu {
declare -n result=$4 declare -n result=$4
declare -n result_code=$4_EXITCODE declare -n result_code=$4_EXITCODE
local IFS=^$'\n' local IFS=^$'\n'
set +e
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3) result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
result_code=$? result_code=$?
set -e
} }
function wget_verify { function wget_verify {

View file

@ -29,7 +29,7 @@ address 127.0.0.1
# send alerts to the following address # send alerts to the following address
contacts admin 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 contact.admin.always_send warning critical
EOF EOF

View file

@ -41,7 +41,7 @@ if [ $TOTAL_PHYSICAL_MEM -lt 750000 ]; then
fi fi
# Check that tempfs is mounted with exec # 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 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" echo "Mail-in-a-Box has to have exec rights on /tmp, please mount /tmp with exec"
exit exit

View file

@ -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 # 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, # 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 # 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 fi
# The box needs a name. # The box needs a name.
if [ -z "$PRIMARY_HOSTNAME" ]; then if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
# We recommend to use box.example.com as this hosts name. The # We recommend to use box.example.com as this hosts name. The
# domain the user possibly wants to use is example.com then. # domain the user possibly wants to use is example.com then.
# We strip the string "box." from the hostname to get the mail # 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 # 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 # its IP address on the public network / the Internet. Ask the Internet
# and possibly confirm with user. # and possibly confirm with user.
if [ -z "$PUBLIC_IP" ]; then if [ -z "${PUBLIC_IP:-}" ]; then
# Ask the Internet. # Ask the Internet.
GUESSED_IP=$(get_publicip_from_web_service 4) GUESSED_IP=$(get_publicip_from_web_service 4)
@ -105,11 +105,11 @@ if [ -z "$PUBLIC_IP" ]; then
PUBLIC_IP=$GUESSED_IP PUBLIC_IP=$GUESSED_IP
fi fi
if [ -z "$PUBLIC_IP" ]; then if [ -z "${PUBLIC_IP:-}" ]; then
input_box "Public IP Address" \ input_box "Public IP Address" \
"Enter the public IP address of this machine, as given to you by your ISP. "Enter the public IP address of this machine, as given to you by your ISP.
\n\nPublic IP address:" \ \n\nPublic IP address:" \
$DEFAULT_PUBLIC_IP \ ${DEFAULT_PUBLIC_IP:-} \
PUBLIC_IP PUBLIC_IP
if [ -z "$PUBLIC_IP" ]; then if [ -z "$PUBLIC_IP" ]; then
@ -121,27 +121,27 @@ fi
# Same for IPv6. But it's optional. Also, if it looks like the system # Same for IPv6. But it's optional. Also, if it looks like the system
# doesn't have an IPv6, don't ask for one. # doesn't have an IPv6, don't ask for one.
if [ -z "$PUBLIC_IPV6" ]; then if [ -z "${PUBLIC_IPV6:-}" ]; then
# Ask the Internet. # Ask the Internet.
GUESSED_IP=$(get_publicip_from_web_service 6) GUESSED_IP=$(get_publicip_from_web_service 6)
MATCHED=0 MATCHED=0
if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then if [[ -z "${DEFAULT_PUBLIC_IPV6:-}" && ! -z "$GUESSED_IP" ]]; then
PUBLIC_IPV6=$GUESSED_IP 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 # No IPv6 entered and machine seems to have none, or what
# the user entered matches what the Internet tells us. # the user entered matches what the Internet tells us.
PUBLIC_IPV6=$GUESSED_IP PUBLIC_IPV6=$GUESSED_IP
MATCHED=1 MATCHED=1
elif [[ -z "$DEFAULT_PUBLIC_IPV6" ]]; then elif [[ -z "${DEFAULT_PUBLIC_IPV6:-}" ]]; then
DEFAULT_PUBLIC_IP=$(get_default_privateip 6) DEFAULT_PUBLIC_IP=$(get_default_privateip 6)
fi fi
if [[ -z "$PUBLIC_IPV6" && $MATCHED == 0 ]]; then if [[ -z "${PUBLIC_IPV6:-}" && $MATCHED == 0 ]]; then
input_box "IPv6 Address (Optional)" \ input_box "IPv6 Address (Optional)" \
"Enter the public IPv6 address of this machine, as given to you by your ISP. "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\nLeave blank if the machine does not have an IPv6 address.
\n\nPublic IPv6 address:" \ \n\nPublic IPv6 address:" \
$DEFAULT_PUBLIC_IPV6 \ ${DEFAULT_PUBLIC_IPV6:-} \
PUBLIC_IPV6 PUBLIC_IPV6
if [ ! $PUBLIC_IPV6_EXITCODE ]; then if [ ! $PUBLIC_IPV6_EXITCODE ]; then
@ -154,10 +154,10 @@ fi
# Get the IP addresses of the local network interface(s) that are connected # 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 # to the Internet. We need these when we want to have services bind only to
# the public network interfaces (not loopback, not tunnel interfaces). # 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) PRIVATE_IP=$(get_default_privateip 4)
fi fi
if [ -z "$PRIVATE_IPV6" ]; then if [ -z "${PRIVATE_IPV6:-}" ]; then
PRIVATE_IPV6=$(get_default_privateip 6) PRIVATE_IPV6=$(get_default_privateip 6)
fi fi
if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then 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 # 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. # we've already got those values from a previous run.
if [ -z "$STORAGE_USER" ]; then if [ -z "${STORAGE_USER:-}" ]; then
STORAGE_USER=$([[ -z "$DEFAULT_STORAGE_USER" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER") STORAGE_USER=$([[ -z "${DEFAULT_STORAGE_USER:-}" ]] && echo "user-data" || echo "$DEFAULT_STORAGE_USER")
fi fi
if [ -z "$STORAGE_ROOT" ]; then if [ -z "${STORAGE_ROOT:-}" ]; then
STORAGE_ROOT=$([[ -z "$DEFAULT_STORAGE_ROOT" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT") STORAGE_ROOT=$([[ -z "${DEFAULT_STORAGE_ROOT:-}" ]] && echo "/home/$STORAGE_USER" || echo "$DEFAULT_STORAGE_ROOT")
fi fi
# Show the configuration, since the user may have not entered it manually. # Show the configuration, since the user may have not entered it manually.

View file

@ -60,8 +60,8 @@ source setup/questions.sh
# Run some network checks to make sure setup on this machine makes sense. # 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 # 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. # upgrade, and these checks are also in the control panel status checks.
if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
if [ -z "$SKIP_NETWORK_CHECKS" ]; then if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
source setup/network-checks.sh source setup/network-checks.sh
fi fi
fi fi

View file

@ -37,9 +37,9 @@ hostname $PRIMARY_HOSTNAME
# for reference # for reference
SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2) SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2)
SWAP_IN_FSTAB=$(grep "swap" /etc/fstab) SWAP_IN_FSTAB=$(grep "swap" /etc/fstab || /bin/true)
ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts) ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts || /bin/true)
TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}') TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}' || /bin/true)
AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1) AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1)
if if
[ -z "$SWAP_MOUNTED" ] && [ -z "$SWAP_MOUNTED" ] &&
@ -143,8 +143,8 @@ fi
# section) and syslog (see #328). There might be other issues, and it's # 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 # not likely the user will want to change this, so we only ask on first
# setup. # setup.
if [ -z "$NONINTERACTIVE" ]; then if [ -z "${NONINTERACTIVE:-}" ]; then
if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then if [ ! -f /etc/timezone ] || [ ! -z ${FIRST_TIME_SETUP:-} ]; then
# If the file is missing or this is the user's first time running # If the file is missing or this is the user's first time running
# Mail-in-a-Box setup, run the interactive timezone configuration # Mail-in-a-Box setup, run the interactive timezone configuration
# tool. # tool.
@ -239,7 +239,7 @@ EOF
# Various virtualized environments like Docker and some VPSs don't provide #NODOC # 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 # 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 # 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. # Install `ufw` which provides a simple firewall configuration.
apt_install ufw apt_install ufw