🐾 rigby

On the 1965 Gemini spaceflights, or, how I escaped the Referer header (part 2)

On the 1965 Gemini spaceflights, or, how I escaped the Referer header (part 2)

My curiosity aroused, I decided to check out this new protocol. My portal into the project was the homepage.

=> https://gemini.circumlunar.space The Gemini homepage (also supports the Gemini protocol of course).

This page includes a list of Gemini clients, but I wanted something more opinionated. So I did a search on Github and sorted by number of stars. The top two are Lagrange and Kristall. There are also recommended by a guide I found on getting started with Gemini.

=> https://geminiquickst.art Gemini quickstart guide.

I have installed both of these clients. Lagrange claims to be beautiful, and it certainly tries. I was doing this browsing at 2am after I had turned off my light, so the fact that Lagrange auto-switched to its dark mode made me very happy. Not all HTML websites do that.

=> https://github.com/skyjake/lagrange

Kristall, at least in its default configuration, is not beautiful. However, it supports basic HTML (think lynx – no JS or CSS, no input elements), in addition to Gopher and Gemini (which they both support). This meant it could already open Thoughts, due to the effort I put towards simplifying that site. Reading Rigby is also perfectly functional there. Kristall is more configurable, so I might be able to make it look better, but for now Lagrange is my client of choice. Kristall’s kerning is also fricking garbage. They must be doing text-setting themselves.

=> https://github.com/MasterQ32/kristall

Once I had poked around the Geminispace a little bit, I was ready to support Gemini on my twitter-like website, WhisperMaPhone.

=> gemini://thoughts.learnerpages.com/

WhisperMaPhone uses Apache, WSGI, and Django. Unfortunately, Apache and WSGI are very much specific to the HTTP protocol, so they wouldn’t be very helpful. Luckily, Django is largely protocol and language agnostic, so creating a Django template to generate a valid Gemini text file was easy. Gemini doesn’t support any semantic markup features and also doesn’t collapse whitespace. So I had to handle layout myself in Unicode, basically. This is the dark side of Gemini. I don’t want to leak my entire source since I’m already planning on changing some of it, but to give you an idea of what it looks like, here’s an example.

 # Thoughts

 {% for thought in thoughts %}{{ thought.text|safe }}
 {% if thought.extended_text %}
 {{ thought.extended_text|safe }}{% endif %}

{{ thought.posted|time:“messy time format codes” }}

 {% endfor %}

But the fun part is configuring the Gemini server. Like clients, there are a lot of gemini servers with varying levels of support. Agate is one of the more popular. It’s written in Rust, and I would have loved to use that, but it only supports static content. Since my content isn’t static, it lives in a Django DB, I needed something that would let me run python, or at least use CGI.

I ended up using a Python Gemini server called Jetforce. It wouldn’t be my first choice for a new project, but in this case it lets me communicate with Django directly. In addition to being a static/CGI server, Jetforce lets you ignore that and use it more like a framework.

=> https://github.com/michael-lazar/jetforce

By poking around in Jetforce’s source a little bit, I was able to overwrite their native application. This gave me a Python file which could import both Jetforce and Django, and dispatch Gemini requests to the proper template. Of course, this means I’m re-implementing my views, templates, and url routing from scratch for Gemini. Here’s a condensed version of this file, leaving out imports and some fluff.

os.environ["DJANGO_SETTINGS_MODULE"] = "whispermaphone.settings"

app = JetforceApplication()

@app.route("", strict_trailing_slash=False)
def index(request):
    thoughts = Thought.objects.order_by("-posted")

    rendered_text = render_to_string("whispermaphone/index.gmi", { "thoughts": thoughts })

    return Response(Status.SUCCESS, "text/gemini", rendered_text)

server = GeminiServer(

Interestingly, Gemini requires TLS. You can read more about this in the Gemini FAQ (linked in my previous post). It was simple for me to point it at my existing Let’s Encrypt certificates.

Gemini runs on port 1965, that being the year of the first manned Gemini mission. So I had to open that port with UFW. To fire up my new site, I created a systemd .service file to run the above Python.

The simplicity of the Gemini protocol means that it was pretty easy for me to find software supporting it (even thought it’s not a mature ecosystem), and meant that I could write my own template without prior knowledge. The future of the internet is simple.


luke on 2021-06-16

This is a really good write-up, thank you for posting. I must admit though that I don't really see a future for Gemini outside of very niche circles. Maybe that's fine, it's probably intended at the very least. Yet, I don't understand the benefits of gemini over simply just serving a simple static HTML website. You can serve it without CSS, at let the browser do the styling, and it doesn't appear to be a faster protocol in any way? I'm assuming it doesn't use UDP or anything.

new reply

new comment