at 25.11-pre 17 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 10 cfg = config.services.tomcat; 11 tomcat = cfg.package; 12in 13 14{ 15 meta = { 16 maintainers = with lib.maintainers; [ 17 danbst 18 anthonyroussel 19 ]; 20 }; 21 22 ###### interface 23 24 options = { 25 services.tomcat = { 26 enable = lib.mkEnableOption "Apache Tomcat"; 27 28 package = lib.mkPackageOption pkgs "tomcat9" { 29 example = "tomcat10"; 30 }; 31 32 port = lib.mkOption { 33 type = lib.types.port; 34 default = 8080; 35 description = '' 36 The TCP port Tomcat should listen on. 37 ''; 38 }; 39 40 purifyOnStart = lib.mkOption { 41 type = lib.types.bool; 42 default = false; 43 description = '' 44 On startup, the `baseDir` directory is populated with various files, 45 subdirectories and symlinks. If this option is enabled, these items 46 (except for the `logs` and `work` subdirectories) are first removed. 47 This prevents interference from remainders of an old configuration 48 (libraries, webapps, etc.), so it's recommended to enable this option. 49 ''; 50 }; 51 52 baseDir = lib.mkOption { 53 type = lib.types.path; 54 default = "/var/tomcat"; 55 description = '' 56 Location where Tomcat stores configuration files, web applications 57 and logfiles. Note that it is partially cleared on each service startup 58 if `purifyOnStart` is enabled. 59 ''; 60 }; 61 62 logDirs = lib.mkOption { 63 default = [ ]; 64 type = lib.types.listOf lib.types.path; 65 description = "Directories to create in baseDir/logs/"; 66 }; 67 68 extraConfigFiles = lib.mkOption { 69 default = [ ]; 70 type = lib.types.listOf lib.types.path; 71 description = "Extra configuration files to pull into the tomcat conf directory"; 72 }; 73 74 extraEnvironment = lib.mkOption { 75 type = lib.types.listOf lib.types.str; 76 default = [ ]; 77 example = [ "ENVIRONMENT=production" ]; 78 description = "Environment Variables to pass to the tomcat service"; 79 }; 80 81 extraGroups = lib.mkOption { 82 default = [ ]; 83 type = lib.types.listOf lib.types.str; 84 example = [ "users" ]; 85 description = "Defines extra groups to which the tomcat user belongs."; 86 }; 87 88 user = lib.mkOption { 89 type = lib.types.str; 90 default = "tomcat"; 91 description = "User account under which Apache Tomcat runs."; 92 }; 93 94 group = lib.mkOption { 95 type = lib.types.str; 96 default = "tomcat"; 97 description = "Group account under which Apache Tomcat runs."; 98 }; 99 100 javaOpts = lib.mkOption { 101 type = lib.types.either (lib.types.listOf lib.types.str) lib.types.str; 102 default = ""; 103 description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat"; 104 }; 105 106 catalinaOpts = lib.mkOption { 107 type = lib.types.either (lib.types.listOf lib.types.str) lib.types.str; 108 default = ""; 109 description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container"; 110 }; 111 112 sharedLibs = lib.mkOption { 113 type = lib.types.listOf lib.types.str; 114 default = [ ]; 115 description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications"; 116 }; 117 118 serverXml = lib.mkOption { 119 type = lib.types.lines; 120 default = ""; 121 description = '' 122 Verbatim server.xml configuration. 123 This is mutually exclusive with the virtualHosts options. 124 ''; 125 }; 126 127 commonLibs = lib.mkOption { 128 type = lib.types.listOf lib.types.str; 129 default = [ ]; 130 description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container"; 131 }; 132 133 webapps = lib.mkOption { 134 type = lib.types.listOf lib.types.path; 135 default = [ tomcat.webapps ]; 136 defaultText = lib.literalExpression "[ config.services.tomcat.package.webapps ]"; 137 description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat"; 138 }; 139 140 virtualHosts = lib.mkOption { 141 type = lib.types.listOf ( 142 lib.types.submodule { 143 options = { 144 name = lib.mkOption { 145 type = lib.types.str; 146 description = "name of the virtualhost"; 147 }; 148 aliases = lib.mkOption { 149 type = lib.types.listOf lib.types.str; 150 description = "aliases of the virtualhost"; 151 default = [ ]; 152 }; 153 webapps = lib.mkOption { 154 type = lib.types.listOf lib.types.path; 155 description = '' 156 List containing web application WAR files and/or directories containing 157 web applications and configuration files for the virtual host. 158 ''; 159 default = [ ]; 160 }; 161 }; 162 } 163 ); 164 default = [ ]; 165 description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host"; 166 }; 167 168 logPerVirtualHost = lib.mkOption { 169 type = lib.types.bool; 170 default = false; 171 description = "Whether to enable logging per virtual host."; 172 }; 173 174 jdk = lib.mkPackageOption pkgs "jdk" { }; 175 176 axis2 = { 177 enable = lib.mkEnableOption "Apache Axis2 container"; 178 179 services = lib.mkOption { 180 default = [ ]; 181 type = lib.types.listOf lib.types.str; 182 description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2"; 183 }; 184 }; 185 }; 186 }; 187 188 ###### implementation 189 190 config = lib.mkIf config.services.tomcat.enable { 191 192 users.groups.tomcat.gid = config.ids.gids.tomcat; 193 194 users.users.tomcat = { 195 uid = config.ids.uids.tomcat; 196 description = "Tomcat user"; 197 home = "/homeless-shelter"; 198 group = "tomcat"; 199 extraGroups = cfg.extraGroups; 200 }; 201 202 systemd.services.tomcat = { 203 description = "Apache Tomcat server"; 204 wantedBy = [ "multi-user.target" ]; 205 after = [ "network.target" ]; 206 207 preStart = '' 208 ${lib.optionalString cfg.purifyOnStart '' 209 # Delete most directories/symlinks we create from the existing base directory, 210 # to get rid of remainders of an old configuration. 211 # The list of directories to delete is taken from the "mkdir" command below, 212 # excluding "logs" (because logs are valuable) and "work" (because normally 213 # session files are there), and additionally including "bin". 214 rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin} 215 ''} 216 217 # Create the base directory 218 mkdir -p \ 219 ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work} 220 chown ${cfg.user}:${cfg.group} \ 221 ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work} 222 223 # Create a symlink to the bin directory of the tomcat component 224 ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin 225 226 # Symlink the config files in the conf/ directory (except for catalina.properties and server.xml) 227 for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml); do 228 ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i` 229 done 230 231 ${lib.optionalString (cfg.extraConfigFiles != [ ]) '' 232 for i in ${toString cfg.extraConfigFiles}; do 233 ln -sfn $i ${cfg.baseDir}/conf/`basename $i` 234 done 235 ''} 236 237 # Create a modified catalina.properties file 238 # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries 239 sed -e 's|''${catalina.home}|''${catalina.base}|g' \ 240 -e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \ 241 ${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties 242 243 ${ 244 if cfg.serverXml != "" then 245 '' 246 cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/ 247 '' 248 else 249 let 250 hostElementForVirtualHost = 251 virtualHost: 252 '' 253 <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps" 254 unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"> 255 '' 256 + lib.concatStrings (innerElementsForVirtualHost virtualHost) 257 + '' 258 </Host> 259 ''; 260 innerElementsForVirtualHost = 261 virtualHost: 262 (map (alias: '' 263 <Alias>${alias}</Alias> 264 '') virtualHost.aliases) 265 ++ (lib.optional cfg.logPerVirtualHost '' 266 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}" 267 prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/> 268 ''); 269 hostElementsString = lib.concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts; 270 hostElementsSedString = lib.replaceStrings [ "\n" ] [ "\\\n" ] hostElementsString; 271 in 272 '' 273 # Create a modified server.xml which listens on the given port, 274 # and also includes all virtual hosts. 275 # The host modification must be last here, 276 # else if hostElementsSedString is empty sed gets confused as to what to append 277 sed -e 's/<Connector port="8080"/<Connector port="${toString cfg.port}"/' \ 278 -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${lib.escapeShellArg hostElementsSedString} \ 279 ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml 280 '' 281 } 282 ${lib.optionalString (cfg.logDirs != [ ]) '' 283 for i in ${toString cfg.logDirs}; do 284 mkdir -p ${cfg.baseDir}/logs/$i 285 chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i 286 done 287 ''} 288 ${lib.optionalString cfg.logPerVirtualHost ( 289 toString ( 290 map (h: '' 291 mkdir -p ${cfg.baseDir}/logs/${h.name} 292 chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name} 293 '') cfg.virtualHosts 294 ) 295 )} 296 297 # Symlink all the given common libs files or paths into the lib/ directory 298 for i in ${tomcat} ${toString cfg.commonLibs}; do 299 if [ -f $i ]; then 300 # If the given web application is a file, symlink it into the common/lib/ directory 301 ln -sfn $i ${cfg.baseDir}/lib/`basename $i` 302 elif [ -d $i ]; then 303 # If the given web application is a directory, then iterate over the files 304 # in the special purpose directories and symlink them into the tomcat tree 305 306 for j in $i/lib/*; do 307 ln -sfn $j ${cfg.baseDir}/lib/`basename $j` 308 done 309 fi 310 done 311 312 # Symlink all the given shared libs files or paths into the shared/lib/ directory 313 for i in ${toString cfg.sharedLibs}; do 314 if [ -f $i ]; then 315 # If the given web application is a file, symlink it into the common/lib/ directory 316 ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i` 317 elif [ -d $i ]; then 318 # If the given web application is a directory, then iterate over the files 319 # in the special purpose directories and symlink them into the tomcat tree 320 321 for j in $i/shared/lib/*; do 322 ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j` 323 done 324 fi 325 done 326 327 # Symlink all the given web applications files or paths into the webapps/ directory 328 for i in ${toString cfg.webapps}; do 329 if [ -f $i ]; then 330 # If the given web application is a file, symlink it into the webapps/ directory 331 ln -sfn $i ${cfg.baseDir}/webapps/`basename $i` 332 elif [ -d $i ]; then 333 # If the given web application is a directory, then iterate over the files 334 # in the special purpose directories and symlink them into the tomcat tree 335 336 for j in $i/webapps/*; do 337 ln -sfn $j ${cfg.baseDir}/webapps/`basename $j` 338 done 339 340 # Also symlink the configuration files if they are included 341 if [ -d $i/conf/Catalina ]; then 342 for j in $i/conf/Catalina/*; do 343 mkdir -p ${cfg.baseDir}/conf/Catalina/localhost 344 ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j` 345 done 346 fi 347 fi 348 done 349 350 ${toString ( 351 map (virtualHost: '' 352 # Create webapps directory for the virtual host 353 mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps 354 355 # Modify ownership 356 chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps 357 358 # Symlink all the given web applications files or paths into the webapps/ directory 359 # of this virtual host 360 for i in "${lib.optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do 361 if [ -f $i ]; then 362 # If the given web application is a file, symlink it into the webapps/ directory 363 ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i` 364 elif [ -d $i ]; then 365 # If the given web application is a directory, then iterate over the files 366 # in the special purpose directories and symlink them into the tomcat tree 367 368 for j in $i/webapps/*; do 369 ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j` 370 done 371 372 # Also symlink the configuration files if they are included 373 if [ -d $i/conf/Catalina ]; then 374 for j in $i/conf/Catalina/*; do 375 mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name} 376 ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j` 377 done 378 fi 379 fi 380 done 381 '') cfg.virtualHosts 382 )} 383 384 ${lib.optionalString cfg.axis2.enable '' 385 # Copy the Axis2 web application 386 cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps 387 388 # Turn off addressing, which causes many errors 389 sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml 390 391 # Modify permissions on the Axis2 application 392 chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2 393 394 # Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory 395 for i in ${toString cfg.axis2.services}; do 396 if [ -f $i ]; then 397 # If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services 398 ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i` 399 elif [ -d $i ]; then 400 # If the given web application is a directory, then iterate over the files 401 # in the special purpose directories and symlink them into the tomcat tree 402 403 for j in $i/webapps/axis2/WEB-INF/services/*; do 404 ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j` 405 done 406 407 # Also symlink the configuration files if they are included 408 if [ -d $i/conf/Catalina ]; then 409 for j in $i/conf/Catalina/*; do 410 ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j` 411 done 412 fi 413 fi 414 done 415 ''} 416 ''; 417 418 serviceConfig = { 419 Type = "forking"; 420 PermissionsStartOnly = true; 421 PIDFile = "/run/tomcat/tomcat.pid"; 422 RuntimeDirectory = "tomcat"; 423 User = cfg.user; 424 Environment = [ 425 "CATALINA_BASE=${cfg.baseDir}" 426 "CATALINA_PID=/run/tomcat/tomcat.pid" 427 "JAVA_HOME='${cfg.jdk}'" 428 "JAVA_OPTS='${builtins.toString cfg.javaOpts}'" 429 "CATALINA_OPTS='${builtins.toString cfg.catalinaOpts}'" 430 ] ++ cfg.extraEnvironment; 431 ExecStart = "${tomcat}/bin/startup.sh"; 432 ExecStop = "${tomcat}/bin/shutdown.sh"; 433 }; 434 }; 435 }; 436}