Here I explain how to set up a gemini:// server, which is known as a capsule.

If you don’t know what gemini:// is, essentially it is a text-only https:// alternative.

It allows you to browse text only feeds, and is a welcome escape from ads and javascript and bloat.

Gemini in general does not support anything except for pure text. There is no CSS, no JavaScript, nothing, except for text and sometimes images (but not in-line, you have to click them).
It is considered an alternative to the web, seen as “better/superior” because it forces minimalism, however it is a bad comparison.
The issue with modern web is the invasiveness, not the CSS or JS (although JS sucks); in this regard, I prefer my site being static and served over the web where I can use CSS, than gemini, where in the name of “minimalism” I have to give up expressive liberty.
Be that as it may, it’s nice to have options, plus the minimalism might appeal to some, or might enable a “rehab” sentiment.

setup #

The nixos configuration is a bit more difficult because of how simple gemini servers are. This simplicity means that we need to set up our own user, open the port (since nginx is a web server, it can’t work with gemini://), and set up autosync so we don’t need to sync files manually with git.

specs #

There are many servers to host; the gemini:// protocol is extremely simple, and so servers can take ~200-500 lines of code, and are extremely lightweight.

This guide sets up agate since it’s a service in nixos.

We start by making a folder gemini and gemini/default.nix

$ mkdir gemini && touch gemini/default.nix

(or use the file explorer of your choice)

nix declaration #

Open gemini/default.nix in any text editor, and copy the following

{ config, pkgs, lib, ... }:
{
  services.agate = {
    enable = true;

    hostnames = [ "<YOUR-DOMAIN>" ];
    addresses = [ "0.0.0.0:1965" ];
    language = "en";
    contentDir = "/srv/gemini";
  };

  networking.firewall.allowedTCPPorts = [ 1965 ];
  
  users = {
    groups.agate = { };
    users = {
      agate = {
        isSystemUser = true;
        description = "gemini user";
        group = "agate";
        home = "/srv/gemini";
        createHome = true;
        shell = "${pkgs.git}/bin/git-shell";
      };
    };
  };

  systemd = {
    # set service
    services."gemini" = {
      description = "Update Gemini site from repo";

      script = ''
        if [ -d /srv/gemini/.git ]; then
          ${pkgs.git}/bin/git -C /srv/gemini pull
        else
          ${pkgs.git}/bin/git clone https://<YOUR-GEMINI-REPOSITORY> /srv/gemini 
        fi
      '';

      serviceConfig = {
        Type = "oneshot";
        User = "agate";
      };
    };

    timers."gemini" = {
      description = "Hourly Gemini-site update";
      wantedBy = [ "timers.target" ];

      timerConfig = {
        OnCalendar = "hourly";
        Persistent = true;
      };  
    };
  };
}

Then to enable it, just include it in the server config file:

  imports = [
    # ... other services
    ./gemini
    # ... other services
  ];

and you’re done.

Make sure to replace <YOUR-GEMINI-REPO> with your actual gemini repo above

Let’s break the config file down.

explanation #

  1. We declare the agate service as enabled.
  2. We define the hostname for the gemini:// network, set the address(es) to serve from, and declare the language we will be writing in. Finally we declare the content directory
  3. We open port 1965 to the internet
  4. We declare a group agate
  5. We declare a user agate, in group agate, with the home folder being at srv/gemini, having access only to the git shell, for security.
  6. We declare a systemd service, gemini, that checks if /srv/gemini is a git repo; if it isn’t it git clone at a specified repository, if it is, it runs git pull. This service is set to run once (not continuously), and to be run by user agate
  7. We declare a timer that runs the gemini service we just declared, once per hour. This automates the setup

adding content #

In order to add content to your new capsule, simply go to the content/ folder and start writing.

By default gemini:// serves the index.gmi at <YOUR-DOMAIN>/, so make that file first.

.gmi is the markup flavour that gemini reads/serves.
It is a stripped down version of markdown (.md files).
You can find out more about it here

Since servers simply serve .gmi files, you don’t need to do any update/reload or anything. It happens automatically.

Personally, I use git to keep files synced (as in, the content folder is a git repo).

client #

In order to browse gemini:// you need a dedicated client.

Use Lagrange.