nixos/httpd: limit serving web content to virtual hosts, convert virtualHosts option type from listOf to attrsOf, add ACME integration
This commit is contained in:
@@ -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.
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user