nginx: support dynamic modules

A module opts in with `dynamic = true` The build writes a `load_module`
line for every .so it installed to $out/etc/nginx/dynamic-modules.conf,
and the NixOS module includes that file.

Closes: #258260

Assisted-by: Claude:claude-fable-5-1
This commit is contained in:
Ilan Joselevich
2026-06-30 18:29:28 +03:00
parent a70d0593e8
commit 27719f2425
4 changed files with 71 additions and 7 deletions

View File

@@ -195,6 +195,11 @@ let
error_log ${cfg.logError};
daemon off;
# load_module is a main-context directive that must precede events{}/http{}.
${optionalString (
(cfg.package.dynamicModules or [ ]) != [ ]
) "include ${cfg.package}/etc/nginx/dynamic-modules.conf;"}
${optionalString cfg.enableQuicBPF ''
quic_bpf on;
''}

View File

@@ -1199,6 +1199,7 @@ in
nginx = runTest ./nginx.nix;
nginx-auth = runTest ./nginx-auth.nix;
nginx-compression = runTest ./nginx-compression.nix;
nginx-dynamic-modules = runTest ./nginx-dynamic-modules.nix;
nginx-etag = runTest ./nginx-etag.nix;
nginx-etag-compression = runTest ./nginx-etag-compression.nix;
nginx-globalredirect = runTest ./nginx-globalredirect.nix;

View File

@@ -0,0 +1,32 @@
{
name = "nginx-dynamic-modules";
nodes.machine =
{ pkgs, ... }:
{
services.nginx = {
enable = true;
additionalModules = [ (pkgs.nginxModules.echo // { dynamic = true; }) ];
virtualHosts."localhost".locations."/".extraConfig = ''
echo "dynamic-module-ok";
'';
};
};
testScript =
{ nodes, ... }:
let
cfg = nodes.machine.services.nginx;
in
''
machine.wait_for_unit("nginx")
machine.wait_for_open_port(80)
machine.succeed("ls ${cfg.package}/modules/*.so")
machine.succeed("grep -F load_module ${cfg.package}/etc/nginx/dynamic-modules.conf")
# The echo directive only exists once nginx has dlopen'd the .so.
response = machine.wait_until_succeeds("curl -fsS http://127.0.0.1/")
assert "dynamic-module-ok" in response, response
'';
}

View File

@@ -53,6 +53,8 @@ let
moduleNames = map (mod: mod.pname) modules;
dynamicModules = lib.filter (mod: mod.dynamic or false) modules;
mapModules =
attrPath:
lib.flip lib.concatMap modules (
@@ -169,6 +171,7 @@ stdenv.mkDerivation {
++ lib.optional (
stdenv.buildPlatform != stdenv.hostPlatform
) "--crossbuild=${stdenv.hostPlatform.uname.system}::${stdenv.hostPlatform.uname.processor}"
++ lib.optional (dynamicModules != [ ]) "--modules-path=${placeholder "out"}/modules"
++ configureFlags;
env = {
@@ -212,13 +215,18 @@ stdenv.mkDerivation {
''
# Make all modules source trees writable
+ ''
for module in ${toString modules}; do
dst="$NIX_BUILD_TOP/$(basename "$module")"
cp --recursive "$module" "$dst"
addModule() {
local dst="$NIX_BUILD_TOP/$(basename "$2")"
cp --recursive "$2" "$dst"
chmod --recursive +w "$dst"
appendToVar configureFlags "--add-module=$dst"
done
appendToVar configureFlags "$1=$dst"
}
''
+ lib.concatLines (
map (
mod: "addModule ${if mod.dynamic or false then "--add-dynamic-module" else "--add-module"} ${mod}"
) modules
)
+ preConfigure
+ lib.concatMapStringsSep "\n" (mod: mod.preConfigure or "") modules;
@@ -275,21 +283,39 @@ stdenv.mkDerivation {
disallowedReferences = map (m: m.src) modules;
stripDebugList = [
"bin"
"sbin"
"lib"
"modules"
];
postInstall =
let
noSourceRefs = lib.concatMapStrings (
m: "remove-references-to -t ${m.src} $(readlink -fn $out/bin/nginx)\n"
) modules;
dynamicPost = lib.optionalString (dynamicModules != [ ]) ''
shopt -s nullglob
sofiles=("$out"/modules/*.so)
if (( ''${#sofiles[@]} == 0 )); then
echo "nginx: dynamic modules were requested but no .so was produced in $out/modules" >&2
exit 1
fi
mkdir -p "$out/etc/nginx"
printf 'load_module %s;\n' "''${sofiles[@]}" > "$out/etc/nginx/dynamic-modules.conf"
'';
in
postInstall + noSourceRefs;
postInstall + noSourceRefs + dynamicPost;
passthru = {
inherit modules;
inherit modules dynamicModules;
tests =
passthru.tests or {
inherit (nixosTests)
nginx
nginx-auth
nginx-dynamic-modules
nginx-etag
nginx-etag-compression
nginx-globalredirect