status 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. #!/usr/bin/lua
  2. local util = require("luci.util")
  3. local fs = require("nixio.fs")
  4. local ltn12 = require 'luci.ltn12'
  5. local sys = require("luci.sys")
  6. local json = require("luci.jsonc")
  7. local nixio = require 'nixio'
  8. local platform_info = require("platform_info")
  9. local site = require 'gluon.site_config'
  10. local ip = require 'luci.ip'
  11. local uci = require('luci.model.uci').cursor()
  12. local hostname = sys.hostname()
  13. local model = platform_info.get_model()
  14. local release = util.trim(fs.readfile("/lib/gluon/release") or "")
  15. local primary_mac = require('gluon.sysconfig').primary_mac
  16. local nodeid = require('gluon.util').node_id()
  17. local latitude = uci:get_first('gluon-node-info', 'location', 'latitude')
  18. local longitude = uci:get_first('gluon-node-info', 'location', 'longitude')
  19. local location = ""
  20. if latitude and longitude then
  21. location = "<a href=https://map.hochstift.freifunk.net/#!v:m;n:" .. nodeid .. ">" .. latitude .. ", " .. longitude .. "</a>"
  22. else
  23. location = "<a href=https://map.hochstift.freifunk.net/#!v:m;n:" .. nodeid .. ">none</a>"
  24. end
  25. local contact = uci:get_first('gluon-node-info', 'owner', 'contact', '')
  26. if contact == '' then
  27. contact = "none"
  28. end
  29. local autoupdater = uci:get('autoupdater', 'settings', 'branch')
  30. if uci:get_bool('autoupdater', 'settings', 'enabled') == false then
  31. autoupdater = "disabled (" .. autoupdater .. ")"
  32. end
  33. local addresses = ""
  34. for line in io.lines('/proc/net/if_inet6') do
  35. local matches = { line:match('^' .. string.rep('(%x%x%x%x)', 8) .. string.rep(' %x%x', 4) .. '%s+([^%s]+)$') }
  36. if matches[9] == 'br-client' then
  37. addresses = addresses .. " " .. ip.IPv6(string.format('%s:%s:%s:%s:%s:%s:%s:%s', unpack(matches))):string():lower() .. "\n"
  38. end
  39. end
  40. local data = io.open('/proc/meminfo'):read('*a')
  41. local fields = {}
  42. for k, v in data:gmatch('([^\n:]+):%s*(%d+) kB') do
  43. fields[k] = tonumber(v)
  44. end
  45. function escape_html(s)
  46. return (s:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;'):gsub('"', '&quot;'))
  47. end
  48. function neighbours(ifname)
  49. local info = util.exec("gluon-neighbour-info -d ff02::2:1001 -p 1001 -r nodeinfo -t 3 -i " .. ifname)
  50. local macs = {}
  51. for _, line in ipairs(util.split(info)) do
  52. local data = json.parse(line)
  53. if data then
  54. local function add_macs(list)
  55. if list then
  56. for _, mac in ipairs(list) do
  57. macs[mac] = data
  58. end
  59. end
  60. end
  61. if data["network"] then
  62. add_macs(data["network"]["mesh_interfaces"])
  63. if data["network"]["mesh"] and data["network"]["mesh"]["bat0"] and
  64. data["network"]["mesh"]["bat0"]["interfaces"] then
  65. local interfaces = data["network"]["mesh"]["bat0"]["interfaces"]
  66. add_macs(interfaces["other"])
  67. add_macs(interfaces["wireless"])
  68. add_macs(interfaces["tunnel"])
  69. end
  70. end
  71. end
  72. end
  73. return macs
  74. end
  75. io.write("Content-type: text/html\n\n")
  76. io.write("<!DOCTYPE html>\n")
  77. io.write("<html>")
  78. io.write("<head>")
  79. io.write("<meta charset=\"utf-8\"/>")
  80. io.write("<script src=\"/status.js\"></script>")
  81. io.write("<title>" .. escape_html(hostname) .. "</title>")
  82. io.write("</head>")
  83. io.write("<body>")
  84. io.write("<h1>" .. escape_html(hostname) .. "</h1>")
  85. io.write("<pre>")
  86. io.write("Region: " .. escape_html(site.site_name) .. "\n")
  87. io.write("Model: " .. escape_html(model) .. "\n")
  88. io.write("Firmware: " .. escape_html(release) .. "\n")
  89. io.write("MAC: " .. escape_html(primary_mac) .. "\n")
  90. io.write("Contact: " .. escape_html(contact) .. "\n")
  91. io.write("Uptime: " .. escape_html(util.trim(sys.exec("uptime | sed 's/^ \+//'"))) .. "\n")
  92. io.write("Autoupdater: " .. escape_html(autoupdater) .. "\n")
  93. io.write("Location: " .. location .. "\n")
  94. io.write("IPs: " .. escape_html(util.trim(addresses)) .. "\n")
  95. io.write("Memory: " .. string.format("%.1f %% used, %.1f %% free",(fields.MemTotal-fields.MemFree)/fields.MemTotal*100,fields.MemFree/fields.MemTotal*100) .. "\n")
  96. io.write("</pre>")
  97. io.write("<h2>Neighbours</h2>")
  98. local interfaces = util.split(util.trim(util.exec("iw dev | egrep 'type IBSS|type mesh' -B 5 | grep Interface | cut -d' ' -f2")))
  99. for _, ifname in ipairs(interfaces) do
  100. io.write("<h3>" .. escape_html(ifname) .. "</h3>")
  101. io.write("<pre>")
  102. local peer=false
  103. for _, line in ipairs(util.split(util.exec("iw dev " .. ifname .. " station dump"))) do
  104. local mac = line:match("^Station (.*) %(on ")
  105. if mac then
  106. io.write("Station <a id=\"" .. escape_html(ifname) .. "-" .. mac .. "\">" .. mac .. "</a> (on " .. escape_html(ifname) .. ")\n")
  107. peer = true
  108. else
  109. io.write(escape_html(line) .. "\n")
  110. end
  111. end
  112. if peer == false then
  113. io.write("no peers connected")
  114. end
  115. io.write("</pre>")
  116. end
  117. io.write("<h2>VPN status</h2>")
  118. io.write("<pre>")
  119. if string.len(util.exec("ip -f inet address show dev br-wan | grep global")) >= 2 then
  120. io.write("IPv4 configured\n")
  121. else
  122. io.write("IPv4 not configured\n")
  123. end
  124. if string.len(util.exec("ip -f inet6 address show dev br-wan | grep global")) >= 2 then
  125. io.write("IPv6 configured\n")
  126. else
  127. io.write("IPv6 not configured\n")
  128. end
  129. local stat, fastd_status = pcall(
  130. function()
  131. local fastd_sock = nixio.socket('unix', 'stream')
  132. assert(fastd_sock:connect('/var/run/fastd.mesh_vpn.socket'))
  133. decoder = json.new()
  134. ltn12.pump.all(ltn12.source.file(fastd_sock), decoder:sink())
  135. return decoder:get()
  136. end
  137. )
  138. if stat then
  139. io.write(string.format("fastd running for %.3f seconds\n\n", fastd_status.uptime/1000))
  140. local peers = 0
  141. local connections = 0
  142. for key, peer in pairs(fastd_status.peers) do
  143. peers = peers+1
  144. if peer.connection then
  145. connections = connections+1
  146. end
  147. end
  148. io.write(string.format("There are %i peers configured, of which %i are connected:\n", peers, connections))
  149. for key, peer in pairs(fastd_status.peers) do
  150. io.write(string.format("%s: ", escape_html(peer.name)))
  151. if peer.connection then
  152. io.write(string.format("connected for %.3f seconds\n", peer.connection.established/1000))
  153. else
  154. io.write("not connected\n")
  155. end
  156. end
  157. else
  158. io.write("fastd not running")
  159. end
  160. io.write("</pre>")
  161. io.write("<script>")
  162. for _, ifname in ipairs(interfaces) do
  163. local macs = neighbours(ifname)
  164. for mac, node in pairs(macs) do
  165. local hostname = node["hostname"]
  166. local ip
  167. if node["network"] and node["network"]["addresses"] then
  168. for _, myip in ipairs(node["network"]["addresses"]) do
  169. if ip == nil and myip:sub(1, 5) ~= "fe80:" then
  170. ip = myip
  171. end
  172. end
  173. end
  174. if ip and hostname then
  175. io.write("update_node(\"" .. escape_html(ifname) .. "-" .. mac .. "\", \"" .. escape_html(ip) .. "\", \"" .. escape_html(hostname) .. "\");")
  176. end
  177. end
  178. end
  179. io.write("</script>")
  180. io.write("</body>")
  181. io.write("</html>")