Run Your Own Mastodon Server on FreeBSD in a Potluck Container

Introduction

Have you heard of Mastodon?

Mastodon is a self-hosted social networking service, where independently run servers federate content to each other using ActivityPub.

Each Mastodon server has its own set of users, own code of conduct, own terms and moderation policies.

Users post short messages (called “toots”) to the world, or to select people. They can also subscribe to other users’ feeds on any server.

ActivityPub is a standard for the Internet in the Social Web Networking Group of the World Wide Web Consortium (W3C).

It is an open, decentralized social networking protocol which provides an API for managing content, and federated server-to-server notifications and content distribution.

The collective term for federated services which use ActivityPub is “the fediverse”, a portmanteau of “federation” and “universe”.

Mastodon is one part of the fediverse, where Wikipedia maintains a listing of others.

What is Involved in Your Own Mastodon Instance?

Your own Mastodon instance will require a few things:

The setup itself is mainly configuring a number of parameters as described in this article and running the ansible playbook described in this article which will pull the container images, configure and start them.

Easy Setup on FreeBSD With Potluck

There is an easy install using an ansible playbook for FreeBSD systems, that will provide a complete environment with monitoring and alerting.

You will need a FreeBSD 13.2 or 14.0 server with the following prerequisites:

You must configure the server’s default network interface to be labelled ‘untrusted’. For example, with interface em0:

ifconfig_em0_name="untrusted"
ifconfig_untrusted="inet 192.168.1.1 netmask 255.255.255.0"

You will also need a domain name, with the subdomains mastodon.domain.name and files.domain.name pointing at the same IP.

The ansible script below will provision a version of Mastodon with a patch to allow 5000 character posts, alternatively you can also pull the official Mastodon versions from https://github.com/mastodon/mastodon.

Obtaining The Playbook And Setting Up Environment

Clone the git repo and configure the environment as follows:

git clone https://codeberg.org/Honeyguide/mastodon-sampler.git
cd mastodon-sampler

Set Up a Python Virtual Environment to Ensure Correct Version Of ansible

Set up a python virtual environment to ensure correct version of ansible as below.

If not already installed, install python virtualenv:

python3 -m pip install virtualenv

Then create a virtual environment and activate it, before installing the required dependencies:

python3 -m venv .venv
source .venv/bin/activate
(.venv) .venv/bin/python3 -m pip install --upgrade pip
(.venv) .venv/bin/python3 -m pip install -r requirements.txt

It’s important to note that you will run the ansible-playbook executable from the virtual environment created, but do not do this yet!

.venv/bin/ansible-playbook -i [inventory] [playbook]

Update The Inventory Parameters

Update the inventory parameters to reflect all your specific details:

(.venv) cd inventory
(.venv) cp hosts.sample hosts
(.venv) nano hosts

Set your default username on the remote server in the following field. This is the username you ssh to your server as:

my_default_username=REPLACE-WITH-USERNAME

Set the hostname or IP address of your server in the following field, do not change other fields on this line:

[servers]
server1 ansible_host=REPLACE-WITH-IP-HOSTNAME ...

If your server has IPv6 enabled, make sure to set the following field from no to yes:

pf_ipv6_enable=yes

Update the following fields with a long complicated string of characters to secure your minio instance. This is the minio admin user:

minio_user=REPLACE-WITH-LONG-MINIO-USERNAME
minio_pass=REPLACE-WITH-LONG-MINIO-PASSWORD

Update the following fields to set your email address and domain names for certificate registration:

my_acme_email=REPLACE-WITH-EMAIL-ADDRESS
my_acme_domain=REPLACE-WITH-DOMAIN-NAME 
my_acme_alias1=mastodon.REPLACE-WITH-DOMAIN-NAME
my_acme_alias2=files.REPLACE-WITH-DOMAIN-NAME

Update the haproxy frontend IP address to match your server’s public IPv4 address:

haproxy_public_ip=REPLACE-FRONTEND-IP

Scroll down and set a grafana admin user and password:

pot_beast_grafana_user=REPLACE-GRAFANA-USER
pot_beast_grafana_pass=REPLACE-GRAFANA-PASSWORD

Also include mail server parameters for alertmanager notices:

pot_beast_smtphostport=localhost:25
pot_beast_smtp_from=notices@REPLACE-WITH-DOMAIN-NAME
pot_beast_alertaddress=REPLACE-WITH-EMAIL
pot_beast_smtp_user=
pot_beast_smtp_pass=

Further down the file is the following setting, configure it as files.your.domain:

pot_nomad_s3_domain_name=REPLACE-WITH-FILES-DOMAIN-NAME

Scroll further down and modify the zincsearch admin user and password:

pot_zincsearch_user=SET-ZINC-USER
pot_zincsearch_pass=SET-ZINC-PASSWORD

Page down some more and set a postgresql_exporter password:

pot_postgresql_exporter_pass=REPLACE-PGEXPORTER-PASS

You can also set the Mastodon user password for postgresql:

pot_postgresql_mastodon_password=REPLACE-WITH-MASTODON-DB-PASSWORD

A little further down are parameters for the Mastodon instance.

Set the domain name of your mastodon instance, for the purposes of this playbook make it mastodon.your.domain:

pot_mastodon_domain=REPLACE-WITH-DOMAIN-NAME

Configure email notifications for Mastodon with the following fields:

pot_mastodon_to_email=YOUR-EMAIL
pot_mastodon_from_email=YOUR-EMAIL
pot_mastodon_email_host=YOUR-EMAIL-HOST
pot_mastodon_email_port=465 
pot_mastodon_smtp_user=YOUR-SMTP-USER
pot_mastodon_smtp_pass=YOUR-SMTP-PASS

Use the same postgresql password as above:

pot_mastodon_db_pass=REPLACE-WITH-MASTODON-DB-PASSWORD

Then update the following field to match the files.your.domain configuration:

pot_mastodon_public_s3_host=files.REPLACE-WITH-DOMAIN-NAME

For the purposes of this playbook we don’t set any secret_key or otp_key and related, we’ll allow the image to generate them. Leave these fields blank to have them autogenerated in the resulting image.

Finally, scroll to the very bottom and update the following field with a long complicated password for the Mastodon minio user. This must be different from the admin password:

mastodon_minio_pass=REPLACE-WITH-LONG-MINIO-MASTODON-PASSWORD

Run The playbook

Change back to the parent directory and run the playbook as follows:

(.venv) cd ..
(.venv) .venv/bin/ansible-playbook -i inventory/hosts site.yml

The playbook will take approximately 60-90mins to provision your Mastodon server.

When complete, a QR code for a wireguard link will be displayed. Make sure to get a screen capture of this, then load it into a QR Code viewer to extract the text.

Copy the extracted text into your wireguard client and then connect.

You can now connect to the IP address displayed in the final task in the playbook.

Open http://10.1.2.10 (or whichever IP is assigned) to get an index page to the various internal services.

From here you can access the links, for example if you want to see charts you can access grafana at http://10.1.2.250:3000 with the user/pass you configured in the inventory file.

All the monitoring services are on the same fixed internal IP 10.1.2.250, but the port changes between services.

You can also access dashboards for consul, nomad and traefik which are on different IP addresses.

If you need to access minio, you can do so directly at https://10.1.1.1:9000. Make sure to accept the self-signed certificate TWICE because the port changes automatically and you need to accept it for each port.

Configure First Mastodon User

Open mastodon.your.domain and setup your first user.

In Case Of Problems

If you have any problems, you can clean the system and try over.

If you misconfigured your inventory, or something went wrong, you can run the clean.yml playbook to clean your system of all images and data.

Important: make sure to disconnect your wireguard session before running clean.yml.

(.venv) .venv/bin/ansible-playbook -i inventory/hosts clean.yml

All data will be wiped too so don’t run this on a system in regular use. It’s for cleaning a bad initial install.

Then make changes to the playbook or inventory and run site.yml again to provision.