{ config, lib, pkgs, ... }: with lib; let cfg = config.informis.cl-gemini; lisp-libs = with pkgs.lispPackages; [ asdf-package-system asdf-system-connections alexandria asdf-package-system asdf-system-connections cl_plus_ssl cl-ppcre quicklisp quri uiop usocket ]; launchServer = ip: port: root: public-dir: key: cert: slynk-port: feeds-string: textfiles-archive: pkgs.writeText "launch-server.lisp" '' (load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))) (ql:quickload :slynk) (ql:quickload :cl-gemini) ${optionalString (slynk-port != null) "(slynk:create-server :port ${toString slynk-port} :dont-close t)"} ${feeds-string} (cl-gemini:start-gemini-server "${ip}" "${key}" "${cert}" :port ${toString port} :document-root "${root}" :textfiles-root "${textfiles-archive}" :file-cmd "${pkgs.file}/bin/file" :log-stream *standard-output* :threaded t :separate-thread t) (loop (sleep 60)) ''; sbcl-with-ssl = pkgs.sbcl.overrideAttrs (oldAttrs: rec { extraLibs = with pkgs; [ openssl_1_1.dev ]; }); feedOpts = with types; { options = { url = mkOption { type = str; description = "Base URI of the feed, i.e. the URI corresponding to the feed path."; example = "gemini://my.server/path/to/feedfiles"; }; title = mkOption { type = str; description = "Title of given feed."; example = "My Fancy Feed"; }; path = mkOption { type = str; description = "Path to Gemini files making up the feed."; example = "/path/to/feed"; }; }; }; register-feed = name: opts: '' (cl-gemini:register-feed :name "${name}" :title "${opts.title}" :path "${opts.path}" :base-uri "${opts.url}")''; register-feeds = feeds: concatStringsSep "\n" (mapAttrsToList register-feed feeds); in { options.informis.cl-gemini = with types; { enable = mkEnableOption "Enable the cl-gemini server."; port = mkOption { type = port; description = "Port on which to serve Gemini traffic."; default = 1965; }; server-ip = mkOption { type = str; description = "IP on which to serve Gemini traffic."; example = "1.2.3.4"; }; document-root = mkOption { type = str; description = "Root at which to look for gemini files."; example = "/my/gemini/root"; }; user-public = mkOption { type = str; description = "Subdirectory of user homes to check for gemini files."; default = "gemini-public"; }; ssl-private-key = mkOption { type = path; description = "Path to the pem-encoded server private key."; example = /path/to/secret/key.pem; }; ssl-certificate = mkOption { type = path; description = "Path to the pem-encoded server public certificate."; example = /path/to/cert.pem; }; slynk-port = mkOption { type = nullOr port; description = "Port on which to open a slynk server, if any."; default = null; }; feeds = mkOption { type = loaOf (submodule feedOpts); description = "Feeds to generate and make available (as eg. /feed/name.xml)."; example = { diary = { title = "My Diary"; path = "/path/to/my/gemfiles/"; url = "gemini://my.host/blog-path/"; }; }; default = {}; }; textfiles-archive = mkOption { type = str; description = "A path containing only gemini & text files."; example = "/path/to/textfiles/"; }; }; config = mkIf cfg.enable { environment.systemPackages = with pkgs; [ cl-gemini ]; users.users = { cl-gemini = { isSystemUser = true; group = "nogroup"; createHome = true; home = "/var/lib/cl-gemini"; }; }; environment.etc = { "cl-gemini/key.pem" = { mode = "0400"; user = "cl-gemini"; source = cfg.ssl-private-key; }; "cl-gemini/cert.pem" = { mode = "0444"; user = "cl-gemini"; source = cfg.ssl-certificate; }; }; systemd.services.cl-gemini = { description = "cl-gemini Gemini server (https://gemini.circumlunar.space/)"; serviceConfig = let feed-registrations = register-feeds cfg.feeds; in { ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; ExecStart = "${sbcl-with-ssl}/bin/sbcl --load ${ launchServer cfg.server-ip cfg.port cfg.document-root cfg.user-public "/etc/cl-gemini/key.pem" "/etc/cl-gemini/cert.pem" cfg.slynk-port feed-registrations cfg.textfiles-archive }"; Restart = "on-failure"; PIDFile = "/run/cl-gemini.$USERNAME.uid"; User = "cl-gemini"; }; environment = { LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; CL_SOURCE_REGISTRY = concatStringsSep ":" (["${config.users.users.cl-gemini.home}/quicklisp/quicklisp"] ++ (map (pkg: "${pkg}//") (lisp-libs ++ [pkgs.cl-gemini]))); }; path = with pkgs; [ gcc file getent ]; wantedBy = [ "default.target" ]; }; }; }