1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9let
10 cfg = config.services.gerrit;
11
12 # NixOS option type for git-like configs
13 gitIniType =
14 with types;
15 let
16 primitiveType = either str (either bool int);
17 multipleType = either primitiveType (listOf primitiveType);
18 sectionType = lazyAttrsOf multipleType;
19 supersectionType = lazyAttrsOf (either multipleType sectionType);
20 in
21 lazyAttrsOf supersectionType;
22
23 gerritConfig = pkgs.writeText "gerrit.conf" (lib.generators.toGitINI cfg.settings);
24
25 replicationConfig = pkgs.writeText "replication.conf" (
26 lib.generators.toGitINI cfg.replicationSettings
27 );
28
29 # Wrap the gerrit java with all the java options so it can be called
30 # like a normal CLI app
31 gerrit-cli = pkgs.writeShellScriptBin "gerrit" ''
32 set -euo pipefail
33 jvmOpts=(
34 ${lib.escapeShellArgs cfg.jvmOpts}
35 -Xmx${cfg.jvmHeapLimit}
36 )
37 exec ${cfg.jvmPackage}/bin/java \
38 "''${jvmOpts[@]}" \
39 -jar ${cfg.package}/webapps/${cfg.package.name}.war \
40 "$@"
41 '';
42
43 gerrit-plugins =
44 pkgs.runCommand "gerrit-plugins"
45 {
46 buildInputs = [ gerrit-cli ];
47 }
48 ''
49 shopt -s nullglob
50 mkdir $out
51
52 for name in ${toString cfg.builtinPlugins}; do
53 echo "Installing builtin plugin $name.jar"
54 gerrit cat plugins/$name.jar > $out/$name.jar
55 done
56
57 for file in ${toString cfg.plugins}; do
58 name=$(echo "$file" | cut -d - -f 2-)
59 echo "Installing plugin $name"
60 ln -sf "$file" $out/$name
61 done
62 '';
63in
64{
65 options = {
66 services.gerrit = {
67 enable = mkEnableOption "Gerrit service";
68
69 package = mkPackageOption pkgs "gerrit" { };
70
71 jvmPackage = mkPackageOption pkgs "jre_headless" { };
72
73 jvmOpts = mkOption {
74 type = types.listOf types.str;
75 default = [
76 "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
77 "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
78 ];
79 description = "A list of JVM options to start gerrit with.";
80 };
81
82 jvmHeapLimit = mkOption {
83 type = types.str;
84 default = "1024m";
85 description = ''
86 How much memory to allocate to the JVM heap
87 '';
88 };
89
90 listenAddress = mkOption {
91 type = types.str;
92 default = "[::]:8080";
93 description = ''
94 `hostname:port` to listen for HTTP traffic.
95
96 This is bound using the systemd socket activation.
97 '';
98 };
99
100 settings = mkOption {
101 type = gitIniType;
102 default = { };
103 description = ''
104 Gerrit configuration. This will be generated to the
105 `etc/gerrit.config` file.
106 '';
107 };
108
109 replicationSettings = mkOption {
110 type = gitIniType;
111 default = { };
112 description = ''
113 Replication configuration. This will be generated to the
114 `etc/replication.config` file.
115 '';
116 };
117
118 plugins = mkOption {
119 type = types.listOf types.package;
120 default = [ ];
121 description = ''
122 List of plugins to add to Gerrit. Each derivation is a jar file
123 itself where the name of the derivation is the name of plugin.
124 '';
125 };
126
127 builtinPlugins = mkOption {
128 type = types.listOf (types.enum cfg.package.passthru.plugins);
129 default = [ ];
130 description = ''
131 List of builtins plugins to install. Those are shipped in the
132 `gerrit.war` file.
133 '';
134 };
135
136 serverId = mkOption {
137 type = types.str;
138 description = ''
139 Set a UUID that uniquely identifies the server.
140
141 This can be generated with
142 `nix-shell -p util-linux --run uuidgen`.
143 '';
144 };
145 };
146 };
147
148 config = mkIf cfg.enable {
149
150 assertions = [
151 {
152 assertion = cfg.replicationSettings != { } -> elem "replication" cfg.builtinPlugins;
153 message = "Gerrit replicationSettings require enabling the replication plugin";
154 }
155 ];
156
157 services.gerrit.settings = {
158 cache.directory = "/var/cache/gerrit";
159 container.heapLimit = cfg.jvmHeapLimit;
160 gerrit.basePath = lib.mkDefault "git";
161 gerrit.serverId = cfg.serverId;
162 httpd.inheritChannel = "true";
163 httpd.listenUrl = lib.mkDefault "http://${cfg.listenAddress}";
164 index.type = lib.mkDefault "lucene";
165 };
166
167 # Add the gerrit CLI to the system to run `gerrit init` and friends.
168 environment.systemPackages = [ gerrit-cli ];
169
170 systemd.sockets.gerrit = {
171 unitConfig.Description = "Gerrit HTTP socket";
172 wantedBy = [ "sockets.target" ];
173 listenStreams = [ cfg.listenAddress ];
174 };
175
176 systemd.services.gerrit = {
177 description = "Gerrit";
178
179 wantedBy = [ "multi-user.target" ];
180 requires = [ "gerrit.socket" ];
181 after = [
182 "gerrit.socket"
183 "network.target"
184 ];
185
186 path = [
187 gerrit-cli
188 pkgs.bash
189 pkgs.coreutils
190 pkgs.git
191 pkgs.openssh
192 ];
193
194 environment = {
195 GERRIT_HOME = "%S/gerrit";
196 GERRIT_TMP = "%T";
197 HOME = "%S/gerrit";
198 XDG_CONFIG_HOME = "%S/gerrit/.config";
199 };
200
201 preStart = ''
202 set -euo pipefail
203
204 # bootstrap if nothing exists
205 if [[ ! -d git ]]; then
206 gerrit init --batch --no-auto-start
207 fi
208
209 # install gerrit.war for the plugin manager
210 rm -rf bin
211 mkdir bin
212 ln -sfv ${cfg.package}/webapps/${cfg.package.name}.war bin/gerrit.war
213
214 # copy the config, keep it mutable because Gerrit
215 ln -sfv ${gerritConfig} etc/gerrit.config
216 ln -sfv ${replicationConfig} etc/replication.config
217
218 # install the plugins
219 rm -rf plugins
220 ln -sv ${gerrit-plugins} plugins
221 '';
222
223 serviceConfig = {
224 CacheDirectory = "gerrit";
225 DynamicUser = true;
226 ExecStart = "${gerrit-cli}/bin/gerrit daemon --console-log";
227 LimitNOFILE = 4096;
228 StandardInput = "socket";
229 StandardOutput = "journal";
230 StateDirectory = "gerrit";
231 WorkingDirectory = "%S/gerrit";
232 AmbientCapabilities = "";
233 CapabilityBoundingSet = "";
234 LockPersonality = true;
235 NoNewPrivileges = true;
236 PrivateDevices = true;
237 PrivateTmp = true;
238 ProtectClock = true;
239 ProtectControlGroups = true;
240 ProtectHome = true;
241 ProtectHostname = true;
242 ProtectKernelLogs = true;
243 ProtectKernelModules = true;
244 ProtectKernelTunables = true;
245 ProtectProc = "noaccess";
246 ProtectSystem = "full";
247 RestrictAddressFamilies = [
248 "AF_UNIX"
249 "AF_INET"
250 "AF_INET6"
251 ];
252 RestrictNamespaces = true;
253 RestrictRealtime = true;
254 RestrictSUIDSGID = true;
255 SystemCallArchitectures = "native";
256 UMask = 27;
257 };
258 };
259 };
260
261 meta.maintainers = with lib.maintainers; [
262 edef
263 zimbatm
264 ];
265 # uses attributes of the linked package
266 meta.buildDocsInSandbox = false;
267}