Gemini is a light-weight text protocol, a spiritual successor to Gopher.

Phoebe is a wiki that serves its pages via Gemini and the web and that can be edited via Titan (and via the web, with some extra code in your config file). It powers The Transjovian Council.

Moku Pona is a feed aggregator and change monitor that collects data from feeds and detects page changes via Gopher, Gemini, and the web.

Lupa Pona is a simple static Gemini file server. You can use it to serve the Moku Pona output, for example.

If you’re an Emacs user, I have an Elpher setup that allows me to edit pages via Gemini and Titan!

This site is available via Gemini, too. The wiki raw text is transformed into Gemini format (”Gemtext”) on a best-effort basis.

2021-07-26 Want to write a Gemini-based game with me?

I run a MUSH already: Ijirait. But what if we wanted to run a real game, with challenges, items to pick up, enemies to fight, lands to explore? Somebody would have to write it up. I’d be happy to turn it into a game. But I need collaborators who write up a cool story. Interested?

Comments on 2021-07-26 Want to write a Gemini-based game with me?

There’s something going on here: gemini://

I got the link from a now defunct Gemini based zine.

Sean Conner 2021-07-27 04:55 UTC

Thanks for the link!

– Alex 2021-07-27 07:52 UTC

I’d like to chat about it, if you want. my contact is on my capsule on responses might be slow though 😉

– test 2021-07-27 08:59 UTC


– test 2021-07-27 09:00 UTC

Thanks for the heads up, hopefully fixed! 🙂

– Alex 2021-07-27 09:56 UTC

I’m interested, please clue me in! 🙂 I wrote something about an idea I’d had here, a while back (see

– panda-roux 2021-07-27 19:56 UTC

pasted a line break by accident; here’s the gemlog post: gemini://

– panda-roux 2021-07-27 19:57 UTC

Add Comment

2021-07-25 Gemini, CAPCOM, Antenna, and Tower

A few days ago I noticed that CAPCOM has become practically useless to me. Remember when @solderpunk reorganized CAPCOM a while ago?

Each month, CAPCOM randomly selects 100 distinct URLs from its list of known feeds, and includes their content in its output. This makes it a nice way to discover new content in Geminispace.” – CAPCOM

At the time I liked the idea. But of the 64 links visible right now, 20 are links to my blog. This doesn’t feel right. I sent Solderpunk an email and suggested he change the feed. I have multiple feeds and currently I think it shows all the regular changes to wiki pages, and to comment pages. This is not good. There’s a different feed that shows just new blog posts. I think that’s the one people should be subscribing to, if at all. Or just use my front page as a gemlog. 😁

Anyway, I’m not sure the wrong feed is the only reason I seem to be dominating CAPCOM right now. It’s true, I’m enjoying summer break these days and that means I’m not working – I have plenty of time to write blog posts. Like this one. It’s also true that some of the links would disappear if Solderpunk were to change my feed: comment pages would disappear, book club pages would disappear… I still fear that I might be taking up 10% of CAPCOM.

I have written two feed aggregators. Perhaps their implementations are simply better than what CAPCOM is doing at the moment.

Moku pona produces one line per feed it watches; when it works without bugs, that means it links to each feed exactly once, simply reordering them when updates come in. Active feeds are at the top. You don’t know whether you’ll like what’s new on the feed, but after a while you start remembering the names. I like it.

Jupiter takes the four latest entries of every feed, sorts them all by date, and keeps the 100 most recent ones. Again, nobody can really overwhelm the feed. You can’t have more than 4/100 entries.

@tinyrabbit said that they suspected many CAPCOM feeds to be abandoned. Thus, if CAPCOM picks a subset of the feeds it knows, many of them have no updates, so the ones that do (like mine) start dominating.

We started talking about Antenna. Antenna is different in that it doesn’t watch pages for changes like my feed aggregators do. Feed authors have to ping Antenna if they have an update. Antenna updates the feeds it is pinged with, once every 10 minutes.

Let’s quickly compare numbers. Moku pona checks all the URLs for changes (gemlog front pages or actual feeds). I subscribe to 55 sites and check them twice a day, making 110 requests per day. But look at the updates I’m actually showing right now:

awk '{ print $3 }' updates.txt | uniq --count
      5 2021-07-25
      6 2021-07-24
      1 2021-07-23
      1 2021-07-21
      2 2021-07-20
      2 2021-07-19
      1 2021-07-18

That’s not a lot of changes. It seems that I should be able to make do with about 5 requests per day! So if all the services knew to a personal Antenna installation, they would make 5 pings, and my Antenna installation would make 5 requests, done for the day. 10 requests instead of 110.

Now it looks as if Antenna is built to be a central hub. But think about it. Doesn’t ActivityPub allow just this? Don’t email newsletters do just this? You “sign up” to get updates from a server, and when they have updates, the send you a ping, and you have some software that knows what to do with it. What Antenna does is get pings and it knows what to do with it: it requests the URL mentioned in the ping and adds it to the updates it knows about. What we’re missing is the sign up part.

Currently, the one Antenna installation asks authors to ping it, using tools of their own devising. It works surprisingly well and shows that Gemini makes it easy to write clients and servers and services. I love it!

But obviously, not ever reader can ask all the authors to ping them. Or I guess they could, like all the newsletters you can subscribe to, like all the fediverse accounts you can follow using ActivityPub. What we’re missing is the right tools.

Let’s imagine this missing tool and call it Tower.

I have a blog, and it has a Tower installation. You want to get pinged when my blog updates, and you have an Antenna installation. You visit my blog, find the Tower link, and submit your Antenna URL. When my blog updates, it calls Tower, and Tower goes through all the Antenna URLs it has listed, and pings them. Then each Antenna installation can fetch the updated front page of my blog, or my feed, and add new entries to their list.

Now, we all have different Gemini hosting software, so perhaps it’s not as easy to implement. We could implement Tower as a service on the net! This Tower installation doesn’t just get called when my blog updates. Instead, it watches a bunch of URLs.

“Huh?” you ask. And you’d be right. Where’s the benefit? It’s true, if we don’t all implement a Tower convention, the use is limited.

Let’s use some imaginary numbers again. Let’s say we’re 100 people, each of us is following 50 gemlogs. Let’s say we all have moku pona installed and they all update twice a day, like mine does. Each of use makes 2×50=100 requests per day, us being 100 people results in 10,000 requests per day. This is when we all use self hosted feed readers.

Let’s assume somebody wrote the ominous Tower service and all 100 people signed up. They don’t actually add Tower to their blogs, they tell the one centralised tower service: this is the URL of my personal Antenna installation and these are the 50 URLs I want to follow (big privacy issue, for sure – let’s just continue with the thought experiment to get an idea of the number of requests this would result in). The Tower service checks all 100 URLs twice a day, making 2×100=200 requests per day.

It then pings all the signed up Antenna installations with the URLs they are following. We need to make some more assumptions. Let’s say that about 10% of all the blogs update on a given day: 10 of them. Since we’re all following 50 gemlogs, let’s assume that this numerical thought experiment is pretty egalitarian: there are no gemlog superstars, no long tail, instead we have a wonderful, uniform distribution. That means every person’s Antenna gets about 10×½=5 pings every day. All the Antenna installations then fetch each of these URLs, so 100×5=500 requests to update their data. Let’s hope the pings are spread out or else those poor servers are hit by all the Antenna installations at once!

Let’s add this all up: Tower makes 200 requests per day, sends out 500 requests per day, and the Antenna installations then make an additional 500 requests per day. That’s 200+500+500=1,200 requests per day instead of the 10,000 requests per day I mentioned above.

So clearly, something is good about this. Perhaps the numbers are worse because there are multiple Tower service installations. It’s still better than everybody running their own moku pona. It’s also a privacy issue, as I said. The Tower service operator would know which Antenna installation subscribed to what URLs. At least with private moku pona installations, it’s hard to know.

Add Comment

2021-07-24 An anatomy of the request

Early Internet protocols were simple. In order to make a request, you contact a machine on a particular port, and send some bytes, terminated by a carriage return (CR or \r) and a line feed (LF or \n). We can simulate this using echo and nc (netcat) to make these requests ourselves.

This is a request for user information using the finger protocol. Here, we’re sending a name to the server. The response is plain text.

echo -ne "alex\r\n" | nc 79

Gopher, at the protocol level, is the same thing. Here, we’re sending a selector to the server. The response is plain text.

echo -ne "page/Alex_Schroeder\r\n" | nc 70

HTML 1.0, at the protocol level, is already a lot more complicated. We’re sending the “method” (in our case, “GET”), the resource we’re interested in, the procotol version, HTTP headers (in our case, none), and an empty line, i.e. another CRLF:

echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\n\r\n" | nc 80

The response in this case is actually a redirect to the HTTPS URL. We’ll talk about this down below. For now, let’s just note that we got a response that we can act upon.

All these protocols mentioned above cannot do virtual hosting. Virtual hosting is when multiple domains are served on the same machine. We’re used to this, on the web. We know that “” and “” result in a different response; I know that this is surprising because they are both hosted on the same server. How is this possible? This is not possible using HTTP/1.0, that’s for sure: the server doesn’t know what host you think you’re talking to. If we try it, both requests get redirected to the page on “”, the default domain.

echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\n\r\n" | nc 80
echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\n\r\n" | nc 80

You could argue that this is a bit of security concern. After all, somebody doing this request for “” now knows that it’s being hosted on the same server as “”. I’m going to ignore that, however.

In any case, the technical reason for all of this is that at the TCP/IP level, domain names do not exist. We tell “nc” to send the request to “” port 80, but what it actually does is look it up using the domain name system (DNS) and then it uses the IP number instead.

We can do our own lookup using “dig”:


The answer is “” if you’re using IPv4. By default, that’s the answer you get because type “A” is the default. To get the IPv6 answer, you need to specify type “AAAA”.

dig -t AAAA

The answer is “2a02:418:6a04:178:209:50:237:1” if you’re using IPv6.

We can verify the results above by substituting the IP numbers ourselves:

echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\n\r\n" | nc 80

The solution, as far as HTTP was concerned, is the use of additional headers. HTTP has a ton of headers to tell servers whether they already have a resourced cached and how old it is, what sort of languages they’d prefer to get back, what sort of MIME types they’d like to get back, and so on. One of these headers tells the server what host we think we’re talking to.

Here’s how to do the requests with a host header:

echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\nhost:\r\n\r\n" | nc 80
echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\nhost:\r\n\r\n" | nc 80

The request for “” now has a redirect to the same resource on “”. You can double check by using the IP number:

echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\nhost:\r\n\r\n" | nc 2a02:418:6a04:178:209:50:237:1 80

What about Gemini? It doesn’t have headers to send along, but instead of just naming the path of the resource on the server like Gopher does, it names the URL it is requesting. Sadly, we can’t illustrate this unless we use TLS. If you are lucky, your “netcat” or “nc” has the “--ssl” option and we can keep using it.

First, let’s just quickly show that HTTPS is HTTP over SSL or TLS, on a different port:

echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\nhost:\r\n\r\n" | nc --ssl 443
echo -ne "GET /wiki/Alex_Schroeder HTTP/1.0\r\nhost:\r\n\r\n" | nc --ssl 443

And so we get to Gemini. As you can see, we got rid of the complication using HTTP headers.

echo -ne "gemini://\r\n" | nc --ssl 1965

All right! 🚀🚀😃🎉

I hope you can see how using the command line tools helped me understand these things.

Now you know how a server like Phoebe can look at the first line of the request it gets and determine whether to serve a Finger response, a Gopher response, a Web response, or a Gemini response.

References, linking to the older RFCs because they’re often simpler to read:

RFCs, yo! I head Gemini might get one, eventually?

Comments on 2021-07-24 An anatomy of the request

If you don’t have an --ssl option on nc you can try:

echo -ne "gemini://\r\n" \
  | openssl s_client

Fails needing a client certificate, but then I assume nc --ssl would fail that way too. I also assume an option on openssl would allow adding one but haven’t looked into it.

Ed Davies 2021-07-24 19:37 UTC

I think it works if you add the -quiet option and ignore stderr:

echo -ne "gemini://\r\n" \
  | openssl s_client -quiet 2>/dev/null

At the time I was experimenting with bash functions as clients, but I have since abandoned it.

At least I think there’s nothing in my personal setup, nothing in my “~/.ssh/config” file that modifies s_client.

– Alex 2021-07-24 20:58 UTC

Actually, /b wrote in letting me know that the key part is not --quiet but that --quiet also turns on --ign_eof, and that’s the important one.

– Alex 2021-07-28 07:50 UTC

Add Comment

2021-07-23 Phoebe 4 released

I finally finished the rewrite of Phoebe I had mentioned on The Transjovian Council (where most of my Gemini activity is). I think I managed to achieve some goals that had always eluded me with Oddmuse, my longest running project (a Usemod fork I started back in 2003!).

I wanted test for all the extensions, and I managed to do that. And Alex Daniel taught me the value of meta tests, so I also have a test that tests whether all the necessary tests exist. In a few cases, I’m even testing the examples provided in the documentation.

This leads me to the next issue I finally fixed. I really wanted better documentation. I’m not happy with how documentation for Oddmuse code ended up on the wiki. I keep thinking about that old adage: “wiki wiki is short for can’t find shit.” For this code, I wanted the code to be part of the source file such that sites like metacpan display it correctly. I also wanted to concatenate all the documentation into a single README document to use as a manual, and to have that show on code hosting services like cgit. This required some improvements to my ‘update-readme’ script, but I think it was worth it.

I also wanted the extensions to be formal Perl modules that you ‘use’ in your wiki configuration, not source files you link into a config directory and maybe they are up to date and maybe they are not. If you update Phoebe via CPAN, you get the new code, and all its dependencies, and all your extensions are also updated.

There is a bit of a grey area, here. Many of the extensions I have are probably of limited use to other people. Therefore I have declared the tests of these extensions to be “author tests”. That is, neither CPAN testers nor users installing Phoebe run those tests. Which then raises the question: should the dependencies for these optional extensions be installed as well? Perhaps the correct answer should be that these extensions belong into separate distributions? I don’t know. Maybe.

I managed to put the web serving into a separate extensions. So now, if you install Phoebe, you really are getting just a Gemini wiki. Phoebe is once again the Gemini-first, Web-second sort of wiki I wanted it to be.

In any case: I now have better code structure such that it looks good on metacpan and in git repositories, I have better tests, I have tests for tests, I have better documentation, I even managed to delete the documentation I had on The Transjovian Council because it was now integrated into Phoebe proper. I love it! 🚀🚀😃🎉

Of course, I already found more things to change. But slowly I’m approaching the point where I feel safe letting it lie for a few years once again, just chugging along and doing its job.

Comments on 2021-07-23 Phoebe 4 released


Add Comment

2021-07-15 Gemini Backlink Project, and Titan

Today I stumbled upon a new blog on Antenna.

The author introduced me to someting very much like webmentions. You know, the simple tech that the majority of blogs don’t support. In theory, it’s easy: the web page contains meta data pointing an agent to the right endpoint. There, you submit a tiny piece of text containing your URL, and the URL you’re linking to. Well, the Backlinks Project is similar, except that you don’t have a single end point where you need to provide two URLs, every target has an individual URL where you can provide your backlink, so you only have to provide your own URL. Works for me! I’ll have to add something like that to Phoebe, since Oddmuse already supports webmentions.

I was happy to see that the author had also implemented Titan support for their site! Oh wow. This makes me happy.

I wondered about having some sort of “bar” on The Transjovian Council, modelled along the Midnight Pub. A cantina? Or some long range slow bulletin board system metaphor. But I like the Midnight. Why open an alternative if I enjoy the original is it is? Maybe some other time. I don’t need anonymous posters. I guess what I could do is generate a random “space name” based on a client certificate without any further login? The “space names” I use for Ijirait are based on the random name generator for the old Elite game. Or just allow anonymous posting. Or perhaps only allow page creation for the current date. You can always edit existing pages to leave a comment, but you can only ever create a page for “today”. We could use some sort of “space date”.

Yeah, I still like Gemini. 🚀🚀😍

Add Comment

2021-07-14 Jupiter and Gemini

Jupiter is my “river of news” style feed aggregator, ostensibly for the web. But I just tried the following: you can provide it with a HTML template to use; and nothing prevents you from generating gemtext in that template!

This could be the “alex-gemini.html” template. Unfortunately, the “.html” extension is mandatory for both the file to generate and for the template to use.

% my ($globals, $feeds, $entries) = @_;
# Alex’ Reading List
% my $day = "";
% for my $entry (@$entries) {
%   $day = $entry->{day};
> <%= $entry->{excerpt} %>
=> <%= $entry->{link} %> <%= $entry->{day} %> <%= $entry->{blog_title} %> — <%= $entry->{title} %>
%   if ($entry->{categories}) {
Tags: <%= join ' ', map { "#$_" } (@{$entry->{categories}}); %>
%   }
% }

But no big deal, we can fix this by simply renaming the result. The arguments for a Jupiter call if you’re not taking the defaults are: “html” to generate the HTML, “index.html“ for the HTML file to generate (which is going to contain the resulting gemtext), “gemini-template.html” is the template above, and “alex.opml“ with the OPML listing all the feeds I care about. Once we’re done we can delete the generated feed “feed.xml” since we don’t care. I guess if we wanted to, we could also write a template for a Gemini-aware Atom feed and generate that, but this is just a demonstration.

jupiter html index.html gemini-template.html alex.opml \
&& mv index.html ~/ \
&& rm feed.xml

I guess I should get rid of the named entities, and the HTML spans in the excerpts, haha.

Well, I don’t think I’ll be using it in any case, since I’m using moku pona to generate Gemini documents based on the feeds.

Like this one:

It works.

Add Comment

2021-07-14 Elpher as the universal browser for Emacs

Elpher is my favourite browser in Emacs these days. There’s a fork I’m maintaining over here, which also adds deeper web support, registering it as the right browser for HTTP links. This prevents the switch from Elpher to Eww (my default browser in Emacs). When that happens, you can’t go “back”, you have to “bury” the Eww buffer in order to get back to Elpher, and the history doesn’t work, and Eww bookmarks are different from Elpher bookmarks... This Elpher fork also uses Emacs bookmarks instead of its own bookmarks.

Find out more about the fork:

If you like what you see, give it a try! 🚀🚀😄

The little video shows Emacs using the Brutalist Dark theme. It was recorded using Peek. It was edited and the audio was added using Pitivi. The audio was made on a small synthesizer, the OP-1. Basic recording was done using Audacity. Whoops! Use Tenacity?

Anyway, still getting the hang of these little videos. 😆

Comments on 2021-07-14 Elpher as the universal browser for Emacs

This is totally awesome!

– Sandra Snan 2021-07-14 22:16 UTC

Add Comment

2021-07-04 Links without scheme

I often write my blog posts using Elpher, a Gemini client for Emacs (a browser), using Gemini Mode (markup highlighting), posting them using Gemini Write (support for the Titan protocol). This Elpher setup ends up being smoother than using Eww (the Emacs Web Wowser, i.e. a web browser), and smoother than using Oddmuse Curl (the Oddmuse client for Emacs using curl).

When checking on the blog, when writing comments, I often use Firefox. Thus, I look at my own posts the Gemini browser Elpher, and the web browser Firefox. So how do I link from one page to the next? If I write “” then following the link from Elpher opens it in the web browser Eww. If I write “gemini://” then following the link from Firefox launches the Gemini browser Lagrange (not Emacs…).

One day I learned that URLs without a scheme are legal URLs. If I link to “//” then a browser looking at “” keeps using HTTPS and requests “” where as a browser looking at “gemini://” keeps using Gemini and requests “gemini://”.

This is a great start but that also means that the server has to be able to serve the URL using either scheme! Currently, the situation is this:

Therefore, I try to write my links without a scheme and then they’ll work for both worlds:

=> //
=> //


These links work in both a Gemini browser and a web browser!

Add Comment

2021-06-21 Gemini with ads

How would could we destroy Gemini like we destroyed the web using ads? Surely this is impossible, you exclaim! Not so. No dirty deed shall go unrewarded under capitalism and thus customized advertising and tracking is still possible, but it’ll be text based.

We have paid advertising that looks close to regular articles in the local newspapers, for example. So text-based ads are not impossible. Just think of all the email spam. It’s spam even if you don’t download all the pictures. Text-based is no defense against spam, and it’s no defense against ads on Gemini.

It is possible to track people using the IP numbers they are connecting from. If you run a server that writes IP numbers to a log file, you can write a server that keeps those IP numbers in a database. Sure, this fails in a lot of cases: people sharing IP numbers; people switching IP numbers. But it also works in a lot of cases.

Therefore it’ll still be possible to auction ads off to agencies and include the appropriate text – it just won’t be as in-your-face and as blatantly distributed as it is right now. It’ll take some programming on the backend. But as we can see right now, there’s enough money to do all the programming in the world for surveillance capitalism. The only reason people aren’t doing it is because Gemini is small.

Anyway, as tweed-cat said on the Gemini IRC channel: I think it would violate the General Data Protection Regulation (GDPR). But then again, the GDPR doesn’t cover the whole world, and even if you’re covered, somebody has to drag the offenders to court. So perhaps the ad industry won’t grow as strong as it is right now? I don’t know. If something is probably illegal, and might get you to court, if somebody were to investigate it, small time criminals might do it, but large companies will often not do it because of the legal risk.

In short: the small size of the community is what keeps us safe, and the GDPR is the shield that might protect us if we were big. I hope it’s true! 🚀🚀😅

Add Comment

2021-06-21 Gemini, RSS, XSLT

I was fiddling with Ijirait again, my Gemini-based MUSH. When you connect, it requests a client certificate to log you in, and that’s how it should be. But I also wanted to offer some form of “activity” signal to the outside world without forcing a visitor to generate a new identity (generating a client certificate also means creating a character in the game, which sounds a bit like a waste).

And here’s where I got a brain fart. Instead of simply generating a gemtext document, I generated some RSS, which I then couldn’t read using a feed reader. D’oh! And the double brain fart happened when I decided to improvise an XSLT document to transform the RSS into gemtext. How bad can it be?

Here’s the XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
<xsl:output method="text" />
<xsl:template match="channel/title">
# <xsl:value-of select="." />
<xsl:template match="item/title">
## <xsl:value-of select="." />
<xsl:template match="description"><xsl:value-of select="." /></xsl:template>
<xsl:template match="pubDate|generator|docs|link"></xsl:template>

And here’s how to call it (with ‘gemini’ being my command line tool), stripping the Gemini header using ‘tail’, processing the file using ‘xsltproc’, and stripping the empty lines using ‘grep’.

$ gemini gemini:// | tail -n +2 | xsltproc ~/src/gemini-xslt/gemini.xslt - | grep -v '^$'
# Ijirait
Recent activity.
Didia was active 28 minutes ago
Arinte was active 14 hours ago
Lerionlaer was active 41 hours ago
Belamageon was active 42 hours ago
Uscediti was active 44 hours ago
Gezaardienbi was active 46 hours ago
Atmaxeed was active 2 days ago
Arrionorce was active 2 days ago
Tilaor was active 2 days ago
Anbeinis was active 2 days ago
Orcetiar was active 3 days ago
Anxediat was active 3 days ago
Onatmage was active 3 days ago
Xeatesaso was active 4 days ago
Orremarile was active 4 days ago
Atebi was active 5 days ago
Sozare was active 5 days ago
Iserle was active 5 days ago
Sorele was active 6 days ago
Sogequri was active 6 days ago
Atqule was active 6 days ago
Ononinle was active 6 days ago
Orongexe was active 8 days ago
Avexeer was active 9 days ago
Ijiraq was active 9 days ago

And now I’m probably going to write the same thing, except it’ll be simply gemtext output. 😆

Comments on 2021-06-21 Gemini, RSS, XSLT


Add Comment



Please make sure you contribute only your own work, or work licensed under the GNU Free Documentation License. Note: in order to facilitate peer review and fight vandalism, we will store your IP number for a number of days. See Privacy Policy for more information. See Info for text formatting rules. You can edit the comment page if you need to fix typos. You can subscribe to new comments by email without leaving a comment.

To save this page you must answer this question:

Just say HELLO