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