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