With the fall of #Firefish I started to look for alternatives for my nerdy instance. I won’t come back to #Akkoma and I really got fed up of the #Misskey / #Calckey flavour. I already have a #Mastodon that simply works great, and what about this #Glitch fork that just adds some features? Looks promising, let’s try it!

Overview

The Mastodon Glitch Edition is a fork from the original Mastodon code. As they explain in their documentation, installing it is mostly the same as installing the original Mastodon code. You could actually interchange the repositories between the original Mastodon and the Glitch Edition and the following steps would work the same.

This is going to be a Docker installation, and taking the simplest and most basic approach (no CDN, no customisations). I’ve divided the process into the following sections:

  1. Assumptions and Requirements
  2. Install Docker & docker-compose
  3. Get the Mastodon Glitch Edition code
  4. Build the Docker images
  5. Setup the instance
  6. Get the traffic in: Reverse Proxy and SSL certificate
  7. Some extra parameters to add
  8. Start the instance
  9. Solving problems
  10. Wrapping up

0. Assumptions and Requirements

As usual in my articles, I assume the following:

1. Install Docker & docker-compose

Docker will allow us to run all the needed infrastructure isolated from the machine itself. It allows us to spin up any infrastructure without having an amount of components installed in our system, making it really easy to keep the system up-to-date and to repurpose the Raspberry Pi if needed.

docker-compose is a wrapping tool that makes our live with Docker easier. We'll install both.

1.1. Install Docker

  1. ssh naboo

    ssh xavi@naboo
  2. Download and install Docker

    curl -sSL https://get.docker.com | sh
  3. Add the current user into the docker group

    sudo usermod -aG docker $USER
  4. Exit the session and ssh in again.

  5. Test that it works with a hello world container:

    $ docker run hello-world
    Unable to find image 'hello-world:latest' locally
    latest: Pulling from library/hello-world
    70f5ac315c5a: Pull complete
    Digest: sha256:c79d06dfdfd3d3eb04cafd0dc2bacab0992ebc243e083cabe208bac4dd7759e0
    Status: Downloaded newer image for hello-world:latest
    
    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    
    To generate this message, Docker took the following steps:
    1. The Docker client contacted the Docker daemon.
    2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
      (arm64v8)
    3. The Docker daemon created a new container from that image which runs the
      executable that produces the output you are currently reading.
    4. The Docker daemon streamed that output to the Docker client, which sent it
      to your terminal.
    
    To try something more ambitious, you can run an Ubuntu container with:
    $ docker run -it ubuntu bash
    
    Share images, automate workflows, and more with a free Docker ID:
    https://hub.docker.com/
    
    For more examples and ideas, visit:
    https://docs.docker.com/get-started/
  6. Get the container ID of this test run. In my case is 1dfdb4d1a3cf

    $ docker ps -a
    CONTAINER ID   IMAGE         COMMAND    CREATED         STATUS                     PORTS     NAMES
    1dfdb4d1a3cf   hello-world   "/hello"   2 minutes ago   Exited (0) 2 minutes ago             stupefied_hofstadter
  7. Remove the test container

    docker rm 1dfdb4d1a3cf 

1.2. Install docker-compose

  1. First of all, check what is the latest version by navigating to their releases: Releases · docker/compose · GitHub. At the moment of this article, the latest stable release was v2.24.3

  2. Dowload the right binary from their releases into the binaries directory of our system. Pay attention to the versions in the following command:

    sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  3. Give executable permissions to the downloaded file

    sudo chmod +x /usr/local/bin/docker-compose
  4. Test the installation:

    docker-compose --version

    It should print something like Docker Compose version v2.24.3

2. Get the Mastodon Glitch Edition code

We just need to clone the code from the Mastodon Glitch Edition’s GitHub repository. As it is not classified by tags nor releases, we just make sure we have the latest code from main.

  1. Move yourself to your home, if you're not there already

    cd ~
  2. Clone the official Mastodon code in a directory called social.arnaus.net (yeah, just the domain name, you can call it whatever you want)

    git clone git@github.com:glitch-soc/mastodon.git social.arnaus.net
  3. Now move to the new directory

    cd social.arnaus.net

3. Build the Docker images

The docker-compose.yml points to the Mastodon original image for 4.2.0, that runs ok but won’t contain the code upgrades of the Glitch version.

Searching around I found that there is actually a repository for the Glitch docker images, apparently for linux/amd64 and linux/arm64 (this last one should work for Raspberry Pi), but I just lost the fear to build my own images, it’s just taking ~20 mins, so why not?

At the moment of this guide, the version 4.3.0 was in the oven, almost ready in the version alpha, but the code in the main branch was not fully updated. I mention it because I found some issues on the go like a new image split between the web and the streaming containers, that I could not find documented anywhere, only as a bug. The solution is actually to create 2 images:

  1. First build the image considered main, for the web and sidekiq containers

    docker build . -t glitch-soc:20240126 -f Dockerfile
  2. Then build the image that will run the streaming container, that we actually have the Dockerfile file as well in the repository.

    docker build . -t glitch-soc-streaming:20240126 -f streaming/Dockerfile

Here we can just use these images into our docker-compose.yml, but I like to push them to my DockerHub and use the image from there, so:

  1. Login to Docker

    docker login
  2. Tag the built main image to what will be used once pushed

    docker tag glitch-soc:20240126 arnaus/glitch-soc:original_rpi4_4.3.0a
  3. Push the built main image

    docker push arnaus/glitch-soc:original_rpi4_4.3.0a
  4. Tag the built streaming image to what will be used once pushed

    docker tag glitch-soc-streaming:20240126 arnaus/glitch-soc-streaming:original_rpi4_4.3.0a
  5. Push the built streaming image

    docker push arnaus/glitch-soc-streaming:original_rpi4_4.3.0a
  6. Update the docker-compose.yml file, and replace the image file from the web and sidekiq containers with our arnaus/glitch-soc:original_rpi4_4.3.0a and the image file from the streaming container with our arnaus/glitch-soc-streaming:original_rpi4_4.3.0a

4. Setup the instance

This point is a bit messy. I found a blog post that have it very well explained, so I will simply quote it directly:

If we simply try and launch setup, Docker will refuse to start the containers because .env.production doesn't exist.

I had initially just touch'd the file (because you won't know what to put in it until after setup is complete), however, doing this leads to setup failing part way through.

There's a step where the setup script exports environment variables for use by later steps. However, some of those steps execute within a different container and variables exported in one container won't be available to another, so the streaming container fails to connect to Redis (because it tries to connect to the default - localhost rather than the redis container).

To resolve this, we start by creating .env.production with known connection details (replace the Database password with the correct value)

cat << EOM > .env.production
DB_HOST=db
DB_PORT=5432
DB_NAME=postgres
DB_USER=postgres
DB_PASS=<replace>
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
EOM

(REDIS_PASSWORD is supposed to have an empty value in the above).

With this file created, we're ready to fire off the setup process

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

It downloads the images and runs the container, triggering the rake process to setup the instance

[+] Creating 4/4
 ✔ Network socialarnausnet_internal_network  Created                                                           0.1s
 ✔ Network socialarnausnet_external_network  Created                                                           0.1s
 ✔ Container socialarnausnet-redis-1         Created                                                           0.1s
 ✔ Container socialarnausnet-db-1            Created                                                           0.1s
[+] Running 2/2
 ✔ Container socialarnausnet-redis-1  Started                                                                  0.7s
 ✔ Container socialarnausnet-db-1     Started                                                                  0.7s
Your instance is identified by its domain name. Changing it afterward will break things.
Domain name: social.arnaus.net

Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? No

Are you using Docker to run Mastodon? Yes

PostgreSQL host: db
PostgreSQL port: 5432
Name of PostgreSQL database: postgres
Name of PostgreSQL user: postgres
Password of PostgreSQL user:
Database configuration works! 🎆

Redis host: redis
Redis port: 6379
Redis password:
Redis configuration works! 🎆

Do you want to store uploaded files on the cloud? No

Do you want to send e-mails from localhost? No
SMTP server: smtp.gmail.com
SMTP port: 587
SMTP username: ****
SMTP password: ****
SMTP authentication: plain
SMTP OpenSSL verify mode: none
E-mail address to send e-mails "from": Mastodon <****>
Send a test e-mail with this configuration right now? Yes
Send test e-mail to: ****

Do you want Mastodon to periodically check for important updates and notify you? (Recommended) Yes

This configuration will be written to .env.production
Save configuration? Yes
Below is your configuration, save it to an .env.production file outside Docker:

# Generated with mastodon:setup on 2024-01-25 20:48:36 UTC

# Some variables in this file will be interpreted differently whether you are
# using docker-compose or not.

LOCAL_DOMAIN=social.arnaus.net
SINGLE_USER_MODE=false
SECRET_KEY_BASE=abcdefg1234567890
OTP_SECRET=bcdefga2345678901
VAPID_PRIVATE_KEY=cdefgab3456789012
VAPID_PUBLIC_KEY=defgacbd4567890123
DB_HOST=db
DB_PORT=5432
DB_NAME=postgres
DB_USER=postgres
DB_PASS=****
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=****
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_LOGIN=****
SMTP_PASSWORD=****
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS=Mastodon <****>

It is also saved within this container so you can proceed with this wizard.

Now that configuration is saved, the database schema must be loaded.
If the database already exists, this will erase its contents.
Prepare the database now? Yes
Running `RAILS_ENV=production rails db:setup` ...

Database 'postgres' already exists
Done!

All done! You can now power on the Mastodon server 🐘

Do you want to create an admin user straight away? Yes
Username: xavi
E-mail: ****
You can login with the password: efgabcd5678901234
You can change your password once you login.

Even it says that the configuration is saved, it is actually not as quoted above, so copy all the env vars above and paste them into the .env.production, overwriting what we previously added.

5. Get the traffic in: Reverse Proxy and SSL certificate

So we assume that we have the domain social.arnaus.net already set up to deliver the traffic to our home’s external IP and our router already routing the traffic for the port 80 and 443 to our reverse proxy machine. The next steps are adding this domain into the reverse proxy and generate a SSL certificate on it.

5.1. Add Virtual host in the Apache reverse proxy

As mentioned in the Assumptions, I run a reverse proxy in a separated machine called Dagobah. The reverse proxy is just a virtual host, so we only need to ssh there, create the virtual host, link it properly and restart the Apache:

  1. SSH into the host

    ssh xavi@dagobah
  2. Create a new virtual host file

    sudo nano /etc/apache2/sites-available/014-social.arnaus.net.conf
  3. Add the following content. I had it quite easy as I already run a Mastodon instance and the configuration is actually the same. Please note that I directly set up both ports 80 and 443, as in the next subsection I generate the SSL certificate and I like the have both references in the same file (as long as the certbot finds the related 443 virtual host, it respects it and adds the configuration in there besides creating a separate file)

    <VirtualHost 192.168.1.231:80>
       ServerName social.arnaus.net
    
       ProxyPreserveHost On
       ProxyPass / http://naboo:3000/ nocanon
       ProxyPassReverse / http://naboo:3000/
       ProxyRequests Off
    </VirtualHost>
    <VirtualHost 192.168.1.231:443>
       ServerName social.arnaus.net
    
       RewriteEngine On
       RewriteCond %{HTTP:Connection} Upgrade [NC]
       RewriteCond %{HTTP:Upgrade} websocket [NC]
       RewriteRule /(.*) ws://naboo:4000/$1 [P,L]
    
       ProxyPreserveHost On
       ProxyPass / http://naboo:3000/ nocanon
       ProxyPassReverse / http://naboo:3000/
       ProxyRequests Off
    
       ProxyPassReverseCookiePath / /
       RequestHeader set X_FORWARDED_PROTO 'https'
    </VirtualHost>
  4. Save (ctrl + o) and Exit (ctrl + x)

  5. Link the file into the Enabled Sites

    cd /etc/apache2/sites-enabled
    sudo ln -s ../sites-available/014-social.arnaus.net.conf
  6. Restart Apache

    sudo service apache2 restart

At this point we should be able to access to naboo from outside if we would have the host up.

5.2. Generate a SSL certification certbot

As we did several times, the reverse proxy host is the one that holds the SSL certificates to communicate with the outside world, but internally we go unencrypted from the reverse proxy to the instance itself. That’s why when we set up the virtual host in the reverse proxy we directed both cases to http://naboo:3000.

Said that, we only need to generate the certificate. We already have the certbot program installed (with the apache module), so it’s as simple as running the following command:

sudo certbot --agree-tos -d social.arnaus.net --apache

… and after the challenge the virtual hosts get updated as follows:

<VirtualHost 192.168.1.231:80>
    ServerName social.arnaus.net

    ProxyPreserveHost On
    ProxyPass / http://naboo:3000/ nocanon
    ProxyPassReverse / http://naboo:3000/
    ProxyRequests Off

RewriteEngine on
RewriteCond %{SERVER_NAME} =social.arnaus.net
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<VirtualHost 192.168.1.231:443>
    ServerName social.arnaus.net

    RewriteEngine On
    RewriteCond %{HTTP:Connection} Upgrade [NC]
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteRule /(.*) ws://naboo:4000/$1 [P,L]

    ProxyPreserveHost On
    ProxyPass / http://naboo:3000/ nocanon
    ProxyPassReverse / http://naboo:3000/
    ProxyRequests Off

    ProxyPassReverseCookiePath / /
    RequestHeader set X_FORWARDED_PROTO 'https'

SSLCertificateFile /etc/letsencrypt/live/social.arnaus.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/social.arnaus.net/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>

6. Some extra parameters to add

And here we come to the interesting part of the installation. Actually we installed the Mastodon Glitch Edition to profit from the extra features that it provide, and some of them are set up as parameters in the .env.production file.

Simply edit it and add them, setting up the value that fits the best to us:

# Custom settings
# ---------------
# Various ways to customize Mastodon's behavior
# ---------------

# Maximum allowed character count
MAX_TOOT_CHARS=500

# Maximum number of pinned posts
MAX_PINNED_TOOTS=5

# Maximum allowed bio characters
MAX_BIO_CHARS=500

# Maximim number of profile fields allowed
MAX_PROFILE_FIELDS=4

# Maximum allowed display name characters
MAX_DISPLAY_NAME_CHARS=30

# Maximum allowed poll options
MAX_POLL_OPTIONS=5

# Maximum allowed poll option characters
MAX_POLL_OPTION_CHARS=100

Start the instance

One moment before starting the instance. In this installation we did not set up any separate location to hold the uploads and media from the statuses. It is not a joke, it takes space and we’re running the instance in a quite small disk.

We have few options, like connecting an external drive and mount it as a docker volume so that the media will exists outside the / disk, or also setting up an Object Storage in a CDN (as I explained in catalan here).

Still, at this point what we need to do is to assign the public directory to the docker user that is the id 991 so uploads will work straight away:

sudo chown -R 991:991 public/

And now yes, we can start the instance!

docker-compose up -d

8. Solving problems

8.1. Pending migrations

After starting the instance everything felt good, but visiting the Administration Dashboard for the first time, a message appeared saying:

There are pending database migrations. Please run them to ensure the application behaves as expected
  1. First stop the instance

    docker-compose down
  2. Run the migrations

    docker-compose run --rm web bundle exec rails db:migrate
  3. Bring the instance back online

    docker-compose up -d

9. Wrapping up

I was surprised about how easy was to install this Mastodon Glitch Edition. Of course there were some challenges that made me search internet for answers (the instance setup that fails with Redis and that I run into a new image split with the new 4.3.0), but what is a project without blockers to solve?

Now I enjoy a Mastodon with the 2 out of my 3 main feature requests solved: configurable length and Markdown support. Now it’s only time for Quotes 😉 And the best of all is that I can just integrate smoothly to all Mastodon API side projects without having to implement adapters. I am very much looking forward to play with it!

Previous Post Next Post