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