From 27719f242554e7e122383e893a9fee4789669e6a Mon Sep 17 00:00:00 2001 From: Ilan Joselevich Date: Tue, 30 Jun 2026 18:29:28 +0300 Subject: [PATCH] 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 --- .../services/web-servers/nginx/default.nix | 5 +++ nixos/tests/all-tests.nix | 1 + nixos/tests/nginx-dynamic-modules.nix | 32 +++++++++++++++ pkgs/servers/http/nginx/generic.nix | 40 +++++++++++++++---- 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 nixos/tests/nginx-dynamic-modules.nix diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index bf6404ff935c..264755d40277 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -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; ''} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 375c9bc60bc0..3f8d7bcaa718 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -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; diff --git a/nixos/tests/nginx-dynamic-modules.nix b/nixos/tests/nginx-dynamic-modules.nix new file mode 100644 index 000000000000..608e95e62f24 --- /dev/null +++ b/nixos/tests/nginx-dynamic-modules.nix @@ -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 + ''; +} diff --git a/pkgs/servers/http/nginx/generic.nix b/pkgs/servers/http/nginx/generic.nix index 60afd3ea9cd8..ee5cbca73e81 100644 --- a/pkgs/servers/http/nginx/generic.nix +++ b/pkgs/servers/http/nginx/generic.nix @@ -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