{ lib, pkgs, config, ... }: let inherit (builtins) split; inherit (lib) flip foldr groupBy head literalExpression mapAttrs mapAttrs' mapAttrsToList mdDoc 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/cgiserver/cgit/${name}.sock"; }; } // optionalAttrs (unslashedPath != "") { ${unslashedPath} = { return = "301 ${path}"; }; }; extraConfig = '' if ($http_user_agent = "my-tiny-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 = mdDoc "Nginx vhost for the cgit"; }; path = mkOption { type = types.strMatching "/(.*[^/])?"; default = "/"; example = "/git"; description = mdDoc '' 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 = mdDoc "cgit package to use"; }; cgiserver = mkOption { type = types.package; default = pkgs.cgiserver; defaultText = literalExpression "pkgs.cgiserver"; description = mdDoc "cgiserver package to use"; }; config = mkOption { type = types.package; description = mdDoc '' Configuration file for cgit. See cgitrc 5. ''; }; }; }); default = {}; description = mdDoc "List of cgit instances to run"; }; }; config = { services.nginx.virtualHosts = vhostConfigs; systemd.services = flip mapAttrs' cfg.instances (name: instance: { name = "cgit-${name}"; value = { environment.CGIT_CONFIG = instance.config; serviceConfig.DynamicUser = true; serviceConfig.ExecStart = "${instance.cgiserver}/bin/cgiserver -r ${instance.path}/ ${instance.package}/cgit/cgit.cgi"; }; }); systemd.sockets = flip mapAttrs' cfg.instances (name: instance: { name = "cgit-${name}"; value = { wantedBy = [ "sockets.target" ]; socketConfig.ListenStream = "/run/cgiserver/cgit/${name}.sock"; }; }); }; }