Setting Up a Misfin Mail Server with Docker to Send/Receive Misfin Mail

Published: 05 October 2024

Being new to Geminispace is awesome! There's so many cool things I've been completely in the dark about that I've been discovering since finding it. My latest discovery has been Misfin thanks to a few gemposts I've read that have linked their Misfin addresses at the bottom. This piqued my curiosity enough to look into what it was.

Email, but for the small web

It turns out that Misfin is essentially email but for the small web, facilitating the sending and receiving of gemtext messages to and from recipients with a completely new protocol. The protocol specification itself can be found here on Gemini[1]:

The Misfin Mail Protocol - misfin.org

After discovering this, I really had to have my own Misfin server and address hosted on my domain to accompany the Gemini Agate server I set up to host this capsule that I talked about setting up in a previous gemlog entry[2]. The remainder of this post will discuss how I installed a Misfin server into a tiny Distroless docker container which may hopefully be of help to anyone looking to setup a Misfin server of their own.

Misfin-Server

The first thing I needed to get this show on the road was, of course, a Misfin mail server. This was a fairly short search for me, as when musing this in the back of my head while scrolling through the popular gemlog aggregator Antenna[3], one of the posts I saw was from a developer posting patch notes for the latest release of, you guessed it, their Misfin server! Perfect!

The developer, Christian Seibold, is the creator of software called, aptly, Misfin-Server, which he outlines over at his capsule[4]:

Misfin-Server - auragem.letz.dev

Written in Golang, this software seemed to have the perfect set of features for my needs for hosting my own individual Misfin address. It can do even more than just that though and I encourage you to check out its full set of features over at the capsule linked above or at the Gitlab code repository for the project[5]:

Christian Seibold / misfin-server - GitLab

Installing Misfin-Server in a Docker container

I really like using Docker's containerisation technology to deploy service daemons where I can. Compartmentalisation is always good and it makes updating software within them pretty easy. So, I'll be building a tiny Distroless[6] image containing the Misfin server for use on my domain.

It's worth noting that I'll be configuring most of the server from scratch. There is actually an automated way of generating default configs and certificates by running Misfin-Server's init command, which is what most of my configuration is based from. If you just want an easy setup it's worth looking into that. The reason I am doing some of this manually is because I'm creating slightly different certificates than those generated by the default initilizer, and due to ease within a containerised environment as the configuration areas will be mounted in advance of starting the server.

Dockerfile

First thing's first, I'll be needing a Dockerfile to create the image for Misfin-Server:

FROM golang:latest AS build
WORKDIR /opt/misfin
RUN git clone https://gitlab.com/clseibold/misfin-server.git . && \
    cd misfin-server && \
    go build -o . ./...

FROM gcr.io/distroless/base
COPY --from=build /opt/misfin/misfin-server/misfin-server /
ENTRYPOINT ["/misfin-server"]

I'm using a multi-stage build here to create the image. The first part harnesses the golang:latest image to clone Misfin-Server's repository and build the binary for the server. Once that's done, in the second part I transfer the created binary from my build image into the much smaller Distroless base image which will be the image the server is ran from. The size of the final image is just a bit above 31MB.

Dedicated Non-Privileged User

Next, I'll be creating a non-privileged user on the host system which will be used as the user context the container will be ran under.

useradd -u 1958 -s /sbin/nologin misfin_d

I'll be using misfin_d as the user's name with a UID of 1958, which won't be needing an interactive shell.

Volumes

Next, I'll be attaching a couple of directories as volumes inside the container which will be storing the server's configuration as well as mailboxes. These will need to be created first.

mkdir -p /opt/misfin_srv/{config/,mailboxes/}

I'm choosing to put these things within /opt/misfin_srv, but you might choose to put them elsewhere if deploying like this.

Misfin-Server Configuration File (misfinserver.conf)

I'll now be needing to create a base configuration for the server to run. This file will be located at /opt/misfin_srv/config/misfinserver.conf in my setup:

port: 1958
bind-address: 0.0.0.0
gemini-permission: admin-only
gemini-user-registration-permission: admin-only
max-concurrent-connections: 0
rate-limit-duration: 0
rate-limit-duration-members: 0
version: 0.5.8c

This is mostly what the server will generate for its defaults, except that I've enabled the Gemini interface to access my mailbox which will be accessible on the same port the server is running on. I've set this to "admin-only" to ensure only I can access it. I've also enabled user registrations through Gemini, again just for myself. Valid options for these settings can be found over at the Gitlab README[7]:

Christian Seibold / misfin-server / README - GitLab

A few settings such as mailbox-dir and server-cert are omitted from my config file as they'll be passed via compose.yaml during orchistration.

Admin Certificate Creation

A couple of certificates will need to be generated for use with the server. The first is the self-signed admin certificate that'll be used as the root of trust to sign any other mailbox certificates residing on the server.

If you call the init command of Misfin-Server, it will generate RSA2048 keys by default. However, I want to create my own keys in a slightly different way. Instead of RSA I'll be using elliptic curve keys which can be computationally faster and smaller while retaining security. As the specification doesn't mandate a particular key type, any key that TLS supports should be fine here. I'll be using this command to generate my admin.pem key file:

openssl req -newkey ec \
            -pkeyopt ec_paramgen_curve:prime256v1 \
            -nodes \
            -subj "/CN=Tiff the Valkyrie/UID=tiff" \
            -addext "subjectAltName = DNS:valkyrieshowl.com" \
            -x509 \
            -days 36525 \
            -sha256 \
            -out /opt/misfin_srv/config/admin.pem \
            -keyout /opt/misfin_srv/config/admin.pem

There are some important parameters in this command that will need changing for your user and domain. The "CN" portion of the subject should be your name which will be seen by others. The "UID" element, "tiff" in my case, is the user part of my actual Misfin address, tiff@valkyrieshowl.com. These should be changed to suit your needs.

Additionally, the "subjectAltName" part is the host portion of the address you're creating. This should be set to your domain which you'll be using for Misfin addresses.

The .pem file containing the public and private keys will end up in my case at /opt/misfin_srv/config/admin.pem

GMAP Certificate Creation

Next, I'll be needing to create a certificate specifically for the GMAP mailbox. This is a special mailbox used as part of the Gemini-Misfin Access Protocol which the server suppots. The informal specification for GMAP can be found at Satch's capsule if you want to know more[8]:

The Gemini-Misfin Access Protocol (GMAP)

This will be a non-CA certificate signed by the previously created admin.pem:

openssl req -newkey ec \
            -pkeyopt ec_paramgen_curve:prime256v1 \
            -nodes \
            -subj "/CN=GMAP/UID=gmap" \
            -addext "subjectAltName = DNS:valkyrieshowl.com" \
            -days 36525 \
            -sha256 \
            -out /opt/misfin_srv/mailboxes/gmap.pem \
            -keyout /opt/misfin_srv/mailboxes/gmap.pem \
            -CA /opt/misfin_srv/config/admin.pem \
            -addext basicConstraints=CA:FALSE

On your own implementation, the CN and UID should be left untouched in this case. The "subjectAltName" will need editing to your own domain, though, as well as potentially the locations of the output and signing certificates depending on where yours are located.

Mailbox List

We're almost done! We just need to create a file to tell the server about which mailboxes are on it. This file shall be located at /opt/misfin_srv/mailboxes/mailbox.list and will look like this for me:

[tiff]
type: root
blurb: Tiff the Valkyrie
fingerprint: da3d34f37c4ab0d58aae1f34048b530af2f0f8a4dbd13a97eb67b194f760daf1
hostname: valkyrieshowl.com
archive-view-permission: subs

[gmap]
type: admin
blurb: GMAP
fingerprint: 2c4da3e04b4cc4ab165acbf0e402fba2daeb35b290ef1a54e11ce877d6ed1e42
hostname: valkyrieshowl.com
archive-view-permission: subs

If you wanted to add regular users to this file, you could use the type "user" instead of "admin" for them.

This file will need editing to match your settings. Where I have "[tiff]" at the top, this should be your root user's UID. The "hostname" will need to be edited to your own, and the "fingerprint" for each key will need to be retrieved from the .pem files we made earlier.

In order to quickly find the fingerprint for your keys in the correct format, you can use this command:

openssl x509 -in /opt/misfin_srv/config/admin.pem -noout -sha256 -fingerprint | sed -n 's/^.*=//; s/://g; s/.*/\L&/p'

The "admin.pem"'s fingerprint will be the first user's fingerprint in your config, and the fingerprint of /opt/misfin_srv/mailboxes/gmap.pem will need to be set for the gmap user.

Fixing Directory Permissions

Once I'd set everything up the last thing I needed to do was fix the permissions of the directory structure so that the non-privileged user I created for the server earlier could access it.

Set the owner of the configuration area to be the non-privileged misfin_d user:

chown -R misfin_d:misfin_d /opt/misfin_srv

Fix the permissions on directories and files so that only the user the server will be running as can read them.

find /opt/misfin_srv -type d -exec chmod 0700 {} \;
find /opt/misfin_srv -type f -exec chmod 0640 {} \;

Putting it all together with Docker Compose (compose.yaml)

The only thing left to do is set up the orchistration of running the server by using Docker Compose. This is my compse.yaml file, which lives alongside the Dockerfile created earlier:

services:
  misfin-srv:
    restart: always
    build: .
    image: tiff/misfin_d
    pull_policy: never
    container_name: tiff_misfin_daemon
    user: 1958:1958
    volumes:
      - /opt/misfin_srv/config:/config
      - /opt/misfin_srv/mailboxes:/mailboxes
    ports:
      - "1958:1958"
    command: > 
      serve /config/misfinserver.conf 
      --mailbox-dir /mailboxes 
      --server-cert /config/admin.pem

This will ensure that the container runs in the correct user context and that the config and mailboxes areas are mounted within the container as volumes so the server can access them.

Once this is in place, the server can be built and ran by using:

docker compose up -d

Updating the Server

When there's an update to Misfin-Server, updating can be accomplished by rebuilding the image to pull the latest changes from git and restarting the server:

docker compose build --no-cache
docker compose down
docker compose up -d

Importing Certificates into your Client

Now that the server is up and running, the final piece to the puzzle was importing my mail identity into my client. I personally use the Lagrange client to browse Gemini which also supports Misfin. In order to import the identity, I first need the keys from the .pem file for my mailbox which I accessed using the following command:

cat /opt/misfin_srv/config/admin.pem

This outputs the public and private keys to the terminal which I copied into my clipboard, after which I went to Lagrange and hit Identity -> Import and pasted the keys in. This should populate both the "certificate" and "private key" boxes which you can then choose to import.

Accessing your Mailbox

As I enabled the Gemini interface during the server configuration, the Misfin server is listening for Gemini connections on port 1958. Viewing my mailbox is as simple as browsing to valkyrieshowl.com:1958 in my Gemini client and choosing the identity that I'd just imported for authentication.

Final Notes

Much thanks goes out to the creator of Misfin-Server, Christian Seibold. Not only has he made some awesome open-source software, but he also quickly fixed a few bugs I discovered during my setup.

If this is useful to you, feel free to contact me via Misfin to tell me about your server setup!

References

[1] The Misfin Mail Protocol - misfin.org

[2] Creating a Capsule/Gempod on Gemini with Tiny Distroless Docker Images

[3] Antenna - Gemlog Aggregator

[4] Misfin-Server - auragem.letz.dev

[5] Christian Seibold / misfin-server - GitLab

[6] GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.

[7] Christian Seibold / misfin-server / README - GitLab

[8] The Gemini-Misfin Access Protocol (GMAP)

Mailbag

Comments and replies to this post are welcome. Feel free to send a cosmic raven to:

Misfin: tiff@valkyrieshowl.com

Email: surges.colts_0s@icloud.com

Back to Gemlog


Source