User manual

GmCapsule is an extensible Gemini/Titan server.

Extensibility is achieved with Python modules that get loaded at launch from the configured directories. A set of built-in extension modules is provided for common functionality like CGI and for serving static files.

The supported protocols are Gemini and Titan. Both are accepted via the same TCP port.

GmCapsule can be used in a few different ways:

  • You can run it as-is for serving static files.

  • You can use CGI programs to generate dynamic content and/or process queries and uploads. As an extreme example, you could attach a CGI program to the path /* and generate the entire capsule procedurally with it.

  • You can use the included extension module gitview to make local Git repositories viewable via Gemini.

  • You can write new extension modules that run as part of the server process for advanced use cases. For example, this enables the use of additional worker threads and caching state in memory.

gmcapsuled is a simple command line utility for loading a configuration and running the server. Use the --help option to see usage instructions.

One can also run the server manually in Python:

from gmcapsule import *
cfg = Config("myconfig.ini")
capsule = Capsule(cfg)
capsule.run()

Getting started

  1. Acquire or generate a server certificate.

  2. Prepare a configuration file. A configuration file is required for running GmCapsule. For more details, see Configuration and example.ini.

  3. Run gmcapsuled.

Configuration

The GmCapsule configuration file is in INI format. The following sections are defined:

  • server — server settings

  • static — serving static files

  • titan — Titan upload settings

  • cgi.* — CGI programs

  • gitview — Git repository viewer settings

  • gitview.* — Git repository settings

Example of a minimal configuration file for serving static files from /var/gemini/example.com/:

[server]
host  = example.com
certs = /home/username/.certs

[static]
root = /var/gemini

server

hoststring [string…]

One or more hostnames for the server. Defaults to localhost.

addressstring

IP address of the network interface where the server is listening. Defaults to 0.0.0.0 (all interfaces).

portint

IP port on which the server is listening.

certspath

Directory where the server certificate is stored. The directory must contain the PEM-formatted files cert.pem and key.pem. Defaults to .certs.

modulespath [path…]

One or more directories to load extension modules from.

threadsint

Number of worker threads. At least 1 is required. Defaults to 5.

titan

Settings for Titan requests.

upload_limitint

Maximum size of content accepted in an upload, in bytes. Defaults to 10485760 (i.e., 10 MiB). Requests that attempt to upload larger content will be rejected with an error status.

static

Settings for the static module that serves files from a directory. Omitting this section from the configuration disables the module.

rootpath [path…]

Content directory. Defaults to . (current directory). The hostname is appended as a subdirectory, so for example if this is set to:

[static]
root = /home/user/gemini

files will be served from /home/user/gemini/example.com/.

cgi.*

Each section whose name begins with cgi. is used for setting up CGI entry points for the cgi module. For example, this registers gemini://example.com/listing to show the output of /bin/ls -l:

[cgi.listfiles]
path    = /listing
cwd     = /home/username/testdir
command = /bin/ls -l
protocolstring

Protocol for the CGI entry point. Defaults to gemini.

hoststring

Hostname for the CGI entry point. If omitted, defaults to the first hostname defined in the server section.

pathstring [string…]

URL path for CGI entry point. An asterisk * in the end is considered a wildcard, matching any path that begins with the specified path. Multiple paths can be specified.

cwdpath

Working directory for executing the CGI command. If omitted, the CGI command is executed in the directory where the server was started from.

commandstring [string…]

Command to execute when a request is received matching the CGI entry point. The specified strings are interpreted like a shell command, i.e., values that contain spaces need to be quoted.

See CGI programs for details about executing CGI programs.

gitview

Settings for the gitview module that enables viewing local Git repositories via Gemini. If this section is missing, gitview is disabled.

hoststring

Hostname where is gitview running. If omitted, defaults to the first hostname defined in the server section. gitview is limited to a single hostname.

This version of gitview assumes that the host is reserved just for viewing Git repositories. Therefore, using a dedicated virtual host is recommended, for example git.example.com. The hostname must be one of the hostnames in the server section.

gitpath

Path of the Git executable to use. Defaults to /usr/bin/git.

cache_pathpath

Directory where cached pages are stored. If omitted, caching is disabled.

gitview.*

Configuration of a Git repository that is visible via gitview. For example:

[gitview.lagrange]
title          = Lagrange
brief          = A Beautiful Gemini Client
clone_url      = https://git.skyjake.fi/gemini/lagrange.git
tag_url        = https://git.skyjake.fi/gemini/lagrange/releases/tag/{tag}
path           = /Users/jaakko/src/lagrange
url_root       = lagrange
default_branch = release
titlestring

Name of the repository.

briefstring

Short description of the repository. Shown on the root page.

clone_urlurl

Git clone URL.

tag_urlurl

Git tag URL for viewing tags in a web browser. This is useful for showing release notes and downloads attached to the tag. The placeholder {tag} is replaced with the tag to view.

pathpath

Local file path of the Git repository. Git will be run in this directory.

url_rootstring

URL root path where the Git repository can be viewed via Gemini.

default_branchstring

The default branch of the repository. This is used if no branch is specified in the URL.

Static files

Static files are served from the configured content directory. There must be a separate subdirectory for each configured hostname inside the content directory. For example, if the content directory is /var/gemini:

var
└── gemini
    ├── example.com
    └── sub.example.com

If the requested path is a directory that contains an index.gmi file, that file is automatically sent as a response.

If the requested path is a directory but the URL path does not end with a slash, a redirect is sent as a response with the slash added.

The MIME types of files are autodetected using Python’s mimetypes and the system file utility, if that is available. A few common file extensions like .txt are directly mapped to the corresponding MIME types.

.meta files

A .meta file can be placed in any directory inside the content directory tree, containing additional directives for handling static files.

Each .meta file affects the directory it’s in and also all its subdirectories. For example, placing a .meta in /var/gemini would affect all directories inside all virtual hosts.

The .meta file has a simple format. Each line has the following structure:

pattern: directive
pattern

Shell globbing expression. For example: *.zip

directive

MIME type, or the full Gemini header that will be sent when serving the file.

If only a MIME type is specified, that will be used instead of the autodetected type. If the full header is specified with a non-20 status code, the response body will be empty and only the header is sent.

CGI programs

CGI programs inherit the environment of the server process. Additionally, the following CGI-specific environment variables are set, providing input and contextual information:

  • REMOTE_ADDR

  • QUERY_STRING

  • PATH_INFO

  • SCRIPT_NAME

  • SERVER_PROTOCOL

  • SERVER_NAME

  • SERVER_PORT

  • AUTH_TYPE

  • REMOTE_IDENT (when client certificate provided)

  • REMOTE_USER (when client certificate provided)

  • CONTENT_LENGTH (Titan only)

  • CONTENT_TYPE (Titan only)

  • CONTENT_TOKEN (Titan only)

The CGI programs’s stdout is used as the response to the request. The response is expected to begin with a valid meta line (status code and meta text), but this is not required. If the meta line is missing, the following header is prepended to UTF-8 text output:

20 text/plain; charset=utf-8

Or, if the output is binary, the prepended header will be:

20 application/octet-stream

The response is sent to the client after the program finishes running.

With Titan requests, the uploaded content is passed in via stdin. Note that the CGI program is only executed after the full contents have already been successfully received, so the program does not need to worry about interrupted uploads.

Extensions

All Python modules in the configured extension module directories (see server configuration) are loaded at launch in alphabetical order, as long as they use the following naming convention:

NN_extmod.py

That is, the name of the extension (“extmod”) is prefixed with two numbers NN and an underscore. This naming gives more control over the order in which modules are loaded. The ones loaded first have precedence over registered URL entry points.

Initialization

Each extension module is required to have an initialization function:

extmod.init(capsule)

Initialize the extension module.

This is called once immediately after the extension has been loaded.

Parameters

capsule (gmcapsule.Capsule) – Server instance. The extension can use this to access configuration parameters, install caches, and register entry points.

Requests

An extension module is responsible for handling requests arriving at its registered entry points. A Request object is given as an argument when the registered entry point callback or handler object gets called.

The return value from the handler determines what gets sent as a response to the client:

  • Returning bytes or str means that the response is UTF-8 encoded “text/gemini”, and the status code is 20.

  • Returning a two-element tuple is interpreted as (status code, meta). The response body will be empty. This is useful for error messages and redirects.

  • Returning a three-element tuple is interpreted as (status code, meta, body). If the body type is str, it will be UTF-8 encoded before sending. Otherwise, the body is sent as-is.

Future improvements

The following limitations could/should be lifted in the future:

  • Extension modules should be reloadable. Add a deinit() method.

  • gitview should have a URL path prefix.

  • Caches should be informed of identities and queries; content might still be cacheable.