Zulip のインストールスクリプトを読んだ (1/2)

# はじめに
インストール手順に従ってグループチャットアプリ [Zulip](https://zulip.org/index.html) をインストールして動かしてみました。が、マシンを再起動するとメッセージを送受信できなくなりました。何かが動いていないっぽい・・・README.prod.md によると、Zulip は下記のサービスを利用しているとのこと。

- postgresql
- rabbitmq-server
- nginx
- redis
- memcached

私は Zulip を AWS EC2 にインストールしたのですが、エラーログを読むと RabbitMQ の起動に失敗していました。ということで、インストールのスクリプトを読んで何がどのようにインストールされて動いているかを覚書にしていきます。

## 対象バージョンとインストール手順
リンク先のページを読んで Zulip 1.3.7 をインストールしました。

- Zulip 1.3.7
- [Run your own Zulip server](https://zulip.org/server.html)
- [zulip/README.prod.md](https://github.com/zulip/zulip/blob/1.3.7/README.prod.md)

# インストールスクリプト
スクリプトは次の順序で実行されます。

1. [zulip/scripts/setup/install](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/install)
2. [zulip/scripts/lib/install](https://github.com/zulip/zulip/blob/1.3.7/scripts/lib/install)
3. [zulip/scripts/zulip-puppet-apply](https://github.com/zulip/zulip/blob/1.3.7/scripts/zulip-puppet-apply)
4. [zulip/puppet/zulip/manifests/voyager.pp](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/manifests/voyager.pp)
5. [zulip/scripts/setup/configure-rabbitmq](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/configure-rabbitmq)
6. [zulip/scripts/setup/initialize-database](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/initialize-database)

## [zulip/scripts/setup/install](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/install)

```bash
#!/bin/bash
set -e
set -o pipefail
if [ "$EUID" -ne 0 ]; then
    echo "Error: The installation script must be run as root" >&2
    exit 1
fi
mkdir -p /var/log/zulip
"$(dirname "$(dirname "$0")")/lib/install" "$@" 2>&1 | tee -a /var/log/zulip/install.log
```

エラーがあればスクリプトを中止する。
パイプライン内のエラーで中止する。
root ユーザーでなければスクリプトを異常終了させる。
/var/log/zulip にディレクトリを作る。
[zulip/scripts/lib/install](https://github.com/zulip/zulip/blob/1.3.7/scripts/lib/install) を実行する。
エラー出力を標準出力に結合して /var/log/zulip/install.log にログ出力する。

## [zulip/scripts/lib/install](https://github.com/zulip/zulip/blob/1.3.7/scripts/lib/install)
### First, install any updates from the apt repo that may be needed

```bash
#!/bin/bash -xe
```

0 以外のステータスで終了した場合は直ちに終了する。
readline で行を取得する。

```bash
# Assumes we've already been untarred

# First, install any updates from the apt repo that may be needed
wget -O /root/zulip-ppa.asc https://zulip.com/dist/keys/zulip-ppa.asc
apt-key add /root/zulip-ppa.asc
cat >/etc/apt/sources.list.d/zulip.list <<EOF
deb http://ppa.launchpad.net/tabbott/zulip/ubuntu trusty main
deb-src http://ppa.launchpad.net/tabbott/zulip/ubuntu trusty main
EOF

apt-get update
apt-get -y dist-upgrade
apt-get install -y puppet git python
```

apt の公開鍵を追加する。
パッケージのダウンロード元を指定する。
パッケージリストを更新する。
ディストリビューションをアップグレードする。
puppet git python をインストールする。

```bash
mkdir -p /etc/zulip
echo -e "[machine]\npuppet_classes = zulip::voyager\ndeploy_type = voyager" > /etc/zulip/zulip.conf
/root/zulip/scripts/zulip-puppet-apply -f
```

/etc/zulip にディレクトリを作る。
/etc/zulip/zulip.conf に設定ファイルを書き込む。

```zulip.conf
[machine]
puppet_classes = zulip::voyager
deploy_type = voyager
```

[zulip/scripts/zulip-puppet-apply](https://github.com/zulip/zulip/blob/1.3.7/scripts/zulip-puppet-apply) を実行する。

### These server restarting bits should be moveable into puppet-land, ideally

```bash
apt-get -y upgrade
if [ -e "/etc/init.d/nginx" ]; then
    service nginx restart
fi
```

インストールされているパッケージを更新する。
Nginx を再起動する。

```bash
/root/zulip/scripts/setup/generate_secrets.py
cp -a /root/zulip/zproject/local_settings_template.py /etc/zulip/settings.py
ln -nsf /etc/zulip/settings.py /root/zulip/zproject/local_settings.py
```

[generate_secrets.py](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/generate_secrets.py) を実行する。
[local_settings_template.py](https://github.com/zulip/zulip/blob/1.3.7/zproject/local_settings_template.py) を /etc/zulip/settings.py にコピーする。
/etc/zulip/settings.py に /root/zulip/zproject/local_settings.py からシンボリックリンクを張る。

```bash
if ! rabbitmqctl status >/dev/null; then
    set +x
    echo; echo "RabbitMQ seems to not have started properly after the installation process."
    echo "Often, this can be caused by misconfigured /etc/hosts in virtualized environments"
    echo "See https://github.com/zulip/zulip/issues/53#issuecomment-143805121"
    echo "for more information"
    echo
    set -x
    exit 1
fi

/root/zulip/scripts/setup/configure-rabbitmq
```

RabbitMQ が起動していなければ異常終了する。

```bash
/root/zulip/scripts/setup/configure-rabbitmq

/root/zulip/scripts/setup/postgres-init-db
```

[configure-rabbitmq](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/configure-rabbitmq) を実行する。
[postgres-init-db](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/initialize-database) を実行する。

```bash
deploy_path=$(/root/zulip/zulip_tools.py make_deploy_path)
mv /root/zulip "$deploy_path"
ln -nsf /home/zulip/deployments/next /root/zulip
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/local_settings.py
cp -rT "$deploy_path"/prod-static/serve /home/zulip/prod-static
chown -R zulip:zulip /home/zulip /var/log/zulip /etc/zulip/settings.py
chown zulip:zulip /var/run/supervisor.sock
```
[zulip_tools.py](https://github.com/zulip/zulip/blob/1.3.7/zulip_tools.py)  make_deploy_path を実行し deploy_path を取得する。
/root/zulip を $deploy_path に移動する。
シンボリックリンクを張る。

| 先 | 元 |
|---|---|
| /home/zulip/deployments/next | /root/zulip |
| "$deploy_path"| /home/zulip/deployments/next |
| "$deploy_path"| /home/zulip/deployments/current |
| /etc/zulip/settings.py | "$deploy_path"/zproject/local_settings.py |

"$deploy_path"/prod-static/serve を /home/zulip/prod-static にコピーする。
ファイルとディレクトリと所有者を zulip:zulip に変更する。

```bash
cd /home/zulip/deployments/current

set +x
cat <<EOF
 Installation complete!
 Now edit /etc/zulip/settings.py and fill in the mandatory values.
 Once you've done that, please run:
 su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database
 To configure the initial database.
EOF
```

/home/zulip/deployments/current に移動する。

## [zulip/scripts/zulip-puppet-apply](https://github.com/zulip/zulip/blob/1.3.7/scripts/zulip-puppet-apply)

```py
#!/usr/bin/python

import sys
import subprocess
import ConfigParser
import re
```

次のモジュールをインポートする。

- システムパラメータと関数
- サブプロセス管理
- 設定ファイルの構文解析器
- 正規表現操作

```py
force = False
extra_args = sys.argv[1:]

if len(extra_args) and extra_args[0] in ('-f', '--force'):
    force = True
    extra_args = extra_args[1:]

config = ConfigParser.RawConfigParser()
config.read("/etc/zulip/zulip.conf")
```

第一引数が -f ならば force = True にする。
zulip.conf を読み込む。

```py
puppet_config = """
Exec { path => "/usr/sbin:/usr/bin:/sbin:/bin" }
include apt
"""
```

puppet_config を設定する。

```py
for pclass in re.split(r'\s*,\s*', config.get('machine', 'puppet_classes')):
    puppet_config += "include %s\n" % (pclass,)
```

Puppet のクラスを puppet_config に追加する。

```py
puppet_cmd = ["puppet", "apply", "--modulepath=/root/zulip/puppet", "-e", puppet_config]
puppet_cmd += extra_args
```

/root/zulip/puppet を Puppet の実行対象にする。

```py
if force:
    subprocess.check_call(puppet_cmd)
    sys.exit(0)
```

force が True なら、Puppet を実行する。コマンド実行後、正常終了する。

```py
subprocess.check_call(puppet_cmd + ['--noop', '--show_diff'])
```

Puppet を実行せず、差分を表示する。

```py
do_apply = None
while not (do_apply == 'y' or do_apply == 'n'):
    sys.stdout.write("Apply changes? [y/N] ")
    do_apply = sys.stdin.readline().strip().lower()
    if do_apply == '':
        do_apply = 'n'
```

変更を適用するかを問い合わせる。

```py
if do_apply == 'y':
    subprocess.check_call(puppet_cmd)
```

選択が 'y' なら、Puppet を実行する。

## [zulip/puppet/zulip/manifests/voyager.pp](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/manifests/voyager.pp)

```pp
class zulip::voyager {
  include zulip::base
  include zulip::app_frontend
  include zulip::postgres_appdb
  include zulip::redis
```

他のクラスをインクルードする。
これらの詳細は [Zulip のインストールスクリプトを読んだ (2/2)](http://qiita.com/usutani/items/8a1862c13ab056fc7197) に書きます。

- [zulip/puppet/zulip/manifests/base.pp](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/manifests/base.pp)
- [zulip/puppet/zulip/manifests/app_frontend.pp
](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/manifests/app_frontend.pp)
- [zulip/puppet/zulip/manifests/postgres_appdb.pp](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/manifests/postgres_appdb.pp)
- [zulip/puppet/zulip/manifests/redis.pp](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/manifests/redis.pp)

```pp
  apt::source {'zulip':
    location    => 'http://ppa.launchpad.net/tabbott/zulip/ubuntu',
    release     => 'trusty',
    repos       => 'main',
    key         => '84C2BE60E50E336456E4749CE84240474E26AE47',
    key_source  => 'https://zulip.com/dist/keys/zulip.asc',
    pin         => '995',
    include_src => true,
  }
```

apt::source クラスに値を設定する。

- [zulip/puppet/apt/manifests/source.pp](https://github.com/zulip/zulip/blob/1.3.7/puppet/apt/manifests/source.pp)

```pp
  file { "/etc/nginx/sites-available/zulip-enterprise":
    require => Package["nginx-full"],
    ensure => file,
    owner  => "root",
    group  => "root",
    mode => 644,
    source => "puppet:///modules/zulip/nginx/sites-available/zulip-enterprise",
    notify => Service["nginx"],
  }
  file { '/etc/nginx/sites-enabled/zulip-enterprise':
    require => Package["nginx-full"],
    ensure => 'link',
    target => '/etc/nginx/sites-available/zulip-enterprise',
    notify => Service["nginx"],
  }
```

- [zulip/puppet/zulip/files/nginx/sites-available/zulip-enterprise](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/files/nginx/sites-available/zulip-enterprise)

```pp
  file { '/home/zulip/prod-static':
    ensure => 'directory',
    owner  => 'zulip',
    group  => 'zulip',
  }
```

```pp
  file { "/etc/cron.d/restart-zulip":
    ensure => file,
    owner  => "root",
    group  => "root",
    mode => 644,
    source => "puppet:///modules/zulip/cron.d/restart-zulip",
  }
```

- [zulip/puppet/zulip/files/cron.d/restart-zulip](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/files/cron.d/restart-zulip)

```pp
  file { '/etc/postgresql/9.3/main/postgresql.conf.template':
    require => Package["postgresql-9.3"],
    ensure => file,
    owner  => "postgres",
    group  => "postgres",
    mode   => 644,
    source => "puppet:///modules/zulip/postgresql/postgresql.conf.template"
  }
```

- [zulip/puppet/zulip/files/postgresql/postgresql.conf.template](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip/files/postgresql/postgresql.conf.template)

```pp
  # We can't use the built-in $memorysize fact because it's a string with human-readable units
  $total_memory = regsubst(file('/proc/meminfo'), '^.*MemTotal:\s*(\d+) kB.*$', '\1', 'M') * 1024
  $half_memory = $total_memory / 2
  $half_memory_pages = $half_memory / 4096

  file {'/etc/sysctl.d/40-postgresql.conf':
    ensure => file,
    owner  => 'root',
    group  => 'root',
    mode   => 644,
    content =>
"kernel.shmall = $half_memory_pages
kernel.shmmax = $half_memory
# These are the defaults on newer kernels
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
"
    }
```

共有メモリとカーネルパラメータを設定する。

- [zulip/puppet/zulip_internal/files/postgresql/40-postgresql.conf.master](https://github.com/zulip/zulip/blob/1.3.7/puppet/zulip_internal/files/postgresql/40-postgresql.conf.master)

```pp
  exec { "sysctl_p":
    command   => "/sbin/sysctl -p /etc/sysctl.d/40-postgresql.conf",
    subscribe => File['/etc/sysctl.d/40-postgresql.conf'],
    refreshonly => true,
  }
```

カーネルパラメータを変更する。

```pp
  exec { 'pgtune':
    require => Package["pgtune"],
    # Let Postgres use half the memory on the machine
    command => "pgtune -T Web -M $half_memory -i /etc/postgresql/9.3/main/postgresql.conf.template -o /etc/postgresql/9.3/main/postgresql.conf",
    refreshonly => true,
    subscribe => File['/etc/postgresql/9.3/main/postgresql.conf.template']
  }
```

[PGTune](http://pgtune.leopard.in.ua) で PostgreSQL をチューニングする。マシンのメモリの半分を使用する。

- [zulip/puppet/zulip/files/postgresql/postgresql.conf.template](https://github.com/zulip/zulip/blob/master/puppet/zulip/files/postgresql/postgresql.conf.template)

```pp
  exec { 'pg_ctlcluster 9.3 main restart':
    require => Exec["sysctl_p"],
    refreshonly => true,
    subscribe => [ Exec['pgtune'], File['/etc/sysctl.d/40-postgresql.conf'] ]
  }
}
```

pgtune を実行する。

## [zulip/scripts/setup/configure-rabbitmq](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/configure-rabbitmq)

```bash
#!/bin/sh -xe
#
# Delete the "guest" default user and replace it with a Zulip user
# with a real password

RMQPW=$("$(dirname "$0")/../../bin/get-django-setting" RABBITMQ_PASSWORD)
sudo rabbitmqctl delete_user zulip || true
sudo rabbitmqctl delete_user guest || true
sudo rabbitmqctl add_user zulip "$RMQPW"
sudo rabbitmqctl set_user_tags zulip administrator
sudo rabbitmqctl set_permissions -p / zulip '.*' '.*' '.*'
```

デフォルトユーザーを削除し、Zulip ユーザーに置き換える。

- [zulip/bin/get-django-setting](https://github.com/zulip/zulip/blob/1.3.7/bin/get-django-setting)

## [zulip/scripts/setup/initialize-database](https://github.com/zulip/zulip/blob/1.3.7/scripts/setup/initialize-database)

```bash
#!/bin/bash -xe

# Change to root directory of the checkout that we're running from
cd "$(dirname "$0")/../.."

python manage.py checkconfig

python manage.py migrate --noinput
python manage.py createcachetable third_party_api_results

if ! python manage.py initialize_voyager_db; then
    set +x
    echo
    echo -e "\033[32mPopulating default database failed."
    echo "After you fix the problem, you will need to do the following before rerunning this:"
    echo "  * supervisorctl stop all # to stop all services that might be accessing the database"
    echo "  * scripts/setup/postgres-init-db # run as root to drop and re-create the database"
    echo -e "\033[0m"
    set -x
    exit 1
fi

supervisorctl restart all

echo "Congratulations!  You have successfully configured your Zulip database."
echo "If you haven't already, you should configure email in /etc/zulip/settings.py"
echo "And then you should now be able to visit your server over https and sign up using"
echo "an email address that ends with @ADMIN_DOMAIN (from your settings file)."
echo ""
echo "See README.prod.md for instructions on how to confirm your Zulip install is healthy, "
echo " change ADMIN_DOMAIN, debug common issues, and otherwise finish setting things up."
```