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!