update editorconfig
fix bug
better error printing
A simple FastCGI program that essentially emulates nginx's try_files
directive for Gemini servers
that don't support it.
Note:
as of 2024-04-03,
you will need to clone my updated version of SB-FastCGI or CL-FastCGI
(if you use SBCL or another implementation, respectively) into
~/quicklisp/local-projects/
until my upstream contributions get merged.
SB-FastCGI: https://github.com/nytpu/sb-fastcgi
CL-FastCGI: https://github.com/nytpu/cl-fastcgi
First clone the repo:
git clone https://git.sr.ht/~nytpu/gemini-try-files && cd gemini-try-files
There are several ways to build a standalone executable:
With SBCL (recommended due to its very fast startup time for standalone executables):
make
(or execute util/build.lisp
)
With another Lisp implementation:
make LISP="<lisp implementation>" build
(or execute util/build.lisp
)
With SBCL or another Lisp implementation but with core compression enabled,
if the implementation supports it
(not recommended for plain CGI usage due to the much slower startup time):
same as with core compression,
but use the build-compress
target instead of build
.
Note that after building a standalone executable, you no longer need a Common Lisp implementation installed on the system, as the executable is by definition standalone. If you built the executable on some local system and then transferred it to a server, then you don't ever need to install Lisp on the server!
If you have Lisp installed on the server,
then having a script that loads and executes util/run.lisp
will execute OSB without building a standalone executable,
more like most other scripting languages.
(make run
does this).
Loading from source every time is relatively slow, but building a standalone executable bundles an entire copy of the Lisp implementation. As an intermediate step, you can dump just a Lisp image or build a Fast Load file using these commands:
SBCL> (load "util/load.lisp")
SBCL> (asdf:perform 'asdf:image-op :gemini-try-files) ; dump a Lisp image
SBCL> (asdf:perform 'asdf:monolithic-compile-bundle-op :gemini-try-files) ; build a big FASL
The first command line argument to Gemini-Try-Files should be a path to a configuration file.
If it isn't provided then it will try to read a file named gtf.sexp
in the current directory.
The format of gtf.sexp
is as follows:
The first s-expression is a list of key-value pairs in association list style.
The accepted keys and their values are:
:text-type-params
— the value is a string containing a series of MIME type parameters that will
be appended to the MIME type of any text/*
file with a semicolon.
Defaults to "lang=en-US;charset=utf-8"
:index-name
— a string containing the name of the file to treat as the directory index.
Defaults to "index.gmi"
:error-code
— an integer for the Gemini response code to use if a file is not found after
trying all the possibilities given.
Defaults to 51
:error-meta
— a string (max length 1024 bytes) that will be given as the response meta if a file
is not found after trying all the possibilities given.
Defaults to "Not found"
:libfcgi
— Path to the libfcgi.so
shared library.
Defaults to /usr/lib/libfcgi.so
The remainder of the config file is a series of filesystem directories to search for incoming requests in.
(:directory "<path>")
will prepend the path to the request URI and append the :index-name
to the request
(all three separated by directory separators)
and test if the index exists.
If it exists but the path doesn't end in a /
, then it will issue a redirect to the same URI but
with an appended slash.((:with-ext "<ext>") "<path>")
will search path like a normal string, but will add the given extension to the filename before
testing if it exists.((:index-name "index.gemini")
(:text-type-params "charset=cp437"))
"/var/gemini/example.com/"
(:directory "/var/gemini/example.com/")
((:with-ext "gmi") "/var/gemini/example.com/")
((:with-ext "gemini") "/var/gemini/example.com/")
"/var/gemini/shared-site-resources/"
This example uses index.gemini
instead of index.gmi
as a directory index,
and for all text/*
MIME types appends ;charset=cp437
.
Given an example request for /files/test
it would check the following in sequence,
stopping at the first file that exists and returning its contents:
/var/gemini/example.com/files/test
/var/gemini/example.com/files/test/index.gemini
/var/gemini/example.com/files/test.gmi
/var/gemini/example.com/files/test.gemini
/var/gemini/shared-site-resources/files/test
51 Not found
to the requesterGemini-Try-Files does not sanitize paths before dereferencing them (TODO). Please ensure your Gemini server validates & sanitizes paths before passing them through to the CGI program. If it does not then tree traversal attacks are possible.
The upstream URL of this project is https://git.sr.ht/~nytpu/gemini-try-files. Send suggestions, bugs, patches, and other contributions to ~nytpu/public-inbox@lists.sr.ht or alex@nytpu.com. For help sending a patch through email, see https://git-send-email.io. You can browse the list archives at https://lists.sr.ht/~nytpu/public-inbox.
If you aren't comfortable sending a patch through email, get in touch with a link to your repo and I'll pull the changes down myself!
Copyright (C) 2024 nytpu <alex [at] nytpu.com>.
Licensed under the terms of the GNU Affero General Public License, version 3. You can view a copy of the GNU AGPL in LICENSE or at https://www.gnu.org/licenses/agpl-3.0.html.