nixos/nginx: add lua option for Lua scripting support

Add `services.nginx.lua.{enable,extraPackages}` to enable OpenResty's
lua-nginx-module on a stock nginx. When enabled it adds the module,
includes lua-resty-core, and wires up lua_package_path /
lua_package_cpath (and lua_ssl_trusted_certificate) from a
luajit_openresty package set built from extraPackages.

When the configured package already bundles Lua (openresty), the module
and bundled libraries are not re-added; only the search path is set up so
its own lualib stays in use.

Migrate the openresty-lua test to the new option and add an nginx-lua
test covering the stock-nginx path.

Assisted-by: Claude:claude-opus-4-8
This commit is contained in:
Ilan Joselevich 2026-06-23 22:24:49 +03:00
commit eaffe290dd
No known key found for this signature in database
5 changed files with 105 additions and 14 deletions

View file

@ -62,6 +62,8 @@
- `boot.loader.systemd-boot` gained support for [Automatic Boot Assessment](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/) via the new [`boot.loader.systemd-boot.bootCounting`](#opt-boot.loader.systemd-boot.bootCounting.enable) options, allowing automatic detection of and recovery from bad NixOS generations. As part of this change, boot loader entries on the ESP/XBOOTLDR partition are now named `nixos-<content-hash>.conf` instead of `nixos-generation-<n>.conf`; existing entries are migrated automatically on the next `nixos-rebuild boot`/`switch`.
- `services.nginx` gained a [`lua`](#opt-services.nginx.lua.enable) option to enable Lua scripting via OpenResty's lua-nginx-module on a stock nginx, configuring `lua_package_path`/`lua_package_cpath` from the packages listed in [`services.nginx.lua.extraPackages`](#opt-services.nginx.lua.extraPackages). Use this to add Lua to a regular nginx; for the full OpenResty platform (libraries that rely on its bundled lualib, such as `lua-resty-openidc`), set `services.nginx.package` to `pkgs.openresty` instead — the option configures the Lua search path for it too.
- `security.polkit.settings` added for RFC42 style configuration of the polkitd daemon.
- `services.plausible` can now again seed an initial admin user declaratively via [`services.plausible.adminUser.email`](#opt-services.plausible.adminUser.email).

View file

@ -126,6 +126,28 @@ let
'') (filterAttrs (name: conf: conf.enable) cfg.proxyCachePath)
);
# openresty bundles the lua module and resty.core; stock nginx needs them added.
packageBundlesLua = p: lib.getName p == "openresty";
luaEnv = pkgs.luajit_openresty.withPackages (
ps:
lib.optional (cfg.lua.enable && !packageBundlesLua cfg.package) ps.lua-resty-core
++ cfg.lua.extraPackages ps
);
luaVersion = pkgs.luajit_openresty.luaversion;
# Lua modules install under lib/lua or share/lua depending on the package; ;; keeps nginx's defaults.
luaConfig = ''
lua_package_path '${
lib.concatMapStringsSep ";" (s: "${luaEnv}/${s}") [
"lib/lua/${luaVersion}/?.lua"
"lib/lua/${luaVersion}/?/init.lua"
"share/lua/${luaVersion}/?.lua"
"share/lua/${luaVersion}/?/init.lua"
]
};;';
lua_package_cpath '${luaEnv}/lib/lua/${luaVersion}/?.so;;';
lua_ssl_trusted_certificate ${config.security.pki.caBundle};
'';
toUpstreamParameter =
key: value:
if builtins.isBool value then lib.optionalString value key else "${key}=${toString value}";
@ -295,6 +317,8 @@ let
server_tokens ${if cfg.serverTokens then "on" else "off"};
${optionalString cfg.lua.enable luaConfig}
${cfg.commonHttpConfig}
${proxyCachePathConfig}
@ -771,7 +795,11 @@ in
apply =
p:
p.override {
modules = lib.unique (p.modules ++ cfg.additionalModules);
modules = lib.unique (
p.modules
++ cfg.additionalModules
++ lib.optional (cfg.lua.enable && !packageBundlesLua p) pkgs.nginxModules.lua
);
};
description = ''
Nginx package to use. This defaults to the stable version. Note
@ -791,6 +819,36 @@ in
'';
};
lua = {
enable = mkEnableOption ''
Lua scripting in nginx via OpenResty's lua-nginx-module,
wiring up `lua_package_path`/`lua_package_cpath` for
{option}`services.nginx.lua.extraPackages`.
Use this to add Lua to a stock nginx. For the full OpenResty platform
required by libraries that depend on its bundled lualib (for example
`lua-resty-openidc`, which needs `resty.string` and friends) set
{option}`services.nginx.package` to `pkgs.openresty` instead; this option
then only sets up the search path and leaves OpenResty's built-in Lua
module in place
'';
extraPackages = mkOption {
type = types.functionTo (types.listOf types.package);
default = ps: [ ];
defaultText = literalExpression "ps: [ ]";
example = literalExpression ''
ps: with ps; [ lua-resty-openidc ]
'';
description = ''
Extra Lua packages to put on `lua_package_path` / `lua_package_cpath`,
for both stock nginx and `pkgs.openresty`. Packages are selected from
`pkgs.luajit_openresty.pkgs`. `lua-resty-core`, which the Lua module
requires to start, is added automatically.
'';
};
};
logError = mkOption {
default = "stderr";
type = types.str;

View file

@ -1125,6 +1125,7 @@ in
nginx-etag-compression = runTest ./nginx-etag-compression.nix;
nginx-globalredirect = runTest ./nginx-globalredirect.nix;
nginx-http3 = import ./nginx-http3.nix { inherit pkgs runTest; };
nginx-lua = runTest ./nginx-lua.nix;
nginx-mime = runTest ./nginx-mime.nix;
nginx-modsecurity = runTest ./nginx-modsecurity.nix;
nginx-moreheaders = runTest ./nginx-moreheaders.nix;

37
nixos/tests/nginx-lua.nix Normal file
View file

@ -0,0 +1,37 @@
{ lib, ... }:
{
name = "nginx-lua";
meta.maintainers = [ lib.maintainers.kranzes ];
nodes.machine = {
services.nginx = {
enable = true;
lua = {
enable = true;
extraPackages = p: [
p.lua-resty-lrucache
p.lua-cjson
];
};
virtualHosts."localhost".locations."/" = {
extraConfig = ''
default_type text/plain;
content_by_lua_block {
local cache = require("resty.lrucache").new(8)
cache:set("greeting", require("cjson").decode('"Hello world!"'))
ngx.say((cache:get("greeting")))
}
'';
};
};
};
testScript = ''
machine.wait_for_unit("nginx")
machine.wait_for_open_port(80)
response = machine.wait_until_succeeds("curl -fsS http://127.0.0.1/").strip()
assert response == "Hello world!", f"Expected 'Hello world!', got '{response}'"
'';
}

View file

@ -1,12 +1,4 @@
{ pkgs, lib, ... }:
let
luaLibs = [
pkgs.lua.pkgs.markdown
];
getLuaPath = lib: "${lib}/share/lua/${pkgs.lua.luaversion}/?.lua";
luaPath = lib.concatStringsSep ";" (map getLuaPath luaLibs);
in
{ pkgs, ... }:
{
name = "openresty-lua";
meta = with pkgs.lib.maintainers; {
@ -15,7 +7,7 @@ in
nodes = {
webserver =
{ pkgs, lib, ... }:
{ pkgs, ... }:
{
networking = {
extraHosts = ''
@ -27,9 +19,10 @@ in
enable = true;
package = pkgs.openresty;
commonHttpConfig = ''
lua_package_path '${luaPath};;';
'';
lua = {
enable = true;
extraPackages = p: [ p.markdown ];
};
virtualHosts."default.test" = {
default = true;