Agate
Very simple server for the Gemini hypertext protocol
Install / Use
/learn @mbrubeck/AgateREADME
Agate
Simple Gemini server for static files
Agate is a server for the [Gemini] network protocol, built with the [Rust] programming language. Agate has very few features, and can only serve static files. It uses async I/O, and should be quite efficient even when running on low-end hardware and serving many concurrent requests.
Learn more
- Home page: [gemini://qwertqwefsday.eu/agate.gmi][home]
- [Cargo package][crates.io]
- [Source code][source]
Installation and Setup
- Get a binary for agate. You can use any of the below ways:
Pre-compiled
Download and unpack the pre-compiled binary.
NixOS/Nix
Using the nix package manager run nix-env -i agate
Guix System
Deploy agate with GNU Guix System by adding the agate-service-type to your system services.
Arch Linux
Install the package agate-bin<sup>AUR</sup> for pre-compiled binary. Otherwise install the agate<sup>AUR</sup> package to get agate compiled from source.
Cargo
If you have the Rust toolchain installed, run cargo install agate to install agate from crates.io.
Docker
Recent builds have also been released as OCI/Docker images on Github's Container Registry (ghcr.io). Most people will need to mount two volumes, one for your content, one for your certificates (this can be empty, they will be automatically generated if needed):
$ docker run \
-p 1965:1965 \
-v your/path/to/gmi:/gmi \
-v your/path/to/certs:/certs \
ghcr.io/mbrubeck/agate:latest \
--hostname example.org
This container will run without a mounted certificates directory, but new certificates will be lost when it shuts down and re-generated every time it boots, showing your site's visitors a certificate warning each time your server restarts.
Each release is tagged with major, major.minor, major.minor.patch, as well as the full version string and "latest". This means docker pull ghcr.io/mbrubeck/agate:3 will always retrieve the latest v3.* image, …:3.3 the latest v3.3.* image, and …:latest the most recent release of any version.
Source
Download the source code and run cargo build --release inside the source repository, then find the binary at target/release/agate.
You can use the install script in the tools directory for the remaining steps if there is one for your system.
If there is none, please consider contributing one to make it easier for less tech-savvy users!
- Run the server. You can use the following arguments to specify the locations of the content directory, IP address and port to listen on, host name to expect in request URLs, and default language code to include in the MIME type for text/gemini files: (Replace the hostname
example.comwith the address of your Gemini server.) If you have not done it yourself, Agate will generate a private key and certificate for you on the first run, using the specified hostname(s). See the section Certificates below for more.
agate --content path/to/content/ \
--addr [::]:1965 \
--addr 0.0.0.0:1965 \
--hostname example.com \
--lang en-US
All of the command-line arguments are optional. Run agate --help to see the default values used when arguments are omitted.
When a client requests the URL gemini://example.com/foo/bar, Agate will respond with the file at path/to/content/foo/bar. If any segment of the requested path starts with a dot, agate will respond with a status code 52, whether the file exists or not. This behaviour can be disabled with --serve-secret or by an entry for the specific file in the .meta configuration file (see Meta-Presets). If there is a directory at that path, Agate will look for a file named index.gmi inside that directory.
Configuration
Automatic Certificate generation
If the --hostname argument is used, Agate will generate keys and self signed certificates for each hostname specified.
Please note that using an IP address for certificates is not (intentionally) supported.
For Gemini it is recommended by the specification to use self signed certificates because Gemini uses the TOFU (Trust on first use) principle for certificates.
Because of this, the generated certificates will also have a long expiration time of 4096-01-01.
For manual configuration of keys and certificates see the section on certificates below.
TLS versions
Agate by default supports TLSv1.2 and TLSv1.3. You can disable support for TLSv1.2 by using the flag --only-tls13 (or its short version -3). This is NOT RECOMMENDED as it may break compatibility with some clients. The Gemini specification requires compatibility with TLSv1.2 "for now" because not all platforms have good support for TLSv1.3 (cf. §4.1 of the specification).
Directory listing
You can enable a basic directory listing for a directory by putting a file called .directory-listing-ok in that directory. This does not have an effect on sub-directories.
This file must be UTF-8 encoded text; it may be empty. Any text in the file will be prepended to the directory listing.
The directory listing will hide files and directories whose name starts with a dot (e.g. the .directory-listing-ok file itself, the .meta configuration file, or the .. directory).
A file called index.gmi will always take precedence over a directory listing.
Meta-Presets
You can put a file called .meta in any content directory. This file stores some metadata about the adjacent files which Agate will use when serving these files. The .meta file must be UTF-8 encoded.
You can also enable a central configuration file with the -C flag (or the long version --central-conf). In this case Agate will always look for the .meta configuration file in the content root directory and will ignore .meta files in other directories.
The .meta file has the following format (*1):
- Empty lines are ignored.
- Everything behind a
#on the same line is a comment and will be ignored. - All other lines must have the form
<path>:<metadata>, i.e. start with a file path, followed by a colon and then the metadata.
<path> is a case sensitive file path, which may or may not exist on disk. If <path> leads to a directory, it is ignored.
If central configuration file mode is not used, using a path that is not a file in the current directory is undefined behaviour (for example ../index.gmi would be undefined behaviour).
You can use Unix style patterns in existing paths. For example content/* will match any file within content, and content/** will additionally match any files in subdirectories of content.
However, the * and ** globs on their own will by default not match files or directories that start with a dot because of their special meaning.
This behaviour can be disabled with --serve-secret or by explicitly matching files starting with a dot with e.g. content/.* or content/**/.* respectively.
For more information on the patterns you can use, please see the documentation of glob::Pattern.
Rules can overwrite other rules, so if a file is matched by multiple rules, the last one applies.
<metadata> can take one of four possible forms:
- empty
Agate will not send a default language parameter, even if it was specified on the command line. - starting with a semicolon followed by MIME parameters
Agate will append the specified string onto the MIME type, if the file is found. - starting with a gemini status code (i.e. a digit 1-6 inclusive followed by another digit) and a space
Agate will send the metadata whether the file exists or not. The file will not be sent or accessed. - a MIME type, may include parameters
Agate will use this MIME type instead of what it would guess, if the file is found. The default language parameter will not be used, even if it was specified on the command line.
If a line violates the format or looks like case 3, but is incorrect, it might be ignored. You should check your logs. Please know that this configuration file is first read when a file from the respective directory is accessed. So no log messages after startup does not mean the .meta file is okay.
Such a configuration file might look like this:
# This line will be ignored.
**/*.de.gmi: ;lang=de
nl/**/*.gmi: ;lang=nl
index.gmi: ;lang=en-GB
LICENSE: text/plain;charset=UTF-8
gone.gmi: 52 This file is no longer here, sorry.
If this is the .meta file in the content root directory and the -C flag is used, this will result in the following response headers:
/or/index.gmi->20 text/gemini;lang=en-GB/LICENSE->20 text/plain;charset=UTF-8/gone.gmi->52 This file is no longer here, sorry.- any non-hidden file ending in
.de.gmi(including in non-hidden subdirectories) ->20 text/gemini;lang=de - any non-hidden file in the
nldirectory ending in.gmi(including in non-hidden subdirectories) ->20 text/gemini;lang=nl
(*1) In theory the syntax is that of a typical INI-like file and also allows for sections with [section] (the default section is set to mime in the parser), since all other sections are disregarded, this does not make a difference. This also means that you can in theory also use = instead o
