enableRollback = true;
};
- eldiron = { config, pkgs, mylibs, ... }:
+ eldiron = { config, pkgs, mylibs, myconfig, ... }:
with mylibs;
let
mypkgs = pkgs.callPackage ./packages.nix {
{
_module.args = {
mylibs = import ../libs.nix;
+ myconfig = {
+ ips = {
+ main = "176.9.151.89";
+ production = "176.9.151.154";
+ integration = "176.9.151.155";
+ };
+ };
};
imports = [
enable = true;
allowedTCPPorts = [ 22 80 443 9418 ];
};
+ interfaces."eth0".ipv4.addresses = [
+ # 176.9.151.89 declared in nixops -> infra / tools
+ { address = myconfig.ips.production; prefixLength = 32; }
+ { address = myconfig.ips.integration; prefixLength = 32; }
+ ];
};
deployment = {
hetzner = {
#robotUser = "defined in HETZNER_ROBOT_USER";
#robotPass = "defined in HETZNER_ROBOT_PASS";
- mainIPv4 = "176.9.151.89";
+ mainIPv4 = myconfig.ips.main;
partitions = ''
clearpart --all --initlabel --drives=sda,sdb
install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions
install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions/adminer
install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions/mantisbt
- install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions/ttrss
install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions/davical
'';
# FIXME: initial sync
sslServerKey = "/var/lib/acme/${domain}/key.pem";
sslServerChain = "/var/lib/acme/${domain}/fullchain.pem";
logFormat = "combinedVhost";
- listen = [ { ip = "*"; port = 443; } ];
+ listen = [
+ { ip = "176.9.151.89"; port = 443; }
+ ];
};
apacheConfig = config.services.myWebsites.apacheConfig;
in rec {
mypkgs.davical.apache.vhostConf
];
})
- (withConf "eldiron" // {
- hostName = "connexionswing.immae.eu";
- serverAliases = [ "sandetludo.immae.eu" ];
- documentRoot = mypkgs.connexionswing_dev.webRoot;
- extraConfig = builtins.concatStringsSep "\n" [
- mypkgs.connexionswing_dev.apache.vhostConf
- ];
- })
(withConf "connexionswing" // {
hostName = "connexionswing.com";
serverAliases = [ "sandetludo.com" "www.connexionswing.com" "www.sandetludo.com" ];
mypkgs.connexionswing_prod.apache.vhostConf
];
})
- (withConf "eldiron" // {
- hostName = "ludivine.immae.eu";
- documentRoot = mypkgs.ludivinecassal_dev.webRoot;
- extraConfig = builtins.concatStringsSep "\n" [
- mypkgs.ludivinecassal_dev.apache.vhostConf
- ];
- })
(withConf "ludivinecassal" // {
hostName = "ludivinecassal.com";
serverAliases = [ "www.ludivinecassal.com" ];
mypkgs.ludivinecassal_prod.apache.vhostConf
];
})
- (withConf "eldiron" // {
- hostName = "piedsjaloux.immae.eu";
- documentRoot = mypkgs.piedsjaloux_dev.webRoot;
- extraConfig = builtins.concatStringsSep "\n" [
- mypkgs.piedsjaloux_dev.apache.vhostConf
- ];
- })
(withConf "piedsjaloux" // {
hostName = "piedsjaloux.fr";
serverAliases = [ "www.piedsjaloux.fr" ];
mypkgs.piedsjaloux_prod.apache.vhostConf
];
})
- (withConf "eldiron" // {
- hostName = "chloe.immae.eu";
- documentRoot = mypkgs.chloe_dev.webRoot;
- extraConfig = builtins.concatStringsSep "\n" [
- mypkgs.chloe_dev.apache.vhostConf
- ];
- })
(withConf "chloe" // {
hostName = "osteopathe-cc.fr";
serverAliases = [ "www.osteopathe-cc.fr" ];
mypkgs.chloe_prod.apache.vhostConf
];
})
- (withConf "eldiron" // {
- hostName = "dev.aten.pro";
- documentRoot = mypkgs.aten_dev.webRoot;
- extraConfig = builtins.concatStringsSep "\n" [
- mypkgs.aten_dev.apache.vhostConf
- ];
- })
(withConf "aten" // {
hostName = "aten.pro";
serverAliases = [ "www.aten.pro" ];
-{ lib, pkgs, config, mylibs, ... }:
+{ lib, pkgs, config, mylibs, myconfig, ... }:
let
cfg = config.services.myWebsites;
+ makeService = name: cfg: let
+ toVhost = vhostConf: {
+ enableSSL = true;
+ sslServerCert = "/var/lib/acme/${vhostConf.certName}/cert.pem";
+ sslServerKey = "/var/lib/acme/${vhostConf.certName}/key.pem";
+ sslServerChain = "/var/lib/acme/${vhostConf.certName}/fullchain.pem";
+ logFormat = "combinedVhost";
+ listen = [
+ { ip = cfg.ip; port = 443; }
+ ];
+ hostName = builtins.head vhostConf.hosts;
+ serverAliases = builtins.tail vhostConf.hosts or [];
+ documentRoot = vhostConf.root;
+ extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig;
+ };
+ in rec {
+ enable = true;
+ listen = [
+ { ip = cfg.ip; port = 443; }
+ ];
+ stateDir = "/run/httpd_${name}";
+ logPerVirtualHost = true;
+ multiProcessingModule = "worker";
+ adminAddr = "httpd@immae.eu";
+ logFormat = "combinedVhost";
+ extraModules = pkgs.lib.lists.unique (pkgs.lib.lists.flatten cfg.modules);
+ extraConfig = builtins.concatStringsSep "\n" cfg.extraConfig;
+ virtualHosts = pkgs.lib.attrsets.mapAttrsToList (n: v: toVhost v) cfg.vhostConfs;
+ };
+ makeServiceOptions = name: ip: {
+ enable = lib.mkEnableOption "enable websites in ${name}";
+ ip = lib.mkOption {
+ type = lib.types.string;
+ default = ip;
+ description = "${name} ip to listen to";
+ };
+ modules = lib.mkOption {
+ type = lib.types.listOf (lib.types.str);
+ default = [];
+ };
+ extraConfig = lib.mkOption {
+ type = lib.types.listOf (lib.types.lines);
+ default = [];
+ };
+ vhostConfs = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.submodule {
+ options = {
+ certName = lib.mkOption { type = lib.types.string; };
+ hosts = lib.mkOption { type = lib.types.listOf lib.types.string; };
+ root = lib.mkOption { type = lib.types.nullOr lib.types.path; };
+ extraConfig = lib.mkOption { type = lib.types.listOf lib.types.lines; default = []; };
+ };
+ });
+ };
+ };
in
{
imports = [
./websites/aten.nix
./websites/piedsjaloux.nix
./websites/connexionswing.nix
+ # built using:
+ # sed -e "s/services\.httpd/services\.httpdProd/g" .nix-defexpr/channels/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
+ # And removed users / groups
+ ./websites/apache/httpd_prod.nix
+ ./websites/apache/httpd_inte.nix
];
options.services.myWebsites = {
- production = {
- enable = lib.mkEnableOption "enable websites in production";
- };
-
- integration = {
- enable = lib.mkEnableOption "enable websites in integration";
- };
+ production = makeServiceOptions "production" myconfig.ips.production;
+ integration = makeServiceOptions "integration" myconfig.ips.integration;
apacheConfig = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
'';
};
};
+
+ # FIXME: logrotate
+ # FIXME: ipv6
+ services.httpdProd = makeService "production" config.services.myWebsites.production;
+ services.myWebsites.production.modules = pkgs.lib.lists.flatten (pkgs.lib.attrsets.mapAttrsToList (n: v: v.modules or []) cfg.apacheConfig);
+ services.myWebsites.production.extraConfig = (builtins.filter (x: x != null) (pkgs.lib.attrsets.mapAttrsToList (n: v: v.extraConfig or null) cfg.apacheConfig));
+
+ services.httpdInte = makeService "integration" config.services.myWebsites.integration;
+ services.myWebsites.integration.modules = pkgs.lib.lists.flatten (pkgs.lib.attrsets.mapAttrsToList (n: v: v.modules or []) cfg.apacheConfig);
+ services.myWebsites.integration.extraConfig = (builtins.filter (x: x != null) (pkgs.lib.attrsets.mapAttrsToList (n: v: v.extraConfig or null) cfg.apacheConfig));
};
}
--- /dev/null
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ mainCfg = config.services.httpdInte;
+
+ httpd = mainCfg.package.out;
+
+ version24 = !versionOlder httpd.version "2.4";
+
+ httpdConf = mainCfg.configFile;
+
+ php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
+
+ phpMajorVersion = head (splitString "." php.version);
+
+ mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
+
+ defaultListen = cfg: if cfg.enableSSL
+ then [{ip = "*"; port = 443;}]
+ else [{ip = "*"; port = 80;}];
+
+ getListen = cfg:
+ let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen;
+ in if list == []
+ then defaultListen cfg
+ else list;
+
+ listenToString = l: "${l.ip}:${toString l.port}";
+
+ extraModules = attrByPath ["extraModules"] [] mainCfg;
+ extraForeignModules = filter isAttrs extraModules;
+ extraApacheModules = filter isString extraModules;
+
+
+ makeServerInfo = cfg: {
+ # 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));
+
+ # Admin address: inherit from the main server if not specified for
+ # a virtual host.
+ adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
+
+ vhostConfig = cfg;
+ serverConfig = mainCfg;
+ fullConfig = config; # machine config
+ };
+
+
+ allHosts = [mainCfg] ++ mainCfg.virtualHosts;
+
+
+ callSubservices = serverInfo: defs:
+ let f = svc:
+ let
+ svcFunction =
+ if svc ? function then svc.function
+ # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
+ else if svc ? serviceExpression then import (toString svc.serviceExpression)
+ else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
+ config = (evalModules
+ { modules = [ { options = res.options; config = svc.config or svc; } ];
+ check = false;
+ }).config;
+ defaults = {
+ extraConfig = "";
+ extraModules = [];
+ extraModulesPre = [];
+ extraPath = [];
+ extraServerPath = [];
+ globalEnvVars = [];
+ robotsEntries = "";
+ startupScript = "";
+ enablePHP = false;
+ enablePerl = false;
+ phpOptions = "";
+ options = {};
+ documentRoot = null;
+ };
+ res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
+ in res;
+ in map f defs;
+
+
+ # !!! callSubservices is expensive
+ subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
+
+ mainSubservices = subservicesFor mainCfg;
+
+ allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
+
+
+ enableSSL = any (vhost: vhost.enableSSL) allHosts;
+
+
+ # Names of modules from ${httpd}/modules that we want to load.
+ apacheModules =
+ [ # HTTP authentication mechanisms: basic and digest.
+ "auth_basic" "auth_digest"
+
+ # Authentication: is the user who he claims to be?
+ "authn_file" "authn_dbm" "authn_anon"
+ (if version24 then "authn_core" else "authn_alias")
+
+ # Authorization: is the user allowed access?
+ "authz_user" "authz_groupfile" "authz_host"
+
+ # Other modules.
+ "ext_filter" "include" "log_config" "env" "mime_magic"
+ "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
+ "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
+ "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
+ "userdir" "alias" "rewrite" "proxy" "proxy_http"
+ ]
+ ++ optionals version24 [
+ "mpm_${mainCfg.multiProcessingModule}"
+ "authz_core"
+ "unixd"
+ "cache" "cache_disk"
+ "slotmem_shm"
+ "socache_shmcb"
+ # For compatibility with old configurations, the new module mod_access_compat is provided.
+ "access_compat"
+ ]
+ ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
+ ++ optional enableSSL "ssl"
+ ++ extraApacheModules;
+
+
+ allDenied = if version24 then ''
+ Require all denied
+ '' else ''
+ Order deny,allow
+ Deny from all
+ '';
+
+ allGranted = if version24 then ''
+ Require all granted
+ '' else ''
+ Order allow,deny
+ Allow from all
+ '';
+
+
+ loggingConf = (if mainCfg.logFormat != "none" then ''
+ ErrorLog ${mainCfg.logDir}/error_log
+
+ LogLevel notice
+
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+ LogFormat "%{Referer}i -> %U" referer
+ LogFormat "%{User-agent}i" agent
+
+ CustomLog ${mainCfg.logDir}/access_log ${mainCfg.logFormat}
+ '' else ''
+ ErrorLog /dev/null
+ '');
+
+
+ browserHacks = ''
+ BrowserMatch "Mozilla/2" nokeepalive
+ BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+ BrowserMatch "RealPlayer 4\.0" force-response-1.0
+ BrowserMatch "Java/1\.0" force-response-1.0
+ BrowserMatch "JDK/1\.0" force-response-1.0
+ BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+ BrowserMatch "^WebDrive" redirect-carefully
+ BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+ BrowserMatch "^gnome-vfs" redirect-carefully
+ '';
+
+
+ sslConf = ''
+ SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000)
+
+ ${if version24 then "Mutex" else "SSLMutex"} posixsem
+
+ SSLRandomSeed startup builtin
+ SSLRandomSeed connect builtin
+
+ SSLProtocol All -SSLv2 -SSLv3
+ SSLCipherSuite HIGH:!aNULL:!MD5:!EXP
+ SSLHonorCipherOrder on
+ '';
+
+
+ mimeConf = ''
+ TypesConfig ${httpd}/conf/mime.types
+
+ AddType application/x-x509-ca-cert .crt
+ AddType application/x-pkcs7-crl .crl
+ AddType application/x-httpd-php .php .phtml
+
+ <IfModule mod_mime_magic.c>
+ MIMEMagicFile ${httpd}/conf/magic
+ </IfModule>
+ '';
+
+
+ perServerConf = isMainServer: cfg: let
+
+ serverInfo = makeServerInfo cfg;
+
+ subservices = callSubservices serverInfo cfg.extraSubservices;
+
+ maybeDocumentRoot = fold (svc: acc:
+ if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
+ ) null ([ cfg ] ++ subservices);
+
+ documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
+ pkgs.runCommand "empty" {} "mkdir -p $out";
+
+ documentRootConf = ''
+ DocumentRoot "${documentRoot}"
+
+ <Directory "${documentRoot}">
+ Options Indexes FollowSymLinks
+ AllowOverride None
+ ${allGranted}
+ </Directory>
+ '';
+
+ robotsTxt =
+ concatStringsSep "\n" (filter (x: x != "") (
+ # If this is a vhost, the include the entries for the main server as well.
+ (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
+ ++ [cfg.robotsEntries]
+ ++ (map (svc: svc.robotsEntries) subservices)));
+
+ in ''
+ ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
+
+ ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
+
+ ${if cfg.sslServerCert != null then ''
+ SSLCertificateFile ${cfg.sslServerCert}
+ SSLCertificateKeyFile ${cfg.sslServerKey}
+ ${if cfg.sslServerChain != null then ''
+ SSLCertificateChainFile ${cfg.sslServerChain}
+ '' else ""}
+ '' else ""}
+
+ ${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_log-${cfg.hostName}
+ CustomLog ${mainCfg.logDir}/access_log-${cfg.hostName} ${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
+ }
+
+ ${concatMapStrings (svc: svc.extraConfig) subservices}
+
+ ${cfg.extraConfig}
+ '';
+
+
+ confFile = pkgs.writeText "httpd.conf" ''
+
+ ServerRoot ${httpd}
+
+ ${optionalString version24 ''
+ DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+ ''}
+
+ PidFile ${mainCfg.stateDir}/httpd.pid
+
+ ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
+ # mod_cgid requires this.
+ ScriptSock ${mainCfg.stateDir}/cgisock
+ ''}
+
+ <IfModule prefork.c>
+ MaxClients ${toString mainCfg.maxClients}
+ MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
+ </IfModule>
+
+ ${let
+ listen = concatMap getListen allHosts;
+ toStr = listen: "Listen ${listenToString listen}\n";
+ uniqueListen = uniqList {inputList = map toStr listen;};
+ in concatStrings uniqueListen
+ }
+
+ User ${mainCfg.user}
+ Group ${mainCfg.group}
+
+ ${let
+ load = {name, path}: "LoadModule ${name}_module ${path}\n";
+ allModules =
+ concatMap (svc: svc.extraModulesPre) allSubservices
+ ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
+ ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+ ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+ ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+ ++ concatMap (svc: svc.extraModules) allSubservices
+ ++ extraForeignModules;
+ in concatMapStrings load allModules
+ }
+
+ AddHandler type-map var
+
+ <Files ~ "^\.ht">
+ ${allDenied}
+ </Files>
+
+ ${mimeConf}
+ ${loggingConf}
+ ${browserHacks}
+
+ Include ${httpd}/conf/extra/httpd-default.conf
+ Include ${httpd}/conf/extra/httpd-autoindex.conf
+ Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
+ Include ${httpd}/conf/extra/httpd-languages.conf
+
+ ${if enableSSL then sslConf else ""}
+
+ # Fascist default - deny access to everything.
+ <Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ ${allDenied}
+ </Directory>
+
+ # But do allow access to files in the store so that we don't have
+ # to generate <Directory> clauses for every generated file that we
+ # want to serve.
+ <Directory /nix/store>
+ ${allGranted}
+ </Directory>
+
+ # Generate directives for the main server.
+ ${perServerConf true mainCfg}
+
+ # Always enable virtual hosts; it doesn't seem to hurt.
+ ${let
+ listen = concatMap getListen allHosts;
+ uniqueListen = uniqList {inputList = listen;};
+ directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen;
+ in optionalString (!version24) directives
+ }
+
+ ${let
+ makeVirtualHost = vhost: ''
+ <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
+ ${perServerConf false vhost}
+ </VirtualHost>
+ '';
+ in concatMapStrings makeVirtualHost mainCfg.virtualHosts
+ }
+ '';
+
+
+ enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
+
+ enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
+
+
+ # Generate the PHP configuration file. Should probably be factored
+ # out into a separate module.
+ phpIni = pkgs.runCommand "php.ini"
+ { options = concatStringsSep "\n"
+ ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
+ }
+ ''
+ cat ${php}/etc/php.ini > $out
+ echo "$options" >> $out
+ '';
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.httpdInte = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Apache HTTP Server.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.apacheHttpd;
+ defaultText = "pkgs.apacheHttpd";
+ description = ''
+ Overridable attribute of the Apache HTTP Server package to use.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ default = confFile;
+ defaultText = "confFile";
+ example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
+ description = ''
+ Override the configuration file used by Apache. By default,
+ NixOS generates one automatically.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Cnfiguration lines appended to the generated Apache
+ configuration file. Note that this mechanism may not work
+ when <option>configFile</option> is overridden.
+ '';
+ };
+
+ extraModules = mkOption {
+ type = types.listOf types.unspecified;
+ default = [];
+ example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+ description = ''
+ Additional Apache modules to be used. These can be
+ specified as a string in the case of modules distributed
+ with Apache, or as an attribute set specifying the
+ <varname>name</varname> and <varname>path</varname> of the
+ module.
+ '';
+ };
+
+ logPerVirtualHost = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, each virtual host gets its own
+ <filename>access_log</filename> and
+ <filename>error_log</filename>, namely suffixed by the
+ <option>hostName</option> of the virtual host.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "wwwrun";
+ description = ''
+ User account under which httpd runs. The account is created
+ automatically if it doesn't exist.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "wwwrun";
+ description = ''
+ Group under which httpd runs. The account is created
+ automatically if it doesn't exist.
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/httpd";
+ description = ''
+ Directory for Apache's log files. It is created automatically.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/run/httpd";
+ description = ''
+ Directory for Apache's transient runtime state (such as PID
+ files). It is created automatically. Note that the default,
+ <filename>/run/httpd</filename>, is deleted at boot time.
+ '';
+ };
+
+ virtualHosts = mkOption {
+ type = types.listOf (types.submodule (
+ { options = import ./per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ }));
+ default = [];
+ example = [
+ { hostName = "foo";
+ documentRoot = "/data/webroot-foo";
+ }
+ { hostName = "bar";
+ documentRoot = "/data/webroot-bar";
+ }
+ ];
+ description = ''
+ 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.
+ '';
+ };
+
+ enableMellon = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the mod_auth_mellon module.";
+ };
+
+ enablePHP = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the PHP module.";
+ };
+
+ phpPackage = mkOption {
+ type = types.package;
+ default = pkgs.php;
+ defaultText = "pkgs.php";
+ description = ''
+ Overridable attribute of the PHP package to use.
+ '';
+ };
+
+ enablePerl = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Perl module (mod_perl).";
+ };
+
+ phpOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ date.timezone = "CET"
+ '';
+ description =
+ "Options appended to the PHP configuration file <filename>php.ini</filename>.";
+ };
+
+ multiProcessingModule = mkOption {
+ type = types.str;
+ default = "prefork";
+ example = "worker";
+ description =
+ ''
+ Multi-processing module to be used by Apache. Available
+ modules are <literal>prefork</literal> (the default;
+ handles each request in a separate child process),
+ <literal>worker</literal> (hybrid approach that starts a
+ number of child processes each running a number of
+ threads) and <literal>event</literal> (a recent variant of
+ <literal>worker</literal> that handles persistent
+ connections more efficiently).
+ '';
+ };
+
+ maxClients = mkOption {
+ type = types.int;
+ default = 150;
+ example = 8;
+ description = "Maximum number of httpd processes (prefork)";
+ };
+
+ maxRequestsPerChild = mkOption {
+ type = types.int;
+ default = 0;
+ example = 500;
+ description =
+ "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
+ };
+ }
+
+ # Include the options shared between the main server and virtual hosts.
+ // (import ./per-server-options.nix {
+ inherit lib;
+ forMainServer = true;
+ });
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.httpdInte.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."; }
+ ];
+
+ warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts);
+
+ environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+
+ services.httpdInte.phpOptions =
+ ''
+ ; Needed for PHP's mail() function.
+ sendmail_path = sendmail -t -i
+ '' + optionalString (!isNull config.time.timeZone) ''
+
+ ; Apparently PHP doesn't use $TZ.
+ date.timezone = "${config.time.timeZone}"
+ '';
+
+ systemd.services.httpdInte =
+ { description = "Apache HTTPD";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "keys.target" ];
+ after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
+
+ path =
+ [ httpd pkgs.coreutils pkgs.gnugrep ]
+ ++ # Needed for PHP's mail() function. !!! Probably the
+ # ssmtp module should export the path to sendmail in
+ # some way.
+ optional config.networking.defaultMailServer.directDelivery pkgs.ssmtp
+ ++ concatMap (svc: svc.extraServerPath) allSubservices;
+
+ environment =
+ optionalAttrs enablePHP { PHPRC = phpIni; }
+ // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
+ // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+
+ preStart =
+ ''
+ mkdir -m 0750 -p ${mainCfg.stateDir}
+ [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
+ ${optionalString version24 ''
+ mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
+ [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
+ ''}
+ mkdir -m 0700 -p ${mainCfg.logDir}
+
+ # Get rid of old semaphores. These tend to accumulate across
+ # server restarts, eventually preventing it from restarting
+ # successfully.
+ for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
+ ${pkgs.utillinux}/bin/ipcrm -s $i
+ done
+
+ # Run the startup hooks for the subservices.
+ for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
+ echo Running Apache startup hook $i...
+ $i
+ done
+ '';
+
+ serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
+ serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
+ serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+ serviceConfig.Type = "forking";
+ serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+ serviceConfig.Restart = "always";
+ serviceConfig.RestartSec = "5s";
+ };
+
+ };
+}
--- /dev/null
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ mainCfg = config.services.httpdProd;
+
+ httpd = mainCfg.package.out;
+
+ version24 = !versionOlder httpd.version "2.4";
+
+ httpdConf = mainCfg.configFile;
+
+ php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
+
+ phpMajorVersion = head (splitString "." php.version);
+
+ mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
+
+ defaultListen = cfg: if cfg.enableSSL
+ then [{ip = "*"; port = 443;}]
+ else [{ip = "*"; port = 80;}];
+
+ getListen = cfg:
+ let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen;
+ in if list == []
+ then defaultListen cfg
+ else list;
+
+ listenToString = l: "${l.ip}:${toString l.port}";
+
+ extraModules = attrByPath ["extraModules"] [] mainCfg;
+ extraForeignModules = filter isAttrs extraModules;
+ extraApacheModules = filter isString extraModules;
+
+
+ makeServerInfo = cfg: {
+ # 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));
+
+ # Admin address: inherit from the main server if not specified for
+ # a virtual host.
+ adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
+
+ vhostConfig = cfg;
+ serverConfig = mainCfg;
+ fullConfig = config; # machine config
+ };
+
+
+ allHosts = [mainCfg] ++ mainCfg.virtualHosts;
+
+
+ callSubservices = serverInfo: defs:
+ let f = svc:
+ let
+ svcFunction =
+ if svc ? function then svc.function
+ # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
+ else if svc ? serviceExpression then import (toString svc.serviceExpression)
+ else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
+ config = (evalModules
+ { modules = [ { options = res.options; config = svc.config or svc; } ];
+ check = false;
+ }).config;
+ defaults = {
+ extraConfig = "";
+ extraModules = [];
+ extraModulesPre = [];
+ extraPath = [];
+ extraServerPath = [];
+ globalEnvVars = [];
+ robotsEntries = "";
+ startupScript = "";
+ enablePHP = false;
+ enablePerl = false;
+ phpOptions = "";
+ options = {};
+ documentRoot = null;
+ };
+ res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
+ in res;
+ in map f defs;
+
+
+ # !!! callSubservices is expensive
+ subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
+
+ mainSubservices = subservicesFor mainCfg;
+
+ allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
+
+
+ enableSSL = any (vhost: vhost.enableSSL) allHosts;
+
+
+ # Names of modules from ${httpd}/modules that we want to load.
+ apacheModules =
+ [ # HTTP authentication mechanisms: basic and digest.
+ "auth_basic" "auth_digest"
+
+ # Authentication: is the user who he claims to be?
+ "authn_file" "authn_dbm" "authn_anon"
+ (if version24 then "authn_core" else "authn_alias")
+
+ # Authorization: is the user allowed access?
+ "authz_user" "authz_groupfile" "authz_host"
+
+ # Other modules.
+ "ext_filter" "include" "log_config" "env" "mime_magic"
+ "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
+ "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
+ "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
+ "userdir" "alias" "rewrite" "proxy" "proxy_http"
+ ]
+ ++ optionals version24 [
+ "mpm_${mainCfg.multiProcessingModule}"
+ "authz_core"
+ "unixd"
+ "cache" "cache_disk"
+ "slotmem_shm"
+ "socache_shmcb"
+ # For compatibility with old configurations, the new module mod_access_compat is provided.
+ "access_compat"
+ ]
+ ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
+ ++ optional enableSSL "ssl"
+ ++ extraApacheModules;
+
+
+ allDenied = if version24 then ''
+ Require all denied
+ '' else ''
+ Order deny,allow
+ Deny from all
+ '';
+
+ allGranted = if version24 then ''
+ Require all granted
+ '' else ''
+ Order allow,deny
+ Allow from all
+ '';
+
+
+ loggingConf = (if mainCfg.logFormat != "none" then ''
+ ErrorLog ${mainCfg.logDir}/error_log
+
+ LogLevel notice
+
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+ LogFormat "%{Referer}i -> %U" referer
+ LogFormat "%{User-agent}i" agent
+
+ CustomLog ${mainCfg.logDir}/access_log ${mainCfg.logFormat}
+ '' else ''
+ ErrorLog /dev/null
+ '');
+
+
+ browserHacks = ''
+ BrowserMatch "Mozilla/2" nokeepalive
+ BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+ BrowserMatch "RealPlayer 4\.0" force-response-1.0
+ BrowserMatch "Java/1\.0" force-response-1.0
+ BrowserMatch "JDK/1\.0" force-response-1.0
+ BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+ BrowserMatch "^WebDrive" redirect-carefully
+ BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+ BrowserMatch "^gnome-vfs" redirect-carefully
+ '';
+
+
+ sslConf = ''
+ SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000)
+
+ ${if version24 then "Mutex" else "SSLMutex"} posixsem
+
+ SSLRandomSeed startup builtin
+ SSLRandomSeed connect builtin
+
+ SSLProtocol All -SSLv2 -SSLv3
+ SSLCipherSuite HIGH:!aNULL:!MD5:!EXP
+ SSLHonorCipherOrder on
+ '';
+
+
+ mimeConf = ''
+ TypesConfig ${httpd}/conf/mime.types
+
+ AddType application/x-x509-ca-cert .crt
+ AddType application/x-pkcs7-crl .crl
+ AddType application/x-httpd-php .php .phtml
+
+ <IfModule mod_mime_magic.c>
+ MIMEMagicFile ${httpd}/conf/magic
+ </IfModule>
+ '';
+
+
+ perServerConf = isMainServer: cfg: let
+
+ serverInfo = makeServerInfo cfg;
+
+ subservices = callSubservices serverInfo cfg.extraSubservices;
+
+ maybeDocumentRoot = fold (svc: acc:
+ if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
+ ) null ([ cfg ] ++ subservices);
+
+ documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
+ pkgs.runCommand "empty" {} "mkdir -p $out";
+
+ documentRootConf = ''
+ DocumentRoot "${documentRoot}"
+
+ <Directory "${documentRoot}">
+ Options Indexes FollowSymLinks
+ AllowOverride None
+ ${allGranted}
+ </Directory>
+ '';
+
+ robotsTxt =
+ concatStringsSep "\n" (filter (x: x != "") (
+ # If this is a vhost, the include the entries for the main server as well.
+ (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
+ ++ [cfg.robotsEntries]
+ ++ (map (svc: svc.robotsEntries) subservices)));
+
+ in ''
+ ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
+
+ ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
+
+ ${if cfg.sslServerCert != null then ''
+ SSLCertificateFile ${cfg.sslServerCert}
+ SSLCertificateKeyFile ${cfg.sslServerKey}
+ ${if cfg.sslServerChain != null then ''
+ SSLCertificateChainFile ${cfg.sslServerChain}
+ '' else ""}
+ '' else ""}
+
+ ${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_log-${cfg.hostName}
+ CustomLog ${mainCfg.logDir}/access_log-${cfg.hostName} ${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
+ }
+
+ ${concatMapStrings (svc: svc.extraConfig) subservices}
+
+ ${cfg.extraConfig}
+ '';
+
+
+ confFile = pkgs.writeText "httpd.conf" ''
+
+ ServerRoot ${httpd}
+
+ ${optionalString version24 ''
+ DefaultRuntimeDir ${mainCfg.stateDir}/runtime
+ ''}
+
+ PidFile ${mainCfg.stateDir}/httpd.pid
+
+ ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
+ # mod_cgid requires this.
+ ScriptSock ${mainCfg.stateDir}/cgisock
+ ''}
+
+ <IfModule prefork.c>
+ MaxClients ${toString mainCfg.maxClients}
+ MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
+ </IfModule>
+
+ ${let
+ listen = concatMap getListen allHosts;
+ toStr = listen: "Listen ${listenToString listen}\n";
+ uniqueListen = uniqList {inputList = map toStr listen;};
+ in concatStrings uniqueListen
+ }
+
+ User ${mainCfg.user}
+ Group ${mainCfg.group}
+
+ ${let
+ load = {name, path}: "LoadModule ${name}_module ${path}\n";
+ allModules =
+ concatMap (svc: svc.extraModulesPre) allSubservices
+ ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
+ ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+ ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+ ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+ ++ concatMap (svc: svc.extraModules) allSubservices
+ ++ extraForeignModules;
+ in concatMapStrings load allModules
+ }
+
+ AddHandler type-map var
+
+ <Files ~ "^\.ht">
+ ${allDenied}
+ </Files>
+
+ ${mimeConf}
+ ${loggingConf}
+ ${browserHacks}
+
+ Include ${httpd}/conf/extra/httpd-default.conf
+ Include ${httpd}/conf/extra/httpd-autoindex.conf
+ Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
+ Include ${httpd}/conf/extra/httpd-languages.conf
+
+ ${if enableSSL then sslConf else ""}
+
+ # Fascist default - deny access to everything.
+ <Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ ${allDenied}
+ </Directory>
+
+ # But do allow access to files in the store so that we don't have
+ # to generate <Directory> clauses for every generated file that we
+ # want to serve.
+ <Directory /nix/store>
+ ${allGranted}
+ </Directory>
+
+ # Generate directives for the main server.
+ ${perServerConf true mainCfg}
+
+ # Always enable virtual hosts; it doesn't seem to hurt.
+ ${let
+ listen = concatMap getListen allHosts;
+ uniqueListen = uniqList {inputList = listen;};
+ directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen;
+ in optionalString (!version24) directives
+ }
+
+ ${let
+ makeVirtualHost = vhost: ''
+ <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
+ ${perServerConf false vhost}
+ </VirtualHost>
+ '';
+ in concatMapStrings makeVirtualHost mainCfg.virtualHosts
+ }
+ '';
+
+
+ enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
+
+ enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
+
+
+ # Generate the PHP configuration file. Should probably be factored
+ # out into a separate module.
+ phpIni = pkgs.runCommand "php.ini"
+ { options = concatStringsSep "\n"
+ ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
+ }
+ ''
+ cat ${php}/etc/php.ini > $out
+ echo "$options" >> $out
+ '';
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.httpdProd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Apache HTTP Server.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.apacheHttpd;
+ defaultText = "pkgs.apacheHttpd";
+ description = ''
+ Overridable attribute of the Apache HTTP Server package to use.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ default = confFile;
+ defaultText = "confFile";
+ example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
+ description = ''
+ Override the configuration file used by Apache. By default,
+ NixOS generates one automatically.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Cnfiguration lines appended to the generated Apache
+ configuration file. Note that this mechanism may not work
+ when <option>configFile</option> is overridden.
+ '';
+ };
+
+ extraModules = mkOption {
+ type = types.listOf types.unspecified;
+ default = [];
+ example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+ description = ''
+ Additional Apache modules to be used. These can be
+ specified as a string in the case of modules distributed
+ with Apache, or as an attribute set specifying the
+ <varname>name</varname> and <varname>path</varname> of the
+ module.
+ '';
+ };
+
+ logPerVirtualHost = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If enabled, each virtual host gets its own
+ <filename>access_log</filename> and
+ <filename>error_log</filename>, namely suffixed by the
+ <option>hostName</option> of the virtual host.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "wwwrun";
+ description = ''
+ User account under which httpd runs. The account is created
+ automatically if it doesn't exist.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "wwwrun";
+ description = ''
+ Group under which httpd runs. The account is created
+ automatically if it doesn't exist.
+ '';
+ };
+
+ logDir = mkOption {
+ type = types.path;
+ default = "/var/log/httpd";
+ description = ''
+ Directory for Apache's log files. It is created automatically.
+ '';
+ };
+
+ stateDir = mkOption {
+ type = types.path;
+ default = "/run/httpd";
+ description = ''
+ Directory for Apache's transient runtime state (such as PID
+ files). It is created automatically. Note that the default,
+ <filename>/run/httpd</filename>, is deleted at boot time.
+ '';
+ };
+
+ virtualHosts = mkOption {
+ type = types.listOf (types.submodule (
+ { options = import ./per-server-options.nix {
+ inherit lib;
+ forMainServer = false;
+ };
+ }));
+ default = [];
+ example = [
+ { hostName = "foo";
+ documentRoot = "/data/webroot-foo";
+ }
+ { hostName = "bar";
+ documentRoot = "/data/webroot-bar";
+ }
+ ];
+ description = ''
+ 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.
+ '';
+ };
+
+ enableMellon = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the mod_auth_mellon module.";
+ };
+
+ enablePHP = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the PHP module.";
+ };
+
+ phpPackage = mkOption {
+ type = types.package;
+ default = pkgs.php;
+ defaultText = "pkgs.php";
+ description = ''
+ Overridable attribute of the PHP package to use.
+ '';
+ };
+
+ enablePerl = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Perl module (mod_perl).";
+ };
+
+ phpOptions = mkOption {
+ type = types.lines;
+ default = "";
+ example =
+ ''
+ date.timezone = "CET"
+ '';
+ description =
+ "Options appended to the PHP configuration file <filename>php.ini</filename>.";
+ };
+
+ multiProcessingModule = mkOption {
+ type = types.str;
+ default = "prefork";
+ example = "worker";
+ description =
+ ''
+ Multi-processing module to be used by Apache. Available
+ modules are <literal>prefork</literal> (the default;
+ handles each request in a separate child process),
+ <literal>worker</literal> (hybrid approach that starts a
+ number of child processes each running a number of
+ threads) and <literal>event</literal> (a recent variant of
+ <literal>worker</literal> that handles persistent
+ connections more efficiently).
+ '';
+ };
+
+ maxClients = mkOption {
+ type = types.int;
+ default = 150;
+ example = 8;
+ description = "Maximum number of httpd processes (prefork)";
+ };
+
+ maxRequestsPerChild = mkOption {
+ type = types.int;
+ default = 0;
+ example = 500;
+ description =
+ "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
+ };
+ }
+
+ # Include the options shared between the main server and virtual hosts.
+ // (import ./per-server-options.nix {
+ inherit lib;
+ forMainServer = true;
+ });
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf config.services.httpdProd.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."; }
+ ];
+
+ warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts);
+
+ environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+
+ services.httpdProd.phpOptions =
+ ''
+ ; Needed for PHP's mail() function.
+ sendmail_path = sendmail -t -i
+ '' + optionalString (!isNull config.time.timeZone) ''
+
+ ; Apparently PHP doesn't use $TZ.
+ date.timezone = "${config.time.timeZone}"
+ '';
+
+ systemd.services.httpdProd =
+ { description = "Apache HTTPD";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "keys.target" ];
+ after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
+
+ path =
+ [ httpd pkgs.coreutils pkgs.gnugrep ]
+ ++ # Needed for PHP's mail() function. !!! Probably the
+ # ssmtp module should export the path to sendmail in
+ # some way.
+ optional config.networking.defaultMailServer.directDelivery pkgs.ssmtp
+ ++ concatMap (svc: svc.extraServerPath) allSubservices;
+
+ environment =
+ optionalAttrs enablePHP { PHPRC = phpIni; }
+ // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
+ // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+
+ preStart =
+ ''
+ mkdir -m 0750 -p ${mainCfg.stateDir}
+ [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
+ ${optionalString version24 ''
+ mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
+ [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
+ ''}
+ mkdir -m 0700 -p ${mainCfg.logDir}
+
+ # Get rid of old semaphores. These tend to accumulate across
+ # server restarts, eventually preventing it from restarting
+ # successfully.
+ for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
+ ${pkgs.utillinux}/bin/ipcrm -s $i
+ done
+
+ # Run the startup hooks for the subservices.
+ for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
+ echo Running Apache startup hook $i...
+ $i
+ done
+ '';
+
+ serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
+ serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
+ serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+ serviceConfig.Type = "forking";
+ serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+ serviceConfig.Restart = "always";
+ serviceConfig.RestartSec = "5s";
+ };
+
+ };
+}
--- /dev/null
+# 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;
+
+{
+
+ 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.
+ '';
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Port for the server. Option will be removed, use <option>listen</option> instead.
+ '';
+ };
+
+ listen = mkOption {
+ type = types.listOf (types.submodule (
+ {
+ options = {
+ port = mkOption {
+ type = types.int;
+ description = "port to listen on";
+ };
+ ip = mkOption {
+ type = types.string;
+ 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.
+ '';
+ };
+
+ extraSubservices = mkOption {
+ type = types.listOf types.unspecified;
+ default = [];
+ description = "Extra subservices to enable in the webserver.";
+ };
+
+ 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.
+ '';
+ };
+
+}
services.phpfpm.poolConfigs.aten_prod = aten_prod.phpFpm.pool;
system.activationScripts.aten_prod = aten_prod.activationScript;
services.myWebsites.apacheConfig.aten_prod.modules = aten_prod.apache.modules;
+ services.myWebsites.production.modules = aten_prod.apache.modules;
+ services.myWebsites.production.vhostConfs.aten = {
+ certName = "aten";
+ hosts = [ "aten.pro" "www.aten.pro" ];
+ root = aten_prod.webRoot;
+ extraConfig = [ aten_prod.apache.vhostConf ];
+ };
})
(lib.mkIf cfg.integration.enable {
security.acme.certs."eldiron".extraDomains."dev.aten.pro" = null;
services.phpfpm.poolConfigs.aten_dev = aten_dev.phpFpm.pool;
system.activationScripts.aten_dev = aten_dev.activationScript;
- services.myWebsites.apacheConfig.aten_dev.modules = aten_dev.apache.modules;
+ services.myWebsites.integration.modules = aten_dev.apache.modules;
+ services.myWebsites.integration.vhostConfs.aten = {
+ certName = "eldiron";
+ hosts = [ "dev.aten.pro" ];
+ root = aten_dev.webRoot;
+ extraConfig = [ aten_dev.apache.vhostConf ];
+ };
})
];
}
services.phpfpm.poolConfigs.chloe_prod = chloe_prod.phpFpm.pool;
system.activationScripts.chloe_prod = chloe_prod.activationScript;
- services.myWebsites.apacheConfig.chloe_prod.modules = chloe_prod.apache.modules;
+ services.myWebsites.production.modules = chloe_prod.apache.modules;
+ services.myWebsites.production.vhostConfs.chloe = {
+ certName = "chloe";
+ hosts = ["osteopathe-cc.fr" "www.osteopathe-cc.fr" ];
+ root = chloe_prod.webRoot;
+ extraConfig = [ chloe_prod.apache.vhostConf ];
+ };
})
(lib.mkIf cfg.integration.enable {
security.acme.certs."eldiron".extraDomains."chloe.immae.eu" = null;
services.phpfpm.poolConfigs.chloe_dev = chloe_dev.phpFpm.pool;
system.activationScripts.chloe_dev = chloe_dev.activationScript;
- services.myWebsites.apacheConfig.chloe_dev.modules = chloe_dev.apache.modules;
+ services.myWebsites.integration.modules = chloe_dev.apache.modules;
+ services.myWebsites.integration.vhostConfs.chloe = {
+ certName = "eldiron";
+ hosts = ["chloe.immae.eu" ];
+ root = chloe_dev.webRoot;
+ extraConfig = [ chloe_dev.apache.vhostConf ];
+ };
})
];
}
services.phpfpm.poolConfigs.connexionswing_prod = connexionswing_prod.phpFpm.pool;
system.activationScripts.connexionswing_prod = connexionswing_prod.activationScript;
- services.myWebsites.apacheConfig.connexionswing_prod.modules = connexionswing_prod.apache.modules;
+ services.myWebsites.production.modules = connexionswing_prod.apache.modules;
+ services.myWebsites.production.vhostConfs.connexionswing = {
+ certName = "connexionswing";
+ hosts = ["connexionswing.com" "sandetludo.com" "www.connexionswing.com" "www.sandetludo.com" ];
+ root = connexionswing_prod.webRoot;
+ extraConfig = [ connexionswing_prod.apache.vhostConf ];
+ };
})
(lib.mkIf cfg.integration.enable {
security.acme.certs."eldiron".extraDomains."sandetludo.immae.eu" = null;
security.acme.certs."eldiron".extraDomains."connexionswing.immae.eu" = null;
services.phpfpm.poolConfigs.connexionswing_dev = connexionswing_dev.phpFpm.pool;
system.activationScripts.connexionswing_dev = connexionswing_dev.activationScript;
- services.myWebsites.apacheConfig.connexionswing_dev.modules = connexionswing_dev.apache.modules;
+ services.myWebsites.integration.modules = connexionswing_dev.apache.modules;
+ services.myWebsites.integration.vhostConfs.connexionswing = {
+ certName = "eldiron";
+ hosts = ["connexionswing.immae.eu" "sandetludo.immae.eu" ];
+ root = connexionswing_dev.webRoot;
+ extraConfig = [ connexionswing_dev.apache.vhostConf ];
+ };
})
];
}
services.phpfpm.poolConfigs.ludivinecassal_prod = ludivinecassal_prod.phpFpm.pool;
system.activationScripts.ludivinecassal_prod = ludivinecassal_prod.activationScript;
- services.myWebsites.apacheConfig.ludivinecassal_prod.modules = ludivinecassal_prod.apache.modules;
+ services.myWebsites.production.modules = ludivinecassal_prod.apache.modules;
+ services.myWebsites.production.vhostConfs.ludivine = {
+ certName = "ludivinecassal";
+ hosts = ["ludivinecassal.com" "www.ludivinecassal.com" ];
+ root = ludivinecassal_prod.webRoot;
+ extraConfig = [ ludivinecassal_prod.apache.vhostConf ];
+ };
})
(lib.mkIf cfg.integration.enable {
security.acme.certs."eldiron".extraDomains."ludivine.immae.eu" = null;
services.phpfpm.poolConfigs.ludivinecassal_dev = ludivinecassal_dev.phpFpm.pool;
system.activationScripts.ludivinecassal_dev = ludivinecassal_dev.activationScript;
services.myWebsites.apacheConfig.ludivinecassal_dev.modules = ludivinecassal_dev.apache.modules;
+ services.myWebsites.integration.modules = ludivinecassal_dev.apache.modules;
+ services.myWebsites.integration.vhostConfs.ludivine = {
+ certName = "eldiron";
+ hosts = [ "ludivine.immae.eu" ];
+ root = ludivinecassal_dev.webRoot;
+ extraConfig = [ ludivinecassal_dev.apache.vhostConf ];
+ };
})
];
}
services.phpfpm.poolConfigs.piedsjaloux_prod = piedsjaloux_prod.phpFpm.pool;
system.activationScripts.piedsjaloux_prod = piedsjaloux_prod.activationScript;
- services.myWebsites.apacheConfig.piedsjaloux_prod.modules = piedsjaloux_prod.apache.modules;
+ services.myWebsites.production.modules = piedsjaloux_prod.apache.modules;
+ services.myWebsites.production.vhostConfs.piedsjaloux = {
+ certName = "piedsjaloux";
+ hosts = [ "piedsjaloux.fr" "www.piedsjaloux.fr" ];
+ root = piedsjaloux_prod.webRoot;
+ extraConfig = [ piedsjaloux_prod.apache.vhostConf ];
+ };
})
(lib.mkIf cfg.integration.enable {
security.acme.certs."eldiron".extraDomains."piedsjaloux.immae.eu" = null;
services.phpfpm.poolConfigs.piedsjaloux_dev = piedsjaloux_dev.phpFpm.pool;
system.activationScripts.piedsjaloux_dev = piedsjaloux_dev.activationScript;
- services.myWebsites.apacheConfig.piedsjaloux_dev.modules = piedsjaloux_dev.apache.modules;
+ services.myWebsites.integration.modules = piedsjaloux_dev.apache.modules;
+ services.myWebsites.integration.vhostConfs.piedsjaloux = {
+ certName = "eldiron";
+ hosts = [ "piedsjaloux.immae.eu" ];
+ root = piedsjaloux_dev.webRoot;
+ extraConfig = [ piedsjaloux_dev.apache.vhostConf ];
+ };
})
];
}