Mastodon on FreeBSD


Updated 2023-12-17 to add FreeBSD 14.0 notes at end.

Updated 2023-02-11: Mastodon is now available in ports, so you can just pkg install it if you want. I went through this process while the version in ports was abandoned, but it was adopted again not long after. I’m keeping with my install though, and publishing my notes in case they’re of use to anyone else doing similar.


Here’s the steps I used to install Mastodon 4.0.2 on FreeBSD.

I started from Colin Percival’s FreeBSD 13.0-RELEASE EC2 image, and first updated it to 13.1-RELEASE.

Starting logged in as root:

freebsd-update -r 13.1-RELEASE upgrade
freebsd-update install

# reboot as requested by freebsd-update

freebsd-update install

I also installed tmux, in case I got disconnected during any long-running tasks. It also makes it easier to have a couple of shells on the go (for example, as different users).

pkg install -y tmux

Then we can follow the Mastodon install guide, with some adjustments for FreeBSD.

pkg install -y curl wget gnupg sudo node16 npm yarn-node16 postgresql15-server

Enable postgresql:

sysrc postgresql_enable=yes

Do the initial configuration for postgresql:

/usr/local/etc/rc.d/postgresql initdb
service postgresql start

Install some more required things:

pkg install ImageMagick7-nox11 ffmpeg libxml2 libxslt git bison libyaml libffi redis nginx py39-certbot-nginx icu bash

Create the mastodon user:

pw user add -m mastodon
pw user mod mastodon -s /usr/local/bin/bash

Switch to the mastodon user:

su - mastodon

As the mastodon user, continue setting up:

corepack enable
yarn set version classic

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec bash

Once bash is relaunched with rbenv configured, continue:

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

The install of Ruby will take a short while. Once it’s done:

rbenv global 3.0.4
gem install bundler --no-document

And return to your root shell:

exit

[TODO: tune pgsql?]

Create the mastodon database:

sudo -u postgres psql
CREATE USER mastodon CREATEDB;
\q

Enable redis and start it:

sysrc redis_enable=yes
service redis start

Back to the mastodon user to install Mastodon itself:

su - mastodon
git clone https://github.com/mastodon/mastodon.git live && cd live
git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)

bundle config deployment 'true'
bundle config without 'development test'

I found I needed this to make bundle install succeed:

bundle config set build.idn-ruby -- --with-idn-dir=/usr/local

Now we can install all the gems:

bundle install

Once they’re installed, run the initial Mastodon setup:

RAILS_ENV=production bundle exec rake mastodon:setup

During the configuration questions, change the Postgres host to localhost.

Back to root:

exit

Configure nginx. Replace YOUR_HOSTNAME in the sed command with your mastodon hostname.

cp ~mastodon/live/dist/nginx.conf /usr/local/etc/nginx/mastodon.conf
sed -I bak s/example.com/YOUR_HOSTNAME/ /usr/local/etc/nginx/mastodon.conf

Edit the main nginx config file and remove the two server { ... } blocks provided (one is commented out).

Add include 'mastodon.conf'; in their place.

vi /usr/local/etc/nginx/nginx.conf

Save and quit.

Edit the mastodon nginx config file. In order for nginx to start, which is needed for the certbot verification, you need to comment out the entire server { .. } block with the SSL configuration.

vi /usr/local/etc/nginx/mastodon.conf

Save and quit.

Ensure DNS points to your host.

Enable and start nginx:

sysrc nginx_enable=yes
service nginx start

Request your SSL cert:

certbot --nginx -d YOUR_HOSTNAME

Ignore error about server configuration.

Edit the mastodon nginx config file again, and uncomment the server { ... } block. Update the two cert paths to add /usr/local at the start (i.e. /usr/local/etc/letsencrypt/...).

Verify and reload nginx.

nginx -t
nginx -s reload

You should now be able to hit your Mastodon hostname and receive (over HTTPS) the Mastodon error page.

Now we need to make sure the Mastodon services run. Install and enable daemontools:

pkg install daemontools
sysrc svscan_enable=yes

Create the service directories for the three services:

mkdir -p /var/service/mastodon-{sidekiq,web,streaming}/{env,log}

Create the run files for each. Starting with Sidekiq:

cd /var/service/mastodon-sidekiq
vi run

Content for the sidekiq run file:

#!/bin/sh

cd /home/mastodon/live

exec envdir /var/service/mastodon-sidekiq/env \
	setuidgid mastodon \
	/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 25 2>&1

Save and quit. Set the values for the environment variables:

echo production > env/RAILS_ENV
echo 25 > env/DB_POOL
echo 2 > env/MALLOC_ARENA_MAX

Create the run file for logging:

vi log/run

with content:

#!/bin/sh

exec /usr/local/bin/multilog n30 s16777215 /var/log/mastodon-sidekiq 2>&1

Save and quit.

Fix the permissions on the two run files:

chmod 755 run log/run

Now the streaming service:

cd /var/service/mastodon-streaming
vi run

With content:

#!/bin/sh

cd /home/mastodon/live

exec envdir /var/service/mastodon-streaming/env \
	setuidgid mastodon \
	/usr/local/bin/node ./streaming 2>&1

Save and quit. Set the values for the environment variables:

echo production > env/NODE_ENV
echo 4000 > env/PORT
echo 1 > env/STREAMING_CLUSTER_NUM

Create the run file for logging:

vi log/run

with content:

#!/bin/sh

exec /usr/local/bin/multilog n30 s16777215 /var/log/mastodon-streaming 2>&1

Save and quit.

Fix the permissions on the two run files:

chmod 755 run log/run

Finally the web service:

cd /var/service/mastodon-web
vi run

With content:

#!/bin/sh

cd /home/mastodon/live

exec envdir /var/service/mastodon-web/env \
	setuidgid mastodon \
	/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb 2>&1

Save and quit. Set the values for the environment variables:

echo 3000 > env/PORT
echo production > env/RAILS_ENV

Create the run file for logging:

vi log/run

with content:

#!/bin/sh

exec /usr/local/bin/multilog n30 s16777215 /var/log/mastodon-web 2>&1

Save and quit.

Fix the permissions on the two run files:

chmod 755 run log/run

Now start the svscan service, which will then start the Mastodon services:

service svscan start

Wait a few seconds, then check that all the services are running correctly. Running this command should show that the three services are staying up rather than having an uptime of 0 or 1 seconds:

svstat /var/service/*

Your Mastdon install should now be functional.

Updating

To update Mastodon, follow these steps.

Create a backup using whatever method is suitable for your environment. I shut down the host and snapshot the disk, then start it up again.

Consult the release notes for the release of Mastodon you’re updating to. If you’re upgrading from more than one release ago, consult the release notes for the intervening versions too as they can have their own requirements.

Verify you have the right versions of the external dependencies installed.

su - mastodon
cd live
ruby --version
postgres --version
# I'm not using Elasticsearch
redis-server --version
node --version

Fetch and check out the version of the code you’re upgrading to (per the release notes)

git fetch && git checkout v4.1.0

Follow the non-Docker and the “both” instructions from the release notes.

If instructed to restart all the Mastodon services, you do it with svc:

svc -d /var/service/mastodon-*

# check they're stopped
svstat !$

svc -u !$

# wait a few seconds for things to (hopefully) stay running and check
svstat !$

If any of the services have an uptime of 0 to a couple of seconds, they’re likely crashing and restarting. Check the right log file in /var/log/mastodon-SERVICE/current for clues.

When you’re happy, remember to delete your backup/snapshot, especially if you pay for storing it (assuming that you have some other regular backup mechanism set up anyway! and if you don’t, do that).

Troubleshooting

I found during my preflight checks (“is it working ok before I mess with it?”) before upgrading that Sidekiq wouldn’t (re)start because it was using a Gem built against a version of a library which had changed, so I had to rebuild that particular Gem to make it work. For reference, here’s how I figured that out and fixed it.

First, looking in the Sidekiq log:

less /var/log/mastodon-sidekiq/current

and jumping to the end of the file (by pressing G) showed this error repeating every time the service tried to start:

bundler: failed to load command: sidekiq (/usr/home/mastodon/live/vendor/bundle/ruby/3.0.0/bin/sidekiq)
/usr/home/mastodon/live/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.13.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require': Shared object "libicudata.so.71" no
t found, required by "charlock_holmes.so" - /usr/home/mastodon/live/vendor/bundle/ruby/3.0.0/gems/charlock_holmes-0.7.7/lib/charlock_holmes/charlock_holmes.so (LoadError)
        from /usr/home/mastodon/live/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.13.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
        from /usr/home/mastodon/live/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7/lib/active_support/dependencies.rb:332:in `block in require'
        from /usr/home/mastodon/live/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.7/lib/active_support/dependencies.rb:299:in `load_dependency'
[snip]

The two key bits of information in this log are that it’s a shared object (library, .so file) called libicudata.so.71 which can’t be found, and that it’s the charlock_holmes gem trying to load it.

First I tried finding that file to see if it was present but not being loaded:

locate libicudata.so.71

which returned no output, so that file doesn’t exist now. The numbers at the end are a version, so I repeated the search without the version to see what was around:

locate libicudata.so

This gave me

/usr/local/lib/libicudata.so
/usr/local/lib/libicudata.so.72
/usr/local/lib/libicudata.so.72.1

So the library got updated (by pkg), which means (because it worked before with the other version) we probably just need to get the gem rebuilt so it links to the new version instead.

# in the ~mastodon/live directory
bundle pristine charlock_holmes

After this, sidekiq started straight up.

FreeBSD 14.0 notes

Upgrading to FreeBSD 14.0 was pretty straightforward. Ruby needed rebuilding to work (as you would expect). 3.0.4 wouldn’t build so I moved to 3.2.2:

rbenv install 3.2.2
rbenv global 3.2.2

(This needed rbenv and rbenv-build to be updated, by git pulling in each of their directories as above.)

I also needed to add some build options for bundle install to succeed:

bundle config build.cbor --with-cflags="-Wno-incompatible-function-pointer-types"
bundle config build.posix-spawn --with-cflags="-Wno-incompatible-function-pointer-types"

With these updates, everything started right back up.