1. 什么是 Mastodon

Mastodon 是一个开源的社交软件,在使用上类似于 Twitter,但是在工作机制上却更加接近于电子邮件。Mastodon 是去中心的,只要愿意,任何一个人都可以自行搭建一个 Mastodon 站点。Mastodon 网络(Fediverse,又叫做象毛世界、长毛象宇宙)以“联邦”的形式运行,不同的 Mastodon 站点之间可以互通。而且,因为使用了 OStatus 协议,Mastodon 和 GNU Social 之间也是可以互通的。这样,即使你搭建了一个全新的 Mastodon 示例,也不会陷入无人可关注的窘境。

2. 选择搭建方式

这里可以找到部署 Mastodon 的官方文档。搭建 Mastodon 有两种方式:

第一种是采用传统的方式,按部就班的配置 PostgreSQL 和 Redis,然后安装 Mastodon 服务端,最后配置 Nginx 以及 HTTPS。第二种则是采用时下较为流行的 Docker 技术。从方便的角度出发,我选择了 Docker。

3. Docker 容器的优势

在国内搭建网站需要备案等繁琐的手续,所以,我直接把这个 Mastodon 实例搭建在了国外的 VPS 上。我所使用的是一个每月开销仅有 5 美元的小型虚拟主机。考虑到小主机不甚稳定,后期人如果有更多的需求,可能需要迁移,如果使用传统方法,那么迁移的时候除了恢复数据库以外,还要把整个安装流程再走一遍,这就带来了很多的问题。

而 Docker 简直就是为了解决这个问题而生的。Docker 分割了应用和数据。其中,应用部分以镜像的形式存在,数据则存储在本地目录当中。然后,可以新建一个容器挂载这个数据目录,并运行镜像当中的应用。这样一来,在迁移服务器的时候,就只需要备份数据库、配置文件还有静态文件就可以了,而镜像部分则完全可以复制过去,开箱即用,这就带来了很大的便利。

这并不是我第一次使用 Docker,我之前也试着使用过 Docker,但是因为感觉使用体验不是很好,于是就放弃了。但是,通过这一次的体验,我发现我之前觉得 Docker 不好并不是 Docker 本身的原因,而是我的使用方式有偏差。Docker 并不适合像虚拟机那样使用,这样的任务应该交给 lxc 这样的容器来完成。Docker 的逻辑是,一个容器只应该办好一件事情,运行一个进程。所以,当一个应用非常复杂,涉及到很多个服务,很多个进程的时候,就应该使用很多个容器共同完成。而 Docker 也确实提供了一个名为 docker-compose 的命令行工具来协助处理此类任务。

4. 搭建流程

我所安装的版本是 Mastodon,大体上还是官方文档。但是,官方文档并非十全十美,如果完全依次行事,可能会遭遇到意想不到的问题。所以,我把整个过程都记录下来,以资参考。

4.1 注册 Mailgun

Mailgun 是用来发注册邮件等东西的。这里不做过多介绍了。在申请完账号之后,Mailgun 会要求你配置 DNS。随后,它会给你分配 SMTP 的用户名、密码。这里需要记下来,后面会用到。

4.2 安装 Docker

我的服务器环境是 Ubuntu 16.04。但是,因为使用 docker 方式安装,其它发行版也大同小异。首先要安装 git、docker 以及 docker-compose。其中,git 只需要 apt-get 即可,而 docker 则不能直接使用官方镜像源的版本(因为太老了),应该直接去官方网站下载最新的稳定版本。运行下列命令即可安装最新的 Docker 社区版:

sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
sudo apt-get update
sudo apt-get install docker-ce

docker-compose 的安装同理,按官方网站的指南安装即可。如下:

sudo curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

4.3 下载 Mastodon

从 Github 拉取最新版本的源代码。

git clone https://github.com/tootsuite/mastodon
cd mastodon

4.4 构建镜像

在构建镜像之前,需要先更改设置,用编辑器打开当前目录下的 docker-compose.yml 文件,把 build . 之前的注释都去掉,另外,镜像中的 volumes 部分的注释也要去掉。否则,在镜像运行完成之后,数据将不会保存。

最后我的 docker-compose.yml 如下,可供参考。

version: '3'
services:

  db:
    restart: always
    image: postgres:9.6-alpine
    networks:
      - internal_network
### Uncomment to enable DB persistance
    volumes:
      - ./postgres:/var/lib/postgresql/data

  redis:
    restart: always
    image: redis:4.0-alpine
    networks:
      - internal_network
### Uncomment to enable REDIS persistance
    volumes:
      - ./redis:/data

#  es:
#    restart: always
#    image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.3
#    environment:
#      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
#    networks:
#      - internal_network
#### Uncomment to enable ES persistance
##    volumes:
##      - ./elasticsearch:/usr/share/elasticsearch/data

  web:
    build: .
    image: tootsuite/mastodon
    restart: always
    env_file: .env.production
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    networks:
      - external_network
      - internal_network
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis
#      - es
    volumes:
      - ./public/assets:/mastodon/public/assets
      - ./public/packs:/mastodon/public/packs
      - ./public/system:/mastodon/public/system

  streaming:
    build: .
    image: tootsuite/mastodon
    restart: always
    env_file: .env.production
    command: yarn start
    networks:
      - external_network
      - internal_network
    ports:
      - "4000:4000"
    depends_on:
      - db
      - redis

  sidekiq:
    build: .
    image: tootsuite/mastodon
    restart: always
    env_file: .env.production
    command: bundle exec sidekiq -q default -q mailers -q pull -q push
    depends_on:
      - db
      - redis
    networks:
      - external_network
      - internal_network
    volumes:
      - ./public/packs:/mastodon/public/packs
      - ./public/system:/mastodon/public/system

networks:
  external_network:
  internal_network:
    internal: true

而这几个镜像的配置文件则是当前目录下的 .env.production,然而在默认状态下这个文件是不存在的,只有一个 .env.production.sample,因此需要重命名。

cp .env.production.sample .env.production

此时暂时不需要编辑这个文件,因为后面会有脚本来处理里面的设置。

此外,在构建镜像之前,需要处理一个问题。public 目录下面有两个无效的软链接,这是因为此时静态文件还没有生成。所以,需要先 hack 一下。

cd public/assets/
touch 500.html
touch sw.js
cd ../../

最后运行 docker-compose build 构建镜像。

4.5 配置

在配置之前,需要解决数据权限的问题。Mastodon 的 Dockerfile 写明了镜像中的应用在运行时以拥有“991”这个 UID 的用户运行。所以,需要更改 public 目录的权限。

chown -R 991:991 public/

然后把刚才的那两个 hack 出来的垃圾文件删掉

rm public/assets/500.html
rm public/assets/sw.js

接着就可以生成配置文件了。

docker-compose run --rm web rake mastodon:setup

这里会让你填写域名之类的,只要按照提示操作即可。其中,需要填写邮箱设置,就把前面 Mailgun 那里的设置复制过来就行了。最后,它会让你生成一个管理员账号,记得把密码复制下来。

配置完成之后,还可以预编译静态文件。

docker-compose run --rm web rake assets:precompile

4.6 完成

运行 Mastodon 吧~

docker-compose up -d

5. Nginx 和 HTTPS

当然到这里还没结束,为了用户的安全,Mastodon 要求服务器开启 HTTPS。曾经,一个 TLS 证书是很昂贵的,价格高达 200 元,但是所幸我们现在有 Let’s Encrypt 这种良心 CA,可以免费拿到证书。最近,Let’s Encrypt 还新增了域名通配符的支持。或许后面我会写一篇文章介绍一下 Let’s Encrypt。但是这里不再赘述。

至于 Nginx 的配置,我的推荐是照搬 Mastodon 的官方文档。我认为其安全性还是达标的。配置文件如下:

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

server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  root /home/mastodon/live/public;
  # Useful for Let's Encrypt
  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 example.com;

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

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

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /home/mastodon/live/public;

  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";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri @proxy;
  }
  
  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    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 https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    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;
  }

  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 https;
    proxy_set_header Proxy "";

    proxy_pass http://127.0.0.1:4000;
    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;
}

当然,要把里面的 example.org 改成自己的域名。

6. 服务器备份

Mastodon 的服务器备份包括三个步骤:备份静态文件、备份数据库、备份配置文件。

6.1 备份数据库

Mastodon 使用 PosgreSQL 作为数据库。PostgreSQL 提供了一个名为 pg_dump 的工具用作备份。使用方式如下:

docker-compose exec -T db pg_dump -U postgres postgres > backup.sql

然后把 backup.sql 下载下来,或者上传到 Google Drive 之类的云存储空间里。

6.2 备份静态文件

需要备份的静态文件位于 public/system 主要是用户上传的图片这些。直接用 rsync 备份即可,可参考这里

6.3 备份配置文件

这个步骤只需要进行一次,把 .env.production 文件复制下来就行了。

7. 其它

服务在日常使用中还可能遇到升级、迁移等问题,这些我还没有实际操作过。日后也许会更新在这里。

8. 结束

至此,服务其已经配置完毕,打开浏览器,输入用户名和密码,开始使用吧~