zulip/scripts/lib/install

373 lines
11 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env bash
set -e
usage() {
cat <<EOF
Usage:
install --hostname=zulip.example.com --email=zulip-admin@example.com [options...]
install --help
Other options:
--certbot
--self-signed-cert
--no-init-db
--cacert
The --hostname and --email options are required,
unless --no-init-db is set and --certbot is not.
EOF
};
# 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:,cacert: -n "$0" -- "$@")"
eval "set -- $args"
while true; do
case "$1" in
--help) usage; exit 0;;
--self-signed-cert) SELF_SIGNED_CERT=1; shift;;
--cacert) export CUSTOM_CA_CERTIFICATES="$2"; shift; 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 >&2
exit 1
fi
## Options from environment variables.
#
# Specify options for apt.
read -r -a APT_OPTIONS <<< "${APT_OPTIONS:-}"
# Install additional packages using apt.
read -r -a 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
set +x
echo "error: --self-signed-cert and --certbot are incompatible" >&2
echo >&2
usage >&2
exit 1
fi
if [ -z "$EXTERNAL_HOST" ] || [ -z "$ZULIP_ADMINISTRATOR" ]; then
if [ -n "$USE_CERTBOT" ] || [ -z "$NO_INIT_DB" ]; then
usage >&2
exit 1
fi
fi
if [ "$EXTERNAL_HOST" = zulip.example.com ] ||
[ "$ZULIP_ADMINISTRATOR" = zulip-admin@example.com ]; then
# These example values are specifically checked for and would fail
# later; see check_config in zerver/lib/management.py.
echo 'error: The example hostname and email must be replaced with real values.' >&2
echo >&2
usage >&2
exit 1
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.
localedef -i en_US -f UTF-8 en_US.UTF-8
export LC_ALL="en_US.UTF-8"
export LANG="en_US.UTF-8"
export LANGUAGE="en_US.UTF-8"
# Check for a supported OS release.
apt-get install -y lsb-release
os_info="$(lsb_release --short --id --release --codename)"
{ read -r os_id; read -r os_release; read -r os_codename; } <<< "$os_info"
case "$os_codename" in
trusty|xenial|stretch|bionic) ;;
*)
set +x
cat <<EOF
Unsupported OS release: $os_codename
Zulip in production is supported only on:
- Debian 9 "stretch"
- Ubuntu 14.04 LTS "trusty" (deprecated)
- Ubuntu 16.04 LTS "xenial"
- Ubuntu 18.04 LTS "bionic"
For more information, see:
https://zulip.readthedocs.io/en/latest/production/requirements.html
EOF
exit 1
esac
if [ "$os_id" = Ubuntu ] && ! apt-cache policy |
grep -q "^ release v=$os_release,o=Ubuntu,a=$os_codename,n=$os_codename,l=Ubuntu,c=universe"; then
set +x
cat <<'EOF'
You must enable the Ubuntu Universe repository before installing
Zulip. You can do this with:
sudo add-apt-repository universe
sudo apt update
For more information, see:
https://zulip.readthedocs.io/en/latest/production/requirements.html
EOF
exit 1
fi
# 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=$(head -n1 /proc/meminfo | awk '{print $2}')
if [ "$mem_kb" -lt 1900000 ]; then
set +x
echo -e '\033[0;31m' >&2
echo "Insufficient RAM. Zulip requires at least 2GB of RAM." >&2
echo >&2
echo -e '\033[0m' >&2
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
cat <<EOF
No SSL certificate found. One or both required files is missing:
/etc/ssl/private/zulip.key
/etc/ssl/certs/zulip.combined-chain.crt
Suggested solutions:
* For most sites, the --certbot option is recommended.
* If you have your own key and cert, see docs linked below
for how to install them.
* For non-production testing, try the --self-signed-cert option.
For help and more details, see our SSL documentation:
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
fi
apt-get -y dist-upgrade "${APT_OPTIONS[@]}"
if ! apt-get install -y \
puppet git curl wget jq \
python python3 python-six python3-six crudini \
"${ADDITIONAL_PACKAGES[@]}"; then
set +x
echo -e '\033[0;31m' >&2
echo "Installing packages failed; is network working and (on Ubuntu) the universe repository enabled?" >&2
echo >&2
echo -e '\033[0m' >&2
exit 1
fi
if [ -n "$USE_CERTBOT" ]; then
"$ZULIP_PATH"/scripts/setup/setup-certbot \
--no-zulip-conf --method=standalone \
"$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
2016-07-12 20:46:49 +02:00
if [ "$VIRTUALENV_NEEDED" = "yes" ]; then
"$ZULIP_PATH"/scripts/lib/create-production-venv "$ZULIP_PATH"
"$ZULIP_PATH"/scripts/lib/create-thumbor-venv "$ZULIP_PATH"
2016-07-12 20:46:49 +02:00
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=0
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
install: Work around a bug in the (our) Debian package for camo. Before this fix, the installer has an extremely annoying bug where when run inside a container with `lxc-attach`, when the installer finishes, the `lxc-attach` just hangs and doesn't respond even to C-c or C-z. The only way to get the terminal back is to root around from some other terminal to find the PID and kill it; then run something like `stty sane` to fix the messed-up terminal settings left behind. After bisecting pieces of the install script to locate which step was causing the issue, it comes down to the `service camo restart`. The comment here indicates that we knew about an annoying bug here years ago, and just swept it under the rug by skipping this step when in Travis. >_< The issue can be reproduced by running simply `service camo restart` under `lxc-attach` instead of the installer; or `service camo start`, following a `service camo stop`. If `lxc-attach` is used to get an interactive shell, these commands appear to work fine; but then when that shell exits, the same hang appears. So, when we start camo we're evidently leaving some kind of mess that entangles the daemon with our shell. Looking at the camo initscript where it starts the daemon, there's not much code, and one flag jumps out as suspicious: start-stop-daemon --start --quiet --pidfile $PIDFILE -bm \ --exec $DAEMON --no-close -c nobody --test > /dev/null 2>&1 \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE -bm \ --no-close -c nobody --exec $DAEMON -- \ $DAEMON_ARGS >> /var/log/camo/camo.log 2>&1 \ || return 2 What does `--no-close` do? -C, --no-close Do not close any file descriptor when forcing the daemon into the background (since version 1.16.5). Used for debugging purposes to see the process output, or to redirect file descriptors to log the process output. And in fact, looking in /proc/PID/fd while a hang is happening finds that fd 0 on the camo daemon process, aka stdin, is connected to our terminal. So, stop that by denying the initscript our stdin in the first place. This fixes the problem. The Debian maintainer turns out to be "Zulip Debian Packaging Team", at debian@zulip.com; so this package and its bugs are basically ours.
2018-01-23 02:27:35 +01:00
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'