#!/usr/bin/env bash
set -e

usage() {
    cat <<EOF
Usage:
  install --hostname=zulip.example.com --email=admin@example.com [options...]
  install --help

Other options:
  --certbot
  --self-signed-cert
  --no-init-db

The --hostname and --email options are required,
unless --no-init-db is set and --certbot is not.

EOF
    exit 0
};

# Shell option parsing.  Over time, we'll want to move some of the
# environment variables below into this self-documenting system.
args="$(getopt -o '' --long help,no-init-db,self-signed-cert,certbot,hostname:,email: -n "$0" -- "$@")"
eval "set -- $args"
while true; do
    case "$1" in
        --help) usage;;
        --self-signed-cert) SELF_SIGNED_CERT=1; shift;;
        --certbot) USE_CERTBOT=1; shift;;
        --hostname) EXTERNAL_HOST="$2"; shift; shift;;
        --email) ZULIP_ADMINISTRATOR="$2"; shift; shift;;
        --no-init-db) NO_INIT_DB=1; shift;;
        --) shift; break;;
    esac
done

if [ "$#" -gt 0 ]; then
    usage
fi

## Options from environment variables.
#
# Specify options for apt.
APT_OPTIONS="${APT_OPTIONS:-}"
# Install additional packages using apt.
ADDITIONAL_PACKAGES=${ADDITIONAL_PACKAGES:-}
# Deployment type is almost always voyager.
DEPLOYMENT_TYPE="${DEPLOYMENT_TYPE:-voyager}"
# Comma-separated list of puppet manifests to install.  default is
# zulip::voyager for an all-in-one system or zulip::dockervoyager for
# Docker.  Use e.g. zulip::app_frontend for a Zulip frontend server.
PUPPET_CLASSES="${PUPPET_CLASSES:-zulip::voyager}"
VIRTUALENV_NEEDED="${VIRTUALENV_NEEDED:-yes}"

if [ -n "$SELF_SIGNED_CERT" ] && [ -n "$USE_CERTBOT" ]; then
    echo "error: --self-signed-cert and --certbot are incompatible" >&2
    echo >&2
    usage
fi

if [ -z "$EXTERNAL_HOST" ] || [ -z "$ZULIP_ADMINISTRATOR" ]; then
    if [ -n "$USE_CERTBOT" ] || [ -z "$NO_INIT_DB" ]; then
        usage
    fi
fi

# Do set -x after option parsing is complete
set -x

ZULIP_PATH="$(readlink -f $(dirname $0)/../..)"

# Force a known locale.  Some packages on PyPI fail to install in some locales.
export LC_ALL="en_US.UTF-8"

# Check for a supported OS release.
apt-get install -y lsb-release
os_release="$(lsb_release -sc)"
case "$os_release" in
    trusty|xenial) ;;
    *)
        set +x
        cat <<EOF

Unsupported OS release: $os_release

Zulip in production is supported only on:
 - Ubuntu 14.04 LTS "trusty"
 - Ubuntu 16.04 LTS "xenial"

For more information, see:
  https://zulip.readthedocs.io/en/latest/production/requirements.html
EOF
        exit 1
esac

# Check for at least ~1.9GB of RAM before starting installation;
# otherwise users will find out about insufficient RAM via weird
# errors like a segfault running `pip install`.
mem_kb=$(cat /proc/meminfo | head -n1 | awk '{print $2}')
if [ "$mem_kb" -lt 1900000 ]; then
    echo "Insufficient RAM.  Zulip requires at least 2GB of RAM."
    exit 1
fi

# setup-apt-repo does an `apt-get update`
"$ZULIP_PATH"/scripts/lib/setup-apt-repo

# Handle issues around upstart on Ubuntu Xenial
"$ZULIP_PATH"/scripts/lib/check-upstart

# Check early for missing SSL certificates
if [ "$PUPPET_CLASSES" = "zulip::voyager" ] && [ -z "$USE_CERTBOT""$SELF_SIGNED_CERT" ] && { ! [ -e "/etc/ssl/private/zulip.key" ] || ! [ -e "/etc/ssl/certs/zulip.combined-chain.crt" ]; }; then
    set +x
    echo
    echo "Could not find SSL certificates!"
    for f in "/etc/ssl/private/zulip.key" "/etc/ssl/certs/zulip.combined-chain.crt"; do
        [ -e "$f" ] || echo " - $f is missing!"
    done
    cat <<EOF

See https://zulip.readthedocs.io/en/latest/production/ssl-certificates.html for help.
For non-production testing, try the --self-signed-cert option.

Once fixed, just rerun scripts/setup/install; it'll pick up from here!

EOF
   exit 1
fi

apt-get -y dist-upgrade $APT_OPTIONS
apt-get install -y \
    puppet git curl \
    python python3 python-six python3-six crudini \
    $ADDITIONAL_PACKAGES

if [ -n "$USE_CERTBOT" ]; then
    "$ZULIP_PATH"/scripts/setup/setup-certbot \
        --no-zulip-conf --method=standalone \
        --hostname "$EXTERNAL_HOST" --email "$ZULIP_ADMINISTRATOR"
elif [ -n "$SELF_SIGNED_CERT" ]; then
    "$ZULIP_PATH"/scripts/setup/generate-self-signed-cert \
        --exists-ok "${EXTERNAL_HOST:-$(hostname)}"
fi

# Create and activate a virtualenv
if [ "$VIRTUALENV_NEEDED" = "yes" ]; then
    "$ZULIP_PATH"/scripts/lib/create-production-venv "$ZULIP_PATH"
fi

"$ZULIP_PATH"/scripts/lib/install-node

# Generate /etc/zulip/zulip.conf .
mkdir -p /etc/zulip
(
    cat <<EOF
[machine]
puppet_classes = $PUPPET_CLASSES
deploy_type = $DEPLOYMENT_TYPE
EOF

    # Note: there are four dpkg-query outputs to consider:
    #
    # root@host# dpkg-query --showformat '${Status}\n' -W rabbitmq-server 2>/dev/null
    # root@host# apt install rabbitmq-server
    # root@host# dpkg-query --showformat '${Status}\n' -W rabbitmq-server 2>/dev/null
    # install ok installed
    # root@host# apt remove rabbitmq-server
    # root@host# dpkg-query --showformat '${Status}\n' -W rabbitmq-server 2>/dev/null
    # deinstall ok config-files
    # root@host# apt purge rabbitmq-server
    # root@host# dpkg-query --showformat '${Status}\n' -W rabbitmq-server 2>/dev/null
    # unknown ok not-installed
    #
    # (There are more possibilities in the case of dpkg errors.)  Here
    # we are checking for either empty or not-installed.
    if [ -n "$TRAVIS" ] || ! dpkg-query --showformat '${Status}\n' -W rabbitmq-server 2>/dev/null | grep -vq ' not-installed$'; then
        cat <<EOF

[rabbitmq]
nodename = zulip@localhost
EOF
    fi

    if [ -n "$USE_CERTBOT" ]; then
        cat <<EOF

[certbot]
auto_renew = yes
EOF
    fi
) > /etc/zulip/zulip.conf

"$ZULIP_PATH"/scripts/zulip-puppet-apply -f

# Detect which features were selected for the below
set +e
[ -e "/etc/init.d/camo" ]; has_camo=$?
[ -e "/etc/init.d/nginx" ]; has_nginx=$?
[ -e "/etc/supervisor/conf.d/zulip.conf" ]; has_appserver=$?
[ -e "/etc/cron.d/rabbitmq-numconsumers" ]; has_rabbit=$?
[ -e "/etc/init.d/postgresql" ]; has_postgres=$?
set -e

# Docker service setup is done in the docker config, not here
if [ "$DEPLOYMENT_TYPE" = "dockervoyager" ]; then
    has_camo=1
    has_nginx=1
    has_appserver=1
    has_rabbit=1
    has_postgres=1
fi

# These server restarting bits should be moveable into puppet-land, ideally
apt-get -y upgrade

if [ "$has_nginx" = 0 ]; then
    # Check nginx was configured properly now that we've installed it.
    # Most common failure mode is certs not having been installed.
    nginx -t || (
        set +x
        cat <<EOF

Verifying the Zulip nginx configuration failed!

This is almost always a problem with your SSL certificates.  See:
  https://zulip.readthedocs.io/en/latest/production/ssl-certificates.html

Once fixed, just rerun scripts/setup/install; it'll pick up from here!

EOF
        exit 1
    )
    service nginx restart
fi

if [ "$has_appserver" = 0 ]; then
    "$ZULIP_PATH"/scripts/setup/generate_secrets.py --production
    cp -a "$ZULIP_PATH"/zproject/prod_settings_template.py /etc/zulip/settings.py
    if [ -n "$EXTERNAL_HOST" ]; then
        sed -i "s/^EXTERNAL_HOST =.*/EXTERNAL_HOST = '$EXTERNAL_HOST'/" /etc/zulip/settings.py
    fi
    if [ -n "ZULIP_ADMINISTRATOR" ]; then
        sed -i "s/^ZULIP_ADMINISTRATOR =.*/ZULIP_ADMINISTRATOR = '$ZULIP_ADMINISTRATOR'/" /etc/zulip/settings.py
    fi
    ln -nsf /etc/zulip/settings.py "$ZULIP_PATH"/zproject/prod_settings.py
fi

# Restart camo since generate_secrets.py likely replaced its secret key
if [ "$has_camo" = 0 ]; then
    # Cut off stdin because a bug in the Debian packaging for camo
    # causes our stdin to leak to the daemon, which can cause tools
    # invoking the installer to hang.
    # TODO: fix in Debian too.
    service camo restart </dev/null
fi

if [ "$has_rabbit" = 0 ]; then
    if ! rabbitmqctl status >/dev/null; then
        set +x
        cat <<EOF

RabbitMQ seems to not have started properly after the installation process.
Often this is caused by misconfigured /etc/hosts in virtualized environments.
For more information, see:
  https://github.com/zulip/zulip/issues/53#issuecomment-143805121

EOF
        exit 1
    fi
    "$ZULIP_PATH"/scripts/setup/configure-rabbitmq
fi

if [ "$has_postgres" = 0 ]; then
    "$ZULIP_PATH"/scripts/setup/postgres-init-db
fi

if [ "$has_appserver" = 0 ]; then
    deploy_path=$("$ZULIP_PATH"/scripts/lib/zulip_tools.py make_deploy_path)
    mv "$ZULIP_PATH" "$deploy_path"
    ln -nsf /home/zulip/deployments/next "$ZULIP_PATH"
    ln -nsf "$deploy_path" /home/zulip/deployments/next
    ln -nsf "$deploy_path" /home/zulip/deployments/current
    ln -nsf /etc/zulip/settings.py "$deploy_path"/zproject/prod_settings.py
    mkdir -p "$deploy_path"/prod-static/serve
    cp -rT "$deploy_path"/prod-static/serve /home/zulip/prod-static
    chown -R zulip:zulip /home/zulip /var/log/zulip /etc/zulip/settings.py

    if ! [ -e "/home/zulip/prod-static/generated" ]; then
        # If we're installing from a git checkout, we need to run
        # `tools/update-prod-static` in order to build the static
        # assets.
        su zulip -c "/home/zulip/deployments/current/tools/update-prod-static --authors-not-required"
    fi
fi

if [ -e "/var/run/supervisor.sock" ]; then
    chown zulip:zulip /var/run/supervisor.sock
fi

if [ -n "$NO_INIT_DB" ]; then
    set +x
    cat <<EOF

 Success!

 Stopping because --no-init-db was passed.  To complete the installation, run:

   su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database
EOF
    exit 0
fi

su zulip -c '/home/zulip/deployments/current/scripts/setup/initialize-database --quiet'

su zulip -c '/home/zulip/deployments/current/manage.py generate_realm_creation_link'
