mirror of https://github.com/aredn/aredn.git
919 lines
31 KiB
Lua
Executable File
919 lines
31 KiB
Lua
Executable File
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
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("aredn.hardware")
|
|
require("aredn.http")
|
|
require("aredn.utils")
|
|
aredn.html = require("aredn.html")
|
|
require("uci")
|
|
aredn.info = require("aredn.info")
|
|
|
|
local html = aredn.html
|
|
|
|
local urlprefix
|
|
local target = "unknown"
|
|
function defaultPackageRepos(repo)
|
|
if not urlprefix then
|
|
urlprefix = "http://downloads.arednmesh.org"
|
|
local release = "unknown"
|
|
for line in io.lines("/etc/openwrt_release")
|
|
do
|
|
local m = line:match("DISTRIB_RELEASE='(.*)'")
|
|
if m then
|
|
release = m
|
|
end
|
|
m = line:match("DISTRIB_TARGET='(.*)'")
|
|
if m then
|
|
target = m
|
|
end
|
|
end
|
|
local a, b = release:match("^(%d+)%.(%d+)%.")
|
|
if a and b then
|
|
urlprefix = urlprefix .. "/releases/" .. a .. "/" .. b .. "/" .. release
|
|
else
|
|
-- nightly
|
|
urlprefix = urlprefix .. "/snapshots"
|
|
end
|
|
end
|
|
if repo:match("aredn_core") then
|
|
return urlprefix .. "/targets/" .. target .. "/packages"
|
|
else
|
|
return urlprefix .. "/packages/mips_24kc/" .. repo
|
|
end
|
|
end
|
|
|
|
local settings = {
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].enable",
|
|
type = "boolean",
|
|
desc = "Enable <b>Link Quality Management</b><br><br><small>aredn.@lqm[0].enable</small>",
|
|
default = "1",
|
|
postcallback = "lqm_defaults()",
|
|
needreboot = true
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].margin_snr",
|
|
type = "string",
|
|
desc = "<b>SNR Margin</b> in dB above Min SNR a signal must reach to be re-activated<br><br><small>aredn.@lqm[0].margin_snr</small>",
|
|
default = "1",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].min_distance",
|
|
type = "string",
|
|
desc = "<b>Min Distance</b> in meters beyond which a neighbor RF link is allowed<br><br><small>aredn.@lqm[0].min_distance</small>",
|
|
default = "0",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].auto_distance",
|
|
type = "string",
|
|
desc = "<b>Default Distance</b> in meters to use when actual distance cannot be calculated<br><br><small>aredn.@lqm[0].auto_distance</small>",
|
|
default = "0",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].margin_quality",
|
|
type = "string",
|
|
desc = "<b>Quality Margin</b> percentage increase before neighbor can be re-activated<br><br><small>aredn.@lqm[0].margin_quality</small>",
|
|
default = "1",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].ping_penalty",
|
|
type = "string",
|
|
desc = "<b>Ping Penalty</b> quality percentage to add when neighbor cannot be pinged<br><br><small>aredn.@lqm[0].ping_penalty</small>",
|
|
default = "5",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].user_blocks",
|
|
type = "string",
|
|
desc = "<b>User Blocked</b> comma-separated list of blocked MACs<br><br><small>aredn.@lqm[0].user_blocks</small>",
|
|
default = "",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "Link Quality Settings",
|
|
key = "aredn.@lqm[0].user_allows",
|
|
type = "string",
|
|
desc = "<b>User Allowed</b> comma-separated list of always allowed MACs<br><br><small>aredn.@lqm[0].user_allows</small>",
|
|
default = "",
|
|
condition = "lqm_enabled()"
|
|
},
|
|
{
|
|
category = "WAN Settings",
|
|
key = "aredn.@wan[0].olsrd_gw",
|
|
type = "boolean",
|
|
desc = "<b>Allow other MESH nodes to use my WAN</b> - not recommended and OFF by default<br><br><small>aredn.@wan[0].olsrd_gw</small>",
|
|
default = "0",
|
|
postcallback = "changeWANGW()",
|
|
needreboot = true
|
|
},
|
|
{
|
|
category = "WAN Settings",
|
|
key = "aredn.@wan[0].lan_dhcp_route",
|
|
type = "boolean",
|
|
desc = "<b>Allow my LAN devices to access my WAN</b> - ON by default<br><br><small>aredn.@wan[0].lan_dhcp_route</small>",
|
|
default = "1",
|
|
postcallback = "changeWANGW()",
|
|
needreboot = true
|
|
},
|
|
{
|
|
category = "WAN Settings",
|
|
key = "aredn.@wan[0].lan_dhcp_defaultroute",
|
|
type = "boolean",
|
|
desc = "<b>Provide default route to LAN devices</b> even when WAN access is disabled<br><br><small>aredn.@wan[0].lan_dhcp_defaultroute</small>",
|
|
default = "0",
|
|
postcallback = "changeWANGW()",
|
|
needreboot = true
|
|
},
|
|
{
|
|
category = "WAN Settings",
|
|
key = "aredn.wan.vlanid",
|
|
type = "string",
|
|
desc = "<b>WAN VLAN Number</b> - must be an integer in the range [1,4094]<br><br><small>aredn.wan.vlanid</small>",
|
|
default = "",
|
|
condition = "supportsVLANChange()",
|
|
current = "currentWANVLAN()",
|
|
postcallback = "changeWANVLAN()",
|
|
needreboot = true
|
|
},
|
|
{
|
|
category = "Power Options",
|
|
key = "aredn.@poe[0].passthrough",
|
|
type = "boolean",
|
|
desc = "<b>PoE Passthrough</b> specifies whether PoE power should be enabled (Not all devices have PoE passthrough ports)<br><br><small>aredn.@poe[0].passthrough</small>",
|
|
default = "0",
|
|
condition = "hasPOE()",
|
|
postcallback = "setPOEOutput()"
|
|
},
|
|
{
|
|
category = "Power Options",
|
|
key = "aredn.@usb[0].passthrough",
|
|
type = "boolean",
|
|
desc = "<b>USB Power Passthrough</b> specifies whether USB power should be enabled (Not all devices have USB powered ports)<br><br><small>aredn.@usb[0].passthrough</small>",
|
|
default = "1",
|
|
postcallback = "setUSBOutput()",
|
|
condition = "hasUSB()"
|
|
},
|
|
{
|
|
category = "Tunnel Options",
|
|
key = "aredn.@tunnel[0].maxclients",
|
|
type = "string",
|
|
desc = "<b>Tunnel Maxclients</b> specifies the maximum number of tunnel clients this node can serve; must be an integer in the range [0,100].<br><br><small>aredn.@tunnel[0].maxclients</small>",
|
|
default = "10",
|
|
precallback = "restrictTunnelLimitToValidRange()",
|
|
postcallback = "adjustTunnelInterfaceCount()"
|
|
},
|
|
{
|
|
category = "Tunnel Options",
|
|
key = "aredn.@tunnel[0].maxservers",
|
|
type = "string",
|
|
desc = "<b>Tunnel Maxservers</b> specifies the maximum number of tunnel servers to which this node can connect; must be an integer in the range [0,100].<br><br><small>aredn.@tunnel[0].maxservers</small>",
|
|
default = "10",
|
|
precallback = "restrictTunnelLimitToValidRange()",
|
|
postcallback = "adjustTunnelInterfaceCount()"
|
|
},
|
|
{
|
|
category = "Tunnel Options",
|
|
key = "aredn.@tunnel[0].wanonly",
|
|
type = "boolean",
|
|
desc = "<b>WAN-Only Tunnel</b> prevents tunnel traffic from being routed over the Mesh network itself<br><br><small>aredn.@tunnel[0].wanonly</small>",
|
|
default = "1",
|
|
needreboot= true
|
|
},
|
|
{
|
|
category = "Memory Settings",
|
|
key = "aredn.@meshstatus[0].lowmem",
|
|
type = "string",
|
|
desc = "<b>Low Memory Threshold</b> in KB when the Mesh Status page will be truncated<br><br><small>aredn.@meshstatus[0].lowmem</small>",
|
|
default = "10000"
|
|
},
|
|
{
|
|
category = "Memory Settings",
|
|
key = "aredn.@meshstatus[0].lowroutes",
|
|
type = "string",
|
|
desc = "<b>Low Memory Max Routes</b> is the maximum number of routes shown on the Mesh Status page when low memory is detected<br><br><small>aredn.@meshstatus[0].lowroutes</small>",
|
|
default = "1000"
|
|
},
|
|
{
|
|
category = "Network Tools",
|
|
key = "aredn.olsr.restart",
|
|
type = "none",
|
|
desc = "<b>OLSR Restart</b> will restart OLSR when executed; wait up to 2 or 3 minutes to receive response<br><br><small>aredn.olsr.restart</small>",
|
|
default = "0",
|
|
postcallback = "olsr_restart()"
|
|
},
|
|
{
|
|
category = "Network Tools",
|
|
key = "aredn.@iperf[0].enable",
|
|
type = "boolean",
|
|
desc = "<b>IPERF Enable</b> allows the included iperf3 client/server<br><br><small>aredn.@iperf[0].enable</small>",
|
|
default = "1"
|
|
},
|
|
{
|
|
category = "Map Paths",
|
|
key = "aredn.@map[0].maptiles",
|
|
type = "string",
|
|
desc = "<b>Map Tiles URL</b><br><br><small>aredn.@map[0].maptiles</small>",
|
|
default = "http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg"
|
|
},
|
|
{
|
|
category = "Map Paths",
|
|
key = "aredn.@map[0].leafletcss",
|
|
type = "string",
|
|
desc = "<b>Leaflet.css URL</b><br><br><small>aredn.@map[0].leafletcss</small>",
|
|
default = "http://unpkg.com/leaflet@0.7.7/dist/leaflet.css"
|
|
},
|
|
{
|
|
category = "Map Paths",
|
|
key = "aredn.@map[0].leafletjs",
|
|
type = "string",
|
|
desc = "<b>Leaflet.js URL</b><br><br><small>aredn.@map[0].leafletjs</small>",
|
|
default = "http://unpkg.com/leaflet@0.7.7/dist/leaflet.js"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].firmwarepath",
|
|
type = "string",
|
|
desc = "<b>Firmware Download URL</b><br><br><small>aredn.@downloads[0].firmwarepath</small>",
|
|
default = "http://downloads.arednmesh.org/firmware"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_core",
|
|
type = "string",
|
|
desc = "<b>Core Packages Download URL</b><br><br><small>aredn.@downloads[0].pkgs_core</small>",
|
|
default = defaultPackageRepos('aredn_core'),
|
|
postcallback = "writePackageRepo('core')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_base",
|
|
type = "string",
|
|
desc = "<b>Base Packages URL</b><br><br><small>aredn.@downloads[0].pkgs_base</small>",
|
|
default = defaultPackageRepos('base'),
|
|
postcallback = "writePackageRepo('base')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_arednpackages",
|
|
type = "string",
|
|
desc = "<b>AREDN Packages URL</b><br><br><small>aredn.@downloads[0].pkgs_arednpackages</small>",
|
|
default = defaultPackageRepos('arednpackages'),
|
|
postcallback = "writePackageRepo('arednpackages')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_luci",
|
|
type = "string",
|
|
desc = "<b>Luci Packages URL</b><br><br><small>aredn.@downloads[0].pkgs_luci</small>",
|
|
default = defaultPackageRepos('luci'),
|
|
postcallback = "writePackageRepo('luci')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_packages",
|
|
type = "string",
|
|
desc = "<b>Package Download URL</b> for packages not included in the other sections<br><br><small>aredn.@downloads[0].pkgs_packages</small>",
|
|
default = defaultPackageRepos('packages'),
|
|
postcallback = "writePackageRepo('packages')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_routing",
|
|
type = "string",
|
|
desc = "<b>Routing Packages URL</b><br><br><small>aredn.@downloads[0].pkgs_routing</small>",
|
|
default = defaultPackageRepos('routing'),
|
|
postcallback = "writePackageRepo('routing')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_telephony",
|
|
type = "string",
|
|
desc = "<b>Telephony Packages URL</b><br><br><small>aredn.@downloads[0].pkgs_telephony</small>",
|
|
default = defaultPackageRepos('telephony'),
|
|
postcallback = "writePackageRepo('telephony')"
|
|
},
|
|
{
|
|
category = "Firmware Paths",
|
|
key = "aredn.@downloads[0].pkgs_freifunk",
|
|
type = "string",
|
|
desc = "<b>Freifunk Packages URL</b><br><br><small>aredn.@downloads[0].pkgs_freifunk</small>",
|
|
default = defaultPackageRepos('freifunk'),
|
|
postcallback = "writePackageRepo('freifunk')"
|
|
},
|
|
{
|
|
category = "AREDN Alert Settings",
|
|
key = "aredn.aam.refresh",
|
|
type = "none",
|
|
desc = "<b>Alert Message Refresh</b> - Execute to pull any AREDN Alert messages<br><br><small>aredn.aam.refresh</small>",
|
|
default = "0",
|
|
postcallback = "aam_refresh()"
|
|
},
|
|
{
|
|
category = "AREDN Alert Settings",
|
|
key = "aredn.@alerts[0].localpath",
|
|
type = "string",
|
|
desc = "<b>Alert Message Local URL</b> - location from which local AREDN Alerts can be downloaded<br><br><small>aredn.@alerts[0].localpath</small>",
|
|
default = ""
|
|
},
|
|
{
|
|
category = "AREDN Alert Settings",
|
|
key = "aredn.@alerts[0].pollrate",
|
|
type = "string",
|
|
desc = "<b>Alert Message Pollrate</b> - how many hours to wait between polling for new AREDN Alerts<br><br><small>aredn.@alerts[0].pollrate</small>",
|
|
default = "12",
|
|
needreboot = true
|
|
},
|
|
{
|
|
category = "AREDN Alert Settings",
|
|
key = "aredn.aam.purge",
|
|
type = "none",
|
|
desc = "<b>Alert Message Purge</b> - execute to immediately delete all alerts from this node<br><br><small>aredn.aam.purge</small>",
|
|
default = "",
|
|
postcallback = "alert_purge()"
|
|
}
|
|
}
|
|
|
|
local msgs = {}
|
|
|
|
--
|
|
-- helpers
|
|
--
|
|
function msg(m)
|
|
msgs[#msgs + 1] = m
|
|
end
|
|
|
|
-- uci cursor
|
|
local cursora = uci.cursor()
|
|
local cursorb = uci.cursor("/etc/config.mesh")
|
|
function cursor_set(a, b, c, d)
|
|
if not cursora:get(a, b) and b:match("@(.+)%[0%]") then
|
|
cursora:add(a, b:match("@(.+)%[0%]"))
|
|
end
|
|
cursora:set(a, b, c, d)
|
|
if not cursorb:get(a, b) and b:match("@(.+)%[0%]") then
|
|
cursorb:add(a, b:match("@(.+)%[0%]"))
|
|
end
|
|
cursorb:set(a, b, c, d)
|
|
cursora:commit(a)
|
|
cursorb:commit(a)
|
|
end
|
|
|
|
function cursor_add(a, b, c)
|
|
cursora:set(a, b, c)
|
|
cursorb:set(a, b, c)
|
|
cursora:commit(a)
|
|
cursorb:commit(a)
|
|
end
|
|
|
|
function cursor_delete(a, b)
|
|
cursora:delete(a, b)
|
|
cursorb:delete(a, b)
|
|
cursora:commit(a)
|
|
cursorb:commit(a)
|
|
end
|
|
|
|
function cursor_get(a, b, c)
|
|
return cursora:get(a, b, c)
|
|
end
|
|
|
|
function reboot()
|
|
local node = aredn.info.get_nvram("node")
|
|
if node == "" then
|
|
node = "Node"
|
|
end
|
|
local lanip, _, lanmask = aredn.hardware.get_interface_ip4(aredn.hardware.get_iface_name("lan"))
|
|
local browser = os.getenv("REMOTE_ADDR")
|
|
local browser6 = browser:match("::ffff:([%d%.]+)")
|
|
if browser6 then
|
|
browser = browser6
|
|
end
|
|
local fromlan = false
|
|
local subnet_change = false
|
|
if lanip then
|
|
fromlan = validate_same_subnet(browser, lanip, lanmask)
|
|
if fromlan then
|
|
lanmask = ip_to_decimal(lanmask)
|
|
local cfgip = cursor_get("network", "lan", "ipaddr")
|
|
local cfgmask = ip_to_decimal(cursor_get("network", "lan", "netmask"))
|
|
if lanmask ~= cfgmask or nixio.bit.band(ip_to_decimal(lanip), lanmask) ~= nixio.bit.band(ip_to_decimal(cfgip), cfgmask) then
|
|
subnet_change = true
|
|
end
|
|
end
|
|
end
|
|
http_header()
|
|
if fromlan and subnet_change then
|
|
html.header(node .. " rebooting", true)
|
|
html.print("<body><center>")
|
|
html.print("<h1>" .. node .. " is rebooting</h1><br>")
|
|
html.print("<h3>The LAN subnet has changed. You will need to acquire a new DHCP lease<br>")
|
|
html.print("and reset any name service caches you may be using.</h3><br>")
|
|
html.print("<h3>When the node reboots you get your new DHCP lease and reconnect with<br>")
|
|
html.print("<a href='http://localnode.local.mesh:8080/'>http://localnode.local.mesh:8080/</a><br>or<br>")
|
|
html.print("<a href='http://" .. node .. ".local.mesh:8080/'>http://" .. node .. ".local.mesh:8080/</a></h3>")
|
|
else
|
|
html.header(node .. " rebooting", false)
|
|
html.print("<meta http-equiv='refresh' content='60;url=/cgi-bin/status'>")
|
|
html.print("</head><body><center>")
|
|
html.print("<h1>" .. node .. " is rebooting</h1><br>")
|
|
html.print("<h3>Your browser should return to this node in 60 seconds.</br><br>")
|
|
html.print("If something goes astray you can try to connect with<br><br>")
|
|
html.print("<a href='http://localnode.local.mesh:8080/'>http://localnode.local.mesh:8080/</a><br>")
|
|
if node ~= "Node" then
|
|
html.print("or<br><a href='http://" .. node .. ".local.mesh:8080/'>http://" .. node .. ".local.mesh:8080/</a></h3>")
|
|
end
|
|
end
|
|
html.print("</center></body></html>")
|
|
http_footer()
|
|
os.execute("reboot >/dev/null 2>&1")
|
|
os.exit()
|
|
end
|
|
|
|
-- conditionals
|
|
|
|
function hasPOE()
|
|
return aredn.hardware.has_poe()
|
|
end
|
|
|
|
function hasUSB()
|
|
return aredn.hardware.has_usb()
|
|
end
|
|
|
|
function supportsVLANChange()
|
|
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 == "gl-ar750" then
|
|
return true
|
|
end
|
|
-- Otherwise
|
|
return false
|
|
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 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 restrictTunnelLimitToValidRange()
|
|
newval = tonumber(newval)
|
|
if not newval then
|
|
msg(key .. " must be an interger in the range [0,100]")
|
|
newval = 0
|
|
elseif newval < 0 then
|
|
msg("Lower limit of " .. key .. " is 0")
|
|
newval = 0
|
|
elseif newval > 100 then
|
|
msg("Upper limit of " .. key .. " is 100")
|
|
newval = 100
|
|
end
|
|
end
|
|
|
|
function addTunnelInterface(file, tunnum)
|
|
local section = "tun" .. tunnum
|
|
cursor_add(file, section, "interface")
|
|
cursor_set(file, section, "ifname", section)
|
|
cursor_set(file, section, "proto", "none")
|
|
end
|
|
|
|
function deleteTunnelInterface(file, tunnum)
|
|
local section = "tun" .. tunnum
|
|
cursor_delete(file, section)
|
|
end
|
|
|
|
function adjustTunnelInterfaceCount()
|
|
local tunnel_if_count = 0
|
|
cursora:foreach('network_tun', 'interface', function(s) tunnel_if_count = tunnel_if_count + 1 end)
|
|
local maxclients = cursor_get("aredn", "@tunnel[0]", "maxclients")
|
|
if not maxclients then
|
|
maxclients = 10
|
|
end
|
|
local maxservers = cursor_get("aredn", "@tunnel[0]", "maxservers")
|
|
if not maxservers then
|
|
maxservers = 10
|
|
end
|
|
local needed_if_count = maxclients + maxservers
|
|
if tunnel_if_count ~= needed_if_count then
|
|
for i = tunnel_if_count,needed_if_count-1
|
|
do
|
|
local tunnum = 50 + i
|
|
addTunnelInterface("network_tun", tunnum)
|
|
addTunnelInterface("network", tunnum)
|
|
end
|
|
for i = tunnel_if_count-1,needed_if_count,-1
|
|
do
|
|
local tunnum = 50 + i
|
|
deleteTunnelInterface("network_tun", tunnum)
|
|
deleteTunnelInterface("network", tunnum)
|
|
end
|
|
-- can't clone network because it contains macros; re-edit it instead
|
|
os.execute("sed -i -e '$r /etc/config.mesh/network_tun' -e '/interface.*tun',$d' /etc/config.mesh/network")
|
|
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
|
|
os.execute("/usr/local/bin/node-setup -a mesh")
|
|
end
|
|
|
|
function changeWANGW()
|
|
os.execute("/usr/local/bin/node-setup -a mesh")
|
|
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
|
|
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
|
|
if setting.needreboot then
|
|
io.open("/tmp/reboot-required", "w"):close()
|
|
end
|
|
break
|
|
end
|
|
end
|
|
|
|
-- generate the page
|
|
|
|
http_header()
|
|
html.header(node .. " Advanced Configuration", false)
|
|
html.print([[
|
|
<style>
|
|
th {
|
|
padding:5px;
|
|
}
|
|
.brsm {
|
|
display:block;
|
|
margin-top:0.5em;
|
|
margin-bottom:0.5em;
|
|
}
|
|
#settings tr:hover {
|
|
background-color:gainsboro;
|
|
}
|
|
/* The switch - the box around the slider */
|
|
.switch {
|
|
position: relative;
|
|
display: inline-block;
|
|
vertical-align:middle;
|
|
width: 48px;
|
|
height: 20px;
|
|
}
|
|
|
|
/* Hide default HTML checkbox */
|
|
.switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
/* The slider */
|
|
.slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: #ccc;
|
|
-webkit-transition: .4s;
|
|
transition: .4s;
|
|
}
|
|
|
|
.slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 13px;
|
|
width: 13px;
|
|
left: 4px;
|
|
bottom: 4px;
|
|
background-color: white;
|
|
-webkit-transition: .4s;
|
|
transition: .4s;
|
|
}
|
|
|
|
input:checked + .slider {
|
|
background-color: #2196F3;
|
|
}
|
|
|
|
input:focus + .slider {
|
|
box-shadow: 0 0 1px #2196F3;
|
|
}
|
|
|
|
input:checked + .slider:before {
|
|
-webkit-transform: translateX(26px);
|
|
-ms-transform: translateX(26px);
|
|
transform: translateX(26px);
|
|
}
|
|
|
|
/* Rounded sliders */
|
|
.slider.round {
|
|
border-radius: 34px;
|
|
}
|
|
|
|
.slider.round:before {
|
|
border-radius: 50%;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
function toggleDefault(fname, defval) {
|
|
if(document.getElementById(fname).checked) {
|
|
cval = '1'
|
|
} else {
|
|
cval = '0'
|
|
}
|
|
if(cval != defval) {
|
|
document.getElementById(fname).click();
|
|
}
|
|
return true;
|
|
}
|
|
</script>
|
|
</head>
|
|
]])
|
|
|
|
html.print("<body><center>")
|
|
html.alert_banner();
|
|
html.print("<div style='padding:5px;background-color:#FF0000;color:#FFFFFF;width:650px;'>")
|
|
html.print("<strong>WARNING:</strong> Changing advanced settings can be harmful to the stability, security, and performance of this node and potentially the entire mesh network.<br><strong>You should only continue if you are sure of what you are doing.</strong></div>")
|
|
html.print("<form method=post action=advancedconfig enctype='multipart/form-data'>")
|
|
|
|
-- navbar
|
|
html.print("<hr style='width:800px'>")
|
|
html.print("<table cellpadding=5 border=0 width=840px><tr align=center>")
|
|
html.print("<td width=14%><a href='status'>Node Status</a></td>")
|
|
html.print("<td width=14%><a href='setup'>Basic Setup</a></td>")
|
|
html.print("<td width=14%><a href='ports'>Port Forwarding,<br>DHCP, and Services</a></td>")
|
|
html.print("<td width=14%><a href='vpn'>Tunnel<br>Server</a></td>")
|
|
html.print("<td width=14%><a href='vpnc'>Tunnel<br>Client</a></td>")
|
|
html.print("<td width=14%><a href='admin'>Administration</a></td>")
|
|
html.print("<td width=14%><a href='advancedconfig'>Advanced<br>Configuration</a></td>")
|
|
html.print("</tr></table><hr style='width:800px'>")
|
|
|
|
-- help link and buttons table
|
|
html.print("<table width=800px><tr align=center><td>")
|
|
html.print("<a href='/help.html#advancedconfig' target='_blank'>Help</a>")
|
|
html.print(" <input type=submit name=button_reboot value=Reboot style='font-weight:bold' title='Immediately reboot this node'>")
|
|
html.print(" <input type=submit name=button_firstboot value='Reset to Firstboot' onclick=\"return confirm('All config settings and add-on packages will be lost back to first boot state. Continue?')\" title='Reset this node to the initial/firstboot status and reboot.'>")
|
|
html.print("</td></tr></table>")
|
|
|
|
-- messages table
|
|
html.print("<table width=800px>")
|
|
if nixio.fs.stat("/tmp/reboot-required") then
|
|
html.print("<tr align=center><td width=100%><h3>Reboot is required for changes to take effect</h3></td></tr>")
|
|
end
|
|
|
|
for _, m in ipairs(msgs)
|
|
do
|
|
html.print("<tr align=center><td width=100%><strong>" .. m .. "</strong></td></tr>")
|
|
end
|
|
html.print("</table><br />")
|
|
|
|
-- advanced configuration settings table
|
|
html.print([[
|
|
<table id=settings border=1 style='border-collapse:collapse;' width=800px>
|
|
<thead>
|
|
<tr align=center>
|
|
<th>Setting</th>
|
|
<th>Value</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
]])
|
|
|
|
-- settings
|
|
html.print("<tbody>")
|
|
local prior_category = ""
|
|
|
|
for i, setting in ipairs(settings)
|
|
do
|
|
if not setting.condition or loadstring("return " .. setting.condition)() then
|
|
local sval
|
|
if setting.current then
|
|
sval = loadstring("return " .. setting.current)()
|
|
else
|
|
local a, b, c = setting.key:match("(.+)%.(.+)%.(.*)")
|
|
sval = cursor_get(a, b, c)
|
|
end
|
|
sval = sval and tostring(sval) or ""
|
|
if setting.category ~= prior_category then
|
|
html.print([[<tr align=center style=background-color:lightseagreen><td colspan=3><b>]] .. setting.category .. [[</b></td></tr>]])
|
|
prior_category = setting.category
|
|
end
|
|
html.print("<tr><td width=40%>" .. setting.desc .. "</td>")
|
|
html.print("<td width=45%>")
|
|
if setting.type == "string" then
|
|
html.print("<input type='text' id='field_" .. i .. "' name='newval_" .. i .. "' style='width:100%' value='" .. sval .. "'>")
|
|
elseif setting.type == "boolean" then
|
|
if sval == "" then
|
|
sval = setting.default
|
|
end
|
|
if sval == "1" then
|
|
html.print("OFF<label class='switch'><input type='checkbox' id='field_" .. i .. "' name='newval_" .. i .."' value='1' checked><span class='slider round'></span></label>ON")
|
|
else
|
|
html.print("OFF<label class='switch'><input type='checkbox' id='field_" .. i .. "' name='newval_" .. i .. "' value='1'><span class='slider round'></span></label>ON")
|
|
end
|
|
elseif setting.type == "none" then
|
|
html.print("Click EXECUTE button to trigger this action<input type='hidden' id='field_" .. i .. "' name='newval_" .. i .."' value='" .. sval .."'>")
|
|
end
|
|
html.print("</td>")
|
|
if setting.type ~= "none" then
|
|
html.print("<td align='center' width=15%><span class=brsm /><input type='submit' name='button_save_" .. i .. "' value='Save Setting' /><br><span class=brsm />")
|
|
else
|
|
html.print("<td align='center' width=15%><span style='vertical-align:middle;'><input type='submit' name='button_save_" .. i .. "' value='Execute' /><span>")
|
|
end
|
|
if setting.type == "string" then
|
|
html.print("<input value='Set to Default' type='button' onclick=\"document.getElementById('field_" .. i .. "').value='" .. setting.default .. "';\">")
|
|
elseif setting.type == "boolean" then
|
|
html.print("<input value='Set to Default' type='button' onclick=\"return toggleDefault('field_" .. i .. "', '" .. setting.default .. "' );\">")
|
|
end
|
|
html.print("</td></tr>")
|
|
end
|
|
end
|
|
|
|
html.print("</tbody></table></form></center>")
|
|
html.footer()
|
|
html.print("</body></html>")
|
|
http_footer()
|