在 Ubuntu20.04 中通过 Docker 部署 Mastodon

Mastodon 是一個免費開源的去中心化的分散式微部落格社群網路。它的使用者介面和操作方式跟推特類似,但整個網路並非由單一機構運作,卻是由多個由不同營運者獨立運作的伺服器以聯邦方式交換資料而組成的去中心化社群網路。

#Mastodon · #去中心化

2022 - 03 - 29

社群網路,還權予您

Mastodon 不像 Twitter 或者 Facebook 那樣是單一個網站,它是由千個組織、團體所各自經營社群組成,互相連繫,無縫接合的網站聯網。Mastodon 自帶有抗欺凌工具讓你保護自己。受惠於網絡開放、獨立的性質,令每個站有更多版主可以 對你施予援手,與及嚴格行為守則為你保持氣氛友善。在超過 4.4M 名使用者的社群網絡中關注舊友新知,隨心發佈網址連結、圖片、文字、影片,享用這個社群擁有、免除廣告的自由社交空間。

部署過程

首先创建存放目录 mkdir -p /home/mastodon/ 并切换至该目录下 cd /home/mastodon/

获取 Mastodon 镜像 docker pull tootsuite/mastodon:v3.4.6 #如果需要指定版本,请将v3.4.6改成其它版本号。接着获取官方 docker-compose.yml 配置文件 wget https://raw.githubusercontent.com/tootsuite/mastodon/master/docker-compose.yml 并将文件中的 web、streaming、sidekiq 三项分类下的 image: tootsuite/mastodon 一行的尾部加入你刚刚获取镜像的版本号 image: tootsuite/mastodon:v3.4.6

执行命令 docker run --name postgres14 -v /home/mastodon/postgres14:/var/lib/postgresql/data -e POSTGRES_PASSWORD=数据库管理员密码 --rm -d postgres:14-alpine 配置 PostgreSQL,操作完成后可在 /home/mastodon/postgres14 目录中看见相关数据库文件。接着执行 docker exec -it postgres14 psql -U postgres 命令,再下一步执行 CREATE USER mastodon WITH PASSWORD '数据库密码' CREATEDB; 创建数据库用户及密码,创建成功后输入 \q 退出,最后输入 docker stop postgres14 停止数据库。此时你已经创建一个用户名为 mastodon 的数据库用户。

接下来在 /home/mastodon/ 目录中使用命令 touch .env.production 创建一个空文件,随后执行 docker-compose run --rm web bundle exec rake mastodon:setup 开始部署 Mastodon , 按照提示输入相关信息,需要注意的是 Postgres 部分,host 与 port 默认即可,database 和 user 都填写 mastodon ,Redis 部分都为默认。完成设置后会出现配置内容,将它保存在先前创建的 .env.production 文件中。

运行 docker-compose up -d 启动 Mastodon, 启动完毕后执行 chown 991:991 -R publicchown -R 70:70 postgres14 设置文件权限。最后执行 docker-compose down 关闭后再启动,此时的 Mastodon 无大碍的话已经是运行状态。

使用 Nginx 将 Mastodon 公共可访问化,对照官方给出的 https://github.com/mastodon/mastodon/tree/main/nanobox 配置文件,同时也可参考下方的配置文件。


map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

upstream backend {
    server 127.0.0.1:3000 fail_timeout=0;
}

upstream streaming {
    server 127.0.0.1:4000 fail_timeout=0;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
  listen 80;
  #listen [::]:80;
  server_name fairy.id;
  root /home/mastodon/public;
  location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  #listen [::]:443 ssl http2;
  server_name fairy.id;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers "EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
  ssl_prefer_server_ciphers on;
  ssl_session_timeout 10m;
  ssl_session_cache shared:SSL:10m;
  ssl_session_tickets off;
  ssl_early_data on;
  ssl_dhparam /usr/local/nginx/conf/ssl/dhparam.pem;
  ssl_certificate /usr/local/nginx/conf/ssl/fAiry.iD/certificate_chain.crt;
  ssl_certificate_key /usr/local/nginx/conf/ssl/fAiry.iD/private.key;
  
  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000" always;

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Strict-Transport-Security "max-age=31536000" always;
    try_files $uri @proxy;
  }

  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    add_header Strict-Transport-Security "max-age=31536000" always;
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $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 Proxy "";
    proxy_pass_header Server;
    proxy_read_timeout 500;
    proxy_set_header Early-Data $ssl_early_data;

    proxy_pass http://backend;
    proxy_buffering on;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_cache CACHE;
    proxy_cache_valid 200 7d;
    proxy_cache_valid 410 24h;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    add_header X-Cached $upstream_cache_status;
    add_header Strict-Transport-Security "max-age=31536000" always;
    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $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 Proxy "";

    proxy_pass http://streaming;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

相關擴展問題

需检查 HTTPS 证书链是否完整,如果缺失的话会造成加入中继返回 502 错误等问题。

外部资源被阻断的问题;可以运行 docker cp mastodon_web_1:/opt/mastodon/config/initializers/content_security_policy.rb . 映射文件后进行修改,继而在 docker-compose.yml 文件中 web 一项的 volumes: 下添加一行指令 - ./content_security_policy.rb:/opt/mastodon/config/initializers/content_security_policy.rb:ro 将修改后的文件挂载回去。

如果出现 Lots of ActiveRecord::ConnectionTimeoutError 超时错误,可以在 .env.production 文件中添加 DB_POOL=50 参数以加大数据库连接数,再修改 docker-compose.yml 文件中 sidekiq 一项的 command: bundle exec sidekiq 一行后添加 -c 50 加大线程数,从而解决堵塞超时问题。

设置 Microsoft 365 Exchange 做为邮件通知时,需要在 .env.production 文件中加入 SMTP_AUTH_METHOD=plainSMTP_OPENSSL_VERIFY_MODE=none 以解决无法发信问题。

使用 Nginx 多点反代负载访问时,可以在源站 Nginx 中添加启用 --with-http_realip_module 模块,并在源站配置文件中加入 set_real_ip_from 127.0.0.1;real_ip_header X-Forwarded-For; 还有 real_ip_recursive on; 用以返回用户真实地址并排除反代节点的地址,set_real_ip_from 此条参数,你有多少反代 IP 地址,就必须添加多少条。反代节点的 Nginx 站点配置文件中也需要添加 proxy_set_header X-Forwarded-For $remote_addr; 参数。