fix: run VLESS Reality on Xray instead of sing-box for v2rayN

sing-box Hy2 stays on 8443+; port 443 VLESS uses Xray which pairs reliably with v2rayN/Xray-core clients.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-16 11:56:22 +08:00
parent 5685b869dc
commit c9895133cb
9 changed files with 305 additions and 39 deletions
+27 -5
View File
@@ -27,6 +27,7 @@ from stats import collect_node_stats
ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1]))
SECRET_FILE = ROOT / "data" / ".panel_secret"
RENDER_SCRIPT = ROOT / "scripts" / "render-server.py"
RENDER_XRAY_SCRIPT = ROOT / "scripts" / "render-xray.py"
_apply_lock = threading.Lock()
@@ -109,14 +110,28 @@ def render_singbox_config() -> tuple[bool, str]:
env=env,
)
if proc.returncode != 0:
return False, proc.stderr or proc.stdout or "配置生成失败"
return False, proc.stderr or proc.stdout or "sing-box 配置生成失败"
return True, "ok"
def restart_singbox_async() -> None:
"""后台重启 sing-box,避免添加/删除节点 API 长时间阻塞。"""
def render_xray_config() -> tuple[bool, str]:
env = os.environ.copy()
env["JIEDIAN_ROOT"] = str(ROOT)
proc = subprocess.run(
["python3", str(RENDER_XRAY_SCRIPT)],
capture_output=True,
text=True,
env=env,
)
if proc.returncode != 0:
return False, proc.stderr or proc.stdout or "Xray 配置生成失败"
return True, "ok"
def restart_services_async() -> None:
"""后台重启 sing-box 与 Xray。"""
subprocess.Popen(
["systemctl", "restart", "sing-box"],
["systemctl", "restart", "xray", "sing-box"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
@@ -126,10 +141,17 @@ def apply_singbox() -> tuple[bool, str]:
ok, msg = render_singbox_config()
if not ok:
return False, msg
restart_singbox_async()
ok, msg = render_xray_config()
if not ok:
return False, msg
restart_services_async()
return True, "ok"
def restart_singbox_async() -> None:
restart_services_async()
def apply_singbox_background(on_fail=None) -> None:
"""后台生成配置并重启 sing-box,避免阻塞 HTTP 请求导致 Nginx 503。"""
+25
View File
@@ -19,10 +19,14 @@ ENV_FILE = ROOT / ".env"
CLASH_ADDR = "127.0.0.1:9090"
_VLESS_INBOUND = "vless-reality-in"
_XRAY_ACCESS_LOG = Path("/var/log/xray/access.log")
_LOG_USER_RE = re.compile(
r"\[([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\]\s+inbound connection"
)
_LOG_INDEX_RE = re.compile(r"\[(\d+)\] inbound connection")
_LOG_XRAY_UUID_RE = re.compile(
r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"
)
_speed_cache: dict[int, tuple[float, int, int]] = {}
_conn_cache: dict[str, dict[str, int | str]] = {}
@@ -72,6 +76,26 @@ def fetch_clash_connections() -> tuple[list[dict], bool]:
return payload.get("connections") or [], True
def fetch_xray_access_uuids(nodes: list[dict]) -> set[str]:
"""VLESS Reality 由 Xray 承载,从 access.log 读取近期活跃 UUID。"""
if not _XRAY_ACCESS_LOG.exists():
return set()
try:
text = _XRAY_ACCESS_LOG.read_text(encoding="utf-8", errors="ignore")
except OSError:
return set()
known = {node["uuid"] for node in nodes}
active: set[str] = set()
for line in text.splitlines()[-400:]:
if "accepted" not in line:
continue
for match in _LOG_XRAY_UUID_RE.finditer(line):
uid = match.group(1)
if uid in known:
active.add(uid)
return active
def fetch_recent_log_uuids(nodes: list[dict]) -> set[str]:
"""sing-box Clash API 不导出 user 字段,VLESS 多用户需从近期日志补全在线 UUID。"""
try:
@@ -343,6 +367,7 @@ def collect_node_stats() -> dict:
uuid_to_node = {node["uuid"]: int(node["id"]) for node in nodes}
connections, clash_ok = fetch_clash_connections()
log_active = fetch_recent_log_uuids(nodes) if len(nodes) > 1 else set()
log_active |= fetch_xray_access_uuids(nodes)
active_by_uuid = _sync_connections(connections, nodes, uuid_to_node, log_active)
single_node = len(nodes) == 1
has_connections = len(connections) > 0