🐶 Sync 2025-11-02 14:26:26

This commit is contained in:
actions-user
2025-11-02 14:26:26 +08:00
parent 64bcc56c2a
commit ac011db799
1557 changed files with 746465 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
#!/bin/sh
[ -f '/etc/rpcd_10_system.js' ] && mv -f '/etc/rpcd_10_system.js' '/www/luci-static/resources/view/status/include/10_system.js'
[ -f '/etc/rpcd_29_ports.js' ] && mv -f '/etc/rpcd_29_ports.js' '/www/luci-static/resources/view/status/include/29_ports.js'
[ -f '/etc/rpcd_luci' ] && mv -f '/etc/rpcd_luci' '/usr/share/rpcd/ucode/luci'
[ -f '/etc/ucode_sys' ] && mv -f '/etc/ucode_sys' '/usr/share/ucode/luci/sys.uc'
/etc/init.d/rpcd restart
exit 0

View File

@@ -0,0 +1,115 @@
'use strict';
'require baseclass';
'require fs';
'require rpc';
var callLuciVersion = rpc.declare({
object: 'luci',
method: 'getVersion'
});
var callSystemBoard = rpc.declare({
object: 'system',
method: 'board'
});
var callSystemInfo = rpc.declare({
object: 'system',
method: 'info'
});
var callCPUBench = rpc.declare({
object: 'luci',
method: 'getCPUBench'
});
var callCPUInfo = rpc.declare({
object: 'luci',
method: 'getCPUInfo'
});
var callCPUUsage = rpc.declare({
object: 'luci',
method: 'getCPUUsage'
});
var callTempInfo = rpc.declare({
object: 'luci',
method: 'getTempInfo'
});
return baseclass.extend({
title: _('System'),
load: function() {
return Promise.all([
L.resolveDefault(callSystemBoard(), {}),
L.resolveDefault(callSystemInfo(), {}),
L.resolveDefault(callCPUBench(), {}),
L.resolveDefault(callCPUInfo(), {}),
L.resolveDefault(callCPUUsage(), {}),
L.resolveDefault(callTempInfo(), {}),
L.resolveDefault(callLuciVersion(), { revision: _('unknown version'), branch: 'LuCI' })
]);
},
render: function(data) {
var boardinfo = data[0],
systeminfo = data[1],
cpubench = data[2],
cpuinfo = data[3],
cpuusage = data[4],
tempinfo = data[5],
luciversion = data[6];
luciversion = luciversion.branch + ' ' + luciversion.revision;
var datestr = null;
if (systeminfo.localtime) {
var date = new Date(systeminfo.localtime * 1000);
datestr = '%04d-%02d-%02d %02d:%02d:%02d'.format(
date.getUTCFullYear(),
date.getUTCMonth() + 1,
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds()
);
}
var fields = [
_('Hostname'), boardinfo.hostname,
_('Model'), boardinfo.model + cpubench.cpubench,
_('Architecture'), cpuinfo.cpuinfo,
_('Target Platform'), (L.isObject(boardinfo.release) ? boardinfo.release.target : ''),
_('Firmware Version'), (L.isObject(boardinfo.release) ? boardinfo.release.description + ' / ' : '') + (luciversion || ''),
_('Kernel Version'), boardinfo.kernel,
_('Local Time'), datestr,
_('Uptime'), systeminfo.uptime ? '%t'.format(systeminfo.uptime) : null,
_('Load Average'), Array.isArray(systeminfo.load) ? '%.2f, %.2f, %.2f'.format(
systeminfo.load[0] / 65535.0,
systeminfo.load[1] / 65535.0,
systeminfo.load[2] / 65535.0
) : null,
_('CPU usage'), cpuusage.cpuusage
];
if (tempinfo.tempinfo) {
fields.splice(6, 0, _('Temperature'));
fields.splice(7, 0, tempinfo.tempinfo);
}
var table = E('table', { 'class': 'table' });
for (var i = 0; i < fields.length; i += 2) {
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, [ fields[i] ]),
E('td', { 'class': 'td left' }, [ (fields[i + 1] != null) ? fields[i + 1] : '?' ])
]));
}
return table;
}
});

View File

@@ -0,0 +1,386 @@
'use strict';
'require baseclass';
'require fs';
'require ui';
'require uci';
'require rpc';
'require network';
'require firewall';
var callLuciETHInfo = rpc.declare({
object: 'luci',
method: 'getETHInfo',
expect: { result: [] }
});
function isString(v)
{
return typeof(v) === 'string' && v !== '';
}
function resolveVLANChain(ifname, bridges, mapping)
{
while (!mapping[ifname]) {
var m = ifname.match(/^(.+)\.([^.]+)$/);
if (!m)
break;
if (bridges[m[1]]) {
if (bridges[m[1]].vlan_filtering)
mapping[ifname] = bridges[m[1]].vlans[m[2]];
else
mapping[ifname] = bridges[m[1]].ports;
}
else if (/^[0-9]{1,4}$/.test(m[2]) && m[2] <= 4095) {
mapping[ifname] = [ m[1] ];
}
else {
break;
}
ifname = m[1];
}
}
function buildVLANMappings(mapping)
{
var bridge_vlans = uci.sections('network', 'bridge-vlan'),
vlan_devices = uci.sections('network', 'device'),
interfaces = uci.sections('network', 'interface'),
bridges = {};
/* find bridge VLANs */
for (var i = 0, s; (s = bridge_vlans[i]) != null; i++) {
if (!isString(s.device) || !/^[0-9]{1,4}$/.test(s.vlan) || +s.vlan > 4095)
continue;
var aliases = L.toArray(s.alias),
ports = L.toArray(s.ports),
br = bridges[s.device] = (bridges[s.device] || { ports: [], vlans: {}, vlan_filtering: true });
br.vlans[s.vlan] = [];
for (var j = 0; j < ports.length; j++) {
var port = ports[j].replace(/:[ut*]+$/, '');
if (br.ports.indexOf(port) === -1)
br.ports.push(port);
br.vlans[s.vlan].push(port);
}
for (var j = 0; j < aliases.length; j++)
if (aliases[j] != s.vlan)
br.vlans[aliases[j]] = br.vlans[s.vlan];
}
/* find bridges, VLAN devices */
for (var i = 0, s; (s = vlan_devices[i]) != null; i++) {
if (s.type == 'bridge') {
if (!isString(s.name))
continue;
var ports = L.toArray(s.ports),
br = bridges[s.name] || (bridges[s.name] = { ports: [], vlans: {}, vlan_filtering: false });
if (s.vlan_filtering == '0')
br.vlan_filtering = false;
else if (s.vlan_filtering == '1')
br.vlan_filtering = true;
for (var j = 0; j < ports.length; j++)
if (br.ports.indexOf(ports[j]) === -1)
br.ports.push(ports[j]);
mapping[s.name] = br.ports;
}
else if (s.type == '8021q' || s.type == '8021ad') {
if (!isString(s.name) || !isString(s.vid) || !isString(s.ifname))
continue;
/* parent device is a bridge */
if (bridges[s.ifname]) {
/* parent bridge is VLAN enabled, device refers to VLAN ports */
if (bridges[s.ifname].vlan_filtering)
mapping[s.name] = bridges[s.ifname].vlans[s.vid];
/* parent bridge is not VLAN enabled, device refers to all bridge ports */
else
mapping[s.name] = bridges[s.ifname].ports;
}
/* parent is a simple netdev */
else {
mapping[s.name] = [ s.ifname ];
}
resolveVLANChain(s.ifname, bridges, mapping);
}
}
/* resolve VLAN tagged interfaces in bridge ports */
for (var brname in bridges) {
for (var i = 0; i < bridges[brname].ports.length; i++)
resolveVLANChain(bridges[brname].ports[i], bridges, mapping);
for (var vid in bridges[brname].vlans)
for (var i = 0; i < bridges[brname].vlans[vid].length; i++)
resolveVLANChain(bridges[brname].vlans[vid][i], bridges, mapping);
}
/* find implicit VLAN devices */
for (var i = 0, s; (s = interfaces[i]) != null; i++) {
if (!isString(s.device))
continue;
resolveVLANChain(s.device, bridges, mapping);
}
}
function resolveVLANPorts(ifname, mapping, seen)
{
var ports = [];
if (!seen)
seen = {};
if (mapping[ifname]) {
for (var i = 0; i < mapping[ifname].length; i++) {
if (!seen[mapping[ifname][i]]) {
seen[mapping[ifname][i]] = true;
ports.push.apply(ports, resolveVLANPorts(mapping[ifname][i], mapping, seen));
}
}
}
else {
ports.push(ifname);
}
return ports.sort(L.naturalCompare);
}
function buildInterfaceMapping(zones, networks) {
var vlanmap = {},
portmap = {},
netmap = {};
buildVLANMappings(vlanmap);
for (var i = 0; i < networks.length; i++) {
var l3dev = networks[i].getDevice();
if (!l3dev)
continue;
var ports = resolveVLANPorts(l3dev.getName(), vlanmap);
for (var j = 0; j < ports.length; j++) {
portmap[ports[j]] = portmap[ports[j]] || { networks: [], zones: [] };
portmap[ports[j]].networks.push(networks[i]);
}
netmap[networks[i].getName()] = networks[i];
}
for (var i = 0; i < zones.length; i++) {
var networknames = zones[i].getNetworks();
for (var j = 0; j < networknames.length; j++) {
if (!netmap[networknames[j]])
continue;
var l3dev = netmap[networknames[j]].getDevice();
if (!l3dev)
continue;
var ports = resolveVLANPorts(l3dev.getName(), vlanmap);
for (var k = 0; k < ports.length; k++) {
portmap[ports[k]] = portmap[ports[k]] || { networks: [], zones: [] };
if (portmap[ports[k]].zones.indexOf(zones[i]) === -1)
portmap[ports[k]].zones.push(zones[i]);
}
}
}
return portmap;
}
function formatSpeed(carrier, speed, duplex) {
if ((speed > 0) && duplex) {
var d = (duplex == 'half') ? '\u202f(H)' : '',
e = E('span', { 'title': _('Speed: %d Mibit/s, Duplex: %s').format(speed, duplex) });
switch (speed) {
case 10: e.innerText = '10\u202fM' + d; break;
case 100: e.innerText = '100\u202fM' + d; break;
case 1000: e.innerText = '1\u202fGbE' + d; break;
case 2500: e.innerText = '2.5\u202fGbE'; break;
case 5000: e.innerText = '5\u202fGbE'; break;
case 10000: e.innerText = '10\u202fGbE'; break;
case 25000: e.innerText = '25\u202fGbE'; break;
case 40000: e.innerText = '40\u202fGbE'; break;
default: e.innerText = '%d\u202fMbE%s'.format(speed, d);
}
return e;
}
return carrier ? _('Connected') : _('no link');
}
function formatStats(portdev) {
var stats = portdev._devstate('stats') || {};
return ui.itemlist(E('span'), [
_('Received bytes'), '%1024mB'.format(stats.rx_bytes),
_('Received packets'), '%1000mPkts.'.format(stats.rx_packets),
_('Received multicast'), '%1000mPkts.'.format(stats.multicast),
_('Receive errors'), '%1000mPkts.'.format(stats.rx_errors),
_('Receive dropped'), '%1000mPkts.'.format(stats.rx_dropped),
_('Transmitted bytes'), '%1024mB'.format(stats.tx_bytes),
_('Transmitted packets'), '%1000mPkts.'.format(stats.tx_packets),
_('Transmit errors'), '%1000mPkts.'.format(stats.tx_errors),
_('Transmit dropped'), '%1000mPkts.'.format(stats.tx_dropped),
_('Collisions seen'), stats.collisions
]);
}
function renderNetworkBadge(network, zonename) {
var l3dev = network.getDevice();
var span = E('span', { 'class': 'ifacebadge', 'style': 'margin:.125em 0' }, [
E('span', {
'class': 'zonebadge',
'title': zonename ? _('Part of zone %q').format(zonename) : _('No zone assigned'),
'style': firewall.getZoneColorStyle(zonename)
}, '\u202f'),
'\u202f', network.getName(), ': '
]);
if (l3dev)
span.appendChild(E('img', {
'title': l3dev.getI18n(),
'src': L.resource('icons/%s%s.svg'.format(l3dev.getType(), l3dev.isUp() ? '' : '_disabled'))
}));
else
span.appendChild(E('em', _('(no interfaces attached)')));
return span;
}
function renderNetworksTooltip(pmap) {
var res = [ null ],
zmap = {};
for (var i = 0; pmap && i < pmap.zones.length; i++) {
var networknames = pmap.zones[i].getNetworks();
for (var k = 0; k < networknames.length; k++)
zmap[networknames[k]] = pmap.zones[i].getName();
}
for (var i = 0; pmap && i < pmap.networks.length; i++)
res.push(E('br'), renderNetworkBadge(pmap.networks[i], zmap[pmap.networks[i].getName()]));
if (res.length > 1)
res[0] = N_((res.length - 1) / 2, 'Part of network:', 'Part of networks:');
else
res[0] = _('Port is not part of any network');
return E([], res);
}
return baseclass.extend({
title: _('Port status'),
load: function() {
return Promise.all([
L.resolveDefault(callLuciETHInfo(), {}),
L.resolveDefault(fs.read('/etc/board.json'), '{}'),
firewall.getZones(),
network.getNetworks(),
uci.load('network')
]);
},
render: function(data) {
if (L.hasSystemFeature('swconfig'))
return null;
var board = JSON.parse(data[1]),
known_ports = [],
port_map = buildInterfaceMapping(data[2], data[3]);
if (Array.isArray(data[0]) && data[0].length > 0) {
known_ports = data[0].map(port => ({
...port,
netdev: network.instantiateDevice(port.device)
}));
}
else {
if (L.isObject(board) && L.isObject(board.network)) {
for (var k = 'lan'; k != null; k = (k == 'lan') ? 'wan' : null) {
if (!L.isObject(board.network[k]))
continue;
if (Array.isArray(board.network[k].ports))
for (let i = 0; i < board.network[k].ports.length; i++)
known_ports.push({
role: k,
device: board.network[k].ports[i],
netdev: network.instantiateDevice(board.network[k].ports[i])
});
else if (typeof(board.network[k].device) == 'string')
known_ports.push({
role: k,
device: board.network[k].device,
netdev: network.instantiateDevice(board.network[k].device)
});
}
}
}
known_ports.sort(function(a, b) {
return L.naturalCompare(a.device, b.device);
});
return E('div', { 'style': 'display:grid;grid-template-columns:repeat(auto-fit, minmax(70px, 1fr));margin-bottom:1em' }, known_ports.map(function(port) {
var speed = port.netdev.getSpeed(),
duplex = port.netdev.getDuplex(),
carrier = port.netdev.getCarrier(),
pmap = port_map[port.netdev.getName()],
pzones = (pmap && pmap.zones.length) ? pmap.zones.sort(function(a, b) { return L.naturalCompare(a.getName(), b.getName()) }) : [ null ];
return E('div', { 'class': 'ifacebox', 'style': 'margin:.25em;min-width:70px;max-width:100px' }, [
E('div', { 'class': 'ifacebox-head', 'style': 'font-weight:bold' }, [ port.netdev.getName() ]),
E('div', { 'class': 'ifacebox-body' }, [
E('img', { 'src': L.resource('icons/port_%s.svg').format(carrier ? 'up' : 'down') }),
E('br'),
formatSpeed(carrier, speed, duplex)
]),
E('div', { 'class': 'ifacebox-head cbi-tooltip-container', 'style': 'display:flex' }, [
E([], pzones.map(function(zone) {
return E('div', {
'class': 'zonebadge',
'style': 'cursor:help;flex:1;height:3px;opacity:' + (carrier ? 1 : 0.25) + ';' + firewall.getZoneColorStyle(zone)
});
})),
E('span', { 'class': 'cbi-tooltip left' }, [ renderNetworksTooltip(pmap) ])
]),
E('div', { 'class': 'ifacebox-body' }, [
E('div', { 'class': 'cbi-tooltip-container', 'style': 'text-align:left;font-size:80%' }, [
'\u25b2\u202f%1024.1mB'.format(port.netdev.getTXBytes()),
E('br'),
'\u25bc\u202f%1024.1mB'.format(port.netdev.getRXBytes()),
E('span', { 'class': 'cbi-tooltip' }, formatStats(port.netdev))
]),
])
]);
}));
}
});

View File

@@ -0,0 +1,61 @@
#!/bin/sh
. /etc/openwrt_release
CPUINFO_PATH="/proc/cpuinfo"
CPUFREQ_PATH="/sys/devices/system/cpu/cpufreq"
THERMAL_PATH="/sys/class/thermal"
cpu_arch="$(awk -F ': ' '/model name/ {print $2}' "$CPUINFO_PATH" | head -n1)"
[ -n "${cpu_arch}" ] || cpu_arch="$(uname -m)"
case "$DISTRIB_TARGET" in
"x86"/*)
cpu_cores="$(grep "core id" "$CPUINFO_PATH" | sort -u | wc -l)C $(grep -c "processor" "$CPUINFO_PATH")T" ;;
*)
cpu_cores="$(grep -c "processor" "$CPUINFO_PATH")" ;;
esac
case "$DISTRIB_TARGET" in
"bcm27xx"/*)
cpu_freq="$(( $(vcgencmd measure_clock arm | awk -F '=' '{print $2}') / 1000000 ))Mhz" ;;
"bcm53xx"/*)
cpu_freq="$(nvram get clkfreq | awk -F ',' '{print $1}')MHz" ;;
"mvebu"/*)
cpu_freq="$(awk -F ': ' '/BogoMIPS/ {print $2}' "$CPUINFO_PATH" | head -n1)MHz" ;;
"x86"/*)
cpu_freq="$(awk -F ': ' '/MHz/ {print $2}' "$CPUINFO_PATH" | head -n1)MHz"
;;
*)
[ ! -e "$CPUFREQ_PATH/policy0/cpuinfo_cur_freq" ] || \
cpu_freq="$(awk '{printf("%.fMHz", $0 / 1000)}' "$CPUFREQ_PATH/policy0/cpuinfo_cur_freq")"
[ ! -e "$CPUFREQ_PATH/policy4/cpuinfo_cur_freq" ] || \
big_cpu_freq="$(awk '{printf("%.fMHz ", $0 / 1000)}' "$CPUFREQ_PATH/policy4/cpuinfo_cur_freq")"
;;
esac
case "$DISTRIB_TARGET" in
"bcm27xx"/*)
cpu_temp="$(vcgencmd measure_temp | awk -F '=' '{print $2}' | awk -F "'" '{print $1}')°C" ;;
"x86"/*)
# Intel
cpu_temp="$(sensors "coretemp-*" 2>"/dev/null" | grep -E "(Package id |Core )" | grep -Eo "\+[0-9.]*°C" | head -n1 | tr -d "+")"
# AMD
[ -n "${cpu_temp}" ] || cpu_temp="$(sensors "k10temp-*" 2>"/dev/null" | awk '/Tctl|Tdie/ {print $2}' | head -n1 | tr -d "+")"
;;
*)
[ ! -e "$THERMAL_PATH/thermal_zone0/temp" ] || \
cpu_temp="$(awk '{printf("%.1f°C", $0 / 1000)}' "$THERMAL_PATH/thermal_zone0/temp")"
;;
esac
if [ -z "$big_cpu_freq$cpu_freq" ] && [ -n "$cpu_temp" ]; then
echo -n "$cpu_arch x $cpu_cores ($cpu_temp)"
elif [ -z "$cpu_temp" ] && [ -n "$big_cpu_freq$cpu_freq" ] || \
grep -Eq "ipq|mt7622" "/etc/openwrt_release"; then
echo -n "$cpu_arch x $cpu_cores ($big_cpu_freq$cpu_freq)"
elif [ -n "$cpu_temp" ] && [ -n "$big_cpu_freq$cpu_freq" ]; then
echo -n "$cpu_arch x $cpu_cores ($big_cpu_freq$cpu_freq, ${cpu_temp})"
else
echo -n "$cpu_arch x $cpu_cores"
fi

View File

@@ -0,0 +1,23 @@
#!/usr/bin/lua
local util = require "luci.util"
local jsonc = require "luci.jsonc"
local eth_info = {}
local ifname, stat
for ifname, stat in pairs(util.ubus("network.device", "status")) do
if ifname:match("^(eth%d+)$") == ifname or ifname:match("^(usb%d+)$") or ifname:match("^(lan%d+)$") or ifname:match("wan") == ifname then
eth_info[#eth_info + 1] = {
device = ifname
}
end
end
table.sort(
eth_info,
function(a, b)
return a.device < b.device
end
)
print(jsonc.stringify(eth_info))

695
autocore-arm/files/generic/luci Executable file
View File

@@ -0,0 +1,695 @@
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.
'use strict';
import { stdin, access, dirname, basename, open, popen, glob, lsdir, readfile, readlink, error } from 'fs';
import { cursor } from 'uci';
import { init_list, init_index, init_enabled, init_action, conntrack_list, process_list } from 'luci.sys';
import { revision, branch } from 'luci.version';
import { statvfs, uname } from 'luci.core';
import timezones from 'luci.zoneinfo';
function shellquote(s) {
return `'${replace(s, "'", "'\\''")}'`;
}
const methods = {
getVersion: {
call: function(request) {
return { revision, branch };
}
},
getInitList: {
args: { name: 'name' },
call: function(request) {
let scripts = {};
for (let name in filter(init_list(), i => !request.args.name || i == request.args.name)) {
let idx = init_index(name);
scripts[name] = {
index: idx?.[0],
stop: idx?.[1],
enabled: init_enabled(name)
};
}
return length(scripts) ? scripts : { error: 'No such init script' };
}
},
setInitAction: {
args: { name: 'name', action: 'action' },
call: function(request) {
switch (request.args.action) {
case 'enable':
case 'disable':
case 'start':
case 'stop':
case 'restart':
case 'reload':
const rc = init_action(request.args.name, request.args.action);
if (rc === false)
return { error: 'No such init script' };
return { result: rc == 0 };
default:
return { error: 'Invalid action' };
}
}
},
getLocaltime: {
call: function(request) {
return { result: time() };
}
},
setLocaltime: {
args: { localtime: 0 },
call: function(request) {
let t = localtime(request.args.localtime);
if (t) {
system(sprintf('date -s "%04d-%02d-%02d %02d:%02d:%02d" >/dev/null', t.year, t.mon, t.mday, t.hour, t.min, t.sec));
system('/etc/init.d/sysfixtime restart >/dev/null');
}
return { result: request.args.localtime };
}
},
getTimezones: {
call: function(request) {
let tz = trim(readfile('/etc/TZ'));
let zn = cursor()?.get?.('system', '@system[0]', 'zonename');
let result = {};
for (let zone, tzstring in timezones) {
result[zone] = { tzstring };
if (zn == zone)
result[zone].active = true;
};
return result;
}
},
getLEDs: {
call: function() {
let result = {};
for (let led in lsdir('/sys/class/leds')) {
let s;
result[led] = { triggers: [] };
s = trim(readfile(`/sys/class/leds/${led}/trigger`));
for (let trigger in split(s, ' ')) {
push(result[led].triggers, trim(trigger, '[]'));
if (trigger != result[led].triggers[-1])
result[led].active_trigger = result[led].triggers[-1];
}
s = readfile(`/sys/class/leds/${led}/brightness`);
result[led].brightness = +s;
s = readfile(`/sys/class/leds/${led}/max_brightness`);
result[led].max_brightness = +s;
}
return result;
}
},
getUSBDevices: {
call: function() {
let result = { devices: [], ports: [] };
for (let path in glob('/sys/bus/usb/devices/[0-9]*/manufacturer')) {
let id = basename(dirname(path));
push(result.devices, {
id,
vid: trim(readfile(`/sys/bus/usb/devices/${id}/idVendor`)),
pid: trim(readfile(`/sys/bus/usb/devices/${id}/idProduct`)),
vendor: trim(readfile(path)),
product: trim(readfile(`/sys/bus/usb/devices/${id}/product`)),
speed: +readfile(`/sys/bus/usb/devices/${id}/speed`)
});
}
for (let path in glob('/sys/bus/usb/devices/*/*-port[0-9]*')) {
let port = basename(path);
let link = readlink(`${path}/device`);
push(result.ports, {
port,
device: basename(link)
});
}
return result;
}
},
getConntrackHelpers: {
call: function() {
const uci = cursor();
let helpers = [];
let package;
if (uci.load('/usr/share/firewall4/helpers'))
package = 'helpers';
else if (uci.load('/usr/share/fw3/helpers.conf'))
package = 'helpers.conf';
if (package) {
uci.foreach(package, 'helper', (s) => {
push(helpers, {
name: s.name,
description: s.description,
module: s.module,
family: s.family,
proto: s.proto,
port: s.port
});
});
}
return { result: helpers };
}
},
getFeatures: {
call: function() {
let result = {
firewall: access('/sbin/fw3') == true,
firewall4: access('/sbin/fw4') == true,
opkg: access('/bin/opkg') == true,
bonding: access('/sys/module/bonding'),
mii_tool: access('/usr/sbin/mii-tool'),
offloading: access('/sys/module/xt_FLOWOFFLOAD/refcnt') == true || access('/sys/module/nft_flow_offload/refcnt') == true,
fullcone: access('/sys/module/xt_FULLCONENAT/refcnt') == true || access('/sys/module/nft_fullcone/refcnt') == true,
shortcutfe: access('/sys/module/shortcut_fe/refcnt') == true,
natflow: access('/dev/natflow_ctl') == true,
br2684ctl: access('/usr/sbin/br2684ctl') == true,
swconfig: access('/sbin/swconfig') == true,
odhcpd: access('/usr/sbin/odhcpd') == true,
zram: access('/sys/class/zram-control') == true,
sysntpd: readlink('/usr/sbin/ntpd') != null,
ipv6: access('/proc/net/ipv6_route') == true,
dropbear: access('/usr/sbin/dropbear') == true,
cabundle: access('/etc/ssl/certs/ca-certificates.crt') == true,
relayd: access('/usr/sbin/relayd') == true,
apk: access('/usr/bin/apk') == true,
wifi: access('/sbin/wifi') == true,
iptables: access('/usr/sbin/iptables') == true,
offload_hw: system((`[[ $(. /etc/openwrt_release ; echo $DISTRIB_TARGET) == "mediatek"* || $(. /etc/openwrt_release ; echo $DISTRIB_TARGET) == *"bcm"* ]]`)) == 0,
};
const wifi_features = [ 'eap', '11ac', '11ax', '11be', '11r', 'acs', 'sae', 'owe', 'suiteb192', 'wep', 'wps', 'ocv' ];
if (access('/usr/sbin/hostapd')) {
result.hostapd = { cli: access('/usr/sbin/hostapd_cli') == true };
for (let feature in wifi_features)
result.hostapd[feature] = system(`/usr/sbin/hostapd -v${feature} >/dev/null 2>/dev/null`) == 0;
}
if (access('/usr/sbin/wpa_supplicant')) {
result.wpasupplicant = { cli: access('/usr/sbin/wpa_cli') == true };
for (let feature in wifi_features)
result.wpasupplicant[feature] = system(`/usr/sbin/wpa_supplicant -v${feature} >/dev/null 2>/dev/null`) == 0;
}
let fd = popen('dnsmasq --version 2>/dev/null');
if (fd) {
const m = match(fd.read('all'), /^Compile time options: (.+)$/s);
for (let opt in split(m?.[1], ' ')) {
let f = replace(opt, 'no-', '', 1);
result.dnsmasq ??= {};
result.dnsmasq[lc(f)] = (f == opt);
}
fd.close();
}
fd = popen('ipset --help 2>/dev/null');
if (fd) {
for (let line = fd.read('line'), flag = false; length(line); line = fd.read('line')) {
if (line == 'Supported set types:\n') {
flag = true;
}
else if (flag) {
const m = match(line, /^ +([\w:,]+)\t+([0-9]+)\t/);
if (m) {
result.ipset ??= {};
result.ipset[m[1]] ??= +m[2];
}
}
}
fd.close();
}
return result;
}
},
getSwconfigFeatures: {
args: { switch: 'switch0' },
call: function(request) {
// Parse some common switch properties from swconfig help output.
const swc = popen(`swconfig dev ${shellquote(request.args.switch)} help 2>/dev/null`);
if (swc) {
let is_port_attr = false;
let is_vlan_attr = false;
let result = {};
for (let line = swc.read('line'); length(line); line = swc.read('line')) {
if (match(line, /^\s+--vlan/)) {
is_vlan_attr = true;
}
else if (match(line, /^\s+--port/)) {
is_vlan_attr = false;
is_port_attr = true;
}
else if (match(line, /cpu @/)) {
result.switch_title = match(line, /^switch[0-9]+: \w+\((.+)\)/)?.[1];
result.num_vlans = match(line, /vlans: ([0-9]+)/)?.[1] ?? 16;
result.min_vid = 1;
}
else if (match(line, /: (pvid|tag|vid)/)) {
if (is_vlan_attr)
result.vid_option = match(line, /: (\w+)/)?.[1];
}
else if (match(line, /: enable_vlan4k/)) {
result.vlan4k_option = 'enable_vlan4k';
}
else if (match(line, /: enable_vlan/)) {
result.vlan_option = 'enable_vlan';
}
else if (match(line, /: enable_learning/)) {
result.learning_option = 'enable_learning';
}
else if (match(line, /: enable_mirror_rx/)) {
result.mirror_option = 'enable_mirror_rx';
}
else if (match(line, /: max_length/)) {
result.jumbo_option = 'max_length';
}
}
swc.close();
if (!length(result))
return { error: 'No such switch' };
return result;
}
else {
return { error: error() };
}
}
},
getSwconfigPortState: {
args: { switch: 'switch0' },
call: function(request) {
const swc = popen(`swconfig dev ${shellquote(request.args.switch)} show 2>/dev/null`);
if (swc) {
let ports = [], port;
for (let line = swc.read('line'); length(line); line = swc.read('line')) {
if (match(line, /^VLAN [0-9]+:/) && length(ports))
break;
let pnum = match(line, /^Port ([0-9]+):/)?.[1];
if (pnum) {
port = {
port: +pnum,
duplex: false,
speed: 0,
link: false,
auto: false,
rxflow: false,
txflow: false
};
push(ports, port);
}
if (port) {
let m;
if (match(line, /full[ -]duplex/))
port.duplex = true;
if ((m = match(line, / speed:([0-9]+)/)) != null)
port.speed = +m[1];
if ((m = match(line, /([0-9]+) Mbps/)) != null && !port.speed)
port.speed = +m[1];
if ((m = match(line, /link: ([0-9]+)/)) != null && !port.speed)
port.speed = +m[1];
if (match(line, /(link|status): ?up/))
port.link = true;
if (match(line, /auto-negotiate|link:.*auto/))
port.auto = true;
if (match(line, /link:.*rxflow/))
port.rxflow = true;
if (match(line, /link:.*txflow/))
port.txflow = true;
}
}
swc.close();
if (!length(ports))
return { error: 'No such switch' };
return { result: ports };
}
else {
return { error: error() };
}
}
},
setPassword: {
args: { username: 'root', password: 'password' },
call: function(request) {
const u = shellquote(request.args.username);
const p = shellquote(request.args.password);
return {
result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0
};
}
},
getBlockDevices: {
call: function() {
const block = popen('/sbin/block info 2>/dev/null');
if (block) {
let result = {};
for (let line = block.read('line'); length(line); line = block.read('line')) {
let dev = match(line, /^\/dev\/([^:]+):/)?.[1];
if (dev) {
let e = result[dev] = {
dev: `/dev/${dev}`,
size: +readfile(`/sys/class/block/${dev}/size`) * 512
};
for (let m in match(line, / (\w+)="([^"]+)"/g))
e[lc(m[1])] = m[2];
}
}
block.close();
const swaps = open('/proc/swaps', 'r');
if (swaps) {
for (let line = swaps.read('line'); length(line); line = swaps.read('line')) {
let m = match(line, /^(\/\S+)\s+\S+\s+(\d+)/);
if (m) {
let dev = replace(m[1], /\\(\d\d\d)/g, (_, n) => chr(int(n, 8)));
result[`swap:${m[1]}`] = {
dev,
type: 'swap',
size: +m[2] * 1024
};
}
}
swaps.close();
}
return result;
}
else {
return { error: 'Unable to execute block utility' };
}
}
},
setBlockDetect: {
call: function() {
return { result: system('/sbin/block detect > /etc/config/fstab') == 0 };
}
},
getMountPoints: {
call: function() {
const fd = open('/proc/mounts', 'r');
if (fd) {
let result = [];
for (let line = fd.read('line'); length(line); line = fd.read('line')) {
const m = split(line, ' ');
const device = replace(m[0], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8)));
const mount = replace(m[1], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8)));
const stat = statvfs(mount);
if (stat?.blocks > 0) {
push(result, {
device, mount,
size: stat.bsize * stat.blocks,
avail: stat.bsize * stat.bavail,
free: stat.bsize * stat.bfree
});
}
}
fd.close();
return { result };
}
else {
return { error: error() };
}
}
},
getRealtimeStats: {
args: { mode: 'interface', device: 'eth0' },
call: function(request) {
let flags;
if (request.args.mode == 'interface')
flags = `-i ${shellquote(request.args.device)}`;
else if (request.args.mode == 'wireless')
flags = `-r ${shellquote(request.args.device)}`;
else if (request.args.mode == 'conntrack')
flags = '-c';
else if (request.args.mode == 'load')
flags = '-l';
else
return { error: 'Invalid mode' };
const fd = popen(`luci-bwc ${flags}`, 'r');
if (fd) {
let result;
try {
result = { result: json(`[${fd.read('all')}]`) };
}
catch (err) {
result = { error: err };
}
return result;
}
else {
return { error: error() };
}
}
},
getConntrackList: {
call: function() {
return { result: conntrack_list() };
}
},
getProcessList: {
call: function() {
return { result: process_list() };
}
},
getBuiltinEthernetPorts: {
call: function() {
let fd = open('/etc/board.json', 'r');
let board = fd ? json(fd) : {};
let ports = [];
for (let k in [ 'lan', 'wan' ]) {
if (!board?.network?.[k])
continue;
if (type(board.network[k].ports) == 'array') {
for (let ifname in board.network[k].ports) {
push(ports, { role: k, device: ifname });
}
}
else if (type(board.network[k].device) == 'string') {
push(ports, { role: k, device: board.network[k].device });
}
}
/* Workaround for targets that do not enumerate all netdevs in board.json */
if (uname().machine in [ 'x86_64' ] &&
match(ports[0]?.device, /^eth\d+$/)) {
let bus = readlink(`/sys/class/net/${ports[0].device}/device/subsystem`);
for (let netdev in lsdir('/sys/class/net')) {
if (!match(netdev, /^eth\d+$/))
continue;
if (length(filter(ports, port => port.device == netdev)))
continue;
if (readlink(`/sys/class/net/${netdev}/device/subsystem`) != bus)
continue;
push(ports, { role: 'unknown', device: netdev });
}
}
return { result: ports };
}
},
getCPUBench: {
call: function() {
return { cpubench: readfile('/etc/bench.log') || '' };
}
},
getCPUInfo: {
call: function() {
if (!access('/sbin/cpuinfo'))
return {};
const fd = popen('/sbin/cpuinfo');
if (fd) {
let cpuinfo = fd.read('all');
if (!cpuinfo)
cpuinfo = '?';
fd.close();
return { cpuinfo: cpuinfo };
} else {
return { cpuinfo: error() };
}
}
},
getCPUUsage: {
call: function() {
const fd = popen('/bin/busybox top -n1 | awk \'/^CPU/ {printf("%d%", 100 - $8)}\'');
if (fd) {
let cpuusage = fd.read('all');
if (!cpuusage)
cpuusage = '?';
fd.close();
return { cpuusage: cpuusage };
} else {
return { cpuusage: error() };
}
}
},
getTempInfo: {
call: function() {
if (!access('/sbin/tempinfo'))
return {};
const fd = popen('/sbin/tempinfo');
if (fd) {
let tempinfo = fd.read('all');
if (!tempinfo)
tempinfo = '?';
fd.close();
return { tempinfo: tempinfo };
} else {
return { tempinfo: error() };
}
}
},
getOnlineUsers: {
call: function() {
const fd = open('/proc/net/arp', 'r');
if (fd) {
let onlineusers = 0;
for (let line = fd.read('line'); length(line); line = fd.read('line'))
if (match(trim(line), /^.*(0x2).*(br-lan)$/))
onlineusers++;
fd.close();
return { onlineusers: onlineusers };
} else {
return { onlineusers: error() };
}
}
},
getETHInfo: {
call: function() {
if (!access('/sbin/ethinfo'))
return {};
const fd = popen('/sbin/ethinfo');
if (fd) {
let ethinfo = fd.read('all');
if (!ethinfo)
ethinfo = '{}';
ethinfo = json(ethinfo);
fd.close();
return { result: ethinfo };
} else {
return { result: error() };
}
}
}
};
return { luci: methods };

View File

@@ -0,0 +1,10 @@
{
"luci-mod-status-autocore": {
"description": "Grant access to autocore",
"read": {
"ubus": {
"luci": [ "getCPUInfo", "getTempInfo", "getCPUBench", "getCPUUsage", "getOnlineUsers", "getETHInfo" ]
}
}
}
}

View File

@@ -0,0 +1,159 @@
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.
import { basename, readlink, readfile, open, popen, stat, glob } from 'fs';
export function process_list() {
const top = popen('/bin/busybox top -bn1');
let line, list = [];
for (let line = top.read('line'); length(line); line = top.read('line')) {
let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][<NW ][<N ]) +([0-9]+m?) +([0-9]+%) +([0-9]+%) +(.+)$/);
if (m && m[8] != '/bin/busybox top -bn1') {
push(list, {
PID: m[1],
PPID: m[2],
USER: trim(m[3]),
STAT: m[4],
VSZ: m[5],
'%MEM': m[6],
'%CPU': m[7],
COMMAND: m[8]
});
}
}
top.close();
return list;
};
export function conntrack_list(callback) {
const etcpr = open('/etc/protocols');
const protos = {};
if (etcpr) {
for (let line = etcpr.read('line'); length(line); line = etcpr.read('line')) {
const m = match(line, /^([^# \t\n]+)\s+([0-9]+)\s+/);
if (m)
protos[m[2]] = m[1];
}
etcpr.close();
}
const nfct = open('/proc/net/nf_conntrack', 'r');
let connt;
if (nfct) {
let lineCount = 0;
for (let line = nfct.read('line'); length(line) && lineCount < 1200; line = nfct.read('line')) {
lineCount++;
let m = match(line, /^(ipv[46]) +([0-9]+) +\S+ +([0-9]+)( +.+)\n$/);
if (!m)
continue;
let fam = m[1];
let l3 = m[2];
let l4 = m[3];
let tuples = m[4];
let timeout = null;
m = match(tuples, /^ +([0-9]+)( .+)$/);
if (m) {
timeout = m[1];
tuples = m[2];
}
if (index(tuples, 'TIME_WAIT') !== -1)
continue;
let e = {
bytes: 0,
packets: 0,
layer3: fam,
layer4: protos[l4] ?? 'unknown',
timeout: +timeout
};
for (let kv in match(tuples, / (\w+)=(\S+)/g)) {
switch (kv[1]) {
case 'bytes':
case 'packets':
e[kv[1]] += +kv[2];
break;
case 'src':
case 'dst':
e[kv[1]] ??= arrtoip(iptoarr(kv[2]));
break;
case 'sport':
case 'dport':
e[kv[1]] ??= +kv[2];
break;
default:
e[kv[1]] = kv[2];
}
}
if (callback)
callback(e);
else
push(connt ??= [], e);
}
nfct.close();
}
return callback ? true : (connt ?? []);
};
export function init_list() {
return map(filter(glob('/etc/init.d/*'), path => {
const s = stat(path);
return s?.type == 'file' && s?.perm?.user_exec;
}), basename);
};
export function init_index(name) {
const src = readfile(`/etc/init.d/${basename(name)}`, 2048);
const idx = [];
for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) {
switch (m[1]) {
case 'START': idx[0] = +trim(m[2], '"\''); break;
case 'STOP': idx[1] = +trim(m[2], '"\''); break;
}
}
return length(idx) ? idx : null;
};
export function init_enabled(name) {
for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) {
const ln = readlink(path);
const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`);
const s2 = stat(`/etc/init.d/${basename(name)}`);
if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec)
return true;
}
return false;
};
export function init_action(name, action) {
const s = stat(`/etc/init.d/${basename(name)}`);
if (s?.type != 'file' || s?.user_exec == false)
return false;
return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`);
};