diff options
| author | Martin Fischer <martin@push-f.com> | 2025-12-29 15:22:46 +0100 |
|---|---|---|
| committer | Martin Fischer <martin@push-f.com> | 2025-12-29 19:35:28 +0100 |
| commit | 5d61f19ac636ce99a1291871e4ad3551edcb83df (patch) | |
| tree | 5cb99477ecc64ba1a11730baa80dbefd55008b66 /nixos/hosts | |
| parent | 928cf7b7f979270f270f315e0e2f17702c870d88 (diff) | |
refactor(tente): vendor cgit nixos module
Diffstat (limited to 'nixos/hosts')
| -rw-r--r-- | nixos/hosts/tente/cgit.nix | 287 | ||||
| -rw-r--r-- | nixos/hosts/tente/git-web.nix | 3 |
2 files changed, 290 insertions, 0 deletions
diff --git a/nixos/hosts/tente/cgit.nix b/nixos/hosts/tente/cgit.nix new file mode 100644 index 0000000..70ea678 --- /dev/null +++ b/nixos/hosts/tente/cgit.nix @@ -0,0 +1,287 @@ +# copied from nixpkgs +{ + config, + lib, + pkgs, + ... +}: +let + cfgs = config.services.cgit; + + settingType = + with lib.types; + oneOf [ + bool + int + str + ]; + repeatedSettingType = + with lib.types; + oneOf [ + settingType + (listOf settingType) + ]; + + genAttrs' = names: f: lib.listToAttrs (map f names); + + regexEscape = + let + # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266 + special = [ + "(" + ")" + "[" + "]" + "{" + "}" + "?" + "*" + "+" + "-" + "|" + "^" + "$" + "\\" + "." + "&" + "~" + "#" + " " + "\t" + "\n" + "\r" + "" # \v / 0x0B + "" # \f / 0x0C + ]; + in + lib.replaceStrings special (map (c: "\\${c}") special); + + stripLocation = cfg: lib.removeSuffix "/" cfg.nginx.location; + + regexLocation = cfg: regexEscape (stripLocation cfg); + + mkFastcgiPass = name: cfg: '' + ${ + if cfg.nginx.location == "/" then + '' + fastcgi_param PATH_INFO $uri; + '' + else + '' + fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + '' + }fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address}; + ''; + + cgitrcLine = + name: value: + "${name}=${ + if value == true then + "1" + else if value == false then + "0" + else + toString value + }"; + + # list value as multiple lines (for "readme" for example) + cgitrcEntry = + name: value: if lib.isList value then map (cgitrcLine name) value else [ (cgitrcLine name value) ]; + + mkCgitrc = + cfg: + pkgs.writeText "cgitrc" '' + # global settings + ${lib.concatStringsSep "\n" ( + lib.flatten ( + lib.mapAttrsToList cgitrcEntry ({ virtual-root = cfg.nginx.location; } // cfg.settings) + ) + )} + ${lib.optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)} + + # repository settings + ${lib.concatStrings ( + lib.mapAttrsToList (url: settings: '' + ${cgitrcLine "repo.url" url} + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: cgitrcLine "repo.${name}") settings)} + '') cfg.repos + )} + + # extra config + ${cfg.extraConfig} + ''; + + fcgiwrapUnitName = name: "fcgiwrap-cgit-${name}"; + fcgiwrapRuntimeDir = name: "/run/${fcgiwrapUnitName name}"; + gitProjectRoot = + name: cfg: if cfg.scanPath != null then cfg.scanPath else "${fcgiwrapRuntimeDir name}/repos"; + +in +{ + options = { + services.cgit = lib.mkOption { + description = "Configure cgit instances."; + default = { }; + type = lib.types.attrsOf ( + lib.types.submodule ( + { config, ... }: + { + options = { + enable = lib.mkEnableOption "cgit"; + + package = lib.mkPackageOption pkgs "cgit" { }; + + nginx.virtualHost = lib.mkOption { + description = "VirtualHost to serve cgit on, defaults to the attribute name."; + type = lib.types.str; + default = config._module.args.name; + example = "git.example.com"; + }; + + nginx.location = lib.mkOption { + description = "Location to serve cgit under."; + type = lib.types.str; + default = "/"; + example = "/git/"; + }; + + repos = lib.mkOption { + description = "cgit repository settings, see {manpage}`cgitrc(5)`"; + type = with lib.types; attrsOf (attrsOf settingType); + default = { }; + example = { + blah = { + path = "/var/lib/git/example"; + desc = "An example repository"; + }; + }; + }; + + scanPath = lib.mkOption { + description = "A path which will be scanned for repositories."; + type = lib.types.nullOr lib.types.path; + default = null; + example = "/var/lib/git"; + }; + + settings = lib.mkOption { + description = "cgit configuration, see {manpage}`cgitrc(5)`"; + type = lib.types.attrsOf repeatedSettingType; + default = { }; + example = lib.literalExpression '' + { + enable-follow-links = true; + source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py"; + } + ''; + }; + + extraConfig = lib.mkOption { + description = "These lines go to the end of cgitrc verbatim."; + type = lib.types.lines; + default = ""; + }; + + user = lib.mkOption { + description = "User to run the cgit service as."; + type = lib.types.str; + default = "cgit"; + }; + + group = lib.mkOption { + description = "Group to run the cgit service as."; + type = lib.types.str; + default = "cgit"; + }; + }; + } + ) + ); + }; + }; + + config = lib.mkIf (lib.any (cfg: cfg.enable) (lib.attrValues cfgs)) { + assertions = lib.mapAttrsToList (vhost: cfg: { + assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == { }); + message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set."; + }) cfgs; + + users = lib.mkMerge ( + lib.flip lib.mapAttrsToList cfgs ( + _: cfg: { + users.${cfg.user} = { + isSystemUser = true; + inherit (cfg) group; + }; + groups.${cfg.group} = { }; + } + ) + ); + + services.fcgiwrap.instances = lib.flip lib.mapAttrs' cfgs ( + name: cfg: + lib.nameValuePair "cgit-${name}" { + process = { inherit (cfg) user group; }; + socket = { inherit (config.services.nginx) user group; }; + } + ); + + systemd.services = lib.flip lib.mapAttrs' cfgs ( + name: cfg: + lib.nameValuePair (fcgiwrapUnitName name) ( + lib.mkIf (cfg.repos != { }) { + serviceConfig.RuntimeDirectory = fcgiwrapUnitName name; + preStart = '' + GIT_PROJECT_ROOT=${lib.escapeShellArg (gitProjectRoot name cfg)} + mkdir -p "$GIT_PROJECT_ROOT" + cd "$GIT_PROJECT_ROOT" + ${lib.concatLines ( + lib.flip lib.mapAttrsToList cfg.repos ( + name: repo: '' + ln -s ${lib.escapeShellArg repo.path} ${lib.escapeShellArg name} + '' + ) + )} + ''; + } + ) + ); + + services.nginx.enable = true; + + services.nginx.virtualHosts = lib.mkMerge ( + lib.mapAttrsToList (name: cfg: { + ${cfg.nginx.virtualHost} = { + locations = + (genAttrs' [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ] ( + fileName: + lib.nameValuePair "= ${stripLocation cfg}/${fileName}" { + alias = lib.mkDefault "${cfg.package}/cgit/${fileName}"; + } + )) + // { + "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = { + fastcgiParams = rec { + SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend"; + GIT_HTTP_EXPORT_ALL = "1"; + GIT_PROJECT_ROOT = gitProjectRoot name cfg; + HOME = GIT_PROJECT_ROOT; + }; + extraConfig = mkFastcgiPass name cfg; + }; + "${stripLocation cfg}/" = { + fastcgiParams = { + SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi"; + QUERY_STRING = "$args"; + HTTP_HOST = "$server_name"; + CGIT_CONFIG = mkCgitrc cfg; + }; + extraConfig = mkFastcgiPass name cfg; + }; + }; + }; + }) cfgs + ); + }; +} diff --git a/nixos/hosts/tente/git-web.nix b/nixos/hosts/tente/git-web.nix index 122157a..1c7ccf7 100644 --- a/nixos/hosts/tente/git-web.nix +++ b/nixos/hosts/tente/git-web.nix @@ -20,6 +20,9 @@ in }; }; + disabledModules = ["services/networking/cgit.nix"]; + imports = [./cgit.nix]; + config = { services.nginx.virtualHosts.${cfg.domain} = { enableACME = true; |
