{"id":13777379,"url":"https://github.com/jprjr/lua-resty-exec","last_synced_at":"2025-03-22T07:30:39.927Z","repository":{"id":70966765,"uuid":"65671104","full_name":"jprjr/lua-resty-exec","owner":"jprjr","description":"Run external programs in OpenResty without spawning a shell or blocking","archived":false,"fork":false,"pushed_at":"2020-02-25T01:59:02.000Z","size":27,"stargazers_count":59,"open_issues_count":5,"forks_count":15,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-17T17:16:14.537Z","etag":null,"topics":["exec","lua-nginx","lua-resty","openresty","stdout"],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jprjr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-08-14T14:46:37.000Z","updated_at":"2023-10-10T08:40:50.000Z","dependencies_parsed_at":"2024-01-13T10:40:41.009Z","dependency_job_id":"deec9b0e-3fc7-45f3-8061-0f6f5d1a993b","html_url":"https://github.com/jprjr/lua-resty-exec","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jprjr%2Flua-resty-exec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jprjr%2Flua-resty-exec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jprjr%2Flua-resty-exec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jprjr%2Flua-resty-exec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jprjr","download_url":"https://codeload.github.com/jprjr/lua-resty-exec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244924816,"owners_count":20532872,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exec","lua-nginx","lua-resty","openresty","stdout"],"created_at":"2024-08-03T18:00:42.472Z","updated_at":"2025-03-22T07:30:36.684Z","avatar_url":"https://github.com/jprjr.png","language":"Lua","readme":"# lua-resty-exec\n\nA small Lua module for executing processes. It's primarily\nintended to be used with OpenResty, but will work in regular Lua applications\nas well. When used with OpenResty, it's completely non-blocking (otherwise it\nfalls back to using LuaSocket and does block).\n\nIt's similar to (and inspired by)\n[lua-resty-shell](https://github.com/juce/lua-resty-shell), the primary\ndifference being this module uses sockexec, which doesn't spawn a shell -\ninstead you provide an array of argument strings, which means you don't need\nto worry about shell escaping/quoting/parsing rules.\n\nAdditionally, as of version 2.0.0, you can use `resty.exec.socket` to access a\nlower-level interface that allows two-way communication with programs. You can\nread and write to running applications!\n\nThis requires your web server to have an active instance of\n[sockexec](https://github.com/jprjr/sockexec) running.\n\n## Changelog\n\n* `3.0.0`\n  * new field returned: `unknown` - if this happens please send me a bug!\n* `2.0.0`\n  * New `resty.exec.socket` module for using a duplex connection\n  * `resty.exec` no longer uses the `bufsize` argument\n  * `resty.exec` now accepts a `timeout` argument, specify in milliseconds, defaults to 60s\n  * This is a major revision, please test thoroughly before upgrading!\n* No changelog before `2.0.0`\n\n## Installation\n\n`lua-resty-exec` is available on [luarocks](https://luarocks.org/modules/jprjr/lua-resty-exec)\nas well as [opm](https://opm.openresty.org/), you can install it with `luarocks install\nlua-resty-exec` or `opm get jprjr/lua-resty-exec`.\n\nIf you're using this outside of OpenResty, you'll also need the LuaSocket\nmodule installed, ie `luarocks install luasocket`.\n\nAdditionally, you'll need `sockexec` running, see [its repo](https://github.com/jprjr/sockexec)\nfor instructions.\n\n## `resty.exec` Usage\n\n```lua\nlocal exec = require'resty.exec'\nlocal prog = exec.new('/tmp/exec.sock')\n```\n\nCreates a new `prog` object, using `/tmp/exec.sock` for its connection to\nsockexec.\n\nFrom there, you can use `prog` in a couple of different ways:\n\n### ez-mode\n\n```lua\nlocal res, err = prog('uname')\n\n-- res = { stdout = \"Linux\\n\", stderr = nil, exitcode = 0, termsig = nil }\n-- err = nil\n\nngx.print(res.stdout)\n```\n\nThis will run `uname`, with no data on stdin.\n\nReturns a table of output/error codes, with `err` set to any errors\nencountered.\n\n### Setup argv beforehand\n\n```lua\nprog.argv = { 'uname', '-a' }\nlocal res, err = prog()\n\n-- res = { stdout = \"Linux localhost 3.10.18 #1 SMP Tue Aug 2 21:08:34 PDT 2016 x86_64 GNU/Linux\\n\", stderr = nil, exitcode = 0, termsig = nil }\n-- err = nil\n\nngx.print(res.stdout)\n```\n\n### Setup stdin beforehand\n\n```lua\nprog.stdin = 'this is neat!'\nlocal res, err = prog('cat')\n\n-- res = { stdout = \"this is neat!\", stderr = nil, exitcode = 0, termsig = nil }\n-- err = nil\n\nngx.print(res.stdout)\n```\n\n### Call with explicit argv, stdin data, stdout/stderr callbacks\n\n```lua\nlocal res, err = prog( {\n    argv = 'cat',\n    stdin = 'fun!',\n    stdout = function(data) print(data) end,\n    stderr = function(data) print(\"error:\", data) end\n} )\n\n-- res = { stdout = nil, stderr = nil, exitcode = 0, termsig = nil }\n-- err = nil\n-- 'fun!' is printed\n```\n\nNote: here `argv` is a string, which is fine if your program doesn't need\nany arguments.\n\n### Setup stdout/stderr callbacks\n\nIf you set `prog.stdout` or `prog.stderr` to a function, it will be called for\neach chunk of stdout/stderr data received.\n\nPlease note that there's no guarantees of stdout/stderr being a complete\nstring, or anything particularly sensible for that matter!\n\n```lua\nprog.stdout = function(data)\n    ngx.print(data)\n    ngx.flush(true)\nend\n\nlocal res, err = prog('some-program')\n\n```\n\n### Treat timeouts as non-errors\n\nBy default, `sockexec` treats a timeout as an error. You can disable this by\nsetting the object's `timeout_fatal` key to false. Examples:\n\n```lua\n-- set timeout_fatal = false on the prog objects\nprog.timeout_fatal = false\n\n-- or, set it at calltime:\nlocal res, err = prog({argv = {'cat'}, timeout_fatal = false})\n```\n\n### But I actually want a shell!\n\nNot a problem! You can just do something like:\n\n```lua\nlocal res, err = prog('bash','-c','echo $PATH')\n```\n\nOr if you want to run an entire script:\n\n```lua\nprog.stdin = script_data\nlocal res, err = prog('bash')\n\n-- this is roughly equivalent to running `bash \u003c script` on the CLI\n```\n\n### Daemonizing processes\n\nI generally recommend against daemonizing processes - I think it's far\nbetter to use some kind of message queue and/or supervision system, so\nyou can monitor processes, take actions on failure, and so on.\n\nThat said, if you want to spin off some process, you could use\n`start-stop-daemon`, ie:\n\n```lua\nlocal res, err = prog('start-stop-daemon','--pidfile','/dev/null','--background','--exec','/usr/bin/sleep', '--start','--','10')\n```\n\nwill spawn `sleep 10` as a detached background process.\n\nIf you don't want to deal with `start-stop-daemon`, I have a small utility\nfor spawning a background program called [idgaf](https://github.com/jprjr/idgaf), ie:\n\n```lua\nlocal res, err = prog('idgaf','sleep','10')\n```\n\nThis will basically accomplish the same thing `start-stop-daemon` does without\nrequiring a billion flags.\n\n## `resty.exec.socket` Usage\n\n```lua\nlocal exec_socket = require'resty.exec.socket'\n\n-- you can specify timeout in milliseconds, optional\nlocal client = exec_socket:new({ timeout = 60000 })\n\n-- every new program instance requires a new\n-- call to connect\nlocal ok, err = client:connect('/tmp/exec.sock')\n\n-- send program arguments, only accepts a table of\n-- arguments\nclient:send_args({'cat'})\n\n-- send data for stdin\nclient:send('hello there')\n\n-- receive data\nlocal data, typ, err = client:receive()\n\n-- `typ` can be one of:\n--    `stdout`   - data from the program's stdout\n--    `stderr`   - data from the program's stderr\n--    `exitcode` - the program's exit code\n--    `termsig`  - if terminated via signal, what signal was used\n\n-- if `err` is set, data and typ will be nil\n-- common `err` values are `closed` and `timeout`\nprint(string.format('Received %s data: %s',typ,data)\n-- will print 'Received stdout data: hello there'\n\nclient:send('hey this cat process is still running')\ndata, typ, err = client:receive()\nprint(string.format('Received %s data: %s',typ,data)\n-- will print 'Received stdout data: hey this cat process is still running'\n\nclient:send_close() -- closes stdin\ndata, typ, err = client:receive()\nprint(string.format('Received %s data: %s',typ,data)\n-- will print 'Received exitcode data: 0'\n\ndata, typ, err = client:receive()\nprint(err) -- will print 'closed'\n```\n\n### `client` object methods:\n\n* **`ok, err = client:connect(path)`**\n\nConnects via unix socket to the path given. If this is running\nin nginx, the `unix:` string will be prepended automatically.\n\n* **`bytes, err = client:send_args(args)`**\n\nSends a table of arguments to sockexec and starts the program.\n\n* **`bytes, err = client:send_data(data)`**\n\nSends `data` to the program's standard input\n\n* **`bytes, err = client:send(data)`**\n\nJust a shortcut to `client:send_data(data)`\n\n* **`bytes, err = client:send_close()`**\n\nCloses the program's standard input. You can also send an empty\nstring, like `client:send_data('')`\n\n* **`data, typ, err = client:receive()`**\n\nReceives data from the running process. `typ` indicates the type\nof data, which can be `stdout`, `stderr`, `termsig`, `exitcode`\n\n`err` is typically either `closed` or `timeout`\n\n* **`client:close()`**\n\nForcefully closes the client connection\n\n* **`client:getfd()`**\n\nA getfd method, useful if you want to monitor the underlying socket\nconnection in a select loop\n\n## Some example nginx configs\n\nAssuming you're running sockexec at `/tmp/exec.sock`\n\n```\n$ sockexec /tmp/exec.sock\n```\n\nThen in your nginx config:\n\n```nginx\nlocation /uname-1 {\n    content_by_lua_block {\n        local prog = require'resty.exec'.new('/tmp/exec.sock')\n        local data,err = prog('uname')\n        if(err) then\n            ngx.say(err)\n        else\n            ngx.say(data.stdout)\n        end\n    }\n}\nlocation /uname-2 {\n    content_by_lua_block {\n        local prog = require'resty.exec'.new('/tmp/exec.sock')\n        prog.argv = { 'uname', '-a' }\n        local data,err = prog()\n        if(err) then\n            ngx.say(err)\n        else\n            ngx.say(data.stdout)\n        end\n    }\n}\nlocation /cat-1 {\n    content_by_lua_block {\n        local prog = require'resty.exec'.new('/tmp/exec.sock')\n        prog.stdin = 'this is neat!'\n        local data,err = prog('cat')\n        if(err) then\n            ngx.say(err)\n        else\n            ngx.say(data.stdout)\n        end\n    }\n}\nlocation /cat-2 {\n    content_by_lua_block {\n        local prog = require'resty.exec'.new('/tmp/exec.sock')\n        local data,err = prog({argv = 'cat', stdin = 'awesome'})\n        if(err) then\n            ngx.say(err)\n        else\n            ngx.say(data.stdout)\n        end\n    }\n}\nlocation /slow-print {\n    content_by_lua_block {\n        local prog = require'resty.exec'.new('/tmp/exec.sock')\n        prog.stdout = function(v)\n            ngx.print(v)\n            ngx.flush(true)\n        end\n        prog('/usr/local/bin/slow-print')\n    }\n    # look in `/misc` of this repo for `slow-print`\n}\nlocation /shell {\n    content_by_lua_block {\n        local prog = require'resty.exec'.new('/tmp/exec.sock')\n        local data, err = prog('bash','-c','echo $PATH')\n        if(err) then\n            ngx.say(err)\n        else\n            ngx.say(data.stdout)\n        end\n    }\n}\n\n```\n\n## License\n\nMIT license (see `LICENSE`)\n","funding_links":[],"categories":["Libraries"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjprjr%2Flua-resty-exec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjprjr%2Flua-resty-exec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjprjr%2Flua-resty-exec/lists"}