{ lib, pkgs, config, ... }: let inherit (builtins) split; inherit (lib) flip foldr groupBy head literalExpression mapAttrs mapAttrs' mapAttrsToList mkOption nameValuePair optionalAttrs types; cfg = config.services.cgit-qyliss; instancesByVhost = groupBy ({ value, ... }: value.vhost) (mapAttrsToList nameValuePair cfg.instances); vhostConfigs = mapAttrs (vhost: instances: foldr (l: r: l // r) {} (map ({ name, value }: let unslashedPath = head (split "/+$" value.path); # We'll be dealing almost exclusively with paths ending in /, # since otherwise Nginx likes to do simple prefix matching. path = "${unslashedPath}/"; in { locations = { ${path} = { alias = "${value.package}/cgit/"; tryFiles = "$uri @${name}-cgit"; }; "@${name}-cgit" = { proxyPass = "http://unix:/run/cgit/${name}.sock"; }; } // optionalAttrs (unslashedPath != "") { ${unslashedPath} = { return = "301 ${path}"; }; }; extraConfig = '' if ($http_user_agent = "my-tiny-bot") { return 429; } if ($http_user_agent = "thesis-research-bot") { return 429; } ''; }) instances) ) instancesByVhost; in { options.services.cgit-qyliss = { instances = mkOption { type = types.attrsOf (types.submodule { options = { vhost = mkOption { type = types.str; example = "spectrum-os.org"; description = "Nginx vhost for the cgit"; }; path = mkOption { type = types.strMatching "/(.*[^/])?"; default = "/"; example = "/git"; description = '' Path to be appended to all cgit URLs. Leading slashes are mandatory; trailing slashes are forbidden. ''; }; package = mkOption { type = types.package; default = pkgs.cgit; defaultText = literalExpression "pkgs.cgit"; description = "cgit package to use"; }; config = mkOption { type = types.package; description = '' Configuration file for cgit. See cgitrc 5. ''; }; }; }); default = {}; description = "List of cgit instances to run"; }; }; config = { services.nginx.virtualHosts = vhostConfigs; systemd.services = flip mapAttrs' cfg.instances (name: instance: { name = "lighttpd-${name}@"; value = { unitConfig.CollectMode = "inactive-or-failed"; serviceConfig.StandardInput = "socket"; serviceConfig.StandardOutput = "socket"; serviceConfig.StandardError = "journal"; serviceConfig.DynamicUser = true; serviceConfig.Type = "oneshot"; serviceConfig.TimeoutSec = "30"; serviceConfig.ExecStart = "${lib.getExe pkgs.lighttpd} -1 -f ${pkgs.writeText "lighttpd-${name}.conf" '' server.modules = ( "mod_alias", "mod_setenv", "mod_cgi" ) server.document-root = "/var/empty" server.errorlog-use-syslog = "enable" alias.url = ( "${if instance.path == "/" then "" else instance.path}" => "${instance.package}/cgit/cgit.cgi" ) cgi.assign = ( "cgit.cgi" => "${instance.package}/cgit/cgit.cgi" ) setenv.add-environment = ( "CGIT_CONFIG" => "${instance.config}" ) ''}"; }; }); systemd.sockets = flip mapAttrs' cfg.instances (name: instance: { name = "lighttpd-${name}"; value = { wantedBy = [ "sockets.target" ]; socketConfig.ListenStream = "/run/cgit/${name}.sock"; socketConfig.Accept = "yes"; }; }); }; }