normcap: fix on GNOME wayland when used via keybind or alt-f2 (#351763)
[NixPkgs.git] / nixos / modules / services / networking / cgit.nix
blob126070aa26cc485b9a4d677c68166250a66ff006
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   cfgs = config.services.cgit;
10   settingType =
11     with lib.types;
12     oneOf [
13       bool
14       int
15       str
16     ];
17   repeatedSettingType =
18     with lib.types;
19     oneOf [
20       settingType
21       (listOf settingType)
22     ];
24   genAttrs' = names: f: lib.listToAttrs (map f names);
26   regexEscape =
27     let
28       # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266
29       special = [
30         "("
31         ")"
32         "["
33         "]"
34         "{"
35         "}"
36         "?"
37         "*"
38         "+"
39         "-"
40         "|"
41         "^"
42         "$"
43         "\\"
44         "."
45         "&"
46         "~"
47         "#"
48         " "
49         "\t"
50         "\n"
51         "\r"
52         "\v" # \v / 0x0B
53         "\f" # \f / 0x0C
54       ];
55     in
56     lib.replaceStrings special (map (c: "\\${c}") special);
58   stripLocation = cfg: lib.removeSuffix "/" cfg.nginx.location;
60   regexLocation = cfg: regexEscape (stripLocation cfg);
62   mkFastcgiPass = name: cfg: ''
63     ${
64       if cfg.nginx.location == "/" then
65         ''
66           fastcgi_param PATH_INFO $uri;
67         ''
68       else
69         ''
70           fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
71           fastcgi_param PATH_INFO $fastcgi_path_info;
72         ''
73     }fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address};
74   '';
76   cgitrcLine =
77     name: value:
78     "${name}=${
79       if value == true then
80         "1"
81       else if value == false then
82         "0"
83       else
84         toString value
85     }";
87   # list value as multiple lines (for "readme" for example)
88   cgitrcEntry =
89     name: value: if lib.isList value then map (cgitrcLine name) value else [ (cgitrcLine name value) ];
91   mkCgitrc =
92     cfg:
93     pkgs.writeText "cgitrc" ''
94       # global settings
95       ${lib.concatStringsSep "\n" (
96         lib.flatten (
97           lib.mapAttrsToList cgitrcEntry ({ virtual-root = cfg.nginx.location; } // cfg.settings)
98         )
99       )}
100       ${lib.optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)}
102       # repository settings
103       ${lib.concatStrings (
104         lib.mapAttrsToList (url: settings: ''
105           ${cgitrcLine "repo.url" url}
106           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: cgitrcLine "repo.${name}") settings)}
107         '') cfg.repos
108       )}
110       # extra config
111       ${cfg.extraConfig}
112     '';
114   fcgiwrapUnitName = name: "fcgiwrap-cgit-${name}";
115   fcgiwrapRuntimeDir = name: "/run/${fcgiwrapUnitName name}";
116   gitProjectRoot =
117     name: cfg: if cfg.scanPath != null then cfg.scanPath else "${fcgiwrapRuntimeDir name}/repos";
121   options = {
122     services.cgit = lib.mkOption {
123       description = "Configure cgit instances.";
124       default = { };
125       type = lib.types.attrsOf (
126         lib.types.submodule (
127           { config, ... }:
128           {
129             options = {
130               enable = lib.mkEnableOption "cgit";
132               package = lib.mkPackageOption pkgs "cgit" { };
134               nginx.virtualHost = lib.mkOption {
135                 description = "VirtualHost to serve cgit on, defaults to the attribute name.";
136                 type = lib.types.str;
137                 default = config._module.args.name;
138                 example = "git.example.com";
139               };
141               nginx.location = lib.mkOption {
142                 description = "Location to serve cgit under.";
143                 type = lib.types.str;
144                 default = "/";
145                 example = "/git/";
146               };
148               repos = lib.mkOption {
149                 description = "cgit repository settings, see cgitrc(5)";
150                 type = with lib.types; attrsOf (attrsOf settingType);
151                 default = { };
152                 example = {
153                   blah = {
154                     path = "/var/lib/git/example";
155                     desc = "An example repository";
156                   };
157                 };
158               };
160               scanPath = lib.mkOption {
161                 description = "A path which will be scanned for repositories.";
162                 type = lib.types.nullOr lib.types.path;
163                 default = null;
164                 example = "/var/lib/git";
165               };
167               settings = lib.mkOption {
168                 description = "cgit configuration, see cgitrc(5)";
169                 type = lib.types.attrsOf repeatedSettingType;
170                 default = { };
171                 example = lib.literalExpression ''
172                   {
173                     enable-follow-links = true;
174                     source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
175                   }
176                 '';
177               };
179               extraConfig = lib.mkOption {
180                 description = "These lines go to the end of cgitrc verbatim.";
181                 type = lib.types.lines;
182                 default = "";
183               };
185               user = lib.mkOption {
186                 description = "User to run the cgit service as.";
187                 type = lib.types.str;
188                 default = "cgit";
189               };
191               group = lib.mkOption {
192                 description = "Group to run the cgit service as.";
193                 type = lib.types.str;
194                 default = "cgit";
195               };
196             };
197           }
198         )
199       );
200     };
201   };
203   config = lib.mkIf (lib.any (cfg: cfg.enable) (lib.attrValues cfgs)) {
204     assertions = lib.mapAttrsToList (vhost: cfg: {
205       assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == { });
206       message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set.";
207     }) cfgs;
209     users = lib.mkMerge (
210       lib.flip lib.mapAttrsToList cfgs (
211         _: cfg: {
212           users.${cfg.user} = {
213             isSystemUser = true;
214             inherit (cfg) group;
215           };
216           groups.${cfg.group} = { };
217         }
218       )
219     );
221     services.fcgiwrap.instances = lib.flip lib.mapAttrs' cfgs (
222       name: cfg:
223       lib.nameValuePair "cgit-${name}" {
224         process = { inherit (cfg) user group; };
225         socket = { inherit (config.services.nginx) user group; };
226       }
227     );
229     systemd.services = lib.flip lib.mapAttrs' cfgs (
230       name: cfg:
231       lib.nameValuePair (fcgiwrapUnitName name) (
232         lib.mkIf (cfg.repos != { }) {
233           serviceConfig.RuntimeDirectory = fcgiwrapUnitName name;
234           preStart = ''
235             GIT_PROJECT_ROOT=${lib.escapeShellArg (gitProjectRoot name cfg)}
236             mkdir -p "$GIT_PROJECT_ROOT"
237             cd "$GIT_PROJECT_ROOT"
238             ${lib.concatLines (
239               lib.flip lib.mapAttrsToList cfg.repos (
240                 name: repo: ''
241                   ln -s ${lib.escapeShellArg repo.path} ${lib.escapeShellArg name}
242                 ''
243               )
244             )}
245           '';
246         }
247       )
248     );
250     services.nginx.enable = true;
252     services.nginx.virtualHosts = lib.mkMerge (
253       lib.mapAttrsToList (name: cfg: {
254         ${cfg.nginx.virtualHost} = {
255           locations =
256             (genAttrs' [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ] (
257               fileName:
258               lib.nameValuePair "= ${stripLocation cfg}/${fileName}" {
259                 extraConfig = ''
260                   alias ${cfg.package}/cgit/${fileName};
261                 '';
262               }
263             ))
264             // {
265               "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = {
266                 fastcgiParams = rec {
267                   SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend";
268                   GIT_HTTP_EXPORT_ALL = "1";
269                   GIT_PROJECT_ROOT = gitProjectRoot name cfg;
270                   HOME = GIT_PROJECT_ROOT;
271                 };
272                 extraConfig = mkFastcgiPass name cfg;
273               };
274               "${stripLocation cfg}/" = {
275                 fastcgiParams = {
276                   SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi";
277                   QUERY_STRING = "$args";
278                   HTTP_HOST = "$server_name";
279                   CGIT_CONFIG = mkCgitrc cfg;
280                 };
281                 extraConfig = mkFastcgiPass name cfg;
282               };
283             };
284         };
285       }) cfgs
286     );
287   };