|
@@ -0,0 +1,217 @@
|
|
|
+#!/usr/bin/lua
|
|
|
+
|
|
|
+local util = require("luci.util")
|
|
|
+local fs = require("nixio.fs")
|
|
|
+local ltn12 = require 'luci.ltn12'
|
|
|
+local sys = require("luci.sys")
|
|
|
+local json = require("luci.jsonc")
|
|
|
+local nixio = require 'nixio'
|
|
|
+local platform_info = require("platform_info")
|
|
|
+local site = require 'gluon.site_config'
|
|
|
+local ip = require 'luci.ip'
|
|
|
+local uci = require('luci.model.uci').cursor()
|
|
|
+
|
|
|
+local hostname = sys.hostname()
|
|
|
+local model = platform_info.get_model()
|
|
|
+local release = util.trim(fs.readfile("/lib/gluon/release") or "")
|
|
|
+local primary_mac = require('gluon.sysconfig').primary_mac
|
|
|
+local nodeid = require('gluon.util').node_id()
|
|
|
+local latitude = uci:get_first('gluon-node-info', 'location', 'latitude')
|
|
|
+local longitude = uci:get_first('gluon-node-info', 'location', 'longitude')
|
|
|
+local location = ""
|
|
|
+if latitude and longitude then
|
|
|
+ location = "<a href=https://map.hochstift.freifunk.net/#!v:m;n:" .. nodeid .. ">" .. latitude .. ", " .. longitude .. "</a>"
|
|
|
+else
|
|
|
+ location = "<a href=https://map.hochstift.freifunk.net/#!v:m;n:" .. nodeid .. ">none</a>"
|
|
|
+end
|
|
|
+
|
|
|
+local contact = uci:get_first('gluon-node-info', 'owner', 'contact', '')
|
|
|
+if contact == '' then
|
|
|
+ contact = "none"
|
|
|
+end
|
|
|
+local autoupdater = uci:get('autoupdater', 'settings', 'branch')
|
|
|
+if uci:get_bool('autoupdater', 'settings', 'enabled') == false then
|
|
|
+ autoupdater = "disabled (" .. autoupdater .. ")"
|
|
|
+end
|
|
|
+
|
|
|
+local addresses = ""
|
|
|
+for line in io.lines('/proc/net/if_inet6') do
|
|
|
+ local matches = { line:match('^' .. string.rep('(%x%x%x%x)', 8) .. string.rep(' %x%x', 4) .. '%s+([^%s]+)$') }
|
|
|
+ if matches[9] == 'br-client' then
|
|
|
+ addresses = addresses .. " " .. ip.IPv6(string.format('%s:%s:%s:%s:%s:%s:%s:%s', unpack(matches))):string():lower() .. "\n"
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local data = io.open('/proc/meminfo'):read('*a')
|
|
|
+local fields = {}
|
|
|
+for k, v in data:gmatch('([^\n:]+):%s*(%d+) kB') do
|
|
|
+ fields[k] = tonumber(v)
|
|
|
+end
|
|
|
+
|
|
|
+function escape_html(s)
|
|
|
+ return (s:gsub('&', '&'):gsub('<', '<'):gsub('>', '>'):gsub('"', '"'))
|
|
|
+end
|
|
|
+
|
|
|
+function neighbours(ifname)
|
|
|
+ local info = util.exec("gluon-neighbour-info -d ff02::2:1001 -p 1001 -r nodeinfo -t 3 -i " .. ifname)
|
|
|
+ local macs = {}
|
|
|
+ for _, line in ipairs(util.split(info)) do
|
|
|
+ local data = json.parse(line)
|
|
|
+ if data then
|
|
|
+ local function add_macs(list)
|
|
|
+ if list then
|
|
|
+ for _, mac in ipairs(list) do
|
|
|
+ macs[mac] = data
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ if data["network"] then
|
|
|
+ add_macs(data["network"]["mesh_interfaces"])
|
|
|
+
|
|
|
+ if data["network"]["mesh"] and data["network"]["mesh"]["bat0"] and
|
|
|
+ data["network"]["mesh"]["bat0"]["interfaces"] then
|
|
|
+ local interfaces = data["network"]["mesh"]["bat0"]["interfaces"]
|
|
|
+ add_macs(interfaces["other"])
|
|
|
+ add_macs(interfaces["wireless"])
|
|
|
+ add_macs(interfaces["tunnel"])
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ return macs
|
|
|
+end
|
|
|
+
|
|
|
+io.write("Content-type: text/html\n\n")
|
|
|
+io.write("<!DOCTYPE html>\n")
|
|
|
+io.write("<html>")
|
|
|
+io.write("<head>")
|
|
|
+io.write("<meta charset=\"utf-8\"/>")
|
|
|
+io.write("<script src=\"/status.js\"></script>")
|
|
|
+io.write("<title>" .. escape_html(hostname) .. "</title>")
|
|
|
+io.write("</head>")
|
|
|
+io.write("<body>")
|
|
|
+
|
|
|
+io.write("<h1>" .. escape_html(hostname) .. "</h1>")
|
|
|
+io.write("<pre>")
|
|
|
+
|
|
|
+io.write("Region: " .. escape_html(site.site_name) .. "\n")
|
|
|
+io.write("Model: " .. escape_html(model) .. "\n")
|
|
|
+io.write("Firmware: " .. escape_html(release) .. "\n")
|
|
|
+io.write("MAC: " .. escape_html(primary_mac) .. "\n")
|
|
|
+io.write("Contact: " .. escape_html(contact) .. "\n")
|
|
|
+io.write("Uptime: " .. escape_html(util.trim(sys.exec("uptime | sed 's/^ \+//'"))) .. "\n")
|
|
|
+io.write("Autoupdater: " .. escape_html(autoupdater) .. "\n")
|
|
|
+io.write("Location: " .. location .. "\n")
|
|
|
+io.write("IPs: " .. escape_html(util.trim(addresses)) .. "\n")
|
|
|
+io.write("Memory: " .. string.format("%.1f %% used, %.1f %% free",(fields.MemTotal-fields.MemFree)/fields.MemTotal*100,fields.MemFree/fields.MemTotal*100) .. "\n")
|
|
|
+io.write("</pre>")
|
|
|
+
|
|
|
+io.write("<h2>Neighbours</h2>")
|
|
|
+
|
|
|
+local interfaces = util.split(util.trim(util.exec("iw dev | egrep 'type IBSS|type mesh' -B 5 | grep Interface | cut -d' ' -f2")))
|
|
|
+
|
|
|
+for _, ifname in ipairs(interfaces) do
|
|
|
+ io.write("<h3>" .. escape_html(ifname) .. "</h3>")
|
|
|
+ io.write("<pre>")
|
|
|
+
|
|
|
+ local peer=false
|
|
|
+ for _, line in ipairs(util.split(util.exec("iw dev " .. ifname .. " station dump"))) do
|
|
|
+ local mac = line:match("^Station (.*) %(on ")
|
|
|
+ if mac then
|
|
|
+ io.write("Station <a id=\"" .. escape_html(ifname) .. "-" .. mac .. "\">" .. mac .. "</a> (on " .. escape_html(ifname) .. ")\n")
|
|
|
+ peer = true
|
|
|
+ else
|
|
|
+ io.write(escape_html(line) .. "\n")
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ if peer == false then
|
|
|
+ io.write("no peers connected")
|
|
|
+ end
|
|
|
+
|
|
|
+ io.write("</pre>")
|
|
|
+end
|
|
|
+
|
|
|
+io.write("<h2>VPN status</h2>")
|
|
|
+io.write("<pre>")
|
|
|
+
|
|
|
+if string.len(util.exec("ip -f inet address show dev br-wan | grep global")) >= 2 then
|
|
|
+ io.write("IPv4 configured\n")
|
|
|
+else
|
|
|
+ io.write("IPv4 not configured\n")
|
|
|
+end
|
|
|
+
|
|
|
+if string.len(util.exec("ip -f inet6 address show dev br-wan | grep global")) >= 2 then
|
|
|
+ io.write("IPv6 configured\n")
|
|
|
+else
|
|
|
+ io.write("IPv6 not configured\n")
|
|
|
+end
|
|
|
+
|
|
|
+local stat, fastd_status = pcall(
|
|
|
+ function()
|
|
|
+ local fastd_sock = nixio.socket('unix', 'stream')
|
|
|
+ assert(fastd_sock:connect('/var/run/fastd.mesh_vpn.socket'))
|
|
|
+
|
|
|
+ decoder = json.new()
|
|
|
+ ltn12.pump.all(ltn12.source.file(fastd_sock), decoder:sink())
|
|
|
+ return decoder:get()
|
|
|
+ end
|
|
|
+)
|
|
|
+
|
|
|
+if stat then
|
|
|
+ io.write(string.format("fastd running for %.3f seconds\n\n", fastd_status.uptime/1000))
|
|
|
+
|
|
|
+ local peers = 0
|
|
|
+ local connections = 0
|
|
|
+
|
|
|
+ for key, peer in pairs(fastd_status.peers) do
|
|
|
+ peers = peers+1
|
|
|
+
|
|
|
+ if peer.connection then
|
|
|
+ connections = connections+1
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ io.write(string.format("There are %i peers configured, of which %i are connected:\n", peers, connections))
|
|
|
+
|
|
|
+ for key, peer in pairs(fastd_status.peers) do
|
|
|
+ io.write(string.format("%s: ", escape_html(peer.name)))
|
|
|
+
|
|
|
+ if peer.connection then
|
|
|
+ io.write(string.format("connected for %.3f seconds\n", peer.connection.established/1000))
|
|
|
+ else
|
|
|
+ io.write("not connected\n")
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+else
|
|
|
+ io.write("fastd not running")
|
|
|
+end
|
|
|
+
|
|
|
+io.write("</pre>")
|
|
|
+
|
|
|
+io.write("<script>")
|
|
|
+for _, ifname in ipairs(interfaces) do
|
|
|
+ local macs = neighbours(ifname)
|
|
|
+ for mac, node in pairs(macs) do
|
|
|
+ local hostname = node["hostname"]
|
|
|
+ local ip
|
|
|
+ if node["network"] and node["network"]["addresses"] then
|
|
|
+ for _, myip in ipairs(node["network"]["addresses"]) do
|
|
|
+ if ip == nil and myip:sub(1, 5) ~= "fe80:" then
|
|
|
+ ip = myip
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ if ip and hostname then
|
|
|
+ io.write("update_node(\"" .. escape_html(ifname) .. "-" .. mac .. "\", \"" .. escape_html(ip) .. "\", \"" .. escape_html(hostname) .. "\");")
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+io.write("</script>")
|
|
|
+io.write("</body>")
|
|
|
+io.write("</html>")
|