aredn/files/usr/local/bin/node-setup

562 lines
20 KiB
Lua
Executable File

#! /usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021 Tim Wilkinson
Orignal Perl Copyright (C) 2015 Conrad Lara
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.utils")
local aredn_info = require('aredn.info')
require("aredn.hardware")
-- helpers start
function is_null(v)
if not v or v == "" or v == 0 or v == "0" then
return true
else
return false
end
end
-- helpers end
-- validate args
local auto = false
local do_basic = true
local config = nil
for i, a in ipairs(arg)
do
if a == "-a" then
auto = true
elseif a == "-p" then
do_basic = false
elseif a[1] == '-' then
break
else
config = a
break
end
end
if not config then
print "usage: node-setup [-a] [-p] <configname>"
print "-a: automatic mode - don't ask any questions"
print "-p: only process port forwarding and dhcp settings"
return -1
end
if not (config == "mesh" and nixio.fs.access("/etc/config.mesh/_setup", "r")) then
print (string.format("'%s' is not a valid configuration", config))
return -1
end
local lanintf = aredn.hardware.get_board_network_ifname("lan")
local node = aredn_info.get_nvram("node")
local tactical = aredn_info.get_nvram("tactical")
local mac2 = mac_to_ip(aredn.hardware.get_interface_mac(aredn.hardware.get_iface_name("wifi")), 0)
local dtdmac = mac_to_ip(aredn.hardware.get_interface_mac(lanintf:match("^(%S+)")), 0) -- *not* based of dtdlink
local deleteme = {}
local cfg = {
lan_intf = lanintf,
wan_intf = aredn.hardware.get_board_network_ifname("wan"),
dtdlink_intf = aredn.hardware.get_bridge_iface_names('dtdlink')
}
if not auto then
-- Is this used now?
print "Non-auto mode no longer supported."
return -1
end
-- load the verify the selected configuration
for line in io.lines("/etc/config.mesh/_setup")
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
line = line:gsub("<NODE>", node):gsub("<MAC2>", mac2):gsub("<DTDMAC>", dtdmac)
local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$")
cfg[k] = v
end
end
if cfg.wifi_enable == "1" then
cfg.wifi_intf = aredn.hardware.get_board_network_ifname("wifi"):match("^(%S+)")
else
cfg.wifi_intf = lanintf:match("^([^%.%s]+).*$") .. ".3975"
end
-- delete some config lines if necessary
if cfg.wan_proto == "dhcp" then
deleteme.wan_ip = true
deleteme.wan_gw = true
deleteme.wan_mask = true
end
if not is_null(cfg.dmz_mode) or cfg.wan_proto ~= "disabled" then
deleteme.lan_gw = true
end
-- lan_dhcp sense is inverted in the dhcp config file
-- and it is a checkbox so it may not be defined - this fixes that
if cfg.lan_dhcp == "1" then
cfg.lan_dhcp = 0
else
cfg.lan_dhcp = 1
end
-- verify that we have all the variables we need
for file in nixio.fs.glob("/etc/config.mesh/*")
do
for line in io.lines(file)
do
if line:match("^[^#]") then
for parm in line:gmatch("<([^%s]*)>")
do
if parm:upper() == parm then
-- nvram variable
if aredn_info.get_nvram(parm:lower()) == "" then
print ("nv parameter '" .. parm .. "' in file '" .. file .. "' does not exist")
return -1
end
else
if not cfg[parm] and not deleteme[parm] then
print ("parameter '" .. parm .. "' in file '" .. file .. "' does not exist")
return -1
end
end
end
end
end
end
-- sensible dmz_mode default
if is_null(cfg.dmz_mode) then
cfg.dmz_mode = "0"
end
-- switch to dmz values if needed
if not is_null(cfg.dmz_mode) then
cfg.lan_ip = cfg.dmz_lan_ip
cfg.lan_mask = cfg.dmz_lan_mask
cfg.dhcp_start = cfg.dmz_dhcp_start
cfg.dhcp_end = cfg.dmz_dhcp_end
cfg.dhcp_limit = cfg.dmz_dhcp_limit
end
-- select ports and dhcp files based on mode
local portfile = "/etc/config.mesh/_setup.ports"
local dhcpfile = "/etc/config.mesh/_setup.dhcp"
local aliasfile = "/etc/config.mesh/aliases"
local servfile = "/etc/config.mesh/_setup.services"
if is_null(cfg.dmz_mode) then
portfile = portfile .. ".nat"
dhcpfile = dhcpfile .. ".nat"
aliasfile = aliasfile .. ".nat"
servfile = servfile .. ".nat"
else
portfile = portfile .. ".dmz"
dhcpfile = dhcpfile .. ".dmz"
aliasfile = aliasfile .. ".dmz"
servfile = servfile .. ".dmz"
end
-- check for old aliases file, copy it to .dmz and create symlink
-- just in case anyone is already using the file for some script or something
if not nixio.fs.readlink("/etc/config.mesh/aliases") then
if nixio.fs.stat("/etc/config.mesh/aliases") then
filecopy("/etc/config.mesh/aliases", "/etc/config.mesh/aliases.dmz")
os.remove("/etc/config.mesh/aliases")
else
io.open("/etc/config.mesh/aliases.dmz", "a"):close()
end
nixio.fs.symlink("aliases.dmz", "/etc/config.mesh/aliases")
end
-- basic configuration
if do_basic then
-- support wan vlan override
if not nixio.fs.stat("/etc/aredn_include/wan.network.user") then
local ports = ""
for _, port in ipairs(cfg.wan_intf:split(" "))
do
ports = ports .. " list ports '" .. port .. "'\n"
end
write_all("/etc/aredn_include/wan.network.config", ports)
end
remove_all("/tmp/new_config")
nixio.fs.mkdir("/tmp/new_config")
for file in nixio.fs.glob("/etc/config.mesh/*")
do
local bfile = nixio.fs.basename(file)
if not (bfile:match("^_setup") or bfile:match("^firewall.user") or bfile:match("^olsrd")) then
local f = io.open("/tmp/new_config/" .. bfile, "w")
if f then
for line in io.lines(file)
do
local out = true
local inc = line:match("^include%s+(.*)%s*")
if inc then
if nixio.fs.stat(inc) then
for iline in io.lines(inc)
do
f:write(iline .. "\n")
end
end
out = false
elseif line:match("^[^#]") then
for parm in line:gmatch("<([^%s]*)>")
do
if deleteme[parm] then
out = false
elseif parm == "NODE" then
line = line:gsub("<NODE>", node)
elseif parm == "MAC2" then
line = line:gsub("<MAC2>", mac2)
elseif parm == "DTDMAC" then
line = line:gsub("<DTDMAC>", dtdmac)
else
line = line:gsub("<" .. parm .. ">", cfg[parm])
end
end
end
if out then
f:write(line .. "\n")
end
end
f:close()
end
end
end
-- make it official
for file in nixio.fs.glob("/etc/config/*")
do
nixio.fs.remove(file)
end
for file in nixio.fs.glob("/tmp/new_config/*")
do
filecopy(file, "/etc/config/" .. nixio.fs.basename(file))
nixio.fs.remove(file)
end
nixio.fs.rmdir("/etc/new_config")
filecopy("/etc/config.mesh/firewall.user", "/etc/firewall.user")
aredn_info.set_nvram("config", "mesh")
aredn_info.set_nvram("node", node)
aredn_info.set_nvram("tactical", tactical)
end
-- generate the system files
local h = io.open("/etc/hosts", "w")
local e = io.open("/etc/ethers", "w")
if h and e then
h:write("# automatically generated file - do not edit\n")
h:write("# use /etc/hosts.user for custom entries\n")
h:write("127.0.0.1\tlocalhost\n")
if not is_null(cfg.wifi_ip) then
h:write(cfg.lan_ip .. "\tlocalnode\n")
h:write(cfg.wifi_ip .. "\t" .. node .. " " .. tactical .. "\n")
else
h:write(cfg.lan_ip .. "\tlocalnode " .. node .. " " .. tactical .. "\n")
end
if not is_null(cfg.dtdlink_ip) then
h:write(cfg.dtdlink_ip .. "\tdtdlink." .. node .. ".local.mesh dtdlink." .. node .."\n")
end
if is_null(cfg.dmz_mode) then
h:write(decimal_to_ip(ip_to_decimal(cfg.lan_ip) + 1) .. "\tlocalap\n")
end
e:write("# automatically generated file - do not edit\n")
e:write("# use /etc/ethers.user for custom entries\n")
local netaddr = nixio.bit.band(ip_to_decimal(cfg.lan_ip), ip_to_decimal(cfg.lan_mask))
if nixio.fs.access(dhcpfile) then
for line in io.lines(dhcpfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local mac, ip, host, noprop = line:match("(%S+)%s+(%S+)%s+(%S+)%s*(%S*)")
if mac and ip and host and noprop then
ip = decimal_to_ip(netaddr + ip)
if validate_same_subnet(ip, cfg.lan_ip, cfg.lan_mask) and validate_ip_netmask(ip, cfg.lan_mask) then
h:write(ip .. "\t" .. host .. " " .. noprop .. "\n")
e:write(mac .. "\t" .. ip .. "\n")
end
end
end
end
end
-- aliases need to ba added to /etc/hosts or they will now show up on the localnode
-- nor will the services thehy offer
-- also add a comment to the hosts file so we can display the aliases differently if needed
local f = io.open(aliasfile, "r")
if f then
for line in f:lines()
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local ip, host = line:match("(%S+)%s+(%S+)")
if ip then
h:write(ip .. "\t" .. host .. " #ALIAS\n")
end
end
end
f:close()
end
h:write("\n")
if nixio.fs.access("/etc/hosts.user", "r") then
for line in io.lines("/etc/hosts.user")
do
h:write(line .. "\n")
end
end
if nixio.fs.access("/etc/ethers.user", "r") then
for line in io.lines("/etc/ethers.user")
do
e:write(line .. "\n")
end
end
h:close()
e:close()
end
if not do_basic then
filecopy("/etc/config.mesh/firewall", "/etc/config/firewall")
filecopy("/etc/config.mesh/firewall.user", "/etc/firewall.user")
end
-- for all the uci changes
local c = uci.cursor()
-- append to firewall
local add_masq = false
local fw = io.open("/etc/config/firewall", "a")
if fw then
if not is_null(cfg.dmz_mode) then
fw:write("\nconfig forwarding\n option src wifi\n option dest lan\n")
fw:write("\nconfig forwarding\n option src dtdlink\n option dest lan\n")
add_masq = true
else
fw:write("\nconfig 'include'\n option 'path' '/etc/firewall.natmode'\n option 'reload' '1'\n")
end
if c:get("aredn", "@wan[0]", "olsrd_gw") == "1" then
fw:write("\nconfig forwarding\n option src wifi\n option dest wan\n")
fw:write("\nconfig forwarding\n option src dtdlink\n option dest wan\n")
end
if nixio.fs.access(portfile) then
for line in io.lines(portfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local dip = line:match("dmz_ip = (%w+)")
if dip and cfg.dmz_mode ~= 0 then
fw:write("\nconfig redirect\n option src wifi\n option proto tcp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n")
fw:write("\nconfig redirect\n option src wifi\n option proto udp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n")
else
local intf, type, oport, host, iport, enable = line:match("(.*):(.*):(.*):(.*):(.*):(.*)")
if enable == "1" then
local match = " option src_dport " .. oport .. "\n"
if type == "tcp" then
match = match .. " option proto tcp\n"
elseif type == "udp" then
match = match .. " option proto udp\n"
end
-- uci the host and then
-- set the inside port unless the rule uses an outside port range
host = "option dest_ip " .. host .. "\n"
if not oport:match("-") then
host = host .. " option dest_port " .. iport .. "\n"
end
if not is_null(cfg.dmz_mode) and intf == "both" then
intf = "wan"
end
if intf == "both" then
fw:write("\nconfig redirect\n option src wifi\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("config redirect\n option src wan\n " .. match .. " " .. host .. "\n")
elseif intf == "wifi" and is_null(cfg.dmz_mode) then
fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("\nconfig redirect\n option src wifi\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
elseif intf == "wan" then
fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("config redirect\n option src wan\n " .. match .. " " .. host .. "\n")
end
end
end
end
end
end
fw:close();
end
if add_masq then
c:set("firewall", "@zone[2]", "masq", "0")
c:commit("firewall")
end
-- generate the services file
local sf = io.open("/etc/config/services", "w")
if sf then
if nixio.fs.access(servfile) then
for line in io.lines(servfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local name, link, proto, host, port, sffx = line:match("(.*)|(.*)|(.*)|(.*)|(.*)|(.*)")
if name and name ~= "" and host ~= "" then
if proto == "" then
proto = "http"
end
if link == "0" then
port = "0"
end
sf:write(string.format("%s://%s:%s/%s|tcp|%s\n", proto, host, port, sffx, name))
end
end
end
end
sf:close()
end
local sf = io.open("/etc/local/services", "w")
if sf then
sf:write("#!/bin/sh\n")
if cfg.wifi_proto ~= "disabled" then
local wifi_channel = tonumber(cfg.wifi_channel)
if is_null(cfg.wifi_txpower) or tonumber(cfg.wifi_txpower) > aredn.hardware.wifi_maxpower(wifi_channel) then
cfg.wifi_txpower = aredn.hardware.wifi_maxpower(wifi_channel)
elseif tonumber(cfg.wifi_txpower) < 1 then
cfg.wifi_txpower = 1
end
if cfg.wifi_enable == "1" then
sf:write("/usr/sbin/iw dev " .. cfg.wifi_intf .. " set txpower fixed " .. cfg.wifi_txpower .. "00\n")
end
if not is_null(cfg.aprs_lat) and not is_null(cfg.aprs_lon) then
c:set("aredn", "@location[0]", "lat", cfg.aprs_lat)
c:set("aredn", "@location[0]", "lon", cfg.aprs_lon)
c:commit("aredn")
end
end
sf:close()
nixio.fs.chmod("/etc/local/services", "777")
end
-- generate olsrd.conf
if nixio.fs.access("/etc/config.mesh/olsrd", "r") then
local of = io.open("/etc/config/olsrd", "w")
if of then
if nixio.fs.access("/etc/config.mesh/olsrd") then
for line in io.lines("/etc/config.mesh/olsrd")
do
if line:match("<olsrd_bridge>") then
if is_null(cfg.olsrd_bridge) then
line = line:gsub("<olsrd_bridge>", '"wifi" "lan"')
else
line = line:gsub("<olsrd_bridge>", '"lan"')
end
elseif line:match("^[^#]") then
for parm in line:gmatch("<([^%s]*)>")
do
line = line:gsub("<" .. parm .. ">", cfg[parm])
end
end
of:write(line .. "\n")
end
if not is_null(cfg.dmz_mode) then
local a, b, c, d = cfg.dmz_lan_ip:match("(.*)%.(.*)%.(.*)%.(.*)")
of:write(string.format("\nconfig Hna4\n\toption netaddr %s.%s.%s.%d\n\toption netmask 255.255.255.%d\n\n", a, b, c, d - 1, nixio.bit.band(255 * 2 ^ cfg.dmz_mode, 255)))
end
if cfg.wifi_enable ~= "1" and not is_null(cfg.wifi_ip) then
local cwan = aredn.hardware.get_iface_name("wan"):match("^([^%.%s]+)")
local cdtd = aredn.hardware.get_iface_name("dtdlink"):match("^([^%.%s]+)")
if cwan ~= cdtd then
of:write(string.format("config Hna4\n\toption netaddr %s\n\toption netmask 255.255.255.255\n\n", cfg.wifi_ip))
end
end
if c:get("aredn", "@wan[0]", "olsrd_gw") == "1" then
of:write("config LoadPlugin\n\toption library 'olsrd_dyn_gw.so.0.5'\n\toption Interval '60'\n\tlist Ping '8.8.8.8'\n\tlist Ping '8.8.4.4'\n\n\n")
end
end
of:close()
end
end
-- indicate whether lan is running in dmz mode
c:set("aredn", "@dmz[0]", "mode", cfg.dmz_mode)
c:commit("aredn")
-- setup node lan dhcp
if c:get("aredn", "@wan[0]", "lan_dhcp_route") == "1" or c:get("aredn", "@wan[0]", "lan_dhcp_defaultroute") == "1" then
-- Provide stateless routes and default route
c:set("dhcp", "@dhcp[0]", "dhcp_option", {
"121,10.0.0.0/8," .. cfg.lan_ip .. ",0.0.0.0/0," .. cfg.lan_ip,
"249,10.0.0.0/8," .. cfg.lan_ip .. ",0.0.0.0/0," .. cfg.lan_ip
})
else
-- Provide stateless routes to the mesh, and a blank default route (option 3 has no values) to
-- surpress default route being sent
c:set("dhcp", "@dhcp[0]", "dhcp_option", {
"121,10.0.0.0/8," .. cfg.lan_ip,
"249,10.0.0.0/8," .. cfg.lan_ip,
"3"
})
end
c:commit("dhcp")
-- generate the wireless config file
os.execute("/usr/local/bin/wifi-setup")
if not auto then
print "configuration complete.";
print "you should now reboot the router.";
end
return 0