noellabo's tech blog

@noellaboの技術ブログ

Mastodonのメディアをローカル保存する際に、オブジェクトストレージと同様のURLを返す

この話の要点

下記の設定で、ローカルにファイルを保存しながら、オブジェクトストレージと同じURL設計にできる

  • .env.productionPAPERCLIP_ROOT_URL=https://media.example.comを指定
  • nginxでroot /home/mastodon/live/public/system;にしてservername media.example.com;を公開する。

Mastodonのメディアファイルの保存場所

標準

Mastodonでは、標準ではメディアをインストールしたディレクトリのpublic/system/ 以下に保存します。

  • accounts/ アカウント関連のメディア
    • avatars アバター(アイコン)
    • headers ヘッダー
  • media_attachments/files 投稿の添付ファイル
  • custom_emojis/images カスタム絵文字
  • preview_cards/images プレビューカード(URL参照先のサムネイル)

また、リモートサーバーのユーザーについては、同様のファイルをpublic/system/cache/以下に保存します。

標準のnginx用の設定ファイルでは、root /home/mastodon/live/public;に設定されているので、たとえばexample.comというドメインでサーバ設置した場合、URLはこうなります。

  • https://example.com/about 概要ページ
  • https://example.com/@admin アカウントページ
  • https://example.com/system/accounts/avatars/000/000/001/original/7d4fbbe3107f536a95a30334bf91137c.png アバター画像のURL

オブジェクトストレージ

一方、メディアをオブジェクトストレージに保存する設定にすることもできます。

.env.productionの設定がこんな感じになっているとして、

S3_BUCKET=media.example.com
S3_REGION=ap-northeast-1
S3_HOSTNAME=s3.ap-northeast-1.wasabisys.com
S3_ENDPOINT=https://s3.ap-northeast-1.wasabisys.com
AWS_ACCESS_KEY_ID=xxxxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxxx
S3_ALIAS_HOST=media.example.com

この場合、URLはこうなります。

  • https://example.com/about 概要ページ
  • https://example.com/@admin アカウントページ
  • https://media.example.com/accounts/avatars/000/000/001/original/7d4fbbe3107f536a95a30334bf91137c.png アバター画像のURL

連合先に伝えたURLは変更できない

サーバを設置する際、ローカルストレージにメディアを保存する決定をすると、あとからオブジェクトストレージに移行しようとした時に、リモートに既に伝えたURLが無効になってしまう問題が発生します。逆の場合も同様です。アカウントのアバターやヘッダーについては強制更新をかけて置き換えることも可能ですが、カスタム絵文字は難しいし、投稿の添付ファイルは絶望的です。

あらかじめ、オブジェクトストレージを採用する場合と同じURLでアクセスするよう設定し、いつ切り替えても問題が起きないようにしておくことをオススメします。

ローカルストレージをオブジェクトストレージと同じURLにする

  • .env.productionに、メディアにアクセスする際に使う、本体とは異なるサーバURLを指定する
  • メディアサーバのドメインをDNSに登録する
  • nginxの設定に、ローカルストレージの内容を返す、メディアサーバとして機能する設定を追加する

ここでは、example.comドメインで運用するサーバのメディアを、media.example.comで提供することにします。まずここを決めます。

.env.productionに以下の環境変数を指定します。

PAPERCLIP_ROOT_URL=https://media.example.com

DNSサーバにmediaの名称とIPアドレスを指定します。

nginxに、例えば下記のような設定を追加します。

server {
    listen 80;
    listen [::]:80;
    server_name media.example.com;

    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name media.example.com;
    ssl_session_cache shared:ssl_session_cache:10m;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES128-SHA;
    ssl_prefer_server_ciphers on;

    access_log  /var/log/nginx/media_access.log;
    error_log  /var/log/nginx/media_error.log;

    root /home/mastodon/live/public/system;

    location / {
      add_header Cache-Control "public, max-age=31536000, immutable";
      add_header Strict-Transport-Security "max-age=31536000" always;
      access_log /var/log/nginx/media.access.log;
      error_log /var/log/nginx/media.error.log;
    }
}