]> git.immae.eu Git - perso/Immae/Config/Nix.git/commitdiff
Add http configuration to modules and separate production from
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 10 Jan 2019 19:56:44 +0000 (20:56 +0100)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 10 Jan 2019 20:02:06 +0000 (21:02 +0100)
integration

virtual/eldiron.nix
virtual/modules/websites.nix
virtual/modules/websites/apache/httpd_inte.nix [new file with mode: 0644]
virtual/modules/websites/apache/httpd_prod.nix [new file with mode: 0644]
virtual/modules/websites/apache/per-server-options.nix [new file with mode: 0644]
virtual/modules/websites/aten.nix
virtual/modules/websites/chloe.nix
virtual/modules/websites/connexionswing.nix
virtual/modules/websites/ludivine.nix
virtual/modules/websites/piedsjaloux.nix

index efaa0686aa899e3b8800b77c8ce80c8ae4d7866e..a1e69091decd65cc158d0fcadf7f22819a1b8691 100644 (file)
@@ -4,7 +4,7 @@
     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 = {
@@ -54,7 +66,7 @@
       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" ];
index 62f45d9a016e8b69e268cf3a8912f1987f6e0625..cbd7de07926b4083694ba690e2c36c86774d653b 100644 (file)
@@ -1,6 +1,61 @@
-{ 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 = [
@@ -9,16 +64,16 @@ in
     ./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 {
@@ -111,5 +166,15 @@ in
         '';
       };
     };
+
+    # 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));
   };
 }
diff --git a/virtual/modules/websites/apache/httpd_inte.nix b/virtual/modules/websites/apache/httpd_inte.nix
new file mode 100644 (file)
index 0000000..83d8ab8
--- /dev/null
@@ -0,0 +1,722 @@
+{ 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";
+      };
+
+  };
+}
diff --git a/virtual/modules/websites/apache/httpd_prod.nix b/virtual/modules/websites/apache/httpd_prod.nix
new file mode 100644 (file)
index 0000000..576a305
--- /dev/null
@@ -0,0 +1,722 @@
+{ 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";
+      };
+
+  };
+}
diff --git a/virtual/modules/websites/apache/per-server-options.nix b/virtual/modules/websites/apache/per-server-options.nix
new file mode 100644 (file)
index 0000000..4bbd041
--- /dev/null
@@ -0,0 +1,188 @@
+# 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.
+    '';
+  };
+
+}
index 1a65389ca564377eaf96e6bd4e124416ed3bb5be..53db03a623061075540361ef65476f06e05cfee5 100644 (file)
@@ -27,12 +27,25 @@ in {
       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 ];
+      };
     })
   ];
 }
index d54c42d69f04d13c2c87601bafcd72666399c177..67e8e1fb3846df1301ef1b65f8160e9d288f9b5f 100644 (file)
@@ -26,13 +26,25 @@ in {
 
       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 ];
+      };
     })
   ];
 }
index 8bf63a8cf4e04437a240d2d271a520f7ab5145af..dcc726414bdadf325c3730aa321db88f38142b86 100644 (file)
@@ -28,14 +28,26 @@ in {
 
       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 ];
+      };
     })
   ];
 }
index f06e41a63bf6ee07d9fff2d9d3f8abdddea8070c..6eb98e7a350242c28771cac5b72523e93194c22a 100644 (file)
@@ -26,7 +26,13 @@ in {
 
       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;
@@ -34,6 +40,13 @@ in {
       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 ];
+      };
     })
   ];
 }
index 285fd18d11c5f55c4d1b1c2747901f01ab05d5d3..46161c7e8de8cc4605b13d58ec9c6e00380ab7ff 100644 (file)
@@ -26,13 +26,25 @@ in {
 
       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 ];
+      };
     })
   ];
 }