From 497f33cb3b50e48b234e0c82c86be66de5eaacef Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 18 Feb 2024 15:51:01 -0500 Subject: [PATCH] feat: Add documentation (#44) * add API docs as comments * add luadox configuation * add parameter table to API calls * create documentation to be published * add github pages artifact code * remove unused README * make publish-docs workflow reusable for releases * add troubleshooting documentation --------- Signed-off-by: Gerard Hickey --- .github/workflows/publish-docs.yaml | 64 +++++++++ .gitignore | 1 + README.md | 46 +------ docs/History.md | 38 ++++++ docs/Install.md | 22 +++ docs/Troubleshooting.md | 57 ++++++++ luadox.conf | 29 ++++ meshchat | 202 +++++++++++++++++++++++++++- meshchatconfig.lua | 18 +++ meshchatlib.lua | 53 ++++++++ 10 files changed, 480 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/publish-docs.yaml create mode 100644 .gitignore create mode 100644 docs/History.md create mode 100644 docs/Install.md create mode 100644 docs/Troubleshooting.md create mode 100644 luadox.conf diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml new file mode 100644 index 0000000..1c1eb52 --- /dev/null +++ b/.github/workflows/publish-docs.yaml @@ -0,0 +1,64 @@ +name: Publish MeshChat Documentation +on: + workflow_call: + inputs: + build_version: + required: true + type: string + +jobs: + build: + runs-on: ubuntu-latest + container: + image: jtackaberry/luadox:latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: release + - run: luadox -c luadox.conf + - name: Fix permissions + run: | + chmod -c -R +rX "_site/" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + - name: Update version strings + run: | + find docs -type f --exec sed -i "s/%VERSION%/${{ inputs.build_version }}/" {} \; + run: | + echo ::group::Archive artifact + tar -C "_site" \ + -cvf "$RUNNER_TEMP/artifact.tar" \ + --exclude=.git \ + --exclude=.github \ + . + echo ::endgroup:: + - name: Upload artifact + id: upload-artifact + uses: actions/upload-artifact@v4 + with: + name: github-pages + path: ${{ runner.temp }}/artifact.tar + retention-days: 1 + if-no-files-found: error + + # Deploy job + deploy: + needs: build + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + # Specify runner + deployment step + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54c4b32 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +docs/.markupserve_index diff --git a/README.md b/README.md index 392db44..64ac965 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -MeshChat -======== +# MeshChat MeshChat for AREDN (in Lua). MeshChat has become the defacto standard chat application for AREDN networks. A number of features make it easy @@ -10,49 +9,6 @@ to implement and use: * No account creation necessary--users access using call sign * Simple user interface -History of MeshChat -------------------- - -This is the history of the various MeshChat versions that have existed--at -least to the best of my knowledge. - -### MeshChat v0.4 - v1.02 - -This was the original version of MeshChat written by Trevor Paskett (K7FPV) -around 2015. It was written in Perl and worked well on the limited resources -of the AREDN nodes. Around 2018 Trevor was not able to or not interested -in supporting MeshChat any longer, it is unclear which but the project -became stagnant at version v1.01 in August of 2018. There was a final -release of v1.02 in September 2022 that mostly added a few patches and -support for Debian Stretch. - -The K7FPV code base still exists at https://github.com/tpaskett/meshchat. - -In addition Trevor wrote a good amount of documentation for his versions -which is still pretty well covers the current versions of MeshChat. -The documentation can be found over at his blog, https://github.com/tpaskett/meshchat. - -### MeshChat v2.0 - v2.8 - -When AREDN firmware v3.22.6.0 was released in June 2022, the AREDN development -team stopped including Perl in the distribution in favor of LUA. In preparation -of this change Tim Wilkinson (KN6PLV) started rewriting MeshChat in LUA -March 2022 with the first release of the new code base in April 2022. The -new MeshChat code continued to receive bug fixes for a year. At which -time Tim's involvement on the AREDN development team prevented him from -continuing to maintain MeshChat. - -### Future of MeshChat - -That brings the story upto the current time, September 2023, where I, -Gerard Hickey (WT0F), have started to be the maintainer of the MeshChat -code base. There has already been work to restructure the repository to -make working with the code more effective and to automatically build -packages when a release occurs. - -There are a number of bug fixes and incremental improvements that will be -released in v2.9. - If you are looking for a feature to be implemented or find a bug, please be sure to [create an issue](https://github.com/hickey/meshchat/issues/new) in the project so that it can be prioritized. diff --git a/docs/History.md b/docs/History.md new file mode 100644 index 0000000..9b22c3c --- /dev/null +++ b/docs/History.md @@ -0,0 +1,38 @@ +# History of MeshChat + +This is the history of the various MeshChat versions that have existed--at +least to the best of my knowledge. + +## MeshChat v0.4 - v1.02 + +This was the original version of MeshChat written by Trevor Paskett (K7FPV) +around 2015. It was written in Perl and worked well on the limited resources +of the AREDN nodes. Around 2018 Trevor was not able to or not interested +in supporting MeshChat any longer, it is unclear which but the project +became stagnant at version v1.01 in August of 2018. There was a final +release of v1.02 in September 2022 that mostly added a few patches and +support for Debian Stretch. + +The K7FPV code base still exists at https://github.com/tpaskett/meshchat. + +In addition Trevor wrote a good amount of documentation for his versions +which is still pretty well covers the current versions of MeshChat. +The documentation can be found over at his blog, https://github.com/tpaskett/meshchat. + +## MeshChat v2.0 - v2.10 + +When AREDN firmware v3.22.6.0 was released in June 2022, the AREDN development +team stopped including Perl in the distribution in favor of LUA. In preparation +of this change Tim Wilkinson (KN6PLV) started rewriting MeshChat in LUA +March 2022 with the first release of the new code base in April 2022. The +new MeshChat code continued to receive bug fixes for a year. At which +time Tim's involvement on the AREDN development team prevented him from +continuing to maintain MeshChat. + +## Future of MeshChat + +That brings the story upto the current time, September 2023, where I, +Gerard Hickey (WT0F), have started to be the maintainer of the MeshChat +code base. There has already been work to restructure the repository to +make working with the code more effective and to automatically build +packages when a release occurs. diff --git a/docs/Install.md b/docs/Install.md new file mode 100644 index 0000000..8f3bf3e --- /dev/null +++ b/docs/Install.md @@ -0,0 +1,22 @@ +# Installing MeshChat + +MeshChat is distributed as an Itsy package (IPK file) to be installed on an +AREDN node. This is the simplest way to install MeshChat. + +Simply download the MeshChat package to your compute and then access the +Administration panel in the AREDN's node setup. Under Package Management +you will have the option to upload a package. Once uploaded the MeshChat +system will be started within a couple of seconds. + +Usually there is not really any configuration that needs to be done, but +review of the [configuration settings](../module/meshchatconfig.html) is +suggested. To make any configuration changes one needs to log into the +node using SSH and edit the file `/www/cgi-bin/meshchatconfig.lua`. + +## Installing MeshChat on Linux + +The current distribution of MeshChat does not currently support Linux. In +order to run MeshChat on a Linux machine, one needs to download MeshChat +v1.0.2 and install it on the Linux machine. Once installed, the configuration +need to be updated to set the `api_host` setting to the hostname or IP +of an AREDN node that has the MeshChat API package installed. diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md new file mode 100644 index 0000000..2c6bdd1 --- /dev/null +++ b/docs/Troubleshooting.md @@ -0,0 +1,57 @@ +# Troubleshooting + +This is a "living" document. It is attempted to keep it up to date with +any new problems and troubleshooting techniques. If you find something +missing, please create an [Issue](https://github.com/hickey/meshchat/issues/new/choose) +do describe what problem or issue is missing. Better yet is to fork the +MeshChat repository, update the documentation in the forked repository +and then generate a PR back to the official MeshChat repository. Your +efforts will be greatly appreciated. + +It is important to realize that MeshChat is effectively two separate +programs: one that runs in your browser (the frontend code) and one that +runs on the AREDN node (the backend code or API). While it may not be +obvious which piece of code is having the problem, it generally can be +broken down as if there is an issue with the format of a message or it +being displayed in the browser then the frontend code should be investigated. +Otherwise the API should be investigated. + +## Installation Issues + +There is a known issue that if an older AREDN firmware is being upgraded, +any additional packages will need to be reinstalled after the node has +completed the firmware upgrade. This should not be the case for AREDN +firmware 3.23.8.0 or greater. + +If it appears that the installation of the package did not completely +install or is not fully functional, check the node to determine how much +disk space is available. Generally one should plan on a minimum of 100 KB +of disk space for MeshChat to operate. + +Package installation failures also generally have an error message displayed +above the upload button when there is a failure. This can help indicate +what the failure type was, so it should be reported back as a project +issue using the link above. + +## Message Synchronization Issues + +In order for messages to be synchronized between MeshChat instances, the +`meshchatsync` process needs to be running. Log into the node and execute +`ps | grep meshchatsync` to see if the process exists. If it is not +running, then one can start it with executing `/usr/local/bin/meshchatsync`. +Doing so will keep the process attached to the current terminal and any +error output will be displayed in the terminal. Once the terminal is +exited, the `meshchatsync` process will terminate. So after determining +that there are no errors being generated, it is best to reboot the node. +This will allow `meshchatsync` to startup normally with no manual +intervention. + +If it appears that `meshchatsync` is operating correctly, then the next +item to check is that the message database exists and messages are being +written to it. On an AREDN node, the message database is normally located +in `/tmp/meshchat`. Check for a `messages.`. If the message +database does exist, post a new message in the MeshChat instance on the +node and insure that the message gets written to the message database. + +Also insure that the message database has write permissions on the file. + diff --git a/luadox.conf b/luadox.conf new file mode 100644 index 0000000..c5e0862 --- /dev/null +++ b/luadox.conf @@ -0,0 +1,29 @@ +[project] +# Project name that is displayed on the top bar of each page +name = MeshChat +# HTML title that is appended to every page. If not defined, name is used. +title = MeshChat (master) +# A list of files or directories for LuaDox to parse. Globs are supported. +# This can be spread across multiple lines if you want, as long as the +# other lines are indented. +files = meshchat* +#files = /data/src/data/www/cgi-bin/meshchat.lua /data/src/data/www/cgi-bin/meshchatlib.lua +# The directory containing the rendered output files, which will be created +# if necessary. +outdir = _site +# Path to a custom css file that will be included on every page. This will +# be copied into the outdir. +# css = custom.css +# Path to a custom favicon. This will be copied into the outdir. +# favicon = img/favicon.png +# If require()d files discovered in source should also be parsed. +follow = false +# Character encoding for input files, which defaults to the current system +# locale. Output files are always utf8. +encoding = utf8 + +[manual] +index = README.md +history = docs/History.md +install = docs/Install.md +troubleshoot = docs/Troubleshooting.md diff --git a/meshchat b/meshchat index 0a2c79b..4513694 100755 --- a/meshchat +++ b/meshchat @@ -43,15 +43,18 @@ require("nixio") require("meshchatconfig") require("meshchatlib") +--- +-- @module meshchat + local query = {} local uploadfilename if os.getenv("QUERY_STRING") ~= "" or os.getenv("REQUEST_METHOD") == "POST" then local request = luci.http.Request(nixio.getenv(), - function() - local v = io.read(1024) - if not v then - io.close() - end + function() + local v = io.read(1024) + if not v then + io.close() + end return v end ) @@ -76,18 +79,72 @@ if os.getenv("QUERY_STRING") ~= "" or os.getenv("REQUEST_METHOD") == "POST" then query = request:formvalue() end +--- Return an error page to a browser. +-- @tparam string msg Error message to be displayed +-- function error(msg) print("Content-type: text/plain\r") print("\r") print(msg) end +--- @section API + +--- Returns a JSON document with basic node configuration. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `config` | +-- +-- ## API Response +-- +-- @example +-- { +-- "version": "meshchat_version", +-- "node": "node_name", +-- "zone": "meshchat_zone_name" +-- } +-- function config() print("Content-type: application/json\r") print("\r") print(string.format([[{"version":"%s","node":"%s","zone":"%s"}]], app_version, node_name(), zone_name())) end +--- Send a message to the MeshChat instance. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `send_message` | +-- | message | yes | Message body | +-- | call_sign | yes | Call sign of the sender | +-- | channel | no | Channel name to post message | +-- | epoch | no | Timestamp specified as unixtime | +-- +-- @note message +-- Needs to have newslines and double quotes escaped. +-- +-- @note channel +-- If not specified or set to empty string will post message to +-- `Everything` channel +-- +-- @note epoch +-- If not specified, the current time on the MeshChat server will +-- be used. +-- +-- ## API Response +-- +-- On a successful entry of the message into the database a success JSON +-- document will be returned. +-- +-- @example +-- { +-- "status": 200, +-- "response": "OK" +-- } +-- function send_message() print("Content-type: application/json\r") print("\r") @@ -104,6 +161,7 @@ function send_message() local f = io.open(messages_db_file, "a") if not f then release_lock() + -- TODO return a proper error code on failure die("Cannot send message") end f:write(hash() .. "\t" .. epoch .. "\t" .. message .. "\t" .. query.call_sign .. "\t" .. node_name() .. "\t" .. platform .. "\t" .. query.channel .. "\n") @@ -117,6 +175,29 @@ function send_message() print([[{"status":200, "response":"OK"}]]) end +--- Return a list of message stored on the MeshChat instance. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `messages` | +-- | call_sign | no | Call sign of the requester | +-- | epoch | no | Timestamp specified as unixtime | +-- | id | no | Generated MeshChat ID | +-- +-- ## API Response +-- +-- @example +-- { +-- "id": "id_str", +-- "epoch": epoch_time, +-- "message": "message_text", +-- "call_sign": "sending_call_sign", +-- "node": "originating_node", +-- "platform": "originating_node_platform", +-- "channel": "channel" +-- } +-- function messages() print("Content-type: application/json\r") @@ -195,6 +276,21 @@ function messages() end +--- Return a JSON document describing the sync status. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `sync_status` | +-- +-- ## API Response +-- +-- @example +-- { +-- "node": "node_name", +-- "epoch": sync_time +-- } +-- function sync_status() print("Content-type: application/json\r") print("\r") @@ -220,6 +316,7 @@ function sync_status() print(luci.jsonc.stringify(status)) end +--- Return a list of messages as text. function messages_raw() get_lock() @@ -242,6 +339,7 @@ function messages_raw() end end +--- Return the current MD5 has of the messages database. function messages_md5() get_lock() @@ -254,6 +352,7 @@ function messages_md5() print(md5) end +--- Package the raw messages as the messages.txt file. function messages_download() get_lock() @@ -277,6 +376,7 @@ function messages_download() end end +--- Return the list of users as raw text. function users_raw() get_lock() @@ -299,6 +399,24 @@ function users_raw() end end +--- Return a JSON document describing the logged in users. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `users` | +-- +-- ## API Response +-- +-- @example +-- { +-- "id": "id_str", +-- "epoch": epoch_time, +-- "call_sign": "sender_call_sign', +-- "node": "originating_node", +-- "platform": "originating_platform", +-- } +-- function users() print("Content-type: application/json\r") print("\r") @@ -340,6 +458,7 @@ function users() print(luci.jsonc.stringify(users)) end +--- Return a list of files as plain text. function local_files_raw() get_lock() @@ -372,6 +491,19 @@ function local_files_raw() nixio.fs.remove(tmp_file) end +--- Return a specified file as a download. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `file_download` | +-- | file | yes | Name of file to downlaod | +-- +-- ## API Response +-- +-- Returns a page as an octet-stream that is tagged as an attachment +-- to cause the browser to receive the file as a download. +-- function file_download() local file = query.file local file_path = local_files_dir .. "/" .. file @@ -399,6 +531,24 @@ function file_download() end end +--- Return a JSON document describing the list of files. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `files` | +-- +-- ## API Response +-- +-- @example +-- { +-- "file": "filename", +-- "epoch": modification_time, +-- "size": file_size_in_bytes, +-- "node": "originating_node", +-- "platform": "originating_platform" +-- } +-- function files() print("Content-type: application/json\r") print("\r") @@ -451,6 +601,7 @@ function files() })) end +--- Delete the specified file. function delete_file() nixio.fs.remove(local_files_dir .. "/" .. query.file) print("Content-type: application/json\r") @@ -458,12 +609,14 @@ function delete_file() print([[{"status":200, "response":"OK"}]]) end +--- Return the current version string for the messages database. function messages_version() print("Content-type: text/plain\r") print("\r") print(get_messages_db_version()) end +--- Return a JSON document of the messages database. function messages_version_ui() print("Content-type: application/json\r") print("\r") @@ -508,6 +661,22 @@ function messages_version_ui() release_lock() end +--- Return a JSON document describing all the hosts. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `hosts` | +-- +-- ## API Response +-- +-- @example +-- { +-- "ip": "ip_address", +-- "hostname": "hostname", +-- "node": "node_name" +-- } +-- function hosts() print("Content-type: application/json\r") print("\r") @@ -559,6 +728,7 @@ function hosts() print(luci.jsonc.stringify(hosts)) end +--- Return a list of hosts as plain text. function hosts_raw() print("Content-type: application/json\r") print("\r") @@ -589,6 +759,7 @@ function hosts_raw() end end +--- Store a file into the file directory. function upload_file() local new_file_size = nixio.fs.stat(tmp_upload_dir .. "/file").size @@ -615,6 +786,22 @@ function upload_file() end end +--- Return a list of nodes running MeshChat as text. +-- +-- ## API Parameters +-- | Parameter | Required | Description | +-- |-----------|----------|------------------------------------------| +-- | action | yes | Must be set to `meshchat_nodes` | +-- | zone_name | yes | MeshChat zone name | +-- +-- ## API Response +-- +-- The list of nodes and ports seperated by a tab. +-- +-- @example +-- node1 8080 +-- node2 8080 +-- function meshchat_nodes() print("Content-type: text/plain\r") print("\r") @@ -629,6 +816,11 @@ function meshchat_nodes() end end +--- Return a JSON document of the action log. +-- +-- Currently this call returns an empty list. In the future it will +-- return a list of action log events. +-- function action_log() print("Content-type: application/json\r") print("\r") diff --git a/meshchatconfig.lua b/meshchatconfig.lua index 6ca23b3..566d38e 100755 --- a/meshchatconfig.lua +++ b/meshchatconfig.lua @@ -34,8 +34,18 @@ --]] +--- +-- @module meshchatconfig +-- @section MeshChat Configuration + +--- Base directory to store all MeshChat generated files +-- @type string meshchat_path = "/tmp/meshchat" +--- Maximum number of messages in the database +-- @type int max_messages_db_size = 500 +--- Maximum amount of filesystem space for storing files +-- @type int max_file_storage = 512 * 1024 lock_file = meshchat_path .. "/lock" messages_db_file = meshchat_path .. "/messages" @@ -47,14 +57,22 @@ remote_files_file = meshchat_path .. "/files_remote" messages_version_file = meshchat_path .. "/messages_version" local_files_dir = meshchat_path .. "/files" tmp_upload_dir = "/tmp/web/upload" +--- How often to check for new messages +-- @type int poll_interval = 10 non_meshchat_poll_interval = 600 valid_future_message_time = 30 * 24 * 60 * 60 connect_timeout = 5 speed_time = 10 speed_limit = 1000 +--- Type of node that MeshChat is installed on ("node" or "pi") +-- @type string platform = "node" +--- Turn debug message on +-- @type bool debug = 0 extra_nodes = {} +--- MeshChat protocol version +-- @type string protocol_version = "1.02" app_version = "2.9" diff --git a/meshchatlib.lua b/meshchatlib.lua index dc9c055..f409ea2 100755 --- a/meshchatlib.lua +++ b/meshchatlib.lua @@ -37,10 +37,21 @@ require("nixio") require("uci") +--- @module meshchatlib + +--- Exit the program with an error message. +-- +-- @tparam string msg Message to display +-- function die(msg) os.exit(-1) end +--- Execute a command and capture the output. +-- +-- @tparam string cmd Command line to execute +-- @treturn string stdout of the command +-- function capture(cmd) local f = io.popen(cmd) if not f then @@ -51,10 +62,23 @@ function capture(cmd) return output end +--- +-- Retrieve the current node name. +-- +-- This function will interogate the UCI settings to retrieve the current +-- node name stored in the `hsmmmesh` settings. +-- +-- @treturn string Name of current node +-- function node_name() return uci.cursor("/etc/local/uci"):get("hsmmmesh", "settings", "node") or "" end +--- +-- Retrieve the current MeshChat zone name that the node is operating under. +-- +-- @treturn string Name of MeshChat zone +-- function zone_name() local dmz_mode = uci.cursor("/etc/config.mesh"):get("aredn", "@dmz[0]", "mode") local servfile = "/etc/config.mesh/_setup.services.nat" @@ -88,6 +112,18 @@ function release_lock() lock_fd:lock("ulock") end +--- Generate the MD5 sum of a file. +-- +-- This under the covers relies on `md5sum` and executes `md5sum` against +-- the specified file. +-- +-- @note +-- There is no checking to determine if `md5sum` is installed or +-- executable. In the future, this may change. +-- +-- @tparam string file Path to file +-- @treturn string Result of `md5sum` of the file +-- function file_md5(file) if not nixio.fs.stat(file) then return "" @@ -123,6 +159,17 @@ function get_messages_version_file() return sum end +--- Generate a unique hash. +-- +-- Combine the current time (epoch time) and a randomly generated number +-- between 0 - 99999 and run through `md5sum` to generate a random hash. +-- +-- @note +-- There is no checking to determine if `md5sum` is installed or +-- executable. In the future, this may change. +-- +-- @treturn string Generated hash value +-- function hash() return capture("echo " .. os.time() .. math.random(99999) .. " | md5sum"):sub(1, 8) end @@ -227,6 +274,12 @@ function node_list() return nodes end +--- +-- Escape percent signs. +-- +-- @tparam string str String to encode +-- @treturn string Encoded string +-- function str_escape(str) return str:gsub("%(", "%%("):gsub("%)", "%%)"):gsub("%%", "%%%%"):gsub("%.", "%%."):gsub("%+", "%%+"):gsub("-", "%%-"):gsub("%*", "%%*"):gsub("%[", "%%["):gsub("%?", "%%?"):gsub("%^", "%%^"):gsub("%$", "%%$") end