at 23.11-pre 5.9 kB view raw
1{ config, lib, pkgs, ...}: 2 3with lib; 4 5let 6 cfgs = config.services.cgit; 7 8 settingType = with types; oneOf [ bool int str ]; 9 10 genAttrs' = names: f: listToAttrs (map f names); 11 12 regexEscape = 13 let 14 # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266 15 special = [ 16 "(" ")" "[" "]" "{" "}" "?" "*" "+" "-" "|" "^" "$" "\\" "." "&" "~" 17 "#" " " "\t" "\n" "\r" "\v" "\f" 18 ]; 19 in 20 replaceStrings special (map (c: "\\${c}") special); 21 22 stripLocation = cfg: removeSuffix "/" cfg.nginx.location; 23 24 regexLocation = cfg: regexEscape (stripLocation cfg); 25 26 mkFastcgiPass = 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.socketAddress}; 34 ''; 35 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 }"; 44 45 mkCgitrc = cfg: pkgs.writeText "cgitrc" '' 46 # global settings 47 ${concatStringsSep "\n" ( 48 mapAttrsToList 49 cgitrcLine 50 ({ virtual-root = cfg.nginx.location; } // cfg.settings) 51 ) 52 } 53 ${optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)} 54 55 # repository settings 56 ${concatStrings ( 57 mapAttrsToList 58 (url: settings: '' 59 ${cgitrcLine "repo.url" url} 60 ${concatStringsSep "\n" ( 61 mapAttrsToList (name: cgitrcLine "repo.${name}") settings 62 ) 63 } 64 '') 65 cfg.repos 66 ) 67 } 68 69 # extra config 70 ${cfg.extraConfig} 71 ''; 72 73 mkCgitReposDir = cfg: 74 if cfg.scanPath != null then 75 cfg.scanPath 76 else 77 pkgs.runCommand "cgit-repos" { 78 preferLocalBuild = true; 79 allowSubstitutes = false; 80 } '' 81 mkdir -p "$out" 82 ${ 83 concatStrings ( 84 mapAttrsToList 85 (name: value: '' 86 ln -s ${escapeShellArg value.path} "$out"/${escapeShellArg name} 87 '') 88 cfg.repos 89 ) 90 } 91 ''; 92 93in 94{ 95 options = { 96 services.cgit = mkOption { 97 description = mdDoc "Configure cgit instances."; 98 default = {}; 99 type = types.attrsOf (types.submodule ({ config, ... }: { 100 options = { 101 enable = mkEnableOption (mdDoc "cgit"); 102 103 package = mkPackageOptionMD pkgs "cgit" {}; 104 105 nginx.virtualHost = mkOption { 106 description = mdDoc "VirtualHost to serve cgit on, defaults to the attribute name."; 107 type = types.str; 108 default = config._module.args.name; 109 example = "git.example.com"; 110 }; 111 112 nginx.location = mkOption { 113 description = mdDoc "Location to serve cgit under."; 114 type = types.str; 115 default = "/"; 116 example = "/git/"; 117 }; 118 119 repos = mkOption { 120 description = mdDoc "cgit repository settings, see cgitrc(5)"; 121 type = with types; attrsOf (attrsOf settingType); 122 default = {}; 123 example = { 124 blah = { 125 path = "/var/lib/git/example"; 126 desc = "An example repository"; 127 }; 128 }; 129 }; 130 131 scanPath = mkOption { 132 description = mdDoc "A path which will be scanned for repositories."; 133 type = types.nullOr types.path; 134 default = null; 135 example = "/var/lib/git"; 136 }; 137 138 settings = mkOption { 139 description = mdDoc "cgit configuration, see cgitrc(5)"; 140 type = types.attrsOf settingType; 141 default = {}; 142 example = literalExpression '' 143 { 144 enable-follow-links = true; 145 source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py"; 146 } 147 ''; 148 }; 149 150 extraConfig = mkOption { 151 description = mdDoc "These lines go to the end of cgitrc verbatim."; 152 type = types.lines; 153 default = ""; 154 }; 155 }; 156 })); 157 }; 158 }; 159 160 config = mkIf (any (cfg: cfg.enable) (attrValues cfgs)) { 161 assertions = mapAttrsToList (vhost: cfg: { 162 assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == {}); 163 message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set."; 164 }) cfgs; 165 166 services.fcgiwrap.enable = true; 167 168 services.nginx.enable = true; 169 170 services.nginx.virtualHosts = mkMerge (mapAttrsToList (_: cfg: { 171 ${cfg.nginx.virtualHost} = { 172 locations = ( 173 genAttrs' 174 [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ] 175 (name: nameValuePair "= ${stripLocation cfg}/${name}" { 176 extraConfig = '' 177 alias ${cfg.package}/cgit/${name}; 178 ''; 179 }) 180 ) // { 181 "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = { 182 fastcgiParams = rec { 183 SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend"; 184 GIT_HTTP_EXPORT_ALL = "1"; 185 GIT_PROJECT_ROOT = mkCgitReposDir cfg; 186 HOME = GIT_PROJECT_ROOT; 187 }; 188 extraConfig = mkFastcgiPass cfg; 189 }; 190 "${stripLocation cfg}/" = { 191 fastcgiParams = { 192 SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi"; 193 QUERY_STRING = "$args"; 194 HTTP_HOST = "$server_name"; 195 CGIT_CONFIG = mkCgitrc cfg; 196 }; 197 extraConfig = mkFastcgiPass cfg; 198 }; 199 }; 200 }; 201 }) cfgs); 202 }; 203}