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