Create cl-gemini flake
This commit is contained in:
commit
4670b8546e
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
description = "Common Lisp Gemini server.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
lisp-repo.url = "git+https://fudo.dev/public/lisp-repository.git";
|
||||
cl-gemini = {
|
||||
url = "git+https://fudo.dev/informis/cl-gemini.git";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, lisp-repo, cl-gemini, utils, ... }:
|
||||
utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
lispPackages = pkgs.lispPackages // lisp-repo.packages."${system}";
|
||||
in {
|
||||
packages = rec {
|
||||
cl-gemini = pkgs.callPackage ./package.nix { inherit lispPackages; };
|
||||
cl-gemini-launcher =
|
||||
pkgs.callPackage ./launcher.nix { inherit lispPackages cl-gemini; };
|
||||
default = cl-gemini;
|
||||
};
|
||||
}) // {
|
||||
nixosModules = rec {
|
||||
cl-gemini = import ./module.nix;
|
||||
default = cl-gemini;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
{ lib, lispPackages, openssl_1_1, sbcl, writeShellApplication, cl-gemini, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
serverLauncher = pkgs.writeText "launch-cl-gemini.lisp" ''
|
||||
(defun getenv-or-fail (env-var &optional default)
|
||||
(let ((value (uiop:getenv env-var)))
|
||||
(if (null value)
|
||||
(if default
|
||||
default
|
||||
(uiop:die 1 "unable to find required env var: ~A" env-var))
|
||||
value)))
|
||||
|
||||
(require :asdf)
|
||||
(asdf:load-system :slynk)
|
||||
(asdf:load-system :cl-gemini)
|
||||
(let ((slynk-port (uiop:getenvp "GEMINI_SLYNK_PORT")))
|
||||
(when slynk-port
|
||||
(slynk:create-server :port (parse-integer slynk-port) :dont-close t)))
|
||||
(let ((feed-file (uiop:getenvp "GEMINI_FEEDS")))
|
||||
(when feed-file
|
||||
(load feed-file)))
|
||||
(cl-gemini:start-gemini-server
|
||||
(getenv-or-fail "GEMINI_LISTEN_IP")
|
||||
(getenv-or-fail "GEMINI_PRIVATE_KEY")
|
||||
(getenv-or-fail "GEMINI_CERTIFICATE")
|
||||
:port (parse-integer (getenv-or-fail "GEMINI_LISTEN_PORT"))
|
||||
:document-root (getenv-or-fail "GEMINI_DOCUMENT_ROOT")
|
||||
:textfiles-root (getenv-or-fail "GEMINI_TEXTFILES_ROOT")
|
||||
:log-stream *standard-output*
|
||||
:threaded t
|
||||
:separate-thread t)
|
||||
(loop (sleep 10))
|
||||
'';
|
||||
|
||||
sbcl-with-ssl = sbcl.overrideAttrs (oldAttrs: rec {
|
||||
propagatedBuildInputs = oldAttrs.buildInputs ++ [ openssl_1_1.dev ];
|
||||
});
|
||||
|
||||
in writeShellApplication {
|
||||
name = "cl-gemini-launcher";
|
||||
|
||||
runtimeInputs = [ asdf sbcl-with-ssl openssl_1_1 cl-gemini ];
|
||||
|
||||
text =
|
||||
"${lispPackages.clwrapper}/bin/common-lisp.sh --load ${server-launcher}";
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
{ lisp-repo }:
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.informis.cl-gemini;
|
||||
|
||||
inherit (lisp-repo.lib) lispSourceRegistry;
|
||||
|
||||
feedOpts = { ... }: {
|
||||
options = with types; {
|
||||
url = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Base URL of the feed, ie. the URL corresponding to the feed path.";
|
||||
example = "gemini://my.server/path/to/feed-files";
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
generate-feeds = feeds:
|
||||
let
|
||||
feed-strings = mapAttrsToList (feed-name: opts:
|
||||
''
|
||||
(cl-gemini:register-feed :name "${feed-name}" :title "${opts.title}" :path "${opts.path}" :base-uri "${opts.url}")'')
|
||||
feeds;
|
||||
in pkgs.writeText "gemini-local-feeds.lisp"
|
||||
(concatStringsSep "\n" feed-strings);
|
||||
|
||||
in {
|
||||
options.services.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;
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Hostname at which the server is available (for generating the SSL certificate).";
|
||||
example = "my.hostname.com";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to run the cl-gemini server.";
|
||||
default = "cl-gemini";
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
|
||||
slynk-port = mkOption {
|
||||
type = nullOr port;
|
||||
description = "Port on which to open a slynk server, if any.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
feeds = mkOption {
|
||||
type = attrsOf (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 {
|
||||
networking.firewall.allowedTCPPorts = [ cfg.port ];
|
||||
|
||||
systemd.services.cl-gemini = {
|
||||
description =
|
||||
"cl-gemini Gemini server (https://gemini.curcumlunar.space/).";
|
||||
|
||||
path = [ cl-gemini-launcher ];
|
||||
|
||||
serviceConfig = let
|
||||
genKeyCommand = { hostname, key, certs, ... }:
|
||||
concatStringsSep " " [
|
||||
"${pkgs.openssl_1_1}/bin/openssl req -new"
|
||||
''-subj "/CN=.${hostname}"''
|
||||
''-addext "subjectAltName = DNS:${hostname}, DNS:.${hostname}"''
|
||||
"-x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1"
|
||||
"-days 3650"
|
||||
"-nodes"
|
||||
"-out ${cert}"
|
||||
"-keyout ${key}"
|
||||
];
|
||||
|
||||
genKey = { key, cert, ... }@opts:
|
||||
pkgs.writeShellScript "cl-gemini-generate-key.sh" ''
|
||||
if [[ ! -f ${key} ]]; then
|
||||
${genKeyCommand opts}
|
||||
chown 0400 ${key}
|
||||
chown 0400 ${cert}
|
||||
else
|
||||
echo "ssl key exists, skipping generation"
|
||||
fi
|
||||
'';
|
||||
|
||||
in {
|
||||
ExecStart = "cl-gemini-launcher";
|
||||
ExecStartPre = genKey {
|
||||
inherit (cfg) hostname;
|
||||
key = "$RUNTIME_DIRECTORY/key.pem";
|
||||
cert = "$RUNTIME_DIRECTORY/cert.pem";
|
||||
};
|
||||
Restart = "on-failure";
|
||||
DynamicUser = true;
|
||||
RuntimeDirectory = "cl-gemini";
|
||||
LoadCredential = [
|
||||
"key.pem:${cfg.ssl-private-key}"
|
||||
"cert.pem:${cfg.ssl-certificate}"
|
||||
];
|
||||
};
|
||||
|
||||
environment = {
|
||||
GEMINI_SLYNK_PART =
|
||||
mkIf (cfg.slynk-port != null) (toString cfg.slynk-port);
|
||||
GEMINI_LISTEN_IP = cfg.server-ip;
|
||||
GEMINI_PRIVATE_KEY = "$RUNTIME_DIRECTORY/key.pem";
|
||||
GEMINI_CERTIFICATE = "$RUNTIME_DIRECTORY/cert.pem";
|
||||
GEMINI_LISTEN_PORT = toString cfg.port;
|
||||
GEMINI_DOCUMENT_ROOT = cfg.document-root;
|
||||
GEMINI_TEXTFILES_ROOT = textfiles-archive;
|
||||
GEMINI_FEEDS = "${generate-feeds cfg.feeds}";
|
||||
|
||||
CL_SOURCE_REGISTRY = lispSourceRegistry cl-gemini;
|
||||
};
|
||||
|
||||
path = [ gcc file getent ];
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{ inputs, lispPackages, buildLispPackage, ... }:
|
||||
|
||||
buildLispPackage {
|
||||
baseName = "cl-gemini";
|
||||
packageName = "cl-gemini";
|
||||
description = "Gemini server written in Common Lisp.";
|
||||
|
||||
buildSystems = [ "cl-gemini" ];
|
||||
|
||||
src = inputs.cl-gemini;
|
||||
|
||||
deps = with lispPackages; [
|
||||
alexandria
|
||||
arrows
|
||||
asdf-package-system
|
||||
asdf-system-connections
|
||||
cl_plus_ssl
|
||||
cl-ppcre
|
||||
fare-mop
|
||||
file-types
|
||||
inferior-shell
|
||||
local-time
|
||||
osicat
|
||||
quicklisp
|
||||
quri
|
||||
slynk
|
||||
slynk-macrostep
|
||||
slynk-stepper
|
||||
uiop
|
||||
usocket-server
|
||||
xml-emitter
|
||||
];
|
||||
|
||||
asdFilesToKeep = [ "cl-gemini.asd" ];
|
||||
}
|
Loading…
Reference in New Issue