* Add umedbazarov/ruh-vpn: VPN/proxy manager for sing-box New community plugin: bar widget, panel, service and control-center shortcut for managing SSH, VLESS, VMess, Shadowsocks and SOCKS5 connections through sing-box, with routing presets, custom rules, system-proxy/TUN modes and a kill switch. The bundled Python backend serves a loopback control API protected by a per-launch bearer token. * Address review: sanitize kill-switch ruleset, scope TUN capability, fix mux error path, disclose DNS - kill switch: only pre-resolved, canonicalized literal IPs enter the nft ruleset; domains are resolved first and anything unparseable is dropped, so subscription-supplied addresses can no longer inject nft syntax - TUN: CAP_NET_ADMIN is granted to a plugin-private copy of sing-box in a 0700 directory instead of the shared system binary; the copy is refreshed (clearing the cap) when the system binary changes, and the legacy grant on the shared binary is removed in the same polkit prompt - fix NameError in the mux startup failure path (undefined mux_name) that hid the log tail and skipped teardown - README: disclose plain-UDP DNS endpoints (8.8.8.8 via tunnel, 223.5.5.5 direct in rules mode) alongside the TUN DoH endpoint --------- Co-authored-by: Umedjon Bazarov <170195993+UmedjonBA@users.noreply.github.com>
48 lines
1.5 KiB
Luau
48 lines
1.5 KiB
Luau
--!nonstrict
|
|
-- widget.luau — bar tile. Reads shared state published by service.luau.
|
|
|
|
local PANEL = "umedbazarov/ruh-vpn:vpn_panel"
|
|
|
|
local function activeName(status)
|
|
if not status.running then return "VPN" end
|
|
for _, sv in ipairs(noctalia.state.get("servers") or {}) do
|
|
if sv.id == status.activeServerId then return sv.name or "VPN" end
|
|
end
|
|
return "VPN"
|
|
end
|
|
|
|
local function fmtBytes(n)
|
|
n = tonumber(n) or 0
|
|
if n < 1024 then return string.format("%dB", n) end
|
|
local u = { "K", "M", "G", "T" }
|
|
local i = 0
|
|
repeat n = n / 1024; i = i + 1 until n < 1024 or i >= #u
|
|
return string.format(n < 10 and "%.1f%s" or "%.0f%s", n, u[i])
|
|
end
|
|
|
|
function update()
|
|
local s = noctalia.state.get("status") or {}
|
|
local h = noctalia.state.get("health") or {}
|
|
local running = s.running == true
|
|
|
|
barWidget.setGlyph(running and "lock" or "lock-open")
|
|
barWidget.setColor(running and "primary" or "on_surface_variant")
|
|
|
|
local label = activeName(s)
|
|
if noctalia.getConfig("show_ping") and running and h.latency_ms and h.latency_ms > 0 then
|
|
label = label .. " · " .. tostring(h.latency_ms) .. "ms"
|
|
end
|
|
if noctalia.getConfig("show_traffic") and running then
|
|
local t = noctalia.state.get("traffic") or {}
|
|
label = label .. " ↓" .. fmtBytes(t.bytes_received) .. " ↑" .. fmtBytes(t.bytes_sent)
|
|
end
|
|
barWidget.setText(label)
|
|
barWidget.setTooltip(running and ("Connected — " .. activeName(s)) or "VPN off")
|
|
end
|
|
|
|
function onClick()
|
|
noctalia.togglePanel(PANEL)
|
|
end
|
|
|
|
noctalia.setUpdateInterval(2000)
|