私的サーバー構築日誌：Mattermostとリバースプロキシと Let's Encrypt

# 能書き

[私的サーバー構築日誌：仕切り直し](https://qiita.com/katz_engineer/items/1f21de2bd9072c260ae6)からの自宅サーバー構築の続きです。

一度は諦めたリバースプロキシ下でのMattermostですが、ふとした拍子に公式の解説を発見しまして、それを元に追求してみました。この記事はその成果です。

ちなみに現在は、公式のリバースプロキシ設定そのままでは動きません。nginxの方で何やら仕様変更があった模様です。

# 目標

- MattermostサーバーをDockerで建てる
- リバースプロキシにMattermostの設定を追加

[GitLabサーバー](https://qiita.com/katz_engineer/items/78d5e4c4c975988b134b)に同梱されているMattermostサーバーは、リバースプロキシと同時には利用できない模様ですので、諦めました。詳しくは後述します。

# 参考文献

MattermostをDockerでインストールする為の公式ドキュメントです。英語なので自動翻訳してどうぞ。

- [Install Mattermost via Docker - Mattermost](https://docs.mattermost.com/install/install-docker.html)

Mattermostをプロキシ越しに動かす為の公式ドキュメントです。こちらも英語です。

- [Configure NGINX as a proxy for Mattermost server - Mattermost](https://docs.mattermost.com/install/config-proxy-nginx.html)

上記の通りにnginxでリバースプロキシを設定すると、2つの警告と1つのエラーが発生します。

- [nginxを再起動するとエラーが出る　nginx: [warn] the “listen … http2” directive is deprecated - Linuxサーバー備忘録](https://linux.okinawa/middlewear/nginx/136/)
- [nginx: [warn] the "http2_push_preload" directive is obsolete & the "listen ... http2" directive is deprecated #1445 - GitHub](https://github.com/laravel/valet/issues/1445) : 正直、よくわからんので、今回は単純にhttp2_push_preloadディレクティブを削除しました。
- [nginx: [emerg] unknown “tls1_3_early_data” variable - icod.de Webentwicklung](https://blog.icod.de/2022/10/19/nginx-emerg-unknown-tls1_3_early_data-variable/)

ちなみに、GitLabに関しては記事を投稿済です。今回はこれの続きになります。

- [私的サーバー構築日誌：GitLabとリバースプロキシと Let's Encrypt - Qiita](https://qiita.com/katz_engineer/items/78d5e4c4c975988b134b)

それからGitLabに同梱されているMattermostの有効化についても、以前、記事を投稿しています。今回これは諦めますので、一つの参考記事として。

- [Mattermost - 自宅サーバー構築譚：GitLabとMattermostをインストール - Qiita](https://qiita.com/katz_engineer/items/4e9331f6f9fcd17f9ae5#mattermost)

# GitLab同梱のMattermostはリバースプロキシとの相性が最悪

GitLab-EE Omnibus版に同梱されていて、有効にすればGitLabと連携してハッピーになれるMattermostですが。

有効にするには、まず、```gitlab.rb```の```mattermost_external_url```を設定する必要があります。

```console
mattermost_external_url 'http://mattermost.example.com'
```

ところがですね。ここを http ではなくて https にすると、502 BadGateway エラーになってしまいます。恐らくは https の時に中で何か特別な処理をしようとするんじゃないかと疑ってます。例えばLet'sEncryptにアクセスして証明書を勝手に取ってこようとするとか。どうなんでしょうね。

ちなみにhttpのままにしておくと、GitLabとの連携が上手くいかなくなります。GitLab側のGUIでサインインした後にリダイレクトする際、そのリダイレクト先に上記```mattermost_external_url```が使われるようですので、ここがきちんと正しく設定できないとリダイレクト先が無いというエラーになってしまいます。

と言う訳で、手詰まりです。どうしようもありません。私は諦めました。

# DNS

まず最初にDNSを設定します。外からアクセス可能にする関係で、お金を出して買った独自ドメインを使用して下さい。ここでは仮にexample.comにしておきますが、このまま設定しないで下さい。

```console:ホスト側
MY_DOMAIN=example.com
APPL_NAME=mattermost
```

```console:ホスト側
APPL_PORT=8065
MATTERMOST_DOMAIN=$APPL_NAME.$MY_DOMAIN
```

そして外部のDNSにこれを設定し、家庭内ルーターのポートフォワードなども設定しておきます。この辺りの手順はそれぞれ異なりますので、各々のマニュアルなどを参照して下さい。

そして家庭内DNSです。Unboundで上記のドメインを設定します。

サーバーの家庭内でのIPアドレスは、私の環境では下記の通りです。

```console:ホスト側
HOST_IPADDR=172.16.1.2
```

CNAMEの設定は面倒なようなので、Aレコードを設定します。

```console:ホスト側
cd /etc/unbound/unbound.conf.d/
cat <<___ | sudo tee -a machines.list >/dev/null
local-data: "$MATTERMOST_DOMAIN. 3600000 IN A $HOST_IPADDR"
___
```

設定したら文法チェックして、Unboundに読み込みます。

```console:ホスト側
unbound-checkconf
sudo systemctl restart unbound
```

そしてSubversion登録。

```console:ホスト側
sudo svn st /etc
```

```console:ホスト側
sudo svn st /etc | grep "^?" | cut -b9- | sudo xargs -I{} find {} -type f -or -type d -or -type l | sudo xargs -t svn add
sudo svn ci /etc -m"DNS setting for Mattermost"
```

# Mattermost

## Dockerで動かす

```console:ホスト側
cd
git clone https://github.com/mattermost/docker docker-$APPL_NAME
cd docker-$APPL_NAME
cp env.example .env
```

ここで、PostgreSQLのmmuserユーザーのパスワードを決めます。

```console:ホスト側
read -s MMPWD
```

そして```.env```ファイルに必要な修正をします。まずは修正内容が正しいか確認。

```console:ホスト側
sed -e"/^DOMAIN=/c DOMAIN=$MATTERMOST_DOMAIN" -e"/^TZ=/c TZ=JST" -e"/^POSTGRES_PASSWORD=/c POSTGRES_PASSWORD=$MMPWD" .env | diff .env -
```

確認できたら反映します。

```console:ホスト側
sed -i -e"/^DOMAIN=/c DOMAIN=$MATTERMOST_DOMAIN" -e"/^TZ=/c TZ=JST" -e"/^POSTGRES_PASSWORD=/c POSTGRES_PASSWORD=$MMPWD" .env
```

引き続き[参考文献](https://docs-mattermost-com.translate.goog/install/install-docker.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja)通りに作業します。

```console:ホスト側
mkdir -p ./volumes/app/mattermost/{config,data,logs,plugins,client/plugins,bleve-indexes}
sudo chown -R 2000:2000 ./volumes/app/mattermost
```

いよいよ起動。

```console:ホスト側
docker compose -f docker-compose.yml -f docker-compose.without-nginx.yml up -d
```

起動確認。まずはHTTPレスポンスが成功を返している事をエイヤッと確認します。

```console:ホスト側
curl -i http://$HOST_IPADDR:$APPL_PORT/ 2>/dev/null | head -n1 | diff -s - <(echo "HTTP/1.1 200 OK"$'\r')
```

```Files - and /dev/fd/63 are identical```と表示されればOK。

次にブラウザで http://172.16.1.2:8065/ にアクセスします。

<image>

リダイレクトされて最初の画面が表示されればOK。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/592da549-8f80-6b22-8140-4427ea22d812.png)

# リバースプロキシ

## 最初の設定

[以前、GitLab用にリバースプロキシを設定しました](https://qiita.com/katz_engineer/items/78d5e4c4c975988b134b)。この時のnginxを利用します。

```console:ホスト側
cd
cd docker-proxy
cat <<___ >config/nginx/$APPL_NAME.conf
server {
    listen        80;
    server_name   $MATTERMOST_DOMAIN;
    server_tokens off;

    location ^~ /.well-known/acme-challenge/ {
        root   /usr/share/nginx/html/ssl-proof;
    }

    location / {
        proxy_pass http://$HOST_IPADDR:$APPL_PORT;
    }
}
___
```

設定ファイルをチェック。

```console:ホスト側
docker compose exec proxy nginx -t
```

問題無ければnginxに読み込みます。

```console:ホスト側
docker compose exec proxy nginx -s reload
```

この段階でブラウザから http://mattermost.example.com/ を表示できます。リダイレクトされて、最初の画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/489bcfd7-7ec4-b66a-cbda-d2a18be283d1.png)


curlコマンドを使う場合は下記。

```console:ホスト側
curl -i http://$MATTERMOST_DOMAIN/
```

## Let's Encrypt 証明書を取得

[Let's Encrypt で証明書を取得して設定](https://qiita.com/katz_engineer/items/78d5e4c4c975988b134b#lets-encrypt-%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%82%92%E5%8F%96%E5%BE%97)します。その為に必要なnginxの設定は、前節で仕込み済みです。

```console:ホスト側
cd
cd docker-proxy
docker compose exec proxy certbot certonly --register-unsafely-without-email --agree-tos --webroot -w /usr/share/nginx/html/ssl-proof -d $MATTERMOST_DOMAIN
```

Mattermostのリバースプロキシの設定です。エラーも対処済みです。

```console:ホスト側
cat <<___ >config/nginx/$APPL_NAME.conf
upstream backend {
    server $HOST_IPADDR:$APPL_PORT;
    keepalive 32;
}

server {
    listen        80;
    server_name   $MATTERMOST_DOMAIN;
    server_tokens off;

    location ^~ /.well-known/acme-challenge/ {
        root   /usr/share/nginx/html/ssl-proof;
    }

    location / {
        return 301 https://\$server_name\$request_uri;
    }
}

server {
    listen      443 ssl;
    http2       on;
    server_name $MATTERMOST_DOMAIN;

    ssl_certificate     /etc/letsencrypt/live/$MATTERMOST_DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$MATTERMOST_DOMAIN/privkey.pem;
    ssl_session_timeout 1d;

    ssl_early_data on;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:50m;
    # HSTS (ngx_http_headers_module is required) (15768000 seconds = six months)
    add_header Strict-Transport-Security max-age=15768000;
    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;

    add_header X-Early-Data \$ssl_early_data;

    location ~ /api/v[0-9]+/(users/)?websocket$ {
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
        client_max_body_size 50M;
        proxy_set_header Host \$http_host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Frame-Options SAMEORIGIN;
        proxy_buffers 256 16k;
        proxy_buffer_size 16k;
        client_body_timeout 60;
        send_timeout 300;
        lingering_timeout 5;
        proxy_connect_timeout 90;
        proxy_send_timeout 300;
        proxy_read_timeout 90s;
        proxy_http_version 1.1;
        proxy_pass http://backend;
    }

    location / {
        client_max_body_size 100M;
        proxy_set_header Connection "";
        proxy_set_header Host \$http_host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Frame-Options SAMEORIGIN;
        proxy_buffers 256 16k;
        proxy_buffer_size 16k;
        proxy_read_timeout 600s;
        proxy_http_version 1.1;
        proxy_pass http://backend;
    }
}
___

```

```console:ホスト側
docker compose exec proxy nginx -t
```

```console:ホスト側
docker compose exec proxy nginx -s reload
```

# ブラウザで確認と設定

ここまで来たら、ブラウザでアクセスして、実際に試してみます。残念ながらLDAPしてないしGitLabも連携してないので、Mattermost独自ユーザーの登録からになります。

ブラウザで https://mattermost.example.com/ にアクセスすると最初の画面が表示されます。デスクトップアプリにするか、このままブラウザで表示するか、の選択ですね。好みに応じてどちらでもいいんですが、今回はブラウザを選択しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/39e44749-1df2-4cb7-5894-922b62e16f24.png)

Let's get started 画面になります。最初は管理者アカウント作成から始めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/36674eb4-85a4-36a4-6330-b254b4b26e1b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/d456dfc7-c0e3-6892-40df-5f481c174543.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/eba1b23d-a5d8-3dca-36c8-823e3ae617d5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/e12e75f7-0e4a-5cee-6e60-f874ddbf4177.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107218/f77e28b0-de95-f093-d621-943366f98039.png)

以下、Mattermostの操作になりますので、そちらを調べてください。アカウントは2つ作成し、ちゃんとリアルタイムでチャットできる事を確認しましょう。

1. [EMailサーバー](https://qiita.com/katz_engineer/items/637f6708ed83b58f30ef)を[設定](https://docs.mattermost.com/configure/smtp-email.html)
2. [ユーザーに招待メールを送信](https://docs.mattermost.com/collaborate/invite-people.html)
3. [サインイン](https://docs.mattermost.com/collaborate/access-your-workspace.html)

ただ、どうも上手くいきませんな。操作に関する記事を検索しても、バージョンが合わないせいか記述が不正確だし、いまいち頼りにならない。少し研究してみます。