Merge pull request #73113 from aanderse/httpd-vhost

nixos/httpd: support overridable virtual hosts
This commit is contained in:
Aaron Andersen 2019-12-26 08:09:08 -05:00 committed by GitHub
commit 4d2dd15546
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 709 additions and 631 deletions

@ -11,50 +11,46 @@
<programlisting>
{
<xref linkend="opt-services.httpd.virtualHosts"/> =
[ { hostName = "example.org";
documentRoot = "/webroot";
{ "blog.example.org" = {
documentRoot = "/webroot/blog.example.org";
adminAddr = "alice@example.org";
enableUserDir = true;
}
{ hostName = "example.org";
documentRoot = "/webroot";
forceSSL = true;
enableACME = true;
enablePHP = true;
};
"wiki.example.org" = {
documentRoot = "/webroot/wiki.example.org";
adminAddr = "alice@example.org";
enableUserDir = true;
enableSSL = true;
sslServerCert = "/root/ssl-example-org.crt";
sslServerKey = "/root/ssl-example-org.key";
}
];
forceSSL = true;
enableACME = true;
enablePHP = true;
};
};
}
</programlisting>
It defines two virtual hosts with nearly identical configuration; the only
difference is that the second one has SSL enabled. To prevent this
difference is the document root directories. To prevent this
duplication, we can use a <literal>let</literal>:
<programlisting>
let
exampleOrgCommon =
{ hostName = "example.org";
documentRoot = "/webroot";
adminAddr = "alice@example.org";
enableUserDir = true;
commonConfig =
{ adminAddr = "alice@example.org";
forceSSL = true;
enableACME = true;
};
in
{
<xref linkend="opt-services.httpd.virtualHosts"/> =
[ exampleOrgCommon
(exampleOrgCommon // {
enableSSL = true;
sslServerCert = "/root/ssl-example-org.crt";
sslServerKey = "/root/ssl-example-org.key";
})
];
{ "blog.example.org" = (commonConfig // { documentRoot = "/webroot/blog.example.org"; });
"wiki.example.org" = (commonConfig // { documentRoot = "/webroot/wiki.example.com"; });
};
}
</programlisting>
The <literal>let exampleOrgCommon = <replaceable>...</replaceable></literal>
defines a variable named <literal>exampleOrgCommon</literal>. The
The <literal>let commonConfig = <replaceable>...</replaceable></literal>
defines a variable named <literal>commonConfig</literal>. The
<literal>//</literal> operator merges two attribute sets, so the
configuration of the second virtual host is the set
<literal>exampleOrgCommon</literal> extended with the SSL options.
<literal>commonConfig</literal> extended with the document root option.
</para>
<para>
@ -63,13 +59,13 @@ in
<programlisting>
{
<xref linkend="opt-services.httpd.virtualHosts"/> =
let exampleOrgCommon = <replaceable>...</replaceable>; in
[ exampleOrgCommon
(exampleOrgCommon // { <replaceable>...</replaceable> })
];
let commonConfig = <replaceable>...</replaceable>; in
{ "blog.example.org" = (commonConfig // { <replaceable>...</replaceable> })
"wiki.example.org" = (commonConfig // { <replaceable>...</replaceable> })
};
}
</programlisting>
but not <literal>{ let exampleOrgCommon = <replaceable>...</replaceable>; in
but not <literal>{ let commonConfig = <replaceable>...</replaceable>; in
<replaceable>...</replaceable>; }</literal> since attributes (as opposed to
attribute values) are not expressions.
</para>
@ -77,80 +73,29 @@ in
<para>
<emphasis>Functions</emphasis> provide another method of abstraction. For
instance, suppose that we want to generate lots of different virtual hosts,
all with identical configuration except for the host name. This can be done
all with identical configuration except for the document root. This can be done
as follows:
<programlisting>
{
<xref linkend="opt-services.httpd.virtualHosts"/> =
let
makeVirtualHost = name:
{ hostName = name;
documentRoot = "/webroot";
makeVirtualHost = webroot:
{ documentRoot = webroot;
adminAddr = "alice@example.org";
forceSSL = true;
enableACME = true;
};
in
[ (makeVirtualHost "example.org")
(makeVirtualHost "example.com")
(makeVirtualHost "example.gov")
(makeVirtualHost "example.nl")
];
{ "example.org" = (makeVirtualHost "/webroot/example.org");
"example.com" = (makeVirtualHost "/webroot/example.com");
"example.gov" = (makeVirtualHost "/webroot/example.gov");
"example.nl" = (makeVirtualHost "/webroot/example.nl");
};
}
</programlisting>
Here, <varname>makeVirtualHost</varname> is a function that takes a single
argument <literal>name</literal> and returns the configuration for a virtual
argument <literal>webroot</literal> and returns the configuration for a virtual
host. That function is then called for several names to produce the list of
virtual host configurations.
</para>
<para>
We can further improve on this by using the function <varname>map</varname>,
which applies another function to every element in a list:
<programlisting>
{
<xref linkend="opt-services.httpd.virtualHosts"/> =
let
makeVirtualHost = <replaceable>...</replaceable>;
in map makeVirtualHost
[ "example.org" "example.com" "example.gov" "example.nl" ];
}
</programlisting>
(The function <literal>map</literal> is called a <emphasis>higher-order
function</emphasis> because it takes another function as an argument.)
</para>
<para>
What if you need more than one argument, for instance, if we want to use a
different <literal>documentRoot</literal> for each virtual host? Then we can
make <varname>makeVirtualHost</varname> a function that takes a
<emphasis>set</emphasis> as its argument, like this:
<programlisting>
{
<xref linkend="opt-services.httpd.virtualHosts"/> =
let
makeVirtualHost = { name, root }:
{ hostName = name;
documentRoot = root;
adminAddr = "alice@example.org";
};
in map makeVirtualHost
[ { name = "example.org"; root = "/sites/example.org"; }
{ name = "example.com"; root = "/sites/example.com"; }
{ name = "example.gov"; root = "/sites/example.gov"; }
{ name = "example.nl"; root = "/sites/example.nl"; }
];
}
</programlisting>
But in this case (where every root is a subdirectory of
<filename>/sites</filename> named after the virtual host), it would have been
shorter to define <varname>makeVirtualHost</varname> as
<programlisting>
makeVirtualHost = name:
{ hostName = name;
documentRoot = "/sites/${name}";
adminAddr = "alice@example.org";
};
</programlisting>
Here, the construct <literal>${<replaceable>...</replaceable>}</literal>
allows the result of an expression to be spliced into a string.
</para>
</section>

@ -27,7 +27,7 @@
{ <xref linkend="opt-services.httpd.enable"/> = true;
<xref linkend="opt-services.httpd.adminAddr"/> = "alice@example.org";
<xref linkend="opt-services.httpd.documentRoot"/> = "/webroot";
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.localhost.documentRoot</link> = "/webroot";
}
</programlisting>
defines a configuration with three option definitions that together enable
@ -50,7 +50,11 @@
httpd = {
enable = true;
adminAddr = "alice@example.org";
documentRoot = "/webroot";
virtualHosts = {
localhost = {
documentRoot = "/webroot";
};
};
};
};
}

@ -327,6 +327,28 @@ services.xserver.displayManager.defaultSession = "xfce+icewm";
module.
</para>
</listitem>
<listitem>
<para>
The httpd module no longer provides options to support serving web content without defining a virtual host. As a
result of this the <link linkend="opt-services.httpd.logPerVirtualHost">services.httpd.logPerVirtualHost</link>
option now defaults to <literal>true</literal> instead of <literal>false</literal>. Please update your
configuration to make use of <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts</link>.
</para>
<para>
The <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;</link>
option has changed type from a list of submodules to an attribute set of submodules, better matching
<link linkend="opt-services.nginx.virtualHosts">services.nginx.virtualHosts.&lt;name&gt;</link>.
</para>
<para>
This change comes with the addition of the following options which mimic the functionality of their <literal>nginx</literal> counterparts:
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.addSSL</link>,
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.forceSSL</link>,
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.onlySSL</link>,
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.enableACME</link>,
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.acmeRoot</link>, and
<link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.useACMEHost</link>.
</para>
</listitem>
</itemizedlist>
</section>

@ -8,6 +8,7 @@ let
nagiosState = "/var/lib/nagios";
nagiosLogDir = "/var/log/nagios";
urlPath = "/nagios";
nagiosObjectDefs = cfg.objectDefs;
@ -49,12 +50,12 @@ let
''
main_config_file=${cfg.mainConfigFile}
use_authentication=0
url_html_path=${cfg.urlPath}
url_html_path=${urlPath}
'';
extraHttpdConfig =
''
ScriptAlias ${cfg.urlPath}/cgi-bin ${pkgs.nagios}/sbin
ScriptAlias ${urlPath}/cgi-bin ${pkgs.nagios}/sbin
<Directory "${pkgs.nagios}/sbin">
Options ExecCGI
@ -62,7 +63,7 @@ let
SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile}
</Directory>
Alias ${cfg.urlPath} ${pkgs.nagios}/share
Alias ${urlPath} ${pkgs.nagios}/share
<Directory "${pkgs.nagios}/share">
Options None
@ -72,6 +73,10 @@ let
in
{
imports = [
(mkRemovedOptionModule [ "services" "nagios" "urlPath" ] "The urlPath option has been removed as it is hard coded to /nagios in the nagios package.")
];
options = {
services.nagios = {
enable = mkOption {
@ -128,13 +133,20 @@ in
";
};
urlPath = mkOption {
default = "/nagios";
description = "
The URL path under which the Nagios web interface appears.
That is, you can access the Nagios web interface through
<literal>http://<replaceable>server</replaceable>/<replaceable>urlPath</replaceable></literal>.
";
virtualHost = mkOption {
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix);
example = literalExample ''
{ hostName = "example.org";
adminAddr = "webmaster@example.org";
enableSSL = true;
sslServerCert = "/var/lib/acme/example.org/full.pem";
sslServerKey = "/var/lib/acme/example.org/key.pem";
}
'';
description = ''
Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
'';
};
};
};
@ -182,6 +194,8 @@ in
'';
};
services.httpd.extraConfig = optionalString cfg.enableWebInterface extraHttpdConfig;
services.httpd.virtualHosts = optionalAttrs cfg.enableWebInterface {
${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { extraConfig = extraHttpdConfig; } ];
};
};
}

@ -3,7 +3,7 @@
let
inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
inherit (lib) mapAttrs optional optionalString types;
inherit (lib) literalExample mapAttrs optional optionalString types;
cfg = config.services.limesurvey;
fpm = config.services.phpfpm.pools.limesurvey;
@ -100,19 +100,15 @@ in
};
virtualHost = mkOption {
type = types.submodule ({
options = import ../web-servers/apache-httpd/per-server-options.nix {
inherit lib;
forMainServer = false;
};
});
example = {
hostName = "survey.example.org";
enableSSL = true;
adminAddr = "webmaster@example.org";
sslServerCert = "/var/lib/acme/survey.example.org/full.pem";
sslServerKey = "/var/lib/acme/survey.example.org/key.pem";
};
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix);
example = literalExample ''
{
hostName = "survey.example.org";
adminAddr = "webmaster@example.org";
forceSSL = true;
enableACME = true;
}
'';
description = ''
Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
@ -184,7 +180,7 @@ in
config = {
tempdir = "${stateDir}/tmp";
uploaddir = "${stateDir}/upload";
force_ssl = mkIf cfg.virtualHost.enableSSL "on";
force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on";
config.defaultlang = "en";
};
};
@ -215,38 +211,36 @@ in
enable = true;
adminAddr = mkDefault cfg.virtualHost.adminAddr;
extraModules = [ "proxy_fcgi" ];
virtualHosts = [ (mkMerge [
cfg.virtualHost {
documentRoot = mkForce "${pkg}/share/limesurvey";
extraConfig = ''
Alias "/tmp" "${stateDir}/tmp"
<Directory "${stateDir}">
AllowOverride all
Require all granted
Options -Indexes +FollowSymlinks
</Directory>
virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
documentRoot = mkForce "${pkg}/share/limesurvey";
extraConfig = ''
Alias "/tmp" "${stateDir}/tmp"
<Directory "${stateDir}">
AllowOverride all
Require all granted
Options -Indexes +FollowSymlinks
</Directory>
Alias "/upload" "${stateDir}/upload"
<Directory "${stateDir}/upload">
AllowOverride all
Require all granted
Options -Indexes
</Directory>
Alias "/upload" "${stateDir}/upload"
<Directory "${stateDir}/upload">
AllowOverride all
Require all granted
Options -Indexes
</Directory>
<Directory "${pkg}/share/limesurvey">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
<Directory "${pkg}/share/limesurvey">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
AllowOverride all
Options -Indexes
DirectoryIndex index.php
</Directory>
'';
}
]) ];
AllowOverride all
Options -Indexes
DirectoryIndex index.php
</Directory>
'';
} ];
};
systemd.tmpfiles.rules = [

@ -64,7 +64,7 @@ let
$wgScriptPath = "";
## The protocol and server name to use in fully-qualified URLs
$wgServer = "${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}";
$wgServer = "${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}";
## The URL path to static resources (images, scripts, etc.)
$wgResourceBasePath = $wgScriptPath;
@ -290,19 +290,13 @@ in
};
virtualHost = mkOption {
type = types.submodule ({
options = import ../web-servers/apache-httpd/per-server-options.nix {
inherit lib;
forMainServer = false;
};
});
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix);
example = literalExample ''
{
hostName = "mediawiki.example.org";
enableSSL = true;
adminAddr = "webmaster@example.org";
sslServerCert = "/var/lib/acme/mediawiki.example.org/full.pem";
sslServerKey = "/var/lib/acme/mediawiki.example.org/key.pem";
forceSSL = true;
enableACME = true;
}
'';
description = ''
@ -389,31 +383,28 @@ in
services.httpd = {
enable = true;
adminAddr = mkDefault cfg.virtualHost.adminAddr;
extraModules = [ "proxy_fcgi" ];
virtualHosts = [ (mkMerge [
cfg.virtualHost {
documentRoot = mkForce "${pkg}/share/mediawiki";
extraConfig = ''
<Directory "${pkg}/share/mediawiki">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
documentRoot = mkForce "${pkg}/share/mediawiki";
extraConfig = ''
<Directory "${pkg}/share/mediawiki">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
Require all granted
DirectoryIndex index.php
AllowOverride All
</Directory>
'' + optionalString (cfg.uploadsDir != null) ''
Alias "/images" "${cfg.uploadsDir}"
<Directory "${cfg.uploadsDir}">
Require all granted
</Directory>
'';
}
]) ];
Require all granted
DirectoryIndex index.php
AllowOverride All
</Directory>
'' + optionalString (cfg.uploadsDir != null) ''
Alias "/images" "${cfg.uploadsDir}"
<Directory "${cfg.uploadsDir}">
Require all granted
</Directory>
'';
} ];
};
systemd.tmpfiles.rules = [

@ -32,7 +32,7 @@ let
'dbcollation' => 'utf8mb4_unicode_ci',
);
$CFG->wwwroot = '${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}';
$CFG->wwwroot = '${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}';
$CFG->dataroot = '${stateDir}';
$CFG->admin = 'admin';
@ -140,19 +140,15 @@ in
};
virtualHost = mkOption {
type = types.submodule ({
options = import ../web-servers/apache-httpd/per-server-options.nix {
inherit lib;
forMainServer = false;
};
});
example = {
hostName = "moodle.example.org";
enableSSL = true;
adminAddr = "webmaster@example.org";
sslServerCert = "/var/lib/acme/moodle.example.org/full.pem";
sslServerKey = "/var/lib/acme/moodle.example.org/key.pem";
};
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix);
example = literalExample ''
{
hostName = "moodle.example.org";
adminAddr = "webmaster@example.org";
forceSSL = true;
enableACME = true;
}
'';
description = ''
Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
@ -241,22 +237,20 @@ in
enable = true;
adminAddr = mkDefault cfg.virtualHost.adminAddr;
extraModules = [ "proxy_fcgi" ];
virtualHosts = [ (mkMerge [
cfg.virtualHost {
documentRoot = mkForce "${cfg.package}/share/moodle";
extraConfig = ''
<Directory "${cfg.package}/share/moodle">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
Options -Indexes
DirectoryIndex index.php
</Directory>
'';
}
]) ];
virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
documentRoot = mkForce "${cfg.package}/share/moodle";
extraConfig = ''
<Directory "${cfg.package}/share/moodle">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
Options -Indexes
DirectoryIndex index.php
</Directory>
'';
} ];
};
systemd.tmpfiles.rules = [

@ -3,7 +3,7 @@
let
inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
inherit (lib) mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
inherit (lib) mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
eachSite = config.services.wordpress;
user = "wordpress";
@ -209,18 +209,12 @@ let
};
virtualHost = mkOption {
type = types.submodule ({
options = import ../web-servers/apache-httpd/per-server-options.nix {
inherit lib;
forMainServer = false;
};
});
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix);
example = literalExample ''
{
enableSSL = true;
adminAddr = "webmaster@example.org";
sslServerCert = "/var/lib/acme/wordpress.example.org/full.pem";
sslServerKey = "/var/lib/acme/wordpress.example.org/key.pem";
forceSSL = true;
enableACME = true;
}
'';
description = ''
@ -304,41 +298,37 @@ in
services.httpd = {
enable = true;
extraModules = [ "proxy_fcgi" ];
virtualHosts = mapAttrsToList (hostName: cfg:
(mkMerge [
cfg.virtualHost {
documentRoot = mkForce "${pkg hostName cfg}/share/wordpress";
extraConfig = ''
<Directory "${pkg hostName cfg}/share/wordpress">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/"
</If>
</FilesMatch>
virtualHosts = mapAttrs (hostName: cfg: mkMerge [ cfg.virtualHost {
documentRoot = mkForce "${pkg hostName cfg}/share/wordpress";
extraConfig = ''
<Directory "${pkg hostName cfg}/share/wordpress">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/"
</If>
</FilesMatch>
# standard wordpress .htaccess contents
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# standard wordpress .htaccess contents
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
DirectoryIndex index.php
Require all granted
Options +FollowSymLinks
</Directory>
DirectoryIndex index.php
Require all granted
Options +FollowSymLinks
</Directory>
# https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php
<Files wp-config.php>
Require all denied
</Files>
'';
}
])
) eachSite;
# https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php
<Files wp-config.php>
Require all denied
</Files>
'';
} ]) eachSite;
};
systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [

@ -113,19 +113,15 @@ in
};
virtualHost = mkOption {
type = types.submodule ({
options = import ../web-servers/apache-httpd/per-server-options.nix {
inherit lib;
forMainServer = false;
};
});
example = {
hostName = "zabbix.example.org";
enableSSL = true;
adminAddr = "webmaster@example.org";
sslServerCert = "/var/lib/acme/zabbix.example.org/full.pem";
sslServerKey = "/var/lib/acme/zabbix.example.org/key.pem";
};
type = types.submodule (import ../web-servers/apache-httpd/per-server-options.nix);
example = literalExample ''
{
hostName = "zabbix.example.org";
adminAddr = "webmaster@example.org";
forceSSL = true;
enableACME = true;
}
'';
description = ''
Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
@ -190,23 +186,21 @@ in
enable = true;
adminAddr = mkDefault cfg.virtualHost.adminAddr;
extraModules = [ "proxy_fcgi" ];
virtualHosts = [ (mkMerge [
cfg.virtualHost {
documentRoot = mkForce "${cfg.package}/share/zabbix";
extraConfig = ''
<Directory "${cfg.package}/share/zabbix">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
AllowOverride all
Options -Indexes
DirectoryIndex index.php
</Directory>
'';
}
]) ];
virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
documentRoot = mkForce "${cfg.package}/share/zabbix";
extraConfig = ''
<Directory "${cfg.package}/share/zabbix">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
</If>
</FilesMatch>
AllowOverride all
Options -Indexes
DirectoryIndex index.php
</Directory>
'';
} ];
};
users.users.${user} = mapAttrs (name: mkDefault) {

@ -18,22 +18,20 @@ let
mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
defaultListen = cfg: if cfg.enableSSL
then [{ip = "*"; port = 443;}]
else [{ip = "*"; port = 80;}];
vhosts = attrValues mainCfg.virtualHosts;
getListen = cfg:
if cfg.listen == []
then defaultListen cfg
else cfg.listen;
mkListenInfo = hostOpts:
if hostOpts.listen != [] then hostOpts.listen
else (
optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++
optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; }
);
listenToString = l: "${l.ip}:${toString l.port}";
listenInfo = unique (concatMap mkListenInfo vhosts);
allHosts = [mainCfg] ++ mainCfg.virtualHosts;
enableSSL = any (listen: listen.ssl) listenInfo;
enableSSL = any (vhost: vhost.enableSSL) allHosts;
enableUserDir = any (vhost: vhost.enableUserDir) allHosts;
enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
# NOTE: generally speaking order of modules is very important
modules =
@ -115,122 +113,137 @@ let
</IfModule>
'';
mkVHostConf = hostOpts:
let
adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr;
listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
perServerConf = isMainServer: cfg: let
useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
sslCertDir =
if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory
else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory
else abort "This case should never happen.";
# Canonical name must not include a trailing slash.
canonicalNames =
let defaultPort = (head (defaultListen cfg)).port; in
map (port:
(if cfg.enableSSL then "https" else "http") + "://" +
cfg.hostName +
(if port != defaultPort then ":${toString port}" else "")
) (map (x: x.port) (getListen cfg));
sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert;
sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain;
maybeDocumentRoot = fold (svc: acc:
if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
) null ([ cfg ]);
acmeChallenge = optionalString useACME ''
Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
<Directory "${hostOpts.acmeRoot}">
AllowOverride None
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
Require method GET POST OPTIONS
Require all granted
</Directory>
'';
in
optionalString (listen != []) ''
<VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
ServerName ${hostOpts.hostName}
${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
ServerAdmin ${adminAddr}
<IfModule mod_ssl.c>
SSLEngine off
</IfModule>
${acmeChallenge}
${if hostOpts.forceSSL then ''
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</IfModule>
'' else mkVHostCommonConf hostOpts}
</VirtualHost>
'' +
optionalString (listenSSL != []) ''
<VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
ServerName ${hostOpts.hostName}
${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
ServerAdmin ${adminAddr}
SSLEngine on
SSLCertificateFile ${sslServerCert}
SSLCertificateKeyFile ${sslServerKey}
${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
${acmeChallenge}
${mkVHostCommonConf hostOpts}
</VirtualHost>
''
;
documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
mkVHostCommonConf = hostOpts:
let
documentRoot = if hostOpts.documentRoot != null
then hostOpts.documentRoot
else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"
;
in
''
${optionalString mainCfg.logPerVirtualHost ''
ErrorLog ${mainCfg.logDir}/error-${hostOpts.hostName}.log
CustomLog ${mainCfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
''}
documentRootConf = ''
DocumentRoot "${documentRoot}"
${optionalString (hostOpts.robotsEntries != "") ''
Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
''}
<Directory "${documentRoot}">
Options Indexes FollowSymLinks
AllowOverride None
${allGranted}
</Directory>
'';
DocumentRoot "${documentRoot}"
# If this is a vhost, the include the entries for the main server as well.
robotsTxt = concatStringsSep "\n" (filter (x: x != "") ([ cfg.robotsEntries ] ++ lib.optional (!isMainServer) mainCfg.robotsEntries));
<Directory "${documentRoot}">
Options Indexes FollowSymLinks
AllowOverride None
${allGranted}
</Directory>
in ''
${concatStringsSep "\n" (map (n: "ServerName ${n}") canonicalNames)}
${optionalString hostOpts.enableUserDir ''
UserDir public_html
UserDir disabled root
<Directory "/home/*/public_html">
AllowOverride FileInfo AuthConfig Limit Indexes
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
<Limit GET POST OPTIONS>
Require all granted
</Limit>
<LimitExcept GET POST OPTIONS>
Require all denied
</LimitExcept>
</Directory>
''}
${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
RedirectPermanent / ${hostOpts.globalRedirect}
''}
${if cfg.sslServerCert != null then ''
SSLCertificateFile ${cfg.sslServerCert}
SSLCertificateKeyFile ${cfg.sslServerKey}
${if cfg.sslServerChain != null then ''
SSLCertificateChainFile ${cfg.sslServerChain}
'' else ""}
'' else ""}
${
let makeFileConf = elem: ''
Alias ${elem.urlPath} ${elem.file}
'';
in concatMapStrings makeFileConf hostOpts.servedFiles
}
${
let makeDirConf = elem: ''
Alias ${elem.urlPath} ${elem.dir}/
<Directory ${elem.dir}>
Options +Indexes
${allGranted}
AllowOverride All
</Directory>
'';
in concatMapStrings makeDirConf hostOpts.servedDirs
}
${if cfg.enableSSL then ''
SSLEngine on
'' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
''
SSLEngine off
'' else ""}
${if isMainServer || cfg.adminAddr != null then ''
ServerAdmin ${cfg.adminAddr}
'' else ""}
${if !isMainServer && mainCfg.logPerVirtualHost then ''
ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
'' else ""}
${optionalString (robotsTxt != "") ''
Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
''}
${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
${if cfg.enableUserDir then ''
UserDir public_html
UserDir disabled root
<Directory "/home/*/public_html">
AllowOverride FileInfo AuthConfig Limit Indexes
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
<Limit GET POST OPTIONS>
${allGranted}
</Limit>
<LimitExcept GET POST OPTIONS>
${allDenied}
</LimitExcept>
</Directory>
'' else ""}
${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
RedirectPermanent / ${cfg.globalRedirect}
'' else ""}
${
let makeFileConf = elem: ''
Alias ${elem.urlPath} ${elem.file}
'';
in concatMapStrings makeFileConf cfg.servedFiles
}
${
let makeDirConf = elem: ''
Alias ${elem.urlPath} ${elem.dir}/
<Directory ${elem.dir}>
Options +Indexes
${allGranted}
AllowOverride All
</Directory>
'';
in concatMapStrings makeDirConf cfg.servedDirs
}
${cfg.extraConfig}
'';
${hostOpts.extraConfig}
''
;
confFile = pkgs.writeText "httpd.conf" ''
ServerRoot ${httpd}
ServerName ${config.networking.hostName}
DefaultRuntimeDir ${runtimeDir}/runtime
PidFile ${runtimeDir}/httpd.pid
@ -246,10 +259,9 @@ let
</IfModule>
${let
listen = concatMap getListen allHosts;
toStr = listen: "Listen ${listenToString listen}\n";
uniqueListen = uniqList {inputList = map toStr listen;};
in concatStrings uniqueListen
toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
uniqueListen = uniqList {inputList = map toStr listenInfo;};
in concatStringsSep "\n" uniqueListen
}
User ${mainCfg.user}
@ -297,17 +309,9 @@ let
${allGranted}
</Directory>
# Generate directives for the main server.
${perServerConf true mainCfg}
${mainCfg.extraConfig}
${let
makeVirtualHost = vhost: ''
<VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
${perServerConf false vhost}
</VirtualHost>
'';
in concatMapStrings makeVirtualHost mainCfg.virtualHosts
}
${concatMapStringsSep "\n" mkVHostConf vhosts}
'';
# Generate the PHP configuration file. Should probably be factored
@ -329,6 +333,21 @@ in
imports = [
(mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
(mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
# virtualHosts options
(mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
(mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
];
###### interface
@ -391,9 +410,25 @@ in
'';
};
adminAddr = mkOption {
type = types.str;
example = "admin@example.org";
description = "E-mail address of the server administrator.";
};
logFormat = mkOption {
type = types.str;
default = "common";
example = "combined";
description = ''
Log format for log files. Possible values are: combined, common, referer, agent.
See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
'';
};
logPerVirtualHost = mkOption {
type = types.bool;
default = false;
default = true;
description = ''
If enabled, each virtual host gets its own
<filename>access.log</filename> and
@ -429,26 +464,28 @@ in
};
virtualHosts = mkOption {
type = types.listOf (types.submodule (
{ options = import ./per-server-options.nix {
inherit lib;
forMainServer = false;
type = with types; attrsOf (submodule (import ./per-server-options.nix));
default = {
localhost = {
documentRoot = "${httpd}/htdocs";
};
};
example = literalExample ''
{
"foo.example.com" = {
forceSSL = true;
documentRoot = "/var/www/foo.example.com"
};
"bar.example.com" = {
addSSL = true;
documentRoot = "/var/www/bar.example.com";
};
}));
default = [];
example = [
{ hostName = "foo";
documentRoot = "/data/webroot-foo";
}
{ hostName = "bar";
documentRoot = "/data/webroot-bar";
}
];
'';
description = ''
Specification of the virtual hosts served by Apache. Each
Specification of the virtual hosts served by Apache. Each
element should be an attribute set specifying the
configuration of the virtual host. The available options
are the non-global options permissible for the main host.
configuration of the virtual host.
'';
};
@ -534,13 +571,7 @@ in
example = "All -SSLv2 -SSLv3";
description = "Allowed SSL/TLS protocol versions.";
};
}
# Include the options shared between the main server and virtual hosts.
// (import ./per-server-options.nix {
inherit lib;
forMainServer = true;
});
};
};
@ -549,11 +580,31 @@ in
config = mkIf config.services.httpd.enable {
assertions = [ { assertion = mainCfg.enableSSL == true
-> mainCfg.sslServerCert != null
&& mainCfg.sslServerKey != null;
message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
];
assertions = [
{
assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
message = ''
The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
or `services.httpd.virtualHosts.<name>.onlySSL`.
'';
}
{
assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
message = ''
Options `services.httpd.virtualHosts.<name>.addSSL`,
`services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
are mutually exclusive.
'';
}
{
assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
message = ''
Options `services.httpd.virtualHosts.<name>.enableACME` and
`services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
'';
}
];
users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton
{ name = "wwwrun";
@ -567,6 +618,15 @@ in
gid = config.ids.gids.wwwrun;
});
security.acme.certs = mapAttrs (name: hostOpts: {
user = mainCfg.user;
group = mkDefault mainCfg.group;
email = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr;
webroot = hostOpts.acmeRoot;
extraDomains = genAttrs hostOpts.serverAliases (alias: null);
postRun = "systemctl reload httpd.service";
}) (filterAttrs (name: hostOpts: hostOpts.enableACME) mainCfg.virtualHosts);
environment.systemPackages = [httpd];
services.httpd.phpOptions =
@ -605,10 +665,14 @@ in
];
systemd.services.httpd =
let
vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
in
{ description = "Apache HTTPD";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "fs.target" ];
wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME);
after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
path =
[ httpd pkgs.coreutils pkgs.gnugrep ]

@ -1,174 +1,235 @@
# This file defines the options that can be used both for the Apache
# main server configuration, and for the virtual hosts. (The latter
# has additional options that affect the web server as a whole, like
# the user/group to run under.)
{ forMainServer, lib }:
with lib;
{ config, lib, name, ... }:
let
inherit (lib) mkOption types;
in
{
options = {
hostName = mkOption {
type = types.str;
default = name;
description = "Canonical hostname for the server.";
};
serverAliases = mkOption {
type = types.listOf types.str;
default = [];
example = ["www.example.org" "www.example.org:8080" "example.org"];
description = ''
Additional names of virtual hosts served by this virtual host configuration.
'';
};
listen = mkOption {
type = with types; listOf (submodule ({
options = {
port = mkOption {
type = types.port;
description = "Port to listen on";
};
ip = mkOption {
type = types.str;
default = "*";
description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
};
ssl = mkOption {
type = types.bool;
default = false;
description = "Whether to enable SSL (https) support.";
};
};
}));
default = [];
example = [
{ ip = "195.154.1.1"; port = 443; ssl = true;}
{ ip = "192.154.1.1"; port = 80; }
{ ip = "*"; port = 8080; }
];
description = ''
Listen addresses and ports for this virtual host.
<note><para>
This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>.
</para></note>
'';
};
enableSSL = mkOption {
type = types.bool;
visible = false;
default = false;
};
addSSL = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
<literal>listen</literal> to listen on all interfaces on the respective default
ports (80, 443).
'';
};
onlySSL = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable HTTPS and reject plain HTTP connections. This will set
defaults for <literal>listen</literal> to listen on all interfaces on port 443.
'';
};
forceSSL = mkOption {
type = types.bool;
default = false;
description = ''
Whether to add a separate nginx server block that permanently redirects (301)
all plain HTTP traffic to HTTPS. This will set defaults for
<literal>listen</literal> to listen on all interfaces on the respective default
ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
'';
};
enableACME = mkOption {
type = types.bool;
default = false;
description = ''
Whether to ask Let's Encrypt to sign a certificate for this vhost.
Alternately, you can use an existing certificate through <option>useACMEHost</option>.
'';
};
useACMEHost = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
A host of an existing Let's Encrypt certificate to use.
This is useful if you have many subdomains and want to avoid hitting the
<link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
Alternately, you can generate a certificate through <option>enableACME</option>.
<emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis>
'';
};
acmeRoot = mkOption {
type = types.str;
default = "/var/lib/acme/acme-challenges";
description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
};
sslServerCert = mkOption {
type = types.path;
example = "/var/host.cert";
description = "Path to server SSL certificate.";
};
sslServerKey = mkOption {
type = types.path;
example = "/var/host.key";
description = "Path to server SSL certificate key.";
};
sslServerChain = mkOption {
type = types.nullOr types.path;
default = null;
example = "/var/ca.pem";
description = "Path to server SSL chain file.";
};
adminAddr = mkOption {
type = types.nullOr types.str;
default = null;
example = "admin@example.org";
description = "E-mail address of the server administrator.";
};
documentRoot = mkOption {
type = types.nullOr types.path;
default = null;
example = "/data/webserver/docs";
description = ''
The path of Apache's document root directory. If left undefined,
an empty directory in the Nix store will be used as root.
'';
};
servedDirs = mkOption {
type = types.listOf types.attrs;
default = [];
example = [
{ urlPath = "/nix";
dir = "/home/eelco/Dev/nix-homepage";
}
];
description = ''
This option provides a simple way to serve static directories.
'';
};
servedFiles = mkOption {
type = types.listOf types.attrs;
default = [];
example = [
{ urlPath = "/foo/bar.png";
file = "/home/eelco/some-file.png";
}
];
description = ''
This option provides a simple way to serve individual, static files.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
<Directory /home>
Options FollowSymlinks
AllowOverride All
</Directory>
'';
description = ''
These lines go to httpd.conf verbatim. They will go after
directories and directory aliases defined by default.
'';
};
enableUserDir = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable serving <filename>~/public_html</filename> as
<literal>/~<replaceable>username</replaceable></literal>.
'';
};
globalRedirect = mkOption {
type = types.nullOr types.str;
default = null;
example = http://newserver.example.org/;
description = ''
If set, all requests for this host are redirected permanently to
the given URL.
'';
};
logFormat = mkOption {
type = types.str;
default = "common";
example = "combined";
description = ''
Log format for Apache's log files. Possible values are: combined, common, referer, agent.
'';
};
robotsEntries = mkOption {
type = types.lines;
default = "";
example = "Disallow: /foo/";
description = ''
Specification of pages to be ignored by web crawlers. See <link
xlink:href='http://www.robotstxt.org/'/> for details.
'';
};
hostName = mkOption {
type = types.str;
default = "localhost";
description = "Canonical hostname for the server.";
};
serverAliases = mkOption {
type = types.listOf types.str;
default = [];
example = ["www.example.org" "www.example.org:8080" "example.org"];
description = ''
Additional names of virtual hosts served by this virtual host configuration.
'';
};
listen = mkOption {
type = types.listOf (types.submodule (
{
options = {
port = mkOption {
type = types.int;
description = "port to listen on";
};
ip = mkOption {
type = types.str;
default = "*";
description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all.";
};
};
} ));
description = ''
List of { /* ip: "*"; */ port = 80;} to listen on
'';
default = [];
};
enableSSL = mkOption {
type = types.bool;
default = false;
description = "Whether to enable SSL (https) support.";
};
# Note: sslServerCert and sslServerKey can be left empty, but this
# only makes sense for virtual hosts (they will inherit from the
# main server).
sslServerCert = mkOption {
type = types.nullOr types.path;
default = null;
example = "/var/host.cert";
description = "Path to server SSL certificate.";
};
sslServerKey = mkOption {
type = types.path;
example = "/var/host.key";
description = "Path to server SSL certificate key.";
};
sslServerChain = mkOption {
type = types.nullOr types.path;
default = null;
example = "/var/ca.pem";
description = "Path to server SSL chain file.";
};
adminAddr = mkOption ({
type = types.nullOr types.str;
example = "admin@example.org";
description = "E-mail address of the server administrator.";
} // (if forMainServer then {} else {default = null;}));
documentRoot = mkOption {
type = types.nullOr types.path;
default = null;
example = "/data/webserver/docs";
description = ''
The path of Apache's document root directory. If left undefined,
an empty directory in the Nix store will be used as root.
'';
};
servedDirs = mkOption {
type = types.listOf types.attrs;
default = [];
example = [
{ urlPath = "/nix";
dir = "/home/eelco/Dev/nix-homepage";
}
];
description = ''
This option provides a simple way to serve static directories.
'';
};
servedFiles = mkOption {
type = types.listOf types.attrs;
default = [];
example = [
{ urlPath = "/foo/bar.png";
file = "/home/eelco/some-file.png";
}
];
description = ''
This option provides a simple way to serve individual, static files.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
<Directory /home>
Options FollowSymlinks
AllowOverride All
</Directory>
'';
description = ''
These lines go to httpd.conf verbatim. They will go after
directories and directory aliases defined by default.
'';
};
enableUserDir = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable serving <filename>~/public_html</filename> as
<literal>/~<replaceable>username</replaceable></literal>.
'';
};
globalRedirect = mkOption {
type = types.nullOr types.str;
default = null;
example = http://newserver.example.org/;
description = ''
If set, all requests for this host are redirected permanently to
the given URL.
'';
};
logFormat = mkOption {
type = types.str;
default = "common";
example = "combined";
description = ''
Log format for Apache's log files. Possible values are: combined, common, referer, agent.
'';
};
robotsEntries = mkOption {
type = types.lines;
default = "";
example = "Disallow: /foo/";
description = ''
Specification of pages to be ignored by web crawlers. See <link
xlink:href='http://www.robotstxt.org/'/> for details.
'';
};
}

@ -113,7 +113,7 @@ in {
services.httpd = {
enable = true;
adminAddr = "test@example.org";
documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
virtualHosts.localhost.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
};
networking.firewall.allowedTCPPorts = [ 80 ];
}

@ -23,12 +23,14 @@ import ./make-test-python.nix ({ pkgs, ...}: {
};
services.httpd = {
enable = true;
documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
adminAddr = "notme@yourhost.local";
listen = [{
ip = "::1";
port = 8000;
}];
virtualHosts.localhost = {
documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
adminAddr = "notme@yourhost.local";
listen = [{
ip = "::1";
port = 8000;
}];
};
};
};
};

@ -16,7 +16,7 @@ import ../make-test-python.nix ({ pkgs, ... }:
services.httpd = {
enable = true;
documentRoot = ./example;
virtualHosts.localhost.documentRoot = ./example;
adminAddr = "noone@testing.nowhere";
};
};

@ -1,4 +1,4 @@
import ./make-test.nix ({ pkgs, ...} :
import ./make-test.nix ({ pkgs, ...} :
let
@ -7,7 +7,7 @@ let
{ services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
services.httpd.virtualHosts.localhost.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
networking.firewall.allowedTCPPorts = [ 80 ];
};
@ -26,11 +26,11 @@ in
{ services.httpd.enable = true;
services.httpd.adminAddr = "bar@example.org";
services.httpd.extraModules = [ "proxy_balancer" "lbmethod_byrequests" ];
services.httpd.extraConfig =
''
ExtendedStatus on
services.httpd.extraConfig = ''
ExtendedStatus on
'';
services.httpd.virtualHosts.localhost = {
extraConfig = ''
<Location /server-status>
Require all granted
SetHandler server-status
@ -50,6 +50,7 @@ in
# For testing; don't want to wait forever for dead backend servers.
ProxyTimeout 5
'';
};
networking.firewall.allowedTCPPorts = [ 80 ];
};

@ -56,9 +56,11 @@ in
networking.firewall.enable = false;
services.httpd.enable = true;
services.httpd.listen = [{ ip = "*"; port = 9000; }];
services.httpd.adminAddr = "foo@example.org";
services.httpd.documentRoot = "/tmp";
services.httpd.virtualHosts.localhost = {
listen = [{ ip = "*"; port = 9000; }];
adminAddr = "foo@example.org";
documentRoot = "/tmp";
};
};
client2 =