MattermostをUbuntu環境にインストール

**Slack**が2022年9月1日を持ってフリープランを改悪(過去履歴を遡れるのが90日まで)するとの[アナウンス](https://slack.com/intl/ja-jp/blog/news/pricing-and-plan-updates)がありました。
Slackの代替としてよく名前のあがるチャットサービスが**Mattermost**で、自前の計算機にインストールすればSlackと同等のサービスを**無制限・無料**で運用することができます。

自身の所属グループにおいてもMattermostを所有の計算機上でオンプレ運用することになりました。
Linuxやサーバー運用に関してあまり詳しくない状態から始めたので、いろいろ躓きながら調べつつやっていたら結局2週間ぐらいかかりました。

この記事では、Mattermostのインストール、初期設定、Slackからのインポート作業について初心者向けに解説しつつ、最後に躓いたポイントをまとめています。

# 環境
・計算機
　OS ：Linux / Ubuntu 20.04.4 LTS
　CPU：Intel x86_64
・Mattermost：ver 7.1.2 (リリースverごとの更新情報は[こちら](https://zenn.dev/kaakaa))
・MySQL：ver 8.0.30


# Mattermostのインストール

### 全体像
最初にインストール完了後の全体像を示しておく。
```
# ディレクトリ構造
root/
  ┣━ opt/mattermost/
                 ┣━ bin/mattermost (実行ファイル)
                 ┣━ config/config.json (設定ファイル)
                 ┣━ data/
                      ┣━ users/    (ユーザー情報)
                      ┣━ import/   (Slack等からのインポート用データを格納)
                      ┣━ 20220801/ (日付に対応するアタッチメントファイル)

# チャットのテキストデータはMySQLのデータベースに保存される
```

## インストール作業
基本的に[公式の手順](https://docs.mattermost.com/install/installing-ubuntu-2004-LTS.html#install-mattermost-server)に従い行った。

### MySQL
今回はインストールにaptを用いた。(環境に応じて好きなインストーラーを選択)
```bash
sudo apt update
sudo apt upgrade
sudo apt install mysql-server
```
初回起動時はrootユーザーの設定を行う。下記コマンド実行後に出てくる案内に従ってパスワードの設定を行う：
```bash
sudo mysql_secure_installation
```
rootユーザーでMySQLにログインし、mattermost用のユーザーアカウント(今回は"mmuser"と名付けた)を作成する：
```
mysql -u root -p
mysql> create user 'mmuser'@'localhost' identified by '(パスワード)';  # (アカウント作成)
mysql> create database mattermost;  # (データベース作成)
mysql> grant all privileges on mattermost.* to 'mmuser'@'localhost';  # (権限付与)
mysql> exit  # (mSQLからログアウト)
```
Mattermostのサーバーは、このアカウントを介してMySQL上のデータベースを操作する。

正しく構築できているかの確認コマンドは以下のようになる：
```
mysql> show databases;  # (データベース一覧)
mysql> select user, host from mysql.user;  # (MySQLユーザー確認)
mysql> show grants for mmuser@localhost;  # (ユーザー権限確認)
```

### Mattermost
tarイメージを取得して展開、必要なフォルダ移動・作成を行う。
```bash
wget https://releases.mattermost.com/7.1.2/mattermost-7.1.2-linux-amd64.tar.gz
tar -xvzf mattermost*.gz
sudo mv mattermost /opt
sudo mkdir /opt/mattermost/data
```
Linuxシステム上にMattermost起動用のユーザーグループを作成し、/opt/mattermost ディレクトリ下の操作権限を付与する。
```bash
sudo useradd --system --user-group mattermost
sudo chown -R mattermost:mattermost /opt/mattermost
sudo chmod -R g+w /opt/mattermost
```
MattermostがMySQLの管理ユーザーmmuserを使える用にする：
```bash
sudo vim /opt/mattermost/config/config.json  # (Mattermostの設定ファイルを書き換える)

"SiteURL":"http://(IPアドレスorドメイン名):8065"
"Drive name":"mysql"
"DataSource":"mmuser:(パスワード)@tcp(localhost:3306)/(データベース名)?charset=utf8mb4,utf8\u0026writeTimeout=30s"
```

### 起動

これで一通り準備ができたのでMattermostを起動してみる：
```bash
# mattermost(システムユーザー) で bin/mattermost を実行
sudo -u mattermost /opt/mattermost/bin/mattermost 
```
10行ぐらいの出力ログが出て、最後に"Server is listening on [::]:8065" みたいなログが出ていたらOK。
(→ エラー表示が出てこのログが出ない場合、[MySQLの設定に問題があるかも](#server-is-listening-on-8065のログが出ない) )

これは、実行状態のMattermostアプリケーションが、ポート番号8065で外部からアクセス来るのを待機している状態。

Mattermostを実行状態で、手元のパソコンのブラウザで`http://(IPアドレス or ドメイン名):8065`をURLとして打ち込めばMattermostのサインアップ画面が出現するはず。
(→ アクセス出来ない場合、[ファイアウォールなどのセキュリティによって外部からのアクセスが遮断されている可能性](#ipアドレス8065-にブラウザからアクセス出来ない) )

### Systemdの設定
Mattermostの起動・停止に関して諸設定を自動化するためにsystemdを設定しておく。
```bash
sudo touch /lib/systemd/system/mattermost.service # 起動用の設定ファイル
sudo vim /lib/systemd/system/mattermost.service
```
以下の内容を書き加える：
```vim 
[Unit]
Description=Mattermost
After=network.target
After=mysql.service
BindsTo=mysql.service

[Service]
Type=notify
ExecStart=/opt/mattermost/bin/mattermost
TimeoutStartSec=3600
KillMode=mixed
Restart=always
RestartSec=10
WorkingDirectory=/opt/mattermost
User=mattermost
Group=mattermost
LimitNOFILE=49152

[Install]
  WantedBy=mysql.service
```
これにて mattermost.service というサービス名で作成された。これ以降は以下のようなsystemctlコマンドを用いて起動・停止ができる。
```bash
sudo systemctl daemon-reload  # (設定を読み込み)
sudo systemctl enable mattermost.service   # (再起動時もsystemdの設定が有効になるようにしておく)
sudo systemctl status mattermost.service   # (起動状態を確認)
sudo systemctl start mattermost.service    # (Mattermostを起動)
sudo systemctl stop mattermost.service     # (Mattermostを停止)
sudo systemctl restart mattermost.service  # (Mattermostを再起動)
```


# 初期設定
管理者ユーザーの作成・メール送信用のSMTP設定・https接続の有効化・日本語検索機能の有効化・チームの削除機能を有効化、を行う。
基本的な設定は、Mattemostログイン中画面での、左上マーク(正方形が3×3個並んだやつ)をクリック、システムコンソールを選択して設定を行う。

### 管理者ユーザを作成
`http://(IPアドレス or ドメイン名):8065`にアクセスし、アカウントを作成する。
この初回接続時のアカウントが自動的に管理者ユーザーとなる。

### SMTP設定
チームへの招待メールや通知メールをMattermost側から送るためにSMTP設定が必須となる。
最も簡単な、outlookアカウントの作成とhotmailサーバーの指定を行った ([公式手順](https://docs.mattermost.com/configure/smtp-email.html))：

・まずは別途outlookアカウントを作成し、hotmail.comのメールアドレスを取得する。
・Mattermost側でSMTPサーバーを設定に登録する。
```:システムコンソール > SMTP
SMTPサーバー：smtp.office365.com
SMTPサーバーポート：587
SMTPサーバーのユーザー名：(your_email@hotmail.com)
SMTPサーバーパスワード：(your_password)
SMTP Server Name to smtp.office365.com
接続のセキュリティ：STARTTLS
```
・招待メールでアカウント作成できるようにする
```:システムコンソール > 利用登録
招待メールを有効にする：有効
```
・本番環境ではセキュリティのためメール確認を有効化しておく
```:システムコンソール > 電子メール
電子メールアドレスの確認が必要：有効
```

### httpsの有効化
外部からMattermostへアクセスする場合は、チャット内容やログイン情報を保持するためhttps通信を有効化する必要がある。
https通信にはTLS証明書が必要であり、TLS証明書の取得にはドメイン名が必要となる。(IPアドレスで取得できるサービスもあるが、結局ドメイン取得した方が安い)

ここでは既にドメイン名を取得したとして、`certbot`を利用したTLS証明書の取得および自動更新設定を説明する。
```
# TLS証明書の取得
sudo apt-get install certbot  # certbotをインストール
sudo certbot certonly --standalone -d $(ドメイン名)
sudo certbot certificates  # 証明書確認
sudo systemctl status certbot.timer  # 証明書自動更新プログラムの状態確認

# よくわからんけど実行 (原文：Activate the CAP_NET_BIND_SERVICE capability to allow Mattermost to bind to low ports)
"sudo setcap cap_net_bind_service=+ep /opt/mattermost/bin/mattermost"
```
https通信を有効化する：
```:システムコンソール > 環境 > ウェブサーバー
サイトURL：(ドメイン名)
接続待ちアドレス：443
ポート80を443へ転送する：有効
接続のセキュリティー：TLS
TLS証明書ファイル：(cert.pemファイルの場所指定)
TLS鍵ファイル：(privkey.pemファイルの場所指定)
Let's Encryptを使用する：有効
```
ファイアウォールなどセキュリティ設定でポート80(http)と443(https)を開けておく。
Mattermostを再起動して完了。


### 日本語検索機能の有効化
Mattermostで検索に必要なインデックス処理を行う機能は**Bleve**と呼ばれている。
Bleveのインデックス保存用のフォルダを作成、書換権限を付与する。
```
sudo mkdir /opt/mattermost/bleveindexes
sudo chown mattermost: /opt/mattermost/bleveindexes
```
`システムコンソール > 実験的機能 > Bleve`を設定する。
全て有効にして、`IndexDir：/opt/mattermost/bleveindexes`に設定、インデックス付与を今すぐ開始するをクリックで完了


### チームの削除機能を有効化
Mattermostでは今のところGUIからチームの削除は出来ない。
`config.json`ファイルを編集することでコマンドからチーム削除できるようにする。
```:config.json > ServiceSetting
"EnableAPITeamDeletion"：true
"EnableAPIUserDeletion"：true
"EnableAPIChannelDeletion": true
```
mmctlコマンドでチーム削除ができる。
```
mmctl team delete ${チーム名}
```


# Slackからのインポート
主に [公式手順(英語)](https://docs.mattermost.com/onboard/migrating-to-mattermost.html#migrating-from-slack)、[日本語解説](https://qiita.com/bezeklik/items/a3882af7f4303bdc3cb6#slack-%E3%81%8B%E3%82%89-mattermost-%E3%81%B8%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%A7%BB%E8%A1%8C) を参照

### Slackデータのエクスポート
Slack側から、ワークスペース名をクリック->「設定とその他管理項目」->「ワークスペースの設定」を選択、エクスポート画面で日付範囲を指定しエクスポート開始、しばらくしたらダウンロードリンクが生成される。

ダウンロードした`slack-bulk-export.zip`にはユーザーアカウント情報とチャット履歴が含まれている。
画像などのアタッチメントファイルはSlackサーバーから別途ダウンロードする必要があり、そのためにSlackアプリ登録およびTokenを発行しておく。詳しい手順は上記リンク参照。

実際のダウンロードは`slack-advanced-exporter`を用いて行う：
```bash
# slack-advanced-exporterをインストール
curl --location --remote-name https://github.com/grundleborg/slack-advanced-exporter/releases/download/v0.4.0/slack-advanced-exporter.linux-amd64.tar.gz
tar --extract --file=slack-advanced-exporter.linux-amd64.tar.gz
mv --verbose slack-advanced-exporter /usr/local/bin/

# アタッチメントファイルとEメールアドレスをSlackサーバーからダウンロードする
slack-advanced-exporter --input-archive slack-bulk-export.zip --output-archive export-with-emails.zip fetch-emails --api-token ${SLACK_TOKEN}
slack-advanced-exporter --input-archive export-with-emails.zip --output-archive export-with-emails-and-attachments.zip fetch-attachments
```
生成された`export-with-emails-and-attachments.zip`には、Slack側の全データが含まれている。

### Mattermostへのインポート
インポート先のMattermostチームを予め作成しておく。

`mmetl`を用いて、このデータをMattermostにインストールしやすいデータ形式への変換を行う。
```bash
# mmetlをインストール
curl --location --remote-name https://github.com/mattermost/mmetl/releases/download/0.0.1/mmetl.linux-amd64.tar.gz
tar --extract --file=mmetl.linux-amd64.tar.gz
mv --verbose mmetl /usr/local/bin/

# ${TEAMid}には、"表示名"ではなく"チームID"(URLに表示される)の方を指定
mmetl transform slack --team ${TEAMid} --file export-with-emails-and-attachments.zip --output mattermost_import.jsonl
```
チャット履歴およびユーザー情報を含む`mattermost_import.jsonl`とアタッチメントファイルを保存した`bulk-export-attachments`が生成される。
これらをまとめてzip化する：
```bash
zip -r mattermost-bulk-import.zip bulk-export-attachments mattermost_import.jsonl
```

Mattermostへのインポートは`mmctl`を用いて行う。

```bash
# mmctlの実行ファイルを移動
ln --symbolic --verbose /opt/mattermost/bin/mmctl /usr/local/bin/

# mmctlを使用
mmctl import upload mattermost-bulk-import.zip  # "/opt/mattermost/data/import" ディレクトリ下にzipファイルをコピーしているだけ
mmctl import list available  # ここで表示されるzipファイルを下のコマンドで指定する
mmctl import process $(File_name).zip

# 実行ジョブの確認
mmctl import job show --json $(ジョブID)
# 全ジョブ履歴の確認
mmctl import job list 
```
いくつかインポート作業中にエラーが出て詰まったのでまとめておく：
→ [ファイルサイズが大きすぎる・ユーザー数が多すぎるなどのエラーが出る](#slackからのインポートでエラー-1)
→ [アタッチメントファイル有りの場合にエラーが出る](#slackからのインポートでエラー-2)
→ [複数のSlackワークスペースからインポートしようとするとエラーが出る](#slackからのインポートでエラー-3)
→ [ステータスが`in_progress`のまま終わらない現象](#slackからのインポートでエラー-4)






# エラー集
### "Server is listening on [::]:8065"のログが出ない
```:error_message
{“level”:“error”,“ts”:1619691579.4546628,“caller”:“sqlstore/supplier.go:258”,
“msg”:“Failed to ping DB”,“error”:“dial tcp: lookup : no such host”,“retrying in seconds”:10}
```
のようなエラーメッセージが出ている場合、MattermostがMySQLのデータベースに接続できでいないことがある。(参考：[同じ症状の人](https://forum.mattermost.com/t/service-not-starting-after-new-installed-mm-5-31-4-lts/11603))
この場合、MySQLのユーザー作成、もしくはそのパスワード情報をconfig.jsonファイルに書き込む箇所で誤っているかもしれない。

### (IPアドレス):8065 にブラウザからアクセス出来ない
Ubuntuサーバーがセキュリティによって外部アクセスを拒否している可能性がある。Ubuntuサーバー側で
```bash
curl -i localhost:8065
```
を実行してhtmlが返ってくるのであれば、mattermostの起動に成功しており、ネットワーク内部からのアクセスは出来ていることが確認できる。

まずはサーバーのポート開放を行う必要がある。(詳しく無い人向け：[ポート開放とはなにか](https://e-craft.io/beginner/port-open/))
```bash
sudo ufw status # ポート状態の確認
sudo ufw allow 8065/TCP # ポート開放
sudo ufw reload # 設定の有効化
```
この他にもセキュリティ関連をチェックしてみると良さそう。
VPS上で仮想サーバーとして構築している場合は、VPS側のファイアウォールでもポート開放する必要があった。


### Slackからのインポートでエラー (1)
アップロード時にいろんなエラーが出るかも。
```
Error: failed to create upload session: : Unable to upload file. File is too large
```
ファイルサイズがMattermost側の設定の限界値を超えているので、`config.json`もしくはGUI側からファイルサイズに関する設定(`"MaxFileSize"`)を書き換える。(初期設定では100MBに設定されている)
設定変更後に`systemctl restart mattermost`で再起動しておくと確実に有効化できる。

他にもチームの最大人数を超えてエラーなどが出た。`"MaxUsersPerTeam"`を書き換えた。(初期設定では50人となっている)


### Slackからのインポートでエラー (2)
Mattermost 7.1.2 ではデフォルトのままmmctlでアタッチメントファイル入りのデータをインポートすると以下のようなエラーが出た
```bash:error_message
[
  {
    〜
    "status": "error",
    "progress": -1,
    "data": {
      "error": "Error while processing bulk import attachments. — attachment \"data/bulk-export-attachments/F035BTPTQ_test-image.png\" not found in map",
      "import_file": "7coajdui93ndmpqrzf_mattermost-bulk-import.zip",
      "line_number": "9"
    }
  }
]
```
なぜか`data/bulk-export-attachments/`下のファイルを読み込む設定になっているっぽいので、手作業で移動してやる。
```bash
mkdir data
mv bulk-export-attachments data/
zip -r mattermost-bulk-import data mattermost_import.jsonl
```
で解決した


### Slackからのインポートでエラー (3)
複数のSlackワークスペースから同一のMattermostサーバーにインポートを行う場合に、次のようなエラーが出た。
```bash:error_message
mmctl import job show --json 9wmqpqhm67y76foesqw  # ジョブ確認コマンド
[
  {
    〜
    "status": "error",
    "progress": -1,
    "data": {
      "error": "We could not count the users. — importUser: An account with that email already exists., invalid input: entity: User field: email value: mail-address@google.com",
      "import_file": "stwsmetagifuume9txg6c_mattermost-bulk-import.zip",
      "line_number": "6"
    }
  }
]
```
これは、複数のSlackワークスペースにおいて同一のメールアドレスを使用しながら異なるユーザーネームを登録している人がいると発生する。
Mattermost側はメールアドレス単位でユーザー情報を1対1に紐づけて管理しているので、バッティングする情報があるとエラーが出る。
解決策としては、`mattermost_import.jsonl`中のユーザー情報とチャット履歴に含まれるユーザーネームを全て既存のIDで置き換えることで通した。


### Slackからのインポートでエラー (4)
ステータスが`in_progress`のまま終わらない現象。これについては明確な原因がわからないが、Twitterで検索したところ同じ症状の人を見かけた。
```bash:error_message
[
  {
    〜
    "status": "in_progress",
    "progress": 0,
    "data": {
      "import_file": "stwsmetagifuume6c_mattermost-bulk-import.zip"
    }
  }
]
```
インポートの合計データサイズが大きい場合に起きるようだったので、Slackからのエクスポートを小分けにして複数回のインポート作業を行って回避した。
in_progress中のインポート作業を停止するコマンドがmmctlに存在しないので困っています ([公式ドキュメント](https://docs.mattermost.com/manage/mmctl-command-line-tool.html#mmctl-logs))。誰かわかる方いれば教えてください。


### その他
・Slackからインポートした日本語チャンネル名は正しく表示されなかったので、手作業で直した。
・全インポート終えた後に、日本語検索有効のためのBleveインデックス付加作業を行うと、途中で止まってしまう (97%ぐらい)。日本語検索自体はできているので無視した。