{"id":18508831,"url":"https://github.com/numkem/msgscript","last_synced_at":"2025-10-06T02:42:22.206Z","repository":{"id":256304578,"uuid":"854895333","full_name":"numkem/msgscript","owner":"numkem","description":"Poor man's Lambda like application server.","archived":false,"fork":false,"pushed_at":"2025-09-30T15:52:47.000Z","size":719,"stargazers_count":21,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-30T17:40:52.062Z","etag":null,"topics":["functions","golang","lua","nats"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/numkem.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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-10T00:21:33.000Z","updated_at":"2025-09-30T15:52:43.000Z","dependencies_parsed_at":"2025-01-15T18:09:23.405Z","dependency_job_id":"542e2a93-0ce7-47c4-bbdd-5acfc911b7a5","html_url":"https://github.com/numkem/msgscript","commit_stats":null,"previous_names":["numkem/msgscript"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/numkem/msgscript","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numkem%2Fmsgscript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numkem%2Fmsgscript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numkem%2Fmsgscript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numkem%2Fmsgscript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/numkem","download_url":"https://codeload.github.com/numkem/msgscript/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numkem%2Fmsgscript/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278551498,"owners_count":26005386,"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","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["functions","golang","lua","nats"],"created_at":"2024-11-06T15:15:32.356Z","updated_at":"2025-10-06T02:42:22.200Z","avatar_url":"https://github.com/numkem.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"logo.webp\" width=\"50%\" height=\"50%\"\u003e\n\u003c/p\u003e\n\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n**Table of Contents**\n\n- [Features](#features)\n- [How it works](#how-it-works)\n  - [Headers](#headers)\n  - [The Function](#the-function)\n    - [In Normal mode](#in-normal-mode)\n    - [In HTTP mode](#in-http-mode)\n    - [In HTTP+HTML mode](#in-httphtml-mode)\n  - [Return value](#return-value)\n- [HTTP handler](#http-handler)\n- [Installation](#installation)\n  - [Docker container](#docker-container)\n  - [NixOS service](#nixos-service)\n  - [Building it](#building-it)\n- [Clustering](#clustering)\n  - [Adding Scripts](#adding-scripts)\n- [Command line flags](#command-line-flags)\n  - [Cli options](#cli-options)\n    - [Commands](#commands)\n    - [dev](#dev)\n    - [devhttp](#devhttp)\n    - [Command line options](#command-line-options)\n  - [Server options](#server-options)\n- [Executors](#executors)\n  - [Lua](#lua)\n    - [Plugin system](#plugin-system)\n    - [Libraries](#libraries)\n    - [Web \"framework\" library](#web-framework-library)\n  - [WASM](#wasm)\n  - [Podman](#podman)\n- [Contributing](#contributing)\n- [Support](#support)\n\n\u003c!-- markdown-toc end --\u003e\n\nTLDR: msgscript is what you could call a poor man's Lambda-like function/application server. It can run functions and even some small web based applications.\n\n## Features\n\n- Single binary\n- Nearly no overheads\n- Good enough performances (RTT of around 10ms for the hello example)\n- Runs Lua functions, WASM binaries and Podman containers (no docker)\n- Can use reusable libraries in Lua\n- Can add Go based plugins in Lua\n- HTTP handler\n- Script storage either as flat files or in etcd\n- Can be scaled up with multiples instances through locking (requires etcd)\n\n## How it works\n\nLet's start with an example script:\n``` lua\n--* subject: funcs.hello\n--* name: hello\n\nfunction OnMessage(subject, payload)\n    local response = \"Processed subject: \" .. subject .. \" with payload: \" .. payload\n\n    return response\nend\n```\n\nFor more technical details, a [flow chart](how_it_works.png) explains what happens when a message is sent and the server sees a match and than executes a script.\n\n### Headers\n\nThe headers are formed with the pattern of `--* \u003cheader\u003e: \u003cvalue\u003e`. There are multiple possible headers:\n- `subject`: The subject the script is associated with\n- `name`: The name of the script. Multiple scripts can be associated with the same subject\n- `http`: Used to return HTML responses\n- `require`: Used to load a library script. It comes from the library \"repository\" of scripts and is prepended to the script that will be executed.\n\nEach script is a Lua file that gets executed when the server receives a message that matches a pattern. The pattern is defined in the `subject` field. The files also contains a `name` field. Multiple scripts can be associated with the same subject.\n\n### The Function\n\n#### In Normal mode\n\nNormal mode is the default mode. The server executes the script when the message matches the `subject` and `name` fields. The function called is named `OnMessage()`. That function is fed 2 arguments, which can be named whatever you want. The first is the subject and the second is the payload. The payload is what the originating NATS message contains.\n\nIt's possible to call this mode both through NATS or with the HTTP handler.\n\n#### In HTTP mode\n\nExample, for a GET request:\n\n``` lua\n--* subject: http.hello\n--* name: http_get\n--* http: true\n\nfunction GET(url, body)\n    return \"Hello, \" .. body .. \"!\", 200, { [\"Content-Type\"] = \"text/plain\" }\nend\n```\n\nThe function executed will have the same name as the HTTP verb of the originating HTTP request.\n\nThis mode is only available with the HTTP handler.\n\n#### In HTTP+HTML mode\n\nIf you want to use the HTTP handler and want to return HTML, you can do so by setting the `http` header to `true`.\n\nJust like in HTTP mode, the function executed will have the same name as the HTTP verb of the originating HTTP request.\n\n### Return value\n\nThe function is expected to return a string. If it does not, the server will log a warning: `Script returned no response`. \n\nIn **NATS** mode: it will return the string as-is.\n\nIn **HTTP** mode: it will return the following JSON document:\n``` json\n{\"calculate\":{\"http_code\":0,\"error\":\"\",\"http_headers\":{},\"is_html\":false,\"payload\":\"SGkhIEknbSBlbmNvZGVkIQo=\"}}\n```\nEach keys at the root level is named after the `name` field of the script. The value is a table with the following keys:\n- `http_code`: The HTTP status code. It defaults to 200\n- `error`: If the script returns an error, it will be set here\n- `http_headers`: A map of HTTP headers\n- `is_html`: Whether the response is HTML\n- `payload`: The payload of the message. It is base64 encoded\n\nIn **HTTP+HTML** mode: you can return 3 different values:\n- The HTML as a string\n- The HTTP code (200 is missing)\n- The HTTP headers (empty if missing)\n\n## HTTP handler\n\nIf you have a application that cannot reach nats by itself (say a webhook), it's possible to use the included HTTP handler.\n\nThe server listens to port 7643 by default (it can be changed through the command line). You can push messages by doing a POST request to `http://serverIP:7643/\u003csubject\u003e` where the `\u003csubject\u003e` is any subjects that you have scripts registered to it.\n\nExample using curl (if you are running locally and for the subject of the example above):\n\n```\ncurl -X POST -d 'John' http://127.0.0.1:7643/http.hello\n```\n\n## Installation\n\n### Docker container\n\nA container is avaible through the ghcr.io container registry. You can use it as so:\n\n``` sh\ndocker pull ghcr.io/numkem/msgscript:latest\n```\n\nIt takes either `server` or `cli` as a first parameter.\n\n### NixOS service\n\nYou can enable it in your NixOS configuration using the provided module (once included from either the flake or importing the module using something like `niv` or manually):\n\n```nix\nservices.msgscript.enable = true;\n```\n\n The options are defined in the [nix/modules/default.nix](nix/modules/default.nix) file.\n \n### Building it\n \nBeing a standalone Go binary, you can build each of the binaries like so:\n ```sh\n # Clone the msgscript repository\n git clone https://github.com/numkem/msgscript.git\n cd msgscript\n go build ./cmd/server # Generates the server binary\n go build ./cmd/cli    # Generates the CLI binary\n ```\n\nIt requires the btrfs headers (podman), gpgme (podman) and wastime (wasm) as dependancies.\n\n## Clustering\n\nWhen msgscript is running in cluster mode, it's possible to use it with etcd. You can use the `etcd` backend to do that.\n\nThe `cli` binary provides some additional commands to manage the scripts stored inside etcd.\n\n### Adding Scripts\n\nYou can add Lua scripts to etcd using the `msgscriptcli` command. Here's an example:\n\n```sh\nmsgscriptcli add -subject funcs.pushover -name pushover ./examples/pushover.lua\n```\n\nThis command adds the `pushover.lua` script from the `examples` directory, associating it with the subject `funcs.pushover` and the name `pushover`.\n\nThe `-subject` and `-name` flags are optional. If they are not provided, they will be read through the headers contained in the file.\n\n## Command line flags\n\n### Cli options\n\nThe `cli` binary helps with managing the scripts in the message store and for helping with developing scripts.\n\n#### Commands\n\n  add         Add a script to the backend by reading the provided lua file\n  completion  Generate the autocompletion script for the specified shell\n  dev         Executes the script locally like how the server would\n  devhttp     Starts a webserver that will run only to receive request from this script\n  help        Help about any command\n  lib         library related commands\n  list        list all the scripts registered in the store\n  rm          Remove an existing script\n  \nThe commands that manages scripts (add, list, rm) are not really useful when using the file base store.\n\n#### dev\n\nUseful for developing scripts that aren't http based (webhooks).\n\nFirst argument is the script in question. It will return the script's output.\n\n#### devhttp\n\nUseful for developing scripts that are http based (webhooks).\n\nFirst argument is the script in question. You can then reach your script at `http://localhost:7634/\u003csubject\u003e/`. The script is reloaded from the store on every HTTP request so you don't have to restart the command each time.\n\n#### Command line options\n\nFlags:\n  -b, --backend string    The name of the backend to use to manipulate the scripts (default \"etcd\")\n  -e, --etcdurls string   Endpoints to connect to etcd (default \"localhost:2379\")\n  -x, --executor string   Which executor to use. Either lua or wasm (default \"lua\")\n  -h, --help              help for msgscript\n  -L, --log string        set the logger to this log level (default \"info\")\n  -u, --natsurl string    NATS url to reach (default \"nats://localhost:4222\")\n\n### Server options\n\nYou can download the binary from the release page. There are 2 binaries available: `server` and `cli`. The server is what most people will want. The `cli` is only useful when paired with the `etcd` backend.\n\nThe server has the following options:\n- `-backend`: The backend to use. Currently supports `etcd` or `file`. `file` is the default.\n- `-etcdurl`: The URL of the etcd server. It can be multiple through a comma separated list.\n- `-library`: The path to a library directory. It has no defaults. It can be an absolute path or a relative path.\n- `-log`: The log level to use. The options are: `debug`, `info`, `warn`, `error`. It defaults to `info`. \n- `-natsurl`: The URL of the NATS server.\n- `-plugin`: The path to the plugin directory. It has no defaults. It can be an absolute path or a relative path.\n- `-port`: The port to listen on. It defaults to 7643.\n- `-script`: The path to a script directory. It defaults to the current working directory. It can be an absolute path or a relative path.\n\n## Executors\n\nMsgscript supports many different executors or more simply, a way to execute a script. The following shows how to use them.\n\n### Lua\n\nThis is the default executor.\n\nWhen writing Lua scripts for msgscript, you have access to additional built-in modules:\n\n- `etcd`: Read/Write/Update/Delete keys in etcd [source](lua/etcd.go)\n- `http`: For making HTTP requests [source](https://github.com/cjoudrey/gluahttp)\n- `json`: For JSON parsing and generation [source](https://github.com/layeh/gopher-json)\n- `lfs`: LuaFilesystem implementation [source](https://layeh.com/gopher-lfs)\n- `nats`: For publishing messages back to NATS [source](lua/nats.go)\n- `re`: Regular expression library [source](https://github.com/yuin/gluare)\n\nthese can be included using the built-in `require()` Lua function.\n\nThe following example shows how to deserialize a JSON payload:\n``` lua\n--* subject: example.json\n--* name: json\nlocal json = require(\"json\")\n\n-- Assuming the payload contains: \n-- {\"name\": \"John\"}\nfunction OnMessage(_, payload)\n    local data = json.decode(payload)\n    \n    return \"Hello, \" .. data.name .. \"!\"\nend\n```\n\nSome examples scripts are provided in the `examples` folder.\n\n#### Plugin system\n\nWhile there is already a lot of modules added to the Lua execution environment, it is possible to add more using the included plugin system.\n\nAn example [plugin](plugins/hello/main.go) is available. The plugins can be loaded using the `--plugin` flag for both the server and cli.\n\nExample using the hello plugin:\n\n``` lua\n--* subject: example.plugins.hello\n--* name: hello\nlocal hello = require(\"hello\")\n\nfunction OnMessage(_, _)\n    return hello.print()\nend\n```\n\nPlugins currently included in this repository:\n\n* `scrape`: An http parser [source](https://github.com/felipejfc/gluahttpscrape)\n* `db`: SQL access to MySQL, SQLite and PostgreSQL [source](https://github.com/tengattack/gluasql)\n* `gopher-lua-libs`: Various modules from the [gopher-lua-libs](https://github.com/vadv/gopher-lua-libs):\n    * `cmd`\n    * `filepath`\n    * `inspect`\n    * `ioutil`\n    * `runtime`\n    * `strings`\n    * `time`\n    \n**NOTE:** The plugin file needs to have the `.so` extension.\n\n#### Libraries\n\nLibraries are Lua files that gets prepended to the script that needs to be run. These libraries can be used within other scripts using the `require` header like this:\n\n``` lua\n--* require: foo\n```\n\nIn this case, it will load the library named `foo` and prepend it to the running script.\n\nSome example libraries are available [here](examples/libs).\n\n#### Web \"framework\" library\n\nThe [web.lua](examples/libs/web.lua) library contains a very simple web framework. It's used in the example below:\n\n``` lua\n--* subject: example.libs.web\n--* name: web\n--* require: web\nlocal json = require(\"json\")\n\nlocal router = Router.new()\n\nrouter:get(\"/plain\", function(req, _)\n    return \"Hello, World!\", {}, 200, { [\"Content-Type\"] = \"text/plain\" }\nend)\n\nrouter:get(\"/json\", function(req, _)\n    return nil, { name = \"John\" }, 200\nend)\n\nrouter:get(\"/path/\u003cfoo\u003e\", function(req, _)\n    return [[\n    \u003cp\u003e{{ param }}\u003c/p\u003e\n    ]], { param = req.params.foo }, 200\nend)\n\nrouter:post(\"/post\", function(req, _)\n    doc = json.decode(req.body)\n    return [[ Hello {{ name }}! ]], { name = doc.name }, 200\nend)\n```\n\nWhile not extensive, these examples shows how to use the library.\n\nNamely:\n- The function handling the endpoint returns 4 values: the mustache template, the data for the template, the HTTP code and HTTP headers\n- If the template is either empty (`\"\"`) or nil, it will be assumed that it's returning a JSON document. The `Content-Type` will be set as such.\n\n### WASM\n\nBinaires compiled targetting WASM can be used. Some examples are provided in the `examples/wasm` directory.\n\nThe format required in the store looks like this:\n\n```\n--* subject: funcs.wasm\n--* name: wasm\n--* executor: wasm\n/home/numkem/src/msgscript/examples/wasm/c/c.wasm\n```\n\nThe import parts are `subject`, `name` which are common with all other executors. The `executor` key needs to be set to `wasm`. The content is the path to the WASM executable.\n\n### Podman\n\nThe format requires in the store looks like this:\n\n```\n--* subject: funcs.hello\n--* name: podman\n--* executor: podman\n{\n    \"image\": \"hello-world\"\n}\n```\n\nLike for WASM, it takes the same important keys. The content of the file can be:\n\n| Key          | Description                                                                |\n|:-------------|:---------------------------------------------------------------------------|\n| `image`      | Container image name                                                       |\n| `mounts`     | List of mounts in the same format you would write them on the command line |\n| `privileged` | true/false if the container should run with more permissions               |\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Support\n\nIf you encounter any problems or have any questions, please open an issue on the GitHub repository.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnumkem%2Fmsgscript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnumkem%2Fmsgscript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnumkem%2Fmsgscript/lists"}