#!/usr/bin/lua
--[[
Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021 Tim Wilkinson
Original Perl Copyright (C) 2020 - Darryl Quinn
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
aredn.@lqm[0].enable",
default = "1",
postcallback = "lqm_defaults()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].margin_snr",
type = "string",
desc = "SNR Margin in dB above Min SNR a signal must reach to be re-activated
aredn.@lqm[0].margin_snr",
default = "1",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].min_distance",
type = "string",
desc = "Min Distance in meters beyond which a neighbor RF link is allowed
aredn.@lqm[0].min_distance",
default = "0",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].auto_distance",
type = "string",
desc = "Default Distance in meters to use when actual distance cannot be calculated
aredn.@lqm[0].auto_distance",
default = "0",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].margin_quality",
type = "string",
desc = "Quality Margin percentage increase before neighbor can be re-activated
aredn.@lqm[0].margin_quality",
default = "1",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].ping_penalty",
type = "string",
desc = "Ping Penalty quality percentage to add when neighbor cannot be pinged
aredn.@lqm[0].ping_penalty",
default = "5",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].rts_threshold",
type = "string",
desc = "RTS Threshold in bytes before using RTS/CTS when hidden nodes are detected
aredn.@lqm[0].rts_threshold",
default = "1",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].mtu",
type = "string",
desc = "Maximum packet size in bytes sent over WiFi (256 to 1500)
aredn.@lqm[0].mtu",
default = "1500"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].user_blocks",
type = "string",
desc = "User Blocked comma-separated list of blocked MACs
aredn.@lqm[0].user_blocks",
default = "",
condition = "lqm_enabled()"
},
{
category = "Link Quality Settings",
key = "aredn.@lqm[0].user_allows",
type = "string",
desc = "User Allowed comma-separated list of always allowed MACs
aredn.@lqm[0].user_allows",
default = "",
condition = "lqm_enabled()"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].olsrd_gw",
type = "boolean",
desc = "Allow other MESH nodes to use my WAN - not recommended and OFF by default
aredn.@wan[0].olsrd_gw",
default = "0"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].lan_dhcp_route",
type = "boolean",
desc = "Allow my LAN devices to access my WAN - ON by default
aredn.@wan[0].lan_dhcp_route",
default = "1"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].lan_dhcp_defaultroute",
type = "boolean",
desc = "Provide default route to LAN devices even when WAN access is disabled
aredn.@wan[0].lan_dhcp_defaultroute",
default = "0"
},
{
category = "WAN Settings",
key = "aredn.wan.vlanid",
type = "string",
desc = "WAN VLAN Number - must be an integer in the range [1,4094]
aredn.wan.vlanid",
default = "",
condition = "supportsVLANChange()",
current = "currentWANVLAN()",
postcallback = "changeWANVLAN()"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].web_access",
type = "boolean",
desc = "Enable web access to the node from the WAN interface
aredn.@wan[0].web_access",
default = "1"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].ssh_access",
type = "boolean",
desc = "Enable SSH access to the node from the WAN interface
aredn.@wan[0].ssh_access",
default = "1"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].telnet_access",
type = "boolean",
desc = "Enable TELNET access to the node from the WAN interface
aredn.@wan[0].telnet_access",
default = "1"
},
{
category = "Power Options",
key = "aredn.@poe[0].passthrough",
type = "boolean",
desc = "PoE Passthrough specifies whether PoE power should be enabled (Not all devices have PoE passthrough ports)
aredn.@poe[0].passthrough",
default = "0",
condition = "hasPOE()",
postcallback = "setPOEOutput()"
},
{
category = "Power Options",
key = "aredn.@usb[0].passthrough",
type = "boolean",
desc = "USB Power Passthrough specifies whether USB power should be enabled (Not all devices have USB powered ports)
aredn.@usb[0].passthrough",
default = "1",
postcallback = "setUSBOutput()",
condition = "hasUSB()"
},
{
category = "Tunnel Options",
key = "aredn.@tunnel[0].weight",
type = "string",
desc = "Tunnel Weight specifies the cost of using a tunnel. The higher the number, the less likely a tunnel is used.
aredn.@tunnel[0].weight",
default = "1",
condition = "not isSupernode()"
},
{
category = "Tunnel Options",
key = "aredn.@tunnel[0].wanonly",
type = "boolean",
desc = "WAN-Only Tunnel prevents tunnel traffic from being routed over the Mesh network itself
aredn.@tunnel[0].wanonly",
default = "1"
},
{
category = "Watchdog",
key = "aredn.@watchdog[0].enable",
type = "boolean",
desc = "The Watchdog will reboot the node if it stops operating correctly
aredn.@watchdog[0].enable",
default = "0"
},
{
category = "Watchdog",
key = "aredn.@watchdog[0].ping_addresses",
type = "string",
desc = "Watchdog IP addresses is a whitespace seperated list of IP addresses, one of which should always be pingable
aredn.@watchdog[0].ping_addresses",
default = ""
},
{
category = "Watchdog",
key = "aredn.@watchdog[0].daily",
type = "string",
desc = "Daily Watchdog hour is the hour every day (0-23) to automatically reboot the node
aredn.@watchdog[0].daily",
default = ""
},
{
category = "Memory Settings",
key = "aredn.@meshstatus[0].lowmem",
type = "string",
desc = "Low Memory Threshold in KB when the Mesh Status page will be truncated
aredn.@meshstatus[0].lowmem",
default = "10000"
},
{
category = "Memory Settings",
key = "aredn.@meshstatus[0].lowroutes",
type = "string",
desc = "Low Memory Max Routes is the maximum number of routes shown on the Mesh Status page when low memory is detected
aredn.@meshstatus[0].lowroutes",
default = "1000"
},
{
category = "Supernode Settings",
key = "aredn.@supernode[0].support",
type = "boolean",
desc = "Use any Supernodes found on the mesh
aredn.@supernode[0].support",
default = "1"
},
{
category = "Network Tools",
key = "aredn.olsr.restart",
type = "none",
desc = "OLSR Restart will restart OLSR when executed; wait up to 2 or 3 minutes to receive response
aredn.olsr.restart",
default = "0",
postcallback = "olsr_restart()"
},
{
category = "Network Tools",
key = "aredn.@iperf[0].enable",
type = "boolean",
desc = "IPERF Enable allows the included iperf3 client/server
aredn.@iperf[0].enable",
default = "1"
},
{
category = "Remote Logging",
key = "aredn.@remotelog[0].url",
type = "string",
desc = "Remote logging URL for the remote syslog machine. Must be formatted as protocol://ipaddress:port
aredn.@remotelog[0].url",
default = "",
precallback = "validate_rsyslog()"
},
{
category = "Map Paths",
key = "aredn.@location[0].map",
type = "string",
desc = "Map URL
aredn.@location[0].maps",
default = "https://worldmap.arednmesh.org/#12/(lat)/(lon)"
},
{
category = "Firmware",
key = "aredn.@downloads[0].firmware_aredn",
type = "string",
desc = "Firmware Download URL
aredn.@downloads[0].firmware_aredn",
default = "http://downloads.arednmesh.org"
},
{
category = "Firmware",
key = "aredn.@downloads[0].packages_default",
type = "string",
desc = "Packages Download URL
aredn.@downloads[0].packages_default",
default = "http://downloads.arednmesh.org"
},
{
category = "Firmware",
key = "aredn.firmware.dangerous_upgrade",
type = "boolean",
desc = "Dangerous Upgrade Disables all safety checks usually applied when upgrading firmware
aredn.firmware.dangerous_upgrade",
default = "0",
current = "current_force_upgrade()",
postcallback = "update_force_upgrade()"
},
{
category = "AREDN Alert Settings",
key = "aredn.aam.refresh",
type = "none",
desc = "Alert Message Refresh - Execute to pull any AREDN Alert messages
aredn.aam.refresh",
default = "0",
postcallback = "aam_refresh()"
},
{
category = "AREDN Alert Settings",
key = "aredn.@alerts[0].localpath",
type = "string",
desc = "Alert Message Local URL - location from which local AREDN Alerts can be downloaded
aredn.@alerts[0].localpath",
default = ""
},
{
category = "AREDN Alert Settings",
key = "aredn.@alerts[0].groups",
type = "string",
desc = "Alert Message Groups - comma seperated list of group names to check for alert messages
aredn.@alerts[0].groups",
default = ""
},
{
category = "AREDN Alert Settings",
key = "aredn.@alerts[0].pollrate",
type = "string",
desc = "Alert Message Pollrate - how many hours to wait between polling for new AREDN Alerts
aredn.@alerts[0].pollrate",
default = "1"
},
{
category = "AREDN Alert Settings",
key = "aredn.aam.purge",
type = "none",
desc = "Alert Message Purge - execute to immediately delete all alerts from this node
aredn.aam.purge",
default = "",
postcallback = "alert_purge()"
}
}
local msgs = {}
--
-- helpers
--
function msg(m)
msgs[#msgs + 1] = m
end
-- uci cursor
local cursor = uci.cursor("/etc/config.mesh")
function cursor_set(a, b, c, d)
if not cursor:get(a, b) and b:match("@(.+)%[0%]") then
cursor:add(a, b:match("@(.+)%[0%]"))
end
cursor:set(a, b, c, d)
cursor:commit(a)
end
function cursor_get(a, b, c)
return cursor:get(a, b, c)
end
-- conditionals
function hasPOE()
return aredn.hardware.has_poe()
end
function hasUSB()
return aredn.hardware.has_usb()
end
function supportsVLANChange()
-- If we support advanced networking, we dont provide this option here
local board = aredn.hardware.get_board_type()
if board == "mikrotik,hap-ac2" or board == "mikrotik,hap-ac3" then
return false
end
local stat = nixio.fs.stat("/etc/aredn_include/swconfig")
-- We always support VLAN changing on devices without switches
if not (stat and stat.size > 0) then
return true
end
-- We also support VLAN changing on hAP, A750 and AR150 as WAN is on it's own ethernet port
local type = aredn.hardware.get_type()
if type == "rb-952ui-5ac2nd" or type == "routerboard-952ui-5ac2nd" or type == "gl-ar750" then
return true
end
-- Otherwise
return false
end
function isSupernode()
return cursor_get("aredn", "@supernode[0]", "enable") == "1"
end
-- callbacks
local newval
local key
function setPOEOutput()
if not newval then
newval = 0
end
os.execute("/usr/local/bin/poe_passthrough " .. newval)
end
function setUSBOutput()
if not newval then
newval = 0
end
os.execute("/usr/local/bin/usb_passthrough " .. newval)
end
function olsr_restart()
os.execute("/etc/init.d/olsrd restart")
end
function aam_refresh()
os.execute("/usr/local/bin/aredn_message.sh")
end
function alert_purge()
os.remove("/tmp/aredn_message")
os.remove("/tmp/local_message")
end
function lqm_enabled()
return cursor_get("aredn", "@lqm[0]", "enable") == "1"
end
function lqm_defaults()
cursor_set("aredn", "@lqm[0]", "min_snr", "15")
cursor_set("aredn", "@lqm[0]", "margin_snr", "1")
cursor_set("aredn", "@lqm[0]", "min_distance", "0")
cursor_set("aredn", "@lqm[0]", "auto_distance", "0")
cursor_set("aredn", "@lqm[0]", "max_distance", "80467")
cursor_set("aredn", "@lqm[0]", "min_quality", "50")
cursor_set("aredn", "@lqm[0]", "ping_penalty", "5")
cursor_set("aredn", "@lqm[0]", "margin_quality", "1")
end
function current_force_upgrade()
return nixio.fs.stat("/tmp/force-upgrade-this-is-dangerous") and 1 or 0
end
function update_force_upgrade()
if not newval or newval ~= "1" then
nixio.fs.remove("/tmp/force-upgrade-this-is-dangerous")
else
io.open("/tmp/force-upgrade-this-is-dangerous", "w+"):close()
end
end
function writePackageRepo(repo)
local uciurl = cursor_get("aredn", "@downloads[0]", "pkgs_" .. repo)
local disturl = capture("grep aredn_" .. repo .. " /etc/opkg/distfeeds.conf | cut -d' ' -f3")
if uciurl and disturl ~= "" then
os.execute("sed -i 's|" .. disturl:chomp() .. "|" .. uciurl:chomp() .. "|g' /etc/opkg/distfeeds.conf")
end
end
function currentWANVLAN()
for line in io.lines("/etc/config.mesh/_setup")
do
local vlan = line:match("^wan_intf = %w+%.(%d+)")
if vlan then
return vlan
end
end
local vlan = aredn.hardware.get_board_network_ifname("wan"):match("^%w+%.(%d+)")
if vlan then
return vlan
end
return ""
end
function changeWANVLAN()
local lines = {}
for line in io.lines("/etc/config.mesh/_setup")
do
if not line:match("^wan_intf = ") then
lines[#lines + 1] = line
end
end
if newval ~= "" then
local wan_intf = ""
for dev in aredn.hardware.get_board_network_ifname("wan"):gmatch("%S+")
do
wan_intf = wan_intf .. " " .. dev:match("^([^%.]+)") .. "." .. newval
end
if wan_intf ~= "" then
lines[#lines + 1] = "wan_intf =" .. wan_intf
end
end
local f = io.open("/etc/config.mesh/_setup", "w")
if f then
for _, line in ipairs(lines)
do
f:write(line .. "\n")
end
f:close()
end
end
function validate_rsyslog()
if newval ~= "" then
local proto, ip, port = newval:match("^(.+)://(%d+%.%d+%.%d+%.%d+):(%d+)$")
if not proto or not (proto == "tcp" or proto == "udp") then
msg("Badly formatted remote logging URL")
newval = ""
end
end
end
-- read_postdata
local parms = {}
if os.getenv("REQUEST_METHOD") == "POST" then
require('luci.http')
local request = luci.http.Request(nixio.getenv(),
function()
local v = io.read(1024)
if not v then
io.close()
end
return v
end
)
parms = request:formvalue()
end
if parms.button_firstboot then
os.execute("firstboot -y")
end
if parms.button_firstboot or parms.button_reboot then
html.reboot()
end
local node = aredn.info.get_nvram("node")
for i, setting in ipairs(settings)
do
if parms["button_save_" .. i] then
newval = parms["newval_" .. i]
if not newval then
newval = ""
else
newval = newval:gsub("^%s+", ""):gsub("%s+$", "")
end
if setting.type == "boolean" then
if newval == "1" or newval == "true" then
newval = "1"
else
newval = "0"
end
end
key = setting.key
if setting.precallback then
loadstring(setting.precallback)()
end
local a, b, c = setting.key:match("(.+)%.(.+)%.(.*)")
cursor_set(a, b, c, newval)
msg("Changed " .. key)
if setting.postcallback then
loadstring(setting.postcallback)()
end
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
break
end
end
-- generate the page
http_header()
html.header(node .. " Advanced Configuration", false)
html.print([[
]])
html.print("