diff --git a/README.md b/README.md index 89255f964..f96685493 100644 --- a/README.md +++ b/README.md @@ -21,22 +21,44 @@ We encourage each network community to create its firmware profile on [In this page][1] we provide a list of requirements that ensure you to have a working LibreMesh node on your router. This list comes with no warranties: read -carefully the [model-specific instructions on OpenWrt wiki][2] and be +carefully the [model-specific instructions on OpenWrt wiki][OpenWrt-ToH] and be extra-careful when flashing your routers! ## Building a Firmware Image on Your PC -The LibreMesh firmware can be compiled by following [these instructions][13]. +### Building the stable release LibreMesh 2020.1 -### Using the ImageBuilder +#### Using the BuildRoot -Start an ImageBuilder of your choice, use containers for an easier setup: +The BuildRoot **will cross-compile the whole OpenWrt and the LibreMesh packages** on your computer, so it will take approx 10 GB of disk space and a few hours of compilation time. + +For compiling LibreMesh firmware with this method, you can follow [these instructions][development_page]. + +#### Using the ImageBuilder + +The ImageBuilder method is not available for the stable release. + +### Building the experimental firmware + +The experimental code still has serious issues that have to be solved, use it only for developing or debugging. + +#### Using the BuildRoot + +As explained above, in the instuctions on the website you will find where to specify the version of the code to compile. + +#### Using the ImageBuilder + +The ImageBuiler **will download pre-compiled parts of the OpenWrt releases**, and add the pre-compiled LibreMesh packages, so it is **much faster** than the BuildRoot method (but less practical if you want to develop some new features modifying LibreMesh source code). + +Start an ImageBuilder of your choice, for example ath79-generic if your device is supported within it, use containers for an easier setup: ```shell mkdir ./images/ -docker run -it -v $(pwd)/images:/images/ ghcr.io/openwrt/imagebuilder:ath79-generic-v22.03.4 +docker run -it -v $(pwd)/images:/images/ ghcr.io/openwrt/imagebuilder:ath79-generic-v22.03.5 ``` +If your device is not part of ath79-generic profiles, you can replace it with another - combination. For knowing which target and subtarget is best suited for your router, check out the page about it in the [OpenWrt's Table of Hardware][OpenWrt-ToH]. + Within the container, add the `lime-packages` feed: ```shell @@ -55,11 +77,18 @@ Now create an image of your choice, to see the names of supported profiles run `make info` first. ```shell -make image PROFILE=ubnt_unifi PACKAGES="lime-system lime-proto-babeld" BIN_DIR=/images FILES=files +make image PROFILE=ubnt_unifi PACKAGES="lime-system lime-proto-babeld lime-proto-batadv lime-proto-anygw lime-hwd-openwrt-wan -dnsmasq" BIN_DIR=/images FILES=files ``` Your images should be available outside of the container in the `./images/` folder +##### Possible errors from the ImageBuilder + +If you get a `docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?.` error, means that you don't have Docker's daemon running. Make sure you have `docker` installed and start its daemon with `systemctl start docker.service`. + +If you get a `opkg_download: Check your network settings and connectivity.` error, both check the connectivity and make sure that the firewall rules of your computer allow the container to reach the internet. + + ## Testing LibreMesh has unit tests that help us add new features while keeping maintenance @@ -115,13 +144,13 @@ sponsor](https://opencollective.com/libremesh#sponsor)] [1]: https://libremesh.org/docs/hardware/ -[2]: https://openwrt.org/toh/start +[OpenWrt-ToH]: https://openwrt.org/toh/start [4]: https://libremesh.org/howitworks.html [5]: https://libremesh.org/ [8]: https://www.autistici.org/mailman/listinfo/libremesh [9]: https://libremesh.org/communication.html [10]: https://github.com/libremesh/network-profiles/ [12]: https://opencollective.com/libremesh -[13]: https://libremesh.org/development.html +[development_page]: https://libremesh.org/development.html [lime-example]: https://github.com/libremesh/lime-packages/blob/master/packages/lime-docs/files/www/docs/lime-example.txt diff --git a/packages/shared-state-node_info/Makefile b/packages/shared-state-node_info/Makefile new file mode 100644 index 000000000..5b894b674 --- /dev/null +++ b/packages/shared-state-node_info/Makefile @@ -0,0 +1,23 @@ +# +# Copyright (C) 2023 Javier Jorge +# Copyright (C) 2023 AsociaciĆ³n Civil Altermundi +# This is free software, licensed under the GNU Affero General Public License v3. +# + +include ../../libremesh.mk + +define Package/$(PKG_NAME) + SECTION:=lime + CATEGORY:=LibreMesh + TITLE:=Node information module for shared-state + MAINTAINER:= Javier + DEPENDS:=+lua +luci-lib-jsonc +ubus-lime-utils \ + +lime-system +ubus-lime-location shared-state + PKGARCH:=all +endef + +define Package/$(PKG_NAME)/description + Syncronize node information beween nodes. +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/packages/shared-state-node_info/files/etc/shared-state/publishers/shared-state-publish_node_info b/packages/shared-state-node_info/files/etc/shared-state/publishers/shared-state-publish_node_info new file mode 120000 index 000000000..bd21001b3 --- /dev/null +++ b/packages/shared-state-node_info/files/etc/shared-state/publishers/shared-state-publish_node_info @@ -0,0 +1 @@ +../../../usr/bin/shared-state-publish_node_info \ No newline at end of file diff --git a/packages/shared-state-node_info/files/etc/uci-defaults/shared-state_node_info_cron b/packages/shared-state-node_info/files/etc/uci-defaults/shared-state_node_info_cron new file mode 100755 index 000000000..27132a824 --- /dev/null +++ b/packages/shared-state-node_info/files/etc/uci-defaults/shared-state_node_info_cron @@ -0,0 +1,9 @@ +#!/bin/sh +unique_append() +{ + grep -qF "$1" "$2" || echo "$1" >> "$2" +} + +unique_append \ + '*/5 * * * * ((sleep $(($RANDOM % 120)); shared-state sync node_info &> /dev/null)&)'\ + /etc/crontabs/root diff --git a/packages/shared-state-node_info/files/usr/bin/shared-state-publish_node_info b/packages/shared-state-node_info/files/usr/bin/shared-state-publish_node_info new file mode 100755 index 000000000..309037c6c --- /dev/null +++ b/packages/shared-state-node_info/files/usr/bin/shared-state-publish_node_info @@ -0,0 +1,46 @@ +#!/usr/bin/lua + +--! LibreMesh +--! Copyright (C) 2023 Javier Jorge +--! Copyright (C) 2023 AsociaciĆ³n Civil Altermundi +--! +--! This program is free software: you can redistribute it and/or modify +--! it under the terms of the GNU Affero General Public License as +--! published by the Free Software Foundation, either version 3 of the +--! License, or (at your option) any later version. +--! +--! 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 Affero General Public License for more details. +--! +--! You should have received a copy of the GNU Affero General Public License +--! along with this program. If not, see . + +local JSON = require("luci.jsonc") +local network = require ("lime.network") +local location = require("lime.location") +local utils = require('lime.utils') +local config = require("lime.config") + +local hostname = utils.hostname() +function get_node_info() + local uci = config.get_uci_cursor() + local coords = location.get_node() or location.get_community() or + {lat="FIXME", long="FIXME"} + local fw_version = "no version" + pcall(function () fw_version = utils.release_info()['DISTRIB_RELEASE'] end) + local board = "no board" + pcall(function () board = utils.current_board() end) + local ipv4 = uci:get("network", "lan", "ipaddr") + local ipv6 = uci:get("network", "lan", "ip6addr") + if ipv6 then ipv6 = ipv6:gsub("/.*$", "") end + local uptime = utils.uptime_s() + local macs = network.get_own_macs("*") + return {hostname=hostname, firmware_version=fw_version, board=board, + ipv4=ipv4, ipv6=ipv6,coordinates=coords,macs= macs,uptime = uptime, + device = "Router" } +end + +local result = { [hostname] = get_node_info() } +io.popen("shared-state insert node_info", "w"):write(JSON.stringify(result)) diff --git a/packages/shared-state-node_info/tests/test_shared-state-node_info.lua b/packages/shared-state-node_info/tests/test_shared-state-node_info.lua new file mode 100644 index 000000000..498c419f6 --- /dev/null +++ b/packages/shared-state-node_info/tests/test_shared-state-node_info.lua @@ -0,0 +1,33 @@ +local JSON = require("luci.jsonc") +local test_utils = require('tests.utils') +local utils = require('lime.utils') + +local uci = nil + +package.path = package.path .. ";packages/shared-state-node_info/files/usr/bin/?;;" +require ("shared-state-publish_node_info") + +describe('Tests network_nodes #network_nodes', function () + before_each('', function() + uci = test_utils.setup_test_uci() + stub(utils, "release_info", function () return {DISTRIB_RELEASE='2021.1'} end) + stub(utils, "current_board", function () return 'devboard' end) + end) + + after_each('', function() + test_utils.teardown_test_uci(uci) + end) + + it('a simple test to get node info and assert requiered fields are present', function() + uci = config.get_uci_cursor() + uci:set('network', 'lan', 'interface') + uci:set('network', 'lan', 'ipaddr', '10.5.0.5') + uci:set('network', 'lan', 'ip6addr', 'fd0d:fe46:8ce8::ab:cd00/64') + uci:commit('network') + nodeinfo = get_node_info() + assert.are.equal('devboard', nodeinfo.board) + assert.are.equal('2021.1', nodeinfo.firmware_version) + assert.are.equal('10.5.0.5', nodeinfo.ipv4) + assert.are.equal('fd0d:fe46:8ce8::ab:cd00', nodeinfo.ipv6) + end) +end) \ No newline at end of file diff --git a/packages/shared-state-wifi_links_info/Makefile b/packages/shared-state-wifi_links_info/Makefile index b5306e726..2a5328ff1 100644 --- a/packages/shared-state-wifi_links_info/Makefile +++ b/packages/shared-state-wifi_links_info/Makefile @@ -20,7 +20,7 @@ define Package/$(PKG_NAME) MAINTAINER:= URL:=http://libremesh.org DEPENDS:=+lua +luci-lib-jsonc +ubus-lime-utils \ - +lime-system shared-state + +lime-system +libiwinfo-lua shared-state PKGARCH:=all endef diff --git a/packages/shared-state-wifi_links_info/files/usr/bin/shared-state-publish_wifi_links_info b/packages/shared-state-wifi_links_info/files/usr/bin/shared-state-publish_wifi_links_info index a9a21ab59..fd0668fca 100755 --- a/packages/shared-state-wifi_links_info/files/usr/bin/shared-state-publish_wifi_links_info +++ b/packages/shared-state-wifi_links_info/files/usr/bin/shared-state-publish_wifi_links_info @@ -20,6 +20,8 @@ local JSON = require("luci.jsonc") local node_status = require ("lime.node_status") local network = require ("lime.network") +local iwinfo = require "iwinfo" + function get_wifi_links_info() local stations = node_status.get_stations() @@ -28,9 +30,10 @@ function get_wifi_links_info() macparts = network.get_mac(station.iface) src_macaddr = table.concat(macparts,":") local station_stats = node_status.get_station_stats(station) + local freq = iwinfo.nl80211.frequency(station.iface) table.insert(links, {src_mac=src_macaddr ,dst_mac=station.station_mac, signal=station_stats.signal,chains=station_stats.chains, - rx_rate=station_stats.rx_rate,tx_rate=station_stats.tx_rate } ) + rx_rate=station_stats.rx_rate,tx_rate=station_stats.tx_rate,freq=freq } ) end return links end diff --git a/packages/shared-state-wifi_links_info/tests/test_shared-state_wifi_links_info.lua b/packages/shared-state-wifi_links_info/tests/test_shared-state_wifi_links_info.lua index 1e3dcc75e..84efb0c13 100644 --- a/packages/shared-state-wifi_links_info/tests/test_shared-state_wifi_links_info.lua +++ b/packages/shared-state-wifi_links_info/tests/test_shared-state_wifi_links_info.lua @@ -13,7 +13,8 @@ it('a simple test to get links info and assert requiered fields are present', fu return iwinfo.mocks.iw_station_get_result_wlan1 end) stub(node_status, "get_stations", function () return iwinfo.mocks.get_stations end) - + stub(node_status, "get_stations", function () return iwinfo.mocks.get_stations end) + stub(iwinfo.nl80211,"frequency",function (iface) return 2400 end) stub(network, "get_mac", function (iface) if string.match(iface, "wlan0") then return iwinfo.mocks.wlan0_mesh_mac @@ -28,6 +29,7 @@ it('a simple test to get links info and assert requiered fields are present', fu assert.is.same({-17,-18}, links_info[1].chains) assert.is.equal(-14, links_info[1].signal) assert.is.equal(13000, links_info[1].rx_rate) + assert.is.equal(2400, links_info[1].freq) assert.is.equal("C0:00:00:00:00:00", links_info[1].src_mac) end) diff --git a/packages/shared-state/files/usr/libexec/rpcd/shared-state b/packages/shared-state/files/usr/libexec/rpcd/shared-state index 7f6e6fda9..4fb387a6b 100755 --- a/packages/shared-state/files/usr/libexec/rpcd/shared-state +++ b/packages/shared-state/files/usr/libexec/rpcd/shared-state @@ -19,14 +19,22 @@ local function getFromSharedState(msg) local sharedState = shared_state.SharedState:new(msg.data_type, nixio.syslog) local resultTable = sharedState:get() - utils.printJson(resultTable) + local response ={} + for k,v in pairs(resultTable) do + response[k]=v.data + end + utils.printJson(response) end local function getFromSharedStateMultiWriter(msg) local sharedState = shared_state.SharedStateMultiWriter:new(msg.data_type, nixio.syslog) local resultTable = sharedState:get() - utils.printJson(resultTable) + local response ={} + for k,v in pairs(resultTable) do + response[k]=v.data + end + utils.printJson(response) end local function insertIntoSharedStateMultiWriter(msg) diff --git a/packages/shared-state/tests/test_rpcd_shared-state.lua b/packages/shared-state/tests/test_rpcd_shared-state.lua index a6ee604a8..e118126b1 100644 --- a/packages/shared-state/tests/test_rpcd_shared-state.lua +++ b/packages/shared-state/tests/test_rpcd_shared-state.lua @@ -1,5 +1,6 @@ local testUtils = require "tests.utils" local sharedState = require("shared-state") +local json = require("luci.jsonc") local testFileName = "packages/shared-state/files/usr/libexec/rpcd/shared-state" local sharedStateRpc = testUtils.load_lua_file_as_function(testFileName) @@ -37,9 +38,9 @@ describe('ubus-shared-state tests #ubus-shared-state', function() assert.is.equal(dbA.zig.data, 'zag') local response = rpcdCall(sharedStateRpc, {'call', 'getFromSharedState'}, '{"data_type": "wifi_links_info"}') - assert.is.equal(response.bar.data, 'foo') - assert.is.equal(response.baz.data, 'qux') - assert.is.equal(response.zig.data, 'zag') + assert.is.equal(response.bar, 'foo') + assert.is.equal(response.baz, 'qux') + assert.is.equal(response.zig, 'zag') end) it('test get multiwriter data ', function() @@ -54,12 +55,12 @@ describe('ubus-shared-state tests #ubus-shared-state', function() assert.is.equal('zag', dbA.zig.data) local response = rpcdCall(sharedStateRpc, {'call', 'getFromSharedStateMultiWriter'}, '{"data_type": "A"}') - assert.is.equal(response.bar.data, 'foo') - assert.is.equal(response.baz.data, 'qux') - assert.is.equal(response.zig.data, 'zag') + assert.is.equal(response.bar, 'foo') + assert.is.equal(response.baz, 'qux') + assert.is.equal(response.zig, 'zag') end) - it('test set multiwriter data ', function() + it('test insert multiwriter data ', function() local sharedStateA = sharedState.SharedStateMultiWriter:new('A') sharedStateA:insert({ bar = 'foo', @@ -72,7 +73,28 @@ describe('ubus-shared-state tests #ubus-shared-state', function() local response = rpcdCall(sharedStateRpc, {'call', 'insertIntoSharedStateMultiWriter'}, '{"data_type": "A", "json": {"zig": "newzag"}}') - local dbA = sharedStateA:get() + dbA = sharedStateA:get() + assert.is.equal('foo', dbA.bar.data) + assert.is.equal('newzag', dbA.zig.data) + end) + + it('test insert multiwriter data ', function() + local sharedStateA = sharedState.SharedStateMultiWriter:new('A') + sharedStateA:insert({ + bar = 'foo', + baz = 'qux', + zig = 'zag' + }) + local response = rpcdCall(sharedStateRpc, {'call', + 'getFromSharedStateMultiWriter'}, '{"data_type": "A"}') + assert.is.equal('foo', response.bar) + assert.is.equal('zag', response.zig) + response.zig="newzag" + callargs = '{"data_type": "A", "json": '..json.stringify(response)..'}' + local response = rpcdCall(sharedStateRpc, {'call', + 'insertIntoSharedStateMultiWriter'}, + callargs) + dbA = sharedStateA:get() assert.is.equal('foo', dbA.bar.data) assert.is.equal('newzag', dbA.zig.data) end) @@ -121,7 +143,7 @@ describe('ubus-shared-state tests #ubus-shared-state', function() '{"data_type": "A", "json": {"zig": "zag"}}') response = rpcdCall(sharedStateRpc, {'call', 'getFromSharedStateMultiWriter'}, '{"data_type": "A"}') - assert.is.equal(response.zig.data, 'zag') + assert.is.equal(response.zig, 'zag') response = rpcdCall(sharedStateRpc, {'call', 'insertIntoSharedStateMultiWriter'}, @@ -129,8 +151,8 @@ describe('ubus-shared-state tests #ubus-shared-state', function() response = rpcdCall(sharedStateRpc, {'call', 'getFromSharedStateMultiWriter'}, '{"data_type": "ref_state_wifilinks"}') - assert.is.equal(response.primero.data.bleachTTL, 23) - assert.is.equal(response.primero.data.author, "primero") + assert.is.equal(response.primero.bleachTTL, 23) + assert.is.equal(response.primero.author, "primero") response = rpcdCall(sharedStateRpc, {'call', 'insertIntoSharedStateMultiWriter'},wifiStatusJsonsample27) @@ -139,8 +161,8 @@ describe('ubus-shared-state tests #ubus-shared-state', function() 'getFromSharedStateMultiWriter'}, '{"data_type": "ref_state_wifilinks"}') - assert.is.equal(response.primero.data.bleachTTL, 27) - assert.is.equal(response.primero.data.author, "primero") + assert.is.equal(response.primero.bleachTTL, 27) + assert.is.equal(response.primero.author, "primero") response = rpcdCall(sharedStateRpc, {'call', 'insertIntoSharedStateMultiWriter'}, @@ -148,7 +170,7 @@ describe('ubus-shared-state tests #ubus-shared-state', function() response = rpcdCall(sharedStateRpc, {'call', 'getFromSharedStateMultiWriter'}, '{"data_type": "ref_state_wifilinks"}') - assert.is.equal(response.primero.data.bleachTTL, 23) - assert.is.equal(response.primero.data.author, "primero") + assert.is.equal(response.primero.bleachTTL, 23) + assert.is.equal(response.primero.author, "primero") end) end)