#!/usr/bin/lua
--[[
Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021 Tim Wilkinson
Original Perl Copyright (C) 2019 Joe Ayers AE6XE
Original Perl Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet
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® 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.http")
require("aredn.utils")
require("aredn.hardware")
require("uci")
require('luci.http')
local html = require("aredn.html")
require("aredn.info")
local errors = {}
local output = {}
local hidden = {}
-- uci cursor
local cursor = uci.cursor()
-- helpers start
function capture_and_match(cmd, pattern)
local f = io.popen(cmd)
if f then
for line in f:lines()
do
local r = line:match(pattern)
if r then
f:close()
return r
end
end
f:close()
end
end
function err(str)
errors[#errors + 1] = str
end
function out(str)
output[#output + 1] = str
end
function validate_hostname(raw_name, name_type)
local trimmed_name = raw_name:match("^%s*(.-)%s*$")
if trimmed_name == "" then
if name_type == "node" then
err("you must set the node name")
end
-- A missing tactical name is not an error
else
local hostname = trimmed_name:match("^%f[%w]([-%w]+)%f[%W]$") -- RFC 1123 + RFC 952
if not hostname then
err(string.format('"%s" is not a valid %s name; only alphanumerics and internal hyphens are allowed', trimmed_name, name_type))
elseif string.len(hostname) > 63 then
err(string.format('%s name "%s" exceeds 63 characters', name_type, hostname)) -- RFC 2181
else
return hostname
end
end
return ""
end
-- helper end
-- timezones
local tz_db_names = {}
for line in io.lines("/etc/zoneinfo")
do
local name, tz = line:match("^(.*)\t(.*)")
tz_db_names[#tz_db_names + 1] = { name = name, tz = tz }
end
-- online ping
local pingOK = false
if capture_and_match("ping -W1 -c1 8.8.8.8", "1 packets received") then
pingOK = true
end
-- must be global not local
dmz_lan_ip = ""
dmz_lan_mask = ""
lan_gw = ""
wan_ip = ""
wan_mask = ""
wan_gw = ""
passwd1 = ""
passwd2 = ""
wifi_intf = ""
local phycount = aredn.hardware.get_radio_count()
local radio_name = (aredn.hardware.get_radio() or {}).name or ""
local M9model = radio_name:match("M9")
local M3model = radio_name:match("M3")
local M39model = M9model or M3model
-- post_data
local parms = {}
local has_parms = false
if 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
)
parms = request:formvalue()
for _,_ in pairs(parms)
do
has_parms = true
break
end
end
if parms.button_uploaddata then
local si = capture("curl 'http://localnode:8080/cgi-bin/sysinfo.json?hosts=1' 2>/dev/null"):chomp()
-- strip closing }
si = si:sub(1, #si - 1)
-- get olsrd topo information
local topo = capture("curl 'http://localnode:9090/links' 2>/dev/null"):chomp()
-- add topo subdoc and close root doc
local newsi = string.format("%s,\"olsr\": %s}", si, topo)
-- PUT it to the server
local upcurl = os.execute("curl -f -H 'Accept: application/json' -X PUT -d '" .. newsi .. "' http://data.arednmesh.org/sysinfo >/dev/null 2>&1")
if upcurl == 0 then
out("AREDN online map updated")
else
err("ERROR: Cannot update online map.");
end
end
if parms.button_default then
local node = aredn.info.get_nvram("node")
local mac2 = aredn.info.get_nvram("mac2")
local dtdmac = aredn.info.get_nvram("dtdmac")
for line in io.lines("/etc/config.mesh/_setup.default")
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
line = line:gsub("", node):gsub("", mac2):gsub("", dtdmac)
local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$")
_G[k] = v
end
end
else
for k, v in pairs(parms)
do
if k:match("^%w+") then
v = v:gsub("^%s+", ""):gsub("%s+$", "")
_G[k] = v
end
end
if parms.button_reset or not has_parms then
local node = aredn.info.get_nvram("node")
local mac2 = aredn.info.get_nvram("mac2")
local dtdmac = aredn.info.get_nvram("dtdmac")
for line in io.lines("/etc/config.mesh/_setup")
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
line = line:gsub("", node):gsub("", mac2):gsub("", dtdmac)
local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$")
_G[k] = v
end
end
local function h2s(hex)
local s = ""
if hex then
for i = 1,#hex,2
do
local p = hex:sub(i, i+1)
if p:match("[0-9a-f][0-9a-f]") then
s = s .. string.char(tonumber(p, 16))
else
s = s .. p
end
end
end
return s
end
wifi2_key = h2s(wifi2_key)
wifi2_ssid = h2s(wifi2_ssid)
wifi3_key = h2s(wifi3_key)
wifi3_ssid = h2s(wifi3_ssid)
end
end
if not wifi_intf or wifi_intf == "" then
wifi_intf = aredn.hardware.get_iface_name("wifi")
end
local phy = iwinfo.nl80211.phyname(wifi_intf)
-- make sure everything we need is initialized
local d0 = { "lan_dhcp", "olsrd_bridge", "wifi2_enable", "wifi_enable", "wifi3_enable", "dhcp_start", "dhcp_end" }
for _, k in ipairs(d0)
do
if not parms[k] then
parms[k] = "0"
end
if not _G[k] then
_G[k] = "0"
end
end
for _, k in ipairs({ "wifi_channel", "wifi2_channel", "wifi_txpower", "dhcp_start", "dhcp_end" })
do
if _G[k] then
_G[k] = tonumber(_G[k])
end
end
local nodetac
if parms.button_reset or parms.button_default or (not nodetac and not has_parms) then
nodetac = aredn.info.get_nvram("node")
tactical = aredn.info.get_nvram("tactical")
if tactical ~= "" then
nodetac = nodetac .. " / " .. tactical
end
else
nodetac = parms.nodetac
end
-- lan is always static
local lan_proto = "static"
-- enforce direct mode settings
-- (formerly known as dmz mode)
if not dmz_mode then
dmz_mode = cursor:get("aredn", "@dmz[0]", "mode")
end
dmz_mode = tonumber(dmz_mode)
if not dmz_mode or (dmz_mode ~= 0 and dmz_mode < 2) then
dmz_mode = 2
elseif dmz_mode > 5 then
dmz_mode = 5
end
if dmz_mode ~= 0 then
local ipshift = (ip_to_decimal(wifi_ip) * math.pow(2, dmz_mode)) % 0x1000000
local a, b = decimal_to_ip(ipshift):match("(%d+%.%d+%.%d+%.)(%d+)")
dmz_lan_ip = "1" .. a .. (tonumber(b) + 1)
dmz_lan_mask = decimal_to_ip((0xffffffff * math.pow(2, dmz_mode)) % 0x100000000)
local octet = dmz_lan_ip:match("%d+%.%d+%.%d+%.(%d+)")
dmz_dhcp_start = octet + 1
dmz_dhcp_end = dmz_dhcp_start + math.pow(2, dmz_mode) - 4;
parms.dmz_lan_ip = dmz_lan_ip
parms.dmz_lan_mask = dmz_lan_mask
parms.dmz_dhcp_start = dmz_dhcp_start
parms.dmz_dhcp_end = dmz_dhcp_end
else
dmz_dhcp_end = dmz_dhcp_start
parms.dmz_dhcp_end = dmz_dhcp_end
end
parms.dhcp_limit = dhcp_end - dhcp_start + 1
parms.dmz_dhcp_limit = dmz_dhcp_end - dmz_dhcp_start + 1
-- get the active wifi settings on a fresh page load
if not parms.reload and wifi_intf ~= "br-nomesh" then
wifi_txpower = capture_and_match("iwinfo " .. wifi_intf .. " info", "Tx%-Power: (%d+)")
local doesiwoffset = capture_and_match("iwinfo " .. wifi_intf .. " info", "TX power offset: (%d+)")
if wifi_txpower then
wifi_txpower = tonumber(wifi_txpower)
if doesiwoffset then
wifi_txpower = wifi_txpower - tonumber(doesiwoffset)
end
end
end
-- lqm
local lqm_mode = false
if cursor:get("aredn", "@lqm[0]", "enable") == "1" then
lqm_mode = true
end
-- sanitize the active settings
if not wifi_txpower or wifi_txpower > aredn.hardware.wifi_maxpower(wifi_intf, wifi_channel) then
wifi_txpower = aredn.hardware.wifi_maxpower(wifi_intf, wifi_channel)
end
if not wifi_txpower or wifi_txpower < 1 then
wifi_txpower = 1
end
if not wifi_distance then
wifi_distance = 0
end
if tostring(wifi_distance):match("%D") then
wifi_distance = 0
end
-- stuff the sanitized data back into the parms tables
-- so they get saved correctly
parms.wifi_distance = wifi_distance
parms.wifi_txpower = wifi_txpower
-- apply the wifi settings
if (parms.button_apply or parms.button_save) and wifi_enable == "1" then
if not lqm_mode then
if phy then
if wifi_distance == 0 then
os.execute("iw phy " .. phy .. " set distance auto >/dev/null 2>&1")
else
os.execute("iw phy " .. phy .. " set distance " .. wifi_distance .. " >/dev/null 2>&1")
end
end
elseif parms.lqm_min_snr ~= nil then
-- validate values
local lqm_min_snr = tonumber(parms.lqm_min_snr)
local lqm_max_distance = tonumber(parms.lqm_max_distance)
local lqm_distance_unit = parms.lqm_distance_unit or "mile"
local lqm_min_quality = tonumber(parms.lqm_min_quality)
if not lqm_min_snr or lqm_min_snr < 0 or lqm_min_snr > 95 then
err("ERROR: Minimum SNR out of range (0-95)")
end
local distance_scale = 1000
if lqm_distance_unit == "mile" then
distance_scale = 1609.344
if not lqm_max_distance or lqm_max_distance < 0 or lqm_max_distance > 71 then
err("ERROR: Maximum Distance out of range (0-71)")
end
else
if not lqm_max_distance or lqm_max_distance < 0 or lqm_max_distance > 114 then
err("ERROR: Maximum Distance out of range (0-114)")
end
end
if not lqm_min_quality or lqm_min_quality < 0 or lqm_min_quality > 100 then
err("ERROR: Minimum Quality out of range (0-100)")
end
if #errors == 0 then
local cursorb = uci.cursor("/etc/config.mesh")
cursor:set("aredn", "@lqm[0]", "min_snr", parms.lqm_min_snr)
cursorb:set("aredn", "@lqm[0]", "min_snr", parms.lqm_min_snr)
cursor:set("aredn", "@lqm[0]", "max_distance", math.floor(parms.lqm_max_distance * distance_scale))
cursorb:set("aredn", "@lqm[0]", "max_distance", math.floor(parms.lqm_max_distance * distance_scale))
cursor:set("aredn", "@lqm[0]", "min_quality", parms.lqm_min_quality)
cursorb:set("aredn", "@lqm[0]", "min_quality", parms.lqm_min_quality)
cursor:commit("aredn")
cursorb:commit("aredn")
end
end
os.execute("iw dev " .. wifi_intf .. " set txpower fixed " .. wifi_txpower .. "00 >/dev/null 2>&1")
end
if (parms.button_updatelocation or parms.button_save) then
-- process gridsquare
local cursora = uci.cursor();
local cursorb = uci.cursor("/etc/config.mesh")
if (cursora:get("aredn", "@location[0]", "gridsquare") or "") ~= parms.gridsquare then
if parms.gridsquare ~= "" then
if parms.gridsquare:match("^[A-Z][A-Z]%d%d[a-z][a-z]$") then
cursora:set("aredn", "@location[0]", "gridsquare", parms.gridsquare)
cursorb:set("aredn", "@location[0]", "gridsquare", parms.gridsquare)
out("Gridsquare updated.")
else
err("ERROR: Gridsquare format is: 2-uppercase letters, 2-digits, 2-lowercase letters. (AB12cd)")
end
else
cursora:set("aredn", "@location[0]", "gridsquare", "")
cursorb:set("aredn", "@location[0]", "gridsquare", "")
out("Gridsquare purged.")
end
end
-- process lat/lng
if (cursora:get("aredn", "@location[0]", "lat") or "") ~= parms.latitude or (cursora:get("aredn", "@location[0]", "lon") or "") ~= parms.longitude then
if parms.latitude ~= "" and parms.longitude ~= "" then
if parms.latitude:match("^[-+]?%d%d?%.%d+$") and parms.longitude:match("^[-+]?%d%d?%d?%.%d+$") then
if tonumber(parms.latitude) >= -90 and tonumber(parms.latitude) <= 90 and tonumber(parms.longitude) >= -180 and tonumber(parms.longitude) <= 180 then
cursora:set("aredn", "@location[0]", "lat", parms.latitude)
cursorb:set("aredn", "@location[0]", "lat", parms.latitude)
cursora:set("aredn", "@location[0]", "lon", parms.longitude)
cursorb:set("aredn", "@location[0]", "lon", parms.longitude)
out("Lat/lon updated.")
else
err("ERROR: Lat/lon values must be between -90/90 and -180/180, respectively.")
end
else
err("ERROR: Lat/lon format is decimal: (ex. 30.121456 or -95.911154).")
end
else
cursora:set("aredn", "@location[0]", "lat", "")
cursorb:set("aredn", "@location[0]", "lat", "")
cursora:set("aredn", "@location[0]", "lon", "")
cursorb:set("aredn", "@location[0]", "lon", "")
out("Lat/lon purged.")
end
end
-- process antenna, azimuth and elevation
if wifi_enable == "1" then
local antenna = parms.antenna or ""
if (cursora:get("aredn", "@location[0]", "antenna") or "") ~= antenna then
cursora:set("aredn", "@location[0]", "antenna", antenna)
cursorb:set("aredn", "@location[0]", "antenna", antenna)
end
local antenna_aux = parms.antenna_aux or ""
if (cursora:get("aredn", "@location[0]", "antenna_aux") or "") ~= antenna_aux then
cursora:set("aredn", "@location[0]", "antenna_aux", antenna_aux)
cursorb:set("aredn", "@location[0]", "antenna_aux", antenna_aux)
end
if (cursora:get("aredn", "@location[0]", "azimuth") or "") ~= parms.azimuth then
local ant = aredn.hardware.get_current_antenna(wifi_intf)
if ant and ant.beamwidth == 360 then
parms.azimuth = ""
end
ant = aredn.hardware.get_current_antenna_aux(wifi_intf)
if ant and ant.beamwidth == 360 then
parms.azimuth = ""
end
local azimuth = tonumber(parms.azimuth)
if parms.azimuth == "" or (azimuth and azimuth >= 0 and azimuth < 360) then
cursora:set("aredn", "@location[0]", "azimuth", parms.azimuth)
cursorb:set("aredn", "@location[0]", "azimuth", parms.azimuth)
elseif parms.azimuth then
err("ERROR: Azimuth value must be blank or between 0 and 360.")
end
end
if (cursora:get("aredn", "@location[0]", "elevation") or "") ~= parms.elevation then
local elevation = tonumber(parms.elevation)
if parms.elevation == "" or (elevation and elevation >= -180 and elevation <= 180) then
cursora:set("aredn", "@location[0]", "elevation", parms.elevation)
cursorb:set("aredn", "@location[0]", "elevation", parms.elevation)
elseif parms.elevation then
err("ERROR: Elevation value must be blank or between -180 and 180.")
end
end
if (cursora:get("aredn", "@location[0]", "height") or "") ~= parms.height then
local height = tonumber(parms.height)
if parms.height == "" or (height and height >= 0 and height <= 500) then
cursora:set("aredn", "@location[0]", "height", parms.height)
cursorb:set("aredn", "@location[0]", "height", parms.height)
elseif parms.height then
err("ERROR: Height value must be blank or between 0 and 500.")
end
end
end
cursora:commit("aredn")
cursorb:commit("aredn")
cursor = cursora
end
-- retrieve location data
lat = cursor:get("aredn", "@location[0]", "lat")
if not lat then
lat = ""
end
lon = cursor:get("aredn", "@location[0]", "lon")
if not lon then
lon = ""
end
gridsquare = cursor:get("aredn", "@location[0]", "gridsquare")
if not gridsquare then
gridsquare = ""
end
local azimuth = cursor:get("aredn", "@location[0]", "azimuth") or ""
local elevation = cursor:get("aredn", "@location[0]", "elevation") or ""
local height = cursor:get("aredn", "@location[0]", "height") or ""
local antenna = cursor:get("aredn", "@location[0]", "antenna") or ""
local antenna_aux = cursor:get("aredn", "@location[0]", "antenna_aux") or ""
-- retrieve ntp_period
local cm = uci.cursor("/etc/config.mesh")
ntp_period = cm:get("aredn", "@ntp[0]", "period")
if not ntp_period then
ntp_period = "daily"
end
function is_vtun_client()
if nixio.fs.stat("/etc/config/vtun") then
for line in io.lines("/etc/config/vtun") do
if line:match("^config server") then
return true
end
end
end
return false
end
-- validate and save configuration
if parms.button_save then
for _,zone in ipairs(tz_db_names)
do
if zone.name == time_zone_name then
parms.time_zone = zone.tz
break
end
end
if wifi_enable == "1" then
if not validate_netmask(wifi_mask) then
err("invalid Mesh netmask")
elseif not validate_ip_netmask(wifi_ip, wifi_mask) then
err("invalid Mesh IP address")
end
if #wifi_ssid > 32 then
err("invalid Mesh RF SSID")
end
if wifi_intf:match("^br") then
wifi_intf = aredn.hardware.get_board_network_ifname("wifi")
phy = iwinfo.nl80211.phyname(wifi_intf)
local defaultwifi = aredn.hardware.get_default_channel(wifi_intf)
wifi_channel = defaultwifi.channel
wifi_chanbw = tostring(defaultwifi.bandwidth)
end
local valid = false
for _, c in ipairs(aredn.hardware.get_rfchannels(wifi_intf))
do
if c.number == wifi_channel then
valid = true
break
end
end
if not valid then
err("invalid Mesh RF channel")
end
local wifi_country_validated = false
local countries = { "00","HX","AD","AE","AL","AM","AN","AR","AT","AU","AW","AZ","BA","BB","BD","BE","BG","BH","BL","BN","BO","BR","BY","BZ","CA","CH","CL","CN","CO","CR","CY","CZ","DE","DK","DO","DZ","EC","EE","EG","ES","FI","FR","GE","GB","GD","GR","GL","GT","GU","HN","HK","HR","HT","HU","ID","IE","IL","IN","IS","IR","IT","JM","JP","JO","KE","KH","KP","KR","KW","KZ","LB","LI","LK","LT","LU","LV","MC","MA","MO","MK","MT","MY","MX","NL","NO","NP","NZ","OM","PA","PE","PG","PH","PK","PL","PT","PR","QA","RO","RS","RU","RW","SA","SE","SG","SI","SK","SV","SY","TW","TH","TT","TN","TR","UA","US","UY","UZ","VE","VN","YE","ZA","ZW" }
for _,country in ipairs(countries)
do
if country == wifi_country then
wifi_country_validated = true
break
end
end
if not wifi_country_validated then
wifi_country = "00"
err("Invalid country")
end
end
if lan_proto == "static" then
if not validate_netmask(lan_mask) then
err("invalid LAN netmask")
elseif not lan_mask:match("^255%.255%.255%.") then
err("LAN netmask must begin with 255.255.255")
elseif not validate_ip_netmask(lan_ip, lan_mask) then
err("invalid LAN IP address")
else
if lan_dhcp ~= "" then
local start_addr = lan_ip:match("^(.*%.)%d+$") .. dhcp_start
local end_addr = lan_ip:match("^(.*%.)%d+$") .. dhcp_end
if not (validate_ip_netmask(start_addr, lan_mask) and validate_same_subnet(start_addr, lan_ip, lan_mask)) then
err("invalid DHCP start address")
end
if not (validate_ip_netmask(end_addr, lan_mask) and validate_same_subnet(end_addr, lan_ip, lan_mask)) then
err("invalid DHCP end address")
end
if dhcp_start > dhcp_end then
err("invalid DHCP start/end addresses")
end
end
if lan_gw ~= "" and not (validate_ip_netmask(lan_gw, lan_mask) and validate_same_subnet(lan_ip, lan_gw, lan_mask)) then
err("invalid LAN gateway")
end
end
end
if wan_proto == "static" then
if not validate_netmask(wan_mask) then
err("invalid WAN netmask")
elseif not validate_ip_netmask(wan_ip, wan_mask) then
err("invalid WAN IP address")
elseif not (validate_ip_netmask(wan_gw, wan_mask) and validate_same_subnet(wan_ip, wan_gw, wan_mask)) then
err("invalid WAN gateway")
end
end
if not validate_ip(wan_dns1) then
err("invalid WAN DNS 1")
end
if wan_dns2 ~= '' and not validate_ip(wan_dns2) then
err("invaliud WAN DNS 2")
end
if passwd1 ~= "" or passwd2 ~= "" then
if passwd1 ~= passwd2 then
err("passwords do not match")
end
if passwd1:match("#") then
err("passwords cannot contain '#'")
end
if passwd1 == "hsmm" then
err("password must be changed")
end
elseif nixio.fs.stat("/etc/config/unconfigured") then
err("password must be changed during initial configuration")
end
local raw_node, raw_tactical = nodetac:match("^([^/]*)(.*)$")
node = validate_hostname(raw_node, "node")
tactical = raw_tactical ~= "" and validate_hostname(string.sub(raw_tactical, 2), "tactical") or ""
if not validate_fqdn(ntp_server) then
err("invalid ntp server")
end
-- move update-clock to correct location if necessary
if parms.ntp_period == "daily" then
if not nixio.fs.stat("/etc/cron.daily/update-clock") then
if nixio.fs.stat("/etc/cron.hourly/update-clock") then
os.execute("mv /etc/cron.hourly/update-clock /etc/cron.daily/update-clock")
ntp_period = parms.ntp_period
else
err("update-clock script not found")
end
end
elseif parms.ntp_period == "hourly" then
if not nixio.fs.stat("/etc/cron.hourly/update-clock") then
if nixio.fs.stat("/etc/cron.daily/update-clock") then
os.execute("mv /etc/cron.daily/update-clock /etc/cron.hourly/update-clock")
ntp_period = parms.ntp_period
else
err("update-clock script not found")
end
end
else
err("ntp_period not set")
end
if wifi2_enable == "1" then
if #wifi2_ssid > 32 then
err("LAN Access Point SSID musr be 32 or less characters")
end
if #wifi2_key < 8 or #wifi2_key > 64 then
err("LAN Access Point Password must be at least 8 characters, up to 64")
end
if wifi2_key:match("'") or wifi2_ssid:match("'") then
err("The LAN Access Point password and ssid may not contain a single quote character")
end
if wifi2_channel < 30 and wifi2_hwmode == "11a" then
err("Changed to 5GHz Mesh LAN AP, please review channel selection")
end
if wifi2_channel > 30 and wifi2_hwmode == "11g" then
err("Changed to 2GHz Mesh LAN AP, please review channel slection")
end
end
if wifi3_enable == "1" then
if #wifi3_key > 64 then
err("WAN Wifi Client Password must be 64 characters or less")
end
if wifi3_key:match("'") or wifi3_ssid:match("'") then
err("The WAN Wifi Client password and ssid cannot contain a single quote character")
end
end
if phycount > 1 and wifi_enable == "1" and wifi2_enable == "1" and (wifi_channel < 36) == (wifi2_channel < 36) then
err("Mesh RF and LAN Access Point can not both use the same wireless card, review LAN AP settings")
end
if phycount > 1 and wifi_enable == "0" and wifi2_enable == "1" and wifi3_enable == "1" and wifi2_hwmode == wifi3_hwmode then
err("Some settings auto updated to avoid conflicts, please review and save one more time")
end
if wifi_enable == "1" and wifi2_enable == "1" and wifi3_enable == "1" then
err("Can not enable Mesh RF, LAN AP, and WAN Wifi Client with only 2 wireless cards, WAN Wifi Client turned off")
wifi2_enable = "0"
end
if phycount == 1 and wifi_enable == "1" and (wifi2_enable == "1" or wifi3_enable == "1") then
err("Can not enable Mesh RF along with LAN AP or WAN Wifi Client. Only Mesh RF enabled now, please review settings.")
wifi2_enable = "0"
wifi3_enable = "0"
end
if #errors == 0 then
parms.node = node
parms.tactical = tactical
if is_vtun_client() then
local vtun_name
cursor:foreach("vtun", "server",
function(section)
vtun_name = section.node:sub(1, #section.node - (#section.netip + 1) )
end
)
if vtun_name and parms.node and (vtun_name:upper() ~= parms.node:upper()) then
cursor:foreach("vtun", "server",
function(section)
for k,v in pairs(section) do
if k == ".name" then
local servnum = "@" .. v:gsub("%_","%[") .. "]"
local newname = parms.node .. "-" .. section.netip:gsub("%.", "%-")
cursor:set("vtun", servnum, "node", newname)
break
end
end
end
)
cursor:commit("vtun")
write_all("/etc/config.mesh/vtun", read_all("/etc/config/vtun"))
end
end
if nixio.fs.stat("/etc/config/unconfigured") then
io.open("/tmp/unconfigured", "w"):close()
end
local function s2h(str)
local h = ""
for i = 1,#str
do
h = h .. string.format("%02x", string.byte(str, i))
end
return h
end
parms.wifi2_key = s2h(wifi2_key)
parms.wifi2_ssid = s2h(wifi2_ssid)
parms.wifi3_key = s2h(wifi3_key)
parms.wifi3_ssid = s2h(wifi3_ssid)
-- escape and limit description
parms.description_node = parms.description_node:sub(1,210):gsub('"',"""):gsub("'","'"):gsub("<","<"):gsub(">",">")
-- save the wifi interface
parms.wifi_intf = wifi_intf
-- save_setup
local f = io.open("/etc/config.mesh/_setup", "w")
if f then
for k, v in pairs(parms)
do
if k:match("^aprs_") or k:match("^dhcp_") or k:match("^dmz_") or k:match("^lan_") or k:match("^olsrd_") or k:match("^wan_") or k:match("^wifi_") or k:match("^wifi2_") or k:match("^wifi3_") or k:match("^dtdlink_") or k:match("^ntp_") or k:match("^time_") or k:match("^description_") or k:match("^compat_version") then
f:write(k .. " = " .. v .. "\n")
end
end
f:close()
end
-- commit new ntp_period value
local cm = uci.cursor("/etc/config.mesh")
if parms.ntp_period ~= cm:get("aredn", "@ntp[0]", "period") then
cm:set("aredn", "@ntp[0]", "period", parms.ntp_period)
cm:commit("aredn")
end
aredn.info.set_nvram("node", parms.node);
aredn.info.set_nvram("tactical", parms.tactical)
aredn.info.set_nvram("config", parms.config)
if not nixio.fs.stat("/tmp/web/save") then
nixio.fs.mkdir("/tmp/web/save")
end
os.execute("/usr/local/bin/node-setup > /tmp/web/save/node-setup.out 2>&1")
if nixio.fs.stat("/tmp/web/save/node-setup.out", "size") > 0 then
err(read_all("/tmp/web/save/node-setup.out"))
else
remove_all("/tmp/web/save")
-- set password
if passwd1 ~= "" then
local pw = passwd1:gsub("'", "\\'")
local f = io.popen("{ echo '" .. pw .. "'; sleep 1; echo '" .. pw .. "'; } | passwd")
f:read("*a")
f:close()
end
if nixio.fs.stat("/tmp/reboot-required") and not nixio.fs.stat("/tmp/reboot-required/reboot") and not nixio.fs.stat("/tmp/unconfigured") then
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
end
end
if nixio.fs.stat("/tmp/unconfigured") and #errors == 0 then
html.reboot()
end
end
end
remove_all("/tmp/web/save")
if parms.button_reboot then
html.reboot()
end
local desc = cursor:get("system", "@system[0]", "description")
if not desc then
desc = ""
end
local maptiles = cursor:get("aredn", "@map[0]", "maptiles")
if not maptiles then
maptiles = ""
end
local leafletcss = cursor:get("aredn", "@map[0]", "leafletcss")
if not leafletcss then
leafletcss = ""
end
local leafletjs = cursor:get("aredn", "@map[0]", "leafletjs")
if not leafletjs then
leafletjs = ""
end
-- generate page
http_header()
html.header(aredn.info.get_nvram("node") .. " setup", false)
html.print([[
]])
html.print("")
html.print("