refactor: remove VLESS/Xray, Hy2-only stack
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,9 +10,6 @@ DOMAIN=66.hyf2.cc
|
|||||||
# Let's Encrypt 申请证书邮箱
|
# Let's Encrypt 申请证书邮箱
|
||||||
ACME_EMAIL=932465777@qq.com
|
ACME_EMAIL=932465777@qq.com
|
||||||
|
|
||||||
# Reality 伪装目标(真实大站,不要用你自己的域名)
|
|
||||||
REALITY_SERVER_NAME=www.microsoft.com
|
|
||||||
|
|
||||||
# 管理面板登录(安装完成后访问 http://域名/PANEL_PATH/)
|
# 管理面板登录(安装完成后访问 http://域名/PANEL_PATH/)
|
||||||
PANEL_USERNAME=dekun
|
PANEL_USERNAME=dekun
|
||||||
PANEL_PASSWORD=Woaini521@
|
PANEL_PASSWORD=Woaini521@
|
||||||
@@ -26,8 +23,3 @@ PANEL_PASSWORD=Woaini521@
|
|||||||
|
|
||||||
# sing-box Clash API 密钥(安装时自动生成,供面板读取连接状态)
|
# sing-box Clash API 密钥(安装时自动生成,供面板读取连接状态)
|
||||||
# CLASH_API_SECRET=
|
# CLASH_API_SECRET=
|
||||||
|
|
||||||
# 以下由 scripts/generate-keys.sh 自动生成,也可手动填写
|
|
||||||
# REALITY_PRIVATE_KEY=
|
|
||||||
# REALITY_PUBLIC_KEY=
|
|
||||||
# REALITY_SHORT_ID=
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
# jiedian — VPS 自建节点
|
# jiedian — VPS 自建节点
|
||||||
|
|
||||||
个人/家庭自用的 **VLESS + Reality(主力)** + **Hysteria2(备用)** 双栈方案,带 **Web 管理面板**。
|
个人/家庭自用的 **Hysteria2** 方案,带 **Web 管理面板**。
|
||||||
|
|
||||||
| 组件 | 职责 |
|
| 组件 | 职责 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **Xray** | VLESS + Reality(TCP 443) |
|
|
||||||
| **sing-box** | Hysteria2(UDP 8443+,每节点独立端口) |
|
| **sing-box** | Hysteria2(UDP 8443+,每节点独立端口) |
|
||||||
| **Flask 面板** | 节点管理、分享链接、在线/流量统计 |
|
| **Flask 面板** | 节点管理、分享链接、在线/流量统计 |
|
||||||
|
|
||||||
@@ -29,55 +28,46 @@ ssh root@YOUR_VPS_IP
|
|||||||
apt update && apt install -y git
|
apt update && apt install -y git
|
||||||
git clone https://git.bz121.com/dekun/jiedian.git /opt/jiedian
|
git clone https://git.bz121.com/dekun/jiedian.git /opt/jiedian
|
||||||
cd /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
|
bash scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
安装完成后会显示面板地址、用户名和密码。登录面板即可 **添加节点、复制分享链接**。
|
安装完成后登录面板 **添加节点、复制 Hy2 链接**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 架构
|
## 架构
|
||||||
|
|
||||||
```
|
```
|
||||||
浏览器 ──► Nginx:80/<PANEL_PATH>/ ──► Web 管理面板(登录、添加节点)
|
浏览器 ──► Nginx:80/<PANEL_PATH>/ ──► Web 管理面板
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
render-server.py + render-xray.py
|
render-server.py
|
||||||
│
|
│
|
||||||
┌───────────────┴───────────────┐
|
▼
|
||||||
▼ ▼
|
sing-box :8443+
|
||||||
Xray :443 sing-box :8443+
|
Hysteria2(每节点一端口)
|
||||||
VLESS + Reality Hysteria2(每节点一端口)
|
|
||||||
|
|
||||||
客户端 (Win/iOS/Android)
|
客户端 (Win/iOS/Android)
|
||||||
├─ TCP 443 ──► Xray VLESS+Reality
|
|
||||||
└─ UDP 8443+ ► sing-box Hysteria2
|
└─ UDP 8443+ ► sing-box Hysteria2
|
||||||
```
|
```
|
||||||
|
|
||||||
> VLESS Reality 使用 **Xray 服务端**,与 v2rayN / v2rayNG(Xray 核心)兼容性最好。Hy2 仍由 sing-box 承载。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
```
|
```
|
||||||
/opt/jiedian/
|
/opt/jiedian/
|
||||||
├── .env # VPS / Reality / 面板账号配置
|
├── .env
|
||||||
├── data/nodes.db # 节点数据库(安装后生成)
|
├── data/nodes.db
|
||||||
├── panel/ # Web 管理面板(Flask)
|
├── panel/ # Flask 管理面板
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── install.sh # 一键部署(Xray + sing-box + 面板)
|
│ ├── install.sh
|
||||||
│ ├── uninstall.sh # 卸载后重装
|
│ ├── uninstall.sh
|
||||||
│ ├── generate-keys.sh # 生成 Reality 密钥与面板密码
|
│ ├── remove-vless.sh # 旧版双栈 VPS 停用 VLESS
|
||||||
│ ├── render-xray.py # 生成 Xray VLESS Reality 配置
|
│ ├── generate-keys.sh # 生成面板密码
|
||||||
│ ├── render-server.py # 生成 sing-box Hy2 配置
|
│ └── render-server.py # 生成 sing-box Hy2 配置
|
||||||
│ ├── migrate-xray-reality.sh # 旧版 sing-box Reality 迁移到 Xray
|
|
||||||
│ └── verify-reality.sh # Reality 密钥与配置诊断
|
|
||||||
└── docs/
|
└── docs/
|
||||||
├── DEPLOY.md
|
|
||||||
├── client-import.md
|
|
||||||
└── troubleshooting.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -87,48 +77,29 @@ bash scripts/install.sh
|
|||||||
| 端口 | 协议 | 用途 |
|
| 端口 | 协议 | 用途 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 22 | TCP | SSH |
|
| 22 | TCP | SSH |
|
||||||
| 80 | TCP | HTTP(ACME 验证 + **管理面板**) |
|
| 80 | TCP | HTTP(ACME + 管理面板) |
|
||||||
| 443 | TCP | VLESS + Reality(**Xray**) |
|
| 8443–8499 | UDP | Hysteria2(每节点递增) |
|
||||||
| 8443–8499 | UDP | Hysteria2(**sing-box**,每节点递增) |
|
|
||||||
|
|
||||||
阿里云安全组需放行 **8443–8499/UDP**,不只 8443。
|
阿里云安全组需放行 **8443–8499/UDP**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 常用运维
|
## 常用运维
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 面板地址
|
|
||||||
grep PANEL_PATH /opt/jiedian/.env
|
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
|
python3 /opt/jiedian/scripts/render-server.py
|
||||||
systemctl restart xray sing-box
|
systemctl restart sing-box
|
||||||
|
|
||||||
# 卸载后干净重装
|
# 从旧版(含 VLESS)迁移到仅 Hy2
|
||||||
bash scripts/uninstall.sh
|
bash /opt/jiedian/scripts/remove-vless.sh
|
||||||
bash scripts/generate-keys.sh # 可选:重置密钥与面板密码
|
|
||||||
bash scripts/install.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`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 免责声明
|
## 免责声明
|
||||||
|
|
||||||
本项目仅供学习网络技术使用。请遵守当地法律法规。
|
本项目仅供学习网络技术使用。请遵守当地法律法规。
|
||||||
|
|||||||
@@ -45,28 +45,7 @@
|
|||||||
{
|
{
|
||||||
"type": "selector",
|
"type": "selector",
|
||||||
"tag": "proxy",
|
"tag": "proxy",
|
||||||
"outbounds": ["reality", "hysteria2", "direct"]
|
"outbounds": ["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}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "hysteria2",
|
"type": "hysteria2",
|
||||||
|
|||||||
+18
-171
@@ -6,206 +6,53 @@
|
|||||||
|------|-----|
|
|------|-----|
|
||||||
| VPS IP | 你的 VPS 公网 IP |
|
| VPS IP | 你的 VPS 公网 IP |
|
||||||
| 域名 | 已解析到 VPS 的域名 |
|
| 域名 | 已解析到 VPS 的域名 |
|
||||||
| 管理面板 | `http://域名/<PANEL_PATH>/`(**必须 http,不要用 https**) |
|
| 管理面板 | `http://域名/<PANEL_PATH>/`(**必须 http**) |
|
||||||
| 部署目录 | `/opt/jiedian` |
|
| 部署目录 | `/opt/jiedian` |
|
||||||
| 系统 | Ubuntu 22.04 / 24.04 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 部署前准备
|
## 安全组
|
||||||
|
|
||||||
### 1. DNS 解析
|
| 端口 | 协议 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
将域名 **A 记录** 指向 VPS 公网 IP(用于 Hy2 证书与面板访问):
|
| 22 | TCP | SSH |
|
||||||
|
| 80 | TCP | ACME + 面板 |
|
||||||
```
|
| 8443–8499 | UDP | Hysteria2(多节点递增) |
|
||||||
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` 自动生成。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 一键部署(新机器)
|
## 一键部署
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
apt update && apt install -y git
|
apt update && apt install -y git
|
||||||
git clone https://git.bz121.com/dekun/jiedian.git /opt/jiedian
|
git clone https://git.bz121.com/dekun/jiedian.git /opt/jiedian
|
||||||
cd /opt/jiedian
|
cd /opt/jiedian
|
||||||
cp .env.example .env
|
cp .env.example .env # 填写 VPS_IP、DOMAIN、ACME_EMAIL
|
||||||
# 编辑 .env 填写 VPS_IP、DOMAIN、ACME_EMAIL
|
|
||||||
bash scripts/install.sh
|
bash scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
安装结束会输出类似:
|
登录面板 → 添加节点 → 复制 **Hysteria2** 链接到客户端。
|
||||||
|
|
||||||
```
|
|
||||||
管理面板: http://your.domain.com/jiedian-xxxx/
|
|
||||||
面板路径: jiedian-xxxx (见 .env 中 PANEL_PATH)
|
|
||||||
用户名: admin
|
|
||||||
密码: xxxxx
|
|
||||||
```
|
|
||||||
|
|
||||||
浏览器打开面板 → 登录 → **添加节点**(或使用默认节点)→ 复制 **VLESS** / **Hysteria2** 链接到客户端。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 安装脚本做了什么
|
## 从旧版(含 VLESS)升级
|
||||||
|
|
||||||
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)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 卸载后重装(推荐流程)
|
|
||||||
|
|
||||||
若之前部署混乱,先卸载再装:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/jiedian
|
cd /opt/jiedian
|
||||||
git pull
|
git pull
|
||||||
bash scripts/uninstall.sh
|
sudo bash scripts/remove-vless.sh
|
||||||
bash scripts/generate-keys.sh # 重新生成 Reality 密钥与面板密码
|
|
||||||
bash scripts/install.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`uninstall.sh` 会停止服务并清理配置,**保留** `/opt/jiedian` 代码与 `.env` 基础字段。
|
会停用 Xray、更新面板,仅保留 Hy2。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 从旧版升级(sing-box 跑 Reality → Xray)
|
## 常用命令
|
||||||
|
|
||||||
若你之前用 sing-box 监听 443 且 v2rayN Reality 一直 `-1`,拉代码后执行:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/jiedian && git pull
|
systemctl status sing-box jiedian-panel
|
||||||
bash scripts/migrate-xray-reality.sh
|
python3 /opt/jiedian/scripts/render-server.py
|
||||||
|
systemctl restart sing-box
|
||||||
```
|
```
|
||||||
|
|
||||||
客户端 **无需改参数**,直接重测 VLESS 节点即可。
|
客户端导入见 [client-import.md](client-import.md)。
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 增删节点后的配置
|
|
||||||
|
|
||||||
面板会自动后台执行 `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)。
|
|
||||||
|
|||||||
+15
-28
@@ -1,37 +1,24 @@
|
|||||||
# 方案决策记录
|
# 技术栈说明
|
||||||
|
|
||||||
## 已确认选型
|
## 组件分工
|
||||||
|
|
||||||
| 项目 | 选择 | 理由 |
|
| 组件 | 用途 |
|
||||||
|------|------|------|
|
|------|------|
|
||||||
| 协议栈 | **VLESS + Reality + Hysteria2 双栈** | Reality 抗主动探测;Hysteria2 作 UDP 备用与流媒体 |
|
| **sing-box** | Hysteria2 服务端、Clash API 统计、多 inbound 按节点分端口 |
|
||||||
| Reality 服务端 | **Xray** | 与 v2rayN / v2rayNG(Xray 核心)兼容性最好;sing-box Reality 易出现 `processed invalid connection` |
|
| **Nginx** | ACME 验证、管理面板反代(HTTP 80) |
|
||||||
| Hy2 服务端 | **sing-box** | 原生 Hysteria2、Clash API 统计、多 inbound 按节点分端口 |
|
| **Flask 面板** | 添加/删除节点、复制 Hy2 链接、在线与流量统计 |
|
||||||
| 系统 | **Ubuntu 22.04/24.04** | 脚本基于 apt,其他发行版需手动适配 |
|
|
||||||
| 面板 | **Flask Web 面板** | 添加/删除节点、复制链接、在线与流量统计 |
|
|
||||||
|
|
||||||
## 端口规划
|
## 端口
|
||||||
|
|
||||||
| 端口 | 协议 | 服务 | 用途 |
|
| 端口 | 协议 | 进程 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| 443 | TCP | **Xray** | VLESS + Reality(主力) |
|
| 80 | TCP | Nginx | ACME + 面板 |
|
||||||
| 8443–8499 | UDP | **sing-box** | Hysteria2(每节点递增:8443、8444…) |
|
| 8443–8499 | UDP | sing-box | Hy2,每节点 +1 |
|
||||||
| 80 | TCP | nginx | ACME 验证 + 管理面板反向代理 |
|
|
||||||
| 127.0.0.1:5080 | TCP | Flask | 面板后端(不对外暴露) |
|
|
||||||
| 127.0.0.1:9090 | TCP | sing-box | Clash API(面板统计 Hy2) |
|
|
||||||
|
|
||||||
## 配置生成
|
## 配置生成
|
||||||
|
|
||||||
| 脚本 | 输出 | 内容 |
|
| 脚本 | 输出 |
|
||||||
|------|------|------|
|
|------|------|
|
||||||
| `render-xray.py` | `/usr/local/etc/xray/config.json` | 所有启用节点的 VLESS UUID + Reality |
|
| `render-server.py` | `/etc/sing-box/config.json` |
|
||||||
| `render-server.py` | `/etc/sing-box/config.json` | 每节点独立 Hy2 inbound + Clash API |
|
|
||||||
|
|
||||||
增删节点或轮换 Reality 密钥后,两个脚本都需重新运行并 `systemctl restart xray sing-box`。
|
增删节点后运行 `render-server.py` 并 `systemctl restart sing-box`。
|
||||||
|
|
||||||
## 单协议简化
|
|
||||||
|
|
||||||
- **仅 Reality**:删除 `render-server.py` 中 Hy2 inbound 逻辑,跳过 acme 证书步骤(需改代码)。
|
|
||||||
- **仅 Hy2**:不安装 Xray,删除 443 inbound(不推荐,失去 TCP 主力)。
|
|
||||||
|
|
||||||
当前默认 **双栈**,与 README / DEPLOY 一致。
|
|
||||||
|
|||||||
+22
-133
@@ -1,154 +1,43 @@
|
|||||||
# 客户端导入与测试指南
|
# 客户端导入
|
||||||
|
|
||||||
部署完成后,在 **管理面板** 复制各节点的 VLESS / Hy2 链接;或本地运行 `bash scripts/render-client.sh` 生成 `client/generated/share-links.txt`。
|
部署完成后,在 **管理面板** 复制各节点的 `hy2://` 链接。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 一、连通性测试(推荐顺序)
|
## Windows(v2rayN)
|
||||||
|
|
||||||
### 1. Reality(TCP 443,Xray)
|
1. 下载 [v2rayN](https://github.com/2dust/v2rayN/releases)
|
||||||
|
2. **服务器 → 从剪贴板导入批量 URL**,粘贴面板复制的 `hy2://` 链接
|
||||||
在**国内网络**下测试(不要在 VPS 本机 curl 自己):
|
3. 设为活动服务器并连接
|
||||||
|
|
||||||
```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` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 二、Windows
|
## Android(v2rayNG)
|
||||||
|
|
||||||
### 方案 A:v2rayN(推荐)
|
1. 安装 [v2rayNG](https://github.com/2dust/v2rayNG/releases)(arm64-v8a)
|
||||||
|
2. **+ → 从剪贴板导入**
|
||||||
1. 下载 [v2rayN](https://github.com/2dust/v2rayN/releases)(选 `v2rayN-With-Core.zip` 或自带 Xray 的版本)
|
3. 连接后开启 VPN
|
||||||
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**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、Android
|
## iOS(Shadowrocket / Streisand)
|
||||||
|
|
||||||
### v2rayNG(推荐)
|
**+ → 从剪贴板导入** `hy2://` 链接。
|
||||||
|
|
||||||
1. [GitHub Releases](https://github.com/2dust/v2rayNG/releases) 安装 **arm64-v8a** APK(多数新机)
|
|
||||||
2. 右上角 **+** → **从剪贴板导入**(先复制面板 `vless://` 链接)
|
|
||||||
3. 再导入 `hy2://` 备用节点
|
|
||||||
4. 点击右下角 **V** 连接
|
|
||||||
5. 设置 → **路由设置** → **绕过局域网及大陆地址**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 四、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
|
hy2://PASSWORD@DOMAIN:8443?sni=DOMAIN#名称-Hy2
|
||||||
```
|
```
|
||||||
|
|
||||||
完整链接见面板或 `client/generated/share-links.txt`。
|
第二节点端口为 **8444**,依此类推,以面板显示为准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
- **一设备一节点**,不要多人共用同一链接
|
||||||
|
- 多节点升级后请 **重新复制** Hy2 链接(端口可能变化)
|
||||||
|
- SNI 为域名(如 `66.hyf2.cc`),不是 IP
|
||||||
|
|||||||
+19
-299
@@ -3,323 +3,43 @@
|
|||||||
## 服务检查
|
## 服务检查
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 核心服务
|
systemctl is-active sing-box jiedian-panel
|
||||||
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
|
|
||||||
sing-box check -c /etc/sing-box/config.json
|
sing-box check -c /etc/sing-box/config.json
|
||||||
|
ss -ulnp | grep 8443
|
||||||
# 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…)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 架构速查
|
|
||||||
|
|
||||||
| 组件 | 端口 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| **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 核心)不兼容
|
- 使用 **`http://域名/PANEL_PATH/`**,不要用 https
|
||||||
2. **`.env` 私钥与 `/usr/local/etc/xray/config.json` 不一致**(改过密钥未 render)
|
- 443 未用于面板,面板在 **80** 端口
|
||||||
3. **SNI 填错**:Reality 必须 `www.microsoft.com`,不能填 Hy2 域名
|
|
||||||
4. **SpiderX 为空**:v2rayN 中填 **`/`**
|
|
||||||
5. **公钥私钥不配对**:`.env` 中 PUBLIC/PRIVATE 不是同一批 `generate-keys.sh` 生成
|
|
||||||
|
|
||||||
**处理**:
|
### 增删节点后 503
|
||||||
|
|
||||||
```bash
|
等 5–10 秒刷新;sing-box 重启期间可能短暂不可用。
|
||||||
cd /opt/jiedian
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# 新架构:迁移到 Xray(只需执行一次)
|
### 从旧版 VLESS 迁移
|
||||||
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/`**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/jiedian && git pull
|
cd /opt/jiedian && git pull
|
||||||
# 重载 nginx 与面板(见 DEPLOY.md)
|
sudo bash scripts/remove-vless.sh
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### sing-box 报错 v2ray api is not included
|
## 端口一览
|
||||||
|
|
||||||
重新生成配置(已移除 v2ray_api):
|
| 端口 | 协议 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
```bash
|
| 80 | TCP | 面板 + ACME |
|
||||||
python3 /opt/jiedian/scripts/render-server.py
|
| 8443–8499 | UDP | Hysteria2 |
|
||||||
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)
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-26
@@ -27,7 +27,6 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
@@ -114,24 +113,9 @@ def render_singbox_config() -> tuple[bool, str]:
|
|||||||
return True, "ok"
|
return True, "ok"
|
||||||
|
|
||||||
|
|
||||||
def render_xray_config() -> tuple[bool, str]:
|
def restart_singbox_async() -> None:
|
||||||
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", "xray", "sing-box"],
|
["systemctl", "restart", "sing-box"],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
@@ -141,17 +125,10 @@ 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
|
||||||
ok, msg = render_xray_config()
|
restart_singbox_async()
|
||||||
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。"""
|
||||||
|
|
||||||
|
|||||||
+4
-34
@@ -1,4 +1,4 @@
|
|||||||
"""分享链接生成。"""
|
"""分享链接生成(Hysteria2)。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -21,44 +21,14 @@ def load_env() -> dict[str, str]:
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def _sni_qs(value: str) -> str:
|
def build_links(node: dict, env: dict | None = None) -> dict[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]]:
|
|
||||||
env = env or load_env()
|
env = env or load_env()
|
||||||
vps_ip = env["VPS_IP"]
|
|
||||||
domain = env["DOMAIN"]
|
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"])
|
name = quote(node["name"])
|
||||||
port = hy2_port(node, list_nodes())
|
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 = (
|
hy2 = (
|
||||||
f"hy2://{quote(node['hy2_password'], safe='')}@{domain}:{port}"
|
f"hy2://{quote(node['hy2_password'], safe='')}@{domain}:{port}"
|
||||||
f"?sni={_sni_qs(domain)}#{name}-Hy2"
|
f"?sni={domain}#{name}-Hy2"
|
||||||
)
|
)
|
||||||
return {
|
return {"hy2": hy2}
|
||||||
"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",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-3
@@ -72,15 +72,14 @@ document.querySelectorAll(".copy-row").forEach((row) => {
|
|||||||
btn.addEventListener("click", async () => {
|
btn.addEventListener("click", async () => {
|
||||||
const card = row.closest(".node-card");
|
const card = row.closest(".node-card");
|
||||||
const nodeId = card?.dataset.id;
|
const nodeId = card?.dataset.id;
|
||||||
const kind = btn.dataset.linkKind || input.dataset.linkKind || "vless";
|
|
||||||
let text = input.value;
|
let text = input.value;
|
||||||
|
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
try {
|
try {
|
||||||
const nodes = await fetchNodeLinks();
|
const nodes = await fetchNodeLinks();
|
||||||
const node = nodes.find((item) => String(item.id) === String(nodeId));
|
const node = nodes.find((item) => String(item.id) === String(nodeId));
|
||||||
if (node?.links) {
|
if (node?.links?.hy2) {
|
||||||
text = kind === "hy2" ? node.links.hy2 : node.links.vless;
|
text = node.links.hy2;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* fall back to input.value */
|
/* fall back to input.value */
|
||||||
|
|||||||
+4
-50
@@ -1,4 +1,4 @@
|
|||||||
"""从 sing-box Clash API 采集节点连接与流量(官方预编译包不含 v2ray_api)。"""
|
"""从 sing-box Clash API 采集节点连接与流量。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -18,15 +18,10 @@ ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1]))
|
|||||||
ENV_FILE = ROOT / ".env"
|
ENV_FILE = ROOT / ".env"
|
||||||
|
|
||||||
CLASH_ADDR = "127.0.0.1:9090"
|
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(
|
_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]] = {}
|
||||||
@@ -76,28 +71,8 @@ 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 字段,多节点 Hy2 从近期日志补全在线 UUID。"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
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]:
|
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_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)
|
total_down = sum(int(c.get("download") or 0) for c in connections)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -193,17 +167,6 @@ def _match_connection(conn: dict, node: dict, *, single_node: bool = False) -> b
|
|||||||
return False
|
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:
|
def _connection_id(conn: dict) -> str:
|
||||||
return str(conn.get("id") or "")
|
return str(conn.get("id") or "")
|
||||||
|
|
||||||
@@ -265,9 +228,7 @@ def _sync_connections(
|
|||||||
connections: list[dict],
|
connections: list[dict],
|
||||||
nodes: list[dict],
|
nodes: list[dict],
|
||||||
uuid_to_node: dict[str, int],
|
uuid_to_node: dict[str, int],
|
||||||
log_active: set[str],
|
|
||||||
) -> dict[str, tuple[int, int]]:
|
) -> dict[str, tuple[int, int]]:
|
||||||
"""同步连接缓存,断开连接时写入累计流量,返回各用户当前活跃会话流量。"""
|
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
active: dict[str, tuple[int, int]] = {}
|
active: dict[str, tuple[int, int]] = {}
|
||||||
single_node = len(nodes) == 1
|
single_node = len(nodes) == 1
|
||||||
@@ -276,9 +237,7 @@ def _sync_connections(
|
|||||||
for conn in connections:
|
for conn in connections:
|
||||||
matched_uuid = ""
|
matched_uuid = ""
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if _match_connection(conn, node, single_node=single_node) or _match_vless_connection(
|
if _match_connection(conn, node, single_node=single_node):
|
||||||
conn, node, log_active
|
|
||||||
):
|
|
||||||
matched_uuid = node["uuid"]
|
matched_uuid = node["uuid"]
|
||||||
break
|
break
|
||||||
if not matched_uuid and single_node and connections:
|
if not matched_uuid and single_node and connections:
|
||||||
@@ -346,7 +305,6 @@ def _connections_for_node(
|
|||||||
c
|
c
|
||||||
for c in connections
|
for c in connections
|
||||||
if _match_connection(c, node, single_node=single_node)
|
if _match_connection(c, node, single_node=single_node)
|
||||||
or _match_vless_connection(c, node, log_active)
|
|
||||||
]
|
]
|
||||||
if matched:
|
if matched:
|
||||||
return matched
|
return matched
|
||||||
@@ -355,9 +313,6 @@ def _connections_for_node(
|
|||||||
return connections
|
return connections
|
||||||
|
|
||||||
if node["uuid"] in log_active:
|
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 [None]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -367,8 +322,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)
|
||||||
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
|
||||||
global_up_speed, global_down_speed = _global_conn_speed(connections) if clash_ok else (0.0, 0.0)
|
global_up_speed, global_down_speed = _global_conn_speed(connections) if clash_ok else (0.0, 0.0)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div>
|
<div>
|
||||||
<h1>节点列表</h1>
|
<h1>节点列表</h1>
|
||||||
<p class="muted">VPS {{ vps_ip }} · Reality 443 · Hysteria2 8443+</p>
|
<p class="muted">VPS {{ vps_ip }} · Hysteria2 8443+</p>
|
||||||
</div>
|
</div>
|
||||||
<button id="addBtn" class="btn primary">+ 添加节点</button>
|
<button id="addBtn" class="btn primary">+ 添加节点</button>
|
||||||
</section>
|
</section>
|
||||||
@@ -72,22 +72,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<label>VLESS + Reality</label>
|
|
||||||
<div class="copy-row">
|
|
||||||
<input class="copy-input" readonly value="{{ node.links.vless }}" data-link-kind="vless">
|
|
||||||
<button type="button" class="btn copy-btn" data-link-kind="vless">复制</button>
|
|
||||||
</div>
|
|
||||||
<p class="link-hint muted">
|
|
||||||
手动核对:SNI={{ node.links.meta.sni }} · pbk={{ node.links.meta.pbk[:20] }}… · sid={{ node.links.meta.sid }} · SpiderX=/
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Hysteria2</label>
|
<label>Hysteria2</label>
|
||||||
<div class="copy-row">
|
<div class="copy-row">
|
||||||
<input class="copy-input" readonly value="{{ node.links.hy2 }}" data-link-kind="hy2">
|
<input class="copy-input" readonly value="{{ node.links.hy2 }}">
|
||||||
<button type="button" class="btn copy-btn" data-link-kind="hy2">复制</button>
|
<button type="button" class="btn copy-btn">复制</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="link-hint muted">一设备一节点;SNI 为 {{ domain }},端口随节点递增(8443、8444…)</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-actions">
|
<div class="node-actions">
|
||||||
<button class="btn danger delete-btn" data-id="{{ node.id }}">删除</button>
|
<button class="btn danger delete-btn" data-id="{{ node.id }}">删除</button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 生成 Reality 密钥;若 .env 无面板密码则一并生成
|
# 生成面板密码与 Clash API 密钥(如 .env 中尚未配置)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -22,17 +22,6 @@ else
|
|||||||
SB="sing-box"
|
SB="sing-box"
|
||||||
fi
|
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
|
GENERATE_PANEL_PASSWORD=1
|
||||||
if [[ -f "$ENV_FILE" ]] && grep -q "^PANEL_PASSWORD=.\+" "$ENV_FILE" 2>/dev/null; then
|
if [[ -f "$ENV_FILE" ]] && grep -q "^PANEL_PASSWORD=.\+" "$ENV_FILE" 2>/dev/null; then
|
||||||
GENERATE_PANEL_PASSWORD=0
|
GENERATE_PANEL_PASSWORD=0
|
||||||
@@ -43,9 +32,6 @@ if (( GENERATE_PANEL_PASSWORD )); then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "========== 生成的密钥 =========="
|
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
|
if (( GENERATE_PANEL_PASSWORD )); then
|
||||||
echo "PANEL_PASSWORD: $PANEL_PASSWORD"
|
echo "PANEL_PASSWORD: $PANEL_PASSWORD"
|
||||||
else
|
else
|
||||||
@@ -54,14 +40,6 @@ fi
|
|||||||
echo "================================"
|
echo "================================"
|
||||||
|
|
||||||
if [[ -f "$ENV_FILE" ]]; then
|
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 (( GENERATE_PANEL_PASSWORD )); then
|
||||||
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
|
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
|
||||||
sed -i "s|^PANEL_PASSWORD=.*|PANEL_PASSWORD=${PANEL_PASSWORD}|" "$ENV_FILE"
|
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"
|
echo "PANEL_USERNAME=dekun" >> "$ENV_FILE"
|
||||||
fi
|
fi
|
||||||
echo "已写入 $ENV_FILE"
|
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
|
else
|
||||||
echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等,再重新运行本脚本" >&2
|
echo "提示: 先复制 .env.example 为 .env 并填写 VPS_IP、DOMAIN 等" >&2
|
||||||
fi
|
fi
|
||||||
|
|||||||
+3
-21
@@ -37,15 +37,8 @@ source "$ENV_FILE"
|
|||||||
: "${VPS_IP:?请在 .env 中设置 VPS_IP}"
|
: "${VPS_IP:?请在 .env 中设置 VPS_IP}"
|
||||||
: "${DOMAIN:?请在 .env 中设置 DOMAIN}"
|
: "${DOMAIN:?请在 .env 中设置 DOMAIN}"
|
||||||
: "${ACME_EMAIL:?请在 .env 中设置 ACME_EMAIL}"
|
: "${ACME_EMAIL:?请在 .env 中设置 ACME_EMAIL}"
|
||||||
: "${REALITY_SERVER_NAME:=www.microsoft.com}"
|
|
||||||
: "${PANEL_USERNAME:=admin}"
|
: "${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
|
if [[ -z "${PANEL_PASSWORD:-}" ]]; then
|
||||||
PANEL_PASSWORD="$(sing-box generate rand --base64 32 | tr -d '/+=' | head -c 20)"
|
PANEL_PASSWORD="$(sing-box generate rand --base64 32 | tr -d '/+=' | head -c 20)"
|
||||||
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
|
if grep -q "^PANEL_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
|
||||||
@@ -56,9 +49,6 @@ if [[ -z "${PANEL_PASSWORD:-}" ]]; then
|
|||||||
source "$ENV_FILE"
|
source "$ENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
: "${REALITY_PRIVATE_KEY:?}"
|
|
||||||
: "${REALITY_PUBLIC_KEY:?}"
|
|
||||||
: "${REALITY_SHORT_ID:?}"
|
|
||||||
: "${PANEL_PASSWORD:?}"
|
: "${PANEL_PASSWORD:?}"
|
||||||
|
|
||||||
if [[ -z "${CLASH_API_SECRET:-}" ]]; then
|
if [[ -z "${CLASH_API_SECRET:-}" ]]; then
|
||||||
@@ -126,7 +116,6 @@ ufw default deny incoming
|
|||||||
ufw default allow outgoing
|
ufw default allow outgoing
|
||||||
ufw allow 22/tcp comment 'SSH'
|
ufw allow 22/tcp comment 'SSH'
|
||||||
ufw allow 80/tcp comment 'HTTP-ACME-Panel'
|
ufw allow 80/tcp comment 'HTTP-ACME-Panel'
|
||||||
ufw allow 443/tcp comment 'Reality'
|
|
||||||
ufw allow 8443:8499/udp comment 'Hysteria2-multi-node'
|
ufw allow 8443:8499/udp comment 'Hysteria2-multi-node'
|
||||||
ufw --force enable
|
ufw --force enable
|
||||||
|
|
||||||
@@ -187,12 +176,6 @@ log "初始化节点数据库 ..."
|
|||||||
log "生成 sing-box 服务端配置 (Hysteria2) ..."
|
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]
|
||||||
@@ -215,7 +198,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 xray.service sing-box.service
|
After=network.target sing-box.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -232,7 +215,7 @@ WantedBy=multi-user.target
|
|||||||
UNIT
|
UNIT
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable xray sing-box jiedian-panel
|
systemctl enable 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" \
|
||||||
@@ -241,7 +224,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 xray sing-box jiedian-panel
|
systemctl restart sing-box jiedian-panel
|
||||||
|
|
||||||
log "部署完成!"
|
log "部署完成!"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -255,6 +238,5 @@ 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"
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/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 节点即可。"
|
|
||||||
@@ -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:// 链接导入。"
|
||||||
@@ -11,37 +11,21 @@ OUT_DIR="${ROOT_DIR}/client/generated"
|
|||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "$ENV_FILE"
|
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; }
|
[[ -n "${!var:-}" ]] || { echo "缺少 .env 变量: $var"; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
||||||
mkdir -p "$OUT_DIR"
|
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")"
|
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" \
|
sed -e "s|\${DOMAIN}|${DOMAIN}|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" \
|
|
||||||
-e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \
|
-e "s|\${HY2_PASSWORD}|${HY2_PASSWORD}|g" \
|
||||||
"$ROOT_DIR/client/sing-box-client.json.template" > "$OUT_DIR/sing-box-client.json"
|
"$ROOT_DIR/client/sing-box-client.json.template" > "$OUT_DIR/sing-box-client.json"
|
||||||
|
|
||||||
cat > "$OUT_DIR/share-links.txt" <<EOF
|
cat > "$OUT_DIR/share-links.txt" <<EOF
|
||||||
========== VLESS + Reality (主力) ==========
|
========== Hysteria2 ==========
|
||||||
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
|
hy2://${HY2_PASSWORD_ENC}@${DOMAIN}:8443?sni=${DOMAIN}#Hysteria2
|
||||||
|
|
||||||
========== Hysteria2 (备用) ==========
|
|
||||||
hy2://${HY2_PASSWORD_ENC}@${DOMAIN}:8443?sni=${DOMAIN_SNI_ENC}#Hysteria2-Backup
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "已生成:"
|
echo "已生成:"
|
||||||
|
|||||||
@@ -43,14 +43,8 @@ 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 = [
|
if not env.get("DOMAIN"):
|
||||||
"REALITY_SHORT_ID",
|
raise SystemExit(".env 缺少 DOMAIN")
|
||||||
"REALITY_SERVER_NAME",
|
|
||||||
"DOMAIN",
|
|
||||||
]
|
|
||||||
for key in required:
|
|
||||||
if not env.get(key):
|
|
||||||
raise SystemExit(f".env 缺少 {key}")
|
|
||||||
|
|
||||||
hy2_base_port = 8443
|
hy2_base_port = 8443
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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"
|
|
||||||
@@ -6,17 +6,16 @@ set -euo pipefail
|
|||||||
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
|
[[ $EUID -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
|
||||||
|
|
||||||
echo "[*] 停止服务 ..."
|
echo "[*] 停止服务 ..."
|
||||||
systemctl stop jiedian-panel xray sing-box 2>/dev/null || true
|
systemctl stop jiedian-panel sing-box xray 2>/dev/null || true
|
||||||
systemctl disable jiedian-panel xray sing-box 2>/dev/null || true
|
systemctl disable jiedian-panel sing-box xray 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 / Xray 配置 ..."
|
echo "[*] 删除 sing-box 配置 ..."
|
||||||
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
|
||||||
@@ -36,5 +35,4 @@ rm -rf "${ROOT}/client/generated"
|
|||||||
echo ""
|
echo ""
|
||||||
echo "卸载完成。重新安装:"
|
echo "卸载完成。重新安装:"
|
||||||
echo " cd ${ROOT}"
|
echo " cd ${ROOT}"
|
||||||
echo " bash scripts/generate-keys.sh # 可选,重置 Reality 密钥与面板密码"
|
|
||||||
echo " bash scripts/install.sh"
|
echo " bash scripts/install.sh"
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user