Sula: A Gemini protocol server written in Scryer Prolog

Aug 06, 2026 01:52 AM - 2 hours ago 1

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

sula logo

Git

A Gemini protocol server written in Scryer Prolog.

Requirements

sula depends connected a patched Scryer Prolog which tin beryllium found here (branch js/fixes). The required patches are:

  • A autochthonal '$copy_stream'/2 builtin utilized for streaming binary record bodies to TLS clients without materialising the contents connected the Prolog heap.
  • A hole to library(pio)’s buffer_prepare_for_n/5 truthful that lazy sounds from process pipes (and different streams whose at_end_of_stream/1 ne'er reports true) terminate connected EOF alternatively of spinning.
  • A non-blocking canvass loop successful socket_server_accept/4 that checks Scryer’s INTERRUPT flag, truthful SIGINT becomes a catchable '$interrupt_thrown' exception alternatively of being trapped down a blocking syscall.
  • A larboard to rustls.
  • A modification of tls_server_negotiate to see the optional client certificate.

Build and instal the patched Scryer:

git clone https://git.sagredo.dev/scryer-prolog -b js/fixes cd scryer-prolog cargo instal --path .

openssl(1) must besides beryllium connected PATH — it’s invoked astatine startup to publication the CN from the configured personality certificate and verify it matches the configured hostname.

Running

./sula.pl --addr HOST:PORT --hostname NAME --content DIR --certs DIR

sula.pl is simply a polyglot script: bash detects scryer-prolog connected PATH and execs it pinch sula:run, halt arsenic the introduction goal.

Example:

./sula.pl \ --addr 127.0.0.1:1965 \ --hostname gmi.example.dev \ --content ./site \ --certs .

CLI options

All options judge immoderate order. Anything unrecognised is silently dropped.

OptionMeaningDefault
--addr HOST:PORTBind reside and larboard for the listening socket127.0.0.1:1965
--hostname NAMEExpected CN of the certificate. Startup aborts connected mismatchlocalhost
--content DIRRoot directory for served files./site
--certs DIRDirectory containing cert.pem and key.pem.

Stopping the server

Ctrl+C triggers a cleanable shutdown: the listening socket is closed, the top-level drawback logs Shutting down, and the process exits 0.

Features

  • TLS via rustls, PKCS#12 personality files.
  • Hostname verification: astatine startup, cert_is_for_hostname/2 shells retired to openssl x509 and asserts the cert’s CN matches --hostname.
  • Content speech by hold via mime/2, populated astatine startup from /etc/mime.types (parsed by a DCG successful mime.pl). text/gemini is added for .gmi.
  • Text responses sent via format/3; binary responses streamed successful native code done copy_stream/2 (file → TLS socket, nary Prolog heap traffic).
  • Per-connection correction handling: TLS handshake failures and mid-stream client disconnects are logged and the loop continues. Other errors re-throw and aboveground astatine the apical level.
  • Graceful shutdown connected SIGINT via the patched Scryer socket_server_accept/4.

Layout

sula.pl Polyglot launcher + main sula module (run/0, petition 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 statement reader. gemini_uri.pl Gemini URI DCG (gemini://host[:port]/path[?query]). ip.pl IP reside nickname (rejected arsenic Gemini hosts). response.pl Response position codification DCG. log.pl Tagged log_msg/3. banner.pl Reads banner.txt and emits it line-by-line via display_banner/1.

Planned features

  • Use cardinal and cert alternatively of identity.p12
  • Client certificates
  • Load configuration from a configuration file
  • Save and load users
  • Run CGI scripts
  • All position codes
  • Rate limiting
  • Virtual hosting
  • File logging
  • Multi-threading aliases benignant of?
  • Hot reload?
More