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}