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 packages = mkOption {
82 default = [ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ];
83 defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]";
84 type = types.listOf types.package;
85 description = ''
86 Packages to add to PATH for the jenkins process.
87 '';
88 };
89
90 environment = mkOption {
91 default = { };
92 type = with types; attrsOf str;
93 description = ''
94 Additional environment variables to be passed to the jenkins process.
95 As a base environment, jenkins receives NIX_PATH from
96 <option>environment.sessionVariables</option>, NIX_REMOTE is set to
97 "daemon" and JENKINS_HOME is set to the value of
98 <option>services.jenkins.home</option>.
99 This option has precedence and can be used to override those
100 mentioned variables.
101 '';
102 };
103
104 plugins = mkOption {
105 default = null;
106 type = types.nullOr (types.attrsOf types.package);
107 description = ''
108 A set of plugins to activate. Note that this will completely
109 remove and replace any previously installed plugins. If you
110 have manually-installed plugins that you want to keep while
111 using this module, set this option to
112 <literal>null</literal>. You can generate this set with a
113 tool such as <literal>jenkinsPlugins2nix</literal>.
114 '';
115 example = literalExample ''
116 import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; }
117 '';
118 };
119
120 extraOptions = mkOption {
121 type = types.listOf types.str;
122 default = [ ];
123 example = [ "--debug=9" ];
124 description = ''
125 Additional command line arguments to pass to Jenkins.
126 '';
127 };
128
129 extraJavaOptions = mkOption {
130 type = types.listOf types.str;
131 default = [ ];
132 example = [ "-Xmx80m" ];
133 description = ''
134 Additional command line arguments to pass to the Java run time (as opposed to Jenkins).
135 '';
136 };
137 };
138 };
139
140 config = mkIf cfg.enable {
141 users.extraGroups = optional (cfg.group == "jenkins") {
142 name = "jenkins";
143 gid = config.ids.gids.jenkins;
144 };
145
146 users.extraUsers = optional (cfg.user == "jenkins") {
147 name = "jenkins";
148 description = "jenkins user";
149 createHome = true;
150 home = cfg.home;
151 group = cfg.group;
152 extraGroups = cfg.extraGroups;
153 useDefaultShell = true;
154 uid = config.ids.uids.jenkins;
155 };
156
157 systemd.services.jenkins = {
158 description = "Jenkins Continuous Integration Server";
159 after = [ "network.target" ];
160 wantedBy = [ "multi-user.target" ];
161
162 environment =
163 let
164 selectedSessionVars =
165 lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
166 config.environment.sessionVariables;
167 in
168 selectedSessionVars //
169 { JENKINS_HOME = cfg.home;
170 NIX_REMOTE = "daemon";
171 } //
172 cfg.environment;
173
174 path = cfg.packages;
175
176 # Force .war (re)extraction, or else we might run stale Jenkins.
177
178 preStart =
179 let replacePlugins =
180 if isNull cfg.plugins
181 then ""
182 else
183 let pluginCmds = lib.attrsets.mapAttrsToList
184 (n: v: "cp ${v} ${cfg.home}/plugins/${n}.hpi")
185 cfg.plugins;
186 in ''
187 rm -r ${cfg.home}/plugins || true
188 mkdir -p ${cfg.home}/plugins
189 ${lib.strings.concatStringsSep "\n" pluginCmds}
190 '';
191 in ''
192 rm -rf ${cfg.home}/war
193 ${replacePlugins}
194 '';
195
196 script = ''
197 ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${pkgs.jenkins}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
198 --httpPort=${toString cfg.port} \
199 --prefix=${cfg.prefix} \
200 ${concatStringsSep " " cfg.extraOptions}
201 '';
202
203 postStart = ''
204 until [[ $(${pkgs.curl.bin}/bin/curl -s --head -w '\n%{http_code}' http://${cfg.listenAddress}:${toString cfg.port}${cfg.prefix} | tail -n1) =~ ^(200|403)$ ]]; do
205 sleep 1
206 done
207 '';
208
209 serviceConfig = {
210 User = cfg.user;
211 };
212 };
213 };
214}