From 1a3773ef60ff8d63e92daee47d39b5ea8932aa4c Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Wed, 24 Jun 2026 14:27:37 +0200 Subject: [PATCH 1/3] feat: add scripts/run + README run instructions for the OpenResty demo The compiled Lua.Ngx bindings wrap the ngx.* API the lua-nginx-module injects, so they only run inside an OpenResty worker. scripts/run serves the dist modules under OpenResty (locations /say and /status) so they can be curled, mirroring scripts/build. - flake.nix: add openresty to the dev shell so `nix develop -c ./scripts/run` works. - scripts/run: generate a minimal nginx.conf and serve dist/*.lua via dofile. - README.md: document build, run, and how to consume the bindings (dofile + curried Effect). - .gitignore: ignore the direnv cache (.direnv/). --- .gitignore | 1 + README.md | 54 ++++++++++++++++++++++++++++++++++ flake.nix | 1 + scripts/run | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100755 scripts/run diff --git a/.gitignore b/.gitignore index ed7672a..3a5472e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ node_modules .psci .spago output +.direnv/ .psc-ide-port diff --git a/README.md b/README.md index 26760c4..34631ec 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,57 @@ Purescript Lua bindings for the openresty/lua-nginx-module * [PureScript Compiler Backend for Lua](https://github.com/purescript-lua/purescript-lua) * [NGINX API for Lua](https://github.com/openresty/lua-nginx-module#nginx-api-for-lua) + +## Building + +```sh +nix develop -c ./scripts/build +``` + +Spago compiles the PureScript to CoreFn (a no-op `backend` keeps codegen on +corefn rather than JavaScript), then pslua links each module to a flat Lua file +under `dist/`, for example `dist/Lua.Ngx.lua`. + +## Running + +The bindings wrap the `ngx.*` API that the lua-nginx-module injects, so they +only work inside an OpenResty worker. Plain `lua` cannot run them (`ngx` is +nil) and neither can vanilla nginx (no Lua). The dev shell ships OpenResty, and +`scripts/run` serves the compiled modules so you can curl them: + +```sh +nix develop -c ./scripts/build # produce dist/*.lua +nix develop -c ./scripts/run # serve on http://127.0.0.1:8099 +``` + +Then, in another terminal: + +```sh +curl http://127.0.0.1:8099/say +# hello from purescript-lua-ngx + +curl -i http://127.0.0.1:8099/status +# HTTP/1.1 404 Not Found +# ... +# HTTP_OK constant = 200 +# ngx.status now = 404 +``` + +`PORT=9000 nix develop -c ./scripts/run` picks another port. + +### Using the bindings from your own config + +A linked module is a flat file whose name contains literal dots, such as +`Lua.Ngx.lua`, so load it with `dofile` by absolute path. `require("Lua.Ngx")` +would rewrite the dot to a directory separator and miss the file. Effectful +bindings are curried, so a `String -> Effect Unit` like `say` is called as +`say(msg)()`: + +```nginx +location /hello { + content_by_lua_block { + local Ngx = dofile("/path/to/dist/Lua.Ngx.lua") + Ngx.say("hello")() + } +} +``` diff --git a/flake.nix b/flake.nix index ca90632..9d18826 100644 --- a/flake.nix +++ b/flake.nix @@ -42,6 +42,7 @@ lua51Packages.luacheck luaformatter nixfmt-rfc-style + openresty # nginx + lua-nginx-module, for ./scripts/run pslua.packages.${system}.default purs-bin.purs-0_15_16 spago-bin.spago-1_0_4 diff --git a/scripts/run b/scripts/run new file mode 100755 index 0000000..ca4e694 --- /dev/null +++ b/scripts/run @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Demo runner: serves the compiled Lua.Ngx bindings under OpenResty so you can +# curl them. The bindings wrap the `ngx.*` API the lua-nginx-module injects, so +# they only run inside an OpenResty worker (plain `lua` or vanilla nginx will +# not work). Build first with ./scripts/build, then: +# +# nix develop -c ./scripts/run # serve on :8099 +# PORT=9000 nix develop -c ./scripts/run # pick another port +# +# In another terminal: +# curl http://127.0.0.1:8099/say +# curl -i http://127.0.0.1:8099/status + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +dist="$here/dist" +port="${PORT:-8099}" + +if ! command -v openresty >/dev/null 2>&1; then + echo "openresty is not on PATH." >&2 + echo "Run inside the dev shell: nix develop -c ./scripts/run" >&2 + echo "(With direnv, reload it after pulling: direnv reload)" >&2 + exit 1 +fi + +if [ ! -f "$dist/Lua.Ngx.lua" ]; then + echo "dist/Lua.Ngx.lua is missing. Run ./scripts/build first." >&2 + exit 1 +fi + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +mkdir -p "$work/logs" + +# A linked dist module is a flat file named e.g. `Lua.Ngx.lua` (a literal dot in +# the filename, not a path), so consumers load it with dofile by absolute path +# rather than require, which would rewrite the dot to a directory separator. +cat > "$work/nginx.conf" < Effect Unit), so the generated value + # is curried: say(msg) returns a thunk that performs the write when called. + location /say { + content_by_lua_block { + local Ngx = dofile("$dist/Lua.Ngx.lua") + Ngx.say("hello from purescript-lua-ngx")() + } + } + + # Lua.Ngx.Http.Status exposes the HTTP_* constants plus get/set for + # ngx.status (set is likewise an effectful thunk). + location /status { + content_by_lua_block { + local Ngx = dofile("$dist/Lua.Ngx.lua") + local Status = dofile("$dist/Lua.Ngx.Http.Status.lua") + Status.set(Status.notFound)() + Ngx.say("HTTP_OK constant = " .. tostring(Status.ok))() + Ngx.say("ngx.status now = " .. tostring(Status.get()))() + } + } + } +} +EOF + +echo "Serving compiled Lua.Ngx on http://127.0.0.1:$port" +echo " curl http://127.0.0.1:$port/say" +echo " curl -i http://127.0.0.1:$port/status" +echo "Ctrl-C to stop." +openresty -p "$work" -c "$work/nginx.conf" From d94f3acf52c9f5400e5eff45eeae379a0a7e85f6 Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Wed, 24 Jun 2026 14:33:51 +0200 Subject: [PATCH 2/3] fix: silence the default error-log alert in scripts/run (-e stderr) --- scripts/run | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/run b/scripts/run index ca4e694..990cf7f 100755 --- a/scripts/run +++ b/scripts/run @@ -81,4 +81,6 @@ echo "Serving compiled Lua.Ngx on http://127.0.0.1:$port" echo " curl http://127.0.0.1:$port/say" echo " curl -i http://127.0.0.1:$port/status" echo "Ctrl-C to stop." -openresty -p "$work" -c "$work/nginx.conf" +# -e stderr sets the error log before the config is parsed, so nginx does not +# try (and noisily fail) to open its compiled-in default /var/log/nginx path. +openresty -p "$work" -e stderr -c "$work/nginx.conf" From bacbf972dc086576e1ab0561df9925fe9de140e8 Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Wed, 24 Jun 2026 14:50:25 +0200 Subject: [PATCH 3/3] address copilot review on PR #6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/run — validate PORT is a 1..65535 integer before interpolating it into the config (https://github.com/purescript-lua/purescript-lua-ngx/pull/6#discussion_r3467077055) - scripts/run — fail fast if either served dist module is missing, not just Lua.Ngx.lua (https://github.com/purescript-lua/purescript-lua-ngx/pull/6#discussion_r3467077104) --- scripts/run | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/run b/scripts/run index 990cf7f..cdf875e 100755 --- a/scripts/run +++ b/scripts/run @@ -17,6 +17,13 @@ here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" dist="$here/dist" port="${PORT:-8099}" +# PORT is interpolated into `listen $port;`, so reject anything that is not a +# plain TCP port number before it reaches the config. +if ! [[ $port =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then + echo "PORT must be an integer in 1..65535 (got: '$port')." >&2 + exit 1 +fi + if ! command -v openresty >/dev/null 2>&1; then echo "openresty is not on PATH." >&2 echo "Run inside the dev shell: nix develop -c ./scripts/run" >&2 @@ -24,10 +31,15 @@ if ! command -v openresty >/dev/null 2>&1; then exit 1 fi -if [ ! -f "$dist/Lua.Ngx.lua" ]; then - echo "dist/Lua.Ngx.lua is missing. Run ./scripts/build first." >&2 - exit 1 -fi +# Both modules are served below (/say and /status), and both come from the same +# ./scripts/build run, so fail fast if either is missing rather than only at the +# first request that needs it. +for f in Lua.Ngx.lua Lua.Ngx.Http.Status.lua; do + if [ ! -f "$dist/$f" ]; then + echo "dist/$f is missing. Run ./scripts/build first." >&2 + exit 1 + fi +done work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT