From 6a42f58f5b99623b5e14788b05c8a6cf9b27d697 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 28 Jun 2026 00:26:18 +0800 Subject: [PATCH] refactor: remove VLESS/Xray, Hy2-only stack Co-authored-by: Cursor --- .env.example | 8 - README.md | 77 ++--- client/sing-box-client.json.template | 23 +- docs/DEPLOY.md | 189 ++--------- docs/STACK.md | 43 +-- docs/client-import.md | 155 ++------- docs/troubleshooting.md | 318 ++----------------- panel/__pycache__/db.cpython-310.pyc | Bin 0 -> 5301 bytes panel/__pycache__/links.cpython-310.pyc | Bin 0 -> 2070 bytes panel/__pycache__/nodes_util.cpython-310.pyc | Bin 0 -> 942 bytes panel/app.py | 29 +- panel/links.py | 38 +-- panel/static/app.js | 5 +- panel/stats.py | 54 +--- panel/templates/dashboard.html | 17 +- scripts/generate-keys.sh | 31 +- scripts/install.sh | 24 +- scripts/migrate-xray-reality.sh | 30 -- scripts/remove-vless.sh | 29 ++ scripts/render-client.sh | 24 +- scripts/render-server.py | 10 +- scripts/render-xray.py | 133 -------- scripts/repair-reality.sh | 39 --- scripts/uninstall.sh | 8 +- scripts/verify-reality.sh | 71 ----- server/sing-box.json.template | 70 ---- server/xray-server.json.template | 56 ---- 27 files changed, 159 insertions(+), 1322 deletions(-) create mode 100644 panel/__pycache__/db.cpython-310.pyc create mode 100644 panel/__pycache__/links.cpython-310.pyc create mode 100644 panel/__pycache__/nodes_util.cpython-310.pyc delete mode 100644 scripts/migrate-xray-reality.sh create mode 100644 scripts/remove-vless.sh delete mode 100644 scripts/render-xray.py delete mode 100644 scripts/repair-reality.sh delete mode 100644 scripts/verify-reality.sh delete mode 100644 server/sing-box.json.template delete mode 100644 server/xray-server.json.template diff --git a/.env.example b/.env.example index 0d522f7..37a738b 100644 --- a/.env.example +++ b/.env.example @@ -10,9 +10,6 @@ DOMAIN=66.hyf2.cc # Let's Encrypt 申请证书邮箱 ACME_EMAIL=932465777@qq.com -# Reality 伪装目标(真实大站,不要用你自己的域名) -REALITY_SERVER_NAME=www.microsoft.com - # 管理面板登录(安装完成后访问 http://域名/PANEL_PATH/) PANEL_USERNAME=dekun PANEL_PASSWORD=Woaini521@ @@ -26,8 +23,3 @@ PANEL_PASSWORD=Woaini521@ # sing-box Clash API 密钥(安装时自动生成,供面板读取连接状态) # CLASH_API_SECRET= - -# 以下由 scripts/generate-keys.sh 自动生成,也可手动填写 -# REALITY_PRIVATE_KEY= -# REALITY_PUBLIC_KEY= -# REALITY_SHORT_ID= diff --git a/README.md b/README.md index 404f7f0..3d5d8ad 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # jiedian — VPS 自建节点 -个人/家庭自用的 **VLESS + Reality(主力)** + **Hysteria2(备用)** 双栈方案,带 **Web 管理面板**。 +个人/家庭自用的 **Hysteria2** 方案,带 **Web 管理面板**。 | 组件 | 职责 | |------|------| -| **Xray** | VLESS + Reality(TCP 443) | | **sing-box** | Hysteria2(UDP 8443+,每节点独立端口) | | **Flask 面板** | 节点管理、分享链接、在线/流量统计 | @@ -29,55 +28,46 @@ ssh root@YOUR_VPS_IP apt update && apt install -y git git clone https://git.bz121.com/dekun/jiedian.git /opt/jiedian cd /opt/jiedian -cp .env.example .env # 首次部署:填写 VPS_IP、DOMAIN、ACME_EMAIL +cp .env.example .env # 填写 VPS_IP、DOMAIN、ACME_EMAIL bash scripts/install.sh ``` -安装完成后会显示面板地址、用户名和密码。登录面板即可 **添加节点、复制分享链接**。 +安装完成后登录面板 **添加节点、复制 Hy2 链接**。 --- ## 架构 ``` -浏览器 ──► Nginx:80// ──► Web 管理面板(登录、添加节点) +浏览器 ──► Nginx:80// ──► Web 管理面板 │ ▼ - render-server.py + render-xray.py + render-server.py │ - ┌───────────────┴───────────────┐ - ▼ ▼ - Xray :443 sing-box :8443+ - VLESS + Reality Hysteria2(每节点一端口) + ▼ + sing-box :8443+ + Hysteria2(每节点一端口) 客户端 (Win/iOS/Android) - ├─ TCP 443 ──► Xray VLESS+Reality └─ UDP 8443+ ► sing-box Hysteria2 ``` -> VLESS Reality 使用 **Xray 服务端**,与 v2rayN / v2rayNG(Xray 核心)兼容性最好。Hy2 仍由 sing-box 承载。 - --- ## 目录结构 ``` /opt/jiedian/ -├── .env # VPS / Reality / 面板账号配置 -├── data/nodes.db # 节点数据库(安装后生成) -├── panel/ # Web 管理面板(Flask) +├── .env +├── data/nodes.db +├── panel/ # Flask 管理面板 ├── scripts/ -│ ├── install.sh # 一键部署(Xray + sing-box + 面板) -│ ├── uninstall.sh # 卸载后重装 -│ ├── generate-keys.sh # 生成 Reality 密钥与面板密码 -│ ├── render-xray.py # 生成 Xray VLESS Reality 配置 -│ ├── render-server.py # 生成 sing-box Hy2 配置 -│ ├── migrate-xray-reality.sh # 旧版 sing-box Reality 迁移到 Xray -│ └── verify-reality.sh # Reality 密钥与配置诊断 +│ ├── install.sh +│ ├── uninstall.sh +│ ├── remove-vless.sh # 旧版双栈 VPS 停用 VLESS +│ ├── generate-keys.sh # 生成面板密码 +│ └── render-server.py # 生成 sing-box Hy2 配置 └── docs/ - ├── DEPLOY.md - ├── client-import.md - └── troubleshooting.md ``` --- @@ -87,48 +77,29 @@ bash scripts/install.sh | 端口 | 协议 | 用途 | |------|------|------| | 22 | TCP | SSH | -| 80 | TCP | HTTP(ACME 验证 + **管理面板**) | -| 443 | TCP | VLESS + Reality(**Xray**) | -| 8443–8499 | UDP | Hysteria2(**sing-box**,每节点递增) | +| 80 | TCP | HTTP(ACME + 管理面板) | +| 8443–8499 | UDP | Hysteria2(每节点递增) | -阿里云安全组需放行 **8443–8499/UDP**,不只 8443。 +阿里云安全组需放行 **8443–8499/UDP**。 --- ## 常用运维 ```bash -# 面板地址 grep PANEL_PATH /opt/jiedian/.env +systemctl status sing-box jiedian-panel -# 服务状态 -systemctl status xray sing-box jiedian-panel - -# Reality 诊断 -bash /opt/jiedian/scripts/verify-reality.sh - -# 增删节点后手动重载配置 -python3 /opt/jiedian/scripts/render-xray.py +# 增删节点后手动重载 python3 /opt/jiedian/scripts/render-server.py -systemctl restart xray sing-box +systemctl restart sing-box -# 卸载后干净重装 -bash scripts/uninstall.sh -bash scripts/generate-keys.sh # 可选:重置密钥与面板密码 -bash scripts/install.sh +# 从旧版(含 VLESS)迁移到仅 Hy2 +bash /opt/jiedian/scripts/remove-vless.sh ``` --- -## 防墙要点 - -1. 不要公开分享节点链接 -2. Reality **SNI** 使用 `www.microsoft.com`(或 `.env` 中 `REALITY_SERVER_NAME`),**不要用域名 `66.hyf2.cc`** -3. 客户端开启 uTLS / **chrome** 指纹;v2rayN 中 **SpiderX 填 `/`** -4. 面板路径与密码请妥善保管,安装后可在 `.env` 查看 `PANEL_PATH` / `PANEL_PASSWORD` - ---- - ## 免责声明 本项目仅供学习网络技术使用。请遵守当地法律法规。 diff --git a/client/sing-box-client.json.template b/client/sing-box-client.json.template index a1bd7d7..541c902 100644 --- a/client/sing-box-client.json.template +++ b/client/sing-box-client.json.template @@ -45,28 +45,7 @@ { "type": "selector", "tag": "proxy", - "outbounds": ["reality", "hysteria2", "direct"] - }, - { - "type": "vless", - "tag": "reality", - "server": "${VPS_IP}", - "server_port": 443, - "uuid": "${UUID}", - "flow": "xtls-rprx-vision", - "tls": { - "enabled": true, - "server_name": "${REALITY_SERVER_NAME}", - "utls": { - "enabled": true, - "fingerprint": "chrome" - }, - "reality": { - "enabled": true, - "public_key": "${REALITY_PUBLIC_KEY}", - "short_id": "${REALITY_SHORT_ID}" - } - } + "outbounds": ["hysteria2", "direct"] }, { "type": "hysteria2", diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index cc7bdfa..1707b7b 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -6,206 +6,53 @@ |------|-----| | VPS IP | 你的 VPS 公网 IP | | 域名 | 已解析到 VPS 的域名 | -| 管理面板 | `http://域名//`(**必须 http,不要用 https**) | +| 管理面板 | `http://域名//`(**必须 http**) | | 部署目录 | `/opt/jiedian` | -| 系统 | Ubuntu 22.04 / 24.04 | --- -## 部署前准备 +## 安全组 -### 1. DNS 解析 - -将域名 **A 记录** 指向 VPS 公网 IP(用于 Hy2 证书与面板访问): - -``` -your.domain.com → YOUR_VPS_IP -``` - -验证: - -```bash -dig +short A your.domain.com -# 应返回 VPS IP -``` - -### 2. 阿里云 / 云厂商安全组 - -| 端口 | 协议 | 用途 | 必须 | -|------|------|------|------| -| 22 | TCP | SSH | 是 | -| 80 | TCP | ACME + 管理面板 | 是 | -| 443 | TCP | VLESS Reality(Xray) | 是 | -| 8443–8499 | UDP | Hysteria2(sing-box,多节点递增) | 是 | - -> **注意**:多节点时 Hy2 端口为 8443、8444、8445…,安全组需放行 **8443–8499/UDP**,不能只开 8443。 - -### 3. 填写 `.env`(首次部署) - -```bash -cd /opt/jiedian -cp .env.example .env -nano .env -``` - -至少填写: - -| 变量 | 说明 | -|------|------| -| `VPS_IP` | VPS 公网 IP | -| `DOMAIN` | 域名(Hy2 与证书用) | -| `ACME_EMAIL` | Let's Encrypt 邮箱 | -| `REALITY_SERVER_NAME` | Reality 伪装 SNI,默认 `www.microsoft.com` | - -`REALITY_*` 密钥、`PANEL_PASSWORD`、`PANEL_PATH` 可在安装时由 `generate-keys.sh` / `install.sh` 自动生成。 +| 端口 | 协议 | 用途 | +|------|------|------| +| 22 | TCP | SSH | +| 80 | TCP | ACME + 面板 | +| 8443–8499 | UDP | Hysteria2(多节点递增) | --- -## 一键部署(新机器) +## 一键部署 ```bash apt update && apt install -y git git clone https://git.bz121.com/dekun/jiedian.git /opt/jiedian cd /opt/jiedian -cp .env.example .env -# 编辑 .env 填写 VPS_IP、DOMAIN、ACME_EMAIL +cp .env.example .env # 填写 VPS_IP、DOMAIN、ACME_EMAIL bash scripts/install.sh ``` -安装结束会输出类似: - -``` -管理面板: http://your.domain.com/jiedian-xxxx/ -面板路径: jiedian-xxxx (见 .env 中 PANEL_PATH) -用户名: admin -密码: xxxxx -``` - -浏览器打开面板 → 登录 → **添加节点**(或使用默认节点)→ 复制 **VLESS** / **Hysteria2** 链接到客户端。 +登录面板 → 添加节点 → 复制 **Hysteria2** 链接到客户端。 --- -## 安装脚本做了什么 - -1. 安装 **sing-box**(Hysteria2)、**Xray**(VLESS Reality)、nginx、Python 面板依赖 -2. UFW 放行 22/80/443 TCP 与 8443–8499 UDP -3. acme.sh 为 `DOMAIN` 申请 TLS 证书(供 Hy2 使用) -4. 初始化 SQLite 节点库 + 默认管理员 -5. `render-server.py` → `/etc/sing-box/config.json`(仅 Hy2 inbound) -6. `render-xray.py` → `/usr/local/etc/xray/config.json`(VLESS Reality 443) -7. 启动 **xray**、**sing-box**、**jiedian-panel** -8. Nginx 80 端口子路径反向代理管理面板 - ---- - -## 服务与端口对照 - -| 服务 | 端口 | 协议 | 说明 | -|------|------|------|------| -| **xray** | 443 | TCP | VLESS + Reality,所有节点 UUID 共用 | -| **sing-box** | 8443+ | UDP | Hysteria2,每节点独立端口(按 ID 排序) | -| **jiedian-panel** | 5080 | TCP | 仅本机,经 Nginx 80 对外 | -| **nginx** | 80 | TCP | ACME + 面板 | - -查看监听: - -```bash -ss -tlnp | grep -E ':443|:80|:5080' -ss -ulnp | grep 8443 -systemctl status xray sing-box jiedian-panel -``` - ---- - -## 管理面板功能 - -| 功能 | 说明 | -|------|------| -| 登录 | `.env` 中 `PANEL_USERNAME` / `PANEL_PASSWORD` | -| 添加节点 | 自动生成 UUID + Hy2 密码,后台更新 Xray + sing-box 配置 | -| 复制链接 | VLESS Reality + Hysteria2(Hy2 端口随节点自动变化) | -| 删除节点 | 至少保留 1 个节点 | -| 连接状态 | 在线/离线、连接数 | -| 流量统计 | 实时速率 + 累计上下行 | - ---- - -## 部署后验证 - -```bash -# 服务 -systemctl is-active xray sing-box jiedian-panel - -# 配置语法 -xray run -test -c /usr/local/etc/xray/config.json -sing-box check -c /etc/sing-box/config.json - -# Reality 密钥是否一致 -bash /opt/jiedian/scripts/verify-reality.sh - -# 面板可访问 -PANEL_PATH=$(grep ^PANEL_PATH= /opt/jiedian/.env | cut -d= -f2) -curl -I "http://$(grep ^DOMAIN= /opt/jiedian/.env | cut -d= -f2)/${PANEL_PATH}/login" -``` - -客户端:导入面板复制的 **VLESS** 链接,v2rayN 测速应显示延迟(非 `-1`)。详见 [client-import.md](client-import.md)。 - ---- - -## 卸载后重装(推荐流程) - -若之前部署混乱,先卸载再装: +## 从旧版(含 VLESS)升级 ```bash cd /opt/jiedian git pull -bash scripts/uninstall.sh -bash scripts/generate-keys.sh # 重新生成 Reality 密钥与面板密码 -bash scripts/install.sh +sudo bash scripts/remove-vless.sh ``` -`uninstall.sh` 会停止服务并清理配置,**保留** `/opt/jiedian` 代码与 `.env` 基础字段。 +会停用 Xray、更新面板,仅保留 Hy2。 --- -## 从旧版升级(sing-box 跑 Reality → Xray) - -若你之前用 sing-box 监听 443 且 v2rayN Reality 一直 `-1`,拉代码后执行: +## 常用命令 ```bash -cd /opt/jiedian && git pull -bash scripts/migrate-xray-reality.sh +systemctl status sing-box jiedian-panel +python3 /opt/jiedian/scripts/render-server.py +systemctl restart sing-box ``` -客户端 **无需改参数**,直接重测 VLESS 节点即可。 - ---- - -## 增删节点后的配置 - -面板会自动后台执行 `render-xray.py`、`render-server.py` 并重启服务。若需手动: - -```bash -cd /opt/jiedian -python3 scripts/render-xray.py -python3 scripts/render-server.py -systemctl restart xray sing-box -``` - -修改 Reality 密钥后(`generate-keys.sh`)也必须执行上述命令。 - ---- - -## 故障排查速查 - -| 问题 | 处理 | -|------|------| -| 面板 404 | URL 须含完整 `PANEL_PATH`,见 `grep PANEL_PATH .env` | -| 面板 Invalid URL / [No Host] | 用 **http://** 访问,不要用 https | -| VLESS 测速 `-1` | `bash scripts/verify-reality.sh`;确认未用 https 访问面板 | -| Hy2 不通 | 安全组放行 **8443–8499/UDP**;重新复制面板 Hy2 链接 | -| apt 锁被占用 | 等待自动更新结束,`install.sh` 会自动等待 | -| 443 被占用 | `ss -tlnp \| grep 443`,应为 **xray** | -| 忘记面板密码/路径 | `grep PANEL_ /opt/jiedian/.env` 或 `generate-keys.sh` | - -更多见 [troubleshooting.md](troubleshooting.md)。 +客户端导入见 [client-import.md](client-import.md)。 diff --git a/docs/STACK.md b/docs/STACK.md index 0783044..0494353 100644 --- a/docs/STACK.md +++ b/docs/STACK.md @@ -1,37 +1,24 @@ -# 方案决策记录 +# 技术栈说明 -## 已确认选型 +## 组件分工 -| 项目 | 选择 | 理由 | -|------|------|------| -| 协议栈 | **VLESS + Reality + Hysteria2 双栈** | Reality 抗主动探测;Hysteria2 作 UDP 备用与流媒体 | -| Reality 服务端 | **Xray** | 与 v2rayN / v2rayNG(Xray 核心)兼容性最好;sing-box Reality 易出现 `processed invalid connection` | -| Hy2 服务端 | **sing-box** | 原生 Hysteria2、Clash API 统计、多 inbound 按节点分端口 | -| 系统 | **Ubuntu 22.04/24.04** | 脚本基于 apt,其他发行版需手动适配 | -| 面板 | **Flask Web 面板** | 添加/删除节点、复制链接、在线与流量统计 | +| 组件 | 用途 | +|------|------| +| **sing-box** | Hysteria2 服务端、Clash API 统计、多 inbound 按节点分端口 | +| **Nginx** | ACME 验证、管理面板反代(HTTP 80) | +| **Flask 面板** | 添加/删除节点、复制 Hy2 链接、在线与流量统计 | -## 端口规划 +## 端口 -| 端口 | 协议 | 服务 | 用途 | +| 端口 | 协议 | 进程 | 说明 | |------|------|------|------| -| 443 | TCP | **Xray** | VLESS + Reality(主力) | -| 8443–8499 | UDP | **sing-box** | Hysteria2(每节点递增:8443、8444…) | -| 80 | TCP | nginx | ACME 验证 + 管理面板反向代理 | -| 127.0.0.1:5080 | TCP | Flask | 面板后端(不对外暴露) | -| 127.0.0.1:9090 | TCP | sing-box | Clash API(面板统计 Hy2) | +| 80 | TCP | Nginx | ACME + 面板 | +| 8443–8499 | UDP | sing-box | Hy2,每节点 +1 | ## 配置生成 -| 脚本 | 输出 | 内容 | -|------|------|------| -| `render-xray.py` | `/usr/local/etc/xray/config.json` | 所有启用节点的 VLESS UUID + Reality | -| `render-server.py` | `/etc/sing-box/config.json` | 每节点独立 Hy2 inbound + Clash API | +| 脚本 | 输出 | +|------|------| +| `render-server.py` | `/etc/sing-box/config.json` | -增删节点或轮换 Reality 密钥后,两个脚本都需重新运行并 `systemctl restart xray sing-box`。 - -## 单协议简化 - -- **仅 Reality**:删除 `render-server.py` 中 Hy2 inbound 逻辑,跳过 acme 证书步骤(需改代码)。 -- **仅 Hy2**:不安装 Xray,删除 443 inbound(不推荐,失去 TCP 主力)。 - -当前默认 **双栈**,与 README / DEPLOY 一致。 +增删节点后运行 `render-server.py` 并 `systemctl restart sing-box`。 diff --git a/docs/client-import.md b/docs/client-import.md index ef8cd73..52cb87b 100644 --- a/docs/client-import.md +++ b/docs/client-import.md @@ -1,154 +1,43 @@ -# 客户端导入与测试指南 +# 客户端导入 -部署完成后,在 **管理面板** 复制各节点的 VLESS / Hy2 链接;或本地运行 `bash scripts/render-client.sh` 生成 `client/generated/share-links.txt`。 +部署完成后,在 **管理面板** 复制各节点的 `hy2://` 链接。 --- -## 一、连通性测试(推荐顺序) +## Windows(v2rayN) -### 1. Reality(TCP 443,Xray) - -在**国内网络**下测试(不要在 VPS 本机 curl 自己): - -```powershell -# Windows PowerShell — 仅测端口是否可达 -Test-NetConnection -ComputerName YOUR_VPS_IP -Port 443 -``` - -v2rayN 中对 VLESS 节点 **右键 → 测试真链接延迟**,应显示毫秒数(不是 `-1`)。 - -连上后访问 https://www.google.com 或 https://ip.sb 确认出口 IP 为 VPS。 - -### 2. Hysteria2(UDP 8443+) - -- **第一个节点**:UDP **8443** -- **第二个节点**:UDP **8444**(依此类推) - -UDP 无法用普通 TCP 工具测。从面板 **重新复制** Hy2 链接(多节点升级后端口可能已变),导入客户端测试。 - -若 Reality 可用但 Hysteria2 不通,可能是运营商 QoS/封锁 UDP,可继续只用 Reality。 - -### 3. 故障判断 - -| 现象 | 可能原因 | -|------|----------| -| ping 通 IP,VLESS 测速 `-1` | Reality 参数或密钥不同步 → `bash scripts/verify-reality.sh` | -| Hy2 不通,VLESS 正常 | UDP 未放行或端口错误(需 8443–8499/UDP) | -| ping 不通 | IP 可能被封,考虑换 IP | -| 连接成功但无网 | 检查防火墙、`journalctl -u xray -f` | +1. 下载 [v2rayN](https://github.com/2dust/v2rayN/releases) +2. **服务器 → 从剪贴板导入批量 URL**,粘贴面板复制的 `hy2://` 链接 +3. 设为活动服务器并连接 --- -## 二、Windows +## Android(v2rayNG) -### 方案 A:v2rayN(推荐) - -1. 下载 [v2rayN](https://github.com/2dust/v2rayN/releases)(选 `v2rayN-With-Core.zip` 或自带 Xray 的版本) -2. **设置 → 核心类型**:VLESS 使用 **Xray** 核心(不要用 sing-box 核心跑 Reality) -3. 托盘 → **服务器** → **从剪贴板导入批量 URL** -4. 从面板复制 `vless://...` 与 `hy2://...` 分别导入 -5. 设为活动服务器,路由选 **绕过大陆** - -**手动核对 Reality 参数:** - -| 字段 | 值 | -|------|-----| -| 地址 | VPS IP(或域名,SNI 仍用 microsoft) | -| 端口 | 443 | -| 用户 ID | 面板节点 UUID | -| 流控 | xtls-rprx-vision | -| 传输 | tcp / raw(Xray 26+ 显示 raw 正常) | -| 安全 | reality | -| SNI | `www.microsoft.com`(**不要用 Hy2 域名**) | -| Fingerprint | chrome | -| Public Key | `.env` 的 `REALITY_PUBLIC_KEY` | -| Short ID | `.env` 的 `REALITY_SHORT_ID` | -| **SpiderX** | **`/`**(空着可能连不上) | - -> **常见错误**:Hy2 的 SNI 是 `66.hyf2.cc`,Reality 的 SNI 必须是 `www.microsoft.com`,两者不要混用。 - -### 方案 B:sing-box 客户端 - -1. 下载 [sing-box for Windows](https://github.com/SagerNet/sing-box/releases) -2. 将 `client/generated/sing-box-client.json` 放入配置目录 -3. 以管理员运行(TUN 模式需要),选择 `reality` 出站 - -### 方案 C:Nekoray - -1. 下载 [Nekoray](https://github.com/MatsuriDayo/nekoray/releases) -2. **Program → Add profile from clipboard**,粘贴 `vless://` 链接 -3. 右键 → **Start** +1. 安装 [v2rayNG](https://github.com/2dust/v2rayNG/releases)(arm64-v8a) +2. **+ → 从剪贴板导入** +3. 连接后开启 VPN --- -## 三、Android +## iOS(Shadowrocket / Streisand) -### v2rayNG(推荐) - -1. [GitHub Releases](https://github.com/2dust/v2rayNG/releases) 安装 **arm64-v8a** APK(多数新机) -2. 右上角 **+** → **从剪贴板导入**(先复制面板 `vless://` 链接) -3. 再导入 `hy2://` 备用节点 -4. 点击右下角 **V** 连接 -5. 设置 → **路由设置** → **绕过局域网及大陆地址** +**+ → 从剪贴板导入** `hy2://` 链接。 --- -## 四、iOS - -需要美区 Apple ID 或已有购买记录。 - -### Shadowrocket(小火箭) - -1. App Store 安装 Shadowrocket -2. **+** → 从剪贴板导入 `vless://` 链接 -3. 再添加 `hy2://` 备用 -4. 连接后 **连通性测试** 应显示延迟 - -**手动添加 Reality:** - -- 类型:VLESS -- 地址:VPS IP -- 端口:443 -- UUID:面板中的 UUID -- TLS:REALITY -- SNI:`REALITY_SERVER_NAME`(默认 `www.microsoft.com`) -- Public Key / Short ID:从 `.env` 或面板链接复制 -- uTLS:chrome -- Flow:xtls-rprx-vision - -### Streisand - -**+** → **Import from Clipboard**,粘贴链接后连接。 - ---- - -## 五、macOS - -与 Windows 类似:V2rayU / Nekoray / sing-box,从剪贴板导入面板链接。 - ---- - -## 六、日常使用建议 - -1. **默认节点**:Reality(`vless://`) -2. **备用节点**:Hysteria2(UDP 卡顿时切换) -3. **不要分享**节点链接 -4. **每月检查**:`systemctl status xray sing-box`、证书续期(acme.sh 自动) - ---- - -## 七、分享链接格式参考 - -面板生成的 VLESS 链接示例: - -``` -vless://UUID@IP:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=SNI&fp=chrome&pbk=PUBLIC_KEY&sid=SHORT_ID&spx=%2F&type=tcp#名称 -``` - -Hy2(端口随节点变化,以面板为准): +## 链接格式 ``` hy2://PASSWORD@DOMAIN:8443?sni=DOMAIN#名称-Hy2 ``` -完整链接见面板或 `client/generated/share-links.txt`。 +第二节点端口为 **8444**,依此类推,以面板显示为准。 + +--- + +## 注意 + +- **一设备一节点**,不要多人共用同一链接 +- 多节点升级后请 **重新复制** Hy2 链接(端口可能变化) +- SNI 为域名(如 `66.hyf2.cc`),不是 IP diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index fffe124..ac9e266 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -3,323 +3,43 @@ ## 服务检查 ```bash -# 核心服务 -systemctl is-active xray # VLESS Reality 443 -systemctl is-active sing-box # Hysteria2 8443+ -systemctl is-active jiedian-panel - -# 配置语法 -xray run -test -c /usr/local/etc/xray/config.json +systemctl is-active sing-box jiedian-panel sing-box check -c /etc/sing-box/config.json - -# Reality 密钥诊断(推荐首选) -bash /opt/jiedian/scripts/verify-reality.sh - -# 端口监听 -ss -tlnp | grep 443 # 应为 xray(VLESS Reality) -ss -tlnp | grep 5080 # 面板后端(仅本机) -ss -tlnp | grep :80 # Nginx(ACME + 面板) -ss -ulnp | grep 8443 # sing-box Hy2(多节点还有 8444…) +ss -ulnp | grep 8443 ``` -## 架构速查 - -| 组件 | 端口 | 说明 | -|------|------|------| -| **Xray** | 443/TCP | VLESS + Reality | -| **sing-box** | 8443+/UDP | Hysteria2,每节点独立端口 | -| **面板** | 80/TCP(Nginx 反代) | `http://域名/PANEL_PATH/` | - -> **443 由 Xray 占用**(不是 Web 面板)。面板走 **80** 端口子路径,必须用 `http://` 访问。 - ---- - -## 管理面板访问 - -```bash -grep PANEL_PATH /opt/jiedian/.env -curl -I "http://66.hyf2.cc/$(grep ^PANEL_PATH= /opt/jiedian/.env | cut -d= -f2)/login" -``` - -面板统计:Hy2 来自 sing-box Clash API;VLESS 在线部分来自 Xray `access.log`。 - -> GitHub 官方 sing-box 预编译包**不含** `v2ray_api`,配置里不要启用 `experimental.v2ray_api`。 - --- ## 常见问题 -### VLESS Reality 测速 `-1` / 无法连接 +### Hy2 连不上 -**现象**:Hy2 正常,VLESS 延迟 `-1`;或 Xray / sing-box 日志出现 `REALITY: processed invalid connection`。 +1. 安全组放行 **8443–8499/UDP** +2. 域名解析到 VPS IP +3. 从面板 **重新复制** Hy2 链接(多节点端口 8443、8444…) +4. `systemctl restart sing-box` -**原因(按概率)**: +### 面板打不开 -1. **旧版仍用 sing-box 跑 443** → 与 v2rayN(Xray 核心)不兼容 -2. **`.env` 私钥与 `/usr/local/etc/xray/config.json` 不一致**(改过密钥未 render) -3. **SNI 填错**:Reality 必须 `www.microsoft.com`,不能填 Hy2 域名 -4. **SpiderX 为空**:v2rayN 中填 **`/`** -5. **公钥私钥不配对**:`.env` 中 PUBLIC/PRIVATE 不是同一批 `generate-keys.sh` 生成 +- 使用 **`http://域名/PANEL_PATH/`**,不要用 https +- 443 未用于面板,面板在 **80** 端口 -**处理**: +### 增删节点后 503 -```bash -cd /opt/jiedian -git pull +等 5–10 秒刷新;sing-box 重启期间可能短暂不可用。 -# 新架构:迁移到 Xray(只需执行一次) -bash scripts/migrate-xray-reality.sh - -# 或已迁移后仅重载配置 -python3 scripts/render-xray.py -python3 scripts/render-server.py -systemctl restart xray sing-box -``` - -**诊断**: - -```bash -bash scripts/verify-reality.sh -journalctl -u xray -n 30 --no-pager -``` - -**客户端核对**(v2rayN): - -| 项 | 值 | -|----|-----| -| 地址 | VPS IP | -| 端口 | 443 | -| SNI | `www.microsoft.com` | -| 流控 | xtls-rprx-vision | -| SpiderX | `/` | -| 核心 | Xray(非 sing-box) | - -从面板 **重新复制** VLESS 链接导入,不要手改旧节点。 - ---- - -### 面板报 Invalid URL / [No Host] - -**原因**:用了 `https://域名/面板路径/` 访问,或 CDN 回源 Host 异常。 - -- 443 是 **Xray Reality**,不是 Web -- 面板必须是:**`http://域名/jiedian-xxxx/`** +### 从旧版 VLESS 迁移 ```bash cd /opt/jiedian && git pull -# 重载 nginx 与面板(见 DEPLOY.md) -systemctl restart jiedian-panel -``` - -域名建议 **关闭 CDN**,仅 DNS 解析到 VPS。 - ---- - -### 多节点全部显示离线 - -sing-box Clash API **不返回用户 UUID**;多节点 Hy2 按 **inbound 端口** 区分。 - -1. 更新并重载: - ```bash - cd /opt/jiedian && git pull - python3 scripts/render-xray.py - python3 scripts/render-server.py - systemctl restart xray sing-box jiedian-panel - ``` -2. **安全组** 放行 `8443-8499/UDP` -3. 面板 **重新复制** 各节点 Hy2 链接(第二节点端口为 8444…) -4. VLESS 多用户在线依赖 Xray access.log + sing-box 日志 - ---- - -### 添加/删除节点后白屏或 503 - -创建/删除节点会后台重启 xray + sing-box。等 5–10 秒后刷新;新版前端会自动重试。 - ---- - -### 在线节点 / 统计一直显示「检测中」 - -页面初始为「检测中」。若长期 `-` 且无数据: - -```bash -cd /opt/jiedian && git pull -systemctl restart jiedian-panel -``` - -浏览器 **Ctrl+F5**;F12 → Network 确认 `.../PANEL_PATH/static/app.js` 与 `.../api/stats` 均为 200。 - ---- - -### 面板流量/在线状态显示「不可用」 - -旧版 `/api/stats` 会同步读 Clash `/traffic`(WebSocket)导致 504。更新后已改为只读 `/connections`: - -```bash -cd /opt/jiedian && git pull -systemctl restart jiedian-panel -``` - -若仍不可用: - -```bash -ss -tlnp | grep 9090 -python3 scripts/render-server.py -systemctl restart sing-box jiedian-panel +sudo bash scripts/remove-vless.sh ``` --- -### sing-box 报错 v2ray api is not included +## 端口一览 -重新生成配置(已移除 v2ray_api): - -```bash -python3 /opt/jiedian/scripts/render-server.py -systemctl restart sing-box -``` - ---- - -### Xray 无法启动 - -```bash -journalctl -u xray -n 50 --no-pager -xray run -test -c /usr/local/etc/xray/config.json -ss -tlnp | grep :443 -``` - -常见原因:443 被其他进程占用、JSON 错误、私钥格式错误。 - -若 sing-box 仍监听 443,说明未迁移: - -```bash -bash /opt/jiedian/scripts/migrate-xray-reality.sh -``` - ---- - -### sing-box 无法启动 - -```bash -journalctl -u sing-box -n 50 --no-pager -sing-box check -c /etc/sing-box/config.json -``` - -常见原因:Hy2 证书路径错误、8443+ UDP 配置错误。443 不应再被 sing-box 占用。 - ---- - -### 面板打不开 / 404 - -1. URL 含完整 `PANEL_PATH`,末尾 `/` -2. `systemctl status jiedian-panel` -3. 安全组放行 **80/TCP**(无需 8444) - ---- - -### 忘记面板密码或路径 - -```bash -grep -E 'PANEL_(USERNAME|PASSWORD|PATH)' /opt/jiedian/.env -bash scripts/generate-keys.sh # 会轮换 Reality 密钥,之后必须 render + restart -python3 scripts/render-xray.py -python3 scripts/render-server.py -systemctl restart xray sing-box -``` - ---- - -### acme.sh 证书申请失败 - -```bash -dig +short A your.domain.com -ss -tlnp | grep :80 -/root/.acme.sh/acme.sh --issue -d your.domain.com -w /var/www/acme --force -``` - -证书仅用于 **Hy2**;Reality 不使用域名证书。 - ---- - -### 客户端能连但速度慢 - -- 换 Hysteria2(UDP) -- 检查带宽:`wget -O /dev/null http://speedtest.tele2.net/100MB.zip` - ---- - -### IP 被封 - -1. 向 VPS 商更换 IP,更新 `.env` 中 `VPS_IP` -2. 修改 `REALITY_SERVER_NAME`(如 `www.apple.com`) -3. `generate-keys.sh` → `render-xray.py` → `restart xray` - ---- - -### apt 锁被占用 - -安装脚本会自动等待。超时后: - -```bash -fuser /var/lib/dpkg/lock-frontend -bash scripts/install.sh -``` - ---- - -### SSH 主机密钥变更 - -VPS 重装后本地: - -```bash -ssh-keygen -R YOUR_VPS_IP -``` - ---- - -### 卸载后重装 - -```bash -cd /opt/jiedian -git pull -bash scripts/uninstall.sh -bash scripts/generate-keys.sh -bash scripts/install.sh -``` - ---- - -### 从旧版 sing-box Reality 升级到 Xray - -```bash -cd /opt/jiedian && git pull -bash scripts/migrate-xray-reality.sh -``` - -客户端参数不变,重测 VLESS 即可。 - ---- - -## 防火墙与安全组对照 - -| 端口 | 协议 | 用途 | 必须放行 | -|------|------|------|----------| -| 22 | TCP | SSH | 是 | -| 80 | TCP | ACME + **管理面板** | 是 | -| 443 | TCP | VLESS + Reality(**Xray**) | 是 | -| 8443–8499 | UDP | Hysteria2(**sing-box**,每节点递增) | 是 | -| ~~8444~~ | ~~TCP~~ | ~~旧版面板 HTTPS~~ | **已废弃** | - -## 安全建议 - -- 使用随机 `PANEL_PATH`,不要公开分享面板地址 -- 可选 `PANEL_ALLOW_IP=你的公网IP` 限制面板访问 -- SSH 密钥登录,禁用密码登录 -- 不要将 `.env` 或节点链接上传到公开仓库 - -## 相关文档 - -- 新部署:[DEPLOY.md](DEPLOY.md) -- 客户端导入:[client-import.md](client-import.md) -- 架构说明:[STACK.md](STACK.md) +| 端口 | 协议 | 用途 | +|------|------|------| +| 80 | TCP | 面板 + ACME | +| 8443–8499 | UDP | Hysteria2 | diff --git a/panel/__pycache__/db.cpython-310.pyc b/panel/__pycache__/db.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8df6e0400e20531213b4b19492bd81ee0b35529c GIT binary patch literal 5301 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 literal 0 HcmV?d00001 diff --git a/panel/__pycache__/links.cpython-310.pyc b/panel/__pycache__/links.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f284b5e16c181e4d07cd0d9b9c94fc3fa7eed78 GIT binary patch literal 2070 zcmZWq&u`R56!whA_WlY90Rkb=ZlSESDp{nIR#nOtQj&_Lu!#~t!cAm3I}_r>Ynz$b zB)jrT6+smjs!Ht*P_u^~s{RK(^v~$QS6V8r?V-|2G=1Zc7Fw_N%{R~U-n@D9-sf;& zAg96e$L|-{MlzcAR83xe2uv<%8WKJLs@WPv6#J-&ZHyGwecje!BtB^xwvp-#-)vg8 zm15J+G_!U##g?CI=IuP-qclhJ_laGg$LIk%2xx$g(&O|99l3AV2dKWFmB(5~V9Q@0 zeDTYVKRo~Xr)OV&_uDt$K6~)h)6efchZkBIC3!apLg`8`3`8B~WX_eVFdBDTp=1DZ zz9*y;gp>)GWmel0P84#Pn4HNL5BdkF*N~qH$$YCXJ-x|*kTQLIsYOk1-kp~geBlj&*G+R~{mj`2>mr>T*T1I|&i zhr~FwV!VNPig`MThrH*7c7mM5*l_(8`(EQ?5KDjj^5o)8!MIqYY`qmMPBXDC!)S5MW7Knl z#mEhqzX%T2MI~w{89#(zz-a-D#W08>BIp>(pb>=85E_OVKM87n97Li+kPVfY{YPO{ z$mvxoAF;NR7IF_mQuWT%jLH}*(ol+`7ZexjwGWFTY_ej*_f$HHO;5lv1*1g~c-76# z%}Ud2@KA&+veF2fl>}8f`k9FtNI&E<^4$iMUm8t%;5~5yc1|n@JUDm7F_;JpqJa5Z z>IG3{nh;fyd;}Ezyn@BJSPui%fAh6U1?_9|rKN^)wg2P+sAHz*{TV=vA|kbIRiKEX zQB{c0#xUwJ+JPpfq=&l%bOz`Q7)=;WiJ_dWQ4NaQl6tI)Ufagq49$Q(JE}d>XfDPe z^U{c+ApjL(1LQz#f;dXNr3R2I2d2YD#YfIJM=dTM<{Sr=IQa%g^L>RRo_ zZD(O-{?^RAQ=hs%(>eIR3rmLg*UjDh_*(6<^Xbg(WN2TvaAkJ>hEtn{J8_$=liBc@ z5R>Q5C8X8zXdhkb)F#iLfA^wnN31FvQAw~yi+i$Ng|2~KX_ue{c{@@~Q4QewN-KW2 zS>2Mp80Qh+8sG2)yeg?$>I{}vqH1H6L%Efr<@IXEC<%{NJK2(mwyI|)J~`bnfw%=lLmxRZ-jKMkE|BvP~K`T`#cnFEggP!p=plW#4N!z!`Q%tU^eRNBKL-ctVNu zO1!6pVv$#xD!<(FeCnixp?E-C1(5@O$e_YYSwO?Y#N;o$|JTV2yj!$PM0PPUckwPZ zFv1uxaLT_w@~8QNzYp%pm|JRB_)2PJve(;hC`GDOsEq26i}b9g)eCiC{`Nmh&Zs4W zKmZO68$h}_px+jJhJb*%>RvtoLNTyh^}XekwCzHR`!K1beO<9#JZX|_`K$y{iLwB@sp6eWtoAczJT!Uf1v0c1|k6^4wz0qx);!2kdN literal 0 HcmV?d00001 diff --git a/panel/__pycache__/nodes_util.cpython-310.pyc b/panel/__pycache__/nodes_util.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..448e000caddee299891a5c1f9f62a53bcca6709c GIT binary patch literal 942 zcmZWn&rj4q6rP#RZVT=LiHKrM-0L3Tz)eF|6FBb$y^xxw>>IRfw_T>wL`X>tkoY4} zFP=0;jUFUMy=o->A-y6lp1A8l-|HeG%x31j>Ar8i_kA;QwK~XfEbm`g+U1Px(B|kE zz|B>h$u(Sn%`@PND>2SM^y8ug3*@Gpw_pg$N*w-gD~UC7(E$erHcRs*7%aY&5pfDC zh_InL$LhoBB|LiX!Fq4~bMM{9-N&!LeR@<2qJ=n(p!WUc-QN4J``h<+-#k62`SN1# z!>ittxBJ`ockZm`r6>lKDLl@TdiOy zC$TmPyV0bwXrsS4xXr8ookJcJ3?p2Vsvo~rSME@$YmrnBo-KA@tsqqJ{S(Ss(6 zdPxsq=t~&JEH4ESsN1;=jOrX0<=k4C^13LSBqsE3NBKEk;o`Rx`ZS?!lH>(&;!?@p zLJy&UXY3h!PWLaGt2UlG5kwOO8`?RF6~~!}iTU73dd9y=trmt3a(xE#CgQ3X>rQTa z9>gur%ViiOCYKPj%s;{XHm$_y0(XRm-jOcu&YAASkcO&37n{&0$YbDzA literal 0 HcmV?d00001 diff --git a/panel/app.py b/panel/app.py index 948d11e..143385e 100644 --- a/panel/app.py +++ b/panel/app.py @@ -27,7 +27,6 @@ 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() @@ -114,24 +113,9 @@ def render_singbox_config() -> tuple[bool, str]: return True, "ok" -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。""" +def restart_singbox_async() -> None: subprocess.Popen( - ["systemctl", "restart", "xray", "sing-box"], + ["systemctl", "restart", "sing-box"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) @@ -141,17 +125,10 @@ def apply_singbox() -> tuple[bool, str]: ok, msg = render_singbox_config() if not ok: return False, msg - ok, msg = render_xray_config() - if not ok: - return False, msg - restart_services_async() + restart_singbox_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。""" diff --git a/panel/links.py b/panel/links.py index d2e737c..bf8c462 100644 --- a/panel/links.py +++ b/panel/links.py @@ -1,4 +1,4 @@ -"""分享链接生成。""" +"""分享链接生成(Hysteria2)。""" from __future__ import annotations import os @@ -21,44 +21,14 @@ def load_env() -> dict[str, str]: return env -def _sni_qs(value: str) -> str: - """SNI in share links — use plain domain; clients decode URL themselves.""" - return value - - -def build_links(node: dict, env: dict | None = None) -> dict[str, str | dict[str, str]]: +def build_links(node: dict, env: dict | None = None) -> dict[str, str]: env = env or load_env() - vps_ip = env["VPS_IP"] domain = env["DOMAIN"] - reality_sni = env.get("REALITY_SERVER_NAME", "www.microsoft.com") - public_key = env["REALITY_PUBLIC_KEY"] - short_id = env["REALITY_SHORT_ID"] name = quote(node["name"]) port = hy2_port(node, list_nodes()) - # Parameter order follows Xray VLESS share-link convention; pbk/sid stay raw. - vless = ( - f"vless://{node['uuid']}@{vps_ip}:443" - f"?type=tcp&security=reality&encryption=none&flow=xtls-rprx-vision" - f"&sni={_sni_qs(reality_sni)}&fp=chrome&pbk={public_key}&sid={short_id}" - f"&spx=%2F#{name}" - ) hy2 = ( f"hy2://{quote(node['hy2_password'], safe='')}@{domain}:{port}" - f"?sni={_sni_qs(domain)}#{name}-Hy2" + f"?sni={domain}#{name}-Hy2" ) - return { - "vless": vless, - "hy2": hy2, - "meta": { - "address": vps_ip, - "port": "443", - "uuid": node["uuid"], - "sni": reality_sni, - "pbk": public_key, - "sid": short_id, - "spx": "/", - "fp": "chrome", - "flow": "xtls-rprx-vision", - }, - } + return {"hy2": hy2} diff --git a/panel/static/app.js b/panel/static/app.js index f9a19dc..ca065d8 100644 --- a/panel/static/app.js +++ b/panel/static/app.js @@ -72,15 +72,14 @@ document.querySelectorAll(".copy-row").forEach((row) => { btn.addEventListener("click", async () => { const card = row.closest(".node-card"); const nodeId = card?.dataset.id; - const kind = btn.dataset.linkKind || input.dataset.linkKind || "vless"; let text = input.value; if (nodeId) { try { const nodes = await fetchNodeLinks(); const node = nodes.find((item) => String(item.id) === String(nodeId)); - if (node?.links) { - text = kind === "hy2" ? node.links.hy2 : node.links.vless; + if (node?.links?.hy2) { + text = node.links.hy2; } } catch { /* fall back to input.value */ diff --git a/panel/stats.py b/panel/stats.py index d724b7e..b9b501d 100644 --- a/panel/stats.py +++ b/panel/stats.py @@ -1,4 +1,4 @@ -"""从 sing-box Clash API 采集节点连接与流量(官方预编译包不含 v2ray_api)。""" +"""从 sing-box Clash API 采集节点连接与流量。""" from __future__ import annotations import json @@ -18,15 +18,10 @@ ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1])) 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]] = {} @@ -76,28 +71,8 @@ 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。""" + """sing-box Clash API 不导出 user 字段,多节点 Hy2 从近期日志补全在线 UUID。""" try: proc = subprocess.run( [ @@ -134,7 +109,6 @@ def fetch_recent_log_uuids(nodes: list[dict]) -> set[str]: def _global_conn_speed(connections: list[dict]) -> tuple[float, float]: - """从 /connections 汇总字节增量估算全局速率(/traffic 为 WebSocket 流,不能同步 HTTP 读)。""" total_up = sum(int(c.get("upload") or 0) for c in connections) total_down = sum(int(c.get("download") or 0) for c in connections) now = time.time() @@ -193,17 +167,6 @@ def _match_connection(conn: dict, node: dict, *, single_node: bool = False) -> b return False -def _match_vless_connection(conn: dict, node: dict, log_active: set[str]) -> bool: - tag = _connection_inbound_tag(conn) - if tag != _VLESS_INBOUND: - return False - user = _connection_user(conn) - if user == node["uuid"]: - return True - # 共享 VLESS inbound 无法从 Clash API 区分用户;仅唯一活跃用户时归因 - return node["uuid"] in log_active and len(log_active) == 1 - - def _connection_id(conn: dict) -> str: return str(conn.get("id") or "") @@ -265,9 +228,7 @@ def _sync_connections( connections: list[dict], nodes: list[dict], uuid_to_node: dict[str, int], - log_active: set[str], ) -> dict[str, tuple[int, int]]: - """同步连接缓存,断开连接时写入累计流量,返回各用户当前活跃会话流量。""" seen: set[str] = set() active: dict[str, tuple[int, int]] = {} single_node = len(nodes) == 1 @@ -276,9 +237,7 @@ def _sync_connections( for conn in connections: matched_uuid = "" for node in nodes: - if _match_connection(conn, node, single_node=single_node) or _match_vless_connection( - conn, node, log_active - ): + if _match_connection(conn, node, single_node=single_node): matched_uuid = node["uuid"] break if not matched_uuid and single_node and connections: @@ -346,7 +305,6 @@ def _connections_for_node( c for c in connections if _match_connection(c, node, single_node=single_node) - or _match_vless_connection(c, node, log_active) ] if matched: return matched @@ -355,9 +313,6 @@ def _connections_for_node( return connections if node["uuid"] in log_active: - vless_hits = [c for c in connections if _connection_inbound_tag(c) == _VLESS_INBOUND] - if vless_hits: - return vless_hits return [None] return [] @@ -367,8 +322,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) + active_by_uuid = _sync_connections(connections, nodes, uuid_to_node) single_node = len(nodes) == 1 has_connections = len(connections) > 0 global_up_speed, global_down_speed = _global_conn_speed(connections) if clash_ok else (0.0, 0.0) diff --git a/panel/templates/dashboard.html b/panel/templates/dashboard.html index b0ce9ad..4b9d785 100644 --- a/panel/templates/dashboard.html +++ b/panel/templates/dashboard.html @@ -13,7 +13,7 @@

节点列表

-

VPS {{ vps_ip }} · Reality 443 · Hysteria2 8443+

+

VPS {{ vps_ip }} · Hysteria2 8443+

@@ -72,22 +72,13 @@ -
- -
- - -
- -
- - + +
+
diff --git a/scripts/generate-keys.sh b/scripts/generate-keys.sh index c4cf109..6667426 100644 --- a/scripts/generate-keys.sh +++ b/scripts/generate-keys.sh @@ -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 diff --git a/scripts/install.sh b/scripts/install.sh index 6958c71..a3707a0 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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 </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 节点即可。" diff --git a/scripts/remove-vless.sh b/scripts/remove-vless.sh new file mode 100644 index 0000000..d17daa8 --- /dev/null +++ b/scripts/remove-vless.sh @@ -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:// 链接导入。" diff --git a/scripts/render-client.sh b/scripts/render-client.sh index adff293..3cf6dcb 100644 --- a/scripts/render-client.sh +++ b/scripts/render-client.sh @@ -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" < 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 diff --git a/scripts/render-xray.py b/scripts/render-xray.py deleted file mode 100644 index 5f25716..0000000 --- a/scripts/render-xray.py +++ /dev/null @@ -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() diff --git a/scripts/repair-reality.sh b/scripts/repair-reality.sh deleted file mode 100644 index 77f2001..0000000 --- a/scripts/repair-reality.sh +++ /dev/null @@ -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" diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index bd07ef1..158be14 100644 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -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" diff --git a/scripts/verify-reality.sh b/scripts/verify-reality.sh deleted file mode 100644 index 5641d72..0000000 --- a/scripts/verify-reality.sh +++ /dev/null @@ -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 diff --git a/server/sing-box.json.template b/server/sing-box.json.template deleted file mode 100644 index 78562f5..0000000 --- a/server/sing-box.json.template +++ /dev/null @@ -1,70 +0,0 @@ -{ - "log": { - "level": "warn", - "timestamp": true - }, - "inbounds": [ - { - "type": "vless", - "tag": "vless-reality-in", - "listen": "::", - "listen_port": 443, - "users": [ - { - "uuid": "${UUID}", - "flow": "xtls-rprx-vision" - } - ], - "tls": { - "enabled": true, - "server_name": "${REALITY_SERVER_NAME}", - "reality": { - "enabled": true, - "handshake": { - "server": "${REALITY_SERVER_NAME}", - "server_port": 443 - }, - "private_key": "${REALITY_PRIVATE_KEY}", - "short_id": ["", "${REALITY_SHORT_ID}"], - "max_time_difference": "1m" - } - } - }, - { - "type": "hysteria2", - "tag": "hysteria2-in", - "listen": "::", - "listen_port": 8443, - "users": [ - { - "password": "${HY2_PASSWORD}" - } - ], - "tls": { - "enabled": true, - "server_name": "${DOMAIN}", - "certificate_path": "/etc/sing-box/certs/fullchain.pem", - "key_path": "/etc/sing-box/certs/privkey.pem" - } - } - ], - "outbounds": [ - { - "type": "direct", - "tag": "direct" - }, - { - "type": "block", - "tag": "block" - } - ], - "route": { - "rules": [ - { - "ip_is_private": true, - "outbound": "block" - } - ], - "final": "direct" - } -} diff --git a/server/xray-server.json.template b/server/xray-server.json.template deleted file mode 100644 index 597c027..0000000 --- a/server/xray-server.json.template +++ /dev/null @@ -1,56 +0,0 @@ -{ - "log": { - "loglevel": "warning" - }, - "inbounds": [ - { - "listen": "0.0.0.0", - "port": 443, - "protocol": "vless", - "settings": { - "clients": [ - { - "id": "${UUID}", - "flow": "xtls-rprx-vision" - } - ], - "decryption": "none" - }, - "streamSettings": { - "network": "tcp", - "security": "reality", - "realitySettings": { - "show": false, - "dest": "${REALITY_SERVER_NAME}:443", - "xver": 0, - "serverNames": ["${REALITY_SERVER_NAME}"], - "privateKey": "${REALITY_PRIVATE_KEY}", - "shortIds": ["${REALITY_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" - } - ] - } -}