Deploying a Mastodon Instance on CentOS 7

A new social networking web application has gained popularity in recent weeks known as Mastodon. It differs greatly from Twitter in its decentralized nature, and can be installed on just about any linux distribution.

It is also federated, meaning that instances can connect to each other, and users on one instance can follow and interact with users on another instance. Beyond that, it will also connect to gnuSocial / postActiv instances.

In this article, I will detail how to set up an instance using CentOS 7. Specifically I will be using resources created on the cloud hosting provider Digital Ocean, but these instructions can generally be applied to any CentOS 7 install.

Assuming you don’t already have a CentOS 7 server, you’ll want to sign up with Digital Ocean and create a droplet, which roughly works out to a virtual machine. https://www.digitalocean.com

I ran into resourcing issues with their lowest tier of 512 MB, it seems like 1 GB is the bare minimum that you’ll want to go with. Once your droplet is activated, you should be able to log in either via a SSH key, or having the root password e-mailed to you.

Assuming you’ve logged in as root, we’ll need to take care of some pre-requisites, most of which are installed via the Yum package management software.

To begin with, we’ll want to do some basic securing / privilege separation.

  • Create a user account for yourself with useradd myusername
  • Set a password with passwd myusername
  • Add them to the wheel group using usermod -Gwheel myusername
  • If necessary (it isn’t for Digital Ocean), use visudo to let all members of the wheel group su.
  • Edit /etc/sshd_conf to disallow remote root login by changing PermitRootLogin to no.

At this point it’s also a good idea to create a local user for mastodon. The documentation recommends only being able to use this account via su, however doing so was causing the ruby commands to have some issues, so I cheated a bit and just made a regular user with a shell to begin with:

useradd mastodon

passwd mastodon

Once we are completely done, you should change the shell from /bin/bash to /bin/false.

With that done, we’ll want to install a bunch of pre-requisites from the Yum package manager as well as add some repositories we’ll be needing.

Repositories:

sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
sudo yum install epel-release
sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-1.el7.nux.noarch.rpm

Next, we’ll need to install a bunch of dependencies:

sudo yum install nodejs
sudo yum install ImageMagick
sudo yum install ffmpeg
sudo yum install postgresql-libs postgresql-devel
sudo yum install libxml2-devel libxslt-devel
sudo yum install redis
sudo yum install postgresql postgresql-contrib postgresql-server
sudo yum install git
sudo yum groupinstall 'Development Tools'
sudo yum install gcc bzip2 openssl-devel libyaml-devel libffi-devel readline-devel zlib-devel gdbm-devel ncurses-devel
sudo yum install certbot
sudo yum intall nginx
npm install -g yarn

With that done, we’ll need to configure some supporting services that we’ve just installed.

Redis:

Start the service via sudo systemctl start redis.service

Check the status by sudo systemctl status redis.service

You should see something similar to the following:

redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: active (running) since Wed 2015-07-22 02:26:31 EDT; 13s ago
Main PID: 18995 (redis-server)
CGroup: /system.slice/redis.service
└─18995 /usr/bin/redis-server 127.0.0.1:6379

Next another test:

redis-cli ping

This should output PONG.

Benchmark it : redis-benchmark -q -n 1000 -c 10 -P 5

You should see something similar to the following:

PING_INLINE: 166666.67 requests per second
PING_BULK: 249999.98 requests per second
SET: 200000.00 requests per second
GET: 200000.00 requests per second
INCR: 200000.00 requests per second
LPUSH: 200000.00 requests per second
LPOP: 200000.00 requests per second
SADD: 200000.00 requests per second
SPOP: 249999.98 requests per second
LPUSH (needed to benchmark LRANGE): 200000.00 requests per second
LRANGE_100 (first 100 elements): 35714.29 requests per second
LRANGE_300 (first 300 elements): 11111.11 requests per second
LRANGE_500 (first 450 elements): 7194.24 requests per second
LRANGE_600 (first 600 elements): 5050.50 requests per second
MSET (10 keys): 100000.00 requests per second

You should now be done, as we will assume it is not desired to set up a master/slave scenario. Further reading is at https://www.digitalocean.com/community/tutorials/how-to-configure-a-redis-cluster-on-centos-7

Postgresql:

Set up the DB:

sudo postgresql-setup initdb

Start and enable the service:

sudo systemctl start postgresql

sudo systemctl enable postgresql

We will now need to change to the postgres user (since the default authentication method is ident) and launch the psql cli:

sudo su - postgres
psql

We’ll then need to create a database user for mastodon:

CREATE USER mastodon CREATEDB;
\q

Further reading is at : https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-centos-7

Lets Encrypt:

sudo certbot

You will be walked through the install process, since we do not have nginx running yet it is recommended to use the standalone web server option. When finished, certs will be output to the /etc/letsencrypt directory structure.

Nginx:

To configure Nginx, create a file /etc/nginx/conf.d/mastodon.conf with the following contents, replacing example.com with your domain in both configuration directives and the path to certificates:

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

server {
listen 443 ssl;
server_name example.com;

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;
gzip off;

root /home/mastodon/live/public;

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

location / {
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_pass_header Server;

proxy_pass http://localhost: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_pass http://localhost: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;
}

Start nginx via service nginx start

Next we’ll need to configure Ruby. We installed the pre-requisites in our initial steps, so next we want to become the mastodon user, you can do this via ssh mastodon@localhost . First, rbenv:

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src (it's ok if this fails)
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile

You’ll need to configure shims, run ~/.rbenv/bin/rbenv init and follow the instructions.

Log out and back in to apply the changes, and then try type rbenv , you should see ‘rb env is a function’.

Next, install ruby-build as a module:

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Then, we need to install the version of ruby mastodon needs :

rbenv install 2.3.1

We’re finally ready to get the mastodon code itself (stay logged in as mastodon):

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

Next, dependencies:

gem install bundler
bundle install --deployment --without development test
yarn install

This should be everything we need to install, now we just need to configure (this example uses nano, but any text editor like vi will work):

cp .env.production.sample .env.production
nano .env.production

# Service dependencies
REDIS_HOST=localhost
REDIS_PORT=6379
DB_HOST=/var/run/postgresql
DB_USER=mastodon
DB_NAME=mastodon_production
DB_PASS=
DB_PORT=5432

# Federation
LOCAL_DOMAIN=mydomain.com
LOCAL_HTTPS=true

We’ll need to generate some secrets with rake to fill in the next section, do this via rake secret 3 times, and record the values it outputs.

PAPERCLIP_SECRET=secret1
SECRET_KEY_BASE=secret2
OTP_SECRET=secret3

We’re assuming local storage, so we won’t be configuring S3. The final bit relates to our sending of mail. You can set up a local SMTP server if you wish, but even easier is using mailgun.org. Go to https://mailgun.org and sign up (you’ll need a paid account unless you want to have to input everyone’s e-mail address manually), set up your domain according to the instructions, and enter your user and password as below.

# E-mail configuration
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=postmaster@mydomain.com
SMTP_PASSWORD=password
SMTP_FROM_ADDRESS=noreply@mydomain.com

Ruby on Rails:

We’ll need to do the initial setup:

RAILS_ENV=production bundle exec rails db:setup

And then precompile some content files:

RAILS_ENV=production bundle exec rails assets:precompile

SystemD:

We’ll need to create some services. As our previously created user (not mastodon), create the following files:

sudo vi /etc/systemd/system/mastodon-web.service

[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="PORT=3000"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

sudo vi /etc/systemd/system/mastodon-sidekiq.service

[Unit]
Description=mastodon-sidekiq
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="DB_POOL=5"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

sudo vi /etc/systemd/system/mastodon-streaming.service

[Unit]
Description=mastodon-streaming
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="NODE_ENV=production"
Environment="PORT=4000"
ExecStart=/usr/bin/npm run start
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

Then, we have to set up these services:

sudo systemctl enable /etc/systemd/system/mastodon-*.service

sudo systemctl start mastodon-web.service mastodon-sidekiq.service mastodon-streaming.service

You’ll then want to try and go to https://mydomain.com to see if the front page loads correctly. You can monitor /var/log/messages for any errors.

The last thing you’ll need to do is create a user via the mastodon web UI, verify that account and log in. You should then be able to post things.

One last step to give that user administrator permissions (do it as the mastodon user in /home/mastodon/live):

rake mastodon:make_admin USERNAME=myuser

Make sure that myuser is the name of the user you just registered with the Web UI. You should now see an ‘administration’ option on your settings.

If you run into an issue with the rake command, you can manually edit the users table in the database, specifically the ‘admin’ field from ‘f’ to ‘t’ to achieve the same effect.

Federation will only occur as users follow people from other instances, so don’t be alarmed if you don’t see any other posts yet or if your extended details shows you as connecting to 0 instances. Get people to join your instance and follow everyone they can!