[1]skip to main content
[2] [logo]
• [3]Archives
• [4]Works
• [5]About
• [6]More...
Using Hugo to Launch a Gemini Capsule
3 April 2021
As you can read in the “[7]exploring the AlterNet” article, I’ve had my eye on
Gemini for a few weeks now. Ever since discovering the new protocol thanks to a
couple of weird Mastodon toots, I’ve been thinking about how to set up a
“capsule” (they’re not called sites) for myself. I like the appeal of a
text-focused, no-whizzbang protocol where the focus is on contents, not
aesthetics, especially for blogs such as this one.
A few questions needed to be answered before switching to action modus and
letting the static site generator Hugo do our dirty Gemini work for us.
How to host a Gemini capsule?
There are many pieces of [8]Gemini software available to us, but they’re all
quite new, as the protocol itself is from 2019. I was keen on trying out a
simple Go server, but both go-gemini-server, shavit, and go-gemini required me
to build it myself and contained very little documentation. Furthermore, some
packages weren’t updated in more than a year… In the end, I decided to go with
[9]Agate, a simple Gemini server written in Rust that can serve static files.
It has binaries for every platform, was updated six days ago, and its README.md
it extensive.
Agate even generates the needed TLS certificates if none are provided. This
allowed me to quickly set up a localhost server using the command agate
--content docs/gemini --addr 0.0.0.0:1965 --hostname localhost --lang en-US.
Fun fact about the port number:
When Gemini is served over TCP/IP, servers should listen on port 1965 (the
first manned Gemini mission, Gemini 3, flew in March ‘65).
Running locally before pushing to a server was important to me as I wanted to
fiddle with the .gmi files first to see how they look like in my Gemini browser
/client, [10]Lagrange. Gotta double-check the ASCII art!
What to publish on Gemini?
This is very personal. There are a few options. People like [11]Drew DevVault
and [12]Sylvain Durand mirror their HTTP(S) blog on Gemini, meaning all blog
entries are consultable both over the web and over Gemini. Then there are more
personal articles, published solely on Gemini to accompany the usually more
technical HTTP blogs, such as [13]gemini://space.eli.li/. He claims to use it
to whine like we did on MySpace yesteryear. I’ve also seen hybrids popping up:
articles that are ported, but some exclusive content is also available through
Gemini. I like that. My method at least makes this possible.
I wanted to blog in Dutch, my mother language, for a while now, and I’ve tried
it a few years back on Brain Baking. It didn’t work out. The entries were
misplaced somehow and I wasn’t satisfied, even though I did not expect to
actually have readers. I hoped to use a new domain, wouter.gr, for a Dutch
Gemini capsule to do some personal whining. That sounded like a good plan.
The plan fell through. Instead, I decided to mirror Brain Baking. Why?
• I already whine in Dutch in my personal diaries using a fountain pen. I do
not want to give that up.
• I already have a (nice?) blog, and I’d like to expand the Gemini
space-i-verse by adding my existing articles to it. I already write in
Markdown, so a conversion would be not too difficult.
• I don’t think I can keep up with posting on yet another blog, since I also
occasionally write about retro games on [14]jefklakscodex.com.
How to publish on Gemini?
Right. Porting articles turns out to be ridiculously easy with the help of my
good old friend, Hugo. [15]Sylvain’s method for declaring Gemini as a custom
Hugo output format turned out to work flawlessly. All credits go to him.
However, I did make a few significant changes to the link replacement system.
First, something important to consider: I do not get rid of special emphasis
symbols such as underscores or stars, that are Markdown-specific. I still think
they add something when reading plain text and they’re the next best thing to
have without any markup at all. So I removed those regex-es.
Gemini pages cannot have inline links, so I had to strip out Markdown-style []
() links and place them on a separate paragraph using => link title. A simple
find-and-replace, like in Sylvain’s method, is quite ugly if you use inline
links extensively like I do. It breaks up the text and the result is a
difficult to read Gemlog (that’s a Gemini blog!). In my approach, I collect all
links, replace them with a reference number like in academic papers ([1]), and
add a section called “References” on the bottom of the article to list them
all. This is what it looks like:
[16] [gemini] My Gemini AlterNet article in Lagrange.
I’m quite pleased with the result, although the code itself is far from pretty,
as Gemini is very newline-sensitive, and I had to jam a bunch of Hugo-specific
regex functions together. Source code available at GitHub: index.gmi source,
single.gmi source (see below). Next to the link change, I also replaced all -
and 1. (number) lines, that are enumerators in Markdown, with * ones, which is
the only supported enumerator in Gemfiles.
I tried to design the index and single layout files as similar as possible to
their html variants, while focusing in simplicity. Related articles are also
visible at the end of an article, and the index file simply contains a short
bio followed by an overview of all posts, groupbed by year and month, just like
in my [17]html /post overview. After defining [outputFormats.GEMINI] in my Hugo
config.toml, all that was left is to use rsync to copy over the gemini
subfolder to an appropriate location that gets picked up by Agate. Job done!
Well, not entirely. My Markdown files are littered with surprisingly
Hugo-specific junk:
• Shortcodes, such as YouTube, embedded video or audio.
• Four hashes - h4 - which isn’t supported by the Gemini protocol.
• tags in my quotes that help with HTML markup.
• Links to aliases that are redirects, which don’t work for the Gemini output
format.
Also, after trying out a second Gemini client, the terminal-friendly [18]Amfora
, I noticed the reference numbers do not align with Amfora’s shortcut keys that
allow you to quickly navigate to a link. Reference 1 would match to key 2. Why?
Because an image is also converted to a link (=> url), wich is placed
in-between text, while the actual references are at the bottom. Hence, pressing
number one would let us download the image - except Amfora can’t handle that
(yet). I solved this by starting at a specific index, based on the number of
times the arrow notation is present in the .gmi file, before processing inline
links. These are all things to take into account when writing future posts.
Now, the the most important question, “why publish on Gemini” could be answered
with “because it’s easy!”. I’m not yet sure if that answer is very
satisfactory, but at least Brain Baking got launched into Space today 🚀! All
that is left is to submit it to the GUS Gemini Universal Search engine…
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Edit 21 June 2021: After a few months of fiddling with Gemini, I came to the
conclusion that it’s simply too early. There’s almost nothing there, and it
only increases the complexity of my website codebase. Therefore, I pulled yet
another plug. Sorry!
For future reference, the following files have been added to enable Gemini
functionality:
layouts/_default/index.atom.xml:
{{- $allowedRssSections := (slice "post") -}}
{{- $baseurl := .Site.BaseURL -}}
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "" | safeHTML }}
{{ .Site.Title }}
{{- $perm := replace .Permalink "/gemini" "" 1 -}}
{{- $alt := .Site.BaseURL | replaceRE `https?://(.+?)` "gemini://$1" -}}
{{ printf "" $perm | safeHTML }}
{{ printf "" $alt | safeHTML }}
{{ .Date.Format "2006-01-02T15:04:05-0700" | safeHTML }}
{{ .Site.Author.name }}
{{ .Site.BaseURL | replaceRE `https?://(.+?)` "gemini://$1" }}
{{ $perm }}
{{ range $pages }}
{{ if in $allowedRssSections .Section }}
{{ .Title }}
{{- $entryperm := .Permalink | replaceRE `https?://(.+?)` "gemini://$1" -}}
{{ printf "" $entryperm | safeHTML }}
{{ $entryperm }}
{{ .Date.Format "2006-01-02T15:04:05-0700" | safeHTML }}
{{ .Lastmod.Format "2006-01-02T15:04:05-0700" | safeHTML }}
{{ if isset .Params "subtitle" }}{{ .Params.subtitle }}{{ else }}{{ .Summary | html }}{{ end }}
{{ end }}
{{ end }}
layouts/index.gmi:
# Brain Baking in Space
> Brain Baking: transforming personal thoughts about thoughts into well-digestible material. The reflective aroma of burnt nervous tissue. Includes a crispy crust of relations between technology, philosophy and the world.
## About The Head Brain Baker
Hey! Yadda yadda
=> https://ko-fi.com/woutergroeneveld Ko-fi Donations
=> mailto:{{ .Site.Author.email }} E-mail
## Freshly Baked Thoughts: The Gemlog
=> /atom.xml Gemini Atom Feed
{{ range (where (where (where .Site.Pages "Section" "in" (slice "post")) ".Params.type" "ne" "archive") ".Params.concept" "ne" "true").GroupByDate "2006" "desc" }}{{ $year := .Key -}}
{{ range .Pages.GroupByDate "January" }}
### {{ .Key }} {{ $year }}
{{ range .Pages.ByDate.Reverse }}
=> {{ replace .RelPermalink "/gemini" "" 1}} {{ .Date.Format ("02") }} - {{ .Title }}
{{ .Params.Subtitle }}{{ end }}
{{ end }}
{{ end }}
# That's All Folks.
=> https://brainbaking.com Brain Baking on the WWW
And lastly, layouts/_default/single.gmi: (Note the space between {{ < that
should be removed)
# {{ .Title }}{{ $scratch := newScratch }}
{{ $content := .RawContent -}}
{{ $content := $content | replaceRE `#### ` "### " -}}
{{ $content := $content | replaceRE `\n- (.+?)` "\n* $1" -}}
{{ $content := $content | replaceRE `\n(\d+). (.+?)` "\n* $2" -}}
{{ $content := $content | replaceRE `\[\^(.+?)\]:?` "" -}}
{{ $content := $content | replaceRE `
` "\n" -}}
{{ $content := $content | replaceRE `(.+?)` "[$2]($1)" -}}
{{ $content := $content | replaceRE `\sgemini://(\S*)` " [gemini://$1](gemini://$1)" -}}
{{ $content := $content | replaceRE `{{ < audio "(.+?)" >}}` "=> https://brainbaking.com/$1 Embedded Audio link - $1" -}}
{{ $content := $content | replaceRE `{{ < video "(.+?)" >}}` "=> https://brainbaking.com/$1 Embedded Video link - $1" -}}
{{ $content := $content | replaceRE `{{ < youtube (.+?) >}}` "=> https://www.youtube.com/watch?v=$1 YouTube Video link to $1" -}}
{{ $content := $content | replaceRE `{{ < vimeo (.+?) >}}` "=> https://vimeo.com/$1 Vimeo Video link to $1" -}}
{{ $content := $content | replaceRE "([^`])<.*?>([^`])" "$1$2" -}}
{{ $content := $content | replaceRE `\n\n!\[.*\]\((.+?) \"(.+?)\"\)` "\n\n=> $1 Image: $2" -}}
{{ $content := $content | replaceRE `\n\n!\[.*]\((.+?)\)` "\n\n=> $1 Embedded Image: $1" -}}
{{ $links := findRE `\n=> ` $content }}{{ $scratch.Set "ref" (add (len $links) 1) }}
{{ $refs := findRE `\[.+?\]\(.+?\)` $content }}
{{ $scratch.Set "content" $content }}{{ range $refs }}{{ $ref := $scratch.Get "ref" }}{{ $contentInLoop := $scratch.Get "content" }}{{ $url := (printf "%s #%d" . $ref) }}{{ $contentInLoop := replace $contentInLoop . $url -}}{{ $scratch.Set "content" $contentInLoop }}{{ $scratch.Set "ref" (add $ref 1) }}{{ end }}{{ $content := $scratch.Get "content" | replaceRE `\[(.+?)\]\((.+?)\) #(\d+)` "$1 [$3]" -}}
{{ $content | safeHTML }}
---
Written by Wouter Groeneveld on {{ .Lastmod.Format (.Site.Params.dateFormat | default "2 January 2006") }}.
## References
{{ $scratch.Set "ref" (add (len $links) 1) }}{{ range $refs }}{{ $ref := $scratch.Get "ref" }}{{ $url := (printf "%s #%d" . $ref) }}
=> {{ $url | replaceRE `\[(.+?)\]\((.+?)\) #(\d+)` "$2 [$3] $1 ($2)" -}}
{{ $scratch.Set "ref" (add $ref 1) }}{{ end}}
{{ $related := first 3 (where (where .Site.RegularPages.ByDate.Reverse ".Params.tags" "intersect" .Params.tags) "Permalink" "!=" .Permalink) }}
{{ if $related }}
## Related articles
{{ range $related }}
=> {{ replace .RelPermalink "/gemini" "" 1}} {{ .Title }}: {{ .Params.Subtitle }}{{ end }}{{ end }}
---
=> / Back to the Index
=> https://brainbaking.com{{ replace (replace .RelPermalink "/gemini" "" 1) "index.gmi" "" }} View this article on the WWW
For more information, feel free to contact me or to [19]plod around in the Git
repo history tab.
[20]webdesign [21]gemini [22]hugo [23]accessibility
You Might Also Like...
• [24]Why I Retired My Webmention Server 08 May 2023
• [25]Cool Things People Do With Their Blogs 27 Apr 2022
• [26]Reducing Workflow Load Facilitates Writing 03 Jul 2021
• [27]Exploring the AlterNet 24 Mar 2021
• [28]Finding Related Images in Hugo 08 Oct 2024
• [29]Visualizing Blog Post Links With Obsidian 10 Jun 2024
• [30]Displaying Series of Posts in Hugo 04 Jan 2024
Bio and Support
[avatar2024]
I'm [31]Wouter Groeneveld, a Brain Baker, and I love the smell of freshly baked
thoughts (and bread) in the morning. I sometimes convince others to bake their
brain (and bread) too.
If you found this article amusing and/or helpful, you can support me via [32]
PayPal or [33]Ko-Fi. I also like to hear your feedback via [34]Mastodon or
email. Thanks!
JavaScript is disabled. I use it to obfuscate my e-mail, keeping spambots at
bay.
Reach me using: [firstname] at [this domain].
↑ [35]Top | [36]Archives | [37]RSS Feed | [38]bv | [39]© CC BY 4.0 License.
[40] [brainbakin]
References:
[1] https://brainbaking.com/post/2021/04/using-hugo-to-launch-a-gemini-capsule/#top
[2] https://brainbaking.com/
[3] https://brainbaking.com/archives/
[4] https://brainbaking.com/works/
[5] https://brainbaking.com/about
[6] https://brainbaking.com/more
[7] https://brainbaking.com/post/2021/03/exploring-the-alternet/
[8] https://gemini.circumlunar.space/software/
[9] https://github.com/mbrubeck/agate
[10] https://gmi.skyjake.fi/lagrange/
[11] gemini://drewdevault.com/
[12] gemini://sylvaindurand.org
[13] gemini://space.eli.li/
[14] https://jefklakscodex.com/
[15] https://sylvaindurand.org/gemini-and-hugo/
[16] https://brainbaking.com/post/2021/04/using-hugo-to-launch-a-gemini-capsule/gemini.jpg
[17] https://brainbaking.com/post
[18] https://github.com/makeworld-the-better-one/amfora
[19] https://git.brainbaking.com/wgroeneveld/brainbaking/
[20] https://brainbaking.com/categories/webdesign
[21] https://brainbaking.com/tags/gemini
[22] https://brainbaking.com/tags/hugo
[23] https://brainbaking.com/tags/accessibility
[24] https://brainbaking.com/post/2023/05/why-i-retired-my-webmention-server/
[25] https://brainbaking.com/post/2022/04/cool-things-people-do-with-their-blogs/
[26] https://brainbaking.com/post/2021/07/reducing-workflow-load-facilitates-writing/
[27] https://brainbaking.com/post/2021/03/exploring-the-alternet/
[28] https://brainbaking.com/post/2024/10/finding-related-images-in-hugo/
[29] https://brainbaking.com/post/2024/06/visualizing-blog-post-links-with-obsidian/
[30] https://brainbaking.com/post/2024/01/displaying-series-of-posts-in-hugo/
[31] https://brainbaking.com/about
[32] https://www.paypal.com/donate/?hosted_button_id=R2WTKY7G9V2KQ
[33] https://ko-fi.com/woutergroeneveld
[34] https://dosgame.club/@jefklak
[35] https://brainbaking.com/post/2021/04/using-hugo-to-launch-a-gemini-capsule/#top
[36] https://brainbaking.com/archives
[37] https://brainbaking.com/index.xml
[38] https://brainbaking.com/bv
[39] https://brainbaking.com/copyright-and-tracking-policy
[40] https://brainbaking.com/links