about summary refs log tree commit diff
path: root/nixos/modules/services/web-servers
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
commit5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010 (patch)
treea6c0f605be6de3f372ae69905b331f9f75452da7 /nixos/modules/services/web-servers
parent6070bc016bd2fd945b04347e25cfd3738622d2ac (diff)
downloadnixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.gz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.bz2
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.lz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.xz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.zst
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.zip
Move all of NixOS to nixos/ in preparation of the repository merge
Diffstat (limited to 'nixos/modules/services/web-servers')
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix662
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mediawiki.nix303
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mercurial.nix75
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/per-server-options.nix146
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix95
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/trac.nix121
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/zabbix.nix82
-rw-r--r--nixos/modules/services/web-servers/jboss/builder.sh72
-rw-r--r--nixos/modules/services/web-servers/jboss/default.nix83
-rw-r--r--nixos/modules/services/web-servers/lighttpd/cgit.nix65
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix178
-rw-r--r--nixos/modules/services/web-servers/lighttpd/gitweb.nix67
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix88
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix344
-rw-r--r--nixos/modules/services/web-servers/varnish/default.nix63
-rw-r--r--nixos/modules/services/web-servers/zope2.nix249
16 files changed, 2693 insertions, 0 deletions
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
new file mode 100644
index 000000000000..e2cfc5cdb265
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -0,0 +1,662 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  mainCfg = config.services.httpd;
+
+  httpd = mainCfg.package;
+
+  version24 = !versionOlder httpd.version "2.4";
+
+  httpdConf = mainCfg.configFile;
+
+  php = pkgs.php.override { apacheHttpd = httpd; };
+
+  getPort = cfg: if cfg.port != 0 then cfg.port else if cfg.enableSSL then 443 else 80;
+
+  extraModules = attrByPath ["extraModules"] [] mainCfg;
+  extraForeignModules = filter builtins.isAttrs extraModules;
+  extraApacheModules = filter (x: !(builtins.isAttrs x)) extraModules; # I'd prefer using builtins.isString here, but doesn't exist yet
+
+
+  makeServerInfo = cfg: {
+    # Canonical name must not include a trailing slash.
+    canonicalName =
+      (if cfg.enableSSL then "https" else "http") + "://" +
+      cfg.hostName +
+      (if getPort cfg != (if cfg.enableSSL then 443 else 80) then ":${toString (getPort cfg)}" else "");
+
+    # Admin address: inherit from the main server if not specified for
+    # a virtual host.
+    adminAddr = if cfg.adminAddr != "" then cfg.adminAddr else mainCfg.adminAddr;
+
+    vhostConfig = cfg;
+    serverConfig = mainCfg;
+    fullConfig = config; # machine config
+  };
+
+
+  vhostOptions = import ./per-server-options.nix {
+    inherit mkOption;
+    forMainServer = false;
+  };
+
+  vhosts = let
+    makeVirtualHost = cfgIn:
+      let
+        # Fill in defaults for missing options.
+        cfg = addDefaultOptionValues vhostOptions cfgIn;
+      in cfg;
+    in map makeVirtualHost mainCfg.virtualHosts;
+
+
+  allHosts = [mainCfg] ++ vhosts;
+
+
+  callSubservices = serverInfo: defs:
+    let f = svc:
+      let
+        svcFunction =
+          if svc ? function then svc.function
+          else import "${./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix";
+        config = addDefaultOptionValues res.options
+          (if svc ? config then svc.config else svc);
+        defaults = {
+          extraConfig = "";
+          extraModules = [];
+          extraModulesPre = [];
+          extraPath = [];
+          extraServerPath = [];
+          globalEnvVars = [];
+          robotsEntries = "";
+          startupScript = "";
+          enablePHP = false;
+          phpOptions = "";
+          options = {};
+        };
+        res = defaults // svcFunction { inherit config 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 vhosts;
+
+
+  # !!! should be in lib
+  writeTextInDir = name: text:
+    pkgs.runCommand name {inherit text;} "ensureDir $out; echo -n \"$text\" > $out/$name";
+
+
+  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"
+    ]
+    ++ (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 = ''
+    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}
+  '';
+
+
+  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 shm:${mainCfg.stateDir}/ssl_scache(512000)
+
+    SSLMutex posixsem
+
+    SSLRandomSeed startup builtin
+    SSLRandomSeed connect builtin
+  '';
+
+
+  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>
+
+    AddEncoding x-compress Z
+    AddEncoding x-gzip gz tgz
+  '';
+
+
+  perServerConf = isMainServer: cfg: let
+
+    serverInfo = makeServerInfo cfg;
+
+    subservices = callSubservices serverInfo cfg.extraSubservices;
+
+    documentRoot = if cfg.documentRoot != null then cfg.documentRoot else
+      pkgs.runCommand "empty" {} "ensureDir $out";
+
+    documentRootConf = ''
+      DocumentRoot "${documentRoot}"
+
+      <Directory "${documentRoot}">
+          Options Indexes FollowSymLinks
+          AllowOverride None
+          ${allGranted}
+      </Directory>
+    '';
+
+    robotsTxt = pkgs.writeText "robots.txt" ''
+      ${# If this is a vhost, the include the entries for the main server as well.
+        if isMainServer then ""
+        else concatMapStrings (svc: svc.robotsEntries) mainSubservices}
+      ${concatMapStrings (svc: svc.robotsEntries) subservices}
+    '';
+
+    robotsConf = ''
+      Alias /robots.txt ${robotsTxt}
+    '';
+
+  in ''
+    ServerName ${serverInfo.canonicalName}
+
+    ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
+
+    ${if cfg.sslServerCert != "" then ''
+      SSLCertificateFile ${cfg.sslServerCert}
+      SSLCertificateKeyFile ${cfg.sslServerKey}
+    '' 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 != "" 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 ""}
+
+    ${robotsConf}
+
+    ${if isMainServer || cfg.documentRoot != 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 != "" 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
+        ports = map getPort allHosts;
+        uniquePorts = uniqList {inputList = ports;};
+      in concatMapStrings (port: "Listen ${toString port}\n") uniquePorts
+    }
+
+    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 enablePHP { name = "php5"; path = "${php}/modules/libphp5.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
+        ports = map getPort allHosts;
+        uniquePorts = uniqList {inputList = ports;};
+        directives = concatMapStrings (port: "NameVirtualHost *:${toString port}\n") uniquePorts;
+      in optionalString (!version24) directives
+    }
+
+    ${let
+        makeVirtualHost = vhost: ''
+          <VirtualHost *:${toString (getPort vhost)}>
+              ${perServerConf false vhost}
+          </VirtualHost>
+        '';
+      in concatMapStrings makeVirtualHost vhosts
+    }
+  '';
+
+
+  enablePHP = any (svc: svc.enablePHP) 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-recommended.ini > $out
+      echo "$options" >> $out
+    '';
+
+in
+
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.httpd = {
+
+      enable = mkOption {
+        default = false;
+        description = "
+          Whether to enable the Apache httpd server.
+        ";
+      };
+
+      package = mkOption {
+        default = pkgs.apacheHttpd.override { mpm = mainCfg.multiProcessingModule; };
+	example = "pkgs.apacheHttpd_2_4";
+        description = "
+          Overridable attribute of the Apache HTTP Server package to use.
+        ";
+      };
+
+      configFile = mkOption {
+        default = confFile;
+	example = ''pkgs.writeText "httpd.conf" "# my custom config file ...";'';
+        description = "
+          Overridable config file to use for Apache. By default, use the
+          file automatically generated by nixos.
+        ";
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        description = "
+          These configuration lines will be appended to the Apache config
+          file. Note that this mechanism may not work when <option>configFile</option>
+          is overridden.
+        ";
+      };
+
+      extraModules = mkOption {
+        default = [];
+        example = [ "proxy_connect" { name = "php5"; path = "${php}/modules/libphp5.so"; } ];
+        description = ''
+          Specifies additional Apache modules.  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 {
+        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 {
+        default = "wwwrun";
+        description = "
+          User account under which httpd runs.  The account is created
+          automatically if it doesn't exist.
+        ";
+      };
+
+      group = mkOption {
+        default = "wwwrun";
+        description = "
+          Group under which httpd runs.  The account is created
+          automatically if it doesn't exist.
+        ";
+      };
+
+      logDir = mkOption {
+        default = "/var/log/httpd";
+        description = "
+          Directory for Apache's log files.  It is created automatically.
+        ";
+      };
+
+      stateDir = mkOption {
+        default = "/var/run/httpd";
+        description = "
+          Directory for Apache's transient runtime state (such as PID
+          files).  It is created automatically.  Note that the default,
+          <filename>/var/run/httpd</filename>, is deleted at boot time.
+        ";
+      };
+
+      virtualHosts = mkOption {
+        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.
+        '';
+      };
+
+      phpOptions = mkOption {
+        default = "";
+        example =
+          ''
+            date.timezone = "CET"
+          '';
+        description =
+          "Options appended to the PHP configuration file <filename>php.ini</filename>.";
+      };
+
+      multiProcessingModule = mkOption {
+        default = "prefork";
+        example = "worker";
+        type = types.uniq types.string;
+        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 {
+        default = 150;
+        example = 8;
+        description = "Maximum number of httpd processes (prefork)";
+      };
+
+      maxRequestsPerChild = mkOption {
+        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 mkOption;
+      forMainServer = true;
+    });
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.httpd.enable {
+
+    users.extraUsers = optionalAttrs (mainCfg.user == "wwwrun") singleton
+      { name = "wwwrun";
+        group = "wwwrun";
+        description = "Apache httpd user";
+        uid = config.ids.uids.wwwrun;
+      };
+
+    users.extraGroups = optionalAttrs (mainCfg.group == "wwwrun") singleton
+      { name = "wwwrun";
+        gid = config.ids.gids.wwwrun;
+      };
+
+    environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+
+    services.httpd.phpOptions =
+      ''
+        ; Needed for PHP's mail() function.
+        sendmail_path = sendmail -t -i
+
+        ; Apparently PHP doesn't use $TZ.
+        date.timezone = "${config.time.timeZone}"
+      '';
+
+    systemd.services.httpd =
+      { description = "Apache HTTPD";
+
+        wantedBy = [ "multi-user.target" ];
+        requires = [ "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 =
+          { PHPRC = if enablePHP then phpIni else "";
+          } // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+
+        preStart =
+          ''
+            mkdir -m 0750 -p ${mainCfg.stateDir}
+            chown root.${mainCfg.group} ${mainCfg.stateDir}
+            ${optionalString version24 ''
+              mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
+              chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
+            ''}
+            mkdir -m 0700 -p ${mainCfg.logDir}
+
+            ${optionalString (mainCfg.documentRoot != null)
+            ''
+              # Create the document root directory if does not exists yet
+              mkdir -p ${mainCfg.documentRoot}
+            ''
+            }
+
+            # 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.Type = "forking";
+        serviceConfig.Restart = "always";
+      };
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
new file mode 100644
index 000000000000..dcc05b03891b
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
@@ -0,0 +1,303 @@
+{ config, pkgs, serverInfo, php, ... }:
+
+with pkgs.lib;
+
+let
+
+  mediawikiConfig = pkgs.writeText "LocalSettings.php"
+    ''
+      <?php
+        # Copied verbatim from the default (generated) LocalSettings.php.
+        if( defined( 'MW_INSTALL_PATH' ) ) {
+                $IP = MW_INSTALL_PATH;
+        } else {
+                $IP = dirname( __FILE__ );
+        }
+
+        $path = array( $IP, "$IP/includes", "$IP/languages" );
+        set_include_path( implode( PATH_SEPARATOR, $path ) . PATH_SEPARATOR . get_include_path() );
+
+        require_once( "$IP/includes/DefaultSettings.php" );
+
+        if ( $wgCommandLineMode ) {
+                if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
+                        die( "This script must be run from the command line\n" );
+                }
+        }
+
+        $wgScriptPath = "${config.urlPrefix}";
+
+        # We probably need to set $wgSecretKey and $wgCacheEpoch.
+
+        # Paths to external programs.
+        $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
+        $wgDiff = "${pkgs.diffutils}/bin/diff";
+        $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
+
+        #$wgDebugLogFile = "/tmp/mediawiki_debug_log.txt";
+
+        # Database configuration.
+        $wgDBtype = "${config.dbType}";
+        $wgDBserver = "${config.dbServer}";
+        $wgDBuser = "${config.dbUser}";
+        $wgDBpassword = "${config.dbPassword}";
+        $wgDBname = "${config.dbName}";
+
+        # E-mail.
+        $wgEmergencyContact = "${config.emergencyContact}";
+        $wgPasswordSender = "${config.passwordSender}";
+
+        $wgSitename = "${config.siteName}";
+
+        ${optionalString (config.logo != "") ''
+          $wgLogo = "${config.logo}";
+        ''}
+
+        ${optionalString (config.articleUrlPrefix != "") ''
+          $wgArticlePath = "${config.articleUrlPrefix}/$1";
+        ''}
+
+        ${optionalString config.enableUploads ''
+          $wgEnableUploads = true;
+          $wgUploadDirectory = "${config.uploadDir}";
+        ''}
+
+        ${optionalString (config.defaultSkin != "") ''
+          $wgDefaultSkin = "${config.defaultSkin}";
+        ''}
+
+        ${config.extraConfig}
+      ?>
+    '';
+
+  # Unpack Mediawiki and put the config file in its root directory.
+  mediawikiRoot = pkgs.stdenv.mkDerivation rec {
+    name= "mediawiki-1.20.5";
+
+    src = pkgs.fetchurl {
+      url = "http://download.wikimedia.org/mediawiki/1.20/${name}.tar.gz";
+      sha256 = "0ix6khrilfdncjqnh41xjs0bd49i1q0rywycjaixjfpwj6vjbqbl";
+    };
+
+    skins = config.skins;
+
+    buildPhase =
+      ''
+        for skin in $skins; do
+          cp -prvd $skin/* skins/
+        done
+      ''; # */
+
+    installPhase =
+      ''
+        ensureDir $out
+        cp -r * $out
+        cp ${mediawikiConfig} $out/LocalSettings.php
+      '';
+  };
+
+  mediawikiScripts = pkgs.runCommand "mediawiki-${config.id}-scripts"
+    { buildInputs = [ pkgs.makeWrapper ]; }
+    ''
+      ensureDir $out/bin
+      for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do
+        makeWrapper ${php}/bin/php $out/bin/mediawiki-${config.id}-$(basename $i .php) \
+          --add-flags ${mediawikiRoot}/maintenance/$i
+      done
+    '';
+
+in
+
+{
+
+  extraConfig =
+    ''
+      ${optionalString config.enableUploads ''
+        Alias ${config.urlPrefix}/images ${config.uploadDir}
+
+        <Directory ${config.uploadDir}>
+            Order allow,deny
+            Allow from all
+            Options -Indexes
+        </Directory>
+      ''}
+
+      Alias ${config.urlPrefix} ${mediawikiRoot}
+
+      <Directory ${mediawikiRoot}>
+          Order allow,deny
+          Allow from all
+          DirectoryIndex index.php
+      </Directory>
+
+      ${optionalString (config.articleUrlPrefix != "") ''
+        Alias ${config.articleUrlPrefix} ${mediawikiRoot}/index.php
+      ''}
+    '';
+
+  enablePHP = true;
+
+  options = {
+
+    id = mkOption {
+      default = "main";
+      description = ''
+        A unique identifier necessary to keep multiple MediaWiki server
+        instances on the same machine apart.  This is used to
+        disambiguate the administrative scripts, which get names like
+        mediawiki-$id-change-password.
+      '';
+    };
+
+    dbType = mkOption {
+      default = "postgres";
+      example = "mysql";
+      description = "Database type.";
+    };
+
+    dbName = mkOption {
+      default = "mediawiki";
+      description = "Name of the database that holds the MediaWiki data.";
+    };
+
+    dbServer = mkOption {
+      default = ""; # use a Unix domain socket
+      example = "10.0.2.2";
+      description = ''
+        The location of the database server.  Leave empty to use a
+        database server running on the same machine through a Unix
+        domain socket.
+      '';
+    };
+
+    dbUser = mkOption {
+      default = "mediawiki";
+      description = "The user name for accessing the database.";
+    };
+
+    dbPassword = mkOption {
+      default = "";
+      example = "foobar";
+      description = ''
+        The password of the database user.  Warning: this is stored in
+        cleartext in the Nix store!
+      '';
+    };
+
+    emergencyContact = mkOption {
+      default = serverInfo.serverConfig.adminAddr;
+      example = "admin@example.com";
+      description = ''
+        Emergency contact e-mail address.  Defaults to the Apache
+        admin address.
+      '';
+    };
+
+    passwordSender = mkOption {
+      default = serverInfo.serverConfig.adminAddr;
+      example = "password@example.com";
+      description = ''
+        E-mail address from which password confirmations originate.
+        Defaults to the Apache admin address.
+      '';
+    };
+
+    siteName = mkOption {
+      default = "MediaWiki";
+      example = "Foobar Wiki";
+      description = "Name of the wiki";
+    };
+
+    logo = mkOption {
+      default = "";
+      example = "/images/logo.png";
+      description = "The URL of the site's logo (which should be a 135x135px image).";
+    };
+
+    urlPrefix = mkOption {
+      default = "/w";
+      description = ''
+        The URL prefix under which the Mediawiki service appears.
+      '';
+    };
+
+    articleUrlPrefix = mkOption {
+      default = "/wiki";
+      example = "";
+      description = ''
+        The URL prefix under which article pages appear,
+        e.g. http://server/wiki/Page.  Leave empty to use the main URL
+        prefix, e.g. http://server/w/index.php?title=Page.
+      '';
+    };
+
+    enableUploads = mkOption {
+      default = false;
+      description = "Whether to enable file uploads.";
+    };
+
+    uploadDir = mkOption {
+      default = throw "You must specify `uploadDir'.";
+      example = "/data/mediawiki-upload";
+      description = "The directory that stores uploaded files.";
+    };
+
+    defaultSkin = mkOption {
+      default = "";
+      example = "nostalgia";
+      description = "Set this value to change the default skin used by MediaWiki.";
+    };
+
+    skins = mkOption {
+      default = [];
+      type = types.listOf types.path;
+      description =
+        ''
+          List of paths whose content is copied to the ‘skins’
+          subdirectory of the MediaWiki installation.
+        '';
+    };
+
+    extraConfig = mkOption {
+      default = "";
+      example =
+        ''
+          $wgEnableEmail = false;
+        '';
+      description = ''
+        Any additional text to be appended to MediaWiki's
+        configuration file.  This is a PHP script.  For configuration
+        settings, see <link xlink:href='http://www.mediawiki.org/wiki/Manual:Configuration_settings'/>.
+      '';
+    };
+
+  };
+
+  extraPath = [ mediawikiScripts ];
+
+  # !!! Need to specify that Apache has a dependency on PostgreSQL!
+
+  startupScript = pkgs.writeScript "mediawiki_startup.sh"
+    # Initialise the database automagically if we're using a Postgres
+    # server on localhost.
+    (optionalString (config.dbType == "postgres" && config.dbServer == "") ''
+      if ! ${pkgs.postgresql}/bin/psql -l | grep -q ' ${config.dbName} ' ; then
+          ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true
+          ${pkgs.postgresql}/bin/createdb "${config.dbName}" -O "${config.dbUser}"
+          ( echo 'CREATE LANGUAGE plpgsql;'
+            cat ${mediawikiRoot}/maintenance/postgres/tables.sql
+            echo 'CREATE TEXT SEARCH CONFIGURATION public.default ( COPY = pg_catalog.english );'
+            echo COMMIT
+          ) | ${pkgs.postgresql}/bin/psql -U "${config.dbUser}" "${config.dbName}"
+      fi
+    '');
+
+  robotsEntries = optionalString (config.articleUrlPrefix != "")
+    ''
+      User-agent: *
+      Disallow: ${config.urlPrefix}/
+      Disallow: ${config.articleUrlPrefix}/Special:Search
+      Disallow: ${config.articleUrlPrefix}/Special:Random
+    '';
+
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/mercurial.nix b/nixos/modules/services/web-servers/apache-httpd/mercurial.nix
new file mode 100644
index 000000000000..755b595c783d
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/mercurial.nix
@@ -0,0 +1,75 @@
+{ config, pkgs, serverInfo, ... }:
+
+let
+  inherit (pkgs) mercurial;
+  inherit (pkgs.lib) mkOption;
+
+  urlPrefix = config.urlPrefix;
+
+  cgi = pkgs.stdenv.mkDerivation {
+    name = "mercurial-cgi";
+    buildCommand = ''
+      ensureDir $out
+      cp -v ${mercurial}/share/cgi-bin/hgweb.cgi $out
+      sed -i "s|/path/to/repo/or/config|$out/hgweb.config|" $out/hgweb.cgi
+      echo "
+      [collections]
+      ${config.dataDir} = ${config.dataDir}
+      [web]
+      style = gitweb
+      allow_push = *
+      " > $out/hgweb.config
+    '';
+  };
+
+in {
+
+  extraConfig = ''
+    RewriteEngine on
+    RewriteRule /(.*) ${cgi}/hgweb.cgi/$1
+
+    <Location "${urlPrefix}">
+        AuthType Basic
+        AuthName "Mercurial repositories"
+        AuthUserFile ${config.dataDir}/hgusers
+        <LimitExcept GET>
+            Require valid-user
+        </LimitExcept>
+    </Location>
+    <Directory "${cgi}">
+        Order allow,deny
+        Allow from all
+        AllowOverride All
+        Options ExecCGI
+        AddHandler cgi-script .cgi
+        PassEnv PYTHONPATH
+    </Directory>
+  '';
+
+  robotsEntries = ''
+    User-agent: *
+    Disallow: ${urlPrefix}
+  '';
+
+  extraServerPath = [ pkgs.python ];
+
+  globalEnvVars = [ { name = "PYTHONPATH"; value = "${mercurial}/lib/${pkgs.python.libPrefix}/site-packages"; } ];
+
+  options = {
+    urlPrefix = mkOption {
+      default = "/hg";
+      description = "
+        The URL prefix under which the Mercurial service appears.
+        Use the empty string to have it appear in the server root.
+      ";
+    };
+
+    dataDir = mkOption {
+      example = "/data/mercurial";
+      description = "
+        Path to the directory that holds the repositories.
+      ";
+    };
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
new file mode 100644
index 000000000000..a5227bae2d40
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
@@ -0,0 +1,146 @@
+# 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, mkOption}:
+
+{
+
+  hostName = mkOption {
+    default = "localhost";
+    description = "
+      Canonical hostname for the server.
+    ";
+  };
+
+  serverAliases = mkOption {
+    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 {
+    default = 0;
+    description = "
+      Port for the server.  0 means use the default port: 80 for http
+      and 443 for https (i.e. when enableSSL is set).
+    ";
+  };
+
+  enableSSL = mkOption {
+    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 {
+    default = "";
+    example = "/var/host.cert";
+    description = "
+      Path to server SSL certificate.
+    ";
+  };
+
+  sslServerKey = mkOption {
+    default = "";
+    example = "/var/host.key";
+    description = "
+      Path to server SSL certificate key.
+    ";
+  };
+
+  adminAddr = mkOption ({
+    example = "admin@example.org";
+    description = "
+      E-mail address of the server administrator.
+    ";
+  } // (if forMainServer then {} else {default = "";}));
+
+  documentRoot = mkOption {
+    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 {
+    default = [];
+    example = [
+      { urlPath = "/nix";
+        dir = "/home/eelco/Dev/nix-homepage";
+      }
+    ];
+    description = "
+      This option provides a simple way to serve static directories.
+    ";
+  };
+
+  servedFiles = mkOption {
+    default = [];
+    example = [
+      { urlPath = "/foo/bar.png";
+        dir = "/home/eelco/some-file.png";
+      }
+    ];
+    description = "
+      This option provides a simple way to serve individual, static files.
+    ";
+  };
+
+  extraConfig = mkOption {
+    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 {
+    default = [];
+    description = "
+      Extra subservices to enable in the webserver.
+    ";
+  };
+
+  enableUserDir = mkOption {
+    default = false;
+    description = "
+      Whether to enable serving <filename>~/public_html</filename> as
+      <literal>/~<replaceable>username</replaceable></literal>.
+    ";
+  };
+
+  globalRedirect = mkOption {
+    default = "";
+    example = http://newserver.example.org/;
+    description = "
+      If set, all requests for this host are redirected permanently to
+      the given URL.
+    ";
+  };
+
+  logFormat = mkOption {
+    default = "common";
+    example = "combined";
+    description = "
+      Log format for Apache's log files. Possible values are: combined, common, referer, agent.
+    ";
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix b/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix
new file mode 100644
index 000000000000..f12ae842b587
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix
@@ -0,0 +1,95 @@
+{ config, pkgs, serverInfo, ... }:
+
+let
+  extraWorkersProperties = pkgs.lib.optionalString (config ? extraWorkersProperties) config.extraWorkersProperties;
+  
+  workersProperties = pkgs.writeText "workers.properties" ''
+# Define list of workers that will be used
+# for mapping requests
+# The configuration directives are valid
+# for the mod_jk version 1.2.18 and later
+#
+worker.list=loadbalancer,status
+
+# Define Node1
+# modify the host as your host IP or DNS name.
+worker.node1.port=8009
+worker.node1.host=localhost
+worker.node1.type=ajp13
+worker.node1.lbfactor=1
+
+# Load-balancing behaviour
+worker.loadbalancer.type=lb
+worker.loadbalancer.balance_workers=node1
+
+# Status worker for managing load balancer
+worker.status.type=status
+
+${extraWorkersProperties}
+  '';
+in
+{
+  extraModules = [
+    { name = "jk"; path = "${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
+  ];
+
+  extraConfig = ''
+# Where to find workers.properties
+JkWorkersFile ${workersProperties}
+
+# Where to put jk logs
+JkLogFile ${config.logDir}/mod_jk.log
+
+# Set the jk log level [debug/error/info]
+JkLogLevel info
+
+# Select the log format
+JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
+
+# JkOptions indicates to send SSK KEY SIZE
+# Note: Changed from +ForwardURICompat.
+# See http://tomcat.apache.org/security-jk.html
+JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories
+
+# JkRequestLogFormat
+JkRequestLogFormat "%w %V %T"
+
+# Mount your applications
+JkMount /__application__/* loadbalancer
+
+# You can use external file for mount points.
+# It will be checked for updates each 60 seconds.
+# The format of the file is: /url=worker
+# /examples/*=loadbalancer
+#JkMountFile uriworkermap.properties
+
+# Add shared memory.
+# This directive is present with 1.2.10 and
+# later versions of mod_jk, and is needed for
+# for load balancing to work properly
+# Note: Replaced JkShmFile logs/jk.shm due to SELinux issues. Refer to
+# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=225452
+JkShmFile ${config.stateDir}/jk.shm
+
+# Static files in all Tomcat webapp context directories are served by apache
+JkAutoAlias /var/tomcat/webapps
+
+# All requests go to worker by default
+JkMount /* loadbalancer
+# Serve some static files using httpd
+#JkUnMount /*.html loadbalancer
+#JkUnMount /*.jpg  loadbalancer
+#JkUnMount /*.gif  loadbalancer
+#JkUnMount /*.css  loadbalancer
+#JkUnMount /*.png  loadbalancer
+#JkUnMount /*.js  loadbalancer
+
+# Add jkstatus for managing runtime data
+<Location /jkstatus/>
+JkMount status
+Order deny,allow
+Deny from all
+Allow from 127.0.0.1
+</Location>
+  '';
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/trac.nix b/nixos/modules/services/web-servers/apache-httpd/trac.nix
new file mode 100644
index 000000000000..dc82fd34f2fa
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/trac.nix
@@ -0,0 +1,121 @@
+{ config, pkgs, serverInfo, ... }:
+
+with pkgs.lib;
+
+let
+
+  # Build a Subversion instance with Apache modules and Swig/Python bindings.
+  subversion = pkgs.subversion.override (origArgs: {
+    bdbSupport = true;
+    httpServer = true;
+    sslSupport = true;
+    compressionSupport = true;
+    pythonBindings = true;
+  });
+
+  pythonLib = p: "${p}/";
+
+in
+
+{
+
+  options = {
+
+    projectsLocation = mkOption {
+      description = "URL path in which Trac projects can be accessed";
+      default = "/projects";
+    };
+
+    projects = mkOption {
+      description = "List of projects that should be provided by Trac. If they are not defined yet empty projects are created.";
+      default = [];
+      example =
+        [ { identifier = "myproject";
+            name = "My Project";
+            databaseURL="postgres://root:password@/tracdb";
+            subversionRepository="/data/subversion/myproject";
+          }
+        ];
+    };
+
+    user = mkOption {
+      default = "wwwrun";
+      description = "User account under which Trac runs.";
+    };
+
+    group = mkOption {
+      default = "wwwrun";
+      description = "Group under which Trac runs.";
+    };
+
+    ldapAuthentication = {
+      enable = mkOption {
+        default = false;
+        description = "Enable the ldap authentication in trac";
+      };
+
+      url = mkOption {
+        default = "ldap://127.0.0.1/dc=example,dc=co,dc=ke?uid?sub?(objectClass=inetOrgPerson)";
+        description = "URL of the LDAP authentication";
+      };
+
+      name = mkOption {
+        default = "Trac server";
+        description = "AuthName";
+      };
+    };
+
+  };
+
+  extraModules = singleton
+    { name = "python"; path = "${pkgs.mod_python}/modules/mod_python.so"; };
+
+  extraConfig = ''
+    <Location ${config.projectsLocation}>
+      SetHandler mod_python
+      PythonHandler trac.web.modpython_frontend
+      PythonOption TracEnvParentDir /var/trac/projects
+      PythonOption TracUriRoot ${config.projectsLocation}
+      PythonOption PYTHON_EGG_CACHE /var/trac/egg-cache
+    </Location>
+    ${if config.ldapAuthentication.enable then ''
+      <LocationMatch "^${config.projectsLocation}[^/]+/login$">
+        AuthType Basic
+        AuthName "${config.ldapAuthentication.name}"
+        AuthBasicProvider "ldap"
+        AuthLDAPURL "${config.ldapAuthentication.url}"
+        authzldapauthoritative Off
+        require valid-user
+      </LocationMatch>
+    '' else ""}
+  '';
+
+  globalEnvVars = singleton
+    { name = "PYTHONPATH";
+      value =
+        makeSearchPath "lib/${pkgs.python.libPrefix}/site-packages"
+          [ pkgs.mod_python
+            pkgs.pythonPackages.trac
+            pkgs.setuptools
+            pkgs.pythonPackages.genshi
+            pkgs.pythonPackages.psycopg2
+            pkgs.python.modules.sqlite3
+            subversion
+          ];
+    };
+
+  startupScript = pkgs.writeScript "activateTrac" ''
+    mkdir -p /var/trac
+    chown ${config.user}:${config.group} /var/trac
+
+    ${concatMapStrings (project:
+      ''
+        if [ ! -d /var/trac/${project.identifier} ]
+        then
+            export PYTHONPATH=${pkgs.pythonPackages.psycopg2}/lib/${pkgs.python.libPrefix}/site-packages
+            ${pkgs.pythonPackages.trac}/bin/trac-admin /var/trac/${project.identifier} initenv "${project.name}" "${project.databaseURL}" svn "${project.subversionRepository}"
+        fi
+      '' ) (config.projects)}
+  '';
+
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/zabbix.nix b/nixos/modules/services/web-servers/apache-httpd/zabbix.nix
new file mode 100644
index 000000000000..a6e6042fdf6d
--- /dev/null
+++ b/nixos/modules/services/web-servers/apache-httpd/zabbix.nix
@@ -0,0 +1,82 @@
+{ config, pkgs, serverInfo, ... }:
+
+let
+
+  # The Zabbix PHP frontend needs to be able to write its
+  # configuration settings (the connection info to the database) to
+  # the "conf" subdirectory.  So symlink $out/conf to some directory
+  # outside of the Nix store where we want to keep this stateful info.
+  # Note that different instances of the frontend will therefore end
+  # up with their own copies of the PHP sources.  !!! Alternatively,
+  # we could generate zabbix.conf.php declaratively.
+  zabbixPHP = pkgs.runCommand "${pkgs.zabbix.server.name}-php" {}
+    ''
+      cp -rs ${pkgs.zabbix.server}/share/zabbix/php "$out"
+      chmod -R u+w $out
+      ln -s "${if config.configFile == null
+               then "${config.stateDir}/zabbix.conf.php"
+               else config.configFile}" "$out/conf/zabbix.conf.php"
+    '';
+
+in
+
+{
+
+  enablePHP = true;
+
+  phpOptions =
+    ''
+      post_max_size = 32M
+      max_execution_time = 300
+      max_input_time = 300
+    '';
+
+  extraConfig = ''
+    Alias ${config.urlPrefix}/ ${zabbixPHP}/
+
+    <Directory ${zabbixPHP}>
+      DirectoryIndex index.php
+      Order deny,allow
+      Allow from *
+    </Directory>
+  '';
+
+  startupScript = pkgs.writeScript "zabbix-startup-hook" ''
+    mkdir -p ${config.stateDir}
+    chown -R ${serverInfo.serverConfig.user} ${config.stateDir}
+  '';
+
+  # The frontend needs "ps" to find out whether zabbix_server is running.
+  extraServerPath = [ pkgs.procps ];
+
+  options = {
+
+    urlPrefix = pkgs.lib.mkOption {
+      default = "/zabbix";
+      description = "
+        The URL prefix under which the Zabbix service appears.
+        Use the empty string to have it appear in the server root.
+      ";
+    };
+
+    configFile = pkgs.lib.mkOption {
+      default = null;
+      type = with pkgs.lib.types; nullOr path;
+      description = ''
+        The configuration file (zabbix.conf.php) which contains the database
+        connection settings. If not set, the configuration settings will created
+        by the web installer.
+      '';
+    };
+
+    stateDir = pkgs.lib.mkOption {
+      default = "/var/lib/zabbix/frontend";
+      description = "
+        Directory where the dynamically generated configuration data
+        of the PHP frontend will be stored.
+      ";
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/jboss/builder.sh b/nixos/modules/services/web-servers/jboss/builder.sh
new file mode 100644
index 000000000000..2eb89a90f67d
--- /dev/null
+++ b/nixos/modules/services/web-servers/jboss/builder.sh
@@ -0,0 +1,72 @@
+set -e
+
+source $stdenv/setup
+
+mkdir -p $out/bin
+
+cat > $out/bin/control <<EOF
+mkdir -p $logDir
+chown -R $user $logDir
+export PATH=$PATH:$su/bin
+
+start()
+{
+  su $user -s /bin/sh -c "$jboss/bin/run.sh \
+      -Djboss.server.base.dir=$serverDir \
+      -Djboss.server.base.url=file://$serverDir \
+      -Djboss.server.temp.dir=$tempDir \
+      -Djboss.server.log.dir=$logDir \
+      -Djboss.server.lib.url=$libUrl \
+      -c default"
+}
+
+stop()
+{
+  su $user -s /bin/sh -c "$jboss/bin/shutdown.sh -S"
+}
+
+if test "\$1" = start
+then
+  trap stop 15
+  
+  start
+elif test "\$1" = stop
+then
+  stop  
+elif test "\$1" = init
+then
+  echo "Are you sure you want to create a new server instance (old server instance will be lost!)?"
+  read answer
+
+  if ! test \$answer = "yes"
+  then
+    exit 1
+  fi
+  
+  rm -rf $serverDir
+  mkdir -p $serverDir
+  cd $serverDir
+  cp -av $jboss/server/default .
+  sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml
+  
+  if ! test "$useJK" = ""
+  then
+    sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml
+    sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml
+  fi
+  
+  # Make files accessible for the server user
+  
+  chown -R $user $serverDir
+  for i in \`find $serverDir -type d\`
+  do
+    chmod 755 \$i
+  done
+  for i in \`find $serverDir -type f\`
+  do
+    chmod 644 \$i
+  done
+fi
+EOF
+
+chmod +x $out/bin/*
diff --git a/nixos/modules/services/web-servers/jboss/default.nix b/nixos/modules/services/web-servers/jboss/default.nix
new file mode 100644
index 000000000000..e1bcede6563c
--- /dev/null
+++ b/nixos/modules/services/web-servers/jboss/default.nix
@@ -0,0 +1,83 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg = config.services.jboss;
+
+  jbossService = pkgs.stdenv.mkDerivation {
+    name = "jboss-server";
+    builder = ./builder.sh;
+    inherit (pkgs) jboss su;
+    inherit (cfg) tempDir logDir libUrl deployDir serverDir user useJK;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.jboss = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable jboss";
+      };
+
+      tempDir = mkOption {
+        default = "/tmp";
+        description = "Location where JBoss stores its temp files";
+      };
+
+      logDir = mkOption {
+        default = "/var/log/jboss";
+        description = "Location of the logfile directory of JBoss";
+      };
+
+      serverDir = mkOption {
+        description = "Location of the server instance files";
+        default = "/var/jboss/server";
+      };
+
+      deployDir = mkOption {
+        description = "Location of the deployment files";
+        default = "/nix/var/nix/profiles/default/server/default/deploy/";
+      };
+
+      libUrl = mkOption {
+        default = "file:///nix/var/nix/profiles/default/server/default/lib";
+        description = "Location where the shared library JARs are stored";
+      };
+
+      user = mkOption {
+        default = "nobody";
+        description = "User account under which jboss runs.";
+      };
+
+      useJK = mkOption {
+        default = false;
+        description = "Whether to use to connector to the Apache HTTP server";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.jboss.enable {
+
+    jobs.jboss =
+      { description = "JBoss server";
+
+        exec = "${jbossService}/bin/control start";
+      };
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
new file mode 100644
index 000000000000..62264f1db452
--- /dev/null
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -0,0 +1,65 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+  cfg = config.services.lighttpd.cgit;
+  configFile = pkgs.writeText "cgitrc"
+    ''
+      ${cfg.configText}
+    '';
+in
+{
+
+  options.services.lighttpd.cgit = {
+
+    enable = mkOption {
+      default = false;
+      type = types.uniq types.bool;
+      description = ''
+        If true, enable cgit (fast web interface for git repositories) as a
+        sub-service in lighttpd. cgit will be accessible at
+        http://yourserver/cgit
+      '';
+    };
+
+    configText = mkOption {
+      default = "";
+      example = ''
+        cache-size=1000
+        scan-path=/srv/git
+      '';
+      type = types.string;
+      description = ''
+        Verbatim contents of the cgit runtime configuration file. Documentation
+        (with cgitrc example file) is available in "man cgitrc". Or online:
+        http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    # make the cgitrc manpage available
+    environment.systemPackages = [ pkgs.cgit ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/cgit" {
+          cgi.assign = (
+              "cgit.cgi" => "${pkgs.cgit}/cgit/cgit.cgi"
+          )
+          alias.url = (
+              "/cgit.css" => "${pkgs.cgit}/cgit/cgit.css",
+              "/cgit.png" => "${pkgs.cgit}/cgit/cgit.png",
+              "/cgit"     => "${pkgs.cgit}/cgit/cgit.cgi"
+          )
+          setenv.add-environment = (
+              "CGIT_CONFIG" => "${configFile}"
+          )
+      }
+    '';
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
new file mode 100644
index 000000000000..f9e40fc4b541
--- /dev/null
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -0,0 +1,178 @@
+# NixOS module for lighttpd web server
+
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg = config.services.lighttpd;
+
+  needModRedirect = cfg.gitweb.enable;
+  needModAlias = cfg.cgit.enable or cfg.gitweb.enable;
+  needModSetenv = cfg.cgit.enable or cfg.gitweb.enable;
+  needModCgi = cfg.cgit.enable or cfg.gitweb.enable;
+  needModStatus = cfg.mod_status;
+  needModUserdir = cfg.mod_userdir;
+
+  configFile = if cfg.configText != "" then
+    pkgs.writeText "lighttpd.conf" ''
+      ${cfg.configText}
+    ''
+    else
+    pkgs.writeText "lighttpd.conf" ''
+      server.document-root = "${cfg.document-root}"
+      server.port = ${toString cfg.port}
+      server.username = "lighttpd"
+      server.groupname = "lighttpd"
+
+      # As for why all modules are loaded here, instead of having small
+      # server.modules += () entries in each sub-service extraConfig snippet,
+      # read this:
+      #
+      #   http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+      #   http://redmine.lighttpd.net/issues/2337
+      #
+      # Basically, lighttpd doesn't want to load (or even silently ignore) a
+      # module for a second time, and there is no way to check if a module has
+      # been loaded already. So if two services were to put the same module in
+      # server.modules += (), that would break the lighttpd configuration.
+      server.modules = (
+          ${optionalString needModRedirect ''"mod_redirect",''}
+          ${optionalString needModAlias ''"mod_alias",''}
+          ${optionalString needModSetenv ''"mod_setenv",''}
+          ${optionalString needModCgi ''"mod_cgi",''}
+          ${optionalString needModStatus ''"mod_status",''}
+          ${optionalString needModUserdir ''"mod_userdir",''}
+          "mod_accesslog"
+      )
+
+      # Logging (logs end up in systemd journal)
+      accesslog.use-syslog = "enable"
+      server.errorlog-use-syslog = "enable"
+
+      mimetype.assign = (
+          ".html" => "text/html",
+          ".htm" => "text/html",
+          ".txt" => "text/plain",
+          ".jpg" => "image/jpeg",
+          ".png" => "image/png",
+          ".css" => "text/css"
+          )
+
+      static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
+      index-file.names = ( "index.html" )
+
+      ${if cfg.mod_userdir then ''
+        userdir.path = "public_html"
+      '' else ""}
+
+      ${if cfg.mod_status then ''
+        status.status-url = "/server-status"
+        status.statistics-url = "/server-statistics"
+        status.config-url = "/server-config"
+      '' else ""}
+
+      ${cfg.extraConfig}
+    '';
+
+in
+
+{
+
+  options = {
+
+    services.lighttpd = {
+
+      enable = mkOption {
+        default = false;
+        type = types.uniq types.bool;
+        description = ''
+          Enable the lighttpd web server.
+        '';
+      };
+
+      port = mkOption {
+        default = 80;
+        type = types.uniq types.int;
+        description = ''
+          TCP port number for lighttpd to bind to.
+        '';
+      };
+
+      document-root = mkOption {
+        default = "/srv/www";
+        type = types.uniq types.string;
+        description = ''
+          Document-root of the web server. Must be readable by the "lighttpd" user.
+        '';
+      };
+
+      mod_userdir = mkOption {
+        default = false;
+        type = types.uniq types.bool;
+        description = ''
+          If true, requests in the form /~user/page.html are rewritten to take
+          the file public_html/page.html from the home directory of the user.
+        '';
+      };
+
+      mod_status = mkOption {
+        default = false;
+        type = types.uniq types.bool;
+        description = ''
+          Show server status overview at /server-status, statistics at
+          /server-statistics and list of loaded modules at /server-config.
+        '';
+      };
+
+      configText = mkOption {
+        default = "";
+        type = types.string;
+	example = ''...verbatim config file contents...'';
+        description = ''
+          Overridable config file contents to use for lighttpd. By default, use
+          the contents automatically generated by NixOS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.string;
+        description = ''
+          These configuration lines will be appended to the generated lighttpd
+          config file. Note that this mechanism does not work when the manual
+          <option>configText</option> option is used.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.lighttpd = {
+      description = "Lighttpd Web Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        ${if cfg.cgit.enable then ''
+          mkdir -p /var/cache/cgit
+          chown lighttpd:lighttpd /var/cache/cgit
+        '' else ""}
+      '';
+      serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}";
+      # SIGINT => graceful shutdown
+      serviceConfig.KillSignal = "SIGINT";
+    };
+
+    users.extraUsers.lighttpd = {
+      group = "lighttpd";
+      description = "lighttpd web server privilege separation user";
+      uid = config.ids.uids.lighttpd;
+    };
+
+    users.extraGroups.lighttpd.gid = config.ids.gids.lighttpd;
+  };
+}
diff --git a/nixos/modules/services/web-servers/lighttpd/gitweb.nix b/nixos/modules/services/web-servers/lighttpd/gitweb.nix
new file mode 100644
index 000000000000..d759d8144b64
--- /dev/null
+++ b/nixos/modules/services/web-servers/lighttpd/gitweb.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+  cfg = config.services.lighttpd.gitweb;
+  gitwebConfigFile = pkgs.writeText "gitweb.conf" ''
+    # path to git projects (<project>.git)
+    $projectroot = "${cfg.projectroot}";
+    ${cfg.extraConfig}
+  '';
+
+in
+{
+
+  options.services.lighttpd.gitweb = {
+
+    enable = mkOption {
+      default = false;
+      type = types.uniq types.bool;
+      description = ''
+        If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb
+      '';
+    };
+
+    projectroot = mkOption {
+      default = "/srv/git";
+      type = types.uniq types.string;
+      description = ''
+        Path to git projects (bare repositories) that should be served by
+        gitweb. Must not end with a slash.
+      '';
+    };
+
+    extraConfig = mkOption {
+      default = "";
+      type = types.uniq types.string;
+      description = ''
+        Verbatim configuration text appended to the generated gitweb.conf file.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/gitweb" {
+          cgi.assign = (
+              ".cgi" => "${pkgs.perl}/bin/perl"
+          )
+          url.redirect = (
+              "^/gitweb$" => "/gitweb/"
+          )
+          alias.url = (
+              "/gitweb/static/" => "${pkgs.git}/share/gitweb/static/",
+              "/gitweb/"        => "${pkgs.git}/share/gitweb/gitweb.cgi"
+          )
+          setenv.add-environment = (
+              "GITWEB_CONFIG" => "${gitwebConfigFile}"
+          )
+      }
+    '';
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
new file mode 100644
index 000000000000..b26af1aa7445
--- /dev/null
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+  cfg = config.services.nginx;
+  nginx = pkgs.nginx.override { fullWebDAV = cfg.fullWebDAV; };
+  configFile = pkgs.writeText "nginx.conf" ''
+    user ${cfg.user} ${cfg.group};
+    daemon off;
+    ${cfg.config}
+  '';
+in
+
+{
+  options = {
+    services.nginx = {
+      enable = mkOption {
+        default = false;
+        description = "
+          Enable the nginx Web Server.
+        ";
+      };
+
+      config = mkOption {
+        default = "events {}";
+        description = "
+          Verbatim nginx.conf configuration.
+        ";
+      };
+
+      stateDir = mkOption {
+        default = "/var/spool/nginx";
+        description = "
+          Directory holding all state for nginx to run.
+        ";
+      };
+
+      user = mkOption {
+        default = "nginx";
+        description = "User account under which nginx runs.";
+      };
+
+      group = mkOption {
+        default = "nginx";
+        description = "Group account under which nginx runs.";
+      };
+
+      fullWebDAV = mkOption {
+        default = false;
+        description = "Compile in a third party module providing full WebDAV support";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ nginx ];
+
+    # TODO: test user supplied config file pases syntax test
+
+    systemd.services.nginx = {
+      description = "Nginx Web Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ nginx ];
+      preStart =
+        ''
+        mkdir -p ${cfg.stateDir}/logs
+        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        '';
+      serviceConfig = {
+        ExecStart = "${nginx}/bin/nginx -c ${configFile} -p ${cfg.stateDir}";
+      };
+    };
+
+    users.extraUsers = optionalAttrs (cfg.user == "nginx") (singleton
+      { name = "nginx";
+        group = "nginx";
+        uid = config.ids.uids.nginx;
+      });
+
+    users.extraGroups = optionalAttrs (cfg.group == "nginx") (singleton
+      { name = "nginx";
+        gid = config.ids.gids.nginx;
+      });
+  };
+}
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
new file mode 100644
index 000000000000..a68828de5d8e
--- /dev/null
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -0,0 +1,344 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg = config.services.tomcat;
+  tomcat = pkgs.tomcat6;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.tomcat = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable Apache Tomcat";
+      };
+
+      baseDir = mkOption {
+        default = "/var/tomcat";
+        description = "Location where Tomcat stores configuration files, webapplications and logfiles";
+      };
+
+      extraGroups = mkOption {
+        default = [];
+        example = [ "users" ];
+        description = "Defines extra groups to which the tomcat user belongs.";
+      };
+
+      user = mkOption {
+        default = "tomcat";
+        description = "User account under which Apache Tomcat runs.";
+      };
+
+      group = mkOption {
+        default = "tomcat";
+        description = "Group account under which Apache Tomcat runs.";
+      };
+
+      javaOpts = mkOption {
+        default = "";
+        description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
+      };
+
+      catalinaOpts = mkOption {
+        default = "";
+        description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
+      };
+
+      sharedLibs = mkOption {
+        default = [];
+        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
+      };
+
+      commonLibs = mkOption {
+        default = [];
+        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
+      };
+
+      webapps = mkOption {
+        default = [ tomcat ];
+        description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
+      };
+
+      virtualHosts = mkOption {
+        default = [];
+        description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
+      };
+
+      logPerVirtualHost = mkOption {
+        default = false;
+        description = "Whether to enable logging per virtual host.";
+      };
+
+      axis2 = {
+
+        enable = mkOption {
+          default = false;
+          description = "Whether to enable an Apache Axis2 container";
+        };
+
+        services = mkOption {
+          default = [];
+          description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.tomcat.enable {
+
+    users.extraGroups = singleton
+      { name = "tomcat";
+        gid = config.ids.gids.tomcat;
+      };
+
+    users.extraUsers = singleton
+      { name = "tomcat";
+        uid = config.ids.uids.tomcat;
+        description = "Tomcat user";
+        home = "/homeless-shelter";
+        extraGroups = cfg.extraGroups;
+      };
+
+    jobs.tomcat =
+      { description = "Apache Tomcat server";
+
+        startOn = "started network-interfaces";
+        stopOn = "stopping network-interfaces";
+
+        preStart =
+          ''
+            # Create the base directory
+            mkdir -p ${cfg.baseDir}
+
+            # Create a symlink to the bin directory of the tomcat component
+            ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin
+
+            # Create a conf/ directory
+            mkdir -p ${cfg.baseDir}/conf
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/conf
+
+            # Symlink the config files in the conf/ directory (except for catalina.properties and server.xml)
+            for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml)
+            do
+                ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
+            done
+
+            # Create subdirectory for virtual hosts
+            mkdir -p ${cfg.baseDir}/virtualhosts
+
+            # Create a modified catalina.properties file
+            # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
+            sed -e 's|''${catalina.home}|''${catalina.base}|g' \
+                -e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \
+                ${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties
+
+            # Create a modified server.xml which also includes all virtual hosts
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\  ${
+                         toString (map (virtualHost: ''<Host name=\"${virtualHost.name}\" appBase=\"virtualhosts/${virtualHost.name}/webapps\" unpackWARs=\"true\" autoDeploy=\"true\" xmlValidation=\"false\" xmlNamespaceAware=\"false\" >${if cfg.logPerVirtualHost then ''<Valve className=\"org.apache.catalina.valves.AccessLogValve\" directory=\"logs/${virtualHost.name}\"  prefix=\"${virtualHost.name}_access_log.\" pattern=\"combined\" resolveHosts=\"false\"/>'' else ""}</Host>'') cfg.virtualHosts)}" \
+                ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
+
+            # Create a logs/ directory
+            mkdir -p ${cfg.baseDir}/logs
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs
+            ${if cfg.logPerVirtualHost then
+               toString (map (h: ''
+                                    mkdir -p ${cfg.baseDir}/logs/${h.name}
+                                    chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
+                                 '') cfg.virtualHosts) else ''''}
+
+            # Create a temp/ directory
+            mkdir -p ${cfg.baseDir}/temp
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/temp
+
+            # Create a lib/ directory
+            mkdir -p ${cfg.baseDir}/lib
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/lib
+
+            # Create a shared/lib directory
+            mkdir -p ${cfg.baseDir}/shared/lib
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/shared/lib
+
+            # Create a webapps/ directory
+            mkdir -p ${cfg.baseDir}/webapps
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps
+
+            # Symlink all the given common libs files or paths into the lib/ directory
+            for i in ${tomcat} ${toString cfg.commonLibs}
+            do
+                if [ -f $i ]
+                then
+                    # If the given web application is a file, symlink it into the common/lib/ directory
+                    ln -sfn $i ${cfg.baseDir}/lib/`basename $i`
+                elif [ -d $i ]
+                then
+                    # If the given web application is a directory, then iterate over the files
+                    # in the special purpose directories and symlink them into the tomcat tree
+
+                    for j in $i/lib/*
+                    do
+                        ln -sfn $j ${cfg.baseDir}/lib/`basename $j`
+                    done
+                fi
+            done
+
+            # Symlink all the given shared libs files or paths into the shared/lib/ directory
+            for i in ${toString cfg.sharedLibs}
+            do
+                if [ -f $i ]
+                then
+                    # If the given web application is a file, symlink it into the common/lib/ directory
+                    ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i`
+                elif [ -d $i ]
+                then
+                    # If the given web application is a directory, then iterate over the files
+                    # in the special purpose directories and symlink them into the tomcat tree
+
+                    for j in $i/shared/lib/*
+                    do
+                        ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j`
+                    done
+                fi
+            done
+
+            # Symlink all the given web applications files or paths into the webapps/ directory
+            for i in ${toString cfg.webapps}
+            do
+                if [ -f $i ]
+                then
+                    # If the given web application is a file, symlink it into the webapps/ directory
+                    ln -sfn $i ${cfg.baseDir}/webapps/`basename $i`
+                elif [ -d $i ]
+                then
+                    # If the given web application is a directory, then iterate over the files
+                    # in the special purpose directories and symlink them into the tomcat tree
+
+                    for j in $i/webapps/*
+                    do
+                        ln -sfn $j ${cfg.baseDir}/webapps/`basename $j`
+                    done
+
+                    # Also symlink the configuration files if they are included
+                    if [ -d $i/conf/Catalina ]
+                    then
+                        for j in $i/conf/Catalina/*
+                        do
+                            mkdir -p ${cfg.baseDir}/conf/Catalina/localhost
+                            ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+                        done
+                    fi
+                fi
+            done
+
+            ${toString (map (virtualHost: ''
+              # Create webapps directory for the virtual host
+              mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+              # Modify ownership
+              chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+              # Symlink all the given web applications files or paths into the webapps/ directory
+              # of this virtual host
+              for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"
+              do
+                  if [ -f $i ]
+                  then
+                      # If the given web application is a file, symlink it into the webapps/ directory
+                      ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
+                  elif [ -d $i ]
+                  then
+                      # If the given web application is a directory, then iterate over the files
+                      # in the special purpose directories and symlink them into the tomcat tree
+
+                      for j in $i/webapps/*
+                      do
+                          ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j`
+                      done
+
+                      # Also symlink the configuration files if they are included
+                      if [ -d $i/conf/Catalina ]
+                      then
+                          for j in $i/conf/Catalina/*
+                          do
+                              mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name}
+                              ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j`
+                          done
+                      fi
+                  fi
+              done
+
+              ''
+            ) cfg.virtualHosts) }
+
+            # Create a work/ directory
+            mkdir -p ${cfg.baseDir}/work
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/work
+
+            ${if cfg.axis2.enable then
+                ''
+                # Copy the Axis2 web application
+                cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
+
+                # Turn off addressing, which causes many errors
+                sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml
+
+                # Modify permissions on the Axis2 application
+                chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2
+
+                # Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory
+                for i in ${toString cfg.axis2.services}
+                do
+                    if [ -f $i ]
+                    then
+                        # If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services
+                        ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i`
+                    elif [ -d $i ]
+                    then
+                        # If the given web application is a directory, then iterate over the files
+                        # in the special purpose directories and symlink them into the tomcat tree
+
+                        for j in $i/webapps/axis2/WEB-INF/services/*
+                        do
+                            ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j`
+                        done
+
+                        # Also symlink the configuration files if they are included
+                        if [ -d $i/conf/Catalina ]
+                        then
+                            for j in $i/conf/Catalina/*
+                            do
+                                ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+                            done
+                        fi
+                    fi
+                done
+                ''
+            else ""}
+
+            ${pkgs.su}/bin/su -s ${pkgs.bash}/bin/sh ${cfg.user} -c 'CATALINA_BASE=${cfg.baseDir} JAVA_HOME=${pkgs.jdk} JAVA_OPTS="${cfg.javaOpts}" CATALINA_OPTS="${cfg.catalinaOpts}" ${tomcat}/bin/startup.sh'
+          '';
+
+        postStop =
+          ''
+            echo "Stopping tomcat..."
+            CATALINA_BASE=${cfg.baseDir} JAVA_HOME=${pkgs.jdk} ${pkgs.su}/bin/su -s ${pkgs.bash}/bin/sh ${cfg.user} -c ${tomcat}/bin/shutdown.sh
+          '';
+
+      };
+
+  };
+
+}
diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix
new file mode 100644
index 000000000000..7e327120c3d1
--- /dev/null
+++ b/nixos/modules/services/web-servers/varnish/default.nix
@@ -0,0 +1,63 @@
+{ config, pkgs, ...}:
+let
+  cfg = config.services.varnish;
+
+in
+with pkgs.lib;
+{
+  options = {
+    services.varnish = {
+      enable = mkOption {
+        default = false;
+        description = "
+          Enable the Varnish Server.
+        ";
+      };
+
+      http_address = mkOption {
+        default = "*:6081";
+        description = "
+          HTTP listen address and port.
+        ";
+      };
+
+      config = mkOption {
+        description = "
+          Verbatim default.vcl configuration.
+        ";
+      };
+
+      stateDir = mkOption {
+        default = "/var/spool/varnish";
+        description = "
+          Directory holding all state for Varnish to run.
+        ";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.varnish = {
+      description = "Varnish";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -p ${cfg.stateDir}
+        chown -R varnish:varnish ${cfg.stateDir}
+      '';
+      path = [ pkgs.gcc ];
+      serviceConfig.ExecStart = "${pkgs.varnish}/sbin/varnishd -a ${cfg.http_address} -f ${pkgs.writeText "default.vcl" cfg.config} -n ${cfg.stateDir} -u varnish";
+      serviceConfig.Type = "forking";
+    };
+
+    environment.systemPackages = [ pkgs.varnish ];
+
+    users.extraUsers.varnish = {
+      group = "varnish";
+      uid = config.ids.uids.varnish;
+    };
+
+    users.extraGroups.varnish.gid = config.ids.uids.varnish;
+  };
+}
diff --git a/nixos/modules/services/web-servers/zope2.nix b/nixos/modules/services/web-servers/zope2.nix
new file mode 100644
index 000000000000..19afa55d7fef
--- /dev/null
+++ b/nixos/modules/services/web-servers/zope2.nix
@@ -0,0 +1,249 @@
+{ pkgs, config, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg = config.services.zope2;
+
+  zope2Opts = { name, config, ... }: {
+    options = {
+
+      name = mkOption {
+        default = "${name}";
+        type = types.string;
+        description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
+      };
+
+      threads = mkOption {
+        default = 2;
+        type = types.int;
+        description = "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
+      };
+
+      http_address = mkOption {
+        default = "localhost:8080";
+        type = types.string;
+        description = "Give a port and adress for the HTTP server.";
+      };
+
+      user = mkOption {
+        default = "zope2";
+        type = types.string;
+        description = "The name of the effective user for the Zope process.";
+      };
+
+      extra = mkOption {
+        default =
+          ''
+          <zodb_db main>
+          mount-point /
+          cache-size 30000
+          <blobstorage>
+              blob-dir /var/lib/zope2/${name}/blobstorage
+              <filestorage>
+              path /var/lib/zope2/${name}/filestorage/Data.fs
+              </filestorage>
+          </blobstorage>
+          </zodb_db>
+          '';
+        type = types.string;
+        description = "Extra zope.conf";
+      };
+
+      packages = mkOption {
+        type = types.listOf types.package;
+        description = "The list of packages you want to make available to the zope2 instance.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.zope2.instances = mkOption {
+      default = {};
+      type = types.loaOf types.optionSet;
+      example = {
+        plone01 = {
+          http_address = "127.0.0.1:8080";
+          extra =
+            ''
+            <zodb_db main>
+            mount-point /
+            cache-size 30000
+            <blobstorage>
+                blob-dir /var/lib/zope2/plone01/blobstorage
+                <filestorage>
+                path /var/lib/zope2/plone01/filestorage/Data.fs
+                </filestorage>
+            </blobstorage>
+            </zodb_db>
+            '';
+
+        };
+      };
+      description = "zope2 instances to be created automaticaly by the system.";
+      options = [ zope2Opts ];
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf (cfg.instances != {}) {
+
+    users.extraUsers.zope2.uid = config.ids.uids.zope2;
+
+    systemd.services =
+      let
+
+        createZope2Instance = opts: name:
+          let
+            interpreter = pkgs.writeScript "interpreter"
+              ''
+import sys
+
+_interactive = True
+if len(sys.argv) > 1:
+    _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
+    _interactive = False
+    for (_opt, _val) in _options:
+        if _opt == '-i':
+            _interactive = True
+        elif _opt == '-c':
+            exec _val
+        elif _opt == '-m':
+            sys.argv[1:] = _args
+            _args = []
+            __import__("runpy").run_module(
+                 _val, {}, "__main__", alter_sys=True)
+
+    if _args:
+        sys.argv[:] = _args
+        __file__ = _args[0]
+        del _options, _args
+        execfile(__file__)
+
+if _interactive:
+    del _interactive
+    __import__("code").interact(banner="", local=globals())
+              '';
+            env = pkgs.buildEnv {
+              name = "zope2-${name}-env";
+              paths = [
+                pkgs.python27
+                pkgs.python27Packages.recursivePthLoader
+                pkgs.python27Packages."plone.recipe.zope2instance"
+              ] ++ attrValues pkgs.python27.modules
+                ++ opts.packages;
+              postBuild =
+                ''
+                echo "#!$out/bin/python" > $out/bin/interpreter
+                cat ${interpreter} >> $out/bin/interpreter
+                '';
+            };
+            conf = pkgs.writeText "zope2-${name}-conf"
+              ''%define INSTANCEHOME ${env}
+instancehome $INSTANCEHOME
+%define CLIENTHOME /var/lib/zope2/${name}
+clienthome $CLIENTHOME
+
+debug-mode off
+security-policy-implementation C
+verbose-security off
+default-zpublisher-encoding utf-8
+zserver-threads ${toString opts.threads}
+effective-user ${opts.user}
+
+pid-filename /var/lib/zope2/${name}/pid
+lock-filename /var/lib/zope2/${name}/lock
+python-check-interval 1000
+enable-product-installation off
+
+<environment>
+  zope_i18n_compile_mo_files false
+</environment>
+
+<eventlog>
+level INFO
+<logfile>
+    path /var/log/zope2/${name}.log
+    level INFO
+</logfile>
+</eventlog>
+
+<logger access>
+level WARN
+<logfile>
+    path /var/log/zope2/${name}-Z2.log
+    format %(message)s
+</logfile>
+</logger>
+
+<http-server>
+address ${opts.http_address}
+</http-server>
+
+<zodb_db temporary>
+<temporarystorage>
+    name temporary storage for sessioning
+</temporarystorage>
+mount-point /temp_folder
+container-class Products.TemporaryFolder.TemporaryContainer
+</zodb_db>
+
+${opts.extra}
+              '';
+            ctlScript = pkgs.writeScript "zope2-${name}-ctl-script"
+              ''#!${env}/bin/python
+
+import sys
+import plone.recipe.zope2instance.ctl
+
+if __name__ == '__main__':
+    sys.exit(plone.recipe.zope2instance.ctl.main(
+        ["-C", "${conf}"]
+        + sys.argv[1:]))
+              '';
+
+            ctl = pkgs.writeScript "zope2-${name}-ctl"
+              ''#!${pkgs.bash}/bin/bash -e
+export PYTHONHOME=${env}
+exec ${ctlScript} "$@"
+              '';
+          in {
+            description = "zope2 ${name} instance";
+            after = [ "network.target" ];  # with RelStorage also add "postgresql.service"
+            wantedBy = [ "multi-user.target" ];
+            path = opts.packages;
+            preStart =
+              ''
+              mkdir -p /var/log/zope2/
+              touch /var/log/zope2/${name}.log
+              touch /var/log/zope2/${name}-Z2.log
+              chown ${opts.user} /var/log/zope2/${name}.log
+              chown ${opts.user} /var/log/zope2/${name}-Z2.log
+
+              mkdir -p /var/lib/zope2/${name}/filestorage /var/lib/zope2/${name}/blobstorage
+              chown ${opts.user} /var/lib/zope2/${name} -R
+
+              ${ctl} adduser admin admin
+              '';
+
+            serviceConfig.Type = "forking";
+            serviceConfig.ExecStart = "${ctl} start";
+            serviceConfig.ExecStop = "${ctl} stop";
+            serviceConfig.ExecReload = "${ctl} restart";
+          };
+
+      in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances));
+
+  };
+
+}