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