#!/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 version = util.trim(fs.readfile("/lib/gluon/gluon-version") or "") local primary_mac = require('gluon.sysconfig').primary_mac local nodeid = require('gluon.util').node_id() 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 local function location() local text = 'none' local locationid = uci:get_first('gluon-node-info', 'location') if locationid then local location = uci:get_all('gluon-node-info', locationid) if uci:get_bool('gluon-node-info', locationid, 'share_location') and location.latitude and location.longitude then text = location.latitude .. ', ' .. location.longitude end end if (site.status_page or {}).location_link then text = '' .. text .. '' end return text end local function escape_html(s) return (s:gsub('&', '&'):gsub('<', '<'):gsub('>', '>'):gsub('"', '"')) end local 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 local function ip_proto(address) if address:match("%.") then return "IPv4" end if address:match(":") then return "IPv6" end return "???" end io.write("Content-type: text/html\n\n") io.write("\n") io.write("") io.write("") io.write("") io.write("") io.write("" .. escape_html(hostname) .. "") io.write("") io.write("") io.write("

" .. escape_html(hostname) .. "

") io.write("
")

io.write("Community:   " .. escape_html(site.site_name) .. "\n")
io.write("Model:       " .. escape_html(model) .. "\n")
io.write("Firmware:    " .. escape_html(release) .. " / " .. escape_html(version) .. "\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("
") io.write("

Neighbours

") 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("

" .. escape_html(ifname) .. "

") io.write("
")

  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 " .. mac .. " (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("
") end io.write("

VPN status

") io.write("
")

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 via %s\n", peer.connection.established/1000, ip_proto(peer.address)))
    else
      io.write("not connected\n")
    end
  end

else
  io.write("fastd not running")
end

io.write("
") io.write("") io.write("") io.write("")