vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / cgit.nix
blob208979364684d7de52323de5f879efe053309e7f
1 { config, lib, pkgs, ...}:
2 let
3   cfgs = config.services.cgit;
5   settingType = with lib.types; oneOf [ bool int str ];
6   repeatedSettingType = with lib.types; oneOf [ settingType (listOf settingType) ];
8   genAttrs' = names: f: lib.listToAttrs (map f names);
10   regexEscape =
11     let
12       # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266
13       special = [
14         "(" ")" "[" "]" "{" "}" "?" "*" "+" "-" "|" "^" "$" "\\" "." "&" "~"
15         "#" " " "\t" "\n" "\r"
16         "\v" # \v / 0x0B
17         "\f" # \f / 0x0C
18       ];
19     in
20       lib.replaceStrings special (map (c: "\\${c}") special);
22   stripLocation = cfg: lib.removeSuffix "/" cfg.nginx.location;
24   regexLocation = cfg: regexEscape (stripLocation cfg);
26   mkFastcgiPass = name: cfg: ''
27     ${if cfg.nginx.location == "/" then ''
28       fastcgi_param PATH_INFO $uri;
29     '' else ''
30       fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
31       fastcgi_param PATH_INFO $fastcgi_path_info;
32     ''
33     }fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address};
34   '';
36   cgitrcLine = name: value: "${name}=${
37     if value == true then
38       "1"
39     else if value == false then
40       "0"
41     else
42       toString value
43   }";
45   # list value as multiple lines (for "readme" for example)
46   cgitrcEntry = name: value:
47     if lib.isList value then
48       map (cgitrcLine name) value
49     else
50       [ (cgitrcLine name value) ];
52   mkCgitrc = cfg: pkgs.writeText "cgitrc" ''
53     # global settings
54     ${lib.concatStringsSep "\n" (
55         lib.flatten (lib.mapAttrsToList
56           cgitrcEntry
57           ({ virtual-root = cfg.nginx.location; } // cfg.settings)
58         )
59       )
60     }
61     ${lib.optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)}
63     # repository settings
64     ${lib.concatStrings (
65         lib.mapAttrsToList
66           (url: settings: ''
67             ${cgitrcLine "repo.url" url}
68             ${lib.concatStringsSep "\n" (
69                 lib.mapAttrsToList (name: cgitrcLine "repo.${name}") settings
70               )
71             }
72           '')
73           cfg.repos
74       )
75     }
77     # extra config
78     ${cfg.extraConfig}
79   '';
81   fcgiwrapUnitName = name: "fcgiwrap-cgit-${name}";
82   fcgiwrapRuntimeDir = name: "/run/${fcgiwrapUnitName name}";
83   gitProjectRoot = name: cfg: if cfg.scanPath != null
84     then cfg.scanPath
85     else "${fcgiwrapRuntimeDir name}/repos";
89   options = {
90     services.cgit = lib.mkOption {
91       description = "Configure cgit instances.";
92       default = {};
93       type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
94         options = {
95           enable = lib.mkEnableOption "cgit";
97           package = lib.mkPackageOption pkgs "cgit" {};
99           nginx.virtualHost = lib.mkOption {
100             description = "VirtualHost to serve cgit on, defaults to the attribute name.";
101             type = lib.types.str;
102             default = config._module.args.name;
103             example = "git.example.com";
104           };
106           nginx.location = lib.mkOption {
107             description = "Location to serve cgit under.";
108             type = lib.types.str;
109             default = "/";
110             example = "/git/";
111           };
113           repos = lib.mkOption {
114             description = "cgit repository settings, see cgitrc(5)";
115             type = with lib.types; attrsOf (attrsOf settingType);
116             default = {};
117             example = {
118               blah = {
119                 path = "/var/lib/git/example";
120                 desc = "An example repository";
121               };
122             };
123           };
125           scanPath = lib.mkOption {
126             description = "A path which will be scanned for repositories.";
127             type = lib.types.nullOr lib.types.path;
128             default = null;
129             example = "/var/lib/git";
130           };
132           settings = lib.mkOption {
133             description = "cgit configuration, see cgitrc(5)";
134             type = lib.types.attrsOf repeatedSettingType;
135             default = {};
136             example = lib.literalExpression ''
137               {
138                 enable-follow-links = true;
139                 source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
140               }
141             '';
142           };
144           extraConfig = lib.mkOption {
145             description = "These lines go to the end of cgitrc verbatim.";
146             type = lib.types.lines;
147             default = "";
148           };
150           user = lib.mkOption {
151             description = "User to run the cgit service as.";
152             type = lib.types.str;
153             default = "cgit";
154           };
156           group = lib.mkOption {
157             description = "Group to run the cgit service as.";
158             type = lib.types.str;
159             default = "cgit";
160           };
161         };
162       }));
163     };
164   };
166   config = lib.mkIf (lib.any (cfg: cfg.enable) (lib.attrValues cfgs)) {
167     assertions = lib.mapAttrsToList (vhost: cfg: {
168       assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == {});
169       message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set.";
170     }) cfgs;
172     users = lib.mkMerge (lib.flip lib.mapAttrsToList cfgs (_: cfg: {
173       users.${cfg.user} = {
174         isSystemUser = true;
175         inherit (cfg) group;
176       };
177       groups.${cfg.group} = { };
178     }));
180     services.fcgiwrap.instances = lib.flip lib.mapAttrs' cfgs (name: cfg:
181       lib.nameValuePair "cgit-${name}" {
182         process = { inherit (cfg) user group; };
183         socket = { inherit (config.services.nginx) user group; };
184       }
185     );
187     systemd.services = lib.flip lib.mapAttrs' cfgs (name: cfg:
188       lib.nameValuePair (fcgiwrapUnitName name)
189       (lib.mkIf (cfg.repos != { }) {
190         serviceConfig.RuntimeDirectory = fcgiwrapUnitName name;
191         preStart = ''
192           GIT_PROJECT_ROOT=${lib.escapeShellArg (gitProjectRoot name cfg)}
193           mkdir -p "$GIT_PROJECT_ROOT"
194           cd "$GIT_PROJECT_ROOT"
195           ${lib.concatLines (lib.flip lib.mapAttrsToList cfg.repos (name: repo: ''
196             ln -s ${lib.escapeShellArg repo.path} ${lib.escapeShellArg name}
197           ''))}
198         '';
199       }
200     ));
202     services.nginx.enable = true;
204     services.nginx.virtualHosts = lib.mkMerge (lib.mapAttrsToList (name: cfg: {
205       ${cfg.nginx.virtualHost} = {
206         locations = (
207           genAttrs'
208             [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ]
209             (fileName: lib.nameValuePair "= ${stripLocation cfg}/${fileName}" {
210               extraConfig = ''
211                 alias ${cfg.package}/cgit/${fileName};
212               '';
213             })
214         ) // {
215           "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = {
216             fastcgiParams = rec {
217               SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend";
218               GIT_HTTP_EXPORT_ALL = "1";
219               GIT_PROJECT_ROOT = gitProjectRoot name cfg;
220               HOME = GIT_PROJECT_ROOT;
221             };
222             extraConfig = mkFastcgiPass name cfg;
223           };
224           "${stripLocation cfg}/" = {
225             fastcgiParams = {
226               SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi";
227               QUERY_STRING = "$args";
228               HTTP_HOST = "$server_name";
229               CGIT_CONFIG = mkCgitrc cfg;
230             };
231             extraConfig = mkFastcgiPass name cfg;
232           };
233         };
234       };
235     }) cfgs);
236   };