3 local json = require "luci.jsonc"
4 local fs = require "nixio.fs"
6 local function readfile(path)
7 local s = fs.readfile(path)
8 return s and (s:gsub("^%s+", ""):gsub("%s+$", ""))
13 args = { name = "name" },
15 local sys = require "luci.sys"
16 local _, name, scripts = nil, nil, {}
17 for _, name in ipairs(args.name and { args.name } or sys.init.names()) do
18 local index = sys.init.index(name)
20 scripts[name] = { index = index, enabled = sys.init.enabled(name) }
22 return { error = "No such init script" }
30 args = { name = "name", action = "action" },
32 local sys = require "luci.sys"
33 if type(sys.init[args.action]) ~= "function" then
34 return { error = "Invalid action" }
36 return { result = sys.init[args.action](args.name) }
42 return { result = os.time() }
47 args = { localtime = 0 },
49 local sys = require "luci.sys"
50 local date = os.date("*t", args.localtime)
52 sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec })
53 sys.call("/etc/init.d/sysfixtime restart >/dev/null")
55 return { result = args.localtime }
61 local util = require "luci.util"
62 local zones = require "luci.sys.zoneinfo"
64 local tz = readfile("/etc/TZ")
65 local res = util.ubus("uci", "get", {
67 section = "@system[0]",
73 for _, zone in ipairs(zones.TZ) do
76 active = (res and res.value == zone[1]) and true or nil
85 local iter = fs.dir("/sys/class/leds")
93 result[led] = { triggers = {} }
95 s = readfile("/sys/class/leds/"..led.."/trigger")
96 for s in (s or ""):gmatch("%S+") do
97 m = s:match("^%[(.+)%]$")
98 result[led].triggers[#result[led].triggers+1] = m or s
99 result[led].active_trigger = m or result[led].active_trigger
102 s = readfile("/sys/class/leds/"..led.."/brightness")
104 result[led].brightness = tonumber(s)
107 s = readfile("/sys/class/leds/"..led.."/max_brightness")
109 result[led].max_brightness = tonumber(s)
120 local fs = require "nixio.fs"
121 local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer")
129 local id = p:match("/([^/]+)/manufacturer$")
131 result.devices[#result.devices+1] = {
133 vid = readfile("/sys/bus/usb/devices/"..id.."/idVendor"),
134 pid = readfile("/sys/bus/usb/devices/"..id.."/idProduct"),
135 vendor = readfile("/sys/bus/usb/devices/"..id.."/manufacturer"),
136 product = readfile("/sys/bus/usb/devices/"..id.."/product"),
137 speed = tonumber((readfile("/sys/bus/usb/devices/"..id.."/product")))
142 iter = fs.glob("/sys/bus/usb/devices/*/*-port[0-9]*")
149 local port = p:match("([^/]+)$")
150 local link = fs.readlink(p.."/device")
152 result.ports[#result.ports+1] = {
154 device = link and fs.basename(link)
165 return { result = nixio.getifaddrs() }
171 local sys = require "luci.sys"
172 return sys.net.host_hints()
178 local fp = io.open('/var/hosts/odhcpd')
181 for line in fp:lines() do
182 local dev, duid, name = string.match(line, '# (%S+)%s+(%S+)%s+%d+%s+(%S+)')
183 if dev and duid and name then
185 name = (name ~= "-") and name or nil,
197 args = { family = 0 },
198 call = function(args)
199 local s = require "luci.tools.status"
201 if args.family == 4 then
202 return { dhcp_leases = s.dhcp_leases() }
203 elseif args.family == 6 then
204 return { dhcp6_leases = s.dhcp6_leases() }
207 dhcp_leases = s.dhcp_leases(),
208 dhcp6_leases = s.dhcp6_leases()
214 getNetworkDevices = {
215 call = function(args)
216 local dir = fs.dir("/sys/class/net")
221 if not result[dev] then
222 result[dev] = { name = dev }
225 if fs.access("/sys/class/net/"..dev.."/master") then
226 local brname = fs.basename(fs.readlink("/sys/class/net/"..dev.."/master"))
227 if not result[brname] then
228 result[brname] = { name = brname }
231 if not result[brname].ports then
232 result[brname].ports = { }
235 result[brname].ports[#result[brname].ports+1] = dev
236 elseif fs.access("/sys/class/net/"..dev.."/bridge") then
237 if not result[dev].ports then
238 result[dev].ports = { }
241 result[dev].id = readfile("/sys/class/net/"..dev.."/bridge/bridge_id")
242 result[dev].stp = (readfile("/sys/class/net/"..dev.."/bridge/stp_state") ~= "0")
243 result[dev].bridge = true
246 local opr = readfile("/sys/class/net/"..dev.."/operstate")
248 result[dev].up = (opr == "up" or opr == "unknown")
249 result[dev].type = tonumber(readfile("/sys/class/net/"..dev.."/type"))
250 result[dev].name = dev
252 local mtu = tonumber(readfile("/sys/class/net/"..dev.."/mtu"))
253 if mtu and mtu > 0 then
254 result[dev].mtu = mtu
257 local qlen = tonumber(readfile("/sys/class/net/"..dev.."/tx_queue_len"))
258 if qlen and qlen > 0 then
259 result[dev].qlen = qlen
262 local master = fs.readlink("/sys/class/net/"..dev.."/master")
264 result[dev].master = fs.basename(master)
267 local mac = readfile("/sys/class/net/"..dev.."/address")
268 if mac and #mac == 17 then
269 result[dev].mac = mac
277 getWirelessDevices = {
278 call = function(args)
279 local ubus = require "ubus".connect()
281 return { error = "Unable to establish ubus connection" }
284 local status = ubus:call("network.wireless", "status", {})
285 if type(status) == "table" then
286 local radioname, radiodata
287 for radioname, radiodata in pairs(status) do
288 if type(radiodata) == "table" then
289 radiodata.iwinfo = ubus:call("iwinfo", "info", { device = radioname }) or {}
290 radiodata.iwinfo.bitrate = nil
291 radiodata.iwinfo.bssid = nil
292 radiodata.iwinfo.encryption = nil
293 radiodata.iwinfo.mode = nil
294 radiodata.iwinfo.quality = nil
295 radiodata.iwinfo.quality_max = nil
296 radiodata.iwinfo.ssid = nil
300 if type(radiodata.interfaces) == "table" then
301 local _, interfacedata
302 for _, interfacedata in ipairs(radiodata.interfaces) do
303 if type(interfacedata) == "table" and
304 type(interfacedata.ifname) == "string"
306 local iwinfo = ubus:call("iwinfo", "info", { device = interfacedata.ifname })
308 iwdata = iwdata or iwinfo
309 interfacedata.iwinfo = iwinfo or {}
314 radiodata.iwinfo = {}
317 for k, v in pairs(iwdata or ubus:call("iwinfo", "info", { device = radioname }) or {}) do
318 if k ~= "bitrate" and k ~= "bssid" and k ~= "encryption" and
319 k ~= "mode" and k ~= "quality" and k ~= "quality_max" and
322 if type(v) == "table" then
323 radiodata.iwinfo[k] = json.parse(json.stringify(v))
325 radiodata.iwinfo[k] = v
338 call = function(args)
339 local jsc = require "luci.jsonc"
340 return jsc.parse(fs.readfile("/etc/board.json") or "")
344 getConntrackHelpers = {
346 local ok, fd = pcall(io.open, "/usr/share/fw3/helpers.conf", "r")
353 local line = fd:read("*l")
358 if line:match("^%s*config%s") then
364 local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$")
366 opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
367 val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
380 return { result = rv }
386 local fs = require "nixio.fs"
390 rv.firewall = fs.access("/sbin/fw3")
391 rv.opkg = fs.access("/bin/opkg")
392 rv.offloading = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt")
393 rv.br2684ctl = fs.access("/usr/sbin/br2684ctl")
394 rv.swconfig = fs.access("/sbin/swconfig")
395 rv.odhcpd = fs.access("/usr/sbin/odhcpd")
396 rv.zram = fs.access("/sys/class/zram-control")
397 rv.sysntpd = fs.readlink("/usr/sbin/ntpd") and true
398 rv.ipv6 = fs.access("/proc/net/ipv6_route")
400 local wifi_features = { "eap", "11n", "11ac", "11r", "11w", "acs", "sae", "owe", "suiteb192" }
402 if fs.access("/usr/sbin/hostapd") then
403 rv.hostapd = { cli = fs.access("/usr/sbin/hostapd_cli") }
406 for _, feature in ipairs(wifi_features) do
407 rv.hostapd[feature] =
408 (os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0)
412 if fs.access("/usr/sbin/wpa_supplicant") then
413 rv.wpasupplicant = { cli = fs.access("/usr/sbin/wpa_cli") }
416 for _, feature in ipairs(wifi_features) do
417 rv.wpasupplicant[feature] =
418 (os.execute(string.format("/usr/sbin/wpa_supplicant -v%s >/dev/null 2>/dev/null", feature)) == 0)
422 ok, fd = pcall(io.popen, "dnsmasq --version 2>/dev/null")
427 local line = fd:read("*l")
432 local opts = line:match("^Compile time options: (.+)$")
435 for opt in opts:gmatch("%S+") do
436 local no = opt:match("^no%-(%S+)$")
437 rv.dnsmasq[string.lower(no or opt)] = not no
446 ok, fd = pcall(io.popen, "ipset --help 2>/dev/null")
453 local line = fd:read("*l")
456 elseif line:match("^Supported set types:") then
459 local set, ver = line:match("^%s+(%S+)%s+(%d+)")
460 if set and not rv.ipset[set] then
461 rv.ipset[set] = tonumber(ver)
475 local fs = require "nixio.fs"
476 local hostname, code, err = fs.readfile("/proc/sys/kernel/hostname")
478 return { error = err }, 1
480 return { result = hostname:gsub("\n$", "") }
485 args = { with_cdc = false, with_tts = true },
486 call = function(args)
487 local fs = require "nixio.fs"
488 local iter = fs.glob("/dev/tty[A-Z]*")
496 if args.with_tts then
497 iter = fs.glob("/dev/tts/*")
505 if args.with_cdc then
506 iter = fs.glob("/dev/cdc-wdm*")
514 return { result = rv }
519 local function parseInput()
520 local parse = json.new()
524 local chunk = io.read(4096)
527 elseif not done and not err then
528 done, err = parse:parse(chunk)
533 print(json.stringify({ error = err or "Incomplete input" }))
540 local function validateArgs(func, uargs)
541 local method = methods[func]
543 print(json.stringify({ error = "Method not found" }))
547 if type(uargs) ~= "table" then
548 print(json.stringify({ error = "Invalid arguments" }))
552 uargs.ubus_rpc_session = nil
555 local margs = method.args or {}
556 for k, v in pairs(uargs) do
557 if margs[k] == nil or
558 (v ~= nil and type(v) ~= type(margs[k]))
560 print(json.stringify({ error = "Invalid arguments" }))
568 if arg[1] == "list" then
569 local _, method, rv = nil, nil, {}
570 for _, method in pairs(methods) do rv[_] = method.args or {} end
571 print((json.stringify(rv):gsub(":%[%]", ":{}")))
572 elseif arg[1] == "call" then
573 local args = parseInput()
574 local method = validateArgs(arg[2], args)
575 local result, code = method.call(args)
576 print((json.stringify(result):gsub("^%[%]$", "{}")))