From faf6f87a63f2405006751b0b7c2d3045a088f5a2 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Mon, 25 Aug 2014 08:09:37 -0400 Subject: [PATCH] move the user-interactive questions and other parts of start.sh into new files --- setup/firstuser.sh | 57 ++++++++++ setup/preflight.sh | 29 +++++ setup/questions.sh | 182 +++++++++++++++++++++++++++++ setup/start.sh | 278 ++------------------------------------------- 4 files changed, 277 insertions(+), 269 deletions(-) create mode 100644 setup/firstuser.sh create mode 100644 setup/preflight.sh create mode 100644 setup/questions.sh diff --git a/setup/firstuser.sh b/setup/firstuser.sh new file mode 100644 index 00000000..c1e2d6cb --- /dev/null +++ b/setup/firstuser.sh @@ -0,0 +1,57 @@ +# If there aren't any mail users yet, create one. +if [ -z "`tools/mail.py user`" ]; then + # The outut of "tools/mail.py user" is a list of mail users. If there + # aren't any yet, it'll be empty. + + # 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 + input_box "Mail Account" \ + "Let's create your first mail account. + \n\nWhat email address do you want?" \ + me@`get_default_hostname` \ + EMAIL_ADDR + + if [ -z "$EMAIL_ADDR" ]; then + # user hit ESC/cancel + exit + fi + while ! management/mailconfig.py validate-email "$EMAIL_ADDR" + do + input_box "Mail Account" \ + "That's not a valid email address. + \n\nWhat email address do you want?" \ + $EMAIL_ADDR \ + EMAIL_ADDR + if [ -z "$EMAIL_ADDR" ]; then + # user hit ESC/cancel + exit + fi + done + + # But in a non-interactive shell, just make something up. + # This is normally for testing. + else + # Use me@PRIMARY_HOSTNAME + EMAIL_ADDR=me@$PRIMARY_HOSTNAME + EMAIL_PW=1234 + echo + echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW." + echo + fi + else + echo + echo "Okay. I'm about to set up $EMAIL_ADDR for you. This account will also" + echo "have access to the box's control panel." + fi + + # Create the user's mail account. This will ask for a password if none was given above. + tools/mail.py user add $EMAIL_ADDR $EMAIL_PW + + # Make it an admin. + hide_output tools/mail.py user make-admin $EMAIL_ADDR + + # Create an alias to which we'll direct all automatically-created administrative aliases. + tools/mail.py alias add administrator@$PRIMARY_HOSTNAME $EMAIL_ADDR +fi \ No newline at end of file diff --git a/setup/preflight.sh b/setup/preflight.sh new file mode 100644 index 00000000..742d6db9 --- /dev/null +++ b/setup/preflight.sh @@ -0,0 +1,29 @@ +# Are we running as root? +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root. Please re-run like this:" + echo + echo "sudo setup/start.sh" + echo + exit +fi + +# Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx). +if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "Ubuntu 14.04 LTS" ]; then + echo "Mail-in-a-Box only supports being installed on Ubuntu 14.04, sorry. You are running:" + echo + lsb_release -d | sed 's/.*:\s*//' + echo + echo "We can't write scripts that run on every possible setup, sorry." + exit +fi + +# Check that we have enough memory. Skip the check if we appear to be +# running inside of Vagrant, because that's really just for testing. +TOTAL_PHYSICAL_MEM=$(free -m | grep ^Mem: | sed "s/^Mem: *\([0-9]*\).*/\1/") +if [ $TOTAL_PHYSICAL_MEM -lt 768 ]; then +if [ ! -d /vagrant ]; then + echo "Your Mail-in-a-Box needs more than $TOTAL_PHYSICAL_MEM MB RAM." + echo "Please provision a machine with at least 768 MB, 1 GB recommended." + exit +fi +fi diff --git a/setup/questions.sh b/setup/questions.sh new file mode 100644 index 00000000..5d61286a --- /dev/null +++ b/setup/questions.sh @@ -0,0 +1,182 @@ +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 + # case, the nifty '[ -t 0 ]' test won't work. But with Vagrant we must suppress so we + # use a shell flag instead. + apt_install dialog + message_box "Mail-in-a-Box Installation" \ + "Hello and thanks for deploying a Mail-in-a-Box! + \n\nI'm going to ask you a few questions. + \n\nTo change your answers later, just re-run this script." +fi + +# The box needs a name. +if [ -z "$PRIMARY_HOSTNAME" ]; then + if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then + # This is the first run. Ask the user for his email address so we can + # provide the best default for the box's hostname. + input_box "Your Email Address" \ +"What email address are you setting this box up to manage? +\n\nThe part after the @-sign must be a domain name or subdomain +that you control. You can add other email addresses to this +box later (including email addresses on other domain names +or subdomains you control). +\n\nWe've guessed an email address. Backspace it and type in what +you really want. +\n\nEmail Address:" \ + me@`get_default_hostname` \ + EMAIL_ADDR + + if [ -z "$EMAIL_ADDR" ]; then + # user hit ESC/cancel + exit + fi + while ! management/mailconfig.py validate-email "$EMAIL_ADDR" + do + input_box "Your Email Address" \ + "That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \ + $EMAIL_ADDR \ + EMAIL_ADDR + if [ -z "$EMAIL_ADDR" ]; then + # user hit ESC/cancel + exit + fi + done + + # Take the part after the @-sign as the user's domain name, and add + # 'box.' to the beginning to create a default hostname for this machine. + DEFAULT_PRIMARY_HOSTNAME=box.$(echo $EMAIL_ADDR | sed 's/.*@//') + fi + + input_box "Hostname" \ +"This box needs a name, called a 'hostname'. The name will form a part of the box's web address. +\n\nWe recommend that the name be a subdomain of the domain in your email +address, so we're suggesting $DEFAULT_PRIMARY_HOSTNAME. +\n\nYou can change it, but we recommend you don't. +\n\nHostname:" \ + $DEFAULT_PRIMARY_HOSTNAME \ + PRIMARY_HOSTNAME + + if [ -z "$PRIMARY_HOSTNAME" ]; then + # user hit ESC/cancel + exit + fi +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 + # Ask the Internet. + GUESSED_IP=$(get_publicip_from_web_service 4) + + # On the first run, if we got an answer from the Internet then don't + # ask the user. + if [[ -z "$DEFAULT_PUBLIC_IP" && ! -z "$GUESSED_IP" ]]; then + PUBLIC_IP=$GUESSED_IP + + # Otherwise on the first run at least provide a default. + elif [[ -z "$DEFAULT_PUBLIC_IP" ]]; then + DEFAULT_PUBLIC_IP=$(get_default_privateip 4) + + # On later runs, if the previous value matches the guessed value then + # don't ask the user either. + elif [ "$DEFAULT_PUBLIC_IP" == "$GUESSED_IP" ]; then + PUBLIC_IP=$GUESSED_IP + fi + + 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 \ + PUBLIC_IP + + if [ -z "$PUBLIC_IP" ]; then + # user hit ESC/cancel + exit + fi + fi +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 + # Ask the Internet. + GUESSED_IP=$(get_publicip_from_web_service 6) + MATCHED=0 + if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then + PUBLIC_IPV6=$GUESSED_IP + 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 + DEFAULT_PUBLIC_IP=$(get_default_privateip 6) + fi + + 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 \ + PUBLIC_IPV6 + + if [ ! $PUBLIC_IPV6_EXITCODE ]; then + # user hit ESC/cancel + exit + fi + fi +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 + PRIVATE_IP=$(get_default_privateip 4) +fi +if [ -z "$PRIVATE_IPV6" ]; then + PRIVATE_IPV6=$(get_default_privateip 6) +fi +if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then + echo + echo "I could not determine the IP or IPv6 address of the network inteface" + echo "for connecting to the Internet. Setup must stop." + echo + hostname -I + route + echo + exit +fi + +# We need a country code to generate a certificate signing request. However +# if a CSR already exists then we won't be generating a new one and there's +# no reason to ask for the country code now. $STORAGE_ROOT has not yet been +# set so we'll check if $DEFAULT_STORAGE_ROOT and $DEFAULT_CSR_COUNTRY are +# set (the values from the current mailinabox.conf) and if the CSR exists +# in the expected location. +if [ ! -z "$DEFAULT_STORAGE_ROOT" ] && [ ! -z "$DEFAULT_CSR_COUNTRY" ] && [ -f $DEFAULT_STORAGE_ROOT/ssl/ssl_cert_sign_req.csr ]; then + CSR_COUNTRY=$DEFAULT_CSR_COUNTRY +fi + +if [ -z "$CSR_COUNTRY" ]; then + # Get a list of country codes. Separate codes from country names with a ^. + # The input_menu function modifies shell word expansion to ignore spaces + # (since country names can have spaces) and use ^ instead. + country_code_list=$(grep -v "^#" setup/csr_country_codes.tsv | sed "s/\(..\)\t\([^\t]*\).*/\1^\2/") + + input_menu "Country Code" \ + "Choose the country where you live or where your organization is based. + \n\n(This is used to create an SSL certificate.) + \n\nCountry Code:" \ + "$country_code_list" \ + CSR_COUNTRY + + if [ -z "$CSR_COUNTRY" ]; then + # user hit ESC/cancel + exit + fi +fi diff --git a/setup/start.sh b/setup/start.sh index ab9c5f25..648a3cb9 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -4,50 +4,9 @@ source setup/functions.sh # load our functions -# Check system setup. - -# Are we running as root? -if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root. Please re-run like this:" - echo - echo "sudo setup/start.sh" - echo - exit -fi - -# Check that we are running on Ubuntu 14.04 LTS (or 14.04.xx). -if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" != "Ubuntu 14.04 LTS" ]; then - echo "Mail-in-a-Box only supports being installed on Ubuntu 14.04, sorry. You are running:" - echo - lsb_release -d | sed 's/.*:\s*//' - echo - echo "We can't write scripts that run on every possible setup, sorry." - exit -fi - -# Check that we have enough memory. Skip the check if we appear to be -# running inside of Vagrant, because that's really just for testing. -TOTAL_PHYSICAL_MEM=$(free -m | grep ^Mem: | sed "s/^Mem: *\([0-9]*\).*/\1/") -if [ $TOTAL_PHYSICAL_MEM -lt 768 ]; then -if [ ! -d /vagrant ]; then - echo "Your Mail-in-a-Box needs more than $TOTAL_PHYSICAL_MEM MB RAM." - echo "Please provision a machine with at least 768 MB, 1 GB recommended." - exit -fi -fi - -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 - # case, the nifty '[ -t 0 ]' test won't work. But with Vagrant we must suppress so we - # use a shell flag instead. - apt_install dialog - message_box "Mail-in-a-Box Installation" \ - "Hello and thanks for deploying a Mail-in-a-Box! - \n\nI'm going to ask you a few questions. - \n\nTo change your answers later, just re-run this script." -fi +# Check system setup: Are we running as root on Ubuntu 14.04 on a +# machine with enough memory? If not, this shows an error and exits. +. setup/preflight.sh # Recall the last settings used if we're running this a second time. if [ -f /etc/mailinabox.conf ]; then @@ -62,175 +21,10 @@ if [ -f /etc/mailinabox.conf ]; then rm -f /tmp/mailinabox.prev.conf fi -# The box needs a name. -if [ -z "$PRIMARY_HOSTNAME" ]; then - if [ -z "$DEFAULT_PRIMARY_HOSTNAME" ]; then - # This is the first run. Ask the user for his email address so we can - # provide the best default for the box's hostname. - input_box "Your Email Address" \ -"What email address are you setting this box up to manage? -\n\nThe part after the @-sign must be a domain name or subdomain -that you control. You can add other email addresses to this -box later (including email addresses on other domain names -or subdomains you control). -\n\nWe've guessed an email address. Backspace it and type in what -you really want. -\n\nEmail Address:" \ - me@`get_default_hostname` \ - EMAIL_ADDR - - if [ -z "$EMAIL_ADDR" ]; then - # user hit ESC/cancel - exit - fi - while ! management/mailconfig.py validate-email "$EMAIL_ADDR" - do - input_box "Your Email Address" \ - "That's not a valid email address.\n\nWhat email address are you setting this box up to manage?" \ - $EMAIL_ADDR \ - EMAIL_ADDR - if [ -z "$EMAIL_ADDR" ]; then - # user hit ESC/cancel - exit - fi - done - - # Take the part after the @-sign as the user's domain name, and add - # 'box.' to the beginning to create a default hostname for this machine. - DEFAULT_PRIMARY_HOSTNAME=box.$(echo $EMAIL_ADDR | sed 's/.*@//') - fi - - input_box "Hostname" \ -"This box needs a name, called a 'hostname'. The name will form a part of the box's web address. -\n\nWe recommend that the name be a subdomain of the domain in your email -address, so we're suggesting $DEFAULT_PRIMARY_HOSTNAME. -\n\nYou can change it, but we recommend you don't. -\n\nHostname:" \ - $DEFAULT_PRIMARY_HOSTNAME \ - PRIMARY_HOSTNAME - - if [ -z "$PRIMARY_HOSTNAME" ]; then - # user hit ESC/cancel - exit - fi -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 - # Ask the Internet. - GUESSED_IP=$(get_publicip_from_web_service 4) - - # On the first run, if we got an answer from the Internet then don't - # ask the user. - if [[ -z "$DEFAULT_PUBLIC_IP" && ! -z "$GUESSED_IP" ]]; then - PUBLIC_IP=$GUESSED_IP - - # Otherwise on the first run at least provide a default. - elif [[ -z "$DEFAULT_PUBLIC_IP" ]]; then - DEFAULT_PUBLIC_IP=$(get_default_privateip 4) - - # On later runs, if the previous value matches the guessed value then - # don't ask the user either. - elif [ "$DEFAULT_PUBLIC_IP" == "$GUESSED_IP" ]; then - PUBLIC_IP=$GUESSED_IP - fi - - 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 \ - PUBLIC_IP - - if [ -z "$PUBLIC_IP" ]; then - # user hit ESC/cancel - exit - fi - fi -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 - # Ask the Internet. - GUESSED_IP=$(get_publicip_from_web_service 6) - MATCHED=0 - if [[ -z "$DEFAULT_PUBLIC_IPV6" && ! -z "$GUESSED_IP" ]]; then - PUBLIC_IPV6=$GUESSED_IP - 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 - DEFAULT_PUBLIC_IP=$(get_default_privateip 6) - fi - - 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 \ - PUBLIC_IPV6 - - if [ ! $PUBLIC_IPV6_EXITCODE ]; then - # user hit ESC/cancel - exit - fi - fi -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 - PRIVATE_IP=$(get_default_privateip 4) -fi -if [ -z "$PRIVATE_IPV6" ]; then - PRIVATE_IPV6=$(get_default_privateip 6) -fi -if [[ -z "$PRIVATE_IP" && -z "$PRIVATE_IPV6" ]]; then - echo - echo "I could not determine the IP or IPv6 address of the network inteface" - echo "for connecting to the Internet. Setup must stop." - echo - hostname -I - route - echo - exit -fi - -# We need a country code to generate a certificate signing request. However -# if a CSR already exists then we won't be generating a new one and there's -# no reason to ask for the country code now. $STORAGE_ROOT has not yet been -# set so we'll check if $DEFAULT_STORAGE_ROOT and $DEFAULT_CSR_COUNTRY are -# set (the values from the current mailinabox.conf) and if the CSR exists -# in the expected location. -if [ ! -z "$DEFAULT_STORAGE_ROOT" ] && [ ! -z "$DEFAULT_CSR_COUNTRY" ] && [ -f $DEFAULT_STORAGE_ROOT/ssl/ssl_cert_sign_req.csr ]; then - CSR_COUNTRY=$DEFAULT_CSR_COUNTRY -fi - -if [ -z "$CSR_COUNTRY" ]; then - # Get a list of country codes. Separate codes from country names with a ^. - # The input_menu function modifies shell word expansion to ignore spaces - # (since country names can have spaces) and use ^ instead. - country_code_list=$(grep -v "^#" setup/csr_country_codes.tsv | sed "s/\(..\)\t\([^\t]*\).*/\1^\2/") - - input_menu "Country Code" \ - "Choose the country where you live or where your organization is based. - \n\n(This is used to create an SSL certificate.) - \n\nCountry Code:" \ - "$country_code_list" \ - CSR_COUNTRY - - if [ -z "$CSR_COUNTRY" ]; then - # user hit ESC/cancel - exit - fi -fi +# Ask the user for the PRIMARY_HOSTNAME, PUBLIC_IP, PUBLIC_IPV6, and CSR_COUNTRY +# if values have not already been set in environment variables. When running +# non-interactively, be sure to set values for all! +. setup/questions.sh # Automatic configuration, e.g. as used in our Vagrant configuration. if [ "$PUBLIC_IP" = "auto" ]; then @@ -311,63 +105,9 @@ curl -s -d POSTDATA --user $(