1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.gitolite;
7 # Use writeTextDir to not leak Nix store hash into file name
8 pubkeyFile = (pkgs.writeTextDir "gitolite-admin.pub" cfg.adminPubkey) + "/gitolite-admin.pub";
9 hooks = lib.concatMapStrings (hook: "${hook} ") cfg.commonHooks;
10in
11{
12 options = {
13 services.gitolite = {
14 enable = mkOption {
15 type = types.bool;
16 default = false;
17 description = ''
18 Enable gitolite management under the
19 <literal>gitolite</literal> user. After
20 switching to a configuration with Gitolite enabled, you can
21 then run <literal>git clone
22 gitolite@host:gitolite-admin.git</literal> to manage it further.
23 '';
24 };
25
26 dataDir = mkOption {
27 type = types.str;
28 default = "/var/lib/gitolite";
29 description = ''
30 The gitolite home directory used to store all repositories. If left as the default value
31 this directory will automatically be created before the gitolite server starts, otherwise
32 the sysadmin is responsible for ensuring the directory exists with appropriate ownership
33 and permissions.
34 '';
35 };
36
37 adminPubkey = mkOption {
38 type = types.str;
39 description = ''
40 Initial administrative public key for Gitolite. This should
41 be an SSH Public Key. Note that this key will only be used
42 once, upon the first initialization of the Gitolite user.
43 The key string cannot have any line breaks in it.
44 '';
45 };
46
47 enableGitAnnex = mkOption {
48 type = types.bool;
49 default = false;
50 description = ''
51 Enable git-annex support. Uses the <literal>extraGitoliteRc</literal> option
52 to apply the necessary configuration.
53 '';
54 };
55
56 commonHooks = mkOption {
57 type = types.listOf types.path;
58 default = [];
59 description = ''
60 A list of custom git hooks that get copied to <literal>~/.gitolite/hooks/common</literal>.
61 '';
62 };
63
64 extraGitoliteRc = mkOption {
65 type = types.lines;
66 default = "";
67 example = literalExpression ''
68 '''
69 $RC{UMASK} = 0027;
70 $RC{SITE_INFO} = 'This is our private repository host';
71 push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature
72 @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
73 '''
74 '';
75 description = ''
76 Extra configuration to append to the default <literal>~/.gitolite.rc</literal>.
77
78 This should be Perl code that modifies the <literal>%RC</literal>
79 configuration variable. The default <literal>~/.gitolite.rc</literal>
80 content is generated by invoking <literal>gitolite print-default-rc</literal>,
81 and extra configuration from this option is appended to it. The result
82 is placed to Nix store, and the <literal>~/.gitolite.rc</literal> file
83 becomes a symlink to it.
84
85 If you already have a customized (or otherwise changed)
86 <literal>~/.gitolite.rc</literal> file, NixOS will refuse to replace
87 it with a symlink, and the `gitolite-init` initialization service
88 will fail. In this situation, in order to use this option, you
89 will need to take any customizations you may have in
90 <literal>~/.gitolite.rc</literal>, convert them to appropriate Perl
91 statements, add them to this option, and remove the file.
92
93 See also the <literal>enableGitAnnex</literal> option.
94 '';
95 };
96
97 user = mkOption {
98 type = types.str;
99 default = "gitolite";
100 description = ''
101 Gitolite user account. This is the username of the gitolite endpoint.
102 '';
103 };
104
105 group = mkOption {
106 type = types.str;
107 default = "gitolite";
108 description = ''
109 Primary group of the Gitolite user account.
110 '';
111 };
112 };
113 };
114
115 config = mkIf cfg.enable (
116 let
117 manageGitoliteRc = cfg.extraGitoliteRc != "";
118 rcDir = pkgs.runCommand "gitolite-rc" { preferLocalBuild = true; } rcDirScript;
119 rcDirScript =
120 ''
121 mkdir "$out"
122 export HOME=temp-home
123 mkdir -p "$HOME/.gitolite/logs" # gitolite can't run without it
124 '${pkgs.gitolite}'/bin/gitolite print-default-rc >>"$out/gitolite.rc.default"
125 cat <<END >>"$out/gitolite.rc"
126 # This file is managed by NixOS.
127 # Use services.gitolite options to control it.
128
129 END
130 cat "$out/gitolite.rc.default" >>"$out/gitolite.rc"
131 '' +
132 optionalString (cfg.extraGitoliteRc != "") ''
133 echo -n ${escapeShellArg ''
134
135 # Added by NixOS:
136 ${removeSuffix "\n" cfg.extraGitoliteRc}
137
138 # per perl rules, this should be the last line in such a file:
139 1;
140 ''} >>"$out/gitolite.rc"
141 '';
142 in {
143 services.gitolite.extraGitoliteRc = optionalString cfg.enableGitAnnex ''
144 # Enable git-annex support:
145 push( @{$RC{ENABLE}}, 'git-annex-shell ua');
146 '';
147
148 users.users.${cfg.user} = {
149 description = "Gitolite user";
150 home = cfg.dataDir;
151 uid = config.ids.uids.gitolite;
152 group = cfg.group;
153 useDefaultShell = true;
154 };
155 users.groups.${cfg.group}.gid = config.ids.gids.gitolite;
156
157 systemd.services.gitolite-init = {
158 description = "Gitolite initialization";
159 wantedBy = [ "multi-user.target" ];
160 unitConfig.RequiresMountsFor = cfg.dataDir;
161
162 environment = {
163 GITOLITE_RC = ".gitolite.rc";
164 GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
165 };
166
167 serviceConfig = mkMerge [
168 (mkIf (cfg.dataDir == "/var/lib/gitolite") {
169 StateDirectory = "gitolite gitolite/.gitolite gitolite/.gitolite/logs";
170 StateDirectoryMode = "0750";
171 })
172 {
173 Type = "oneshot";
174 User = cfg.user;
175 Group = cfg.group;
176 WorkingDirectory = "~";
177 RemainAfterExit = true;
178 }
179 ];
180
181 path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ];
182 script =
183 let
184 rcSetupScriptIfCustomFile =
185 if manageGitoliteRc then ''
186 cat <<END
187 <3>ERROR: NixOS can't apply declarative configuration
188 <3>to your .gitolite.rc file, because it seems to be
189 <3>already customized manually.
190 <3>See the services.gitolite.extraGitoliteRc option
191 <3>in "man configuration.nix" for more information.
192 END
193 # Not sure if the line below addresses the issue directly or just
194 # adds a delay, but without it our error message often doesn't
195 # show up in `systemctl status gitolite-init`.
196 journalctl --flush
197 exit 1
198 '' else ''
199 :
200 '';
201 rcSetupScriptIfDefaultFileOrStoreSymlink =
202 if manageGitoliteRc then ''
203 ln -sf "${rcDir}/gitolite.rc" "$GITOLITE_RC"
204 '' else ''
205 [[ -L "$GITOLITE_RC" ]] && rm -f "$GITOLITE_RC"
206 '';
207 in
208 ''
209 if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) ||
210 ( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) ||
211 ( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] )
212 then
213 '' + rcSetupScriptIfDefaultFileOrStoreSymlink +
214 ''
215 else
216 '' + rcSetupScriptIfCustomFile +
217 ''
218 fi
219
220 if [ ! -d repositories ]; then
221 gitolite setup -pk ${pubkeyFile}
222 fi
223 if [ -n "${hooks}" ]; then
224 cp -f ${hooks} .gitolite/hooks/common/
225 chmod +x .gitolite/hooks/common/*
226 fi
227 gitolite setup # Upgrade if needed
228 '';
229 };
230
231 environment.systemPackages = [ pkgs.gitolite pkgs.git ]
232 ++ optional cfg.enableGitAnnex pkgs.git-annex;
233 });
234}