nixos/drupal: refactor sites dir interface

This commit is contained in:
Andrew Benbow
2026-04-05 16:28:20 -04:00
parent 51866ac4a4
commit f2cfbe07c3
2 changed files with 156 additions and 40 deletions
+2
View File
@@ -319,3 +319,5 @@ gnuradioMinimal.override {
### Additions and Improvements {#sec-nixpkgs-release-26.05-lib-additions-improvements}
- The builder `php.buildComposerProject2` for PHP applications has been improved for better reliability and stability.
- The `services.drupal` module has a few improvements aimed at making it better for installing custom Drupal instances, namely a `webRoot` option and a some new settings for managing variable content and filepaths.
+154 -40
View File
@@ -10,6 +10,7 @@ let
any
attrValues
flatten
getExe
literalExpression
mapAttrs
mapAttrs'
@@ -24,6 +25,9 @@ let
optionalAttrs
types
;
inherit (lib.strings)
removePrefix
;
inherit (pkgs)
mariadb
stdenv
@@ -41,23 +45,42 @@ let
name = "drupal-${hostName}";
src = cfg.package;
buildInputs = [ pkgs.rsync ];
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r * $out/
rsync -aq * $out/ --exclude=${removePrefix "/" cfg.webRoot}/sites --exclude=sites
runHook postInstall
'';
postInstall = ''
ln -s ${cfg.filesDir} $out/share/php/${cfg.package.pname}/sites/default/files
ln -s ${cfg.stateDir}/sites/default/settings.php $out/share/php/${cfg.package.pname}/sites/default/settings.php
ln -s ${cfg.stateDir}/sites $out/share/php/${cfg.package.pname}${cfg.webRoot}
ln -s ${cfg.modulesDir} $out/share/php/${cfg.package.pname}/modules
ln -s ${cfg.themesDir} $out/share/php/${cfg.package.pname}/themes
'';
});
sites =
hostName: cfg:
stdenv.mkDerivation (finalAttrs: {
pname = "drupal-sites-${hostName}";
name = "drupal-sites-${hostName}";
src = cfg.package;
buildInputs = with pkgs; [ rsync ];
installPhase = ''
runHook preInstall
mkdir -p $out/sites
rsync -a ./share/php/${cfg.package.pname}${cfg.webRoot}/sites/* $out/sites/
runHook postInstall
'';
});
drupalSettings =
hostName: cfg:
pkgs.writeTextFile {
@@ -87,6 +110,75 @@ let
'';
};
# Required .htaccess for private files directory
# See: https://www.drupal.org/docs/getting-started/installing-drupal/securing-drupal-file-directories
privateFilesHtAccess = pkgs.writeTextFile {
name = "private-files-htaccess";
text = ''
# Turn off all options we don't need.
Options -Indexes -ExecCGI -Includes -MultiViews
# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
# Override the handler again if we're run later in the evaluation list.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>
# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php.c>
php_flag engine off
</IfModule>
'';
};
stateDirManage =
hostName: cfg:
pkgs.writeShellApplication {
name = "drupal-state-init-${hostName}";
excludeShellChecks = [ "SC2194" ];
runtimeInputs = with pkgs; [ rsync ];
text = ''
echo "Updating the sites directory for ${hostName}..."
rsync -auq "${sites hostName cfg}/sites/" "${cfg.stateDir}/sites/" \
--exclude "*/files" \
--delete-before
if [ ! -d "${cfg.filesDir}" ]; then
echo "Preparing files directory..."
mkdir -p "${cfg.filesDir}"
chown -R ${user}:${webserver.group} ${cfg.filesDir}
fi
case ${cfg.filesDir} in
${cfg.stateDir}/sites*) echo "Files directory is in sites directory. Skipping optional link!";;
*) ln -sf "${cfg.filesDir}" "${cfg.stateDir}/sites/default/files";;
esac
if [ ! -f "${cfg.privateFilesDir}/.htaccess" ]; then
echo "Linking .htaccess file for private files directory..."
ln -s "${privateFilesHtAccess}" "${cfg.privateFilesDir}/.htaccess"
fi
echo "Preparing settings.php for ${hostName}..."
settings_file="${cfg.stateDir}/sites/default/settings.php"
if [ ! -f "$settings_file" ]; then
default_settings_file="${cfg.stateDir}/sites/default/default.settings.php";
cp "$default_settings_file" "$settings_file"
fi
cat < ${appendSettings hostName} >> "$settings_file"
chmod 644 "$settings_file"
# Link the NixOS-managed settings file to the state directory.
ln -sf ${drupalSettings hostName cfg} ${cfg.stateDir}/sites/default/settings.nixos-${hostName}.php
# Set or reset file permissions so that the web user and webserver owns them.
chown -R ${user}:${webserver.group} ${cfg.stateDir}
'';
};
siteOpts =
{
options,
@@ -106,6 +198,9 @@ let
defaultText = "/var/lib/drupal/<name>/sites/default/files";
description = ''
The location of the Drupal files directory.
Many of the files in this directory are variable, so they must be located
in a location writeable by users of the webgroup.
'';
};
@@ -120,7 +215,22 @@ let
type = types.path;
default = "/var/lib/drupal/${name}/config/sync";
defaultText = "/var/lib/drupal/<name>/config/sync";
description = "The location of the Drupal config sync directory.";
description = ''
The location of the user-managed Drupal config sync directory.
Drupal will both read from and write to this directory when executing
configuration management operations.
'';
};
webRoot = mkOption {
type = types.str;
default = "";
description = ''
An optional path string with a leading slash
indicating the location of the Drupal webroot relative to the
project root directory, if one exists.
'';
example = "/web";
};
extraConfig = mkOption {
@@ -140,21 +250,41 @@ let
type = types.path;
default = "/var/lib/drupal/${name}";
defaultText = "/var/lib/drupal/<name>";
description = "The location of the Drupal site state directory.";
description = ''
The location of the user-managed Drupal site state directory.
This directory will contain the settings and configuration files for
your Drupal instance. It may also contain your files directory if the
`filesDir` option remains unchanged.
Many of the files in this directory are variable, so they must be located
in a location writeable by users of the webgroup.
'';
};
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.";
description = ''
The location for users to manually install Drupal modules.
Note: in most instances, it is preferable to install modules using
composer, or to package them with your source code repository, if
you are using a custom Drupal.
'';
};
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.";
description = ''
The location for users to manually install Drupal themes.
Note: in most instances, it is preferable to install themes using
composer, or to package them with your source code repository, if
you are using a custom Drupal.
'';
};
phpOptions = mkOption {
@@ -357,6 +487,8 @@ in
"d '${cfg.themesDir}' 0750 ${user} ${webserver.group} - -"
"Z '${cfg.themesDir}' 0750 ${user} ${webserver.group} - -"
"d '${cfg.privateFilesDir}' 0750 ${user} ${webserver.group} - -"
"d '${cfg.filesDir}' 0750 ${user} ${webserver.group} - -"
"Z '${cfg.filesDir}' 0750 ${user} ${webserver.group} - -"
"d '${cfg.configSyncDir}' 0750 ${user} ${webserver.group} - -"
]) eachSite
);
@@ -382,42 +514,15 @@ in
User = "root";
RemainAfterExit = true;
ExecStart = writeShellScript "drupal-state-init-${hostName}" ''
set -e
if [ ! -d "${cfg.stateDir}/sites" ]; then
echo "Preparing sites directory..."
cp -r "${cfg.package}/share/php/${cfg.package.pname}/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_file="${cfg.stateDir}/sites/default/settings.php"
default_settings="${cfg.package}/share/php/${cfg.package.pname}/sites/default/default.settings.php"
if [ ! -f "$settings_file" ]; then
echo "Preparing settings.php for ${hostName}..."
cp "$default_settings" "$settings_file"
cat < ${appendSettings hostName} >> "$settings_file"
chmod 644 "$settings_file"
fi
# Link the NixOS-managed settings file to the state directory.
ln -sf ${drupalSettings hostName cfg} ${cfg.stateDir}/sites/default/settings.nixos-${hostName}.php
# Set or reset file permissions so that the web user and webserver owns them.
chown -R ${user}:${webserver.group} ${cfg.stateDir}
'';
ExecStart = getExe (stateDirManage hostName cfg);
};
# Rerun this service if certain settings were updated
reloadTriggers = [
cfg.extraConfig
cfg.privateFilesDir
cfg.filesDir
cfg.stateDir
cfg.configSyncDir
];
})
@@ -434,9 +539,9 @@ in
enable = true;
virtualHosts = mapAttrs (hostName: cfg: {
serverName = mkDefault hostName;
root = "${pkg hostName cfg}/share/php/${cfg.package.pname}";
root = "${pkg hostName cfg}/share/php/${cfg.package.pname}${cfg.webRoot}";
extraConfig = ''
index index.php;
index index.php index.htm index.html;
'';
locations = {
"~ '\\.php$|^/update\\.php'" = {
@@ -515,11 +620,19 @@ in
};
"~ ^/sites/.*/files/styles/" = {
extraConfig = ''
alias ${cfg.filesDir}/;
try_files $uri @rewrite;
'';
};
"^~ /sites/.*/files/" = {
extraConfig = ''
alias ${cfg.filesDir}/;
try_files $uri @rewrite;
'';
};
"~ ^(/[a-z\\-]+)?/system/files/" = {
extraConfig = ''
alias ${cfg.privateFilesDir}/;
try_files $uri /index.php?$query_string;
'';
};
@@ -535,8 +648,9 @@ in
hostName: cfg:
(nameValuePair hostName {
extraConfig = ''
root * ${pkg hostName cfg}/share/php/${cfg.package.pname}
root * ${pkg hostName cfg}/share/php/${cfg.package.pname}${cfg.webRoot}
file_server
root /sites/*/files ${cfg.filesDir}
encode zstd gzip
php_fastcgi unix/${config.services.phpfpm.pools."drupal-${hostName}".socket}