mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-10 03:23:29 +03:00
478 lines
14 KiB
Nix
478 lines
14 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
any
|
|
attrValues
|
|
flatten
|
|
literalExpression
|
|
mapAttrs
|
|
mapAttrs'
|
|
mapAttrsToList
|
|
mkDefault
|
|
mkEnableOption
|
|
mkIf
|
|
mkMerge
|
|
mkOption
|
|
mkPackageOption
|
|
nameValuePair
|
|
optionalAttrs
|
|
types
|
|
;
|
|
inherit (pkgs)
|
|
mariadb
|
|
stdenv
|
|
writeShellScript
|
|
;
|
|
cfg = config.services.drupal;
|
|
eachSite = cfg.sites;
|
|
user = "drupal";
|
|
webserver = config.services.${cfg.webserver};
|
|
|
|
pkg =
|
|
hostName: cfg:
|
|
stdenv.mkDerivation (finalAttrs: {
|
|
pname = "drupal-${hostName}";
|
|
name = "drupal-${hostName}";
|
|
src = cfg.package;
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
mkdir -p $out
|
|
cp -r * $out/
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
postInstall = ''
|
|
ln -s ${cfg.filesDir} $out/share/php/drupal/sites/default/files
|
|
ln -s ${cfg.stateDir}/sites/default/settings.php $out/share/php/drupal/sites/default/settings.php
|
|
ln -s ${cfg.modulesDir} $out/share/php/drupal/modules
|
|
ln -s ${cfg.themesDir} $out/share/php/drupal/themes
|
|
'';
|
|
});
|
|
|
|
siteOpts =
|
|
{
|
|
options,
|
|
config,
|
|
lib,
|
|
name,
|
|
...
|
|
}:
|
|
{
|
|
options = {
|
|
enable = mkEnableOption "Drupal web application";
|
|
package = mkPackageOption pkgs "drupal" { };
|
|
|
|
filesDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/drupal/${name}/sites/default/files";
|
|
defaultText = "/var/lib/drupal/<name>/sites/default/files";
|
|
description = ''
|
|
The location of the Drupal files directory.
|
|
'';
|
|
};
|
|
|
|
stateDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/drupal/${name}";
|
|
defaultText = "/var/lib/drupal/<name>";
|
|
description = "The location of the Drupal site state directory.";
|
|
};
|
|
|
|
modulesDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/drupal/${name}/modules";
|
|
defaultText = "/var/lib/drupal/<name>/modules";
|
|
description = "The location for users to install Drupal modules.";
|
|
};
|
|
|
|
themesDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/drupal/${name}/themes";
|
|
defaultText = "/var/lib/drupal/<name>/themes";
|
|
description = "The location for users to install Drupal themes.";
|
|
};
|
|
|
|
phpOptions = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = { };
|
|
description = ''
|
|
Options for PHP's php.ini file for this Drupal site.
|
|
'';
|
|
example = literalExpression ''
|
|
{
|
|
"opcache.interned_strings_buffer" = "8";
|
|
"opcache.max_accelerated_files" = "10000";
|
|
"opcache.memory_consumption" = "128";
|
|
"opcache.revalidate_freq" = "15";
|
|
"opcache.fast_shutdown" = "1";
|
|
}
|
|
'';
|
|
};
|
|
|
|
database = {
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "localhost";
|
|
description = "Database host address.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 3306;
|
|
description = "Database host port.";
|
|
};
|
|
|
|
name = mkOption {
|
|
type = types.str;
|
|
default = "drupal";
|
|
description = "Database name.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "drupal";
|
|
description = "Database user.";
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
example = "/run/keys/database-dbpassword";
|
|
description = ''
|
|
A file containing the password corresponding to
|
|
{option}`database.user`.
|
|
'';
|
|
};
|
|
|
|
tablePrefix = mkOption {
|
|
type = types.str;
|
|
default = "dp_";
|
|
description = ''
|
|
The $table_prefix is the value placed in the front of your database tables.
|
|
Change the value if you want to use something other than dp_ for your database
|
|
prefix. Typically this is changed if you are installing multiple Drupal sites
|
|
in the same database.
|
|
'';
|
|
};
|
|
|
|
socket = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
defaultText = literalExpression "/run/mysqld/mysqld.sock";
|
|
description = "Path to the unix socket file to use for authentication.";
|
|
};
|
|
|
|
createLocally = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Create the database and database user locally.";
|
|
};
|
|
};
|
|
|
|
virtualHost = mkOption {
|
|
type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
|
|
example = literalExpression ''
|
|
{
|
|
adminAddr = "webmaster@example.org";
|
|
forceSSL = true;
|
|
enableACME = true;
|
|
}
|
|
'';
|
|
description = ''
|
|
Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
|
|
'';
|
|
};
|
|
|
|
poolConfig = mkOption {
|
|
type =
|
|
with types;
|
|
attrsOf (oneOf [
|
|
str
|
|
int
|
|
bool
|
|
]);
|
|
default = {
|
|
"pm" = "dynamic";
|
|
"pm.max_children" = 32;
|
|
"pm.start_servers" = 2;
|
|
"pm.min_spare_servers" = 2;
|
|
"pm.max_spare_servers" = 4;
|
|
"pm.max_requests" = 500;
|
|
};
|
|
description = ''
|
|
Options for the Drupal PHP pool. See the documentation on `php-fpm.conf`
|
|
for details on configuration directives.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config.virtualHost.hostName = mkDefault name;
|
|
};
|
|
in
|
|
{
|
|
options = {
|
|
services.drupal = {
|
|
enable = mkEnableOption "drupal";
|
|
package = mkPackageOption pkgs "drupal" { };
|
|
|
|
sites = mkOption {
|
|
type = types.attrsOf (types.submodule siteOpts);
|
|
default = {
|
|
"localhost" = {
|
|
enable = true;
|
|
};
|
|
};
|
|
description = "Specification of one or more Drupal sites to serve";
|
|
};
|
|
|
|
webserver = mkOption {
|
|
type = types.enum [
|
|
"nginx"
|
|
"caddy"
|
|
];
|
|
default = "nginx";
|
|
description = ''
|
|
Whether to use nginx or caddy for virtual host management.
|
|
|
|
Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
|
|
See [](#opt-services.nginx.virtualHosts) for further information.
|
|
|
|
Further caddy configuration can be done by adapting `services.caddy.virtualHosts.<name>`.
|
|
See [](#opt-services.caddy.virtualHosts) for further information.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf (cfg.enable) (mkMerge [
|
|
{
|
|
|
|
assertions =
|
|
(mapAttrsToList (hostName: cfg: {
|
|
assertion = cfg.database.createLocally -> cfg.database.user == user;
|
|
message = ''services.drupal.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
|
|
}) eachSite)
|
|
++ (mapAttrsToList (hostName: cfg: {
|
|
assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
|
|
message = ''services.drupal.sites."${hostName}".database.passwordFile cannot be specified if services.drupal.sites."${hostName}".database.createLocally is set to true.'';
|
|
}) eachSite);
|
|
|
|
services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
|
|
enable = true;
|
|
package = mkDefault mariadb;
|
|
ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
|
|
ensureUsers = mapAttrsToList (hostName: cfg: {
|
|
name = cfg.database.user;
|
|
ensurePermissions = {
|
|
"${cfg.database.name}.*" = "ALL PRIVILEGES";
|
|
};
|
|
}) eachSite;
|
|
};
|
|
|
|
services.phpfpm.pools = mapAttrs' (
|
|
hostName: cfg:
|
|
(nameValuePair "drupal-${hostName}" {
|
|
inherit user;
|
|
group = webserver.group;
|
|
settings = {
|
|
"listen.owner" = webserver.user;
|
|
"listen.group" = webserver.group;
|
|
} // cfg.poolConfig;
|
|
})
|
|
) eachSite;
|
|
}
|
|
|
|
{
|
|
systemd.tmpfiles.rules = flatten (
|
|
mapAttrsToList (hostName: cfg: [
|
|
"d '${cfg.stateDir}' 0750 ${user} ${webserver.group} - -"
|
|
"d '${cfg.modulesDir}' 0750 ${user} ${webserver.group} - -"
|
|
"Z '${cfg.modulesDir}' 0750 ${user} ${webserver.group} - -"
|
|
"d '${cfg.themesDir}' 0750 ${user} ${webserver.group} - -"
|
|
"Z '${cfg.themesDir}' 0750 ${user} ${webserver.group} - -"
|
|
]) eachSite
|
|
);
|
|
|
|
users.users.${user} = {
|
|
group = webserver.group;
|
|
isSystemUser = true;
|
|
};
|
|
}
|
|
|
|
{
|
|
# Run a service that prepares the state directory.
|
|
systemd.services = mkMerge [
|
|
(mapAttrs' (
|
|
hostName: cfg:
|
|
(nameValuePair "drupal-state-init-${hostName}" {
|
|
wantedBy = [ "multi-user.target" ];
|
|
before = [ "nginx.service" ];
|
|
after = [ "local-fs.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = "root";
|
|
ExecStart = writeShellScript "drupal-state-init-${hostName}" ''
|
|
set -e
|
|
|
|
if [ ! -d "${cfg.stateDir}/sites" ]; then
|
|
echo "Preparing sites directory..."
|
|
cp -r "${cfg.package}/share/php/drupal/sites" "${cfg.stateDir}"
|
|
fi
|
|
|
|
if [ ! -d "${cfg.filesDir}" ]; then
|
|
echo "Preparing files directory..."
|
|
mkdir -p "${cfg.filesDir}"
|
|
chown -R ${user}:${webserver.group} ${cfg.filesDir}
|
|
fi
|
|
|
|
settings="${cfg.stateDir}/sites/default/settings.php"
|
|
defaultSettings="${cfg.package}/share/php/drupal/sites/default/default.settings.php"
|
|
|
|
if [ ! -f "$settings" ]; then
|
|
echo "Preparing settings.php for ${hostName}..."
|
|
cp "$defaultSettings" "$settings"
|
|
chmod 644 "$settings"
|
|
fi
|
|
|
|
# Set or reset file permissions so that the web user and webserver owns them.
|
|
chown -R ${user}:${webserver.group} ${cfg.stateDir}
|
|
'';
|
|
};
|
|
})
|
|
) eachSite)
|
|
|
|
(optionalAttrs (any (v: v.database.createLocally) (attrValues eachSite)) {
|
|
httpd.after = [ "mysql.service" ];
|
|
})
|
|
];
|
|
}
|
|
|
|
(mkIf (cfg.webserver == "nginx") {
|
|
services.nginx = {
|
|
enable = true;
|
|
virtualHosts = mapAttrs (hostName: cfg: {
|
|
serverName = mkDefault hostName;
|
|
root = "${pkg hostName cfg}/share/php/drupal";
|
|
extraConfig = ''
|
|
index index.php;
|
|
'';
|
|
locations = {
|
|
"~ '\.php$|^/update.php'" = {
|
|
extraConfig = ''
|
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
fastcgi_pass unix:${config.services.phpfpm.pools."drupal-${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;
|
|
'';
|
|
};
|
|
"= /favicon.ico" = {
|
|
extraConfig = ''
|
|
log_not_found off;
|
|
access_log off;
|
|
'';
|
|
};
|
|
"= /robots.txt" = {
|
|
extraConfig = ''
|
|
allow all;
|
|
log_not_found off;
|
|
access_log off;
|
|
'';
|
|
};
|
|
"~ \..*/.*\.php$" = {
|
|
extraConfig = ''
|
|
return 403;
|
|
'';
|
|
};
|
|
"~ ^/sites/.*/private/" = {
|
|
extraConfig = ''
|
|
return 403;
|
|
'';
|
|
};
|
|
"~ ^/sites/[^/]+/files/.*\.php$" = {
|
|
extraConfig = ''
|
|
deny all;
|
|
'';
|
|
};
|
|
"~* ^/.well-known/" = {
|
|
extraConfig = ''
|
|
allow all;
|
|
'';
|
|
};
|
|
"/" = {
|
|
extraConfig = ''
|
|
try_files $uri /index.php?$query_string;
|
|
'';
|
|
};
|
|
"@rewrite" = {
|
|
extraConfig = ''
|
|
rewrite ^ /index.php;
|
|
'';
|
|
};
|
|
"~ /vendor/.*\.php$" = {
|
|
extraConfig = ''
|
|
deny all;
|
|
return 404;
|
|
'';
|
|
};
|
|
"~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$" = {
|
|
extraConfig = ''
|
|
try_files $uri @rewrite;
|
|
expires max;
|
|
log_not_found off;
|
|
'';
|
|
};
|
|
"~ ^/sites/.*/files/styles/" = {
|
|
extraConfig = ''
|
|
try_files $uri @rewrite;
|
|
'';
|
|
};
|
|
"~ ^(/[a-z\-]+)?/system/files/" = {
|
|
extraConfig = ''
|
|
try_files $uri /index.php?$query_string;
|
|
'';
|
|
};
|
|
};
|
|
}) eachSite;
|
|
};
|
|
})
|
|
|
|
(mkIf (cfg.webserver == "caddy") {
|
|
services.caddy = {
|
|
enable = true;
|
|
virtualHosts = mapAttrs' (
|
|
hostName: cfg:
|
|
(nameValuePair "http://${hostName}" {
|
|
extraConfig = ''
|
|
root * ${pkg hostName cfg}/share/php/drupal
|
|
file_server
|
|
|
|
encode zstd gzip
|
|
php_fastcgi unix/${config.services.phpfpm.pools."drupal-${hostName}".socket}
|
|
'';
|
|
})
|
|
) cfg.sites;
|
|
};
|
|
})
|
|
|
|
]);
|
|
}
|