Add umedbazarov/ruh-vpn: VPN/proxy manager for sing-box (#304)
* 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>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
"""Private copy of the sing-box binary used only for TUN mode.
|
||||
|
||||
CAP_NET_ADMIN is granted to a plugin-private copy under DATA_DIR/bin (a 0700
|
||||
directory) instead of the shared system binary, so the privilege never
|
||||
extends to other users or to sing-box invocations outside this plugin.
|
||||
|
||||
When the system binary changes, the copy is rewritten from scratch; a fresh
|
||||
file starts with no capabilities, so a stale copy never keeps the grant
|
||||
across sing-box upgrades.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from backend.paths import DATA_DIR, ensure_private_dir
|
||||
|
||||
BIN_DIR = DATA_DIR / "bin"
|
||||
TUN_BIN = BIN_DIR / "sing-box-tun"
|
||||
|
||||
|
||||
def source_binary(singbox_bin: str) -> str:
|
||||
# setcap/getcap act on the real file, not a symlink (NixOS wraps binaries
|
||||
# in store symlinks, and setcap on the link fails).
|
||||
return os.path.realpath(singbox_bin)
|
||||
|
||||
|
||||
def ensure_copy(singbox_bin: str) -> tuple[str, bool]:
|
||||
"""Make sure the private copy exists and matches the system binary.
|
||||
|
||||
Returns (path to the copy, True if the copy was (re)created). Callers must
|
||||
treat a recreated copy as having no capabilities.
|
||||
"""
|
||||
src = Path(source_binary(singbox_bin))
|
||||
ensure_private_dir(BIN_DIR)
|
||||
st_src = src.stat()
|
||||
if TUN_BIN.exists():
|
||||
st_dst = TUN_BIN.stat()
|
||||
if st_dst.st_size == st_src.st_size and st_dst.st_mtime == st_src.st_mtime:
|
||||
return str(TUN_BIN), False
|
||||
# copy2 preserves mtime, which the staleness check above relies on
|
||||
tmp = TUN_BIN.with_name(TUN_BIN.name + ".tmp")
|
||||
shutil.copy2(src, tmp)
|
||||
tmp.chmod(0o700)
|
||||
os.replace(tmp, TUN_BIN)
|
||||
return str(TUN_BIN), True
|
||||
Reference in New Issue
Block a user