AzureでMastodon(非Docker)を構築した話

## 前提
### 準備物
#### Azure
| 設定項目             | 設定値               | 備考                                     |
| -------------------- | -------------------- | ---------------------------------------- |
| VMサイズ             | Standard B1ms        | これ以下だとそもそもインストール出来ない |
| OS                   | Ubuntu 22.04         |                                          |
| 受信セキュリティ規則 | 80、443、sshのポート | 追加する                                 |

#### ドメイン
好きなものを使おう
お名前.comでもGoogle domainsでもどうぞ。

### 注意点
Azureの操作方法は省略します。

## 作業
### OSアップグレード
rootユーザーで作業します。

```
$ sudo su -
[sudo] password for <username>:　#AzureのSSHで作業している場合は出ないかも
# apt update
# apt install -y update-manager
# apt dist-upgrade
# do-release-upgrade -d
```
その後、バージョンを確認します。
`cat /etc/os-release`
```
# cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
```
AzureのSSHで作業しているのであれば、鍵の設定等はいらないです。

### GUIとスリープモードの無効化
Ubuntuくんは賢いので、UpdateするとGUIが有効になります。
有効になるとスリープモードが有効になります。
有効になるとどうなるか？一定時間経過後、サーバーからの応答がなくなります。(2敗)
それを止めます。

```
sudo systemctl set-default multi-user.target
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
```

### ファイアウォールの設定
ufwを使います。
sudoで実行しています。
`ufw default deny`　#とりあえず全部塞ぐやつ
`ufw allow 80`　#httpポートの開放
`ufw allow 443`　#httpsポートの開放
`ufw allow <sshd port>` #ssh用ポートの開放　デフォルトは22
`ufw enable` #ファイアウォールを有効化する

```
# ufw default deny          
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)

# ufw allow 80
Rules updated
Rules updated (v6)

# ufw allow 443
Rules updated
Rules updated (v6)

# ufw allow <sshd port>
Rules updated
Rules updated (v6)

# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)?  ※ここで'y'を入力
Firewall is active and enabled on system startup
```

設定後、動いているか確認します。
`ufw status`
```
# ufw status
Status: active

To                         Action      From
--                         ------      ----
80                         ALLOW       Anywhere
443                        ALLOW       Anywhere
<sshd port>                ALLOW       Anywhere
80 (v6)                    ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)
<sshd port> (v6)           ALLOW       Anywhere (v6)
```

### パッケージのインストール
[公式ドキュメント](https://docs.joinmastodon.org/admin/install/)に沿いつつ、パッケージのインストールしていきます。
手始めにrootでCurlをインストールします。

```
$ sudo su -
# apt install -y curl
```

#### Node.jsのリポジトリを登録
```
# curl -sL https://deb.nodesource.com/setup_16.x | bash -
```
18でもいいですが、18(17以上)だとエラーになってしまうので、下記を実行します。
16でよかったねこれ。(1敗)

```
# export NODE_OPTIONS=--openssl-legacy-provider
```

#### Yarnのリポジトリを登録

```
# curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
# echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
# apt update
```

#### パッケージのインストール
```
# apt install -y imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git-core \
g++ libprotobuf-dev protobuf-compiler pkg-config nodejs gcc autoconf bison build-essential \
libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses-dev libffi-dev libgdbm6 libgdbm-dev \
nginx redis-server redis-tools postgresql postgresql-contrib \
certbot python3-certbot-nginx yarn libidn11-dev libicu-dev libjemalloc-dev
```
コピペでいけます。
公式だと下記となっているので、こっちのほうがいいかもしれないです(未検証)
```
apt install -y \
imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git-core \
g++ libprotobuf-dev protobuf-compiler pkg-config nodejs gcc autoconf \
bison build-essential libssl-dev libyaml-dev libreadline6-dev \
zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev \
nginx redis-server redis-tools postgresql postgresql-contrib \
certbot python3-certbot-nginx libidn11-dev libicu-dev libjemalloc-dev
```
### ユーザーの作成
Mastodonを実行するユーザーを作成します。
```
# adduser --disabled-login mastodon
```
名前やらなにやらいろいろ聞かれますが、全部空欄でいいです。
その後、ユーザーを切り替えます。
```
# su - mastodon
```

### Rubyのインストール
rbenvとrbenv-buildをインストールします。

```
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ cd ~/.rbenv && src/configure && make -C src
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ exec bash
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
```
Rubyをインストールします。

```
$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.0.6
$ rbenv global 3.0.6
```

bundlerもインストールします。
```
$ gem install bundler --no-document
```

mastodonユーザーでの作業は一旦おわりです。
`exit`でrootに戻ります。
```
$ exit
```

### Rubyのバージョンアップ

作業はmastodonユーザーで実行します。
インストールできるバージョンのリストを更新します。
```
# su - mastodon
$ cd .rbenv/plugins/ruby-build
$ git pull
$ cd ~
```
次に更新したリストを確認します。
```
$ rbenv install -l
3.0.6
3.1.4
3.2.2
jruby-9.4.2.0
mruby-3.2.0
picoruby-3.0.0
truffleruby-22.3.1
truffleruby+graalvm-22.3.1

Only latest stable releases for each Ruby implementation are shown.
Use 'rbenv install --list-all / -L' to show all local versions.
```

推奨バージョンをインストールします。
間違っても最新の方がいいでしょ！といって入れてはいけません。(1敗)

```
$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 3.0.6
$ rbenv global 3.0.6
```

バージョンを確認します。
```
$ rbenv version
3.0.6 (set by /home/mastodon/.rbenv/version)
```

### データベースのセットアップ
公式ドキュメントではチューニングがどうのこうの行っていましたが、特にやらなくても動きました。
Postgresqlのバージョンを確認します。
特になにかしていなければ14.7が入っているはず。※2023/5/16現在

```
# sudo -u postgres psql -V
could not change directory to "/root": Permission denied
psql (PostgreSQL) 14.7 (Ubuntu 14.7-0ubuntu0.22.04.1)
```

### Postgresqlのユーザーとデータベースの作成
まず文字コードを変更します。
```
# systemctl stop postgresql
# pg_dropcluster -stop 14 main
# export LANGUAGE="en_US.UTF-8"
# export LANG="en_US.UTF-8"
# export LC_ALL="en_US.UTF-8"
# systemctl start postgresql
# pg_createcluster -e UTF8 -start 14 main
```

次にユーザーとデータベースを作成します。
```
# sudo -u postgres psql
psql (14.7 (Ubuntu 14.7-0ubuntu0.22.04.1))
Type "help" for help.

postgres=# CREATE USER mastodon CREATEDB;
postgres=# CREATE DATABASE mastodon_production WITH ENCODING ‘UTF8’ OWNER mastodon;
```
末尾の`;`を忘れないようにしましょう(n敗)
作成されたユーザーとデータベースを確認します。

```
postgres-# \l
                                       List of databases
        Name         |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
---------------------+----------+----------+-------------+-------------+-----------------------
 mastodon_production | mastodon | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 postgres            | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 template0           | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
                     |          |          |             |             | postgres=CTc/postgres
 template1           | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
                     |          |          |             |             | postgres=CTc/postgres
(4 rows)
```

Nameが`mastodon_production`、Ownerが`mastodon`、Encodingが`UTF8`となっていればOKです。
確認ができたらrootに戻ります。
```
postgres-# \q
```

### Mastodonのインストール
まずはrootからmastodonユーザーに切り替えます。
```
# su - mastodon
```
Githubに公開されているリポジトリからMastodonを`~/live`にコピーし、最新安定版をインストールします。
```
$ git clone https://github.com/tootsuite/mastodon.git live && cd live
$ git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)
$ bundle config deployment 'true'
$ bundle config without 'development test'
$ bundle install -j$(getconf _NPROCESSORS_ONLN)
$ yarn install --pure-lockfile
```

### Mastodonの構成ファイルを作成する
一問一答方式で作ってくれます。べんり。
あとで修正もできますし、再度実行もできるので分かる範囲を埋めていきましょう。
```
$ RAILS_ENV=production bundle exec rake mastodon:setup

Your instance is identified by its domain name. Changing it afterward will break things.
Domain name:<ドメイン名を入力>

Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? (y/N)
<お一人様仕様ですか？ y か n を入力>

Are you using Docker to run Mastodon? (Y/n)
<DockerでMastodon動かしてますか？　ここでは n を入力>

PostgreSQL host: (/var/run/postgresql)
<データベースのホストの場所指定。ここではそのままEnter>

PostgreSQL port: (5432)
<データベースのポート番号指定。ここではそのままEnter>

Name of PostgreSQL database: (mastodon_production)
<データベース名の指定。デフォルトで進めてればそのままEnter>

Name of PostgreSQL user: (mastodon)
<PostgreSQLのユーザー名の指定。デフォルトで進めてればそのままEnter>

Password of PostgreSQL user:
<PostgreSQLユーザーのパスワード。今回は設定していないのでそのままEnter>

Redis host: (localhost)
<Redisのホストの場所指定。ここではそのままEnter>

Redis port: (6379)
<Redisのポート番号指定。ここではそのままEnter>

Redis password:
<Redisのパスワード。これも設定してないのでそのままEnter>

Do you want to store uploaded files on the cloud? (y/N)
<メディアファイル等をAmazon S3等のクラウドサービスへアップロードしますか？ y　か　n　を入力>
※ここで "y" を選択すればアクセスキー等の設定へ進みます。

Do you want to send e-mails from localhost? (y/N)
<mastodonサービス上でユーザーへ送信されるメールはローカルホストから送りますか？ y　か　n　を入力>
※通常はGmailはMailgunなどのサービスを使うので "n" になると思います。
※Azureで構築している場合は、たぶんTwilio SendGridを使っているはずなので例としてそちらを記載します。

SMTP server:smtp.sendgrid.net
SMTP port:587
SMTP username:apikey
SMTP password:<Mail Sendパーミッションを与えたSendGridのAPIキー>
SMTP authentication: plain
SMTP OpenSSL verify mode:peer
Enable STARTTLS:auto
E-mail address to send e-mails "from":<Mastodonから送るやつ>
Send a test e-mail with this configuration right now? (Y/n)
※localhostの質問で "n" と回答すると上記の質問がなされます。
　最後の質問はテストメールを送るか？です。必要に応じて回答してください。

This configuration will be written to .env.production
Save configuration? (Y/n)
<今までの設定を構成ファイルに書き込みますか？ ここは "y" でいいです>

Now that configuration is saved, the database schema must be loaded.
If the database already exists, this will erase its contents.
Prepare the database now? (Y/n)
<データベースの最適化を行いますか？ Y/nどちらでも構いません。どちらにしろ最終的には実行します>

The final step is compiling CSS/JS assets.
This may take a while and consume a lot of RAM.
Compile the assets now? (Y/n)
<アセットの作成を実行しますか？ Y/nどちらでも構いません。どちらにしろ最終的には実行します>

Do you want to create an admin user straight away? (Y/n)
<管理者アカウントを作成しますか？ Y/nどちらでも構いません。なくても動きますし、後でも作れます>
```
構成ファイルは`~/live/.env.production`にありますので、修正したい場合はこちらを編集してください。
完了したらrootに戻ります。

### リバースプロキシの設定と証明書の取得
Nginxの設定とLet'sEncrypt証明書の取得をします。

#### NginxのConfigファイルの作成

設定ファイルのテンプレートをコピーします。
公式とは違うらしいですが、参考にしたサイトと同じようにします。
```
# cp /home/mastodon/live/dist/nginx.conf /etc/nginx/conf.d/mastodon.conf
```

次にコピー先の`/etc/nginx/conf.d/mastodon.conf`を編集していきます。

```
# vim /etc/nginx/conf.d/mastodon.conf

※example.comをドメインに変更します (4箇所)
　また、2箇所のsslを一時的に消去します。

server {
  listen 80;
  listen [::]:80;
  server_name example.com;                                                    ※ここ
  root /home/mastodon/live/public;
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }
}                                                                                                                      
server {
  listen 443 http2;                                                           <-sslを消す
  listen [::]:443 http2;                                                      <-sslを消す
  server_name example.com;                                                    ※ここ

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  # Uncomment these lines once you acquire a certificate:
  # ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;      ※ここ
  # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 
```
エディタはなんでもいいです。
書き換えたら保存し、設定ファイルの確認をします。
```
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
問題なければ設定を読み込みます。
```
# systemctl reload nginx
```

次に、証明書の取得をします。
```
# certbot --nginx -d <yourdomain>
```
実行後、設定ファイルが書き換わっているので、
再度、テンプレートからコピーします。
```
cp /home/mastodon/live/dist/nginx.conf /etc/nginx/conf.d/mastodon.conf
```

先程同様、コンフィグファイルを書き換えます。

```
# vim /etc/nginx/conf.d/mastodon.conf

server {
  listen 80;
  listen [::]:80;          
  server_name <yourdomain>;                                                  ※書き直す
  root /home/mastodon/live/public;
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }
}                                                                                                                      
server {
  listen 443 ssl http2;                                                       
  listen [::]:443 ssl http2;                                                  
  server_name <yourdomain>;                                                  ※書き直す

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  # Uncomment these lines once you acquire a certificate:
  ssl_certificate     /etc/letsencrypt/live/<yourdomain>/fullchain.pem;      ※書き直す
  ssl_certificate_key /etc/letsencrypt/live/<yourdomain>/privkey.pem;        ※書き直す
```
編集が終わったら、また確認と読み込みを行います。
```
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# systemctl reload nginx
```

この状態でWebにつながっていればラッキー。
多分つながらないと思うので、下記手順を試します。

データベース最適化とアセットの作成を試します。
```
# su - mastodon
$ cd live
$ RAILS_ENV=production bundle exec rails db:migrate
$ RAILS_ENV=production bundle exec rails assets:precompile
$ exit　　※mastodonユーザーを抜けrootに戻ります。
```

これでつながればラッキー。
多分まだつながらないと思うので、` /etc/nginx/nginx.conf`の中身をちょっと編集します。
ここでめっちゃ悩みました(3敗)
エラーログ上ではアップストリームがどうのこうのってでます。

```
vim /etc/nginx/nginx.conf

↓↓nginx.confのなかみ↓↓
# user www-data;  ※コメントアウト(消してもいい)
user mastodon;　　※追加
```

ここまでやってようやくゾウさんがバンバンしているはず。

### mastodonサービスの登録と起動
サーバー起動時にmastodonを自動起動するようにします。

```
# cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/

# ls /etc/systemd/system
mastodon-sidekiq.service    mastodon-streaming.service    mastodon-web.service
※色々ファイルがあると思いますが、この3つがあることを確認。あればOK

# systemctl daemon-reload
# systemctl start mastodon-web mastodon-sidekiq mastodon-streaming
# systemctl enable mastodon-web mastodon-sidekiq mastodon-streaming
```

設定は以上です。
アカウント作成後、最後に管理者アカウントを作成します。

### 既存アカウントを管理者に変更する
記入するロールは`Admin`としてください。`admin`ではダメです。(1敗)

```
# su - mastodon
$ cd live
$ RAILS_ENV=production bundle exec bin/tootctl accounts modify <youraccount> --role Admin
OK
```

## おわりｽﾞｲ₍₍ (ง ˘ω˘ )ว ⁾⁾ｽﾞｲ

## 参考にさせてもらったサイト
大変お世話になりました。
[Ubuntu20.04 LTSでmastodonをNon-Dockerで建ててみた](https://qiita.com/oyogetaiyakikun/items/1d32e8e525d0e515d304)
[Ubuntu 22.04を使ってMastodon(非Docker)を動かす！正式リリースされたストア版WSL2で起動](https://pc.watch.impress.co.jp/docs/column/nishikawa/1468283.html)
[Ubuntu 20.04 の自動スリープを無効にする](https://ocg.aori.u-tokyo.ac.jp/member/daigo/comp/memo/?val=valid&typ=all&nbr=2021052501)
[nodeを16から18にアップデートした話](https://zenn.dev/ymmt1089/articles/20221120_node_16to18)
[MastodonのメールサーバにSendGridを使う際のポイント](https://sendgrid.kke.co.jp/blog/?p=7789)