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