feat: add Nex and Gopher listeners

Reuses md2txt's existing nex and text renderers (both already ship
unmodified) -- rendered unwrapped gemtext still works for Gemini/Spartan
clients that wrap themselves, but Nex and Gopher clients don't, so those
two get the classic 80-column pre-wrap instead.

Neither protocol has a redirect status of its own, so Site.resolve_flat()
follows the /foo.md -> /foo canonical bounce and the WML-card-only ->
parent redirect server-side and hands back real content directly, rather
than leaving the two new listeners to invent a redirect convention that
doesn't exist on the wire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
randogoth 2026-09-26 15:13:43 +03:00
parent 12956a5034
commit 0b67069760
8 changed files with 290 additions and 13 deletions

View file

@ -1,5 +1,5 @@
{
description = "Serve a folder of Markdown as gemtext, Spartan, WML, and XHTML-MP";
description = "Serve a folder of Markdown as gemtext, Nex, Gopher, WML, and XHTML-MP";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@ -93,6 +93,16 @@
default = ":8080";
description = "HTTP listen address; empty disables it.";
};
nexAddr = mkOption {
type = types.str;
default = ":1900";
description = "Nex listen address; empty disables it.";
};
gopherAddr = mkOption {
type = types.str;
default = ":70";
description = "Gopher listen address; empty disables it.";
};
};
config = lib.mkIf cfg.enable {
@ -112,12 +122,15 @@
++ lib.optionals (cfg.geminiAddr != "") [ "--gemini" cfg.geminiAddr ]
++ lib.optionals (cfg.spartanAddr != "") [ "--spartan" cfg.spartanAddr ]
++ lib.optionals (cfg.httpAddr != "") [ "--http" cfg.httpAddr ]
++ lib.optionals (cfg.nexAddr != "") [ "--nex" cfg.nexAddr ]
++ lib.optionals (cfg.gopherAddr != "") [ "--gopher" cfg.gopherAddr ]
);
DynamicUser = true;
StateDirectory = "smolweb";
# Spartan's default port (300) and Gemini's TLS handshake
# both need this rather than running the whole service as
# root -- DynamicUser alone can't bind a privileged port.
# Spartan's default port (300), Gopher's default port (70),
# and Gemini's TLS handshake all need this rather than
# running the whole service as root -- DynamicUser alone
# can't bind a privileged port.
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
NoNewPrivileges = true;
ProtectSystem = "strict";