1# NixOS module for lighttpd web server
2
3{ config, lib, pkgs, ... }:
4
5with lib;
6
7let
8
9 cfg = config.services.lighttpd;
10
11 # List of known lighttpd modules, ordered by how the lighttpd documentation
12 # recommends them being imported:
13 # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
14 #
15 # Some modules are always imported and should not appear in the config:
16 # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
17 #
18 # Get full module list: "ls -1 $lighttpd/lib/*.so"
19 allKnownModules = [
20 "mod_rewrite"
21 "mod_redirect"
22 "mod_alias"
23 "mod_access"
24 "mod_auth"
25 "mod_status"
26 "mod_simple_vhost"
27 "mod_evhost"
28 "mod_userdir"
29 "mod_secdownload"
30 "mod_fastcgi"
31 "mod_proxy"
32 "mod_cgi"
33 "mod_ssi"
34 "mod_compress"
35 "mod_usertrack"
36 "mod_expire"
37 "mod_rrdtool"
38 "mod_accesslog"
39 # Remaining list of modules, order assumed to be unimportant.
40 "mod_authn_file"
41 "mod_authn_mysql"
42 "mod_cml"
43 "mod_deflate"
44 "mod_evasive"
45 "mod_extforward"
46 "mod_flv_streaming"
47 "mod_magnet"
48 "mod_mysql_vhost"
49 "mod_scgi"
50 "mod_setenv"
51 "mod_trigger_b4_dl"
52 "mod_uploadprogress"
53 "mod_webdav"
54 ];
55
56 maybeModuleString = moduleName:
57 if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
58
59 modulesIncludeString = concatStringsSep ",\n"
60 (filter (x: x != "") (map maybeModuleString allKnownModules));
61
62 configFile = if cfg.configText != "" then
63 pkgs.writeText "lighttpd.conf" ''
64 ${cfg.configText}
65 ''
66 else
67 pkgs.writeText "lighttpd.conf" ''
68 server.document-root = "${cfg.document-root}"
69 server.port = ${toString cfg.port}
70 server.username = "lighttpd"
71 server.groupname = "lighttpd"
72
73 # As for why all modules are loaded here, instead of having small
74 # server.modules += () entries in each sub-service extraConfig snippet,
75 # read this:
76 #
77 # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
78 # http://redmine.lighttpd.net/issues/2337
79 #
80 # Basically, lighttpd doesn't want to load (or even silently ignore) a
81 # module for a second time, and there is no way to check if a module has
82 # been loaded already. So if two services were to put the same module in
83 # server.modules += (), that would break the lighttpd configuration.
84 server.modules = (
85 ${modulesIncludeString}
86 )
87
88 # Logging (logs end up in systemd journal)
89 accesslog.use-syslog = "enable"
90 server.errorlog-use-syslog = "enable"
91
92 ${lib.optionalString cfg.enableUpstreamMimeTypes ''
93 include "${pkgs.lighttpd}/share/lighttpd/doc/config/conf.d/mime.conf"
94 ''}
95
96 static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
97 index-file.names = ( "index.html" )
98
99 ${if cfg.mod_userdir then ''
100 userdir.path = "public_html"
101 '' else ""}
102
103 ${if cfg.mod_status then ''
104 status.status-url = "/server-status"
105 status.statistics-url = "/server-statistics"
106 status.config-url = "/server-config"
107 '' else ""}
108
109 ${cfg.extraConfig}
110 '';
111
112in
113
114{
115
116 options = {
117
118 services.lighttpd = {
119
120 enable = mkOption {
121 default = false;
122 type = types.bool;
123 description = ''
124 Enable the lighttpd web server.
125 '';
126 };
127
128 port = mkOption {
129 default = 80;
130 type = types.int;
131 description = ''
132 TCP port number for lighttpd to bind to.
133 '';
134 };
135
136 document-root = mkOption {
137 default = "/srv/www";
138 type = types.path;
139 description = ''
140 Document-root of the web server. Must be readable by the "lighttpd" user.
141 '';
142 };
143
144 mod_userdir = mkOption {
145 default = false;
146 type = types.bool;
147 description = ''
148 If true, requests in the form /~user/page.html are rewritten to take
149 the file public_html/page.html from the home directory of the user.
150 '';
151 };
152
153 enableModules = mkOption {
154 type = types.listOf types.str;
155 default = [ ];
156 example = [ "mod_cgi" "mod_status" ];
157 description = ''
158 List of lighttpd modules to enable. Sub-services take care of
159 enabling modules as needed, so this option is mainly for when you
160 want to add custom stuff to
161 <option>services.lighttpd.extraConfig</option> that depends on a
162 certain module.
163 '';
164 };
165
166 enableUpstreamMimeTypes = mkOption {
167 type = types.bool;
168 default = true;
169 description = ''
170 Whether to include the list of mime types bundled with lighttpd
171 (upstream). If you disable this, no mime types will be added by
172 NixOS and you will have to add your own mime types in
173 <option>services.lighttpd.extraConfig</option>.
174 '';
175 };
176
177 mod_status = mkOption {
178 default = false;
179 type = types.bool;
180 description = ''
181 Show server status overview at /server-status, statistics at
182 /server-statistics and list of loaded modules at /server-config.
183 '';
184 };
185
186 configText = mkOption {
187 default = "";
188 type = types.lines;
189 example = ''...verbatim config file contents...'';
190 description = ''
191 Overridable config file contents to use for lighttpd. By default, use
192 the contents automatically generated by NixOS.
193 '';
194 };
195
196 extraConfig = mkOption {
197 default = "";
198 type = types.lines;
199 description = ''
200 These configuration lines will be appended to the generated lighttpd
201 config file. Note that this mechanism does not work when the manual
202 <option>configText</option> option is used.
203 '';
204 };
205
206 };
207
208 };
209
210 config = mkIf cfg.enable {
211
212 assertions = [
213 { assertion = all (x: elem x allKnownModules) cfg.enableModules;
214 message = ''
215 One (or more) modules in services.lighttpd.enableModules are
216 unrecognized.
217
218 Known modules: ${toString allKnownModules}
219
220 services.lighttpd.enableModules: ${toString cfg.enableModules}
221 '';
222 }
223 ];
224
225 services.lighttpd.enableModules = mkMerge
226 [ (mkIf cfg.mod_status [ "mod_status" ])
227 (mkIf cfg.mod_userdir [ "mod_userdir" ])
228 # always load mod_accesslog so that we can log to the journal
229 [ "mod_accesslog" ]
230 ];
231
232 systemd.services.lighttpd = {
233 description = "Lighttpd Web Server";
234 after = [ "network.target" ];
235 wantedBy = [ "multi-user.target" ];
236 serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}";
237 # SIGINT => graceful shutdown
238 serviceConfig.KillSignal = "SIGINT";
239 };
240
241 users.extraUsers.lighttpd = {
242 group = "lighttpd";
243 description = "lighttpd web server privilege separation user";
244 uid = config.ids.uids.lighttpd;
245 };
246
247 users.extraGroups.lighttpd.gid = config.ids.gids.lighttpd;
248 };
249}