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