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