Slow Servers' Gemlog: Galileo to Static HTML

Posted: 2026-07-24

I've been using Galileo to proxy HTTP requests to Slow Servers' native Gemini capsule.

This has worked very well, but it's not the most efficient setup. I've found that I love Gemtext as a CMS of sorts.

I ended up writing a Gemini to HTML converter in Perl and a shell script to do batch conversions of a directory.

This allows httpd to serve files much faster and more efficiently. I doubt anyone will notice the different of this site unless benchmarking, but it gives me some more options to expand on this model. One thing I'd like to add is automatic gemlog to RSS.

All URLs on the HTTP/HTTPS endpoints ended in .gmi, just like on the Gemini endpoints. Now HTTP/HTTPS serves .html files, which makes more sense. .gmi is 301 redirected to .html, so existing links should work seamlessly. It's still Gemini native, but without any overhead for using HTTP.

All of that said, if you find any bugs, please let me know!

For the curious, here's the current script at the time of this writing. The CSS is hardcoded and shamelessly based on Galileo's CSS.

My Perl is not great, I'm sure this could be a lot cleaner!

#!/usr/bin/perl

use v5.40;
use OpenBSD::Pledge;
use English;

open my $file, $ARGV[0] or die "Could not open file: $!";

die "pledge" unless pledge;

say '';
say '';
say '';
say '';
say '';

my $inpreformatted = 0;
my $inlist         = 0;
my $inquote        = 0;

while ( my $line = <$file> ) {
    chomp $line;
    if ($inlist) {
        if ( index( $line, "* " ) != 0 ) {
            say "";
            $inlist = 0;
        }
    }
    if ($inquote) {
        if ( index( $line, ">" ) != 0 ) {
            say "";
            $inquote = 0;
        }
    }

    if ( index( $line, "# " ) == 0 ) {
        $line =~ s/^# //;
        say "

$line

"; } elsif ( index( $line, "## " ) == 0 ) { $line =~ s/^## //; say "

$line

"; } elsif ( index( $line, "### " ) == 0 ) { $line =~ s/^### //; say "

$line

"; } elsif ( index( $line, "#### " ) == 0 ) { $line =~ s/^#### //; say "

$line

"; } elsif ( index( $line, "=> " ) == 0 ) { $line =~ s/^=\> //; my @parts = split( ' ', $line ); my $url = $parts[0]; $url =~ s/\.gmi$/.html/; my $text = join( ' ', @parts[ 1 .. $#parts ] ); if ( $url =~ /\.(jpg|png)$/ ) { say "
$text
"; } else { say ""; } } elsif ( $line eq '```' ) { if ($inpreformatted) { say '
'; $inpreformatted = 0; } else { say '
';
            $inpreformatted = 1;
        }
    }
    elsif ( index( $line, "* " ) == 0 ) {
        if ( $inlist == 0 ) {
            say "
#!/bin/sh

set -e

SRCDIR=$1

DESTDIR=$2

rm -r "$DESTDIR" || true

mkdir "$DESTDIR"

cd "$SRCDIR"

Make relevant directories.

find . -type d -exec mkdir $DESTDIR/{} \; find . -type f ! -name '*.gmi' -exec cp -a {} $DESTDIR/{} \; for gemini in $(find . -type f -name '*.gmi'); do html=$(echo $gemini | sed 's/\.gmi$/.html/') gemini-to-html $gemini > $DESTDIR/$html done