はじめに
Mastodon (マストドン)、流行ってますよねー。
ということで 自宅のサーバ にインスタンスを構築してみました。
ただ、既に他の用途でも運用中のサーバなので既存システムとうまくすり合わせる必要があり、ちょっとハマったのでメモしてみます。
使用したソフトのバージョン
項目 | 詳細 |
---|---|
Mastodon | v1.2.2 |
OS | CentOS 7.3 (x86_64) |
HTTPd | Apache 2.4 |
もともと CentOS 7 (x86_64) + Apache 2.4 系の構成だったので、それに合わせる形を取りました。
システム構成
Mastodon の公式解説 では Nginx をフロントエンドとして用いていますが、既に入っている Apache にその役目を持たせました。
名前ベースのバーチャルホストにより、https://mstdn.yamachan.org/ としてアクセスされた場合は Mastodon に、https://tokyo.yamachan.org/ としてアクセスされた場合はこれまで既存 Web ページをそのまま表示するという形を取りました。
作業手順
Let's Encrypt で Web サーバを SSL 化する
Let's Encrypt でサーバ証明書を入手して Apache の SSL/TLS 暗号化通信 (https) を有効化します。
まず、EPEL を導入し、Let's Encrypt のクライアント certbot を入れました。
# yum install epel-release
# yum install certbot
証明書取得のみを行うため、引数 "certonly" 付きで実行。
# certbot certonly
ドメイン名と Webroot (例 /var/www/html) を入力して証明書を取得します。
これを Mastodon 用・既存 Web ページ用それぞれのサブドメインに対して実施しました。
あとは、公式の解説文書 を見ながら設定していきました。
# yum install mod_ssl
# vi /etc/httpd/conf.d/ssl.conf
まずは既存 Web ページ側で試してみて、SSL 通信が動作することだけ確認しておきます。
SSLCertificateKeyFile /etc/letsencrypt/live/tokyo.yamachan.org/privkey.pem
SSLCertificateFile /etc/letsencrypt/live/tokyo.yamachan.org/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/tokyo.yamachan.org/chain.pem
# systemctl reload httpd
https://~ でアクセスしてみて無事に表示されれば、まずは一安心。
Let's Encrypt は定期更新が必要なので、適宜 cron.daily にでも入れておきましょう。
証明書の更新後は Apache の reload が必要な模様。
#!/bin/sh
/usr/bin/certbot renew
/bin/systemctl reload httpd.service
Mastodon を入れる
素直に Docker あり版 で入れることにします。
とりあえず SELinux は一時的に Permissive にしました。構築出来たら後で改めて Enforcing にします。
# setenforce 0
Docker を導入する
# yum install docker
# systemctl enable docker
# systemctl start docker
Docker Compose とやらも要るんだとか。とりあえず 公式の手順 に沿って cURL で入れました。
# curl -L https://github.com/docker/compose/releases/download/1.12.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
一般ユーザで使いたいので、socket ファイルの所有グループを dockerroot に変えつつ、作業用ユーザを dockerroot に入れる。
# gpasswd -a USERNAME dockerroot
# chgrp dockerroot /var/run/docker.sock
# su USERNAME
$ docker ps
エラーにならなければ大丈夫らしいとのこと。
Mastodon 本体を導入する
公式 GitHub からおもむろに落とします。
# yum install git
$ git clone https://github.com/tootsuite/mastodon.git
現時点での最終安定版をチェックアウト。
https://github.com/tootsuite/mastodon/releases の最新のタグのものを落とすことになります。
$ cd mastodon/
$ git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
環境設定を行います。
まず、docker-compose.yml を開いてコメントアウトされている volume を有効化 (データを永続化) します。
version: '2'
services:
db:
restart: always
image: postgres:alpine
### Uncomment to enable DB persistance
volumes:
- ./postgres:/var/lib/postgresql/data
redis:
restart: always
image: redis:alpine
### Uncomment to enable REDIS persistance
volumes:
- ./redis:/data
ここでビルドを実行。
$ docker-compose build
続いて secret を 3 つ作成。一個目の時だけ色々走るので時間が掛かる。
$ docker-compose run --rm web rake secret
(なんやかんや走って…)
019ab2fba0145 ... 8ab0e3
$ docker-compose run --rm web rake secret
a039b12ce89fa ... 84ffde
$ docker-compose run --rm web rake secret
3c439ab43821d ... f8b201
上記の secret を基に、.env.production への設定を実施。
基本的には公式手順のまんま。唯一違うのは何となく PostgreSQL の DB にパスワードを付けたみたことくらい (意味あるのかなこれ?)。
# Service dependencies
DB_PASS=(my-postgres-password)
# Federation
LOCAL_DOMAIN=mstdn.yamachan.org
LOCAL_HTTPS=true
# Application secrets
# Generate each with the `rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
PAPERCLIP_SECRET=019ab2fba0145 ... 8ab0e3
SECRET_KEY_BASE=a039b12ce89fa ... 84ffde
OTP_SECRET=3c439ab43821d ... f8b201
# Optionally change default language
DEFAULT_LOCALE=ja
# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
# then set SMTP_AUTH_METHOD to 'none' and leave SMTP_LOGIN and SMTP_PASSWORD blank
SMTP_SERVER=(my-smtp-server)
SMTP_PORT=587
SMTP_LOGIN=(my-smtp-account)
SMTP_PASSWORD=(my-smtp-password)
SMTP_FROM_ADDRESS=(my-admin-mailaddr)
PostgreSQL のパスワードをコンテナ側で設定します。
$ docker exec -it mastodon_db_1 /bin/bash
bash-4.3# su - postgres
(container hostname):~$ psql
postgres=# \du
postgres=# alter role postgres with password '(my-postgres-password)';
postgres=# \q
(container hostname):~$ exit
bash-4.3# exit
ここまで来たら、データベースのマイグレーションと、関連ファイルの事前準備をします。
$ docker-compose run --rm web rails db:migrate
$ docker-compose run --rm web rails assets:precompile
Apache でリバースプロキシを設定する
さてここまで来たら起動!・・・の前に。リバースプロキシを設定しないとまともに動かないので設定します。
まず、名前ベースのバーチャルホストにするために ssl.conf 内の ## SSL Virtual Host Context
以下を削除しました。
##
## SSL Virtual Host Context
##
(これ以降をまるっと削除)
次に、conf.d 内に以下のようなバーチャルホスト定義用の設定ファイル virtualhost.conf を追加しました。
長いので分けて掲載します。
従来サービスの http://~ 側
必要最小限の設定だけしておきます (ほとんどの項目は httpd.conf でグローバルに設定しているため)。
# Name-based Virtual Host Settings
#### tokyo.yamachan.org:80 ####
<VirtualHost *:80>
# ServerName and DocumentRoot are minimally required
ServerName tokyo.yamachan.org
DocumentRoot /var/www/html
</VirtualHost>
従来サービスの https://~ 側
もともと ssl.conf に記載されていた内容をほぼコピペ。
#### tokyo.yamachan.org:443 ####
<VirtualHost *:443>
# Copy from ssl.conf
ServerName tokyo.yamachan.org
DocumentRoot /var/www/html
ErrorLog logs/ssl_error_log
TransferLog logs/ssl_access_log
LogLevel warn
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA
SSLCertificateKeyFile /etc/letsencrypt/live/tokyo.yamachan.org/privkey.pem
SSLCertificateFile /etc/letsencrypt/live/tokyo.yamachan.org/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/tokyo.yamachan.org/chain.pem
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars
</Files>
<Directory "/var/www/cgi-bin">
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-5]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
CustomLog logs/ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>
Mastodon 環境の http://~ 側
基本的には https:// にリダイレクトしたいので RewriteEngine を使用して飛ばしちゃいますが、全て飛ばすと今度は Let's Encrypt が動作しなくなってしまいます。
そこで、Let's Encrypt が用いる /.well-known/acme-challenge
以下だけは Rewrite しないように追加ルールを入れました。
#### mstdn.yamachan.org:80 ####
<VirtualHost *:80>
# ServerName and DocumentRoot are minimally required
ServerName mstdn.yamachan.org
DocumentRoot /var/www/html/mstdn
RewriteEngine on
# for Let's Encrypt
RewriteRule "^/.well-known/acme-challenge" "-" [END]
RewriteRule "^/(.*)" "https://mstdn.yamachan.org/$1"
ErrorLog "logs/mstdn_error_log"
LogLevel warn
<IfModule log_config_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
<IfModule logio_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
CustomLog "logs/mstdn_access_log" combined
</IfModule>
</VirtualHost>
Mastodon 環境の https://~ 側
Streaming API は WebSocket を使用しているので、明示的に ws://~ に飛ばします (要 mod_proxy_wstunnel)。
それ以外はフツーにリバースプロキシとして設定。
設定値は 公式の Nginx の設定 に倣っています。Streaming API のバックエンドが localhost:4000 なのに対してそれ以外は 127.0.0.1:3000 なのは何か違うんですかね…?
#### mstdn.yamachan.org:443 ####
<VirtualHost *:443>
# Copy from ssl.conf
ServerName mstdn.yamachan.org
DocumentRoot /var/www/html/mstdn
# for Let's Encrypt
# Let's Encrypt only use non-secured http while certification,
# so this setting is not needed.
# ProxyPass /.well-known !
ProxyPass /api/v1/streaming ws://localhost:4000
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse /api/v1/streaming ws://localhost:4000
ProxyPassReverse / http://mstdn.yamachan.org/
ErrorLog logs/mstdn_ssl_error_log
TransferLog logs/mstdn_ssl_access_log
LogLevel warn
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA
SSLCertificateKeyFile /etc/letsencrypt/live/mstdn.yamachan.org/privkey.pem
SSLCertificateFile /etc/letsencrypt/live/mstdn.yamachan.org/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/mstdn.yamachan.org/chain.pem
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars
</Files>
<Directory "/var/www/cgi-bin">
SSLOptions +StdEnvVars
</Directory>
# tell frontend protocol to backend
RequestHeader set X-Forwarded-Proto "https"
BrowserMatch "MSIE [2-5]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
CustomLog logs/mstdn_ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>
Firewall に穴をあける
Apache のプロキシ設定を済ませても、これだけだと Firewall によってガードされてしまいます。
とりあえず docker の仮想ネットワークを処理対象に追加しつつ、port 3000/tcp, 4000/tcp を開放。
ゾーンの概念を使うとか、もっといい方法がありそうだけどとりあえずこれでいいかなぁ…。
# firewall-cmd --permanent --add-interface=docker0
# firewall-cmd --permanent --add-port=3000/tcp
# firewall-cmd --permanent --add-port=4000/tcp
さあ、動かしてみよう!
$ docker-compose up -d
# systemctl reload httpd
名前ベースで (今回の場合だと https://mstdn.yamachan.org として) アクセス出来たら OK!
とりあえず自分のアカウントを作ってしまいましょう。
アカウントに管理権限を与える
統計情報の確認やサーバ全体の設定変更、ユーザの追放・他インスタンスの拒否などをするための権限を特定のアカウントに与えます。
$ docker-compose exec web bundle exec rails mastodon:make_admin USERNAME=(your account)
これでひとまず使えるはず。
Daily で動かすべきバッチ処理やらはテキトーに設定しましょー。
補足 : SELinux 環境下でもちゃんと動くようにする
試しに動かすだけならともかく、公開するつもりなら SELinux は有効化したいですよね。
基本的には、/var/log/audit/audit.log
とにらめっこしながら denied と怒られている行を片っ端から潰していくのでいいはず…。
httpd
type=AVC msg=audit(**********.***:******): avc:
denied { name_connect } for pid=***** comm="httpd"
dest=3000
scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:ntop_port_t:s0
tclass=tcp_socket
"httpd" (Apache) による port 3000 へのアクセスが拒否されている。
まずは httpd によるネットワークリレーを有効にする。
# setsebool -P httpd_can_network_relay on
更に、port 3000 や 4000 への通信をプロキシで用いるポート扱いにしてしまう。
# semanage port -a -t http_cache_port_t -p tcp 3000
# semanage port -a -t http_cache_port_t -p tcp 4000
redis, postgres
type=AVC msg=audit(**********.***:******): avc:
denied { remove_name } for pid=***** comm="redis-server"
name="********" dev="sda*" ino=*****
scontext=system_u:system_r:svirt_lxc_net_t:s0:c****,c****
tcontext=system_u:object_r:user_home_t:s0
tclass=dir
type=AVC msg=audit(**********.***:******): avc:
denied { read write } for pid=***** comm="postgres"
name="********" dev="sda*" ino=*****
scontext=system_u:system_r:svirt_lxc_net_t:s0:c****,c****
tcontext=system_u:object_r:user_home_t:s0
tclass=file
"redis-server" "postgres" によるディレクトリ・ファイルへのアクセスが拒否されている。
Mastodon の導入先のディレクトリのコンテキストを svirt_sandbox_file_t
に設定すればよい。
(仮想マシンのサンドボックスである旨を明示することで、読み書きを許可することになる?)
# chcon -Rt svirt_sandbox_file_t postgres
# chcon -Rt svirt_sandbox_file_t redis
# chcon -Rt svirt_sandbox_file_t public/assets
# chcon -Rt svirt_sandbox_file_t public/system
最後に
祈りながら
# setenforce 1
しましょう。