/'su.la/for the suffix of capsule in Spanish, Cápsula.

A Gemini protocol server written in Scryer Prolog.
sula depends on a patched Scryer Prolog which can be found here (branch js/fixes). The required patches are:
'$copy_stream'/2 builtin used for streaming binary file bodies to TLS clients without materialising the contents on the Prolog heap.library(pio)’s buffer_prepare_for_n/5 so that lazy reads from process pipes (and other streams whose at_end_of_stream/1 never reports true) terminate on EOF instead of spinning.socket_server_accept/4 that checks Scryer’s INTERRUPT flag, so SIGINT becomes a catchable '$interrupt_thrown' exception instead of being trapped behind a blocking syscall.rustls.tls_server_negotiate to include the optional client certificate.Build and install the patched Scryer:
git clone https://git.sagredo.dev/scryer-prolog -b js/fixes
cd scryer-prolog
cargo install --path .
openssl(1) must also be on PATH — it’s invoked at startup to read the CN from the configured identity certificate and verify it matches the configured hostname.
./sula.pl --addr HOST:PORT --hostname NAME --content DIR --certs DIR
sula.pl is a polyglot script: bash detects scryer-prolog on PATH and execs it with sula:run, halt as the entry goal.
Example:
./sula.pl \
--addr 127.0.0.1:1965 \
--hostname gmi.example.dev \
--content ./site \
--certs .
All options accept any order. Anything unrecognised is silently dropped.
| Option | Meaning | Default |
|---|---|---|
--addr HOST:PORT |
Bind address and port for the listening socket | 127.0.0.1:1965 |
--hostname NAME |
Expected CN of the certificate. Startup aborts on mismatch |
localhost |
--content DIR |
Root directory for served files | ./site |
--certs DIR |
Directory containing cert.pem and key.pem |
. |
Ctrl+C triggers a clean shutdown: the listening socket is closed, the top-level catch logs Shutting down, and the process exits 0.
rustls, PKCS#12 identity files.cert_is_for_hostname/2 shells out to openssl x509 and asserts the cert’s CN matches --hostname.mime/2, populated at startup from /etc/mime.types (parsed by a DCG in mime.pl). text/gemini is added for .gmi.format/3; binary responses streamed in native code through copy_stream/2 (file → TLS socket, no Prolog heap traffic).SIGINT via the patched Scryer socket_server_accept/4.sula.pl Polyglot launcher + main sula module (run/0, request loop).
config.pl CLI parsing (DCG) and config accessors (cert/1, addr/1, ...).
cert.pl Certificate loading + hostname-vs-CN check.
mime.pl /etc/mime.types parser (DCG) and mime/2 facts.
request.pl Request line reader.
gemini_uri.pl Gemini URI DCG (gemini://host[:port]/path[?query]).
ip.pl IP address recognition (rejected as Gemini hosts).
response.pl Response status code DCG.
log.pl Tagged log_msg/3.
banner.pl Reads banner.txt and emits it line-by-line via display_banner/1.