{"id":13413816,"url":"https://github.com/xyproto/algernon","last_synced_at":"2025-05-13T23:05:29.563Z","repository":{"id":28438523,"uuid":"31953662","full_name":"xyproto/algernon","owner":"xyproto","description":"Small self-contained pure-Go web server with Lua, Teal, Markdown, Ollama, HTTP/2, QUIC, Redis, SQLite and PostgreSQL support ++","archived":false,"fork":false,"pushed_at":"2025-05-13T14:16:09.000Z","size":67865,"stargazers_count":2902,"open_issues_count":28,"forks_count":141,"subscribers_count":50,"default_branch":"main","last_synced_at":"2025-05-13T15:41:07.074Z","etag":null,"topics":["algernon","buildless","cross-platform","fasthttp","go","http2","http3","live-reload","llm","lua","mysql","ollama","pongo2","postgresql","quic","redis","server-sent-events","sqlite","tls13"],"latest_commit_sha":null,"homepage":"https://algernon.roboticoverlords.org","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xyproto.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":["xyproto"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2015-03-10T11:25:30.000Z","updated_at":"2025-05-13T14:16:14.000Z","dependencies_parsed_at":"2024-01-24T17:36:45.617Z","dependency_job_id":"f708d2f7-8ce3-49fb-89f7-a3ed5c6bfddd","html_url":"https://github.com/xyproto/algernon","commit_stats":{"total_commits":2373,"total_committers":19,"mean_commits":"124.89473684210526","dds":0.03750526759376316,"last_synced_commit":"f9858be3daf697ecf92e18c9b917db13b1391ac4"},"previous_names":["xyproto/luawebserver"],"tags_count":115,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyproto%2Falgernon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyproto%2Falgernon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyproto%2Falgernon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyproto%2Falgernon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xyproto","download_url":"https://codeload.github.com/xyproto/algernon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253990441,"owners_count":21995773,"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":["algernon","buildless","cross-platform","fasthttp","go","http2","http3","live-reload","llm","lua","mysql","ollama","pongo2","postgresql","quic","redis","server-sent-events","sqlite","tls13"],"created_at":"2024-07-30T20:01:50.133Z","updated_at":"2025-05-13T23:05:24.555Z","avatar_url":"https://github.com/xyproto.png","language":"Go","funding_links":["https://github.com/sponsors/xyproto"],"categories":["Server Applications","Go","服务器应用程序","后端开发框架及项目","sqlite","Resources","JavaScript","Relational Databases","服务器程序","\u003cspan id=\"服务器应用程序-server-applications\"\u003e服务器应用程序 Server Applications\u003c/span\u003e","服务端应用","Repositories","服務器程序"],"sub_categories":["HTTP Clients","HTTP客户端","管理面板","Contents","Advanced Console UIs","Web Plataforms","交流","高级控制台界面","\u003cspan id=\"高级控制台用户界面-advanced-console-uis\"\u003e高级控制台用户界面 Advanced Console UIs\u003c/span\u003e","查询语","高級控制台界面"],"readme":"\u003c!--\ntitle: Algernon\ndescription: Web server with built-in support for Lua, Teal, Markdown, Pongo2, Amber, Sass, SCSS, GCSS, JSX, Bolt, PostgreSQL, SQLite, Redis, MariaDB/MySQL, MSSQL, Tollbooth, Pie, Graceful, Permissions2, users and permissions\nkeywords: web server, QUIC, lua, teal, markdown, pongo2, application server, http, http2, HTTP/2, go, golang, algernon, JSX, React, BoltDB, Bolt, PostgreSQL, SQLite, Redis, MariaDB, MySQL, Three.js\ntheme: material\n--\u003e\n\n\u003c!--\u003ca href=\"https://github.com/xyproto/algernon\"\u003e\u003cimg src=\"https://algernon.roboticoverlords.org/img/algernon_logo.png\" style=\"margin-left: 2em\"\u003e\u003c/a\u003e--\u003e\n![Algernon](img/algernon_logo.png)\n\n[![Build](https://github.com/xyproto/algernon/actions/workflows/build.yml/badge.svg)](https://github.com/xyproto/algernon/actions/workflows/build.yml) [![GoDoc](https://godoc.org/github.com/xyproto/algernon?status.svg)](https://godoc.org/github.com/xyproto/algernon) [![License](https://img.shields.io/badge/license-BSD-green.svg?style=flat)](https://raw.githubusercontent.com/xyproto/algernon/main/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/xyproto/algernon)](https://goreportcard.com/report/github.com/xyproto/algernon) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fxyproto%2Falgernon.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fxyproto%2Falgernon?ref=badge_shield) [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)\n\nWeb server with built-in support for QUIC, HTTP/2, Lua, Teal, Markdown, Pongo2, HyperApp, Amber, Sass(SCSS), GCSS, JSX, Ollama (LLMs), BoltDB (built-in, stores the database in a file, like SQLite), Redis, PostgreSQL, SQLite, MariaDB/MySQL, MSSQL, rate limiting, graceful shutdown, plugins, users and permissions.\n\nAll in one small self-contained executable.\n\nDistro Packages\n---------------\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/algernon.svg)](https://repology.org/project/algernon/versions)\n\nQuick installation\n------------------\n\nRequires Go 1.21 or later.\n\n    go install github.com/xyproto/algernon@latest\n\nOr manually (development version):\n\n```\ngit clone https://github.com/xyproto/algernon\ncd algernon\ngo build -mod=vendor\n./welcome.sh\n```\n\nReleases and pre-built images\n-----------------------------\n\nSee the [release](https://github.com/xyproto/algernon/releases/latest) page for releases for a variety of platforms and architectures.\n\n\nGetting Started\n---------------\n\nSee [TUTORIAL.md](TUTORIAL.md).\n\nDocker\n------\n\nThe [Docker image](https://hub.docker.com/r/xyproto/algernon/tags) is less than 12MB and can be tried out (on x86_64) with:\n\n```\nmkdir localhost\necho 'hi!' \u003e localhost/index.md\ndocker run -it -p4000:4000 -v .:/srv/algernon xyproto/algernon\n```\n\nAnd then visiting `http://localhost:4000` in a browser.\n\nTechnologies\n------------\n\nWritten in [Go](https://golang.org). Uses [Bolt](https://github.com/coreos/bbolt) (built-in), [MySQL](https://github.com/go-sql-driver/mysql), [PostgreSQL](https://www.postgresql.org/), SQLite or [Redis](https://redis.io) (recommended) for the database backend, [permissions2](https://github.com/xyproto/permissions2) for handling users and permissions, [gopher-lua](https://github.com/yuin/gopher-lua) for interpreting and running Lua, optional [Teal](https://github.com/teal-language/tl) for type-safe Lua scripting, [http2](https://github.com/bradfitz/http2) for serving HTTP/2, [QUIC](https://github.com/xyproto/quic) for serving over QUIC, [gomarkdown/markdown](https://github.com/gomarkdown/markdown) for Markdown rendering, [amber](https://github.com/eknkc/amber) for Amber templates, [Pongo2](https://github.com/flosch/pongo2) for Pongo2 templates, [Sass](https://github.com/wellington/sass)(SCSS) and [GCSS](https://github.com/yosssi/gcss) for CSS preprocessing. [logrus](https://github.com/Sirupsen/logrus) is used for logging, [goja-babel](github.com/jvatic/goja-babel) for converting from JSX to JavaScript, [tollbooth](https://github.com/didip/tollbooth) for rate limiting, [pie](https://github.com/natefinch/pie) for plugins and [graceful](https://github.com/tylerb/graceful) for graceful shutdowns.\n\nDesign decisions\n----------------\n\n* HTTP/2 over SSL/TLS (https) is used by default, if a certificate and key is given.\n  * If not, regular HTTP is used.\n* QUIC (\"HTTP over UDP\", HTTP/3) can be enabled with a flag.\n* /data and /repos have user permissions, /admin has admin permissions and / is public, by default. This is configurable.\n* The following filenames are special, in prioritized order:\n    * index.lua is Lua code that is interpreted as a handler function for the current directory.\n    * index.html is HTML that is outputted with the correct Content-Type.\n    * index.md is Markdown code that is rendered as HTML.\n    * index.txt is plain text that is outputted with the correct Content-Type.\n    * index.pongo2, index.po2 or index.tmpl is Pongo2 code that is rendered as HTML.\n    * index.amber is Amber code that is rendered as HTML.\n    * index.hyper.js or index.hyper.jsx is JSX+HyperApp code that is rendered as HTML\n    * index.tl is Teal code that is interpreted as a handler function for the current directory.\n    * index.prompt is a content-type, an Ollama model, a blank line and a prompt, for generating content with LLMs.\n    * data.lua is Lua code, where the functions and variables are made available for Pongo2, Amber and Markdown pages in the same directory.\n    * If a single Lua script is given as a command line argument, it will be used as a standalone server. It can be used for setting up handlers or serving files and directories for specific URL prefixes.\n    * style.gcss is GCSS code that is used as the style for all Pongo2, Amber and Markdown pages in the same directory.\n* The following filename extensions are handled by Algernon:\n    * Markdown: .md (rendered as HTML)\n    * Pongo2: .po2, .pongo2 or .tpl (rendered as any text, typically HTML)\n    * Amber: .amber (rendered as HTML)\n    * Sass: .scss (rendered as CSS)\n    * GCSS: .gcss (rendered as CSS)\n    * JSX: .jsx (rendered as JavaScript/ECMAScript)\n    * Lua: .lua (a script that provides its own output and content type)\n    * Teal: .tl (same as .lua but with type safety)\n    * HyperApp: .hyper.js or .hyper.jsx (rendered as HTML)\n* Other files are given a mimetype based on the extension.\n* Directories without an index file are shown as a directory listing, where the design is hard coded.\n* UTF-8 is used whenever possible.\n* The server can be configured by command line flags or with a lua script, but no configuration should be needed for getting started.\n\nFeatures and limitations\n------------------------\n\n* Supports HTTP/2, with or without HTTPS (browsers may require HTTPS when using HTTP/2).\n* Also supports QUIC and regular HTTP.\n* Can use Lua scripts as handlers for HTTP requests.\n* The Algernon executable is compiled to native and is reasonably fast.\n* Works on Linux, macOS and 64-bit Windows.\n* The [Lua interpreter](https://github.com/yuin/gopher-lua) is compiled into the executable.\n* The [Teal typechecker](https://github.com/teal-language/tl) is loaded into the Lua VM.\n* Live editing/preview when using the auto-refresh feature.\n* The use of Lua allows for short development cycles, where code is interpreted when the page is refreshed (or when the Lua file is modified, if using auto-refresh).\n* Self-contained Algernon applications can be zipped into an archive (ending with `.zip` or `.alg`) and be loaded at start.\n* Built-in support for [Markdown](https://github.com/gomarkdown/markdown), [Pongo2](https://github.com/flosch/pongo2), [Amber](https://github.com/eknkc/amber), [Sass](https://github.com/wellington/sass)(SCSS), [GCSS](https://github.com/yosssi/gcss) and [JSX](https://github.com/mamaar/risotto).\n* Redis is used for the database backend, by default.\n* Algernon will fall back to the built-in Bolt database if no Redis server is available.\n* The HTML title for a rendered Markdown page can be provided by the first line specifying the title, like this: `title: Title goes here`. This is a subset of MultiMarkdown.\n* No file converters needs to run in the background (like for SASS). Files are converted on the fly.\n* If `-autorefresh` is enabled, the browser will automatically refresh pages when the source files are changed. Works for Markdown, Lua error pages and Amber (including Sass, GCSS and *data.lua*). This only works on Linux and macOS, for now. If listening for changes on too many files, the OS limit for the number of open files may be reached.\n* Includes an interactive REPL.\n* If only given a Markdown filename as the first argument, it will be served on port 3000, without using any database, as regular HTTP. This can be handy for viewing `README.md` files locally. Use `-m` to display it in a browser and only serve it once.\n* Full multi-threading. All available CPUs will be used.\n* Supports rate limiting, by using [tollbooth](https://github.com/didip/tollbooth).\n* The `help` command is available at the Lua REPL, for a quick overview of the available Lua functions.\n* Can load plugins written in any language. Plugins must offer the `Lua.Code` and `Lua.Help` functions and talk JSON-RPC over stderr+stdin. See [pie](https://github.com/natefinch/pie) for more information. Sample plugins for Go and Python are in the `plugins` directory.\n* Thread-safe file caching is built-in, with several available cache modes (for only caching images, for example).\n* Can read from and save to JSON documents. Supports simple JSON path expressions (like a simple version of XPath, but for JSON).\n* If cache compression is enabled, files that are stored in the cache can be sent directly from the cache to the client, without decompressing.\n* Files that are sent to the client are compressed with [gzip](https://golang.org/pkg/compress/gzip/#BestSpeed), unless they are under 4096 bytes.\n* When using PostgreSQL, the HSTORE key/value type is used (available in PostgreSQL version 9.1 or later).\n* No external dependencies, only pure Go.\n* Requires Go \u003e= 1.21 or a version of GCC/`gccgo` that supports Go 1.21.\n* The Lua implementation used in Algernon (gopherlua) does not support `package.loadlib`.\n\nQ\u0026A\n---\n\nQ:\n\n\u003e What is the benefit of using this? In what scenario would this excel? Thanks. -- [mtw@HN](https://news.ycombinator.com/item?id=19583144).\n\nA:\n\n\u003e Good question. I'm not sure if it excels in any scenario. There are specialized web servers that excel at caching or at raw performance. There are dedicated backends for popular front-end toolkits like Vue or React. There are dedicated editors that excel at editing and previewing Markdown, or HTML.\n\u003e\n\u003e I guess the main benefit is that Algernon covers a lot of ground, with a minimum of configuration, while being powerful enough to have a plugin system and support for programming in Lua. There is an auto-refresh feature that uses Server Sent Events, when editing Markdown or web pages. There is also support for the latest in Web technologies, like HTTP/2, QUIC and TLS 1.3. The caching system is decent. And the use of Go ensures that also smaller platforms like NetBSD and systems like Raspberry Pi are covered. There are no external dependencies, so Algernon can run on any system that Go can support.\n\u003e\n\u003e The main benefit is that is is versatile, fresh, and covers many platforms and use cases.\n\u003e\n\u003e For a more specific description of a potential benefit, a more specific use case would be needed.\n\nInstallation\n------------------\n\n##### macOS\n\n* Install [Homebrew](https://brew.sh), if needed.\n* `brew install algernon`\n\n##### Arch Linux\n\n* `pacman -S algernon`\n\n##### Any system where Go is available\n\nThis method is using the latest commit from the main branch:\n\n    go install github.com/xyproto/algernon@main\n\nIf neeed, add `~/go/bin` to the path. For example: `export PATH=$PATH:$HOME/go/bin`.\n\nUtilities\n---------\n* Comes with the `alg2docker` utility, for creating Docker images from Algernon web applications (`.alg` files).\n* [http2check](https://github.com/xyproto/http2check) can be used for checking if a web server is offering [HTTP/2](https://tools.ietf.org/html/rfc7540).\n\nOverview\n--------\n\nRunning Algernon:\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/algernon_gopher.png\"\u003e\n\nScreenshot of an earlier version:\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/algernon_redis_054.png\"\u003e\n\n---\n\nThe idea is that web pages can be written in Markdown, Pongo2, Amber, HTML or JSX (+React or HyperApp), depending on the need, and styled with CSS, Sass(SCSS) or GCSS, while data can be provided by a Lua or Teal script that talks to Redis, BoltDB, PostgreSQL, MSQL or MariaDB/MySQL.\n\nAmber and GCSS is a good combination for static pages, that allows for more clarity and less repetition than HTML and CSS. It˙s also easy to use Lua for providing data for the Amber templates, which helps separate model, controller and view.\n\nPongo2, Sass and Lua or Teal also combines well. Pongo2 is more flexible than Amber.\n\nThe auto-refresh feature is supported when using Markdown, Pongo2 or Amber, and is useful to get an instant preview when developing.\n\nThe JSX to JavaScript (ECMAscript) transpiler is built-in.\n\nRedis is fast, scalable and offers good [data persistence](https://redis.io/topics/persistence). This should be the preferred backend.\n\nBolt is a [pure key/value store](https://github.com/coreos/bbolt), written in Go. It makes it easy to run Algernon without having to set up a database host first.\nMariaDB/MySQL support is included because of its widespread availability.\n\nPostgreSQL is a solid and fast database that is also supported.\n\nScreenshots\n-----------\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/algernon_markdown.png\"\u003e\n\n*Markdown can easily be styled with Sass or GCSS.*\n\n---\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/algernon_lua_error.png\"\u003e\n\n*This is how errors in Lua scripts are handled, when Debug mode is enabled.*\n\n---\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/algernon_threejs.png\"\u003e\n\n*One of the poems of Algernon Charles Swinburne, with three rotating tori in the background.*\n*Uses CSS3 for the Gaussian blur and [three.js](https://threejs.org) for the 3D graphics.*\n\n---\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/prettify.png\"\u003e\n\n*Screenshot of the \u003cstrong\u003eprettify\u003c/strong\u003e sample. Served from a single Lua script.*\n\n---\n\n\u003cimg src=\"https://raw.github.com/xyproto/algernon/main/img/algernon_react.png\"\u003e\n\n*JSX transforms are built-in. Using [React](https://facebook.github.io/react/) together with Algernon is easy.*\n\nSamples\n-------\n\nThe sample collection can be downloaded from the `samples` directory in this repository, or here: [samplepack.zip](https://algernon.roboticoverlords.org/samplepack.zip).\n\n\nGetting started\n---------------\n\n##### Run Algernon in \"dev\" mode\n\nThis enables debug mode, uses the internal Bolt database, uses regular HTTP instead of HTTPS+HTTP/2 and enables caching for all files except: Pongo2, Amber, Lua, Teal, Sass, GCSS, Markdown and JSX.\n\n* `algernon -e`\n\nThen try creating an `index.lua` file with `print(\"Hello, World!\")` and visit the served web page in a browser.\n\n##### Enable HTTP/2 in the browser (for older browsers)\n\n* Chrome: go to `chrome://flags/#enable-spdy4`, enable, save and restart the browser.\n* Firefox: go to `about:config`, set `network.http.spdy.enabled.http2draft` to `true`. You might need the nightly version of Firefox.\n\n##### Configure the required ports for local use\n\n* You may need to change the firewall settings for port 3000, if you wish to use the default port for exploring the samples.\n* For the auto-refresh feature to work, port 5553 must be available (or another host/port of your choosing, if configured otherwise).\n\n##### Prepare for running the samples\n\n    git clone https://github.com/xyproto/algernon\n    make -C algernon\n\n##### Launch the \"welcome\" page\n\n* Run `./welcome.sh` to start serving the \"welcome\" sample.\n* Visit `http://localhost:3000/`\n\n##### Create your own Algernon application, for regular HTTP\n\n* `mkdir mypage`\n* `cd mypage`\n* Create a file named `index.lua`, with the following contents:\n  `print(\"Hello, Algernon\")`\n* Start `algernon --httponly --autorefresh`.\n* Visit `http://localhost:3000/`.\n* Edit `index.lua` and refresh the browser to see the new result.\n* If there were errors, the page will automatically refresh when `index.lua` is changed.\n* Markdown, Pongo2 and Amber pages will also refresh automatically, as long as `-autorefresh` is used.\n\n##### Create your own Algernon application, for HTTP/2 + HTTPS\n\n* `mkdir mypage`\n* `cd mypage`\n* Create a file named `index.lua`, with the following contents:\n  `print(\"Hello, Algernon\")`\n* Create a self-signed certificate, just for testing:\n * `openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 3000 -nodes`\n * Press return at all the prompts, but enter `localhost` at *Common Name*.\n * For production, store the keys in a directory with as strict permissions as possible, then specify them with the `--cert` and `--key` flags.\n* Start `algernon`.\n* Visit `https://localhost:3000/`.\n* If you have not imported the certificates into the browser, nor used certificates that are signed by trusted certificate authorities, perform the necessary clicks to confirm that you wish to visit this page.\n* Edit `index.lua` and refresh the browser to see the result (or a Lua error message, if the script had a problem).\n\nThere is also a small [tutorial](TUTORIAL.md).\n\nUsing AI / LLMs / Ollama\n------------------------\n\n* The `ollama` server must be running locally, or a `host:port` must be set in the `OLLAMA_HOST` environment variable.\n\nFor example, using the default `tinyllama` model (will be downloaded at first use, the size is 637 MiB and it should run anywhere).\n\n```\nlua\u003e ollama()\nAutumn leaves, crisp air, poetry flowing - this is what comes to mind when I think of Algernon.\n\nlua\u003e ollama(\"Write a haiku about software developers\")\nThe software developer,\nIn silence, tapping at keys,\nCreating digital worlds.\n```\n\nUsing `OllamaClient` and the `mixtral` model (will be downloaded at first use, the size is 26 GiB and it might require quite a bit of RAM and also a fast CPU and/or GPU).\n\n```\nlua\u003e oc = OllamaClient(\"mixtral\")\nlua\u003e oc:ask(\"Write a quicksort function in OCaml\")\nSure! Here's an implementation of the quicksort algorithm in OCaml:\n\nlet rec qsort = function\n  | [] -\u003e []\n  | pivot :: rest -\u003e\n      let smaller, greater = List.partition (fun x -\u003e x \u003c pivot) rest in\n      qsort smaller @ [pivot] @ qsort greater\n\nThis function takes a list as input and returns a new list with the same elements but sorted in ascending order using the quicksort algorithm. The `qsort` funct.\n\nHere are some examples of using the `qsort` function:\n\n# qsort [5; 2; 9; 1; 3];;\n- : int list = [1; 2; 3; 5; 9]\n\n# qsort [\"apple\"; \"banana\"; \"cherry\"];;\n- : string list = [\"apple\"; \"banana\"; \"cherry\"]\n\n# qsort [3.14; 2.718; 1.618];;\n- : float list = [1.618; 2.718; 3.14]\n\nI hope this helps! Let me know if you have any questions or need further clarification.\n```\n\nExample use of finding the distance between how the LLM models interpret the prompts:\n\n```\nlua\u003e oc = OllamaClient(\"llama3\")\nlua\u003e oc:distance(\"cat\", \"dog\")\n0.3629187146002938\nlua\u003e oc:distance(\"cat\", \"kitten\")\n0.3584441305547792\nlua\u003e oc:distance(\"dog\", \"puppy\")\n0.2825554473355113\nlua\u003e oc:distance(\"dog\", \"kraken\", \"manhattan\")\n7945.885516248905\nlua\u003e oc:distance(\"dog\", \"kraken\", \"cosine\")\n0.5277307399621305\n```\n\nAs you can tell, according to Llama3, \"dog\" is closer to \"puppy\" (0.28) than \"cat\" is to \"kitten\" (0.36) and \"dog\" is very different from \"kraken\" (0.53).\n\nThe available distance measurement algorithms are: `cosine`, `euclidean`, `manhattan`, `chebyshev` and `hamming`. The default metric is `cosine`.\n\nAvailable Ollama models are available here: [Ollama Library](https://ollama.com/library).\n\nThere is also support for `.prompt` files that can generate contents, such as HTML pages, in a reproducible way. The results will be cached for as long as Algernon is running.\n\nExample `index.prompt` file:\n\n```\ntext/html\ngemma\n\nGenerate a fun and over the top web page that demonstrates the use of CSS animations and JavaScript.\nEverything should be inline in one HTML document. Only output the full and complete HTML document.\n```\n\nThe experimental `prompt` format is very simple:\n\n* The first line is the `content-type`.\n* The second line is the Ollama model, such as `tinyllama:latest` or just `tinyllama`.\n* The third line is blank.\n* The rest of the lines are the prompt that will be passed to the large language model.\n\nNote that the Ollama server must be fast enough to reply within 10 seconds for this to work!\n`tinyllama` or `gemma` should be more than fast enough with a good GPU or on an M1/M2/M3 processor.\n\nFor more fine-grained control, try using the Ollama-related Lua functions instead, and please create a PR or issue if something central is missing.\n\nThe `ClearCache()` function can be used at the Algernon Lua prompt to also clear the AI cache.\n\nBasic Lua functions\n-------------------\n\n~~~c\n// Return the version string for the server.\nversion() -\u003e string\n\n// Sleep the given number of seconds (can be a float).\nsleep(number)\n\n// Log the given strings as information. Takes a variable number of strings.\nlog(...)\n\n// Log the given strings as a warning. Takes a variable number of strings.\nwarn(...)\n\n// Log the given strings as an error. Takes a variable number of strings.\nerr(...)\n\n// Return the number of nanoseconds from 1970 (\"Unix time\")\nunixnano() -\u003e number\n\n// Convert Markdown to HTML\nmarkdown(string) -\u003e string\n\n// Sanitize HTML\nsanhtml(string) -\u003e string\n\n// Return the directory where the REPL or script is running. If a filename (optional) is given, then the path to where the script is running, joined with a path separator and the given filename, is returned.\nscriptdir([string]) -\u003e string\n\n// Read a glob, ie. \"*.md\" in the current script directory, or the given directory (optional). The contents of all found files are reeturned as a table.\nreadglob(string[, string]) -\u003e table\n\n// Return the directory where the server is running. If a filename (optional) is given, then the path to where the server is running, joined with a path separator and the given filename, is returned.\nserverdir([string]) -\u003e string\n~~~\n\n\nLua functions for handling requests\n-----------------------------------\n\n~~~c\n// Set the Content-Type for a page.\ncontent(string)\n\n// Return the requested HTTP method (GET, POST etc).\nmethod() -\u003e string\n\n// Output text to the browser/client. Takes a variable number of strings.\nprint(...)\n\n// Same as print, but does not add a newline at the end.\nprint_nonl(...)\n\n// Return the requested URL path.\nurlpath() -\u003e string\n\n// Return the HTTP header in the request, for a given key, or an empty string.\nheader(string) -\u003e string\n\n// Set an HTTP header given a key and a value.\nsetheader(string, string)\n\n// Return the HTTP headers, as a table.\nheaders() -\u003e table\n\n// Return the HTTP body in the request (will only read the body once, since it's streamed).\nbody() -\u003e string\n\n// Set a HTTP status code (like 200 or 404). Must be used before other functions that writes to the client!\nstatus(number)\n\n// Set a HTTP status code and output a message (optional).\nerror(number[, string])\n\n// Serve a file that exists in the same directory as the script. Takes a filename.\nserve(string)\n\n// Serve a Pongo2 template file, with an optional table with template key/values.\nserve2(string[, table)\n\n// Return the rendered contents of a file that exists in the same directory as the script. Takes a filename.\nrender(string) -\u003e string\n\n// Return a table with keys and values as given in a posted form, or as given in the URL.\nformdata() -\u003e table\n\n// Return a table with keys and values as given in the request URL, or in the given URL (`/some/page?x=7` makes the key `x` with the value `7` available).\nurldata([string]) -\u003e table\n\n// Redirect to an absolute or relative URL. May take an HTTP status code that will be used when redirecting.\n// Returns false if the connection has been closed.\nredirect(string[, number]) -\u003e bool\n\n// Permanent redirect to an absolute or relative URL. Uses status code 302.\n// Returns false if the connection has been closed.\npermanent_redirect(string) -\u003e bool\n\n// Send \"Connection: close\" as a header to the client, flush the body and also\n// stop Lua functions from writing more data to the HTTP body.\nclose()\n\n// Transmit what has been outputted so far, to the client. Returns true if it worked out and the connection has not been closed.\nflush() -\u003e bool\n~~~\n\n\nLua functions for formatted output\n----------------------------------\n\n~~~c\n// Output rendered Markdown to the browser/client. The given text is converted from Markdown to HTML. Takes a variable number of strings. A script tag may also be added.\nmprint(...)\n\n// Output rendered Markdown to the browser/client. The given text is converted from Markdown to HTML. Takes a variable number of strings. If a script tag needs to be added to the HTML, it is returned as a string.\nmprint_ret(...) -\u003e string\n\n// Output rendered Amber to the browser/client. The given text is converted from Amber to HTML. Takes a variable number of strings.\naprint(...)\n\n// Output rendered GCSS to the browser/client. The given text is converted from GCSS to CSS. Takes a variable number of strings.\ngprint(...)\n\n// Output rendered HyperApp JSX to the browser/client. The given text is converted from JSX to JavaScript. Takes a variable number of strings.\nhprint(...)\n\n// Output rendered React JSX to the browser/client. The given text is converted from JSX to JavaScript. Takes a variable number of strings.\njprint(...)\n\n// Output rendered HTML to the browser/client. The given text is converted from Pongo2 to HTML. The first argument is the Pongo2 template and the second argument is a table. The keys in the table can be referred to in the template.\npoprint(string[, table])\n\n// Output a simple HTML page with a message, title and theme.\n// The title and theme are optional.\nmsgpage(string[, string][, string])\n~~~\n\n\nLua functions related to JSON\n-----------------------------\n\nTips:\n\n* Use `JFile(`*filename*`)` to use or store a JSON document in the same directory as the Lua script.\n* A JSON path is on the form `x.mapkey.listname[2].mapkey`, where `[`, `]` and `.` have special meaning. It can be used for pinpointing a specific place within a JSON document. It's a bit like a simple version of XPath, but for JSON.\n* Use `tostring(userdata)` to fetch the JSON string from the JFile object.\n\n~~~c\n// Use, or create, a JSON document/file.\nJFile(filename) -\u003e userdata\n\n// Takes a JSON path. Returns a string value, or an empty string.\njfile:getstring(string) -\u003e string\n\n// Takes a JSON path. Returns a JNode or nil.\njfile:getnode(string) -\u003e userdata\n\n// Takes a JSON path. Returns a value or nil.\njfile:get(string) -\u003e value\n\n// Takes a JSON path (optional) and JSON data to be added to the list.\n// The JSON path must point to a list, if given, unless the JSON file is empty.\n// \"x\" is the default JSON path. Returns true on success.\njfile:add([string, ]string) -\u003e bool\n\n// Take a JSON path and a string value. Changes the entry. Returns true on success.\njfile:set(string, string) -\u003e bool\n\n// Remove a key in a map. Takes a JSON path, returns true on success.\njfile:delkey(string) -\u003e bool\n\n// Convert a Lua table, where keys are strings and values are strings or numbers, to JSON.\n// Takes an optional number of spaces to indent the JSON data.\n// (Note that keys in JSON maps are always strings, ref. the JSON standard).\njson(table[, number]) -\u003e string\n\n// Create a JSON document node.\nJNode() -\u003e userdata\n\n// Add JSON data to a node. The first argument is an optional JSON path.\n// The second argument is a JSON data string. Returns true on success.\n// \"x\" is the default JSON path.\njnode:add([string, ]string) -\u003e bool\n\n// Given a JSON path, retrieves a JSON node.\njnode:get(string) -\u003e userdata\n\n// Given a JSON path, retrieves a JSON string.\njnode:getstring(string) -\u003e string\n\n// Given a JSON path and a JSON string, set the value.\njnode:set(string, string)\n\n// Given a JSON path, remove a key from a map.\njnode:delkey(string) -\u003e bool\n\n// Return the JSON data, nicely formatted.\njnode:pretty() -\u003e string\n\n// Return the JSON data, as a compact string.\njnode:compact() -\u003e string\n\n// Sends JSON data to the given URL. Returns the HTTP status code as a string.\n// The content type is set to \"application/json; charset=utf-8\".\n// The second argument is an optional authentication token that is used for the\n// Authorization header field.\njnode:POST(string[, string]) -\u003e string\n\n// Alias for jnode:POST\njnode:send(string[, string]) -\u003e string\n\n// Same as jnode:POST, but sends HTTP PUT instead.\njnode:PUT(string[, string]) -\u003e string\n\n// Fetches JSON over HTTP given an URL that starts with http or https.\n// The JSON data is placed in the JNode. Returns the HTTP status code as a string.\njnode:GET(string) -\u003e string\n\n// Alias for jnode:GET\njnode:receive(string) -\u003e string\n\n// Convert from a simple Lua table to a JSON string\nJSON(table) -\u003e string\n~~~\n\nLua functions for making HTTP requests\n--------------------------------------\n\nQuick example: `GET(\"http://ix.io/1FTw\")`\n\n~~~c\n// Create a new HTTP Client object\nHTTPClient() -\u003e userdata\n\n// Select Accept-Language (ie. \"en-us\")\nhc:SetLanguage(string)\n\n// Set the request timeout (in milliseconds)\nhc:SetTimeout(number)\n\n// Set a cookie (name and value)\nhc:SetCookie(string, string)\n\n// Set the user agent (ie. \"curl\")\nhc:SetUserAgent(string)\n\n// Perform a HTTP GET request. First comes the URL, then an optional table with\n// URL parameters, then an optional table with HTTP headers.\nhc:Get(string, [table], [table]) -\u003e string\n\n// Perform a HTTP POST request. It's the same arguments as for `Get`, except\n// the fourth optional argument is the POST body.\nhc:Post(string, [table], [table], [string]) -\u003e string\n\n// Like `Get`, except the first argument is the HTTP method (like \"PUT\")\nhc:Do(string, string, [table], [table]) -\u003e string\n\n// Shorthand for HTTPClient():Get()\nGET(string, [table], [table]) -\u003e string\n\n// Shorthand for HTTPClient():Post()\nPOST(string, [table], [table], [string]) -\u003e string\n\n// Shorthand for HTTPClient():Do()\nDO(string, string, [table], [table]) -\u003e string\n~~~\n\n\nLua functions for AI\n--------------------\n\n~~~c\n// Connect to the local Ollama server and use either the `tinyllama` model, or the supplied model string (like ie. mixtral:latest).\n// Also takes an optional host address.\nOllamaClient([string], [string]) -\u003e userdata\n\n// Download the required model, if needed. This may take a while if the model is large.\noc:pull()\n\n// Pass a prompt to Ollama and return the reproducible generated output. If no prompt is given, it will request a poem about Algernon.\n// Can also take an optional model name, which will trigger a download if the model is missing.\noc:ask([string], [string]) -\u003e string\n\n// Pass a prompt to Ollama and return the generated output that will differ every time.\n// Can also take an optional model name, which will trigger a download if the model is missing.\noc:creative([string], [string]) -\u003e string\n\n// Check if the given model name is downloaded and ready\noc:has(string)\n\n// List all models that are downloaded and ready\noc:list()\n\n// Get or set the currently active model name, but does not pull anything\noc:model([string])\n\n// Get the size of the given model name as a human-friendly string\noc:size(string) -\u003e string\n\n// Get the size of the given model name, in bytes\noc:bytesize(string) -\u003e number\n\n// Given two prompts, return how similar they are.\n// The first optional string is the algorithm for measuring the distance: cosine, euclidean, manhattan, chebyshev or hamming.\n// Only the two first letters of the algorithm name are needed, so \"co\" or \"ma\" are also valid. The default is cosine.\n// The second optional string is the model name.\noc:distance(string, string, [string], [string]) -\u003e number\n\n// Convenience function for the local Ollama server that takes an optional prompt and an optional model name.\n// Generates a poem with the `tinyllama` model by default.\nollama([string], [string]) -\u003e string\n\n// Convenience function for Base64-encoding the given file\nbase64EncodeFile(string) -\u003e string\n\n// Describe the given base64-encoded image using Ollama (and the `llava-llama3` model, by default)\ndescribeImage(string, [string]) -\u003e string\n\n// Given two embeddings (tables of floats, representing text or data), return how similar they are.\n// The optional string is the algorithm for measuring the distance: cosine, euclidean, manhattan, chebyshev or hamming.\n// Only the two first letters of the algorithm name are needed, so \"co\" or \"ma\" are also valid. The default is cosine.\nembeddedDistance(table, table, [string]) -\u003e number\n~~~\n\n\nLua functions for plugins\n-------------------------\n\n~~~c\n// Load a plugin given the path to an executable. Returns true on success. Will return the plugin help text if called on the Lua prompt.\n// Pass in true as the second argument to keep it running.\nPlugin(string, [bool])\n\n// Returns the Lua code as returned by the Lua.Code function in the plugin, given a plugin path. May return an empty string.\n// Pass in true as the second argument to keep it running.\nPluginCode(string, [bool]) -\u003e string\n\n// Takes a plugin path, function name and arguments. Returns an empty string if the function call fails, or the results as a JSON string if successful.\nCallPlugin(string, string, ...) -\u003e string\n~~~\n\n\nLua functions for code libraries\n--------------------------------\n\nThese functions can be used in combination with the plugin functions for storing Lua code returned by plugins when serverconf.lua is loaded, then retrieve the Lua code later, when handling requests. The code is stored in the database.\n\n~~~c\n// Create or uses a code library object. Optionally takes a data structure name as the first parameter.\nCodeLib([string]) -\u003e userdata\n\n// Given a namespace and Lua code, add the given code to the namespace. Returns true on success.\ncodelib:add(string, string) -\u003e bool\n\n// Given a namespace and Lua code, set the given code as the only code in the namespace. Returns true on success.\ncodelib:set(string, string) -\u003e bool\n\n// Given a namespace, return Lua code, or an empty string.\ncodelib:get(string) -\u003e string\n\n// Import (eval) code from the given namespace into the current Lua state. Returns true on success.\ncodelib:import(string) -\u003e bool\n\n// Completely clear the code library. Returns true on success.\ncodelib:clear() -\u003e bool\n~~~\n\n\nLua functions for file uploads\n------------------------------\n\n~~~c\n// Creates a file upload object. Takes a form ID (from a POST request) as the first parameter.\n// Takes an optional maximum upload size (in MiB) as the second parameter.\n// Returns nil and an error string on failure, or userdata and an empty string on success.\nUploadedFile(string[, number]) -\u003e userdata, string\n\n// Return the uploaded filename, as specified by the client\nuploadedfile:filename() -\u003e string\n\n// Return the size of the data that has been received\nuploadedfile:size() -\u003e number\n\n// Return the mime type of the uploaded file, as specified by the client\nuploadedfile:mimetype() -\u003e string\n\n// Return the full textual content of the uploaded file\nuploadedfile:content() -\u003e string\n\n// Save the uploaded data locally. Takes an optional filename. Returns true on success.\nuploadedfile:save([string]) -\u003e bool\n\n// Save the uploaded data as the client-provided filename, in the specified directory.\n// Takes a relative or absolute path. Returns true on success.\nuploadedfile:savein(string)  -\u003e bool\n\n// Return the uploaded data as a base64-encoded string\nuploadedfile:base64() -\u003e string\n~~~\n\n\nLua functions for the file cache\n--------------------------------\n\n~~~c\n// Return information about the file cache.\nCacheInfo() -\u003e string\n\n// Clear the file cache.\nClearCache()\n\n// Load a file into the cache, returns true on success.\npreload(string) -\u003e bool\n~~~\n\nLua functions for data structures\n---------------------------------\n\n##### Set\n\n~~~c\n// Get or create a database-backed Set (takes a name, returns a set object)\nSet(string) -\u003e userdata\n\n// Add an element to the set\nset:add(string)\n\n// Remove an element from the set\nset:del(string)\n\n// Check if a set contains a value\n// Returns true only if the value exists and there were no errors.\nset:has(string) -\u003e bool\n\n// Get all members of the set\nset:getall() -\u003e table\n\n// Remove the set itself. Returns true on success.\nset:remove() -\u003e bool\n\n// Clear the set\nset:clear() -\u003e bool\n~~~\n\n##### List\n\n~~~c\n// Get or create a database-backed List (takes a name, returns a list object)\nList(string) -\u003e userdata\n\n// Add an element to the list\nlist:add(string)\n\n// Get all members of the list\nlist:getall() -\u003e table\n\n// Get the last element of the list\n// The returned value can be empty\nlist:getlast() -\u003e string\n\n// Get the N last elements of the list\nlist:getlastn(number) -\u003e table\n\n// Remove the list itself. Returns true on success.\nlist:remove() -\u003e bool\n\n// Clear the list. Returns true on success.\nlist:clear() -\u003e bool\n\n// Return all list elements (expected to be JSON strings) as a JSON list\nlist:json() -\u003e string\n~~~\n\n##### HashMap\n\n~~~c\n// Get or create a database-backed HashMap (takes a name, returns a hash map object)\nHashMap(string) -\u003e userdata\n\n// For a given element id (for instance a user id), set a key\n// (for instance \"password\") and a value.\n// Returns true on success.\nhash:set(string, string, string) -\u003e bool\n\n// For a given element id (for instance a user id), and a key\n// (for instance \"password\"), return a value.\n// Returns a value only if they key was found and if there were no errors.\nhash:get(string, string) -\u003e string\n\n// For a given element id (for instance a user id), and a key\n// (for instance \"password\"), check if the key exists in the hash map.\n// Returns true only if it exists and there were no errors.\nhash:has(string, string) -\u003e bool\n\n// For a given element id (for instance a user id), check if it exists.\n// Returns true only if it exists and there were no errors.\nhash:exists(string) -\u003e bool\n\n// Get all keys of the hash map\nhash:getall() -\u003e table\n\n// Remove a key for an entry in a hash map\n// (for instance the email field for a user)\n// Returns true on success\nhash:delkey(string, string) -\u003e bool\n\n// Remove an element (for instance a user)\n// Returns true on success\nhash:del(string) -\u003e bool\n\n// Remove the hash map itself. Returns true on success.\nhash:remove() -\u003e bool\n\n// Clear the hash map. Returns true on success.\nhash:clear() -\u003e bool\n~~~\n\n##### KeyValue\n\n~~~c\n// Get or create a database-backed KeyValue collection (takes a name, returns a key/value object)\nKeyValue(string) -\u003e userdata\n\n// Set a key and value. Returns true on success.\nkv:set(string, string) -\u003e bool\n\n// Takes a key, returns a value.\n// Returns an empty string if the function fails.\nkv:get(string) -\u003e string\n\n// Takes a key, returns the value+1.\n// Creates a key/value and returns \"1\" if it did not already exist.\n// Returns an empty string if the function fails.\nkv:inc(string) -\u003e string\n\n// Remove a key. Returns true on success.\nkv:del(string) -\u003e bool\n\n// Remove the KeyValue itself. Returns true on success.\nkv:remove() -\u003e bool\n\n// Clear the KeyValue. Returns true on success.\nkv:clear() -\u003e bool\n~~~\n\nLua functions for external databases\n------------------------------------\n\n~~~c\n// Query a PostgreSQL database with a SQL query and a connection string\nPQ([string], [string]) -\u003e table\n~~~\n\nThe default connection string is `host=localhost port=5432 user=postgres dbname=test sslmode=disable` and the default SQL query is `SELECT version()`. Database connections are re-used if they still answer to `.Ping()`, for the same connection string.\n\n~~~c\n// Query a MSSQL database with SQL, a connection string, and a parameter table\nMSSQL([string], [string], [table]) -\u003e table\n~~~\n\n- The default connection string is `server=localhost;user=sa;password=Password123,port=1433` and the default SQL query is `\"SELECT @@VERSION`. Database connections are re-used if they still answer to `.Ping()`, for the same connection string.\n- If the param table is numerically indexed, positional placeholders are expected: `MSSQL(\"SELECT * FROM users WHERE first = @p1 AND last = @p2\", conn, {\"John\", \"Smith\"})`\n- If the param table is keyed with strings, named placeholders are expected: `MSSQL(\"SELECT * FROM users WHERE first = @first AND last = @last\", conn, {first = \"John\", last = \"Smith\"})`\n\n\nLua functions for handling users and permissions\n------------------------------------------------\n\n~~~c\n// Check if the current user has \"user\" rights\nUserRights() -\u003e bool\n\n// Check if the given username exists (does not look at the list of unconfirmed users)\nHasUser(string) -\u003e bool\n\n// Check if the given username exists in the list of unconfirmed users\nHasUnconfirmedUser(string) -\u003e bool\n\n// Get the value from the given boolean field\n// Takes a username and field name\nBooleanField(string, string) -\u003e bool\n\n// Save a value as a boolean field\n// Takes a username, field name and boolean value\nSetBooleanField(string, string, bool)\n\n// Check if a given username is confirmed\nIsConfirmed(string) -\u003e bool\n\n// Check if a given username is logged in\nIsLoggedIn(string) -\u003e bool\n\n// Check if the current user has \"admin rights\"\nAdminRights() -\u003e bool\n\n// Check if a given username is an admin\nIsAdmin(string) -\u003e bool\n\n// Get the username stored in a cookie, or an empty string\nUsernameCookie() -\u003e string\n\n// Store the username in a cookie, returns true on success\nSetUsernameCookie(string) -\u003e bool\n\n// Clear the login cookie\nClearCookie()\n\n// Get a table containing all usernames\nAllUsernames() -\u003e table\n\n// Get the email for a given username, or an empty string\nEmail(string) -\u003e string\n\n// Get the password hash for a given username, or an empty string\nPasswordHash(string) -\u003e string\n\n// Get all unconfirmed usernames\nAllUnconfirmedUsernames() -\u003e table\n\n// Get the existing confirmation code for a given user,\n// or an empty string. Takes a username.\nConfirmationCode(string) -\u003e string\n\n// Add a user to the list of unconfirmed users\n// Takes a username and a confirmation code\n// Remember to also add a user, when registering new users.\nAddUnconfirmed(string, string)\n\n// Remove a user from the list of unconfirmed users\n// Takes a username\nRemoveUnconfirmed(string)\n\n// Mark a user as confirmed\n// Takes a username\nMarkConfirmed(string)\n\n// Removes a user\n// Takes a username\nRemoveUser(string)\n\n// Make a user an admin\n// Takes a username\nSetAdminStatus(string)\n\n// Make an admin user a regular user\n// Takes a username\nRemoveAdminStatus(string)\n\n// Add a user\n// Takes a username, password and email\nAddUser(string, string, string)\n\n// Set a user as logged in on the server (not cookie)\n// Takes a username\nSetLoggedIn(string)\n\n// Set a user as logged out on the server (not cookie)\n// Takes a username\nSetLoggedOut(string)\n\n// Log in a user, both on the server and with a cookie\n// Takes a username\nLogin(string)\n\n// Log out a user, on the server (which is enough)\n// Takes a username\nLogout(string)\n\n// Get the current username, from the cookie\nUsername() -\u003e string\n\n// Get the current cookie timeout\n// Takes a username\nCookieTimeout(string) -\u003e number\n\n// Set the current cookie timeout\n// Takes a timeout number, measured in seconds\nSetCookieTimeout(number)\n\n// Get the current server-wide cookie secret. This is used when setting\n// and getting browser cookies when users log in.\nCookieSecret() -\u003e string\n\n// Set the current server-side cookie secret. This is used when setting\n// and getting browser cookies when users log in. Using the same secret\n// makes browser cookies usable across server restarts.\nSetCookieSecret(string)\n\n// Get the current password hashing algorithm (bcrypt, bcrypt+ or sha256)\nPasswordAlgo() -\u003e string\n\n// Set the current password hashing algorithm (bcrypt, bcrypt+ or sha256)\n// ‘bcrypt+‘ accepts bcrypt or sha256 for old passwords, but will only use\n// bcrypt for new passwords.\nSetPasswordAlgo(string)\n\n// Hash the password\n// Takes a username and password (username can be used for salting sha256)\nHashPassword(string, string) -\u003e string\n\n// Change the password for a user, given a username and a new password\nSetPassword(string, string)\n\n// Check if a given username and password is correct\n// Takes a username and password\nCorrectPassword(string, string) -\u003e bool\n\n// Checks if a confirmation code is already in use\n// Takes a confirmation code\nAlreadyHasConfirmationCode(string) -\u003e bool\n\n// Find a username based on a given confirmation code,\n// or returns an empty string. Takes a confirmation code\nFindUserByConfirmationCode(string) -\u003e string\n\n// Mark a user as confirmed\n// Takes a username\nConfirm(string)\n\n// Mark a user as confirmed, returns true on success\n// Takes a confirmation code\nConfirmUserByConfirmationCode(string) -\u003e bool\n\n// Set the minimum confirmation code length\n// Takes the minimum number of characters\nSetMinimumConfirmationCodeLength(number)\n\n// Generates a unique confirmation code, or an empty string\nGenerateUniqueConfirmationCode() -\u003e string\n~~~\n\n\nLua functions that are available for server configuration files\n---------------------------------------------------------------\n\n~~~c\n// Set the default address for the server on the form [host][:port].\n// May be useful in Algernon application bundles (.alg or .zip files).\nSetAddr(string)\n\n// Reset the URL prefixes and make everything *public*.\nClearPermissions()\n\n// Add an URL prefix that will have *admin* rights.\nAddAdminPrefix(string)\n\n// Add a reverse proxy given a path prefix and an endpoint URL\n// For example: \"/api\" and \"http://localhost:8080\"\nAddReverseProxy(string, string)\n\n// Add an URL prefix that will have *user* rights.\nAddUserPrefix(string)\n\n// Provide a lua function that will be used as the permission denied handler.\nDenyHandler(function)\n\n// Return a string with various server information.\nServerInfo() -\u003e string\n\n// Direct the logging to the given filename. If the filename is an empty\n// string, direct logging to stderr. Returns true on success.\nLogTo(string) -\u003e bool\n\n// Returns the version string for the server.\nversion() -\u003e string\n\n// Logs the given strings as INFO. Takes a variable number of strings.\nlog(...)\n\n// Logs the given strings as WARN. Takes a variable number of strings.\nwarn(...)\n\n// Logs the given string as ERROR. Takes a variable number of strings.\nerr(...)\n\n// Provide a lua function that will be run once, when the server is ready to start serving.\nOnReady(function)\n\n// Use a Lua file for setting up HTTP handlers instead of using the directory structure.\nServerFile(string) -\u003e bool\n\n// Serve files from this directory.\nServerDir(string) -\u003e bool\n\n// Get the cookie secret from the server configuration.\nCookieSecret() -\u003e string\n\n// Set the cookie secret that will be used when setting and getting browser cookies.\nSetCookieSecret(string)\n~~~\n\nFunctions that are only available for Lua server files\n------------------------------------------------------\n\nThis function is only available when a Lua script is used instead of a server directory, or from Lua files that are specified with the `ServerFile` function in the server configuration.\n\n~~~c\n// Given an URL path prefix (like \"/\") and a Lua function, set up an HTTP handler.\n// The given Lua function should take no arguments, but can use all the Lua functions for handling requests, like `content` and `print`.\nhandle(string, function)\n\n// Given an URL prefix (like \"/\") and a directory, serve the files and directories.\nservedir(string, string)\n~~~\n\nCommands that are only available in the REPL\n--------------------------------------------\n\n* `help` displays a syntax highlighted overview of most functions.\n* `webhelp` displays a syntax highlighted overview of functions related to handling requests.\n* `confighelp` displays a syntax highlighted overview of functions related to server configuration.\n\nExtra Lua functions\n-------------------\n\n~~~c\n// Pretty print. Outputs the values in, or a description of, the given Lua value(s).\npprint(...)\n\n// Takes a Python filename, executes the script with the `python` binary in the Path.\n// Returns the output as a Lua table, where each line is an entry.\npy(string) -\u003e table\n\n// Takes one or more system commands (separated by `;`) and runs them.\n// Returns the output lines as a table.\nrun(string) -\u003e table\n\n// Takes one or more system commands (separated by `;`) and runs them.\n// Returns stdout as a table, stderr as a table and the exit code as a number.\nrun3(string) -\u003e table, table, number\n\n// Lists the keys and values of a Lua table. Returns a string.\n// Lists the contents of the global namespace `_G` if no arguments are given.\ndir([table]) -\u003e string\n~~~\n\nMarkdown\n--------\n\nAlgernon can be used as a quick Markdown viewer with the `-m` flag.\n\nTry `algernon -m README.md` to view `README.md` in the browser, serving the file once on a port \u003e3000.\n\nIn addition to the regular Markdown syntax, Algernon supports setting the page title and syntax highlight style with a header comment like this at the top of a Markdown file:\n\n    \u003c!--\n    title: Page title\n    theme: dark\n    code_style: lovelace\n    replace_with_theme: default_theme\n    --\u003e\n\nCode is highlighted with [highlight.js](https://highlightjs.org/) and [several styles](https://highlightjs.org/static/demo/) are available.\n\nThe string that follows `replace_with_theme` will be used for replacing the current theme string (like `dark`) with the given string. This makes it possible to use one image (like `logo_default_theme.png`) for one theme and another image (`logo_dark.png`) for the dark theme.\n\nThe theme can be `light`, `dark`, `redbox`, `bw`, `github`, `wing`, `material`, `neon`, `default`, `werc`, `setconf` or a path to a CSS file. Or `style.gcss` can exist in the same directory.\n\nAn overview of available syntax highlighting styles can be found at the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).\n\n\nHTTPS certificates with Let's Encrypt and Algernon\n--------------------------------------------------\n\n#### Method 1\n\nFollow the guide at [certbot.eff.org](https://certbot.eff.org/) for the \"None of the above\" web server, then start `algernon` with `--cert=/etc/letsencrypt/live/myhappydomain.com/cert.pem --key=/etc/letsencrypt/live/myhappydomain.com/privkey.pem` where `myhappydomain.com` is replaced with your own domain name.\n\nFirst make Algernon serve a directory for the domain, like `/srv/myhappydomain.com`, then use that as the webroot when configuring `certbot` with the `certbot certonly` command.\n\nRemember to set up a cron-job or something similar to run `certbot renew` every once in a while (every 12 hours is suggested by [certbot.eff.org](https://certbot.eff.org/)). Also remember to restart the algernon service after updating the certificates.\n\n#### Method 2\n\nUse the `--letsencrypt` flag together with the `--domain` flag to automatically fetch and use certificates from Let's Encrypt.\n\nFor instance, if `/srv/myhappydomain.com` exists, then `algernon --letsencrypt --domain /srv` can be used to serve `myhappydomain.com` if it points to this server, and fetch certificates from Let's Encrypt.\n\nWhen `--letsencrypt` is used, it will try to serve on port 443 and 80 (which redirects to 443).\n\nReleases\n--------\n\n* [Arch Linux package](https://aur.archlinux.org/packages/algernon) in the AUR.\n* [Windows executable](https://github.com/xyproto/algernon/releases/tag/v1.0-win8-64).\n* [macOS homebrew package](https://raw.githubusercontent.com/xyproto/algernon/main/system/homebrew/algernon.rb)\n* [Algernon Tray Launcher for macOS, in App Store](https://itunes.apple.com/no/app/algernon-server/id1030394926?l=nb\u0026mt=12)\n* Source releases are tagged with a version number at release.\n\n\nRequirements\n------------\n\n* `go 1.21` or later is a requirement for building Algernon.\n* For `go 1.10`, `1.11`, `1.12`, `1.13`, `1.14`, '1.15`, `1.16` + `gcc-go \u003c10` version `1.12.7` of Algernon is the last supported version.\n\nAccess logs\n-----------\n\nCan log to a Combined Log Format access log with the `--accesslog` flag. This works nicely together with [goaccess](https://goaccess.io/).\n\n### Example usage\n\nServe files in one directory:\n\n    algernon --accesslog=access.log -x\n\nThen visit the web page once, to create one entry in the access.log.\n\nThe wonderful [goaccess](https://goaccess.io) utility can then be used to view the access log, while it is being filled:\n\n    goaccess --no-global-config --log-format=COMBINED access.log\n\nIf you have goaccess setup correctly, running goaccess without any flags should work too:\n\n    goaccess access.log\n\n`.alg` files\n------------\n\n`.alg` files are just renamed `.zip` files, that can be served by Algernon. There is an example application here: [wercstyle](https://github.com/xyproto/wercstyle).\n\nLogo license\n------------\n\nThanks to [Egon Elbre](https://twitter.com/egonelbre) for the two SVG drawings that I remixed into the current logo ([CC0](https://creativecommons.org/publicdomain/zero/1.0/) licensed).\n\nListening to port 80 without running as root\n--------------------------------------------\n\nFor Linux:\n\n    sudo setcap cap_net_bind_service=+ep /usr/bin/algernon\n\nOther resources\n---------------\n\n* [Algernon on Docker Hub](https://hub.docker.com/r/xyproto/algernon/)\n\nGeneral information\n-------------------\n\n* Version: 1.17.3\n* License: BSD-3\n* Alexander F. Rødseth \u0026lt;xyproto@archlinux.org\u0026gt;\n\nStargazers over time\n--------------------\n\n[![Stargazers over time](https://starchart.cc/xyproto/algernon.svg)](https://starchart.cc/xyproto/algernon)\n\n\u003ca href=\"https://algernon.roboticoverlords.org\"\u003e\u003cimg alt=\"0-0\" src=\"img/gophereyes.png\" align=\"right\"\u003e\u003c/a\u003e\n\nThe jump in stargazers happened when Algernon reached the front page of Hacker News:\n\n* [Self-Contained Pure-Go Web Server with Lua, MD, HTTP/2, QUIC, Redis Support](https://news.ycombinator.com/item?id=19578351)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxyproto%2Falgernon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxyproto%2Falgernon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxyproto%2Falgernon/lists"}