summaryrefslogtreecommitdiff
path: root/service.nix
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2025-04-06 09:40:30 +0200
committerMartin Fischer <martin@push-f.com>2025-04-13 23:18:01 +0200
commit281d0a867bc542bef52d5091fed3bf1b0d3b9ef6 (patch)
treedff4cb69b1560655257b44190a17f99ed5b5f62b /service.nix
parent50ea018252ce69542eab6a107b99ea8179810d1e (diff)
build: introduce Nix package and NixOS service
Diffstat (limited to 'service.nix')
-rw-r--r--service.nix139
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
+ ));
+ };
+ }
+ );
+}