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:
@@ -68,8 +68,10 @@ if [[ -f "$ENV_FILE" ]]; then
|
||||
fi
|
||||
echo "已写入 $ENV_FILE"
|
||||
echo ""
|
||||
echo "重要: 密钥已变更,必须重新生成 sing-box 配置并重启:"
|
||||
echo " python3 ${ROOT_DIR}/scripts/render-server.py && systemctl restart sing-box"
|
||||
echo "重要: 密钥已变更,必须重新生成配置并重启:"
|
||||
echo " python3 ${ROOT_DIR}/scripts/render-xray.py"
|
||||
echo " python3 ${ROOT_DIR}/scripts/render-server.py"
|
||||
echo " systemctl restart xray sing-box"
|
||||
else
|
||||
echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等,再重新运行本脚本" >&2
|
||||
fi
|
||||
|
||||
+11
-4
@@ -184,9 +184,15 @@ python3 -m venv "$ROOT_DIR/panel/venv"
|
||||
log "初始化节点数据库 ..."
|
||||
"$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"
|
||||
|
||||
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 服务 ..."
|
||||
cat > /etc/systemd/system/sing-box.service <<'UNIT'
|
||||
[Unit]
|
||||
@@ -209,7 +215,7 @@ log "创建管理面板 systemd 服务 ..."
|
||||
cat > /etc/systemd/system/jiedian-panel.service <<UNIT
|
||||
[Unit]
|
||||
Description=jiedian admin panel
|
||||
After=network.target sing-box.service
|
||||
After=network.target xray.service sing-box.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -226,7 +232,7 @@ WantedBy=multi-user.target
|
||||
UNIT
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable sing-box jiedian-panel
|
||||
systemctl enable xray sing-box jiedian-panel
|
||||
|
||||
log "注册证书续期 reload 命令 ..."
|
||||
/root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
|
||||
@@ -235,7 +241,7 @@ log "注册证书续期 reload 命令 ..."
|
||||
--reloadcmd "systemctl restart sing-box && systemctl reload nginx" \
|
||||
|| log "acme reloadcmd 注册失败,可忽略"
|
||||
|
||||
systemctl restart sing-box jiedian-panel
|
||||
systemctl restart xray sing-box jiedian-panel
|
||||
|
||||
log "部署完成!"
|
||||
echo ""
|
||||
@@ -249,5 +255,6 @@ echo ""
|
||||
echo "节点链接请在面板中添加/复制。"
|
||||
echo ""
|
||||
log "sing-box: systemctl status sing-box"
|
||||
log "Xray: systemctl status xray"
|
||||
log "面板: systemctl status jiedian-panel"
|
||||
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:
|
||||
required = [
|
||||
"REALITY_PRIVATE_KEY",
|
||||
"REALITY_SHORT_ID",
|
||||
"REALITY_SERVER_NAME",
|
||||
"DOMAIN",
|
||||
@@ -53,32 +52,9 @@ def build_config(env: dict[str, str], nodes: list[dict]) -> dict:
|
||||
if not env.get(key):
|
||||
raise SystemExit(f".env 缺少 {key}")
|
||||
|
||||
vless_users = [{"uuid": n["uuid"], "flow": "xtls-rprx-vision"} for n in nodes]
|
||||
hy2_base_port = 8443
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
inbounds: list[dict] = []
|
||||
for index, node in enumerate(nodes):
|
||||
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; }
|
||||
|
||||
echo "[*] 停止服务 ..."
|
||||
systemctl stop jiedian-panel sing-box 2>/dev/null || true
|
||||
systemctl disable jiedian-panel sing-box 2>/dev/null || true
|
||||
systemctl stop jiedian-panel xray sing-box 2>/dev/null || true
|
||||
systemctl disable jiedian-panel xray sing-box 2>/dev/null || true
|
||||
|
||||
echo "[*] 删除 systemd 单元 ..."
|
||||
rm -f /etc/systemd/system/jiedian-panel.service
|
||||
rm -f /etc/systemd/system/sing-box.service
|
||||
systemctl daemon-reload
|
||||
|
||||
echo "[*] 删除 sing-box 配置 ..."
|
||||
echo "[*] 删除 sing-box / Xray 配置 ..."
|
||||
rm -rf /etc/sing-box
|
||||
rm -f /usr/local/etc/xray/config.json
|
||||
|
||||
echo "[*] 删除 nginx 站点 ..."
|
||||
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