From 2deb253e728cc34185d7df29b03f28ebe1e456cc Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sat, 10 Jun 2023 15:19:23 -0400 Subject: [PATCH 01/16] Initial pass at reorganization Reorginanization of the repository so that it is code focused vs. being focused on how the packages are built. Packaging will be moved to GitHub Actions so that every commit will create new packages that can be used for testing. Releases will be created and produce packages when a tag is committed to the repository. Signed-off-by: Gerard Hickey --- .github/workflows/release-meshchat.yaml | 3 +- cgi-bin/meshchat | 892 ++++++++++++++++++++++++ cgi-bin/meshchatconfig.lua | 79 +++ cgi-bin/meshchatlib.lua | 285 ++++++++ meshchatsync | 293 ++++++++ package/apionly/control/control | 10 + package/src/control/control | 10 + package/src/control/postinst | 38 + package/src/control/preinst | 7 + package/src/control/prerm | 8 + support/init.d/meshchatsync | 14 + 11 files changed, 1638 insertions(+), 1 deletion(-) create mode 100755 cgi-bin/meshchat create mode 100755 cgi-bin/meshchatconfig.lua create mode 100755 cgi-bin/meshchatlib.lua create mode 100755 meshchatsync create mode 100755 package/apionly/control/control create mode 100644 package/src/control/control create mode 100755 package/src/control/postinst create mode 100755 package/src/control/preinst create mode 100755 package/src/control/prerm create mode 100755 support/init.d/meshchatsync diff --git a/.github/workflows/release-meshchat.yaml b/.github/workflows/release-meshchat.yaml index c6502d9..ee90504 100644 --- a/.github/workflows/release-meshchat.yaml +++ b/.github/workflows/release-meshchat.yaml @@ -78,6 +78,7 @@ jobs: with: fetch-depth: 0 fetch-tags: true - - uses: ./.github/workflows/publish-docs.yaml + - uses: + ./.github/workflows/publish-docs.yaml with: build_version: ${{ needs.create-release.outputs.build_version }} diff --git a/cgi-bin/meshchat b/cgi-bin/meshchat new file mode 100755 index 0000000..9a3c507 --- /dev/null +++ b/cgi-bin/meshchat @@ -0,0 +1,892 @@ +#!/usr/bin/lua +--[[ + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2022 Tim Wilkinson + Base on code (C) Trevor Paskett (see https://github.com/tpaskett) + See Contributors file for additional contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 3 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Additional Terms: + + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. + + Attributions to the AREDN Project must be retained in the source code. + If importing this code into a new or existing project attribution + to the AREDN project must be added to the source code. + + You must not misrepresent the origin of the material contained within. + + Modified versions must be modified to attribute to the original source + and be marked in reasonable ways as differentiate it from the original + version + +--]] + +package.path = package.path .. ";/www/cgi-bin/?.lua" + +require('luci.http') +local json = require("luci.jsonc") +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 + return v + end + ) + local fp + request:setfilehandler( + function(meta, chunk, eof) + if not fp then + if meta and meta.file then + uploadfilename = meta.file + end + nixio.fs.mkdir(tmp_upload_dir) + fp = io.open(tmp_upload_dir .. "/file", "w") + end + if chunk then + fp:write(chunk) + end + if eof then + fp:close() + end + end + ) + 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") + + local settings = { + version = app_version, + protocol_verison = protocol_version, + node = node_name(), + zone = zone_name(), + default_channel = default_channel, + debug = debug, + } + + print(json.stringify(settings)) +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") + + local message = query.message:gsub("\n", "\\n"):gsub('"', '\\"'):gsub("\t", " ") + local id = query.id or hash(); + local epoch = os.time() + if tonumber(query.epoch) > epoch then + epoch = query.epoch + end + + get_lock() + + 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(id .. "\t" .. epoch .. "\t" .. message .. "\t" .. query.call_sign .. "\t" .. node_name() .. "\t" .. platform .. "\t" .. query.channel .. "\n") + f:close() + + sort_and_trim_db() + save_messages_db_version() + + release_lock() + + 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") + local output = io.stdout + local encoding = os.getenv("HTTP_ACCEPT_ENCODING") + if encoding and encoding:match("gzip") then + print "Content-Encoding: gzip\r" + output = io.popen("gzip", "w") + end + print("\r") + io.flush() + + get_lock() + + local node = node_name() + + -- read in message DB and parse the contents + local messages = {} + for line in io.lines(messages_db_file) + do + local id, epoch, message, call_sign, node, platform, channel = line:match("^(%S+)\t(%S+)\t(.+)\t([^\t]+)\t(%S*)\t(%S+)\t(%S*)$") + if epoch and #epoch > 0 then + messages[#messages + 1] = { + id = id, + epoch = tonumber(epoch), + message = message:gsub("\\n", "\n"):gsub('\\"', '"'), + call_sign = call_sign, + node = node, + platform = platform, + channel = channel + } + end + end + + if tonumber(query.epoch) and query.call_sign then + local users = {} + + -- read the users status file + if nixio.fs.stat(local_users_status_file) then + for line in io.lines(local_users_status_file) + do + local call_sign = line:match("^([^\t]+)\t") + if call_sign then + users[call_sign] = line + end + end + end + + -- set the timestamp + local epoch = os.time() + if tonumber(query.epoch) > epoch then + epoch = query.epoch + end + + -- rewrite user status file updating the timestamp for requesting call sign + -- query.id is the meshchat_id + local f = io.open(local_users_status_file, "w") + if f then + local found_user = false + for call_sign, line in pairs(users) + do + if call_sign == query.call_sign then + f:write(call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") + found_user = true + else + f:write(line .. "\n") + end + end + if not found_user then + f:write(query.call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") + end + f:close() + end + end + + release_lock() + + -- order messages according to time + table.sort(messages, function(a, b) return a.epoch > b.epoch end) + + output:write(json.stringify(messages)) + output:flush() + +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") + + get_lock() + + local status = {} + if nixio.fs.stat(sync_status_file) then + for line in io.lines(sync_status_file) + do + local node, epoch = line:match("^(.*)\t(.*)$") + status[#status + 1] = { + node = node, + epoch = tonumber(epoch) + } + end + end + + release_lock() + + table.sort(status, function(a, b) return a.epoch > b.epoch end) + + print(json.stringify(status)) +end + +--- Return a list of messages as text. +function messages_raw() + get_lock() + + local md5 = file_md5(messages_db_file) + local lines = {} + for line in io.lines(messages_db_file) + do + lines[#lines + 1] = line + end + + release_lock() + + print("Content-MD5: " .. md5 .. "\r") + print("Content-type: text/plain\r") + print("\r") + + for _, line in ipairs(lines) + do + print(line) + end +end + +--- Return the current MD5 has of the messages database. +function messages_md5() + get_lock() + + local md5 = file_md5(messages_db_file) + + release_lock() + + print("Content-type: text/plain\r") + print("\r") + print(md5) +end + +--- Package the raw messages as the messages.txt file. +function messages_download() + get_lock() + + local md5 = file_md5(messages_db_file) + local lines = {} + for line in io.lines(messages_db_file) + do + lines[#lines + 1] = line + end + + release_lock() + + print("Content-MD5: " .. md5 .. "\r") + print("Content-Disposition: attachment; filename=messages.txt;\r") + print("Content-type: text/plain\r") + print("\r") + + for _, line in ipairs(lines) + do + print(line) + end +end + +--- Return the list of users as raw text. +function users_raw() + get_lock() + + local md5 = file_md5(local_users_status_file) + local lines = {} + for line in io.lines(local_users_status_file) + do + lines[#lines + 1] = line + end + + release_lock() + + print("Content-MD5: " .. md5 .. "\r") + print("Content-type: text/plain\r") + print("\r") + + for _, line in ipairs(lines) + do + print(line) + 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") + + get_lock() + + local users = {} + for line in io.lines(local_users_status_file) + do + local call_sign, id, node, epoch, platform = line:match("^(.*)\t(.*)\t(.*)\t(.*)\t(.*)$") + if epoch and #epoch > 0 then + users[#users + 1] = { + epoch = tonumber(epoch), + id = id, + call_sign = call_sign, + node = node, + platform = platform + } + end + end + for line in io.lines(remote_users_status_file) + do + local call_sign, id, node, epoch, platform = line:match("^(.*)\t(.*)\t(.*)\t(.*)\t(.*)$") + if epoch and #epoch > 0 then + users[#users + 1] = { + epoch = tonumber(epoch), + id = id, + call_sign = call_sign, + node = node, + platform = platform + } + end + end + + release_lock() + + table.sort(users, function(a, b) return a.epoch > b.epoch end) + + print(json.stringify(users)) +end + +--- Return a list of files as plain text. +function local_files_raw() + get_lock() + + local tmp_file = meshchat_path .. "/meshchat_files_local." .. nixio.getpid() + local f = io.open(tmp_file, "w") + if not f then + die("Cannot list local files") + end + local name = node_name() .. ":" .. os.getenv("SERVER_PORT") + for file in nixio.fs.dir(local_files_dir) + do + local stat = nixio.fs.stat(local_files_dir .. "/" .. file) + f:write(file .. "\t" .. name .. "\t" .. stat.size .. "\t" .. stat.mtime .. platform .. "\n") + end + f:close() + + local md5 = file_md5(tmp_file) + + release_lock() + + print("Content-MD5: " .. md5 .. "\r") + print("Content-type: text/plain\r") + print("\r") + + for line in io.lines(tmp_file) + do + print(line) + end + + 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 + + if file == "" or not nixio.fs.stat(file_path) then + error("no file") + return + end + + get_lock() + + local md5 = file_md5(file_path) + local f = io.open(file_path, "rb") + + release_lock() + + print("Content-MD5: " .. md5 .. "\r") + print("Content-Disposition: attachment; filename=\"" .. file .. "\";\r") + print("Content-type: application/octet-stream\r") + print("\r") + + if f then + io.write(f:read("*a")) + f:close() + 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") + + get_lock() + + local files = {} + local node = node_name() .. ":" .. os.getenv("SERVER_PORT") + for file in nixio.fs.dir(local_files_dir) + do + local stat = nixio.fs.stat(local_files_dir .. "/" .. file) + files[#files + 1] = { + file = file, + epoch = stat.mtime, + size = stat.size, + node = node, + platform = platform + } + files[#files]["local"] = 1 + end + for file in nixio.fs.dir(meshchat_path) + do + if file:match("^remote_files%.") then + for line in io.lines(meshchat_path .. "/" .. file) + do + local name, node, size, epoch, platform = line:match("^(.*)\t(.*)\t(.*)\t(.*)\t(.*)$") + if epoch and #epoch > 0 then + files[#files + 1] = { + file = name, + epoch = tonumber(epoch), + size = size, + node = node, + platform = platform + } + files[#files]["local"] = 0 + end + end + end + end + + local stats = file_storage_stats() + + release_lock() + + table.sort(files, function(a, b) return a.epoch > b.epoch end) + + print(json.stringify({ + stats = stats, + files = files + })) +end + +--- Delete the specified file. +function delete_file() + nixio.fs.remove(local_files_dir .. "/" .. query.file) + print("Content-type: application/json\r") + print("\r") + 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") + + print(string.format([[{"messages_version":%s}]], get_messages_db_version())) + + get_lock() + + local users = {} + for line in io.lines(local_users_status_file) + do + local call_sign = line:match("^([^\t]+)\t") + if call_sign then + users[call_sign] = line + end + end + + local node = node_name() + local epoch = os.time() + if tonumber(query.epoch) > epoch then + epoch = query.epoch + end + + -- TODO refactor here and messages function into a single code block + local f = io.open(local_users_status_file, "w") + if f then + local found_user = false + for call_sign, line in pairs(users) + do + if call_sign == query.call_sign then + f:write(call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") + found_user = true + else + f:write(line .. "\n") + end + end + if not found_user then + f:write(query.call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") + end + f:close() + end + + 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") + + local node = node_name() + local hosts = {} + for line in io.lines("/var/dhcp.leases") + do + local epoch, mac1, ip, hostname, mac2 = line:match("^(%S+)%s(%S+)%s(%S+)%s(%S+)%s(%S+)$") + hosts[#hosts + 1] = { + ip = ip, + hostname = hostname, + node = node + } + end + + for line in io.lines("/etc/config.mesh/_setup.dhcp.dmz") + do + local mac, num, hostname = line:match("^(%S+)%s(%S+)%s(%S+)$") + local ip = gethostbyname(hostname) + hosts[#hosts + 1] = { + ip = ip, + hostname = hostname, + node = node + } + end + + for _, remote_node in ipairs(node_list()) + do + local f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " http://" .. remote_node .. ":8080/cgi-bin/meshchat?action=hosts_raw 2> /dev/null") + if f then + for line in f:lines() + do + if line ~= "" and not line:match("error") then + local ip, hostname = line:match("^(.+)\t(.+)$") + hosts[#hosts + 1] = { + ip = ip, + hostname = hostname, + node = remote_node + } + end + end + f:close() + end + end + + table.sort(hosts, function(a, b) return a.hostname < b.hostname end) + + print(json.stringify(hosts)) +end + +--- Return a list of hosts as plain text. +function hosts_raw() + print("Content-type: application/json\r") + print("\r") + + local hosts = {} + for line in io.lines("/var/dhcp.leases") + do + local epoch, mac1, ip, hostname, mac2 = line:match("^(%S+)%s(%S+)%s(%S+)%s(%S+)%s(%S+)$") + hosts[#hosts + 1] = { + ip = ip, + hostname = hostname + } + end + + for line in io.lines("/etc/config.mesh/_setup.dhcp.dmz") + do + local mac, num, hostname = line:match("^(%S+)%s(%S+)%s(%S+)$") + local ip = gethostbyname(hostname) + hosts[#hosts + 1] = { + ip = ip, + hostname = hostname + } + end + + for _, host in ipairs(hosts) + do + print(host.ip .. "\t" .. host.hostname) + end +end + +--- Store a file into the file directory. +function upload_file() + local new_file_size = nixio.fs.stat(tmp_upload_dir .. "/file").size + + get_lock() + + local stats = file_storage_stats() + + release_lock() + + print("Content-type: application/json\r") + print("\r") + + if new_file_size > stats.files_free then + nixio.fs.remove(tmp_upload_dir .. "/file") + print([[{"status":500, "response":"Not enough storage, delete some files"}]]) + else + local fi = io.open(tmp_upload_dir .. "/file", "r") + local fo = io.open(local_files_dir .. "/" .. uploadfilename, "w") + fo:write(fi:read("*a")) + fi:close() + fo:close() + nixio.fs.remove(tmp_upload_dir .. "/file") + print([[{"status":200, "response":"OK"}]]) + 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") + + local pattern = "http://(%S+):(%d+)/meshchat|tcp|" .. str_escape(query.zone_name) .. "%s" + for line in io.lines("/var/run/services_olsr") + do + local node, port = line:match(pattern) + if node and port then + print(node .. "\t" .. port) + end + 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") + print("[]") +end + +-- Command dispatch -- + +if query.action == "messages" then + messages() +elseif query.action == "config" then + config() +elseif query.action == "send_message" then + send_message() +elseif query.action == "sync_status" then + sync_status() +elseif query.action == "messages_raw" then + messages_raw() +elseif query.action == "messages_md5" then + messages_md5() +elseif query.action == "messages_download" then + messages_download() +elseif query.action == "users_raw" then + users_raw() +elseif query.action == "users" then + users() +elseif query.action == "local_files_raw" then + local_files_raw() +elseif query.action == "file_download" then + file_download() +elseif query.action == "files" then + files() +elseif query.action == "delete_file" then + delete_file() +elseif query.action == "messages_version" then + messages_version() +elseif query.action == "messages_version_ui" then + messages_version_ui() +elseif query.action == "hosts" then + hosts() +elseif query.action == "hosts_raw" then + hosts_raw() +elseif query.action == "upload_file" then + upload_file() +elseif query.action == "meshchat_nodes" then + meshchat_nodes() +elseif query.action == "action_log" then + action_log() +else + error("error no action") +end diff --git a/cgi-bin/meshchatconfig.lua b/cgi-bin/meshchatconfig.lua new file mode 100755 index 0000000..b91e5ab --- /dev/null +++ b/cgi-bin/meshchatconfig.lua @@ -0,0 +1,79 @@ +--[[ + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2022 Tim Wilkinson + Base on code (C) Trevor Paskett (see https://github.com/tpaskett) + See Contributors file for additional contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 3 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Additional Terms: + + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. + + Attributions to the AREDN Project must be retained in the source code. + If importing this code into a new or existing project attribution + to the AREDN project must be added to the source code. + + You must not misrepresent the origin of the material contained within. + + Modified versions must be modified to attribute to the original source + and be marked in reasonable ways as differentiate it from the original + version + +--]] + +--- +-- @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" +messages_db_file_orig = meshchat_path .. "/messages" +sync_status_file = meshchat_path .. "/sync_status" +local_users_status_file = meshchat_path .. "/users_local" +remote_users_status_file = meshchat_path .. "/users_remote" +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 = "master" +default_channel = "chat" diff --git a/cgi-bin/meshchatlib.lua b/cgi-bin/meshchatlib.lua new file mode 100755 index 0000000..f409ea2 --- /dev/null +++ b/cgi-bin/meshchatlib.lua @@ -0,0 +1,285 @@ +--[[ + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2022 Tim Wilkinson + Base on code (C) Trevor Paskett (see https://github.com/tpaskett) + See Contributors file for additional contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 3 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Additional Terms: + + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. + + Attributions to the AREDN Project must be retained in the source code. + If importing this code into a new or existing project attribution + to the AREDN project must be added to the source code. + + You must not misrepresent the origin of the material contained within. + + Modified versions must be modified to attribute to the original source + and be marked in reasonable ways as differentiate it from the original + version + +--]] + +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 + return "" + end + local output = f:read("*a") + f:close() + 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" + -- LAN mode is not set to NAT + if dmz_mode ~= "0" then + servfile = "/etc/config.mesh/_setup.services.dmz" + end + if nixio.fs.access(servfile) then + for line in io.lines(servfile) + do + local zone = line:match("^(.*)|.*|.*|.*|.*|meshchat$") + if zone then + return zone + end + end + end + return "MeshChat" +end + +messages_db_file = messages_db_file_orig .. "." .. zone_name() + +local lock_fd +function get_lock() + if not lock_fd then + lock_fd = nixio.open(lock_file, "w", "666") + end + lock_fd:lock("lock") +end + +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 "" + end + local output = capture("md5sum " .. file:gsub(" ", "\\ ")):match("^(%S+)%s") + return output and output or "" +end + +function get_messages_db_version() + for line in io.lines(messages_version_file) + do + line = line:gsub("\n$", "") + return line + end +end + +function save_messages_db_version() + local f = io.open(messages_version_file, "w") + f:write(get_messages_version_file() .. "\n") + f:close() + nixio.fs.chmod(messages_version_file, "666") +end + +function get_messages_version_file() + local sum = 0 + for line in io.lines(messages_db_file) + do + local key = line:match("^([0-9a-f]+)") + if key then + sum = sum + tonumber(key, 16) + end + end + 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 + +function sort_and_trim_db() + local valid_time = os.time() + valid_future_message_time + local unused_count = max_messages_db_size + local messages = {} + for line in io.lines(messages_db_file) + do + local id, epoch = line:match("^(%x+)\t(%S+)\t") + -- ignore messages that are too far in the future (assume they're errors) + epoch = tonumber(epoch) + if epoch and epoch < valid_time then + messages[#messages + 1] = { + epoch = epoch, + id = tonumber(id, 16), + line = line + } + end + unused_count = unused_count - 1 + end + + table.sort(messages, function(a, b) + if a.epoch == b.epoch then + return a.id < b.id + else + return a.epoch < b.epoch + end + end) + + local f = io.open(messages_db_file, "w") + for _, line in ipairs(messages) + do + unused_count = unused_count + 1 + if unused_count > 0 then + f:write(line.line .. "\n") + end + end + f:close() +end + +function file_storage_stats() + local lines = capture("df -k " .. local_files_dir) + local blocks, used, available, perc = lines:match("(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%%") + used = tonumber(used) * 1024 + available = tonumber(available) * 1024 + local total = used + available + + local local_files_bytes = 0 + for file in nixio.fs.dir(local_files_dir) + do + local_files_bytes = local_files_bytes + nixio.fs.stat(local_files_dir .. "/" .. file).size + end + + if max_file_storage - local_files_bytes < 0 then + local_files_bytes = max_file_storage + end + + return { + total = total, + used = used, + files = local_files_bytes, + files_free = max_file_storage - local_files_bytes, + allowed = max_file_storage + } +end + +function gethostbyname(hostname) + return capture("nslookup " .. hostname):match("Address 1:%s*([%d%.]+)") +end + +function node_list() + if not nixio.fs.stat("/var/run/services_olsr") then + return {} + end + local local_node = node_name():lower() + local zone = zone_name() + + local nodes = {} + local pattern = "http://(%S+):(%d+)/meshchat|tcp|" .. str_escape(zone) .. "%s" + for line in io.lines("/var/run/services_olsr") + do + local node, port = line:match(pattern) + if node and port then + node = node:lower() + if node ~= local_node then + nodes[#nodes + 1] = { + platform = (port == "8080" and "node" or "pi"), + node = node, + port = port + } + end + end + end + + for _, extra in ipairs(extra_nodes) + do + nodes[#node + 1] = extra + end + + 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 diff --git a/meshchatsync b/meshchatsync new file mode 100755 index 0000000..b10631b --- /dev/null +++ b/meshchatsync @@ -0,0 +1,293 @@ +#!/usr/bin/lua +--[[ + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2022 Tim Wilkinson + Based on code (C) Trevor Paskett (see https://github.com/tpaskett) + See Contributors file for additional contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 3 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Additional Terms: + + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. + + Attributions to the AREDN Project must be retained in the source code. + If importing this code into a new or existing project attribution + to the AREDN project must be added to the source code. + + You must not misrepresent the origin of the material contained within. + + Modified versions must be modified to attribute to the original source + and be marked in reasonable ways as differentiate it from the original + version + +--]] + +package.path = package.path .. ";/www/cgi-bin/?.lua" +require("nixio") +require("meshchatconfig") +require("meshchatlib") + +local sync_status = {} +local non_mesh_chat_nodes = {} + +local node = node_name() + +if not nixio.fs.stat(meshchat_path) then + nixio.fs.mkdir(meshchat_path) + nixio.fs.mkdir(local_files_dir) +end + +if not nixio.fs.stat(messages_db_file) then + io.open(messages_db_file, "w"):close() + nixio.fs.chmod(messages_db_file, "666") +end + +io.open(local_users_status_file, "a"):close() +io.open(remote_users_status_file, "a"):close() + +save_messages_db_version() + +nixio.fs.chmod(meshchat_path, "666") + +io.open(lock_file, "a"):close() + +function log_status() + local cur_status = {} + + if not nixio.fs.stat(sync_status_file) then + io.open(sync_status_file, "w"):close() + end + + get_lock() + + for line in io.lines(sync_status_file) + do + local key, value = line:match("^(.*)\t(.*)$") + cur_status[key] = value + end + + local f = io.open(sync_status_file, "w") + if f then + for key, value in pairs(sync_status) + do + f:write(key .. "\t" .. value .. "\n") + end + for key, value in pairs(cur_status) + do + if not sync_status[key] then + f:write(key .. "\t" .. value .. "\n") + end + end + f:close() + end + + release_lock() +end + +function merge_messages() + local rmsg = {} + local lmsg = {} + + for line in io.lines(meshchat_path .. "/remote_messages") + do + local key = line:match("^(%S+)%s") + rmsg[key] = line + end + + get_lock() + + for line in io.lines(messages_db_file) + do + local key = line:match("^(%S+)%s") + lmsg[key] = line + end + + local f = io.open(messages_db_file, "a") + if f then + for rmsg_id, line in pairs(rmsg) + do + if not lmsg[rmsg_id] then + f:write(line .. "\n") + end + end + f:close() + end + + sort_and_trim_db() + + save_messages_db_version() + + release_lock() +end + +function merge_users() + local rusers = {} + local lusers = {} + + for line in io.lines(meshchat_path .. "/remote_users") + do + local key, value = line:match("^(%S+\t%S+\t%S+)\t(.*)$") + if not line:match("error") and key then + rusers[key] = value + end + end + + get_lock() + + for line in io.lines(remote_users_status_file) + do + local key, value = line:match("^(%S+\t%S+\t%S+)\t(.*)$") + if not line:match("error") and key then + lusers[key] = value + end + end + + local f = io.open(remote_users_status_file, "w") + if f then + for key, _ in pairs(rusers) + do + if lusers[key] and lusers[key] > rusers[key] then + f:write(key .. "\t" .. lusers[key] .. "\n") + else + f:write(key .. "\t" .. rusers[key] .. "\n") + end + end + for key, _ in pairs(lusers) + do + if not rusers[key] then + f:write(key .. "\t" .. lusers[key] .. "\n") + end + end + f:close() + end + + release_lock() +end + +while true +do + local nodes = node_list() + + sync_status = {} + + for _, node_info in ipairs(nodes) + do + for _ = 1,1 + do + local remote_node = node_info.node + local remote_platform = node_info.platform + local remote_port = node_info.port + + local port = "" + if remote_port ~= "" then + port = ":" .. remote_port + end + + if port == "" and remote_platform == "node" then + port = ":8080" + end + + local version = get_messages_db_version() + + -- Poll non mesh chat nodes at a longer interval + if non_mesh_chat_nodes[remote_node] and os.time() < non_mesh_chat_nodes[remote_node] then + break + end + + nixio.fs.remove(meshchat_path .. "/remote_users") + + -- Get remote users file + local f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -sD - \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=users_raw&platform=" .. platform .. "&node=" .. node .. "\" -o " .. meshchat_path .. "/remote_users 2>&1") + local output = f:read("*a") + f:close() + + -- Check if meshchat is installed + if output:match("404 Not Found") then + non_mesh_chat_nodes[remote_node] = os.time() + non_meshchat_poll_interval + break + end + + local md5 = output:match("Content%-MD5:%s([0-9a-f]+)\r\n") + if md5 then + local f_md5 = file_md5(meshchat_path .. "/remote_users") + if md5 == f_md5 then + local cur_size = nixio.fs.stat(meshchat_path .. "/remote_users").size + if cur_size > 0 then + merge_users() + end + end + end + + -- Get remote files file + nixio.fs.remove(meshchat_path .. "/remote_files") + f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -sD - \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=local_files_raw\" -o " .. meshchat_path .. "/remote_files 2>&1") + output = f:read("*a") + f:close() + + md5 = output:match("Content%-MD5:%s([0-9a-f]+)\r\n") + if md5 then + local f_md5 = file_md5(meshchat_path .. "/remote_files") + nixio.fs.remove(meshchat_path .. "/remote_files." .. remote_node) + if md5 == f_md5 then + local cur_size = nixio.fs.stat(meshchat_path .. "/remote_files").size + if cur_size > 0 then + nixio.fs.rename(meshchat_path .. "/remote_files", meshchat_path .. "/remote_files." .. remote_node) + end + end + end + + -- Get remote messages + nixio.fs.remove(meshchat_path .. "/remote_messages") + + f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=messages_version\" -o - 2> /dev/null") + local remote_version = f:read("*a") + f:close() + + -- Check the version of the remote db against ours. Only download the db if the remote has a different copy + + if remote_version ~= "" and version == remote_version then + sync_status[remote_node] = os.time() + break + end + + f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -sD - \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=messages_raw\" -o " .. meshchat_path .. "/remote_messages 2>&1") + local output = f:read("*a") + f:close() + + if nixio.fs.stat(meshchat_path .. "/remote_messages") then + local md5 = output:match("Content%-MD5:%s([0-9a-f]+)\r\n") + if md5 then + local f_md5 = file_md5(meshchat_path .. "/remote_messages") + if md5 == f_md5 then + local cur_size = nixio.fs.stat(meshchat_path .. "/remote_messages").size + if cur_size > 0 then + sync_status[remote_node] = os.time() + merge_messages() + end + end + end + end + end + end + + log_status() + + nixio.fs.remove(meshchat_path .. "/remote_messages") + nixio.fs.remove(meshchat_path .. "/remote_users") + nixio.fs.remove(meshchat_path .. "/remote_files") + + nixio.nanosleep(poll_interval, 0) +end diff --git a/package/apionly/control/control b/package/apionly/control/control new file mode 100755 index 0000000..0761bf2 --- /dev/null +++ b/package/apionly/control/control @@ -0,0 +1,10 @@ +Package: meshchat-api +Version: +Depends: lua +Provides: +Source: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY +Section: net +Priority: optional +Maintainer: Tim Wilkinson (KN6PLV) and Trevor Paskett (K7FPV) +Architecture: all +Description: P2P distributed chat for mesh networks diff --git a/package/src/control/control b/package/src/control/control new file mode 100644 index 0000000..d78266e --- /dev/null +++ b/package/src/control/control @@ -0,0 +1,10 @@ +Package: meshchat +Version: +Depends: curl lua +Provides: +Source: package/meshchat +Section: net +Priority: optional +Maintainer: Tim Wilkinson (KN6PLV) and Trevor Paskett (K7FPV) +Architecture: all +Description: P2P distributed chat for mesh networks diff --git a/package/src/control/postinst b/package/src/control/postinst new file mode 100755 index 0000000..67004c2 --- /dev/null +++ b/package/src/control/postinst @@ -0,0 +1,38 @@ +#!/bin/sh + +grep "|8080|meshchat" /etc/config.mesh/_setup.services.dmz &> /dev/null +DMZPRESENT=$? +grep "|8080|meshchat" /etc/config.mesh/_setup.services.nat &> /dev/null +NATPRESENT=$? +NODEMODE=$(uci -q -c /etc/local/uci/ get hsmmmesh.settings.config) +RAND=$(awk 'BEGIN{srand();print int(rand()*10000) }') +RESTART=0 + +if [ "$DMZPRESENT" != 0 ]; then + echo "MeshChat-$RAND|1|http|$(uname -n)|8080|meshchat" >> /etc/config.mesh/_setup.services.dmz + RESTART=1 +fi + +if [ "$NATPRESENT" != 0 ]; then + echo "MeshChat-$RAND|1|http|$(uname -n)|8080|meshchat" >> /etc/config.mesh/_setup.services.nat + RESTART=1 +fi + +if [ "$NODEMODE" = "mesh" -a "$RESTART" = "1" ]; then + echo "Applying service announcement" + /usr/local/bin/node-setup -a -p mesh &> /dev/null + /etc/init.d/olsrd restart &> /dev/null +fi + +/etc/init.d/meshchatsync enable +/etc/init.d/meshchatsync start + +echo "
" + +echo "Mesh Chat has been setup at http://$(uname -n):8080/meshchat" +echo "
" +if [ "$RESTART" = "1" ]; then + echo "An advertised service has been added for Mesh Chat on the Services configuration page" +fi + +exit 0 diff --git a/package/src/control/preinst b/package/src/control/preinst new file mode 100755 index 0000000..643506d --- /dev/null +++ b/package/src/control/preinst @@ -0,0 +1,7 @@ +#!/bin/sh + +/etc/init.d/meshchatsync stop > /dev/null 2> /dev/null + +mkdir -p /www/meshchat + +exit 0 diff --git a/package/src/control/prerm b/package/src/control/prerm new file mode 100755 index 0000000..eb6c0f7 --- /dev/null +++ b/package/src/control/prerm @@ -0,0 +1,8 @@ +#!/bin/sh + +/etc/init.d/meshchatsync disable +/etc/init.d/meshchatsync stop + +rm -rf /tmp/meshchat + +exit 0 diff --git a/support/init.d/meshchatsync b/support/init.d/meshchatsync new file mode 100755 index 0000000..7520456 --- /dev/null +++ b/support/init.d/meshchatsync @@ -0,0 +1,14 @@ +#!/bin/sh /etc/rc.common + +START=99 +APP=meshchatsync +SERVICE_WRITE_PID=1 +SERVICE_DAEMONIZE=1 + +start() { + service_start /usr/local/bin/meshchatsync +} +stop() { + service_stop /usr/local/bin/meshchatsync + killall meshchatsync +} From 470d02e9f506dd3b53dbee33a37f5ba36e0d64f8 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 11 Jun 2023 12:39:51 -0400 Subject: [PATCH 02/16] Added beginnings of package building code Signed-off-by: Gerard Hickey --- .github/workflows/build-api-package.yaml | 18 ++++ package/apionly/control/control | 10 -- package/ipk-build | 126 +++++++++++++++++++++++ package/src/control/control | 10 -- package/src/control/postinst | 38 ------- package/src/control/preinst | 7 -- package/src/control/prerm | 8 -- 7 files changed, 144 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/build-api-package.yaml delete mode 100755 package/apionly/control/control create mode 100755 package/ipk-build delete mode 100644 package/src/control/control delete mode 100755 package/src/control/postinst delete mode 100755 package/src/control/preinst delete mode 100755 package/src/control/prerm diff --git a/.github/workflows/build-api-package.yaml b/.github/workflows/build-api-package.yaml new file mode 100644 index 0000000..225bab8 --- /dev/null +++ b/.github/workflows/build-api-package.yaml @@ -0,0 +1,18 @@ +name: Build API Package +on: push +env: + IPK_DIR: package/meshchat-api-ipkg +jobs: + create-meshchat-api-package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: package/populate-meshchat-api-fs.sh $IPK_DIR + update-version-information: + runs-on: ubuntu-latest + steps: + - run: package/update-version.sh $IPK_DIR + package-files: + runs-on: ubuntu-latest + steps: + - run: package/ipk-build.sh $IPK_DIR diff --git a/package/apionly/control/control b/package/apionly/control/control deleted file mode 100755 index 0761bf2..0000000 --- a/package/apionly/control/control +++ /dev/null @@ -1,10 +0,0 @@ -Package: meshchat-api -Version: -Depends: lua -Provides: -Source: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY -Section: net -Priority: optional -Maintainer: Tim Wilkinson (KN6PLV) and Trevor Paskett (K7FPV) -Architecture: all -Description: P2P distributed chat for mesh networks diff --git a/package/ipk-build b/package/ipk-build new file mode 100755 index 0000000..9960e77 --- /dev/null +++ b/package/ipk-build @@ -0,0 +1,126 @@ +#!/bin/sh +# ipkg-build -- construct a .ipk from a directory +# Carl Worth +# based on a script by Steve Redler IV, steve@sr-tech.com 5-21-2001 +set -e + +ipkg_extract_value() { + sed -e "s/^[^:]*:[[:space:]]*//" +} + +required_field() { + field=$1 + + value=`grep "^$field:" < $CONTROL/control | ipkg_extract_value` + if [ -z "$value" ]; then + echo "ipkg-build: Error: $CONTROL/control is missing field $field" ; + PKG_ERROR=1 + fi + echo $value +} + +pkg_appears_sane() { + local pkg_dir=$1 + + local owd=`pwd` + cd $pkg_dir + + PKG_ERROR=0 + if [ ! -f "$CONTROL/control" ]; then + echo "ipkg-build: Error: Control file $pkg_dir/$CONTROL/control not found." + cd $owd + return 1 + fi + + pkg=`required_field Package` + version=`required_field Version` + arch=`required_field Architecture` + required_field Maintainer >/dev/null + required_field Description >/dev/null + + if echo $pkg | grep '[^a-z0-9.+-]'; then + echo "ipkg-build: Error: Package name $name contains illegal characters, (other than [a-z0-9.+-])" + PKG_ERROR=1; + fi + + local bad_fields=`sed -ne 's/^\([^[:space:]][^:[:space:]]\+[[:space:]]\+\)[^:].*/\1/p' < $CONTROL/control | sed -e 's/\\n//'` + if [ -n "$bad_fields" ]; then + bad_fields=`echo $bad_fields` + echo "ipkg-build: Error: The following fields in $CONTROL/control are missing a ':'" + echo " $bad_fields" + echo "ipkg-build: This may be due to a missing initial space for a multi-line field value" + PKG_ERROR=1 + fi + + for script in $CONTROL/preinst $CONTROL/postinst $CONTROL/prerm $CONTROL/postrm; do + if [ -f $script -a ! -x $script ]; then + echo "ipkg-build: Error: package script $script is not executable" + PKG_ERROR=1 + fi + done + + if [ -f $CONTROL/conffiles ]; then + for cf in `cat $CONTROL/conffiles`; do + if [ ! -f ./$cf ]; then + echo "ipkg-build: Error: $CONTROL/conffiles mentions conffile $cf which does not exist" + PKG_ERROR=1 + fi + done + fi + + cd $owd + return $PKG_ERROR +} + +### +# ipkg-build "main" +### + +case $# in +1) + dest_dir=. + ;; +2) + dest_dir=$2 + ;; +*) + echo "Usage: ipkg-build []" ; + exit 1 + ;; +esac + +pkg_dir=$1 + +if [ ! -d $pkg_dir ]; then + echo "ipkg-build: Error: Directory $pkg_dir does not exist" + exit 1 +fi + +# CONTROL is second so that it takes precedence +CONTROL= +[ -d $pkg_dir/DEBIAN ] && CONTROL=DEBIAN +[ -d $pkg_dir/CONTROL ] && CONTROL=CONTROL +if [ -z "$CONTROL" ]; then + echo "ipkg-build: Error: Directory $pkg_dir has no CONTROL subdirectory." + exit 1 +fi + +if ! pkg_appears_sane $pkg_dir; then + echo "Please fix the above errors and try again." + exit 1 +fi + +tmp_dir=$dest_dir/IPKG_BUILD.$$ +mkdir $tmp_dir + +tar -C $pkg_dir -czf $tmp_dir/data.tar.gz . --exclude=$CONTROL +tar -C $pkg_dir/$CONTROL -czf $tmp_dir/control.tar.gz . + +echo "2.0" > $tmp_dir/debian-binary + +pkg_file=$dest_dir/${pkg}_${version}_${arch}.ipk +tar -C $tmp_dir -czf $pkg_file debian-binary data.tar.gz control.tar.gz +rm $tmp_dir/debian-binary $tmp_dir/data.tar.gz $tmp_dir/control.tar.gz +rmdir $tmp_dir + +echo "Packaged contents of $pkg_dir into $pkg_file" diff --git a/package/src/control/control b/package/src/control/control deleted file mode 100644 index d78266e..0000000 --- a/package/src/control/control +++ /dev/null @@ -1,10 +0,0 @@ -Package: meshchat -Version: -Depends: curl lua -Provides: -Source: package/meshchat -Section: net -Priority: optional -Maintainer: Tim Wilkinson (KN6PLV) and Trevor Paskett (K7FPV) -Architecture: all -Description: P2P distributed chat for mesh networks diff --git a/package/src/control/postinst b/package/src/control/postinst deleted file mode 100755 index 67004c2..0000000 --- a/package/src/control/postinst +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -grep "|8080|meshchat" /etc/config.mesh/_setup.services.dmz &> /dev/null -DMZPRESENT=$? -grep "|8080|meshchat" /etc/config.mesh/_setup.services.nat &> /dev/null -NATPRESENT=$? -NODEMODE=$(uci -q -c /etc/local/uci/ get hsmmmesh.settings.config) -RAND=$(awk 'BEGIN{srand();print int(rand()*10000) }') -RESTART=0 - -if [ "$DMZPRESENT" != 0 ]; then - echo "MeshChat-$RAND|1|http|$(uname -n)|8080|meshchat" >> /etc/config.mesh/_setup.services.dmz - RESTART=1 -fi - -if [ "$NATPRESENT" != 0 ]; then - echo "MeshChat-$RAND|1|http|$(uname -n)|8080|meshchat" >> /etc/config.mesh/_setup.services.nat - RESTART=1 -fi - -if [ "$NODEMODE" = "mesh" -a "$RESTART" = "1" ]; then - echo "Applying service announcement" - /usr/local/bin/node-setup -a -p mesh &> /dev/null - /etc/init.d/olsrd restart &> /dev/null -fi - -/etc/init.d/meshchatsync enable -/etc/init.d/meshchatsync start - -echo "
" - -echo "Mesh Chat has been setup at http://$(uname -n):8080/meshchat" -echo "
" -if [ "$RESTART" = "1" ]; then - echo "An advertised service has been added for Mesh Chat on the Services configuration page" -fi - -exit 0 diff --git a/package/src/control/preinst b/package/src/control/preinst deleted file mode 100755 index 643506d..0000000 --- a/package/src/control/preinst +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -/etc/init.d/meshchatsync stop > /dev/null 2> /dev/null - -mkdir -p /www/meshchat - -exit 0 diff --git a/package/src/control/prerm b/package/src/control/prerm deleted file mode 100755 index eb6c0f7..0000000 --- a/package/src/control/prerm +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -/etc/init.d/meshchatsync disable -/etc/init.d/meshchatsync stop - -rm -rf /tmp/meshchat - -exit 0 From f472eabf1a1d749141eb0b2cd30ec0e6fbc03da6 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 11 Jun 2023 13:38:38 -0400 Subject: [PATCH 03/16] Updated GitHub workflow Signed-off-by: Gerard Hickey --- .github/workflows/build-api-package.yaml | 6 -- package/ipk-build | 126 ----------------------- 2 files changed, 132 deletions(-) delete mode 100755 package/ipk-build diff --git a/.github/workflows/build-api-package.yaml b/.github/workflows/build-api-package.yaml index 225bab8..e039f36 100644 --- a/.github/workflows/build-api-package.yaml +++ b/.github/workflows/build-api-package.yaml @@ -8,11 +8,5 @@ jobs: steps: - uses: actions/checkout@v3 - run: package/populate-meshchat-api-fs.sh $IPK_DIR - update-version-information: - runs-on: ubuntu-latest - steps: - run: package/update-version.sh $IPK_DIR - package-files: - runs-on: ubuntu-latest - steps: - run: package/ipk-build.sh $IPK_DIR diff --git a/package/ipk-build b/package/ipk-build deleted file mode 100755 index 9960e77..0000000 --- a/package/ipk-build +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/sh -# ipkg-build -- construct a .ipk from a directory -# Carl Worth -# based on a script by Steve Redler IV, steve@sr-tech.com 5-21-2001 -set -e - -ipkg_extract_value() { - sed -e "s/^[^:]*:[[:space:]]*//" -} - -required_field() { - field=$1 - - value=`grep "^$field:" < $CONTROL/control | ipkg_extract_value` - if [ -z "$value" ]; then - echo "ipkg-build: Error: $CONTROL/control is missing field $field" ; - PKG_ERROR=1 - fi - echo $value -} - -pkg_appears_sane() { - local pkg_dir=$1 - - local owd=`pwd` - cd $pkg_dir - - PKG_ERROR=0 - if [ ! -f "$CONTROL/control" ]; then - echo "ipkg-build: Error: Control file $pkg_dir/$CONTROL/control not found." - cd $owd - return 1 - fi - - pkg=`required_field Package` - version=`required_field Version` - arch=`required_field Architecture` - required_field Maintainer >/dev/null - required_field Description >/dev/null - - if echo $pkg | grep '[^a-z0-9.+-]'; then - echo "ipkg-build: Error: Package name $name contains illegal characters, (other than [a-z0-9.+-])" - PKG_ERROR=1; - fi - - local bad_fields=`sed -ne 's/^\([^[:space:]][^:[:space:]]\+[[:space:]]\+\)[^:].*/\1/p' < $CONTROL/control | sed -e 's/\\n//'` - if [ -n "$bad_fields" ]; then - bad_fields=`echo $bad_fields` - echo "ipkg-build: Error: The following fields in $CONTROL/control are missing a ':'" - echo " $bad_fields" - echo "ipkg-build: This may be due to a missing initial space for a multi-line field value" - PKG_ERROR=1 - fi - - for script in $CONTROL/preinst $CONTROL/postinst $CONTROL/prerm $CONTROL/postrm; do - if [ -f $script -a ! -x $script ]; then - echo "ipkg-build: Error: package script $script is not executable" - PKG_ERROR=1 - fi - done - - if [ -f $CONTROL/conffiles ]; then - for cf in `cat $CONTROL/conffiles`; do - if [ ! -f ./$cf ]; then - echo "ipkg-build: Error: $CONTROL/conffiles mentions conffile $cf which does not exist" - PKG_ERROR=1 - fi - done - fi - - cd $owd - return $PKG_ERROR -} - -### -# ipkg-build "main" -### - -case $# in -1) - dest_dir=. - ;; -2) - dest_dir=$2 - ;; -*) - echo "Usage: ipkg-build []" ; - exit 1 - ;; -esac - -pkg_dir=$1 - -if [ ! -d $pkg_dir ]; then - echo "ipkg-build: Error: Directory $pkg_dir does not exist" - exit 1 -fi - -# CONTROL is second so that it takes precedence -CONTROL= -[ -d $pkg_dir/DEBIAN ] && CONTROL=DEBIAN -[ -d $pkg_dir/CONTROL ] && CONTROL=CONTROL -if [ -z "$CONTROL" ]; then - echo "ipkg-build: Error: Directory $pkg_dir has no CONTROL subdirectory." - exit 1 -fi - -if ! pkg_appears_sane $pkg_dir; then - echo "Please fix the above errors and try again." - exit 1 -fi - -tmp_dir=$dest_dir/IPKG_BUILD.$$ -mkdir $tmp_dir - -tar -C $pkg_dir -czf $tmp_dir/data.tar.gz . --exclude=$CONTROL -tar -C $pkg_dir/$CONTROL -czf $tmp_dir/control.tar.gz . - -echo "2.0" > $tmp_dir/debian-binary - -pkg_file=$dest_dir/${pkg}_${version}_${arch}.ipk -tar -C $tmp_dir -czf $pkg_file debian-binary data.tar.gz control.tar.gz -rm $tmp_dir/debian-binary $tmp_dir/data.tar.gz $tmp_dir/control.tar.gz -rmdir $tmp_dir - -echo "Packaged contents of $pkg_dir into $pkg_file" From 6977fc6833cfaaaa16babb92777459363002e517 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 11 Jun 2023 13:57:53 -0400 Subject: [PATCH 04/16] Storing generated package as an artifact Signed-off-by: Gerard Hickey --- .github/workflows/build-api-package.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-api-package.yaml b/.github/workflows/build-api-package.yaml index e039f36..7751f9b 100644 --- a/.github/workflows/build-api-package.yaml +++ b/.github/workflows/build-api-package.yaml @@ -10,3 +10,7 @@ jobs: - run: package/populate-meshchat-api-fs.sh $IPK_DIR - run: package/update-version.sh $IPK_DIR - run: package/ipk-build.sh $IPK_DIR + - uses: actions/upload-artifact@v3 + with: + name: meshchat-api + path: meshchat-api_* \ No newline at end of file From 749dc6b416cbffb620102fb70a64f4ef641419b0 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 11 Jun 2023 14:03:55 -0400 Subject: [PATCH 05/16] Refactored build API workflow Signed-off-by: Gerard Hickey --- .github/workflows/build-api-package.yaml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/workflows/build-api-package.yaml diff --git a/.github/workflows/build-api-package.yaml b/.github/workflows/build-api-package.yaml deleted file mode 100644 index 7751f9b..0000000 --- a/.github/workflows/build-api-package.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: Build API Package -on: push -env: - IPK_DIR: package/meshchat-api-ipkg -jobs: - create-meshchat-api-package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: package/populate-meshchat-api-fs.sh $IPK_DIR - - run: package/update-version.sh $IPK_DIR - - run: package/ipk-build.sh $IPK_DIR - - uses: actions/upload-artifact@v3 - with: - name: meshchat-api - path: meshchat-api_* \ No newline at end of file From 7e9afcb7de4ab77cf2cfb672933e871562444731 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 11 Jun 2023 14:10:48 -0400 Subject: [PATCH 06/16] Added build MeshChat package job Signed-off-by: Gerard Hickey --- package/populate-meshchat-api-fs.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/package/populate-meshchat-api-fs.sh b/package/populate-meshchat-api-fs.sh index 19ec8b5..f68f50a 100755 --- a/package/populate-meshchat-api-fs.sh +++ b/package/populate-meshchat-api-fs.sh @@ -13,4 +13,3 @@ sed -i "s%\$GITHUB_REPOSITORY%$GITHUB_REPOSITORY%" $IPK_DIR/CONTROL/control # Populate the filesystem image for the package install -D api/meshchat -m 755 $IPK_DIR/www/cgi-bin/meshchat - From d5b15cb51e3564adb8d882e5c382ef9e04659fb4 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 11 Jun 2023 21:33:59 -0400 Subject: [PATCH 07/16] More reorganization of files --- meshchat | 892 -------------------- meshchatsync | 293 ------- support/init.d/meshchatsync | 14 - {cgi-bin => www/cgi-bin}/meshchat | 0 {cgi-bin => www/cgi-bin}/meshchatconfig.lua | 0 {cgi-bin => www/cgi-bin}/meshchatlib.lua | 0 6 files changed, 1199 deletions(-) delete mode 100755 meshchat delete mode 100755 meshchatsync delete mode 100755 support/init.d/meshchatsync rename {cgi-bin => www/cgi-bin}/meshchat (100%) rename {cgi-bin => www/cgi-bin}/meshchatconfig.lua (100%) rename {cgi-bin => www/cgi-bin}/meshchatlib.lua (100%) diff --git a/meshchat b/meshchat deleted file mode 100755 index 9a3c507..0000000 --- a/meshchat +++ /dev/null @@ -1,892 +0,0 @@ -#!/usr/bin/lua ---[[ - - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2022 Tim Wilkinson - Base on code (C) Trevor Paskett (see https://github.com/tpaskett) - See Contributors file for additional contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation version 3 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Additional Terms: - - Additional use restrictions exist on the AREDN(TM) trademark and logo. - See AREDNLicense.txt for more info. - - Attributions to the AREDN Project must be retained in the source code. - If importing this code into a new or existing project attribution - to the AREDN project must be added to the source code. - - You must not misrepresent the origin of the material contained within. - - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version - ---]] - -package.path = package.path .. ";/www/cgi-bin/?.lua" - -require('luci.http') -local json = require("luci.jsonc") -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 - return v - end - ) - local fp - request:setfilehandler( - function(meta, chunk, eof) - if not fp then - if meta and meta.file then - uploadfilename = meta.file - end - nixio.fs.mkdir(tmp_upload_dir) - fp = io.open(tmp_upload_dir .. "/file", "w") - end - if chunk then - fp:write(chunk) - end - if eof then - fp:close() - end - end - ) - 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") - - local settings = { - version = app_version, - protocol_verison = protocol_version, - node = node_name(), - zone = zone_name(), - default_channel = default_channel, - debug = debug, - } - - print(json.stringify(settings)) -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") - - local message = query.message:gsub("\n", "\\n"):gsub('"', '\\"'):gsub("\t", " ") - local id = query.id or hash(); - local epoch = os.time() - if tonumber(query.epoch) > epoch then - epoch = query.epoch - end - - get_lock() - - 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(id .. "\t" .. epoch .. "\t" .. message .. "\t" .. query.call_sign .. "\t" .. node_name() .. "\t" .. platform .. "\t" .. query.channel .. "\n") - f:close() - - sort_and_trim_db() - save_messages_db_version() - - release_lock() - - 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") - local output = io.stdout - local encoding = os.getenv("HTTP_ACCEPT_ENCODING") - if encoding and encoding:match("gzip") then - print "Content-Encoding: gzip\r" - output = io.popen("gzip", "w") - end - print("\r") - io.flush() - - get_lock() - - local node = node_name() - - -- read in message DB and parse the contents - local messages = {} - for line in io.lines(messages_db_file) - do - local id, epoch, message, call_sign, node, platform, channel = line:match("^(%S+)\t(%S+)\t(.+)\t([^\t]+)\t(%S*)\t(%S+)\t(%S*)$") - if epoch and #epoch > 0 then - messages[#messages + 1] = { - id = id, - epoch = tonumber(epoch), - message = message:gsub("\\n", "\n"):gsub('\\"', '"'), - call_sign = call_sign, - node = node, - platform = platform, - channel = channel - } - end - end - - if tonumber(query.epoch) and query.call_sign then - local users = {} - - -- read the users status file - if nixio.fs.stat(local_users_status_file) then - for line in io.lines(local_users_status_file) - do - local call_sign = line:match("^([^\t]+)\t") - if call_sign then - users[call_sign] = line - end - end - end - - -- set the timestamp - local epoch = os.time() - if tonumber(query.epoch) > epoch then - epoch = query.epoch - end - - -- rewrite user status file updating the timestamp for requesting call sign - -- query.id is the meshchat_id - local f = io.open(local_users_status_file, "w") - if f then - local found_user = false - for call_sign, line in pairs(users) - do - if call_sign == query.call_sign then - f:write(call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") - found_user = true - else - f:write(line .. "\n") - end - end - if not found_user then - f:write(query.call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") - end - f:close() - end - end - - release_lock() - - -- order messages according to time - table.sort(messages, function(a, b) return a.epoch > b.epoch end) - - output:write(json.stringify(messages)) - output:flush() - -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") - - get_lock() - - local status = {} - if nixio.fs.stat(sync_status_file) then - for line in io.lines(sync_status_file) - do - local node, epoch = line:match("^(.*)\t(.*)$") - status[#status + 1] = { - node = node, - epoch = tonumber(epoch) - } - end - end - - release_lock() - - table.sort(status, function(a, b) return a.epoch > b.epoch end) - - print(json.stringify(status)) -end - ---- Return a list of messages as text. -function messages_raw() - get_lock() - - local md5 = file_md5(messages_db_file) - local lines = {} - for line in io.lines(messages_db_file) - do - lines[#lines + 1] = line - end - - release_lock() - - print("Content-MD5: " .. md5 .. "\r") - print("Content-type: text/plain\r") - print("\r") - - for _, line in ipairs(lines) - do - print(line) - end -end - ---- Return the current MD5 has of the messages database. -function messages_md5() - get_lock() - - local md5 = file_md5(messages_db_file) - - release_lock() - - print("Content-type: text/plain\r") - print("\r") - print(md5) -end - ---- Package the raw messages as the messages.txt file. -function messages_download() - get_lock() - - local md5 = file_md5(messages_db_file) - local lines = {} - for line in io.lines(messages_db_file) - do - lines[#lines + 1] = line - end - - release_lock() - - print("Content-MD5: " .. md5 .. "\r") - print("Content-Disposition: attachment; filename=messages.txt;\r") - print("Content-type: text/plain\r") - print("\r") - - for _, line in ipairs(lines) - do - print(line) - end -end - ---- Return the list of users as raw text. -function users_raw() - get_lock() - - local md5 = file_md5(local_users_status_file) - local lines = {} - for line in io.lines(local_users_status_file) - do - lines[#lines + 1] = line - end - - release_lock() - - print("Content-MD5: " .. md5 .. "\r") - print("Content-type: text/plain\r") - print("\r") - - for _, line in ipairs(lines) - do - print(line) - 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") - - get_lock() - - local users = {} - for line in io.lines(local_users_status_file) - do - local call_sign, id, node, epoch, platform = line:match("^(.*)\t(.*)\t(.*)\t(.*)\t(.*)$") - if epoch and #epoch > 0 then - users[#users + 1] = { - epoch = tonumber(epoch), - id = id, - call_sign = call_sign, - node = node, - platform = platform - } - end - end - for line in io.lines(remote_users_status_file) - do - local call_sign, id, node, epoch, platform = line:match("^(.*)\t(.*)\t(.*)\t(.*)\t(.*)$") - if epoch and #epoch > 0 then - users[#users + 1] = { - epoch = tonumber(epoch), - id = id, - call_sign = call_sign, - node = node, - platform = platform - } - end - end - - release_lock() - - table.sort(users, function(a, b) return a.epoch > b.epoch end) - - print(json.stringify(users)) -end - ---- Return a list of files as plain text. -function local_files_raw() - get_lock() - - local tmp_file = meshchat_path .. "/meshchat_files_local." .. nixio.getpid() - local f = io.open(tmp_file, "w") - if not f then - die("Cannot list local files") - end - local name = node_name() .. ":" .. os.getenv("SERVER_PORT") - for file in nixio.fs.dir(local_files_dir) - do - local stat = nixio.fs.stat(local_files_dir .. "/" .. file) - f:write(file .. "\t" .. name .. "\t" .. stat.size .. "\t" .. stat.mtime .. platform .. "\n") - end - f:close() - - local md5 = file_md5(tmp_file) - - release_lock() - - print("Content-MD5: " .. md5 .. "\r") - print("Content-type: text/plain\r") - print("\r") - - for line in io.lines(tmp_file) - do - print(line) - end - - 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 - - if file == "" or not nixio.fs.stat(file_path) then - error("no file") - return - end - - get_lock() - - local md5 = file_md5(file_path) - local f = io.open(file_path, "rb") - - release_lock() - - print("Content-MD5: " .. md5 .. "\r") - print("Content-Disposition: attachment; filename=\"" .. file .. "\";\r") - print("Content-type: application/octet-stream\r") - print("\r") - - if f then - io.write(f:read("*a")) - f:close() - 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") - - get_lock() - - local files = {} - local node = node_name() .. ":" .. os.getenv("SERVER_PORT") - for file in nixio.fs.dir(local_files_dir) - do - local stat = nixio.fs.stat(local_files_dir .. "/" .. file) - files[#files + 1] = { - file = file, - epoch = stat.mtime, - size = stat.size, - node = node, - platform = platform - } - files[#files]["local"] = 1 - end - for file in nixio.fs.dir(meshchat_path) - do - if file:match("^remote_files%.") then - for line in io.lines(meshchat_path .. "/" .. file) - do - local name, node, size, epoch, platform = line:match("^(.*)\t(.*)\t(.*)\t(.*)\t(.*)$") - if epoch and #epoch > 0 then - files[#files + 1] = { - file = name, - epoch = tonumber(epoch), - size = size, - node = node, - platform = platform - } - files[#files]["local"] = 0 - end - end - end - end - - local stats = file_storage_stats() - - release_lock() - - table.sort(files, function(a, b) return a.epoch > b.epoch end) - - print(json.stringify({ - stats = stats, - files = files - })) -end - ---- Delete the specified file. -function delete_file() - nixio.fs.remove(local_files_dir .. "/" .. query.file) - print("Content-type: application/json\r") - print("\r") - 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") - - print(string.format([[{"messages_version":%s}]], get_messages_db_version())) - - get_lock() - - local users = {} - for line in io.lines(local_users_status_file) - do - local call_sign = line:match("^([^\t]+)\t") - if call_sign then - users[call_sign] = line - end - end - - local node = node_name() - local epoch = os.time() - if tonumber(query.epoch) > epoch then - epoch = query.epoch - end - - -- TODO refactor here and messages function into a single code block - local f = io.open(local_users_status_file, "w") - if f then - local found_user = false - for call_sign, line in pairs(users) - do - if call_sign == query.call_sign then - f:write(call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") - found_user = true - else - f:write(line .. "\n") - end - end - if not found_user then - f:write(query.call_sign .. "\t" .. query.id .. "\t" .. node .. "\t" .. epoch .. "\t" .. platform .. "\n") - end - f:close() - end - - 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") - - local node = node_name() - local hosts = {} - for line in io.lines("/var/dhcp.leases") - do - local epoch, mac1, ip, hostname, mac2 = line:match("^(%S+)%s(%S+)%s(%S+)%s(%S+)%s(%S+)$") - hosts[#hosts + 1] = { - ip = ip, - hostname = hostname, - node = node - } - end - - for line in io.lines("/etc/config.mesh/_setup.dhcp.dmz") - do - local mac, num, hostname = line:match("^(%S+)%s(%S+)%s(%S+)$") - local ip = gethostbyname(hostname) - hosts[#hosts + 1] = { - ip = ip, - hostname = hostname, - node = node - } - end - - for _, remote_node in ipairs(node_list()) - do - local f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " http://" .. remote_node .. ":8080/cgi-bin/meshchat?action=hosts_raw 2> /dev/null") - if f then - for line in f:lines() - do - if line ~= "" and not line:match("error") then - local ip, hostname = line:match("^(.+)\t(.+)$") - hosts[#hosts + 1] = { - ip = ip, - hostname = hostname, - node = remote_node - } - end - end - f:close() - end - end - - table.sort(hosts, function(a, b) return a.hostname < b.hostname end) - - print(json.stringify(hosts)) -end - ---- Return a list of hosts as plain text. -function hosts_raw() - print("Content-type: application/json\r") - print("\r") - - local hosts = {} - for line in io.lines("/var/dhcp.leases") - do - local epoch, mac1, ip, hostname, mac2 = line:match("^(%S+)%s(%S+)%s(%S+)%s(%S+)%s(%S+)$") - hosts[#hosts + 1] = { - ip = ip, - hostname = hostname - } - end - - for line in io.lines("/etc/config.mesh/_setup.dhcp.dmz") - do - local mac, num, hostname = line:match("^(%S+)%s(%S+)%s(%S+)$") - local ip = gethostbyname(hostname) - hosts[#hosts + 1] = { - ip = ip, - hostname = hostname - } - end - - for _, host in ipairs(hosts) - do - print(host.ip .. "\t" .. host.hostname) - end -end - ---- Store a file into the file directory. -function upload_file() - local new_file_size = nixio.fs.stat(tmp_upload_dir .. "/file").size - - get_lock() - - local stats = file_storage_stats() - - release_lock() - - print("Content-type: application/json\r") - print("\r") - - if new_file_size > stats.files_free then - nixio.fs.remove(tmp_upload_dir .. "/file") - print([[{"status":500, "response":"Not enough storage, delete some files"}]]) - else - local fi = io.open(tmp_upload_dir .. "/file", "r") - local fo = io.open(local_files_dir .. "/" .. uploadfilename, "w") - fo:write(fi:read("*a")) - fi:close() - fo:close() - nixio.fs.remove(tmp_upload_dir .. "/file") - print([[{"status":200, "response":"OK"}]]) - 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") - - local pattern = "http://(%S+):(%d+)/meshchat|tcp|" .. str_escape(query.zone_name) .. "%s" - for line in io.lines("/var/run/services_olsr") - do - local node, port = line:match(pattern) - if node and port then - print(node .. "\t" .. port) - end - 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") - print("[]") -end - --- Command dispatch -- - -if query.action == "messages" then - messages() -elseif query.action == "config" then - config() -elseif query.action == "send_message" then - send_message() -elseif query.action == "sync_status" then - sync_status() -elseif query.action == "messages_raw" then - messages_raw() -elseif query.action == "messages_md5" then - messages_md5() -elseif query.action == "messages_download" then - messages_download() -elseif query.action == "users_raw" then - users_raw() -elseif query.action == "users" then - users() -elseif query.action == "local_files_raw" then - local_files_raw() -elseif query.action == "file_download" then - file_download() -elseif query.action == "files" then - files() -elseif query.action == "delete_file" then - delete_file() -elseif query.action == "messages_version" then - messages_version() -elseif query.action == "messages_version_ui" then - messages_version_ui() -elseif query.action == "hosts" then - hosts() -elseif query.action == "hosts_raw" then - hosts_raw() -elseif query.action == "upload_file" then - upload_file() -elseif query.action == "meshchat_nodes" then - meshchat_nodes() -elseif query.action == "action_log" then - action_log() -else - error("error no action") -end diff --git a/meshchatsync b/meshchatsync deleted file mode 100755 index b10631b..0000000 --- a/meshchatsync +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/lua ---[[ - - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2022 Tim Wilkinson - Based on code (C) Trevor Paskett (see https://github.com/tpaskett) - See Contributors file for additional contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation version 3 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Additional Terms: - - Additional use restrictions exist on the AREDN(TM) trademark and logo. - See AREDNLicense.txt for more info. - - Attributions to the AREDN Project must be retained in the source code. - If importing this code into a new or existing project attribution - to the AREDN project must be added to the source code. - - You must not misrepresent the origin of the material contained within. - - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version - ---]] - -package.path = package.path .. ";/www/cgi-bin/?.lua" -require("nixio") -require("meshchatconfig") -require("meshchatlib") - -local sync_status = {} -local non_mesh_chat_nodes = {} - -local node = node_name() - -if not nixio.fs.stat(meshchat_path) then - nixio.fs.mkdir(meshchat_path) - nixio.fs.mkdir(local_files_dir) -end - -if not nixio.fs.stat(messages_db_file) then - io.open(messages_db_file, "w"):close() - nixio.fs.chmod(messages_db_file, "666") -end - -io.open(local_users_status_file, "a"):close() -io.open(remote_users_status_file, "a"):close() - -save_messages_db_version() - -nixio.fs.chmod(meshchat_path, "666") - -io.open(lock_file, "a"):close() - -function log_status() - local cur_status = {} - - if not nixio.fs.stat(sync_status_file) then - io.open(sync_status_file, "w"):close() - end - - get_lock() - - for line in io.lines(sync_status_file) - do - local key, value = line:match("^(.*)\t(.*)$") - cur_status[key] = value - end - - local f = io.open(sync_status_file, "w") - if f then - for key, value in pairs(sync_status) - do - f:write(key .. "\t" .. value .. "\n") - end - for key, value in pairs(cur_status) - do - if not sync_status[key] then - f:write(key .. "\t" .. value .. "\n") - end - end - f:close() - end - - release_lock() -end - -function merge_messages() - local rmsg = {} - local lmsg = {} - - for line in io.lines(meshchat_path .. "/remote_messages") - do - local key = line:match("^(%S+)%s") - rmsg[key] = line - end - - get_lock() - - for line in io.lines(messages_db_file) - do - local key = line:match("^(%S+)%s") - lmsg[key] = line - end - - local f = io.open(messages_db_file, "a") - if f then - for rmsg_id, line in pairs(rmsg) - do - if not lmsg[rmsg_id] then - f:write(line .. "\n") - end - end - f:close() - end - - sort_and_trim_db() - - save_messages_db_version() - - release_lock() -end - -function merge_users() - local rusers = {} - local lusers = {} - - for line in io.lines(meshchat_path .. "/remote_users") - do - local key, value = line:match("^(%S+\t%S+\t%S+)\t(.*)$") - if not line:match("error") and key then - rusers[key] = value - end - end - - get_lock() - - for line in io.lines(remote_users_status_file) - do - local key, value = line:match("^(%S+\t%S+\t%S+)\t(.*)$") - if not line:match("error") and key then - lusers[key] = value - end - end - - local f = io.open(remote_users_status_file, "w") - if f then - for key, _ in pairs(rusers) - do - if lusers[key] and lusers[key] > rusers[key] then - f:write(key .. "\t" .. lusers[key] .. "\n") - else - f:write(key .. "\t" .. rusers[key] .. "\n") - end - end - for key, _ in pairs(lusers) - do - if not rusers[key] then - f:write(key .. "\t" .. lusers[key] .. "\n") - end - end - f:close() - end - - release_lock() -end - -while true -do - local nodes = node_list() - - sync_status = {} - - for _, node_info in ipairs(nodes) - do - for _ = 1,1 - do - local remote_node = node_info.node - local remote_platform = node_info.platform - local remote_port = node_info.port - - local port = "" - if remote_port ~= "" then - port = ":" .. remote_port - end - - if port == "" and remote_platform == "node" then - port = ":8080" - end - - local version = get_messages_db_version() - - -- Poll non mesh chat nodes at a longer interval - if non_mesh_chat_nodes[remote_node] and os.time() < non_mesh_chat_nodes[remote_node] then - break - end - - nixio.fs.remove(meshchat_path .. "/remote_users") - - -- Get remote users file - local f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -sD - \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=users_raw&platform=" .. platform .. "&node=" .. node .. "\" -o " .. meshchat_path .. "/remote_users 2>&1") - local output = f:read("*a") - f:close() - - -- Check if meshchat is installed - if output:match("404 Not Found") then - non_mesh_chat_nodes[remote_node] = os.time() + non_meshchat_poll_interval - break - end - - local md5 = output:match("Content%-MD5:%s([0-9a-f]+)\r\n") - if md5 then - local f_md5 = file_md5(meshchat_path .. "/remote_users") - if md5 == f_md5 then - local cur_size = nixio.fs.stat(meshchat_path .. "/remote_users").size - if cur_size > 0 then - merge_users() - end - end - end - - -- Get remote files file - nixio.fs.remove(meshchat_path .. "/remote_files") - f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -sD - \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=local_files_raw\" -o " .. meshchat_path .. "/remote_files 2>&1") - output = f:read("*a") - f:close() - - md5 = output:match("Content%-MD5:%s([0-9a-f]+)\r\n") - if md5 then - local f_md5 = file_md5(meshchat_path .. "/remote_files") - nixio.fs.remove(meshchat_path .. "/remote_files." .. remote_node) - if md5 == f_md5 then - local cur_size = nixio.fs.stat(meshchat_path .. "/remote_files").size - if cur_size > 0 then - nixio.fs.rename(meshchat_path .. "/remote_files", meshchat_path .. "/remote_files." .. remote_node) - end - end - end - - -- Get remote messages - nixio.fs.remove(meshchat_path .. "/remote_messages") - - f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=messages_version\" -o - 2> /dev/null") - local remote_version = f:read("*a") - f:close() - - -- Check the version of the remote db against ours. Only download the db if the remote has a different copy - - if remote_version ~= "" and version == remote_version then - sync_status[remote_node] = os.time() - break - end - - f = io.popen("/usr/bin/curl --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -sD - \"http://" .. remote_node .. port .. "/cgi-bin/meshchat?action=messages_raw\" -o " .. meshchat_path .. "/remote_messages 2>&1") - local output = f:read("*a") - f:close() - - if nixio.fs.stat(meshchat_path .. "/remote_messages") then - local md5 = output:match("Content%-MD5:%s([0-9a-f]+)\r\n") - if md5 then - local f_md5 = file_md5(meshchat_path .. "/remote_messages") - if md5 == f_md5 then - local cur_size = nixio.fs.stat(meshchat_path .. "/remote_messages").size - if cur_size > 0 then - sync_status[remote_node] = os.time() - merge_messages() - end - end - end - end - end - end - - log_status() - - nixio.fs.remove(meshchat_path .. "/remote_messages") - nixio.fs.remove(meshchat_path .. "/remote_users") - nixio.fs.remove(meshchat_path .. "/remote_files") - - nixio.nanosleep(poll_interval, 0) -end diff --git a/support/init.d/meshchatsync b/support/init.d/meshchatsync deleted file mode 100755 index 7520456..0000000 --- a/support/init.d/meshchatsync +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh /etc/rc.common - -START=99 -APP=meshchatsync -SERVICE_WRITE_PID=1 -SERVICE_DAEMONIZE=1 - -start() { - service_start /usr/local/bin/meshchatsync -} -stop() { - service_stop /usr/local/bin/meshchatsync - killall meshchatsync -} diff --git a/cgi-bin/meshchat b/www/cgi-bin/meshchat similarity index 100% rename from cgi-bin/meshchat rename to www/cgi-bin/meshchat diff --git a/cgi-bin/meshchatconfig.lua b/www/cgi-bin/meshchatconfig.lua similarity index 100% rename from cgi-bin/meshchatconfig.lua rename to www/cgi-bin/meshchatconfig.lua diff --git a/cgi-bin/meshchatlib.lua b/www/cgi-bin/meshchatlib.lua similarity index 100% rename from cgi-bin/meshchatlib.lua rename to www/cgi-bin/meshchatlib.lua From 3ce7f01fe0fd45e915d3aa92688753e7eaeb1681 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Mon, 17 Jul 2023 18:26:57 -0400 Subject: [PATCH 08/16] Moved MeshChat source to top level Signed-off-by: Gerard Hickey --- www/cgi-bin/meshchat => meshchat | 0 www/cgi-bin/meshchatconfig.lua | 79 --------- www/cgi-bin/meshchatlib.lua | 285 ------------------------------- 3 files changed, 364 deletions(-) rename www/cgi-bin/meshchat => meshchat (100%) delete mode 100755 www/cgi-bin/meshchatconfig.lua delete mode 100755 www/cgi-bin/meshchatlib.lua diff --git a/www/cgi-bin/meshchat b/meshchat similarity index 100% rename from www/cgi-bin/meshchat rename to meshchat diff --git a/www/cgi-bin/meshchatconfig.lua b/www/cgi-bin/meshchatconfig.lua deleted file mode 100755 index b91e5ab..0000000 --- a/www/cgi-bin/meshchatconfig.lua +++ /dev/null @@ -1,79 +0,0 @@ ---[[ - - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2022 Tim Wilkinson - Base on code (C) Trevor Paskett (see https://github.com/tpaskett) - See Contributors file for additional contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation version 3 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Additional Terms: - - Additional use restrictions exist on the AREDN(TM) trademark and logo. - See AREDNLicense.txt for more info. - - Attributions to the AREDN Project must be retained in the source code. - If importing this code into a new or existing project attribution - to the AREDN project must be added to the source code. - - You must not misrepresent the origin of the material contained within. - - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version - ---]] - ---- --- @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" -messages_db_file_orig = meshchat_path .. "/messages" -sync_status_file = meshchat_path .. "/sync_status" -local_users_status_file = meshchat_path .. "/users_local" -remote_users_status_file = meshchat_path .. "/users_remote" -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 = "master" -default_channel = "chat" diff --git a/www/cgi-bin/meshchatlib.lua b/www/cgi-bin/meshchatlib.lua deleted file mode 100755 index f409ea2..0000000 --- a/www/cgi-bin/meshchatlib.lua +++ /dev/null @@ -1,285 +0,0 @@ ---[[ - - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2022 Tim Wilkinson - Base on code (C) Trevor Paskett (see https://github.com/tpaskett) - See Contributors file for additional contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation version 3 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Additional Terms: - - Additional use restrictions exist on the AREDN(TM) trademark and logo. - See AREDNLicense.txt for more info. - - Attributions to the AREDN Project must be retained in the source code. - If importing this code into a new or existing project attribution - to the AREDN project must be added to the source code. - - You must not misrepresent the origin of the material contained within. - - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version - ---]] - -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 - return "" - end - local output = f:read("*a") - f:close() - 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" - -- LAN mode is not set to NAT - if dmz_mode ~= "0" then - servfile = "/etc/config.mesh/_setup.services.dmz" - end - if nixio.fs.access(servfile) then - for line in io.lines(servfile) - do - local zone = line:match("^(.*)|.*|.*|.*|.*|meshchat$") - if zone then - return zone - end - end - end - return "MeshChat" -end - -messages_db_file = messages_db_file_orig .. "." .. zone_name() - -local lock_fd -function get_lock() - if not lock_fd then - lock_fd = nixio.open(lock_file, "w", "666") - end - lock_fd:lock("lock") -end - -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 "" - end - local output = capture("md5sum " .. file:gsub(" ", "\\ ")):match("^(%S+)%s") - return output and output or "" -end - -function get_messages_db_version() - for line in io.lines(messages_version_file) - do - line = line:gsub("\n$", "") - return line - end -end - -function save_messages_db_version() - local f = io.open(messages_version_file, "w") - f:write(get_messages_version_file() .. "\n") - f:close() - nixio.fs.chmod(messages_version_file, "666") -end - -function get_messages_version_file() - local sum = 0 - for line in io.lines(messages_db_file) - do - local key = line:match("^([0-9a-f]+)") - if key then - sum = sum + tonumber(key, 16) - end - end - 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 - -function sort_and_trim_db() - local valid_time = os.time() + valid_future_message_time - local unused_count = max_messages_db_size - local messages = {} - for line in io.lines(messages_db_file) - do - local id, epoch = line:match("^(%x+)\t(%S+)\t") - -- ignore messages that are too far in the future (assume they're errors) - epoch = tonumber(epoch) - if epoch and epoch < valid_time then - messages[#messages + 1] = { - epoch = epoch, - id = tonumber(id, 16), - line = line - } - end - unused_count = unused_count - 1 - end - - table.sort(messages, function(a, b) - if a.epoch == b.epoch then - return a.id < b.id - else - return a.epoch < b.epoch - end - end) - - local f = io.open(messages_db_file, "w") - for _, line in ipairs(messages) - do - unused_count = unused_count + 1 - if unused_count > 0 then - f:write(line.line .. "\n") - end - end - f:close() -end - -function file_storage_stats() - local lines = capture("df -k " .. local_files_dir) - local blocks, used, available, perc = lines:match("(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%%") - used = tonumber(used) * 1024 - available = tonumber(available) * 1024 - local total = used + available - - local local_files_bytes = 0 - for file in nixio.fs.dir(local_files_dir) - do - local_files_bytes = local_files_bytes + nixio.fs.stat(local_files_dir .. "/" .. file).size - end - - if max_file_storage - local_files_bytes < 0 then - local_files_bytes = max_file_storage - end - - return { - total = total, - used = used, - files = local_files_bytes, - files_free = max_file_storage - local_files_bytes, - allowed = max_file_storage - } -end - -function gethostbyname(hostname) - return capture("nslookup " .. hostname):match("Address 1:%s*([%d%.]+)") -end - -function node_list() - if not nixio.fs.stat("/var/run/services_olsr") then - return {} - end - local local_node = node_name():lower() - local zone = zone_name() - - local nodes = {} - local pattern = "http://(%S+):(%d+)/meshchat|tcp|" .. str_escape(zone) .. "%s" - for line in io.lines("/var/run/services_olsr") - do - local node, port = line:match(pattern) - if node and port then - node = node:lower() - if node ~= local_node then - nodes[#nodes + 1] = { - platform = (port == "8080" and "node" or "pi"), - node = node, - port = port - } - end - end - end - - for _, extra in ipairs(extra_nodes) - do - nodes[#node + 1] = extra - end - - 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 From f26130ba5f06f9b4fbd26257be8d967a61e531bc Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 13 Jun 2023 16:47:43 -0400 Subject: [PATCH 09/16] feat: Set send channel when channel filter changed Signed-off-by: Gerard Hickey --- www/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/chat.js b/www/chat.js index 31a503f..0eafd4a 100644 --- a/www/chat.js +++ b/www/chat.js @@ -133,7 +133,7 @@ function meshchat_init() { Cookies.set('meshchat_id', make_id()); meshchat_id = Cookies.get('meshchat_id'); } - + //console.log(meshchat_id); $('#submit-message').on('click', function(e) { e.preventDefault(); if ($('#message').val().length == 0) return; From 8f57aa2c42dd6eec91fb4f2f790393b71c318ae0 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Thu, 21 Sep 2023 14:56:41 -0400 Subject: [PATCH 10/16] docs: Updated README with history of MeshChat Signed-off-by: Gerard Hickey --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 64ac965..663efec 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,3 @@ to implement and use: 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. - - From bfadccb3f8010c51374e206d78585b22ac86848a Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sat, 9 Dec 2023 11:15:24 -0500 Subject: [PATCH 11/16] fix: set epoch in send_message API even if not specified Signed-off-by: Gerard Hickey --- meshchat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshchat b/meshchat index 9a3c507..859ecfa 100755 --- a/meshchat +++ b/meshchat @@ -162,7 +162,7 @@ function send_message() local message = query.message:gsub("\n", "\\n"):gsub('"', '\\"'):gsub("\t", " ") local id = query.id or hash(); local epoch = os.time() - if tonumber(query.epoch) > epoch then + if query.epoch and tonumber(query.epoch) > epoch then epoch = query.epoch end From 28d17592f7ee1a778fdd0bc740f451646493f9dd Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Fri, 22 Dec 2023 09:51:03 -0500 Subject: [PATCH 12/16] feat: add message class abstracting message handling (#23) * refactor: add comments to clarify flow Signed-off-by: Gerard Hickey * feat: add messages class to front end Update the handling of messages on the front end code to better control the update of messages and coordinateion with the rest of the UI. Signed-off-by: Gerard Hickey * add more config support Signed-off-by: Gerard Hickey * feat: message ID can be specified in send_message API Signed-off-by: Gerard Hickey * add md5 module to front-end Signed-off-by: Gerard Hickey * integrate message class into front-end code Signed-off-by: Gerard Hickey --------- Signed-off-by: Gerard Hickey --- www/chat.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/www/chat.js b/www/chat.js index 0eafd4a..cf768ce 100644 --- a/www/chat.js +++ b/www/chat.js @@ -1,16 +1,19 @@ var meshchat_id; var last_messages_update = epoch(); -var call_sign = 'NOCALL'; -var enable_video = 0; - -var messages = new Messages(); -let alert = new Audio('alert.mp3'); - -let config = {}; -let context = { - config_loaded: false, - debug: true, // let startup funcs show debug -} +var call_sign = 'NOCALL'; +var meshchat_id; +var peer; +var mediaConnection; +var enable_video = 0; +var messages_updating = false; +var users_updating = false; +var messages = []; +var channel_filter = ''; +var messages_version = 0; +var alert = new Audio('alert.mp3'); +var message_db_version = 0; +var pending_message_db_version = 0; +var search_filter = ''; $(function() { meshchat_init(); @@ -133,7 +136,7 @@ function meshchat_init() { Cookies.set('meshchat_id', make_id()); meshchat_id = Cookies.get('meshchat_id'); } - //console.log(meshchat_id); + $('#submit-message').on('click', function(e) { e.preventDefault(); if ($('#message').val().length == 0) return; From 1e86795039c967514bc9da5932d47e4e25b75e08 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Tue, 13 Feb 2024 00:33:11 -0500 Subject: [PATCH 13/16] Add GitHub Actions workflow to automate releases (#40) * Added beginnings of package building code * chore: create release CI job * chore: add documentation section to changelog * chore: make build-meshchat-package a reusable workflow * chore: add build packages to release * fix: update-version.sh can now be told version to set * chore(ci): convert meshchat-api build to reusable workflow * chore(ci): set release to only work on release branch Signed-off-by: Gerard Hickey --- README.md | 2 + meshchat | 20 +++----- package/ipk-build | 126 ++++++++++++++++++++++++++++++++++++++++++++++ www/chat.js | 25 ++++----- 4 files changed, 145 insertions(+), 28 deletions(-) create mode 100755 package/ipk-build mode change 100644 => 100755 www/chat.js diff --git a/README.md b/README.md index 663efec..64ac965 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,5 @@ to implement and use: 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/meshchat b/meshchat index 859ecfa..63f6731 100755 --- a/meshchat +++ b/meshchat @@ -160,9 +160,9 @@ function send_message() print("\r") local message = query.message:gsub("\n", "\\n"):gsub('"', '\\"'):gsub("\t", " ") - local id = query.id or hash(); + local epoch = os.time() - if query.epoch and tonumber(query.epoch) > epoch then + if tonumber(query.epoch) > epoch then epoch = query.epoch end @@ -174,7 +174,7 @@ function send_message() -- TODO return a proper error code on failure die("Cannot send message") end - f:write(id .. "\t" .. epoch .. "\t" .. message .. "\t" .. query.call_sign .. "\t" .. node_name() .. "\t" .. platform .. "\t" .. query.channel .. "\n") + f:write(hash() .. "\t" .. epoch .. "\t" .. message .. "\t" .. query.call_sign .. "\t" .. node_name() .. "\t" .. platform .. "\t" .. query.channel .. "\n") f:close() sort_and_trim_db() @@ -224,7 +224,6 @@ function messages() local node = node_name() - -- read in message DB and parse the contents local messages = {} for line in io.lines(messages_db_file) do @@ -244,8 +243,6 @@ function messages() if tonumber(query.epoch) and query.call_sign then local users = {} - - -- read the users status file if nixio.fs.stat(local_users_status_file) then for line in io.lines(local_users_status_file) do @@ -256,14 +253,11 @@ function messages() end end - -- set the timestamp local epoch = os.time() if tonumber(query.epoch) > epoch then epoch = query.epoch end - -- rewrite user status file updating the timestamp for requesting call sign - -- query.id is the meshchat_id local f = io.open(local_users_status_file, "w") if f then local found_user = false @@ -285,7 +279,6 @@ function messages() release_lock() - -- order messages according to time table.sort(messages, function(a, b) return a.epoch > b.epoch end) output:write(json.stringify(messages)) @@ -637,7 +630,7 @@ end function messages_version_ui() print("Content-type: application/json\r") print("\r") - + print(string.format([[{"messages_version":%s}]], get_messages_db_version())) get_lock() @@ -657,7 +650,6 @@ function messages_version_ui() epoch = query.epoch end - -- TODO refactor here and messages function into a single code block local f = io.open(local_users_status_file, "w") if f then local found_user = false @@ -698,7 +690,7 @@ end function hosts() print("Content-type: application/json\r") print("\r") - + local node = node_name() local hosts = {} for line in io.lines("/var/dhcp.leases") @@ -750,7 +742,7 @@ end function hosts_raw() print("Content-type: application/json\r") print("\r") - + local hosts = {} for line in io.lines("/var/dhcp.leases") do diff --git a/package/ipk-build b/package/ipk-build new file mode 100755 index 0000000..9960e77 --- /dev/null +++ b/package/ipk-build @@ -0,0 +1,126 @@ +#!/bin/sh +# ipkg-build -- construct a .ipk from a directory +# Carl Worth +# based on a script by Steve Redler IV, steve@sr-tech.com 5-21-2001 +set -e + +ipkg_extract_value() { + sed -e "s/^[^:]*:[[:space:]]*//" +} + +required_field() { + field=$1 + + value=`grep "^$field:" < $CONTROL/control | ipkg_extract_value` + if [ -z "$value" ]; then + echo "ipkg-build: Error: $CONTROL/control is missing field $field" ; + PKG_ERROR=1 + fi + echo $value +} + +pkg_appears_sane() { + local pkg_dir=$1 + + local owd=`pwd` + cd $pkg_dir + + PKG_ERROR=0 + if [ ! -f "$CONTROL/control" ]; then + echo "ipkg-build: Error: Control file $pkg_dir/$CONTROL/control not found." + cd $owd + return 1 + fi + + pkg=`required_field Package` + version=`required_field Version` + arch=`required_field Architecture` + required_field Maintainer >/dev/null + required_field Description >/dev/null + + if echo $pkg | grep '[^a-z0-9.+-]'; then + echo "ipkg-build: Error: Package name $name contains illegal characters, (other than [a-z0-9.+-])" + PKG_ERROR=1; + fi + + local bad_fields=`sed -ne 's/^\([^[:space:]][^:[:space:]]\+[[:space:]]\+\)[^:].*/\1/p' < $CONTROL/control | sed -e 's/\\n//'` + if [ -n "$bad_fields" ]; then + bad_fields=`echo $bad_fields` + echo "ipkg-build: Error: The following fields in $CONTROL/control are missing a ':'" + echo " $bad_fields" + echo "ipkg-build: This may be due to a missing initial space for a multi-line field value" + PKG_ERROR=1 + fi + + for script in $CONTROL/preinst $CONTROL/postinst $CONTROL/prerm $CONTROL/postrm; do + if [ -f $script -a ! -x $script ]; then + echo "ipkg-build: Error: package script $script is not executable" + PKG_ERROR=1 + fi + done + + if [ -f $CONTROL/conffiles ]; then + for cf in `cat $CONTROL/conffiles`; do + if [ ! -f ./$cf ]; then + echo "ipkg-build: Error: $CONTROL/conffiles mentions conffile $cf which does not exist" + PKG_ERROR=1 + fi + done + fi + + cd $owd + return $PKG_ERROR +} + +### +# ipkg-build "main" +### + +case $# in +1) + dest_dir=. + ;; +2) + dest_dir=$2 + ;; +*) + echo "Usage: ipkg-build []" ; + exit 1 + ;; +esac + +pkg_dir=$1 + +if [ ! -d $pkg_dir ]; then + echo "ipkg-build: Error: Directory $pkg_dir does not exist" + exit 1 +fi + +# CONTROL is second so that it takes precedence +CONTROL= +[ -d $pkg_dir/DEBIAN ] && CONTROL=DEBIAN +[ -d $pkg_dir/CONTROL ] && CONTROL=CONTROL +if [ -z "$CONTROL" ]; then + echo "ipkg-build: Error: Directory $pkg_dir has no CONTROL subdirectory." + exit 1 +fi + +if ! pkg_appears_sane $pkg_dir; then + echo "Please fix the above errors and try again." + exit 1 +fi + +tmp_dir=$dest_dir/IPKG_BUILD.$$ +mkdir $tmp_dir + +tar -C $pkg_dir -czf $tmp_dir/data.tar.gz . --exclude=$CONTROL +tar -C $pkg_dir/$CONTROL -czf $tmp_dir/control.tar.gz . + +echo "2.0" > $tmp_dir/debian-binary + +pkg_file=$dest_dir/${pkg}_${version}_${arch}.ipk +tar -C $tmp_dir -czf $pkg_file debian-binary data.tar.gz control.tar.gz +rm $tmp_dir/debian-binary $tmp_dir/data.tar.gz $tmp_dir/control.tar.gz +rmdir $tmp_dir + +echo "Packaged contents of $pkg_dir into $pkg_file" diff --git a/www/chat.js b/www/chat.js old mode 100644 new mode 100755 index cf768ce..31a503f --- a/www/chat.js +++ b/www/chat.js @@ -1,19 +1,16 @@ var meshchat_id; var last_messages_update = epoch(); -var call_sign = 'NOCALL'; -var meshchat_id; -var peer; -var mediaConnection; -var enable_video = 0; -var messages_updating = false; -var users_updating = false; -var messages = []; -var channel_filter = ''; -var messages_version = 0; -var alert = new Audio('alert.mp3'); -var message_db_version = 0; -var pending_message_db_version = 0; -var search_filter = ''; +var call_sign = 'NOCALL'; +var enable_video = 0; + +var messages = new Messages(); +let alert = new Audio('alert.mp3'); + +let config = {}; +let context = { + config_loaded: false, + debug: true, // let startup funcs show debug +} $(function() { meshchat_init(); From 0298eae810b37ea2f11ae2dd957ef5dac0c27f0d Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sun, 18 Feb 2024 16:05:00 -0500 Subject: [PATCH 14/16] chore(ci): update release pipeline Signed-off-by: Gerard Hickey --- .github/workflows/build-meshchat-api-package.yaml | 8 ++++++++ .github/workflows/build-meshchat-package.yaml | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/.github/workflows/build-meshchat-api-package.yaml b/.github/workflows/build-meshchat-api-package.yaml index e11f448..6bab1b4 100644 --- a/.github/workflows/build-meshchat-api-package.yaml +++ b/.github/workflows/build-meshchat-api-package.yaml @@ -8,6 +8,11 @@ on: build_dir: required: true type: string + ref: + required: false + type: string + default: $GITHUB_REF_NAME + jobs: create-meshchat-api-package: @@ -16,6 +21,9 @@ jobs: # image: registry.gitlab.com/wt0f/gitlab-runner-images/shell:latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ inputs.ref }} - run: echo ${{ inputs.build_version }} > VERSION - run: package/populate-meshchat-api-fs.sh ${{ inputs.build_dir }} - run: package/update-version.sh ${{ inputs.build_dir }} diff --git a/.github/workflows/build-meshchat-package.yaml b/.github/workflows/build-meshchat-package.yaml index 2d4f86e..9e56a78 100644 --- a/.github/workflows/build-meshchat-package.yaml +++ b/.github/workflows/build-meshchat-package.yaml @@ -8,6 +8,10 @@ on: build_dir: required: true type: string + ref: + required: false + type: string + default: $GITHUB_REF_NAME jobs: create-meshchat-package: @@ -18,6 +22,9 @@ jobs: package_file: ${{ steps.detect-package-file.outputs.file }} steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ inputs.ref }} # - run: info "Populating the filesystem with MeshChat files" - run: echo ${{ inputs.build_version }} > VERSION - run: package/populate-meshchat-fs.sh ${{ inputs.build_dir }} From d90fc33eafecd31fec2dc825d8388f8e98ae8fd0 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Fri, 1 Mar 2024 00:20:54 -0500 Subject: [PATCH 15/16] feat: allow admin to set default channel (#19) A MeshChat administrator now has the ability to define the default channel that a newly logged in user is placed into. This requires that the administrator set the value `default_channel` in the configuration file. Setting `default_channel` to an empty string ('') will continue using the legacy setting of 'Everything'. Signed-off-by: Gerard Hickey --- .../workflows/build-meshchat-api-package.yaml | 37 ----- .github/workflows/build-meshchat-package.yaml | 41 ------ meshchat | 18 ++- package/ipk-build | 126 ------------------ 4 files changed, 13 insertions(+), 209 deletions(-) delete mode 100644 .github/workflows/build-meshchat-api-package.yaml delete mode 100644 .github/workflows/build-meshchat-package.yaml delete mode 100755 package/ipk-build diff --git a/.github/workflows/build-meshchat-api-package.yaml b/.github/workflows/build-meshchat-api-package.yaml deleted file mode 100644 index 6bab1b4..0000000 --- a/.github/workflows/build-meshchat-api-package.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build MeshChat API Package -on: - workflow_call: - inputs: - build_version: - required: true - type: string - build_dir: - required: true - type: string - ref: - required: false - type: string - default: $GITHUB_REF_NAME - - -jobs: - create-meshchat-api-package: - runs-on: ubuntu-latest - # container: - # image: registry.gitlab.com/wt0f/gitlab-runner-images/shell:latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ inputs.ref }} - - run: echo ${{ inputs.build_version }} > VERSION - - run: package/populate-meshchat-api-fs.sh ${{ inputs.build_dir }} - - run: package/update-version.sh ${{ inputs.build_dir }} - - run: package/ipk-build.sh ${{ inputs.build_dir }} - - id: detect-package-file - run: echo "file=$(ls -1 meshchat_*.ipk)" >> $GITHUB_OUTPUT - - run: echo "${{ steps.detect-package-file.outputs.file }}" - - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.detect-package-file.outputs.file }} - path: ${{ steps.detect-package-file.outputs.file }} diff --git a/.github/workflows/build-meshchat-package.yaml b/.github/workflows/build-meshchat-package.yaml deleted file mode 100644 index 9e56a78..0000000 --- a/.github/workflows/build-meshchat-package.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build MeshChat Package -on: - workflow_call: - inputs: - build_version: - required: true - type: string - build_dir: - required: true - type: string - ref: - required: false - type: string - default: $GITHUB_REF_NAME - -jobs: - create-meshchat-package: - runs-on: ubuntu-latest - # container: - # image: registry.gitlab.com/wt0f/gitlab-runner-images/shell:latest - outputs: - package_file: ${{ steps.detect-package-file.outputs.file }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ inputs.ref }} - # - run: info "Populating the filesystem with MeshChat files" - - run: echo ${{ inputs.build_version }} > VERSION - - run: package/populate-meshchat-fs.sh ${{ inputs.build_dir }} - # - run: info "Updating version numbers to " - - run: package/update-version.sh ${{ inputs.build_dir }} - # - run: info "Packing up MeshChat files" - - run: package/ipk-build.sh ${{ inputs.build_dir }} - - id: detect-package-file - run: echo "file=$(ls -1 meshchat_*.ipk)" >> $GITHUB_OUTPUT - - run: echo "${{ steps.detect-package-file.outputs.file }}" - - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.detect-package-file.outputs.file }} - path: ${{ steps.detect-package-file.outputs.file }} diff --git a/meshchat b/meshchat index 63f6731..9a3c507 100755 --- a/meshchat +++ b/meshchat @@ -160,7 +160,7 @@ function send_message() print("\r") local message = query.message:gsub("\n", "\\n"):gsub('"', '\\"'):gsub("\t", " ") - + local id = query.id or hash(); local epoch = os.time() if tonumber(query.epoch) > epoch then epoch = query.epoch @@ -174,7 +174,7 @@ function send_message() -- 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") + f:write(id .. "\t" .. epoch .. "\t" .. message .. "\t" .. query.call_sign .. "\t" .. node_name() .. "\t" .. platform .. "\t" .. query.channel .. "\n") f:close() sort_and_trim_db() @@ -224,6 +224,7 @@ function messages() local node = node_name() + -- read in message DB and parse the contents local messages = {} for line in io.lines(messages_db_file) do @@ -243,6 +244,8 @@ function messages() if tonumber(query.epoch) and query.call_sign then local users = {} + + -- read the users status file if nixio.fs.stat(local_users_status_file) then for line in io.lines(local_users_status_file) do @@ -253,11 +256,14 @@ function messages() end end + -- set the timestamp local epoch = os.time() if tonumber(query.epoch) > epoch then epoch = query.epoch end + -- rewrite user status file updating the timestamp for requesting call sign + -- query.id is the meshchat_id local f = io.open(local_users_status_file, "w") if f then local found_user = false @@ -279,6 +285,7 @@ function messages() release_lock() + -- order messages according to time table.sort(messages, function(a, b) return a.epoch > b.epoch end) output:write(json.stringify(messages)) @@ -630,7 +637,7 @@ end function messages_version_ui() print("Content-type: application/json\r") print("\r") - + print(string.format([[{"messages_version":%s}]], get_messages_db_version())) get_lock() @@ -650,6 +657,7 @@ function messages_version_ui() epoch = query.epoch end + -- TODO refactor here and messages function into a single code block local f = io.open(local_users_status_file, "w") if f then local found_user = false @@ -690,7 +698,7 @@ end function hosts() print("Content-type: application/json\r") print("\r") - + local node = node_name() local hosts = {} for line in io.lines("/var/dhcp.leases") @@ -742,7 +750,7 @@ end function hosts_raw() print("Content-type: application/json\r") print("\r") - + local hosts = {} for line in io.lines("/var/dhcp.leases") do diff --git a/package/ipk-build b/package/ipk-build deleted file mode 100755 index 9960e77..0000000 --- a/package/ipk-build +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/sh -# ipkg-build -- construct a .ipk from a directory -# Carl Worth -# based on a script by Steve Redler IV, steve@sr-tech.com 5-21-2001 -set -e - -ipkg_extract_value() { - sed -e "s/^[^:]*:[[:space:]]*//" -} - -required_field() { - field=$1 - - value=`grep "^$field:" < $CONTROL/control | ipkg_extract_value` - if [ -z "$value" ]; then - echo "ipkg-build: Error: $CONTROL/control is missing field $field" ; - PKG_ERROR=1 - fi - echo $value -} - -pkg_appears_sane() { - local pkg_dir=$1 - - local owd=`pwd` - cd $pkg_dir - - PKG_ERROR=0 - if [ ! -f "$CONTROL/control" ]; then - echo "ipkg-build: Error: Control file $pkg_dir/$CONTROL/control not found." - cd $owd - return 1 - fi - - pkg=`required_field Package` - version=`required_field Version` - arch=`required_field Architecture` - required_field Maintainer >/dev/null - required_field Description >/dev/null - - if echo $pkg | grep '[^a-z0-9.+-]'; then - echo "ipkg-build: Error: Package name $name contains illegal characters, (other than [a-z0-9.+-])" - PKG_ERROR=1; - fi - - local bad_fields=`sed -ne 's/^\([^[:space:]][^:[:space:]]\+[[:space:]]\+\)[^:].*/\1/p' < $CONTROL/control | sed -e 's/\\n//'` - if [ -n "$bad_fields" ]; then - bad_fields=`echo $bad_fields` - echo "ipkg-build: Error: The following fields in $CONTROL/control are missing a ':'" - echo " $bad_fields" - echo "ipkg-build: This may be due to a missing initial space for a multi-line field value" - PKG_ERROR=1 - fi - - for script in $CONTROL/preinst $CONTROL/postinst $CONTROL/prerm $CONTROL/postrm; do - if [ -f $script -a ! -x $script ]; then - echo "ipkg-build: Error: package script $script is not executable" - PKG_ERROR=1 - fi - done - - if [ -f $CONTROL/conffiles ]; then - for cf in `cat $CONTROL/conffiles`; do - if [ ! -f ./$cf ]; then - echo "ipkg-build: Error: $CONTROL/conffiles mentions conffile $cf which does not exist" - PKG_ERROR=1 - fi - done - fi - - cd $owd - return $PKG_ERROR -} - -### -# ipkg-build "main" -### - -case $# in -1) - dest_dir=. - ;; -2) - dest_dir=$2 - ;; -*) - echo "Usage: ipkg-build []" ; - exit 1 - ;; -esac - -pkg_dir=$1 - -if [ ! -d $pkg_dir ]; then - echo "ipkg-build: Error: Directory $pkg_dir does not exist" - exit 1 -fi - -# CONTROL is second so that it takes precedence -CONTROL= -[ -d $pkg_dir/DEBIAN ] && CONTROL=DEBIAN -[ -d $pkg_dir/CONTROL ] && CONTROL=CONTROL -if [ -z "$CONTROL" ]; then - echo "ipkg-build: Error: Directory $pkg_dir has no CONTROL subdirectory." - exit 1 -fi - -if ! pkg_appears_sane $pkg_dir; then - echo "Please fix the above errors and try again." - exit 1 -fi - -tmp_dir=$dest_dir/IPKG_BUILD.$$ -mkdir $tmp_dir - -tar -C $pkg_dir -czf $tmp_dir/data.tar.gz . --exclude=$CONTROL -tar -C $pkg_dir/$CONTROL -czf $tmp_dir/control.tar.gz . - -echo "2.0" > $tmp_dir/debian-binary - -pkg_file=$dest_dir/${pkg}_${version}_${arch}.ipk -tar -C $tmp_dir -czf $pkg_file debian-binary data.tar.gz control.tar.gz -rm $tmp_dir/debian-binary $tmp_dir/data.tar.gz $tmp_dir/control.tar.gz -rmdir $tmp_dir - -echo "Packaged contents of $pkg_dir into $pkg_file" From 57d2766d1bece87138ff4b76053e3316955454a0 Mon Sep 17 00:00:00 2001 From: Gerard Hickey Date: Sat, 2 Mar 2024 11:06:27 -0500 Subject: [PATCH 16/16] fix: remove duplicate config definitions Signed-off-by: Gerard Hickey --- www/chat.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/chat.js b/www/chat.js index 31a503f..fb40078 100755 --- a/www/chat.js +++ b/www/chat.js @@ -6,7 +6,6 @@ var enable_video = 0; var messages = new Messages(); let alert = new Audio('alert.mp3'); -let config = {}; let context = { config_loaded: false, debug: true, // let startup funcs show debug