diff options
author | Martin Fischer <martin@push-f.com> | 2025-04-06 09:40:30 +0200 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2025-04-13 23:18:01 +0200 |
commit | 281d0a867bc542bef52d5091fed3bf1b0d3b9ef6 (patch) | |
tree | dff4cb69b1560655257b44190a17f99ed5b5f62b /service.nix | |
parent | 50ea018252ce69542eab6a107b99ea8179810d1e (diff) |
build: introduce Nix package and NixOS service
Diffstat (limited to 'service.nix')
-rw-r--r-- | service.nix | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/service.nix b/service.nix new file mode 100644 index 0000000..6024b5e --- /dev/null +++ b/service.nix @@ -0,0 +1,139 @@ +{ config, lib, pkgs, ... }: + +let + lex_surf = pkgs.callPackage ./default.nix {}; + cfg = config.services.lex-surf; +in +{ + options.services.lex-surf = { + enable = lib.mkEnableOption "lex-surf"; + + domain = lib.mkOption { + type = lib.types.str; + description = "Domain under which lex-surf will be served."; + }; + + enableACME = lib.mkOption { + type = lib.types.bool; + description = "Whether to generate certificates."; + default = false; + }; + + nginx = + let + nginxOpts = + (import <nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix> { + inherit config lib; + }).options; + in + lib.mkOption { + type = lib.types.submodule { + options = lib.removeAttrs nginxOpts ["serverName" "useACMEHost" "acmeRoot"]; + }; + default = {}; + }; + }; + + config = lib.mkIf cfg.enable ( + let + socketPath = "/run/lex-serve/http.sock"; + ccTLDs = lib.splitString "\n" (lib.strings.trim (builtins.readFile ./cc-tlds.txt)); + chunkList = + n: list: + if list == [ ] then [ ] else [ (lib.lists.take n list) ] ++ (chunkList n (lib.lists.drop n list)); + # Let's Encrypt only supports 100 domains per certificate. + # We could also use a wildcard certificate but that requires + # the dns-01 challenge which takes more effort to set up. + ccChunks = chunkList 100 ccTLDs; + in + { + systemd.services.lex-serve = { + serviceConfig = { + ExecStart = "${lex_surf}/bin/lex-serve"; + DynamicUser = true; + RuntimeDirectory = "lex-serve"; + WorkingDirectory = lex_surf; + }; + + environment = { + SOCKET_PATH = socketPath; + DOMAIN = cfg.domain; + }; + wantedBy = ["multi-user.target"]; + }; + + security.acme.certs = lib.mkIf cfg.enableACME ( + builtins.listToAttrs ( + lib.imap0 (i: ccTLDs: { + name = "batch${toString i}.${cfg.domain}"; + value = + let + domains = map (tld: "${tld}.${cfg.domain}") ccTLDs; + in + { + domain = builtins.head domains; + extraDomainNames = builtins.tail domains; + group = config.services.nginx.group; + webroot = "/var/lib/acme/acme-challenge"; + }; + }) ccChunks + ) // { + ${cfg.domain} = { + group = config.services.nginx.group; + webroot = "/var/lib/acme/acme-challenge"; + }; + } + ); + + services.nginx = { + enable = true; + + virtualHosts = + { + ${cfg.domain} = lib.mkMerge [ + cfg.nginx + { + locations."/" = { + proxyPass = "http://unix:${socketPath}"; + recommendedProxySettings = true; + }; + locations."/assets/" = { + root = lex_surf; + tryFiles = "$uri =404"; + }; + useACMEHost = if cfg.enableACME then cfg.domain else null; + } + ]; + } + // (builtins.listToAttrs ( + lib.imap0 (i: ccTLDs: { + name = "host${toString i}.${cfg.domain}"; + value = lib.mkMerge [ + cfg.nginx + { + serverName = + let + escapedDomain = builtins.replaceStrings ["."] ["\\."] cfg.domain; + in + "~^(?<cc>${lib.strings.concatStringsSep "|" ccTLDs})\\.${escapedDomain}$"; + useACMEHost = if cfg.enableACME then "batch${toString i}.${cfg.domain}" else null; + locations."/" = { + proxyPass = "http://unix:${socketPath}"; + recommendedProxySettings = true; + }; + locations."=/laws.json" = { + root = lex_surf; + tryFiles = "/laws/$cc.json =404"; + extraConfig = '' + gzip on; + gzip_types *; + ''; + }; + } + ]; + }) ccChunks + )); + }; + } + ); +} |