refactor: remove VLESS/Xray, Hy2-only stack

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-28 00:26:18 +08:00
parent c2c8ae826d
commit 6a42f58f5b
27 changed files with 159 additions and 1322 deletions
+2 -29
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# 生成 Reality 密钥;若 .env 无面板密码则一并生成
# 生成面板密码与 Clash API 密钥(如 .env 中尚未配置)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -22,17 +22,6 @@ else
SB="sing-box"
fi
if command -v xray &>/dev/null; then
KEYPAIR="$(xray x25519)"
REALITY_PRIVATE_KEY="$(echo "$KEYPAIR" | awk '/Private key/ {print $3; exit} /PrivateKey/ {print $3; exit}')"
REALITY_PUBLIC_KEY="$(echo "$KEYPAIR" | awk '/Public key/ {print $3; exit} /Password/ {print $3; exit}')"
else
KEYPAIR="$("$SB" generate reality-keypair)"
REALITY_PRIVATE_KEY="$(echo "$KEYPAIR" | grep 'PrivateKey:' | awk '{print $2}')"
REALITY_PUBLIC_KEY="$(echo "$KEYPAIR" | grep 'PublicKey:' | awk '{print $2}')"
fi
REALITY_SHORT_ID="$("$SB" generate rand --hex 8)"
GENERATE_PANEL_PASSWORD=1
if [[ -f "$ENV_FILE" ]] && grep -q "^PANEL_PASSWORD=.\+" "$ENV_FILE" 2>/dev/null; then
GENERATE_PANEL_PASSWORD=0
@@ -43,9 +32,6 @@ if (( GENERATE_PANEL_PASSWORD )); then
fi
echo "========== 生成的密钥 =========="
echo "REALITY_PRIVATE_KEY: $REALITY_PRIVATE_KEY"
echo "REALITY_PUBLIC_KEY: $REALITY_PUBLIC_KEY"
echo "REALITY_SHORT_ID: $REALITY_SHORT_ID"
if (( GENERATE_PANEL_PASSWORD )); then
echo "PANEL_PASSWORD: $PANEL_PASSWORD"
else
@@ -54,14 +40,6 @@ fi
echo "================================"
if [[ -f "$ENV_FILE" ]]; then
for var in REALITY_PRIVATE_KEY REALITY_PUBLIC_KEY REALITY_SHORT_ID; do
val="${!var}"
if grep -q "^${var}=" "$ENV_FILE" 2>/dev/null; then
sed -i "s|^${var}=.*|${var}=${val}|" "$ENV_FILE"
else
echo "${var}=${val}" >> "$ENV_FILE"
fi
done
if (( GENERATE_PANEL_PASSWORD )); then
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
sed -i "s|^PANEL_PASSWORD=.*|PANEL_PASSWORD=${PANEL_PASSWORD}|" "$ENV_FILE"
@@ -73,11 +51,6 @@ if [[ -f "$ENV_FILE" ]]; then
echo "PANEL_USERNAME=dekun" >> "$ENV_FILE"
fi
echo "已写入 $ENV_FILE"
echo ""
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
echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等" >&2
fi
+3 -21
View File
@@ -37,15 +37,8 @@ source "$ENV_FILE"
: "${VPS_IP:?请在 .env 中设置 VPS_IP}"
: "${DOMAIN:?请在 .env 中设置 DOMAIN}"
: "${ACME_EMAIL:?请在 .env 中设置 ACME_EMAIL}"
: "${REALITY_SERVER_NAME:=www.microsoft.com}"
: "${PANEL_USERNAME:=admin}"
if [[ -z "${REALITY_PRIVATE_KEY:-}" ]]; then
log "未检测到 Reality 密钥,运行 generate-keys.sh ..."
bash "$SCRIPT_DIR/generate-keys.sh"
source "$ENV_FILE"
fi
if [[ -z "${PANEL_PASSWORD:-}" ]]; then
PANEL_PASSWORD="$(sing-box generate rand --base64 32 | tr -d '/+=' | head -c 20)"
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
@@ -56,9 +49,6 @@ if [[ -z "${PANEL_PASSWORD:-}" ]]; then
source "$ENV_FILE"
fi
: "${REALITY_PRIVATE_KEY:?}"
: "${REALITY_PUBLIC_KEY:?}"
: "${REALITY_SHORT_ID:?}"
: "${PANEL_PASSWORD:?}"
if [[ -z "${CLASH_API_SECRET:-}" ]]; then
@@ -126,7 +116,6 @@ ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP-ACME-Panel'
ufw allow 443/tcp comment 'Reality'
ufw allow 8443:8499/udp comment 'Hysteria2-multi-node'
ufw --force enable
@@ -187,12 +176,6 @@ log "初始化节点数据库 ..."
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]
@@ -215,7 +198,7 @@ log "创建管理面板 systemd 服务 ..."
cat > /etc/systemd/system/jiedian-panel.service <<UNIT
[Unit]
Description=jiedian admin panel
After=network.target xray.service sing-box.service
After=network.target sing-box.service
[Service]
Type=simple
@@ -232,7 +215,7 @@ WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable xray sing-box jiedian-panel
systemctl enable sing-box jiedian-panel
log "注册证书续期 reload 命令 ..."
/root/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
@@ -241,7 +224,7 @@ log "注册证书续期 reload 命令 ..."
--reloadcmd "systemctl restart sing-box && systemctl reload nginx" \
|| log "acme reloadcmd 注册失败,可忽略"
systemctl restart xray sing-box jiedian-panel
systemctl restart sing-box jiedian-panel
log "部署完成!"
echo ""
@@ -255,6 +238,5 @@ 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"
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# 已有 VPS:将 VLESS Reality 从 sing-box 迁移到 Xray443
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 节点即可。"
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# 已有 VPS:停用 Xray/VLESS,仅保留 Hysteria2
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行: sudo bash scripts/remove-vless.sh"; exit 1; }
export JIEDIAN_ROOT="$ROOT"
echo "[1/4] 停止并禁用 Xray ..."
systemctl stop xray 2>/dev/null || true
systemctl disable xray 2>/dev/null || true
echo "[2/4] 更新代码并重载 sing-box 配置 ..."
if [[ -d "$ROOT/.git" ]]; then
git -C "$ROOT" pull --ff-only || echo "git pull 跳过,请手动同步)"
fi
python3 "$ROOT/scripts/render-server.py"
echo "[3/4] 重启服务 ..."
systemctl restart sing-box jiedian-panel
echo "[4/4] 可选:关闭防火墙 443(若不再需要) ..."
ufw delete allow 443/tcp 2>/dev/null || true
echo ""
echo "完成。VLESS 已停用,面板仅显示 Hysteria2 链接。"
echo "客户端请删除旧 VLESS 节点,从面板复制 hy2:// 链接导入。"
+4 -20
View File
@@ -11,37 +11,21 @@ OUT_DIR="${ROOT_DIR}/client/generated"
# shellcheck disable=SC1090
source "$ENV_FILE"
for var in VPS_IP DOMAIN UUID REALITY_SERVER_NAME REALITY_PUBLIC_KEY REALITY_SHORT_ID HY2_PASSWORD; do
for var in DOMAIN HY2_PASSWORD; do
[[ -n "${!var:-}" ]] || { echo "缺少 .env 变量: $var"; exit 1; }
done
mkdir -p "$OUT_DIR"
urlencode_sni() {
python3 -c "import sys; print(sys.argv[1])" "$1"
}
REALITY_SNI_ENC="$(urlencode_sni "$REALITY_SERVER_NAME")"
REALITY_PBK_ENC="$REALITY_PUBLIC_KEY"
REALITY_SID_ENC="$REALITY_SHORT_ID"
HY2_PASSWORD_ENC="$(python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=''))" "$HY2_PASSWORD")"
DOMAIN_SNI_ENC="$(urlencode_sni "$DOMAIN")"
sed -e "s|\${VPS_IP}|${VPS_IP}|g" \
-e "s|\${DOMAIN}|${DOMAIN}|g" \
-e "s|\${UUID}|${UUID}|g" \
-e "s|\${REALITY_SERVER_NAME}|${REALITY_SERVER_NAME}|g" \
-e "s|\${REALITY_PUBLIC_KEY}|${REALITY_PUBLIC_KEY}|g" \
-e "s|\${REALITY_SHORT_ID}|${REALITY_SHORT_ID}|g" \
sed -e "s|\${DOMAIN}|${DOMAIN}|g" \
-e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \
"$ROOT_DIR/client/sing-box-client.json.template" > "$OUT_DIR/sing-box-client.json"
cat > "$OUT_DIR/share-links.txt" <<EOF
========== VLESS + Reality (主力) ==========
vless://${UUID}@${VPS_IP}:443?type=tcp&security=reality&encryption=none&flow=xtls-rprx-vision&sni=${REALITY_SNI_ENC}&fp=chrome&pbk=${REALITY_PBK_ENC}&sid=${REALITY_SID_ENC}&spx=%2F#Reality-Main
========== Hysteria2 (备用) ==========
hy2://${HY2_PASSWORD_ENC}@${DOMAIN}:8443?sni=${DOMAIN_SNI_ENC}#Hysteria2-Backup
========== Hysteria2 ==========
hy2://${HY2_PASSWORD_ENC}@${DOMAIN}:8443?sni=${DOMAIN}#Hysteria2
EOF
echo "已生成:"
+2 -8
View File
@@ -43,14 +43,8 @@ def load_nodes(db_path: Path) -> list[dict]:
def build_config(env: dict[str, str], nodes: list[dict]) -> dict:
required = [
"REALITY_SHORT_ID",
"REALITY_SERVER_NAME",
"DOMAIN",
]
for key in required:
if not env.get(key):
raise SystemExit(f".env 缺少 {key}")
if not env.get("DOMAIN"):
raise SystemExit(".env 缺少 DOMAIN")
hy2_base_port = 8443
-133
View File
@@ -1,133 +0,0 @@
#!/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": [
{
"tag": "vless-reality-in",
"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()
-39
View File
@@ -1,39 +0,0 @@
#!/usr/bin/env bash
# 一键修复 VLESS Reality:拉代码、重渲染配置、重启服务、诊断
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行: sudo bash scripts/repair-reality.sh"; exit 1; }
export JIEDIAN_ROOT="$ROOT"
echo "[1/5] 更新代码 ..."
if [[ -d "$ROOT/.git" ]]; then
git -C "$ROOT" pull --ff-only || echo "git pull 跳过,请手动同步代码)"
fi
echo "[2/5] 检查 443 监听进程 ..."
if ss -tlnp 2>/dev/null | grep -q ':443'; then
ss -tlnp | grep ':443' || true
else
echo " 443 端口未监听"
fi
if [[ -f /etc/sing-box/config.json ]] && grep -q vless-reality /etc/sing-box/config.json 2>/dev/null; then
echo "[!] sing-box 仍含 VLESS Reality,执行迁移 ..."
bash "$ROOT/scripts/migrate-xray-reality.sh"
else
echo "[3/5] 渲染 Xray + sing-box 配置 ..."
python3 "$ROOT/scripts/render-xray.py"
python3 "$ROOT/scripts/render-server.py"
echo "[4/5] 重启服务 ..."
systemctl restart xray sing-box jiedian-panel
fi
echo "[5/5] 诊断 ..."
bash "$ROOT/scripts/verify-reality.sh"
echo ""
echo "完成。请在面板重新复制 VLESS 链接,删除 v2rayNG 旧节点后重新导入。"
echo "若仍失败,请执行: journalctl -u xray -n 50 --no-pager"
+3 -5
View File
@@ -6,17 +6,16 @@ set -euo pipefail
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
echo "[*] 停止服务 ..."
systemctl stop jiedian-panel xray sing-box 2>/dev/null || true
systemctl disable jiedian-panel xray sing-box 2>/dev/null || true
systemctl stop jiedian-panel sing-box xray 2>/dev/null || true
systemctl disable jiedian-panel sing-box xray 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 / Xray 配置 ..."
echo "[*] 删除 sing-box 配置 ..."
rm -rf /etc/sing-box
rm -f /usr/local/etc/xray/config.json
echo "[*] 删除 nginx 站点 ..."
rm -f /etc/nginx/sites-enabled/panel
@@ -36,5 +35,4 @@ rm -rf "${ROOT}/client/generated"
echo ""
echo "卸载完成。重新安装:"
echo " cd ${ROOT}"
echo " bash scripts/generate-keys.sh # 可选,重置 Reality 密钥与面板密码"
echo " bash scripts/install.sh"
-71
View File
@@ -1,71 +0,0 @@
#!/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; exit} /Password/ {print $3; exit}')"
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