#!/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("
") html.alert_banner() html.print("
\n") -- navbar html.navbar_admin("setup") html.print("") -- control buttons html.print([[]]) if #output > 0 then html.print("") end if #errors > 0 then html.print("") html.print("") elseif parms.button_save then html.print("") end if #errors == 0 and nixio.fs.stat("/tmp/reboot-required") then html.print("") end -- node name and type, password html.print([[
Help          
 
") html.print("
    ") for _,o in ipairs(output) do html.print("
  • " .. o .. "
  • ") end html.print("
") html.print("
Configuration NOT saved!
") html.print("
    ") for _,e in ipairs(errors) do html.print("
  • " .. e .. "
  • ") end html.print("
") html.print("
") html.print("Configuration saved.

") html.print("

Reboot is required for changes to take effect

]]) html.print([[ ]]) hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" html.print([[
Node Name Password
Node Description (optional) Verify Password

") -- lan settings html.print([[ ") -- wan settings html.print("
]]) -- reset wifi channel/bandwidth to default if phycount > 0 and (nixio.fs.stat("/etc/config/unconfigured") or parms.button_reset) then local defaultwifi = aredn.hardware.get_default_channel(wifi_intf) wifi_channel = defaultwifi.channel wifi_chanbw = tostring(defaultwifi.bandwidth) end -- mesh rf settings html.print("") if phycount == 1 then html.print("") else html.print("") end hidden[#hidden + 1] = "" -- add enable/disable if phycount > 0 then html.print("") if phycount > 1 and wifi_enable == "1" then local defaultwifi = aredn.hardware.get_default_channel(wifi_intf) local alt_wifi = (not phy or phy == "phy0") and "wlan1" or "wlan0" html.print("") end end html.print("") if wifi_enable == "1" then html.print("") hidden[#hidden + 1] = "" html.print("") html.print("") hidden[#hidden + 1] = "" if lqm_mode then html.print("") else html.print("") end html.print("") if lqm_mode then local lqm_max_distance = tonumber(cursor:get("aredn", "@lqm[0]", "max_distance")) or 114750 local lqm_min_snr = cursor:get("aredn", "@lqm[0]", "min_snr") or "15" local lqm_min_quality = cursor:get("aredn", "@lqm[0]", "min_quality") or "50" html.print([[ ]]) html.print("") html.print("") else html.print("") local wifi_distance = math.floor(tonumber(wifi_distance)) local wifi_distance_disp_km = math.floor(wifi_distance / 1000) local wifi_distance_disp_miles = string.format("%.2f", wifi_distance_disp_km * 0.621371192) html.print("") end html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("
Mesh RF (" .. (M3model and "3GHz" or M9model and "900MHz" or wifi_channel < 36 and "2GHz" or "5GHz") .. ")
Mesh
Enable
Band
IP Address
Netmask
SSID-" .. wifi_chanbw .. "-v3
Channel  
Channel Width

Power & Link Quality

Power & Distance
Tx Power  
Max Distance   
Min SNR dB
Min Quality %
Distance to
FARTHEST Neighbor

'0' is auto

 mi  
") html.print(" km
") html.print(" m
") html.print("
") html.print("") html.print("
") hidden[#hidden + 1] = "" if dmz_mode ~= 0 then html.print("") hidden[#hidden + 1] = "" html.print("") hidden[#hidden + 1] = "" html.print("") html.print("") hidden[#hidden + 1] = "" html.print("") hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" else html.print("") html.print("") if wan_proto == "disabled" then html.print("") else hidden[#hidden + 1] = "" end html.print("") html.print("") html.print("") hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end local APokay = nixio.fs.stat("/usr/sbin/wpad") if APokay and ((phycount > 1 and (wifi_enable ~= "1" or wifi3_enable ~= "1")) or (phycount == 1 and wifi_enable ~= "1" and wifi3_enable ~= "1") and not M39model) then html.print("") -- lan ap shows as an option -- determine hardware options and set band ahd channels accordingly if phycount == 1 then chan = aredn.hardware.get_rfchannels("wlan0") if chan[1].frequency < 3000 then wifi2_hwmode = "11g" if wifi2_channel > 14 then wifi2_channel = 1 end else wifi2_hwmode = "11a" if wifi2_channel < 36 then wifi2_channel = 36 end end else -- 2 band device if wifi_enable == "1" then local alt_wifi = (not phy or phy == "phy0") and "wlan1" or "wlan0" chan = aredn.hardware.get_rfchannels(alt_wifi) if #chan == 0 then -- Hardware device can crash after switching - so at least make the page render until a reboot chan[1] = { frequency = 2412, number = 1, label = nil } end if chan[1].frequency < 3000 then wifi2_hwmode = "11g" if wifi2_channel > 14 then wifi2_channel = 1 end else wifi2_hwmode = "11a" if wifi2_channel < 36 then wifi2_channel = 36 end end else if wifi2_enable ~= "1" and wifi3_enable == "1" and wifi3_hwmode == "11a" then wifi2_hwmode = "11g" end if wifi2_enable ~= "1" and wifi3_enable == "1" and wifi3_hwmode == "11g" then wifi2_hwmode = "11a" end local chan0 = aredn.hardware.get_rfchannels("wlan0") local chan1 = aredn.hardware.get_rfchannels("wlan1") if wifi2_hwmode == "11a" then if #chan0 >= 1 and chan0[1].frequency > 3000 then chan = chan0 else chan = chan1 end if wifi2_channel < 36 then wifi2_channel = 36 end else if #chan0 >= 1 and chan0[1].frequency < 3000 then chan = chan0 else chan = chan1 end if wifi2_channel > 14 then wifi2_channel = 1 end end end end html.print("") if phycount > 1 then if wifi_enable ~= "1" then html.print("") else html.print("") hidden[#hidden + 1] = "" end else hidden[#hidden + 1] = "" end html.print("") html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("
LAN
LAN Mode   
IP Address" .. dmz_lan_ip .."
Netmask" .. dmz_lan_mask .. "
DHCP Server
DHCP Start" .. dmz_dhcp_start .. "
DHCP End" .. dmz_dhcp_end .. "
IP Address
Netmask
Gateway
DHCP Server
DHCP Start
DHCP End

LAN Access Point
Enable  
AP band
AP band" .. (wifi2_hwmode == "11g" and "2GHz" or "5GHz") .. "
SSID
Channel
Encryption
Password
") if wan_proto == "static" then html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("") html.print("") -- wan wifi client if APokay and ((phycount > 1 and (wifi_enable ~= "1" or wifi2_enable ~= "1")) or (phycount == 1 and wifi_enable ~= "1" and wifi2_enable ~= "1")) and not M39model then -- wifi client shows as an option -- determine hardware options and set band accordingly html.print("") if phycount == 1 then local rc3 = os.execute("iw phy phy0 info | grep -q '5180 MHz' > /dev/null") if rc3 ~= 0 then wifi3_hwmode = "11g" else wifi3_hwmode = "11a" end else -- 2 band if wifi_enable == "1" then local defaultwifi = aredn.hardware.get_default_channel(wifi_intf) if defaultwifi.channel == 149 then wifi3_hwmode = "11g" else wifi3_hwmode = "11a" end else if wifi2_hwmode == "11g" and wifi2_enable == "1" then wifi3_hwmode = "11a" end if wifi2_hwmode == "11a" and wifi2_enable == "1" then wifi3_hwmode = "11g" end end end local connected = "/notdone.png" local cmessage = "Not connected." local wanintf = aredn.hardware.get_iface_name("wan") local essid = capture_and_match("iwinfo " .. wanintf .. " info", 'ESSID: "(.+)"') if essid == wifi3_ssid then if pingOK then connected = "/donedone.png" cmessage = "Connected to Wifi and Internet." else connected = "/done.png" cmessage = "Connected to Wifi. Not connected to Internet." end end html.print("") html.print("") if wifi_enable ~= "1" and wifi2_enable ~= "1" and phycount > 1 then html.print("") else hidden[#hidden + 1] = "" end html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end -- end wan wifi client html.print("
WAN
Protocol
IP Address
Netmask
Gateway
DNS 1
DNS 2

WAN Wifi Client 
Enable  
WAN Wifi Client band
SSID
Password

") -- Location and antenna settings html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") if wifi_enable == "1" then html.print("") html.print("") local ants = aredn.hardware.get_antennas(wifi_intf) local antsx = aredn.hardware.get_antennas_aux(wifi_intf) local changeh = true local changea = true if ants and #ants == 1 then changea = false antenna = ants[1].description if ants[1].beamwidth == 360 then changeh = false end end if not changeh then html.print("") else html.print("") end if not changea then html.print("") elseif ants then html.print("") end if not changeh then html.print("") else html.print("") end if antsx then html.print("") end html.print("") end html.print("") html.print("") html.print("") if ntp_period == "daily" or not ntp_period then html.print("
Location Settings

Latitude") local locdisabled = pingOK and "" or "disabled" html.print(" ") html.print("") html.print("  ") html.print(" ") html.print("
LongitudeGrid Square
Antenna Settings (optional)

Azimuth-
Azimuth °Antenna Antenna
Elevation-
Elevation °Aux Antenna
Height m

Timezone NTP Server   NTP Updates  
") elseif ntp_period == "hourly" then html.print("NTP Updates   ") end hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" if wan_intf then hidden[#hidden + 1] = "" end for _,hid in ipairs(hidden) do html.print(hid) end html.print("
") html.print([[ ]]) html.footer() html.print("") http_footer()