1{ config, lib, pkgs, ... }:
2with lib;
3let
4 cfg = config.services.jenkins;
5in {
6 options = {
7 services.jenkins = {
8 enable = mkOption {
9 type = types.bool;
10 default = false;
11 description = ''
12 Whether to enable the jenkins continuous integration server.
13 '';
14 };
15
16 user = mkOption {
17 default = "jenkins";
18 type = types.str;
19 description = ''
20 User the jenkins server should execute under.
21 '';
22 };
23
24 group = mkOption {
25 default = "jenkins";
26 type = types.str;
27 description = ''
28 If the default user "jenkins" is configured then this is the primary
29 group of that user.
30 '';
31 };
32
33 extraGroups = mkOption {
34 type = types.listOf types.str;
35 default = [ ];
36 example = [ "wheel" "dialout" ];
37 description = ''
38 List of extra groups that the "jenkins" user should be a part of.
39 '';
40 };
41
42 home = mkOption {
43 default = "/var/lib/jenkins";
44 type = types.path;
45 description = ''
46 The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
47 this is the home of the "jenkins" user.
48 '';
49 };
50
51 listenAddress = mkOption {
52 default = "0.0.0.0";
53 example = "localhost";
54 type = types.str;
55 description = ''
56 Specifies the bind address on which the jenkins HTTP interface listens.
57 The default is the wildcard address.
58 '';
59 };
60
61 port = mkOption {
62 default = 8080;
63 type = types.int;
64 description = ''
65 Specifies port number on which the jenkins HTTP interface listens.
66 The default is 8080.
67 '';
68 };
69
70 prefix = mkOption {
71 default = "";
72 example = "/jenkins";
73 type = types.str;
74 description = ''
75 Specifies a urlPrefix to use with jenkins.
76 If the example /jenkins is given, the jenkins server will be
77 accessible using localhost:8080/jenkins.
78 '';
79 };
80
81 package = mkOption {
82 default = pkgs.jenkins;
83 defaultText = "pkgs.jenkins";
84 type = types.package;
85 description = "Jenkins package to use.";
86 };
87
88 packages = mkOption {
89 default = [ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ];
90 defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]";
91 type = types.listOf types.package;
92 description = ''
93 Packages to add to PATH for the jenkins process.
94 '';
95 };
96
97 environment = mkOption {
98 default = { };
99 type = with types; attrsOf str;
100 description = ''
101 Additional environment variables to be passed to the jenkins process.
102 As a base environment, jenkins receives NIX_PATH from
103 <option>environment.sessionVariables</option>, NIX_REMOTE is set to
104 "daemon" and JENKINS_HOME is set to the value of
105 <option>services.jenkins.home</option>.
106 This option has precedence and can be used to override those
107 mentioned variables.
108 '';
109 };
110
111 plugins = mkOption {
112 default = null;
113 type = types.nullOr (types.attrsOf types.package);
114 description = ''
115 A set of plugins to activate. Note that this will completely
116 remove and replace any previously installed plugins. If you
117 have manually-installed plugins that you want to keep while
118 using this module, set this option to
119 <literal>null</literal>. You can generate this set with a
120 tool such as <literal>jenkinsPlugins2nix</literal>.
121 '';
122 example = literalExample ''
123 import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; }
124 '';
125 };
126
127 extraOptions = mkOption {
128 type = types.listOf types.str;
129 default = [ ];
130 example = [ "--debug=9" ];
131 description = ''
132 Additional command line arguments to pass to Jenkins.
133 '';
134 };
135
136 extraJavaOptions = mkOption {
137 type = types.listOf types.str;
138 default = [ ];
139 example = [ "-Xmx80m" ];
140 description = ''
141 Additional command line arguments to pass to the Java run time (as opposed to Jenkins).
142 '';
143 };
144 };
145 };
146
147 config = mkIf cfg.enable {
148 # server references the dejavu fonts
149 environment.systemPackages = [
150 pkgs.dejavu_fonts
151 ];
152
153 users.groups = optional (cfg.group == "jenkins") {
154 name = "jenkins";
155 gid = config.ids.gids.jenkins;
156 };
157
158 users.users = optional (cfg.user == "jenkins") {
159 name = "jenkins";
160 description = "jenkins user";
161 createHome = true;
162 home = cfg.home;
163 group = cfg.group;
164 extraGroups = cfg.extraGroups;
165 useDefaultShell = true;
166 uid = config.ids.uids.jenkins;
167 };
168
169 systemd.services.jenkins = {
170 description = "Jenkins Continuous Integration Server";
171 after = [ "network.target" ];
172 wantedBy = [ "multi-user.target" ];
173
174 environment =
175 let
176 selectedSessionVars =
177 lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
178 config.environment.sessionVariables;
179 in
180 selectedSessionVars //
181 { JENKINS_HOME = cfg.home;
182 NIX_REMOTE = "daemon";
183 } //
184 cfg.environment;
185
186 path = cfg.packages;
187
188 # Force .war (re)extraction, or else we might run stale Jenkins.
189
190 preStart =
191 let replacePlugins =
192 if isNull cfg.plugins
193 then ""
194 else
195 let pluginCmds = lib.attrsets.mapAttrsToList
196 (n: v: "cp ${v} ${cfg.home}/plugins/${n}.hpi")
197 cfg.plugins;
198 in ''
199 rm -r ${cfg.home}/plugins || true
200 mkdir -p ${cfg.home}/plugins
201 ${lib.strings.concatStringsSep "\n" pluginCmds}
202 '';
203 in ''
204 rm -rf ${cfg.home}/war
205 ${replacePlugins}
206 '';
207
208 # For reference: https://wiki.jenkins.io/display/JENKINS/JenkinsLinuxStartupScript
209 script = ''
210 ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
211 --httpPort=${toString cfg.port} \
212 --prefix=${cfg.prefix} \
213 -Djava.awt.headless=true \
214 ${concatStringsSep " " cfg.extraOptions}
215 '';
216
217 postStart = ''
218 until [[ $(${pkgs.curl.bin}/bin/curl -L -s --head -w '\n%{http_code}' http://${cfg.listenAddress}:${toString cfg.port}${cfg.prefix} | tail -n1) =~ ^(200|403)$ ]]; do
219 sleep 1
220 done
221 '';
222
223 serviceConfig = {
224 User = cfg.user;
225 };
226 };
227 };
228}