Apogee is a server for the Gemini protocol, written in Kotlin.
Apogee currently has support for the following features:
- Static file serving.
- Redirections.
- Client authentication.
- CGI and SCGI.
Apogee runs on a JVM, so some implementation is required (it was developed and tested on OpenJDK). Java 8 is the minimum supported version.
Other than that, Apogee depends on the following external libraries:
- Netty to handle socket connections.
- TOML4J to handle configuration file parsing.
- kotlinx-cli to handle command line options.
Apogee has been developed and tested for Linux; Windows is not officially supported, though if you try to run it on Windows and encounter a specific problem please feel free to file an issue.
Currently, the easiest way to run Apogee is to clone the Git repository:
git clone https://github.com/bunburya/apogee.git
Then, from the cloned directory, run:
./gradlew run --args="--config /path/to/config.toml"
Alternatively, you can build a shell script:
./gradlew build -x test
This builds Apogee without running the tests (some of which are integration tests that depend on an already running
instance). The shell script can be found in the build/distributions
directory (in the bin
subdirectory of the
directory you get when you extract the zip or tar archive). You can then initialise Apogee using the following command
(make sure the apogee
shell script is in your shell path):
apogee --config /path/to/config.toml
Apogee is configured through a single TOML file. This section will discuss the main configuration
options. In the src/main/resources
directory you will find an example config file, populated with default values
where appropriate.
NOTE: Configuration of certain features, such as redirection, is done via key-value pairs stored in tables, where the keys are regex patterns against which the path is compared. The first pattern which matches the path will be used to determine the appropriate value. Tables in TOML files are not guaranteed to be in any particular order. Therefore, you should be careful to ensure that patterns are mutually exclusive, as there is no guarantee as to what order they will be checked.
These are settings to control the basic behaviour of Apogee.
Option name | Description | Default value |
---|---|---|
PORT | The port on which Apogee will listen for requests. | 1965 |
HOSTNAME | The name of the host at which Apogee will listen for requests. | localhost |
CERT_FILE | The location of the file containing the server's SSL certificate, in PEM format. | cert.pem |
KEY_FILE | The location of the file containing the server's SSL key, in PEM format. | key.pem |
DOCUMENT_ROOT | The location of the directory containing the files to be served by Apogee. | /var/gemini/ |
ACCESS_FILE | Where to log access attempts. | null (logs to standard error) |
LOG_FILE | Where to store general logs. | null (logs to standard error) |
LOG_LEVEL | How severe a log message must be in order to be logged to LOG_FILE. This should correspond to the levels used by the java.util.logging library, ie, it should be one of "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER" or "FINEST". | WARNING |
These settings control certain aspects of how Apogee serves static files.
Option name | Description | Default value |
---|---|---|
GEMINI_EXT | The file extension that Apogee will interpret as indicating the "text/gemini" MIME type. | gmi |
INDEX_FILE | The name of the file that Apogee will attempt to serve if a request corresponds to a directory. If no index file is present, Apogee will display a basic index which lists files in the directory. | index.gmi |
DIR_SORT_METHOD | (Not yet implemented) Where Apogee auto-generates an index for a directory, how to sort the files in the directory. | NAME |
The MIME_OVERRIDES
section allows you to customise the MIME type that is sent where a filename corresponds to a given
regular expression.
Apogee currently supports dynamic content through the CGI and SCGI protocols. It is intended to also support FastCGI in the future.
The CGI_TIMEOUT
option governs how many seconds Apogee should wait for a response from a process before returning an
error response. It defaults to 10.
The CGI_PATHS
option should be an array of filesystem paths to directories. Where a request corresponds to a file in
one of these directories, Apogee will attempt to run that file as a script and return the standard output of that script
to the client.
The SCGI_PATHS
section is a table mapping (relative) URL prefixes to filesystem paths to Unix domain sockets. Where a
request contains one of the specified prefixes, it will be encoded according to the SCGI protocol and written to the
relevant socket. It will then listen on the socket and return any output to the client. In the future it is intended to
support network sockets in addition to Unix domain sockets.
Both CGI and SCGI scripts are responsible for sending their own headers (eg, 20 text/gemini\r\n
).
The following are the main variables passed to applications over CGI/SCGI:
Variable name | Description | Protocol |
---|---|---|
GATEWAY_INTERFACE | The name of the gateway interface used, followed, if applicable, by a "/" and the version number, eg, CGI/1.1 or SCGI . |
CGI/SCGI |
SERVER_NAME | The hostname of the server on which Apogee is running. | CGI/SCGI |
SERVER_PORT | The port on which Apogee is listening. | CGI/SCGI |
SERVER_PROTOCOL | The protocol the server is using - always "GEMINI". | CGI/SCGI |
SERVER_SOFTWARE | The software running the server - always "APOGEE". | CGI/SCGI |
REMOTE_ADDR | The IP address from which the request originated. | CGI/SCGI |
SCRIPT_PATH | The part of the request path corresponding to the relevant script. For CGI, this is the part of the path that corresponds to the CGI script to be run; for SCGI, it corresponds to the prefix specified in the config file. | CGI/SCGI |
PATH_INFO | The part of the request path after SCRIPT_PATH (excluding any query string). | CGI/SCGI |
QUERY_STRING | You guessed it - the query string, if any, specified in the request. | CGI/SCGI |
REQUEST_METHOD | Always an empty string, as Gemini does not support different request methods. | CGI/SCGI |
TLS_CLIENT_HASH | If a client certificate is provided, the SHA256 hash of that certificate. Not present otherwise. | CGI/SCGI |
TLS_CLIENT_ISSUER_DN | If a client certificate is provided, the distinguished name of the certificate issuer. Not present otherwise. | CGI/SCGI |
TLS_CLIENT_SUBJECT_DN | If a client certificate is provided, the distinguished name of the certificate subject. Not present otherwise. | CGI/SCGI |
AUTH_TYPE | If a client certificate is provided, "Certificate". Not present otherwise. | CGI/SCGI |
SCGI | Always "1", as required by the SCGI protocol. | SCGI |
CONTENT_LENGTH | The length of the request body - this will always be 0, as Gemini does not permit request bodies. | SCGI |
You can tell Apogee to redirect certain paths using the TEMP_REDIRECTS
and PERM_REDIRECTS
sections of the config
file. Each entry in these sections should map a regular expression, against which a path will be checked, to the path to
redirect to. Requests matched against entries in the TEMP_REDIRECTS
section will receive a temporary redirect
response (status code 30). Requests matched against entries in the PERM_REDIRECTS
section will receive a permanent
redirect response (status code 31).
Apogee has support for client authentication using client-side SSL certificates. The CLIENT_CERT_ZONES
section should
be a table mapping regular expressions to lists of supported client certificate SHA256 fingerprints. Client
authentication is done immediately after the request is received by the server, so redirections, dynamic content, etc,
will not be handled unless a request is authenticated (if necessary).
Remember that Apogee is still a work in progress and should not be considered to be completely secure. Use at your own risk. It is your responsibility to ensure that your server, and any scripts called by it, are secure. If you do discover any vulnerabilities or other issues with Apogee, please file an issue.