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:
+27
-5
@@ -27,6 +27,7 @@ from stats import collect_node_stats
|
|||||||
ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1]))
|
ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1]))
|
||||||
SECRET_FILE = ROOT / "data" / ".panel_secret"
|
SECRET_FILE = ROOT / "data" / ".panel_secret"
|
||||||
RENDER_SCRIPT = ROOT / "scripts" / "render-server.py"
|
RENDER_SCRIPT = ROOT / "scripts" / "render-server.py"
|
||||||
|
RENDER_XRAY_SCRIPT = ROOT / "scripts" / "render-xray.py"
|
||||||
_apply_lock = threading.Lock()
|
_apply_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
@@ -109,14 +110,28 @@ def render_singbox_config() -> tuple[bool, str]:
|
|||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
if proc.returncode != 0:
|
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"
|
return True, "ok"
|
||||||
|
|
||||||
|
|
||||||
def restart_singbox_async() -> None:
|
def render_xray_config() -> tuple[bool, str]:
|
||||||
"""后台重启 sing-box,避免添加/删除节点 API 长时间阻塞。"""
|
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(
|
subprocess.Popen(
|
||||||
["systemctl", "restart", "sing-box"],
|
["systemctl", "restart", "xray", "sing-box"],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
@@ -126,10 +141,17 @@ def apply_singbox() -> tuple[bool, str]:
|
|||||||
ok, msg = render_singbox_config()
|
ok, msg = render_singbox_config()
|
||||||
if not ok:
|
if not ok:
|
||||||
return False, msg
|
return False, msg
|
||||||
restart_singbox_async()
|
ok, msg = render_xray_config()
|
||||||
|
if not ok:
|
||||||
|
return False, msg
|
||||||
|
restart_services_async()
|
||||||
return True, "ok"
|
return True, "ok"
|
||||||
|
|
||||||
|
|
||||||
|
def restart_singbox_async() -> None:
|
||||||
|
restart_services_async()
|
||||||
|
|
||||||
|
|
||||||
def apply_singbox_background(on_fail=None) -> None:
|
def apply_singbox_background(on_fail=None) -> None:
|
||||||
"""后台生成配置并重启 sing-box,避免阻塞 HTTP 请求导致 Nginx 503。"""
|
"""后台生成配置并重启 sing-box,避免阻塞 HTTP 请求导致 Nginx 503。"""
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,14 @@ ENV_FILE = ROOT / ".env"
|
|||||||
|
|
||||||
CLASH_ADDR = "127.0.0.1:9090"
|
CLASH_ADDR = "127.0.0.1:9090"
|
||||||
_VLESS_INBOUND = "vless-reality-in"
|
_VLESS_INBOUND = "vless-reality-in"
|
||||||
|
_XRAY_ACCESS_LOG = Path("/var/log/xray/access.log")
|
||||||
_LOG_USER_RE = re.compile(
|
_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"
|
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_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]] = {}
|
_speed_cache: dict[int, tuple[float, int, int]] = {}
|
||||||
_conn_cache: dict[str, dict[str, int | str]] = {}
|
_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
|
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]:
|
def fetch_recent_log_uuids(nodes: list[dict]) -> set[str]:
|
||||||
"""sing-box Clash API 不导出 user 字段,VLESS 多用户需从近期日志补全在线 UUID。"""
|
"""sing-box Clash API 不导出 user 字段,VLESS 多用户需从近期日志补全在线 UUID。"""
|
||||||
try:
|
try:
|
||||||
@@ -343,6 +367,7 @@ def collect_node_stats() -> dict:
|
|||||||
uuid_to_node = {node["uuid"]: int(node["id"]) for node in nodes}
|
uuid_to_node = {node["uuid"]: int(node["id"]) for node in nodes}
|
||||||
connections, clash_ok = fetch_clash_connections()
|
connections, clash_ok = fetch_clash_connections()
|
||||||
log_active = fetch_recent_log_uuids(nodes) if len(nodes) > 1 else set()
|
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)
|
active_by_uuid = _sync_connections(connections, nodes, uuid_to_node, log_active)
|
||||||
single_node = len(nodes) == 1
|
single_node = len(nodes) == 1
|
||||||
has_connections = len(connections) > 0
|
has_connections = len(connections) > 0
|
||||||
|
|||||||
@@ -68,8 +68,10 @@ if [[ -f "$ENV_FILE" ]]; then
|
|||||||
fi
|
fi
|
||||||
echo "已写入 $ENV_FILE"
|
echo "已写入 $ENV_FILE"
|
||||||
echo ""
|
echo ""
|
||||||
echo "重要: 密钥已变更,必须重新生成 sing-box 配置并重启:"
|
echo "重要: 密钥已变更,必须重新生成配置并重启:"
|
||||||
echo " python3 ${ROOT_DIR}/scripts/render-server.py && systemctl restart sing-box"
|
echo " python3 ${ROOT_DIR}/scripts/render-xray.py"
|
||||||
|
echo " python3 ${ROOT_DIR}/scripts/render-server.py"
|
||||||
|
echo " systemctl restart xray sing-box"
|
||||||
else
|
else
|
||||||
echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等,再重新运行本脚本" >&2
|
echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等,再重新运行本脚本" >&2
|
||||||
fi
|
fi
|
||||||
|
|||||||
+11
-4
@@ -184,9 +184,15 @@ python3 -m venv "$ROOT_DIR/panel/venv"
|
|||||||
log "初始化节点数据库 ..."
|
log "初始化节点数据库 ..."
|
||||||
"$ROOT_DIR/panel/venv/bin/python" "$ROOT_DIR/panel/init_db.py"
|
"$ROOT_DIR/panel/venv/bin/python" "$ROOT_DIR/panel/init_db.py"
|
||||||
|
|
||||||
log "生成 sing-box 服务端配置 ..."
|
log "生成 sing-box 服务端配置 (Hysteria2) ..."
|
||||||
python3 "$ROOT_DIR/scripts/render-server.py"
|
python3 "$ROOT_DIR/scripts/render-server.py"
|
||||||
|
|
||||||
|
log "安装 Xray (VLESS Reality 443) ..."
|
||||||
|
bash -c "$(curl -fsSL https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
|
||||||
|
|
||||||
|
log "生成 Xray 服务端配置 ..."
|
||||||
|
python3 "$ROOT_DIR/scripts/render-xray.py"
|
||||||
|
|
||||||
log "创建 sing-box systemd 服务 ..."
|
log "创建 sing-box systemd 服务 ..."
|
||||||
cat > /etc/systemd/system/sing-box.service <<'UNIT'
|
cat > /etc/systemd/system/sing-box.service <<'UNIT'
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -209,7 +215,7 @@ log "创建管理面板 systemd 服务 ..."
|
|||||||
cat > /etc/systemd/system/jiedian-panel.service <<UNIT
|
cat > /etc/systemd/system/jiedian-panel.service <<UNIT
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=jiedian admin panel
|
Description=jiedian admin panel
|
||||||
After=network.target sing-box.service
|
After=network.target xray.service sing-box.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -226,7 +232,7 @@ WantedBy=multi-user.target
|
|||||||
UNIT
|
UNIT
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable sing-box jiedian-panel
|
systemctl enable xray sing-box jiedian-panel
|
||||||
|
|
||||||
log "注册证书续期 reload 命令 ..."
|
log "注册证书续期 reload 命令 ..."
|
||||||
/root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
|
/root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
|
||||||
@@ -235,7 +241,7 @@ log "注册证书续期 reload 命令 ..."
|
|||||||
--reloadcmd "systemctl restart sing-box && systemctl reload nginx" \
|
--reloadcmd "systemctl restart sing-box && systemctl reload nginx" \
|
||||||
|| log "acme reloadcmd 注册失败,可忽略"
|
|| log "acme reloadcmd 注册失败,可忽略"
|
||||||
|
|
||||||
systemctl restart sing-box jiedian-panel
|
systemctl restart xray sing-box jiedian-panel
|
||||||
|
|
||||||
log "部署完成!"
|
log "部署完成!"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -249,5 +255,6 @@ echo ""
|
|||||||
echo "节点链接请在面板中添加/复制。"
|
echo "节点链接请在面板中添加/复制。"
|
||||||
echo ""
|
echo ""
|
||||||
log "sing-box: systemctl status sing-box"
|
log "sing-box: systemctl status sing-box"
|
||||||
|
log "Xray: systemctl status xray"
|
||||||
log "面板: systemctl status jiedian-panel"
|
log "面板: systemctl status jiedian-panel"
|
||||||
log "卸载重装: bash scripts/uninstall.sh && bash scripts/install.sh"
|
log "卸载重装: bash scripts/uninstall.sh && bash scripts/install.sh"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 已有 VPS:将 VLESS Reality 从 sing-box 迁移到 Xray(443)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
|
||||||
|
|
||||||
|
if ! command -v xray &>/dev/null; then
|
||||||
|
echo "[+] 安装 Xray ..."
|
||||||
|
bash -c "$(curl -fsSL https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
|
||||||
|
fi
|
||||||
|
|
||||||
|
export JIEDIAN_ROOT="$ROOT_DIR"
|
||||||
|
|
||||||
|
echo "[+] 更新 sing-box 配置(仅 Hysteria2)..."
|
||||||
|
python3 "$ROOT_DIR/scripts/render-server.py"
|
||||||
|
|
||||||
|
echo "[+] 生成 Xray 配置(VLESS Reality 443)..."
|
||||||
|
python3 "$ROOT_DIR/scripts/render-xray.py"
|
||||||
|
|
||||||
|
systemctl enable xray 2>/dev/null || true
|
||||||
|
systemctl restart xray sing-box jiedian-panel
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[+] 迁移完成。请运行诊断:"
|
||||||
|
bash "$ROOT_DIR/scripts/verify-reality.sh"
|
||||||
|
echo ""
|
||||||
|
echo "客户端无需改参数,直接测速 VLESS 节点即可。"
|
||||||
@@ -44,7 +44,6 @@ def load_nodes(db_path: Path) -> list[dict]:
|
|||||||
|
|
||||||
def build_config(env: dict[str, str], nodes: list[dict]) -> dict:
|
def build_config(env: dict[str, str], nodes: list[dict]) -> dict:
|
||||||
required = [
|
required = [
|
||||||
"REALITY_PRIVATE_KEY",
|
|
||||||
"REALITY_SHORT_ID",
|
"REALITY_SHORT_ID",
|
||||||
"REALITY_SERVER_NAME",
|
"REALITY_SERVER_NAME",
|
||||||
"DOMAIN",
|
"DOMAIN",
|
||||||
@@ -53,32 +52,9 @@ def build_config(env: dict[str, str], nodes: list[dict]) -> dict:
|
|||||||
if not env.get(key):
|
if not env.get(key):
|
||||||
raise SystemExit(f".env 缺少 {key}")
|
raise SystemExit(f".env 缺少 {key}")
|
||||||
|
|
||||||
vless_users = [{"uuid": n["uuid"], "flow": "xtls-rprx-vision"} for n in nodes]
|
|
||||||
hy2_base_port = 8443
|
hy2_base_port = 8443
|
||||||
|
|
||||||
inbounds: list[dict] = [
|
inbounds: list[dict] = []
|
||||||
{
|
|
||||||
"type": "vless",
|
|
||||||
"tag": "vless-reality-in",
|
|
||||||
"listen": "0.0.0.0",
|
|
||||||
"listen_port": 443,
|
|
||||||
"users": vless_users,
|
|
||||||
"tls": {
|
|
||||||
"enabled": True,
|
|
||||||
"server_name": env["REALITY_SERVER_NAME"],
|
|
||||||
"reality": {
|
|
||||||
"enabled": True,
|
|
||||||
"handshake": {
|
|
||||||
"server": env["REALITY_SERVER_NAME"],
|
|
||||||
"server_port": 443,
|
|
||||||
},
|
|
||||||
"private_key": env["REALITY_PRIVATE_KEY"],
|
|
||||||
"short_id": ["", env["REALITY_SHORT_ID"]],
|
|
||||||
"max_time_difference": "1m",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
for index, node in enumerate(nodes):
|
for index, node in enumerate(nodes):
|
||||||
inbounds.append(
|
inbounds.append(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""根据 data/nodes.db 与 .env 生成 Xray VLESS+Reality 配置(443 端口)。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1]))
|
||||||
|
ENV_FILE = ROOT / ".env"
|
||||||
|
DB_FILE = ROOT / "data" / "nodes.db"
|
||||||
|
OUT_FILE = Path("/usr/local/etc/xray/config.json")
|
||||||
|
ACCESS_LOG = Path("/var/log/xray/access.log")
|
||||||
|
|
||||||
|
|
||||||
|
def load_env(path: Path) -> dict[str, str]:
|
||||||
|
env: dict[str, str] = {}
|
||||||
|
if not path.exists():
|
||||||
|
raise SystemExit(f"缺少 .env: {path}")
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, _, value = line.partition("=")
|
||||||
|
env[key.strip()] = value.strip()
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def load_nodes(db_path: Path) -> list[dict]:
|
||||||
|
if not db_path.exists():
|
||||||
|
raise SystemExit(f"缺少节点数据库: {db_path},请先运行 install.sh")
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT id, name, uuid, hy2_password FROM nodes WHERE enabled = 1 ORDER BY id"
|
||||||
|
).fetchall()
|
||||||
|
conn.close()
|
||||||
|
if not rows:
|
||||||
|
raise SystemExit("没有可用节点,请在管理面板中添加节点")
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def build_config(env: dict[str, str], nodes: list[dict]) -> dict:
|
||||||
|
required = [
|
||||||
|
"REALITY_PRIVATE_KEY",
|
||||||
|
"REALITY_SHORT_ID",
|
||||||
|
"REALITY_SERVER_NAME",
|
||||||
|
]
|
||||||
|
for key in required:
|
||||||
|
if not env.get(key):
|
||||||
|
raise SystemExit(f".env 缺少 {key}")
|
||||||
|
|
||||||
|
short_id = env["REALITY_SHORT_ID"]
|
||||||
|
clients = [
|
||||||
|
{"id": node["uuid"], "flow": "xtls-rprx-vision", "email": node["uuid"]}
|
||||||
|
for node in nodes
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"log": {
|
||||||
|
"access": str(ACCESS_LOG),
|
||||||
|
"loglevel": "warning",
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"port": 443,
|
||||||
|
"protocol": "vless",
|
||||||
|
"settings": {
|
||||||
|
"clients": clients,
|
||||||
|
"decryption": "none",
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "tcp",
|
||||||
|
"security": "reality",
|
||||||
|
"realitySettings": {
|
||||||
|
"show": False,
|
||||||
|
"dest": f"{env['REALITY_SERVER_NAME']}:443",
|
||||||
|
"xver": 0,
|
||||||
|
"serverNames": [env["REALITY_SERVER_NAME"]],
|
||||||
|
"privateKey": env["REALITY_PRIVATE_KEY"],
|
||||||
|
"shortIds": ["", short_id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sniffing": {
|
||||||
|
"enabled": True,
|
||||||
|
"destOverride": ["http", "tls", "quic"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{"protocol": "freedom", "tag": "direct"},
|
||||||
|
{"protocol": "blackhole", "tag": "block"},
|
||||||
|
],
|
||||||
|
"routing": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"ip": ["geoip:private"],
|
||||||
|
"outboundTag": "block",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
env = load_env(ENV_FILE)
|
||||||
|
nodes = load_nodes(DB_FILE)
|
||||||
|
config = build_config(env, nodes)
|
||||||
|
|
||||||
|
OUT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
ACCESS_LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
OUT_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
xray = subprocess.run(
|
||||||
|
["xray", "run", "-test", "-c", str(OUT_FILE)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if xray.returncode != 0:
|
||||||
|
sys.stderr.write(xray.stderr or xray.stdout)
|
||||||
|
raise SystemExit(xray.returncode)
|
||||||
|
|
||||||
|
print(f"已生成 {OUT_FILE}({len(nodes)} 个 VLESS 用户)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -6,16 +6,17 @@ set -euo pipefail
|
|||||||
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
|
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
|
||||||
|
|
||||||
echo "[*] 停止服务 ..."
|
echo "[*] 停止服务 ..."
|
||||||
systemctl stop jiedian-panel sing-box 2>/dev/null || true
|
systemctl stop jiedian-panel xray sing-box 2>/dev/null || true
|
||||||
systemctl disable jiedian-panel sing-box 2>/dev/null || true
|
systemctl disable jiedian-panel xray sing-box 2>/dev/null || true
|
||||||
|
|
||||||
echo "[*] 删除 systemd 单元 ..."
|
echo "[*] 删除 systemd 单元 ..."
|
||||||
rm -f /etc/systemd/system/jiedian-panel.service
|
rm -f /etc/systemd/system/jiedian-panel.service
|
||||||
rm -f /etc/systemd/system/sing-box.service
|
rm -f /etc/systemd/system/sing-box.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
echo "[*] 删除 sing-box 配置 ..."
|
echo "[*] 删除 sing-box / Xray 配置 ..."
|
||||||
rm -rf /etc/sing-box
|
rm -rf /etc/sing-box
|
||||||
|
rm -f /usr/local/etc/xray/config.json
|
||||||
|
|
||||||
echo "[*] 删除 nginx 站点 ..."
|
echo "[*] 删除 nginx 站点 ..."
|
||||||
rm -f /etc/nginx/sites-enabled/panel
|
rm -f /etc/nginx/sites-enabled/panel
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 核对 Reality 密钥是否一致,并验证 Xray 配置
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ENV_FILE="${ROOT}/.env"
|
||||||
|
XRAY_CFG="/usr/local/etc/xray/config.json"
|
||||||
|
SB_CFG="/etc/sing-box/config.json"
|
||||||
|
|
||||||
|
[[ -f "$ENV_FILE" ]] || { echo "缺少 $ENV_FILE"; exit 1; }
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
|
||||||
|
echo "========== .env =========="
|
||||||
|
grep ^REALITY_ "$ENV_FILE" | grep -v PRIVATE || true
|
||||||
|
echo "REALITY_PRIVATE_KEY=***(已隐藏)"
|
||||||
|
|
||||||
|
if command -v xray &>/dev/null && [[ -f "$XRAY_CFG" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "========== Xray config.json =========="
|
||||||
|
ENV_FILE="$ENV_FILE" XRAY_CFG="$XRAY_CFG" python3 - <<'PY'
|
||||||
|
import json, os
|
||||||
|
from pathlib import Path
|
||||||
|
env = {}
|
||||||
|
for line in Path(os.environ["ENV_FILE"]).read_text().splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition("=")
|
||||||
|
env[k.strip()] = v.strip()
|
||||||
|
cfg = json.load(open(os.environ["XRAY_CFG"]))
|
||||||
|
rs = cfg["inbounds"][0]["streamSettings"]["realitySettings"]
|
||||||
|
pk = rs["privateKey"]
|
||||||
|
print("privateKey:", pk[:8] + "..." if pk else "(empty)")
|
||||||
|
print("shortIds:", rs.get("shortIds"))
|
||||||
|
print("serverNames:", rs.get("serverNames"))
|
||||||
|
print("clients:", len(cfg["inbounds"][0]["settings"]["clients"]))
|
||||||
|
match = pk == env.get("REALITY_PRIVATE_KEY", "")
|
||||||
|
print("privateKey 与 .env 一致:", "是" if match else "否 ← 需运行 render-xray.py")
|
||||||
|
PY
|
||||||
|
echo ""
|
||||||
|
echo "========== xray -test =========="
|
||||||
|
xray run -test -c "$XRAY_CFG"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Xray 未安装或配置不存在。VLESS Reality 需 Xray:"
|
||||||
|
echo " bash -c \"\$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)\" @ install"
|
||||||
|
echo " python3 ${ROOT}/scripts/render-xray.py"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$SB_CFG" ]] && grep -q vless-reality "$SB_CFG" 2>/dev/null; then
|
||||||
|
echo ""
|
||||||
|
echo "[!] sing-box 仍含 vless-reality inbound,会与 Xray 争抢 443。"
|
||||||
|
echo " 请运行: python3 ${ROOT}/scripts/render-server.py && systemctl restart sing-box"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v xray &>/dev/null && [[ -n "${REALITY_PRIVATE_KEY:-}" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "========== 公钥配对 =========="
|
||||||
|
DERIVED="$(xray x25519 -i "$REALITY_PRIVATE_KEY" 2>/dev/null | awk '/Public key/ {print $3}')"
|
||||||
|
if [[ -n "$DERIVED" ]]; then
|
||||||
|
if [[ "$DERIVED" == "${REALITY_PUBLIC_KEY:-}" ]]; then
|
||||||
|
echo "公钥与私钥配对: 是"
|
||||||
|
else
|
||||||
|
echo "公钥与私钥配对: 否"
|
||||||
|
echo " .env PUBLIC: ${REALITY_PUBLIC_KEY:-}"
|
||||||
|
echo " 推导 PUBLIC: $DERIVED"
|
||||||
|
echo " 请重新运行 generate-keys.sh 并 render-xray.py"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user