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