status 7.4 KB

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