diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index b9967ffb982..fcaac9e8bec 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -546,6 +546,22 @@
claws-mail-gtk2 package.
+
+
+ The wordpress module provides a new interface which allows to
+ use different webservers with the new option
+ services.wordpress.webserver.
+ Currently httpd and
+ nginx are supported. The definitions of
+ wordpress sites should now be set in
+ services.wordpress.sites.
+
+
+ Sites definitions that use the old interface are automatically
+ migrated in the new option. This backward compatibility will
+ be removed in 22.05.
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 377dbf598d9..030f1d21818 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -135,3 +135,7 @@ In addition to numerous new and upgraded packages, this release has the followin
- Sway: The terminal emulator `rxvt-unicode` is no longer installed by default via `programs.sway.extraPackages`. The current default configuration uses `alacritty` (and soon `foot`) so this is only an issue when using a customized configuration and not installing `rxvt-unicode` explicitly.
- The `claws-mail` package now references the new GTK+ 3 release branch, major version 4. To use the GTK+ 2 releases, one can install the `claws-mail-gtk2` package.
+
+- The wordpress module provides a new interface which allows to use different webservers with the new option [`services.wordpress.webserver`](options.html#opt-services.wordpress.webserver). Currently `httpd` and `nginx` are supported. The definitions of wordpress sites should now be set in [`services.wordpress.sites`](options.html#opt-services.wordpress.sites).
+
+ Sites definitions that use the old interface are automatically migrated in the new option. This backward compatibility will be removed in 22.05.
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 775ecb3acaf..6f1ef815bc4 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -3,13 +3,18 @@
let
inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
- inherit (lib) mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
+ inherit (lib) filterAttrs mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
- eachSite = config.services.wordpress;
+ cfg = migrateOldAttrs config.services.wordpress;
+ eachSite = cfg.sites;
user = "wordpress";
- group = config.services.httpd.group;
+ webserver = config.services.${cfg.webserver};
stateDir = hostName: "/var/lib/wordpress/${hostName}";
+ # Migrate config.services.wordpress. to config.services.wordpress.sites.
+ oldSites = filterAttrs (o: _: o != "sites" && o != "webserver");
+ migrateOldAttrs = cfg: cfg // { sites = cfg.sites // oldSites cfg; };
+
pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
pname = "wordpress-${hostName}";
version = src.version;
@@ -261,21 +266,48 @@ in
# interface
options = {
services.wordpress = mkOption {
- type = types.attrsOf (types.submodule siteOpts);
+ type = types.submodule {
+ # Used to support old interface
+ freeformType = types.attrsOf (types.submodule siteOpts);
+
+ # New interface
+ options.sites = mkOption {
+ type = types.attrsOf (types.submodule siteOpts);
+ default = {};
+ description = "Specification of one or more WordPress sites to serve";
+ };
+
+ options.webserver = mkOption {
+ type = types.enum [ "httpd" "nginx" ];
+ default = "httpd";
+ description = ''
+ Whether to use apache2 or nginx for virtual host management.
+
+ Further nginx configuration can be done by adapting services.nginx.virtualHosts.<name>.
+ See for further information.
+
+ Further apache2 configuration can be done by adapting services.httpd.virtualHosts.<name>.
+ See for further information.
+ '';
+ };
+ };
default = {};
- description = "Specification of one or more WordPress sites to serve via Apache.";
+ description = "Wordpress configuration";
};
+
};
# implementation
- config = mkIf (eachSite != {}) {
+ config = mkIf (eachSite != {}) (mkMerge [{
assertions = mapAttrsToList (hostName: cfg:
{ assertion = cfg.database.createLocally -> cfg.database.user == user;
- message = "services.wordpress.${hostName}.database.user must be ${user} if the database is to be automatically provisioned";
+ message = ''services.wordpress.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
}
) eachSite;
+ warnings = mapAttrsToList (hostName: _: ''services.wordpress."${hostName}" is deprecated use services.wordpress.sites."${hostName}"'') (oldSites cfg);
+
services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
enable = true;
package = mkDefault pkgs.mariadb;
@@ -289,14 +321,18 @@ in
services.phpfpm.pools = mapAttrs' (hostName: cfg: (
nameValuePair "wordpress-${hostName}" {
- inherit user group;
+ inherit user;
+ group = webserver.group;
settings = {
- "listen.owner" = config.services.httpd.user;
- "listen.group" = config.services.httpd.group;
+ "listen.owner" = webserver.user;
+ "listen.group" = webserver.group;
} // cfg.poolConfig;
}
)) eachSite;
+ }
+
+ (mkIf (cfg.webserver == "httpd") {
services.httpd = {
enable = true;
extraModules = [ "proxy_fcgi" ];
@@ -332,11 +368,13 @@ in
'';
} ]) eachSite;
};
+ })
+ {
systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
- "d '${stateDir hostName}' 0750 ${user} ${group} - -"
- "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
- "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+ "d '${stateDir hostName}' 0750 ${user} ${webserver.group} - -"
+ "d '${cfg.uploadsDir}' 0750 ${user} ${webserver.group} - -"
+ "Z '${cfg.uploadsDir}' 0750 ${user} ${webserver.group} - -"
]) eachSite);
systemd.services = mkMerge [
@@ -350,7 +388,7 @@ in
serviceConfig = {
Type = "oneshot";
User = user;
- Group = group;
+ Group = webserver.group;
};
})) eachSite)
@@ -360,9 +398,65 @@ in
];
users.users.${user} = {
- group = group;
+ group = webserver.group;
isSystemUser = true;
};
+ }
- };
+ (mkIf (cfg.webserver == "nginx") {
+ services.nginx = {
+ enable = true;
+ virtualHosts = mapAttrs (hostName: cfg: {
+ serverName = mkDefault hostName;
+ root = "${pkg hostName cfg}/share/wordpress";
+ extraConfig = ''
+ index index.php;
+ '';
+ locations = {
+ "/" = {
+ priority = 200;
+ extraConfig = ''
+ try_files $uri $uri/ /index.php$is_args$args;
+ '';
+ };
+ "~ \\.php$" = {
+ priority = 500;
+ extraConfig = ''
+ fastcgi_split_path_info ^(.+\.php)(/.+)$;
+ fastcgi_pass unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket};
+ fastcgi_index index.php;
+ include "${config.services.nginx.package}/conf/fastcgi.conf";
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
+ # Mitigate https://httpoxy.org/ vulnerabilities
+ fastcgi_param HTTP_PROXY "";
+ fastcgi_intercept_errors off;
+ fastcgi_buffer_size 16k;
+ fastcgi_buffers 4 16k;
+ fastcgi_connect_timeout 300;
+ fastcgi_send_timeout 300;
+ fastcgi_read_timeout 300;
+ '';
+ };
+ "~ /\\." = {
+ priority = 800;
+ extraConfig = "deny all;";
+ };
+ "~* /(?:uploads|files)/.*\\.php$" = {
+ priority = 900;
+ extraConfig = "deny all;";
+ };
+ "~* \\.(js|css|png|jpg|jpeg|gif|ico)$" = {
+ priority = 1000;
+ extraConfig = ''
+ expires max;
+ log_not_found off;
+ '';
+ };
+ };
+ }) eachSite;
+ };
+ })
+
+ ]);
}
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index a5c10c2de74..45c58b5b65c 100644
--- a/nixos/tests/wordpress.nix
+++ b/nixos/tests/wordpress.nix
@@ -10,48 +10,68 @@ import ./make-test-python.nix ({ pkgs, ... }:
];
};
- machine =
- { ... }:
- { services.httpd.adminAddr = "webmaster@site.local";
+ nodes = {
+ wp_httpd = { ... }: {
+ services.httpd.adminAddr = "webmaster@site.local";
services.httpd.logPerVirtualHost = true;
- services.wordpress."site1.local" = {
- database.tablePrefix = "site1_";
- };
-
- services.wordpress."site2.local" = {
- database.tablePrefix = "site2_";
+ services.wordpress = {
+ # Test support for old interface
+ "site1.local" = {
+ database.tablePrefix = "site1_";
+ };
+ sites = {
+ "site2.local" = {
+ database.tablePrefix = "site2_";
+ };
+ };
};
+ networking.firewall.allowedTCPPorts = [ 80 ];
networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
};
+ wp_nginx = { ... }: {
+ services.wordpress.webserver = "nginx";
+ services.wordpress.sites = {
+ "site1.local" = {
+ database.tablePrefix = "site1_";
+ };
+ "site2.local" = {
+ database.tablePrefix = "site2_";
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = [ 80 ];
+ networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+ };
+ };
+
testScript = ''
import re
start_all()
- machine.wait_for_unit("httpd")
-
- machine.wait_for_unit("phpfpm-wordpress-site1.local")
- machine.wait_for_unit("phpfpm-wordpress-site2.local")
+ wp_httpd.wait_for_unit("httpd")
+ wp_nginx.wait_for_unit("nginx")
site_names = ["site1.local", "site2.local"]
- with subtest("website returns welcome screen"):
+ for machine in (wp_httpd, wp_nginx):
for site_name in site_names:
- assert "Welcome to the famous" in machine.succeed(f"curl -fL {site_name}")
+ machine.wait_for_unit(f"phpfpm-wordpress-{site_name}")
- with subtest("wordpress-init went through"):
- for site_name in site_names:
- info = machine.get_unit_info(f"wordpress-init-{site_name}")
- assert info["Result"] == "success"
+ with subtest("website returns welcome screen"):
+ assert "Welcome to the famous" in machine.succeed(f"curl -L {site_name}")
- with subtest("secret keys are set"):
- pattern = re.compile(r"^define.*NONCE_SALT.{64,};$", re.MULTILINE)
- for site_name in site_names:
- assert pattern.search(
- machine.succeed(f"cat /var/lib/wordpress/{site_name}/secret-keys.php")
- )
+ with subtest("wordpress-init went through"):
+ info = machine.get_unit_info(f"wordpress-init-{site_name}")
+ assert info["Result"] == "success"
+
+ with subtest("secret keys are set"):
+ pattern = re.compile(r"^define.*NONCE_SALT.{64,};$", re.MULTILINE)
+ assert pattern.search(
+ machine.succeed(f"cat /var/lib/wordpress/{site_name}/secret-keys.php")
+ )
'';
})