Gemini Gemtext blog engine and to HTML + Markdown converter
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Go to file
Paul Buetow 10bded3269 Make newer version of ShellCheck happy again 4 weeks ago
extras dont use 99% 2 months ago
lib Make newer version of ShellCheck happy again 4 weeks ago
.gitignore add POST_PUBLISH_HOOK 3 months ago link samples page 2 months ago
LICENSE Initial commit 2 years ago draft also generates .gmi files from draft templates 2 months ago
favicon.ico add external ttf fonts 1 year ago
gemtexter bump verion 2 months ago add more vars 2 months ago add more vars 2 months ago
gemtexter.conf use hack font by default 2 months ago

The Gemtexter blog engine and static site generator

This is the source code of my personal internet site and blog engine. All content is written in Gemini Gemtext format, but the script gemtexter generates multiple other static output formats from it. You can reach the site(s)...

Have a look at the content-* branches of the Git project for static content examples.

External Licenses

Gemtexter uses some external TrueType fonts for the HTML output. For license information please look into all font sub-directories of the HTML extras folder. But to summarize, all fonts are free for personal use.

Getting started


These are the requirements for running the gemtexter static site generator script:

  • GNU Bash 5.x or higher
  • GNU Sed
  • GNU Date
  • GNU Grep
  • Git (optional for version control)
  • ShellCheck installed (optional for testing)
  • XMLLint (optional for validating the atom feed syntax)

The script is tested on a recent Fedora Linux. For *BSD or macOS, you would need to install GNU Sed, GNU Date, GNU Grep and a newer version of Bash.


So you want such a pretty internet site too?

To get started, clone this repo and run ./gemtexter. You will be prompted with further instructions.

You will notice soon that all site content is located in ../ (you can configure the $BASE_CONTENT_DIR in gemtexter.conf). There is one sub-directory per output format, e.g.:


What is what

Whereas you only want to edit the content in the gemtext folder directly. The gemtexter then will take the Gemtext and update all other formats accordingly. Summary of what is what:

  • gemtext: The Gemini Gemtext markup files of the internet site. This can also contain Gemtext template files.
  • html: The XHTML version of it.
  • md: The Markdown version of it.
  • cache: Some volatile cache data for speeding up Atom feed generation.

Store all formats in Git

It is advisable to store $BASE_CONTENT_DIR/{gemtext,html,md} in a separate Git repository each. Gemtexter automatically detects whether one of these directories is in Git. It is then possible to run ./gemtexter --git-add command for adding all new and changed files to Git and ./gemtexter --git-sync for synchronizing everything with the remote repositories. The GIT_COMMIT_MESSAGE environment variable can be set to for customizing the Git commit message (E.g.: GIT_COMMIT_MESSAGE='New blog post' ./gemtexter --git-add.

Publishing a blog post

What needs to be done is to create a new file in $BASE_CONTENT_DIR/gemtext/gemfeed/YYYY-MM-DD-article-title-dash-separated.gmi, whereas YYYY-MM-DD defines the publishing date of the blog post.

A subsequent ./gemtexter --generate will then detect the new post and link it from $BASE_CONTENT_DIR/gemtext/gemfeed/index.gmi, link it from the main index $BASE_CONTENT_DIR/gemtext/index.gmi, and also add it to the Atom feed at $BASE_CONTENT_DIR/gemtext/gemfeed/atom.xml.

  • The first level 1 Gemtext title (e.g. # Title here) will be the displayed link name from the index.gmi's mentioned above.
  • By default, the last modification time of the Gemtext file will be the publishing date. Gemtexter will add a > Published at TIMESTAMP right underneath the title if that line isn't there yet. That timestamp will be used for subsequent atom.xml feed generations as the feed entry timestamp.
  • Various other settings, such as Author, come from the gemtexter.conf configuration file.

An example blog posts looks like this:

% cat gemfeed/2023-02-26-title-here.gmi
# Title here

> Published at 2023-02-26T21:43:51+01:00

The remaining content of the Gemtext file...

Once all of that is done, the gemtexter script will convert the new post (plus all the indices and the Atom feed) to the other formats, too (e.g. HTML, Markdown).

Ready to be published

After running ./gemtexter --generate, you will have all static files ready to be published. But before you do that, you could preview the content with firefox $BASE_CONTENT_DIR/html/index.html or glow $BASE_CONTENT_DIR/md/ (you get the idea).

Have also a look at the generated $BASE_CONTENT_DIR/{gemtext,html}/gemfeed/atom.xml Atom feed files.

If you use git, you can use ./gemtexter --publish, which does a --generate followed by a --git-add and a --git-sync.

It is up to you to set up a Gemini server for the Gemtext, a webserver for the HTML or a GitHub page for the Markdown format (or both). You could also set up a cron job on your server to periodically pull new Gemtext, HTML and Markdown content from your Git repository.

Advanced usage

Content filter

Once your capsule reaches a certain size it can become annoying to re-generate everything if you only want to preview one single content file. The following will add a filter to only generate the files matching a regular expression:

./gemtexter --generate '.*hello.*'

This will help you to quickly review the results once in a while. Once you are happy you should always re-generate the whole capsule before publishing it! Note, that there will be no Atom feed generation in filter mode so before publishing it you should always run a full --generate.


Since version 2.0.0, Gemtexter supports templating. A template file name must have the suffix gmi.tpl. A template must be put into the same directory as the Gemtext .gmi file to be generated. Gemtexter will generate a Gemtext file index.gmi from a given template index.gmi.tpl. All lines starting with << will be evaluated as a single line of Bash code and the output will be written into the resulting Gemtext file. A <<< and >>> encloses a multiline template.

For example, the template index.gmi.tpl:

# Hello world

<< echo "> This site was generated at $(date --iso-8601=seconds) by \`Gemtexter\`"

Welcome to this capsule!

  for i in {1..10}; do
    echo Multiline template line $i

... results into the following index.gmi after running ./gemtexter --generate (or ./gemtexter --template, which instructs to do only template processing and nothing else):

# Hello world

> This site was generated at 2023-03-15T19:07:59+02:00 by `Gemtexter`

Welcome to this capsule!

Multiline template line 1
Multiline template line 2
Multiline template line 3
Multiline template line 4
Multiline template line 5
Multiline template line 6
Multiline template line 7
Multiline template line 8
Multiline template line 9
Multiline template line 10

Another thing you can do is insert an index with links to similar blog posts. E.g.:

See more entries about DTail and Golang:

<< template::inline::index dtail golang


... scans all other post entries with dtail and golang in the file name and generates a link list like this:

See more entries about DTail and Golang:

=> ./2022-10-30-installing-dtail-on-openbsd.gmi 2022-10-30 Installing DTail on OpenBSD
=> ./2022-04-22-programming-golang.gmi 2022-04-22 The Golang Programming language
=> ./2022-03-06-the-release-of-dtail-4.0.0.gmi 2022-03-06 The release of DTail 4.0.0
=> ./2021-04-22-dtail-the-distributed-log-tail-program.gmi 2021-04-22 DTail - The distributed log tail program (You are currently reading this)


Alternative configuration file path

If you don't want to mess with gemtexter.conf, you can use an alternative config file path in ~/.config/gemtexter.conf, which takes precedence if it exists. Another way is to set the CONFIG_FILE_PATH environment variable, e.g.:

export CONFIG_FILE_PATH=~/.config/my-site.geek.conf
./gemtexter --generate

Special HTML configuration

You will find the ./extras/html/header.html.part and ./extras/html/footer.html.part files, they are minimal template files for the HTML generation. There's also the ./extras/html/style.css for HTML.

gemtexter will never touch the $BASE_CONTENT_DIR/html/.domains, as this is a required file for a Codeberg page. Furthermore, the robots.txt file won't be overridden as well.

Special Markdown configuration for GitHub pages

gemtexter will never touch the $BASE_CONTENT_DIR/md/_config.yml file (if it exists). That's a particular configuration file for GitHub Pages. gemtexter also will never modify the file $BASE_CONTENT_DIR/md/CNAME, as this is also a file required by GitHub pages for using custom domains.

Happy gemtexting!!