Merge pull request #109 from xinsuiyuandong/main

v2.6.2
This commit is contained in:
心隨緣動 2025-07-12 03:10:05 +08:00 committed by GitHub
commit eda72f6a34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1922 additions and 1093 deletions

784
README.md
View File

@ -1,56 +1,754 @@
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) <p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
<p align="center"> **------------------一个更好的面板 • 基于Xray Core构建----------------**
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg?style=for-the-badge)](https://github.com/MHSanaei/3x-ui/releases) [![](https://img.shields.io/github/v/release/xeefei/3x-ui.svg)](https://github.com/xeefei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg?style=for-the-badge)](https://github.com/MHSanaei/3x-ui/actions) [![](https://img.shields.io/github/actions/workflow/status/xeefei/3x-ui/release.yml.svg)](#)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg?style=for-the-badge)](#) [![GO Version](https://img.shields.io/github/go-mod/go-version/xeefei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg?style=for-the-badge)](https://github.com/MHSanaei/3x-ui/releases/latest) [![Downloads](https://img.shields.io/github/downloads/xeefei/3x-ui/total.svg)](#)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true&style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
**3X-UI** — advanced, open-source web-based control panel designed for managing Xray-core server. It offers a user-friendly interface for configuring and monitoring various VPN and proxy protocols. > **声明:** 此项目仅供个人学习、交流使用,请遵守当地法律法规,勿用于非法用途;请勿用于生产环境。
> [!IMPORTANT] > **注意:** 在使用此项目和〔教程〕过程中,若因违反以上声明使用规则而产生的一切后果由使用者自负。
> This project is only for personal using, please do not use it for illegal purposes, please do not use it in a production environment.
As an enhanced fork of the original X-UI project, 3X-UI provides improved stability, broader protocol support, and additional features. **如果此项目对你有用,请给一个**:star2:
## Quick Start
```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki).
## A Special Thanks to
- [alireza0](https://github.com/alireza0/)
## Acknowledgment
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
## Support project
**If this project is helpful to you, you may wish to give it a**:star2:
<p align="left"> <p align="left">
<a href="https://buymeacoffee.com/mhsanaei" target="_blank"> <a href="https://buymeacoffee.com/xeefeiz" target="_blank">
<img src="./media/buymeacoffe.png" alt="Image"> <img src="./media/buymeacoffe.png" alt="Image">
</a> </a>
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - 赞助地址USDT/TRC20`TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Stargazers over Time ## [【3X-UI】中文交流群https://t.me/XUI_CN](https://t.me/XUI_CN)
## [【3X-UI】详细安装流程步骤https://xeefei.github.io/xufei/2024/05/3x-ui/](https://xeefei.github.io/xufei/2024/05/3x-ui/)
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui) ------------
## ✰3X-UI优化版跟原版3X-UI的区别
### 大部分功能基于原版3X-UI进行汉化优化主要的优化内容如下
#### 1、最大限度地汉化了面板项目更适合中文宝宝体质包括
##### ①优化在VPS中进行脚本安装过程的汉化提示增加相应的安装中文提示让中文用户能明白清楚自己安装到了哪个环节在细节方面增加了安装成功之后的用户设置信息提示在脚本中加入面板登录地址显示
##### ②管理后台进行了相应的〔图标和按钮〕汉化,让中文宝宝能够看得懂,
##### ③安装成功后〔自动更改〕后台管理界面和电报机器人界面默认为〔中文〕,
##### ④在管理后台中设置证书处增加了acme方式填入路径的提示
#### 2、优化了电报机器人响应按钮的名称和排序
#### 3、创建了3X-UI中文交流群各位中文宝宝可以一起讨论交流
#### 4、管理后台中增加了实用导航页面里面包含实用内容
#### 5、优化了后台二维码显示模式点击打开会更加丝滑美观
#### 6、在创建reality协议时更改uTLS指纹默认使用chrome
#### 7、更新README内容添加备份&恢复操作说明,以及更多其他图文介绍;
#### 8、管理后台中增加端口检测网络测速点击可以跳转直达
#### 9、增加了详细的项目安装配置教程解决小白用户不懂配置的烦恼。
------------
## ✰如何从其他x-ui版本迁移到3X-UI优化版
#### 1、若你用的是伊朗老哥的原版3X-UI是可以直接覆盖安装因为3X-UI优化版是fork了原版3X-UI的项目基于原有的功能进行优化的大功能是没有变化的主要是进行了脚本的汉化处理其他诸如数据库文件等位置是没有改变的所以直接覆盖安装并不会影响你原有节点及配置等数据安装命令如下
```
bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh)
```
#### 2、若你之前用的是Docker方式安装那先进入容器里面/命令docker exec -it 容器id /bin/sh再执行以上脚本命令直接【覆盖安装】即可
#### 3、若你用的是之前F佬的x-ui或者其他分支版本那直接覆盖安装的话并不能确保一定就能够兼容建议你先去备份数据库配置文件再进行安装3X-UI优化版
------------
## 安装之前的准备
- 购买一台性能还不错的VPS可通过本页底部链接购买
- PS若你不想升级系统则可以跳过此步骤。
- 若你需要更新/升级系统Debian系统可用如下命令
```
apt update
apt upgrade -y
apt dist-upgrade -y
apt autoclean
apt autoremove -y
```
- 查看系统当前版本:
```
cat /etc/debian_version
```
- 查看内核版本:
```
uname -r
```
- 列出所有内核:
```
dpkg --list | grep linux-image
```
- 更新完成后执行重新引导:
```
update-grub
```
- 完成以上步骤之后输入reboot重启系统
------------
## 【搬瓦工】重装/升级系统之后SSH连不上如何解决
- 【搬瓦工】重装/升级系统会恢复默认22端口如果需要修改SSH的端口号您需要进行以下步骤
- 以管理员身份使用默认22端口登录到SSH服务器
- 打开SSH服务器的配置文件进行编辑SSH配置文件通常位于/etc/ssh/sshd_config
- 找到"Port"选项,并将其更改为您想要的端口号
- Port <新端口号>,请将<新端口号>替换为您想要使用的端口号
- 保存文件并退出编辑器
- 重启服务器以使更改生效
------------
## 安装 & 升级
- 使用3x-ui脚本一般情况下安装完成创建入站之后端口是默认关闭的所以必须进入脚本选择【22】去放行端口
- 要使用【自动续签】证书功能也必须放行【80】端口保持80端口是打开的才会每3个月自动续签一次
- 【全新安装】请执行以下脚本:
```
bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh)
```
#### 如果执行了上面的代码但是报错证明你的系统里面没有curl这个软件请执行以下命令先安装curl软件安装curl之后再去执行上面代码
```
apt update -y&&apt install -y curl&&apt install -y socat
```
- 若要对版本进行升级可直接通过脚本选择【2】如下图
![8](./media/8.png)
![10](./media/10.png)
- 在到这一步必须要注意要保留旧设置的话需要输入【n】
![11](./media/11.png)
## 安装指定版本
若要安装指定的版本,请使用以下安装命令。 e.g., ver `v2.4.5`:
```
VERSION=v2.4.5 && bash <(curl -Ls "https://raw.githubusercontent.com/xeefei/3x-ui/$VERSION/install.sh") $VERSION
```
------------
## 若你的VPS默认有防火墙请在安装完成之后放行指定端口
- 放行【面板登录端口】
- 放行出入站管理协议端口
- 如果要申请安装证书并每3个月【自动续签】证书请确保80和443端口是放行打开的
- 可通过此脚本的第【22】选项去安装防火墙进行管理如下图
![9](./media/9.png)
- 若要一次性放行多个端口或一整个段的端口,用英文逗号隔开。
#### PS若你的VPS没有防火墙则所有端口都是能够ping通的可自行选择是否进入脚本安装防火墙保证安全但安装了防火墙必须放行相应端口。
------------
## 安装证书开启https方式实现域名登录访问管理面板/偷自己
#### PS如果不需要以上功能或无域名可以跳过这步
##### 1、把自己的域名托管到CF并解析到自己VPS的IP不要开启【小云朵】
##### 2、如果要申请安装证书并每3个月【自动续签】证书请确保80和443端口是放行打开的
##### 3、输入x-ui命令进入面板管理脚本通过选择第【18】选项去进行安装
##### 4、记录好已经安装证书的【路径】位置在/root/.acme.sh/域名_ecc后续需要用到
##### 5、进入后台【面板设置】—>【常规】中,去分别填入刚才已经记录的证书公钥、私钥路径,
##### 6、点击左上角的【保存】和【重启面板】即可用自己域名进行登录管理也可按照后续方法实现【自己偷自己】。
------------
## 登录面板进行【常规】设置
### 特别是如果在安装过程中,全部都是默认【回车键】安装的话,用户名/密码/访问路径是随机的而面板监听端口默认是2053最好进入面板更改
##### 1、填写自己想要设置的【面板监听端口】并去登录SSH放行
##### 2、更改自己想要设置的【面板登录访问路径】后续加上路径登录访问
![25](./media/25.png)
##### 3、其他安全设定和电报机器人等配置可自行根据需求去进行设置
##### 4、若申请了证书须填写证书公钥/私钥路径,建议配置电报机器人方便管理,
![26](./media/26.png)
##### 5、面板设置【改动保存】之后都需要点击左上角【重启面板】才能生效。
#### PS若你在正确完成了上述步骤之后你没有安装证书的情况下去用IP+端口号/路径的方式却不能访问面板那请检查一下是不是你的浏览器自动默认开启了https模式需要手动调整一下改成http方式把“s”去掉即可访问成功。
------------
## 创建【入站协议】和添加【客户端】,并测试上网
##### 1、点击左边【入站列表】然后【添加入站】传输方式保持【TCP】不变尽量选择主流的vless+reality+vision协议组合
![23](./media/23.png)
##### 2、在选择reality协议时偷的域名可以使用默认的要使用其他的请替换尽量保持一致就行比如Apple、YahooVPS所在地区的旅游、学校网站等如果要实现【偷自己】请参看后续【如何偷自己】的说明部分而私钥/公钥部分可以直接点击下方的【Get New Cert】获取一个随机的
##### 3、在创建reality协议过程中至于其他诸如PROXY ProtocolHTTP 伪装TPROXYExternal Proxy等等选项若无特殊要求保持默认设置即可不用去动它们
![24](./media/24.png)
##### 4、创建好入站协议之后默认只有一个客户端可根据自己需求继续添加重点并编辑客户端选择【Flow流控】为xtls-rprx-vision-udp443
![19](./media/19.png)
##### 5、其他流量限制到期时间客户TG的ID等选项根据自己需求填写
![4](./media/4.png)
##### 6、一定要放行端口之后确保端口能够ping通再导入软件
##### 7、点击二维码或者复制链接导入到v2rayN等软件中进行测试。
------------
## 备份与恢复/迁移数据库以Debian系统为例
#### 一、备份通过配置好电报管理机器人并去设置开启【自动备份】每天凌晨12点会通过VPS管理机器人获取【备份配置】文件有x-ui.db和config.json两个文件可自行下载保存到自己电脑里面
![14](./media/14.png)
#### 二、搭建在新的VPS中全新安装好3x-ui面板通过脚本放行之前配置的所有端口一次性放行多个端口请用【英文逗号】分隔
#### 三、若需要安装证书则提前把域名解析到新的VPS对应的IP并且去输入x-ui选择第【18】选项去安装并记录公钥/私钥的路径,无域名则跳过这一步,
#### 四、恢复SSH登录服务器找到/etc/x-ui/x-ui.db和/usr/local/x-ui/bin/config.json文件位置上传之前的两个备份文件进行覆盖
![12](./media/12.png)
##### PS把之前通过自动备份下载得到的两个文件上传覆盖掉旧文件重启3x-ui面板即可【迁移成功】即使迁移过程中出现问题你是有备份文件的不用担心多试几次。
![13](./media/13.png)
#### 五、若安装了证书,去核对/更改一下证书的路径,一般是同一个域名的话,位置在:/root/.acme.sh/域名_ecc路径是相同的就不用更改
#### 六、重启面板/重启服务器,让上述步骤生效即可,这时可以看到所有配置都是之前自己常用的,包括面板用户名、密码,入站、客户端,电报机器人配置等。
------------
## 安装完成后如何设置调整成【中文界面】?
- 方法一:通过管理后台【登录页面】调整,登录时可以选择,如下图:
![15](./media/15.png)
- 方法二:通过在管理后台-->【面板设置】中去选择设置,如下图:
![16](./media/16.png)
- 【TG机器人】设置中文通过在管理后台-->【面板设置】-->【机器人配置】中去选择设置,并建议打开数据库备份和登录通知,如下图:
![17](./media/17.png)
------------
## 用3x-ui如何实现【自己偷自己】
- 其实很简单,只要你为面板设置了证书,
- 开启了HTTPS登录就可以将3x-ui自身作为Web Server
- 无需Nginx等这里给一个示例
- 其中目标网站Dest请填写面板监听端口
- 可选域名SNI填写面板登录域名
- 如果您使用其他web server如nginx
- 将目标网站改为对应监听端口也可。
- 需要说明的是,如果您处于白名单地区,自己“偷”自己并不适合你;
- 其次可选域名一项实际上可以填写任意SNI只要客户端保持一致即可不过并不推荐这样做。
- 配置方法如下图所示:
![18](./media/18.png)
------------
## 〔子域名〕被墙针对特征
#### 网络表现:
##### 1、可以Ping通域名和IP地址
##### 2、子域名无法打开3X-UI管理界面
##### 3、什么都正常就是不能上网
#### 问题:
##### 你的子域名被墙针对了:无法上网!
#### 解决方案:
##### 1、更换为新的子域名
##### 2、解析新的子域名到VPS的IP
##### 3、重新去安装新证书
##### 4、重启3X-UI和服务器
##### 5、重新去获取链接并测试上网。
#### PS若通过以上步骤还是不能正常上网则重装VPS服务器OS系统以及3X-UI面板全部重新安装之后就正常了
------------
## 在自己的VPS服务器部署【订阅转换】功能
### 如何把vless/vmess等协议转换成Clash/Surge等软件支持的格式
##### 1、进入脚本输入x-ui命令调取面板选择第【24】选项安装订阅转换模块如下图
![21](./media/21.png)
##### 2、等待安装【订阅转换】成功之后访问地址你的IP:18080端口号进行转换
![22](./media/22.png)
##### 3、因为在转换过程中需要调取后端API所以请确保端口25500是打开放行的
##### 4、在得到【转换链接】之后只要你的VPS服务器25500端口是能ping通的就能导入Clash/Surge等软件成功下载配置
##### 5、此功能集成到3x-ui面板中是为了保证安全通过调取24选项把【订阅转换】功能部署在自己的VPS中不会造成链接泄露。
### 【订阅转换】功能在自己的VPS中安装部署成功之后的界面如下图所示
![20](./media/20.png)
------------
## 如何保护自己的IP不被墙被封
##### 1、使用的代理协议要安全加密是必备推荐使用vless+reality+vision协议组合
##### 2、因为有时节点会共享在不同的地区多个省份之间不要共同连接同一个IP
##### 3、连接同一个IP就算了不要同一个端口不要同IP+同端口到处漫游,要分开,
##### 4、同一台VPS不要在一天内一直大流量去下载东西使用不要流量过高要切换
##### 5、创建【入站协议】的时候尽量用【高位端口】比如40000--65000之间的端口号。
#### 提醒:为什么在特殊时期,比如:两会,春节等被封得最严重最惨?
##### 尼玛同一个IP+同一个端口号多个省份去漫游跟开飞机场一样不封你封谁的IP和端口
#### 总结:不要多终端/多省份/多个朋友/共同使用同一个IP和端口号使用3x-ui多创建几个【入站】
#### 多做几条备用各用各的各行其道才比较安全GFW的思维模式是干掉机场机场的特征个人用户不要去沾染自然IP就保护好了。
------------
## SSL 认证
<details>
<summary>点击查看 SSL 认证</summary>
### ACME
要使用 ACME 管理 SSL 证书:
1. 确保您的域名已正确解析到服务器,
2. 输入“x-ui”命令并选择“SSL 证书管理”,
3. 您将看到以下选项:
- **获取证书** ----获取SSL证书
- **吊销证书** ----吊销现有的SSL证书
- **续签证书** ----强制续签SSL证书
- **显示所有证书** ----显示服务器中所有能用的证书
- **设置面板证书路径** ----指定面板要使用的证书
### Certbot
安装和使用 Certbot
```sh
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
### Cloudflare
管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件:
- Cloudflare 邮箱地址
- Cloudflare Global API Key
- 域名已通过 cloudflare 解析到当前服务器
**如何获取 Cloudflare全局API密钥:**
1. 在终端中输入“x-ui”命令然后选择“CF SSL 证书”。
2. 访问链接: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens).
3. 点击“查看全局 API 密钥”(如下图所示):
![](media/APIKey1.PNG)
4. 您可能需要重新验证您的帐户。之后,将显示 API 密钥(请参见下面的屏幕截图):
![](media/APIKey2.png)
使用时只需输入您的“域名”、“电子邮件”和“API KEY”即可。示意图如下
![](media/DetailEnter.png)
</details>
------------
## 手动安装 & 升级
<details>
<summary>点击查看 手动安装 & 升级</summary>
#### 使用
1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
s390x) echo 's390x' ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/xeefei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. 下载压缩包后,执行以下命令安装或升级 x-ui
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
s390x) echo 's390x' ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui
```
</details>
------------
## 通过Docker安装
<details>
<summary>点击查看 通过Docker安装</summary>
#### 使用
1. **安装Docker**
```sh
bash <(curl -sSL https://get.docker.com)
```
2. **克隆项目仓库**
```sh
git clone https://github.com/xeefei/3x-ui.git
cd 3x-ui
```
3. **启动服务**
```sh
docker compose up -d
```
添加 ```--pull always``` 标志使 docker 在拉取更新的镜像时自动重新创建容器。有关更多信息请参阅https://docs.docker.com/reference/cli/docker/container/run/#pull
**或**
```sh
docker run -itd \
-e XRAY_VMESS_AEAD_FORCED=false \
-v $PWD/db/:/etc/x-ui/ \
-v $PWD/cert/:/root/cert/ \
--network=host \
--restart=unless-stopped \
--name 3x-ui \
ghcr.io/xeefei/3x-ui:latest
```
4. **更新至最新版本**
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
5. **从Docker中删除3x-ui **
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
------------
## 建议使用的操作系统
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- OpenEuler 22.03+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian
- AlmaLinux 8.0+
- Rocky Linux 8+
- Oracle Linux 8+
- OpenSUSE Tubleweed
- Amazon Linux 2023
------------
## 支持的架构和设备
<details>
<summary>点击查看 支持的架构和设备</summary>
我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构:
- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。
- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。
- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。
- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。
- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备虽然不太普遍但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。
- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。
</details>
------------
## Languages
- English英语
- Farsi伊朗语
- Simplified Chinese简体中文
- Traditional Chinese繁体中文
- Russian俄语
- Vietnamese越南语
- Spanish西班牙语
- Indonesian (印度尼西亚语)
- Ukrainian乌克兰语
- Turkish土耳其语
- Português (葡萄牙语)
------------
## 项目特点
- 系统状态查看与监控
- 可搜索所有入站和客户端信息
- 深色/浅色主题随意切换
- 支持多用户和多协议
- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard
- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY
- 流量统计、流量限制、过期时间限制
- 可自定义的 Xray配置模板
- 支持HTTPS访问面板自备域名+SSL证书
- 支持一键式SSL证书申请和自动续签证书
- 更多高级配置项目请参考面板去进行设定
- 修复了 API 路由(用户设置将使用 API 创建)
- 支持通过面板中提供的不同项目更改配置。
- 支持从面板导出/导入数据库
## 默认面板设置
<details>
<summary>点击查看 默认设置</summary>
### 默认信息
- **端口**
- 2053
- **用户名 & 密码 & 访问路径**
- 当您跳过设置时,这些信息会随机生成,
- 您也可以在安装的时候自定义访问路径。
- **数据库路径:**
- /etc/x-ui/x-ui.db
- **Xray 配置路径:**
- /usr/local/x-ui/bin/config.json
- **面板链接无SSL**
- http://ip:2053/访问路径/panel
- **面板链接有SSL**
- https://你的域名:2053/访问路径/panel
</details>
------------
## [WARP 配置](https://gitlab.com/fscarmen/warp)
<details>
<summary>点击查看 WARP 配置</summary>
#### 使用
**对于版本 `v2.1.0` 及更高版本:**
WARP 是内置的,无需额外安装;只需在面板中打开必要的配置即可。
**如果要在 v2.1.0 之前使用 WARP 路由**,请按照以下步骤操作:
**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap
- **Account Type (free, plus, team):** Choose the appropriate account type.
- **Enable/Disable WireProxy:** Toggle WireProxy on or off.
- **Uninstall WARP:** Remove the WARP application.
**2.** 如果您已经安装了 warp您可以使用以下命令卸载
```sh
warp u
```
**3.** 在面板中打开您需要的配置
配置:
- Block Ads
- Route Google, Netflix, Spotify, and OpenAI (ChatGPT) traffic to WARP
- Fix Google 403 error
</details>
------------
## IP 限制
<details>
<summary>点击查看 IP 限制</summary>
#### 使用
**注意:** 使用 IP 隧道时IP 限制无法正常工作。
- 对于 `v1.6.1`之前的版本
- IP 限制 已被集成在面板中。
- 对于 `v1.7.0` 以及更新的版本:
- 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件:
1. 使用面板内置的 `x-ui` 指令
2. 选择 `IP Limit Management`.
3. 根据您的需要选择合适的选项。
- 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
- 您需要在Xray配置中手动设置访问日志的路径。
</details>
------------
## Telegram 机器人
<details>
<summary>点击查看 Telegram 机器人</summary>
#### 使用
Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括:
- 电报令牌
- 管理员聊天 ID
- 通知时间cron 语法)
- 到期日期通知
- 流量上限通知
- 数据库备份
- CPU 负载通知
**参考:**
- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知
- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知
- `@hourly` - 每小时通知
- `@daily` - 每天通知 (00:00)
- `@weekly` - 每周通知
- `@every 8h` - 每8小时通知
### Telegram Bot 功能
- 定期报告
- 登录通知
- CPU 阈值通知
- 提前报告的过期时间和流量阈值
- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单
- 支持使用UUIDVMESS/VLESS或密码TROJAN搜索报文流量报告 - 匿名
- 基于菜单的机器人
- 通过电子邮件搜索客户端(仅限管理员)
- 检查所有入库
- 检查服务器状态
- 检查耗尽的用户
- 根据请求和定期报告接收备份
- 多语言机器人
### 注册 Telegram bot
- 与 [Botfather](https://t.me/BotFather) 对话:
![Botfather](./media/botfather.png)
- 使用 /newbot 创建新机器人你需要提供机器人名称以及用户名注意名称中末尾要包含“bot”
![创建机器人](./media/newbot.png)
- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。
![令牌](./media/token.png)
- 输入您的面板并配置 Telegram 机器人设置,如下所示:
![面板设置](./media/panel-bot-config.png)
在输入字段编号 3 中输入机器人令牌。
在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可)
- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot) 启动机器人,它会给你 Telegram 用户 ID。
![用户 ID](./media/user-id.png)
</details>
------------
## API 路由
<details>
<summary>点击查看 API 路由</summary>
#### 使用
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
- `/panel/api/inbounds` 以下操作的基础:
| 方法 | 路径 | 操作 |
| :----: | ---------------------------------- | ------------------------------------------- |
| `GET` | `"/list"` | 获取所有入站 |
| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id |
| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 |
| `GET` | `"/getClientTrafficsById/:id"` | 通过用户ID获取客户端流量 |
| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 |
| `POST` | `"/add"` | 添加入站 |
| `POST` | `"/del/:id"` | 删除入站 |
| `POST` | `"/update/:id"` | 更新入站 |
| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 |
| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 |
| `POST` | `"/addClient"` | 将客户端添加到入站 |
| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 |
| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 |
| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 |
| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 |
| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 |
| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 -1 all |
| `POST` | `"/onlines"` | 获取在线用户 电子邮件列表 |
- 使用`clientId` 项应该填写下列数据:
- `client.id` for VMESS and VLESS
- `client.password` for TROJAN
- `client.email` for Shadowsocks
- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
</details>
------------
## 环境变量
<details>
<summary>点击查看 环境变量</summary>
#### Usage
| 变量 | Type | 默认 |
| -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
例子:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
</details>
------------
## 预览
![1](./media/1.png)
![2](./media/2.png)
![3](./media/3.png)
![5](./media/5.png)
![6](./media/6.png)
![7](./media/7.png)
------------
## 广告赞助
- 如果你觉得本项目对你有用,而且你也恰巧有这方面的需求,你也可以选择通过我的购买链接赞助我。
- [搬瓦工GIA高端线路仅推荐购买GIA套餐](https://bandwagonhost.com/aff.php?aff=75015)
- [Dmit高端GIA线路](https://www.dmit.io/aff.php?aff=9326)
- [白丝云【4837线路】实惠量大管饱](https://cloudsilk.io/aff.php?aff=706)
------------
## 特别感谢
- [alireza0](https://github.com/alireza0/)
------------
## 致谢
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
------------
## Star 趋势
[![Stargazers over time](https://starchart.cc/xeefei/3x-ui.svg)](https://starchart.cc/xeefei/3x-ui)

476
README.zh.md Normal file
View File

@ -0,0 +1,476 @@
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
**一个更好的面板 • 基于Xray Core构建**
[![](https://img.shields.io/github/v/release/xeefei/3x-ui.svg)](https://github.com/xeefei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/xeefei/3x-ui/release.yml.svg)](#)
[![GO Version](https://img.shields.io/github/go-mod/go-version/xeefei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/xeefei/3x-ui/total.svg)](#)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。
**如果此项目对你有用,请给一个**:star2:
<p align="left">
<a href="https://buymeacoffee.com/xeefeiz" target="_blank">
<img src="./media/buymeacoffe.png" alt="Image">
</a>
</p>
- USDT (TRC20): `TYQEmQp1P65u9bG7KPehgJdvuokfb72YkZ`
## 安装 & 升级
```
bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh)
```
## 安装指定版本
若需要安装指定的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.13`:
```
bash <(curl -Ls https://raw.githubusercontent.com/xeefei/3x-ui/master/install.sh) v2.3.13
```
## SSL 认证
<details>
<summary>点击查看 SSL 认证</summary>
### Cloudflare
管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件:
- Cloudflare 邮箱地址
- Cloudflare Global API Key
- 域名已通过 cloudflare 解析到当前服务器
**1:** 在终端中运行`x-ui` 选择 `Cloudflare SSL Certificate`.
### Certbot
```
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
***Tip:*** *管理脚本具有 Certbot 。使用 `x-ui` 命令, 选择 `SSL Certificate Management`.*
</details>
## 手动安装 & 升级
<details>
<summary>点击查看 手动安装 & 升级</summary>
#### 使用
1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/xeefei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. 下载压缩包后,执行以下命令安装或升级 x-ui
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui
```
</details>
## 通过Docker安装
<details>
<summary>点击查看 通过Docker安装</summary>
#### 使用
1. 安装Docker
```sh
bash <(curl -sSL https://get.docker.com)
```
2. 克隆仓库:
```sh
git clone https://github.com/xeefei/3x-ui.git
cd 3x-ui
```
3. 运行服务:
```sh
docker compose up -d
```
```sh
docker run -itd \
-e XRAY_VMESS_AEAD_FORCED=false \
-v $PWD/db/:/etc/x-ui/ \
-v $PWD/cert/:/root/cert/ \
--network=host \
--restart=unless-stopped \
--name 3x-ui \
ghcr.io/xeefei/3x-ui:latest
```
更新至最新版本
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
从Docker中删除3x-ui
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
## 建议使用的操作系统
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian
- AlmaLinux 9+
- Rockylinux 9+
- OpenSUSE Tubleweed
## 支持的架构和设备
<details>
<summary>点击查看 支持的架构和设备</summary>
我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构:
- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。
- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。
- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。
- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。
- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备虽然不太普遍但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。
- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。
</details>
## Languages
- English英语
- Farsi伊朗语
- Chinese中文
- Russian俄语
- Vietnamese越南语
- Spanish西班牙语
- Indonesian (印度尼西亚语)
- Ukrainian乌克兰语
## Features
- 系统状态监控
- 在所有入站和客户端中搜索
- 深色/浅色主题
- 支持多用户和多协议
- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard
- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY
- 流量统计、流量限制、过期时间限制
- 可自定义的 Xray配置模板
- 支持HTTPS访问面板自建域名+SSL证书
- 支持一键式SSL证书申请和自动续费
- 更多高级配置项目请参考面板
- 修复了 API 路由(用户设置将使用 API 创建)
- 支持通过面板中提供的不同项目更改配置。
- 支持从面板导出/导入数据库
## 默认设置
<details>
<summary>点击查看 默认设置</summary>
### 信息
- **端口:** 2053
- **用户名 & 密码:** 当您跳过设置时,此项会随机生成。
- **数据库路径:**
- /etc/x-ui/x-ui.db
- **Xray 配置路径:**
- /usr/local/x-ui/bin/config.json
- **面板链接无SSL**
- http://ip:2053/panel
- http://domain:2053/panel
- **面板链接有SSL**
- https://domain:2053/panel
</details>
## WARP 配置
<details>
<summary>点击查看 WARP 配置</summary>
#### 使用
如果要在 v2.1.0 之前使用 WARP 路由,请按照以下步骤操作:
**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap
```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
```
**2.** 如果您已经安装了 warp您可以使用以下命令卸载
```sh
warp u
```
**3.** 在面板中打开您需要的配置
配置:
- Block Ads
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
- Fix Google 403 error
</details>
## IP 限制
<details>
<summary>点击查看 IP 限制</summary>
#### 使用
**注意:** 使用 IP 隧道时IP 限制无法正常工作。
- 适用于最高 `v1.6.1`
- IP 限制 已被集成在面板中。
- 适用于 `v1.7.0` 以及更新的版本:
- 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件:
1. 使用面板内置的 `x-ui` 指令
2. 选择 `IP Limit Management`.
3. 根据您的需要选择合适的选项。
- 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
</details>
## Telegram 机器人
<details>
<summary>点击查看 Telegram 机器人</summary>
#### 使用
Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括:
- 电报令牌
- 管理员聊天 ID
- 通知时间cron 语法)
- 到期日期通知
- 流量上限通知
- 数据库备份
- CPU 负载通知
**参考:**
- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知
- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知
- `@hourly` - 每小时通知
- `@daily` - 每天通知 (00:00)
- `@weekly` - 每周通知
- `@every 8h` - 每8小时通知
### Telegram Bot 功能
- 定期报告
- 登录通知
- CPU 阈值通知
- 提前报告的过期时间和流量阈值
- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单
- 支持使用UUIDVMESS/VLESS或密码TROJAN搜索报文流量报告 - 匿名
- 基于菜单的机器人
- 通过电子邮件搜索客户端(仅限管理员)
- 检查所有入库
- 检查服务器状态
- 检查耗尽的用户
- 根据请求和定期报告接收备份
- 多语言机器人
### 注册 Telegram bot
- 与 [Botfather](https://t.me/BotFather) 对话:
![Botfather](./media/botfather.png)
- 使用 /newbot 创建新机器人你需要提供机器人名称以及用户名注意名称中末尾要包含“bot”
![创建机器人](./media/newbot.png)
- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。
![令牌](./media/token.png)
- 输入您的面板并配置 Telegram 机器人设置,如下所示:
![面板设置](./media/panel-bot-config.png)
在输入字段编号 3 中输入机器人令牌。
在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可)
- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot) 启动机器人,它会给你 Telegram 用户 ID。
![用户 ID](./media/user-id.png)
</details>
## API 路由
<details>
<summary>点击查看 API 路由</summary>
#### 使用
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
- `/panel/api/inbounds` 以下操作的基础:
| 方法 | 路径 | 操作 |
| :----: | ---------------------------------- | --------------------------------- |
| `GET` | `"/list"` | 获取所有入站 |
| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id |
| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 |
| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 |
| `POST` | `"/add"` | 添加入站 |
| `POST` | `"/del/:id"` | 删除入站 |
| `POST` | `"/update/:id"` | 更新入站 |
| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 |
| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 |
| `POST` | `"/addClient"` | 将客户端添加到入站 |
| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 |
| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 |
| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 |
| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 |
| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 |
| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 -1 all |
| `POST` | `"/onlines"` | 获取在线用户 电子邮件列表 |
\*- `clientId` 项应该使用下列数据
- `client.id` VMESS and VLESS
- `client.password` TROJAN
- `client.email` Shadowsocks
- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
</details>
## 环境变量
<details>
<summary>点击查看 环境变量</summary>
#### Usage
| 变量 | Type | 默认 |
| -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
例子:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
</details>
## 预览
![1](./media/1.png)
![2](./media/2.png)
![3](./media/3.png)
![4](./media/4.png)
![5](./media/5.png)
![6](./media/6.png)
![7](./media/7.png)
## 特别感谢
- [alireza0](https://github.com/alireza0/)
## 致谢
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
## Star趋势
[![Stargazers over time](https://starchart.cc/xeefei/3x-ui.svg)](https://starchart.cc/xeefei/3x-ui)

View File

@ -2,6 +2,7 @@
red='\033[0;31m' red='\033[0;31m'
green='\033[0;32m' green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m' yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
@ -33,11 +34,23 @@ arch() {
armv7* | armv7 | arm ) echo 'armv7' ;; armv7* | armv7 | arm ) echo 'armv7' ;;
armv6* | armv6 ) echo 'armv6' ;; armv6* | armv6 ) echo 'armv6' ;;
armv5* | armv5 ) echo 'armv5' ;; armv5* | armv5 ) echo 'armv5' ;;
armv5* | armv5 ) echo 's390x' ;; s390x) echo 's390x' ;;
*) echo -e "${green}不支持的CPU架构! ${plain}" && rm -f install.sh && exit 1 ;; *) echo -e "${green}不支持的CPU架构! ${plain}" && rm -f install.sh && exit 1 ;;
esac esac
} }
check_glibc_version() {
glibc_version=$(ldd --version | head -n1 | awk '{print $NF}')
required_version="2.32"
if [[ "$(printf '%s\n' "$required_version" "$glibc_version" | sort -V | head -n1)" != "$required_version" ]]; then
echo -e "${red}------>>>GLIBC版本 $glibc_version 太旧了! 要求2.32或以上版本${plain}"
echo -e "${green}-------->>>>请升级到较新版本的操作系统以便获取更高版本的GLIBC${plain}"
exit 1
echo -e "${green}-------->>>>GLIBC版本 $glibc_version符合高于2.32的要求)${plain}"
}
check_glibc_version
echo "" echo ""
echo -e "${yellow}---------->>>>>当前系统的架构为: $(arch)${plain}" echo -e "${yellow}---------->>>>>当前系统的架构为: $(arch)${plain}"
echo "" echo ""
@ -129,13 +142,13 @@ install_base() {
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update && apt-get install -y -q wget curl tar tzdata apt-get update && apt-get install -y -q wget curl tar tzdata
;; ;;
centos | almalinux | rocky | oracle) centos | rhel | almalinux | rocky | ol)
yum -y update && yum install -y -q wget curl tar tzdata yum -y update && yum install -y -q wget curl tar tzdata
;; ;;
fedora) fedora | amzn | virtuozzo)
dnf -y update && dnf install -y -q wget curl tar tzdata dnf -y update && dnf install -y -q wget curl tar tzdata
;; ;;
arch | manjaro) arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
;; ;;
alpine) alpine)

268
main.go
View File

@ -6,6 +6,8 @@ import (
"log" "log"
"os" "os"
"os/signal" "os/signal"
"os/exec"
"strings"
"syscall" "syscall"
_ "unsafe" _ "unsafe"
@ -40,11 +42,9 @@ func runWebServer() {
log.Fatalf("Unknown log level: %v", config.GetLogLevel()) log.Fatalf("Unknown log level: %v", config.GetLogLevel())
} }
godotenv.Load()
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
log.Fatalf("Error initializing database: %v", err) log.Fatalf("Error initializing database(初始化数据库出错): %v", err)
} }
var server *web.Server var server *web.Server
@ -114,65 +114,119 @@ func runWebServer() {
func resetSetting() { func resetSetting() {
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
fmt.Println("Failed to initialize database:", err) fmt.Println("Failed to initialize database(初始化数据库失败):", err)
return return
} }
settingService := service.SettingService{} settingService := service.SettingService{}
err = settingService.ResetSettings() err = settingService.ResetSettings()
if err != nil { if err != nil {
fmt.Println("Failed to reset settings:", err) fmt.Println("reset setting failed重置设置失败:", err)
} else { } else {
fmt.Println("Settings successfully reset.") fmt.Println("reset setting success---->>重置设置成功")
} }
} }
func showSetting(show bool) { func showSetting(show bool) {
// 执行 shell 命令获取 IPv4 地址
cmdIPv4 := exec.Command("sh", "-c", "curl -s4m8 ip.p3terx.com -k | sed -n 1p")
outputIPv4, err := cmdIPv4.Output()
if err != nil {
log.Fatal(err)
}
// 执行 shell 命令获取 IPv6 地址
cmdIPv6 := exec.Command("sh", "-c", "curl -s6m8 ip.p3terx.com -k | sed -n 1p")
outputIPv6, err := cmdIPv6.Output()
if err != nil {
log.Fatal(err)
}
// 去除命令输出中的换行符
ipv4 := strings.TrimSpace(string(outputIPv4))
ipv6 := strings.TrimSpace(string(outputIPv6))
// 定义转义字符,定义不同颜色的转义字符
const (
Reset = "\033[0m"
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
)
if show { if show {
settingService := service.SettingService{} settingService := service.SettingService{}
port, err := settingService.GetPort() port, err := settingService.GetPort()
if err != nil { if err != nil {
fmt.Println("get current port failed, error info:", err) fmt.Println("get current port failed, error info(获取当前端口失败,错误信息):", err)
} }
webBasePath, err := settingService.GetBasePath() webBasePath, err := settingService.GetBasePath()
if err != nil { if err != nil {
fmt.Println("get webBasePath failed, error info:", err) fmt.Println("get webBasePath failed, error info获取访问路径失败错误信息:", err)
}
certFile, err := settingService.GetCertFile()
if err != nil {
fmt.Println("get cert file failed, error info:", err)
}
keyFile, err := settingService.GetKeyFile()
if err != nil {
fmt.Println("get key file failed, error info:", err)
} }
userService := service.UserService{} userService := service.UserService{}
userModel, err := userService.GetFirstUser() userModel, err := userService.GetFirstUser()
if err != nil { if err != nil {
fmt.Println("get current user info failed, error info:", err) fmt.Println("get current user info failed, error info获取当前用户信息失败错误信息:", err)
} }
if userModel.Username == "" || userModel.Password == "" { username := userModel.Username
fmt.Println("current username or password is empty") userpasswd := userModel.Password
if username == "" || userpasswd == "" {
fmt.Println("current username or password is empty--->>当前用户名或密码为空")
} }
fmt.Println("")
fmt.Println("current panel settings as follows:") fmt.Println(Yellow + "----->>>以下为面板重要信息,请自行记录保存<<<-----" + Reset)
if certFile == "" || keyFile == "" { fmt.Println(Green + "Current panel settings as follows (当前面板设置如下):" + Reset)
fmt.Println("Warning: Panel is not secure with SSL") fmt.Println("")
fmt.Println(Green + fmt.Sprintf("username用户名: %s", username) + Reset)
fmt.Println(Green + fmt.Sprintf("password密 码): %s", userpasswd) + Reset)
fmt.Println(Green + fmt.Sprintf("port端口号: %d", port) + Reset)
if webBasePath != "" {
fmt.Println(Green + fmt.Sprintf("webBasePath访问路径: %s", webBasePath) + Reset)
} else { } else {
fmt.Println("Panel is secure with SSL") fmt.Println("webBasePath is not set----->>未设置访问路径")
} }
fmt.Println("")
fmt.Println("--------------------------------------------------")
// 根据条件打印带颜色的字符串
if ipv4 != "" {
fmt.Println("")
formattedIPv4 := fmt.Sprintf("%s %s%s:%d%s" + Reset,
Green+"面板 IPv4 访问地址------>>",
Yellow+"http://",
ipv4,
port,
Yellow+webBasePath + Reset)
fmt.Println(formattedIPv4)
fmt.Println("")
}
hasDefaultCredential := func() bool { if ipv6 != "" {
return userModel.Username == "admin" && crypto.CheckPasswordHash(userModel.Password, "admin") fmt.Println("")
}() formattedIPv6 := fmt.Sprintf("%s %s[%s%s%s]:%d%s%s",
Green+"面板 IPv6 访问地址------>>", // 绿色的提示信息
fmt.Println("hasDefaultCredential:", hasDefaultCredential) Yellow+"http://", // 黄色的 http:// 部分
fmt.Println("port:", port) Yellow, // 黄色的[ 左方括号
fmt.Println("webBasePath:", webBasePath) ipv6, // IPv6 地址
Yellow, // 黄色的] 右方括号
port, // 端口号
Yellow+webBasePath, // 黄色的 Web 基础路径
Reset) // 重置颜色
fmt.Println(formattedIPv6)
fmt.Println("")
}
fmt.Println(Green + ">>>>>>>>注若您安装了证书请把IP换成您的域名用https方式登录" + Reset)
fmt.Println("")
fmt.Println("--------------------------------------------------")
fmt.Println("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑")
fmt.Println(fmt.Sprintf("%s请确保 %s%d%s 端口已打开放行%s",Green, Red, port, Green, Reset))
fmt.Println("请自行确保此端口没有被其他程序占用")
fmt.Println(Green + "若要登录访问面板,请复制上面的地址到浏览器" + Reset)
fmt.Println("")
fmt.Println("--------------------------------------------------")
fmt.Println("")
} }
} }
@ -198,7 +252,7 @@ func updateTgbotEnableSts(status bool) {
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) { func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
fmt.Println("Error initializing database:", err) fmt.Println("Error initializing database(初始化数据库出错):", err)
return return
} }
@ -207,35 +261,35 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
if tgBotToken != "" { if tgBotToken != "" {
err := settingService.SetTgBotToken(tgBotToken) err := settingService.SetTgBotToken(tgBotToken)
if err != nil { if err != nil {
fmt.Printf("Error setting Telegram bot token: %v\n", err) fmt.Printf("Error setting Telegram bot token设置TG电报机器人令牌出错: %v\n", err)
return return
} }
logger.Info("Successfully updated Telegram bot token.") logger.Info("Successfully updated Telegram bot token----->>已成功更新TG电报机器人令牌")
} }
if tgBotRuntime != "" { if tgBotRuntime != "" {
err := settingService.SetTgbotRuntime(tgBotRuntime) err := settingService.SetTgbotRuntime(tgBotRuntime)
if err != nil { if err != nil {
fmt.Printf("Error setting Telegram bot runtime: %v\n", err) fmt.Printf("Error setting Telegram bot runtime设置TG电报机器人通知周期出错: %v\n", err)
return return
} }
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime) logger.Infof("Successfully updated Telegram bot runtime to已成功将TG电报机器人通知周期设置为 [%s].", tgBotRuntime)
} }
if tgBotChatid != "" { if tgBotChatid != "" {
err := settingService.SetTgBotChatId(tgBotChatid) err := settingService.SetTgBotChatId(tgBotChatid)
if err != nil { if err != nil {
fmt.Printf("Error setting Telegram bot chat ID: %v\n", err) fmt.Printf("Error setting Telegram bot chat ID设置TG电报机器人管理者聊天ID出错: %v\n", err)
return return
} }
logger.Info("Successfully updated Telegram bot chat ID.") logger.Info("Successfully updated Telegram bot chat ID----->>已成功更新TG电报机器人管理者聊天ID")
} }
} }
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) { func updateSetting(port int, username string, password string, webBasePath string) {
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
fmt.Println("Database initialization failed:", err) fmt.Println("Database initialization failed(初始化数据库失败):", err)
return return
} }
@ -245,47 +299,27 @@ func updateSetting(port int, username string, password string, webBasePath strin
if port > 0 { if port > 0 {
err := settingService.SetPort(port) err := settingService.SetPort(port)
if err != nil { if err != nil {
fmt.Println("Failed to set port:", err) fmt.Println("Failed to set port(设置端口失败):", err)
} else { } else {
fmt.Printf("Port set successfully: %v\n", port) fmt.Printf("Port set successfully(端口设置成功): %v\n", port)
} }
} }
if username != "" || password != "" { if username != "" || password != "" {
err := userService.UpdateFirstUser(username, password) err := userService.UpdateFirstUser(username, password)
if err != nil { if err != nil {
fmt.Println("Failed to update username and password:", err) fmt.Println("Failed to update username and password(更新用户名和密码失败):", err)
} else { } else {
fmt.Println("Username and password updated successfully") fmt.Println("Username and password updated successfully------>>用户名和密码更新成功")
} }
} }
if webBasePath != "" { if webBasePath != "" {
err := settingService.SetBasePath(webBasePath) err := settingService.SetBasePath(webBasePath)
if err != nil { if err != nil {
fmt.Println("Failed to set base URI path:", err) fmt.Println("Failed to set base URI path(设置访问路径失败):", err)
} else { } else {
fmt.Println("Base URI path set successfully") fmt.Println("Base URI path set successfully------>>设置访问路径成功")
}
}
if resetTwoFactor {
err := settingService.SetTwoFactorEnable(false)
if err != nil {
fmt.Println("Failed to reset two-factor authentication:", err)
} else {
settingService.SetTwoFactorToken("")
fmt.Println("Two-factor authentication reset successfully")
}
}
if listenIP != "" {
err := settingService.SetListen(listenIP)
if err != nil {
fmt.Println("Failed to set listen IP:", err)
} else {
fmt.Printf("listen %v set successfully", listenIP)
} }
} }
} }
@ -301,50 +335,19 @@ func updateCert(publicKey string, privateKey string) {
settingService := service.SettingService{} settingService := service.SettingService{}
err = settingService.SetCertFile(publicKey) err = settingService.SetCertFile(publicKey)
if err != nil { if err != nil {
fmt.Println("set certificate public key failed:", err) fmt.Println("set certificate public key failed(设置证书公钥失败):", err)
} else { } else {
fmt.Println("set certificate public key success") fmt.Println("set certificate public key success--------->>设置证书公钥成功")
} }
err = settingService.SetKeyFile(privateKey) err = settingService.SetKeyFile(privateKey)
if err != nil { if err != nil {
fmt.Println("set certificate private key failed:", err) fmt.Println("set certificate private key failed(设置证书私钥失败):", err)
} else { } else {
fmt.Println("set certificate private key success") fmt.Println("set certificate private key success--------->>设置证书私钥成功")
} }
} else { } else {
fmt.Println("both public and private key should be entered.") fmt.Println("both public and private key should be entered.------>>必须同时输入证书公钥和私钥")
}
}
func GetCertificate(getCert bool) {
if getCert {
settingService := service.SettingService{}
certFile, err := settingService.GetCertFile()
if err != nil {
fmt.Println("get cert file failed, error info:", err)
}
keyFile, err := settingService.GetKeyFile()
if err != nil {
fmt.Println("get key file failed, error info:", err)
}
fmt.Println("cert:", certFile)
fmt.Println("key:", keyFile)
}
}
func GetListenIP(getListen bool) {
if getListen {
settingService := service.SettingService{}
ListenIP, err := settingService.GetListen()
if err != nil {
log.Printf("Failed to retrieve listen IP: %v", err)
return
}
fmt.Println("listenIP:", ListenIP)
} }
} }
@ -355,9 +358,40 @@ func migrateDb() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
fmt.Println("Start migrating database...") fmt.Println("Start migrating database...---->>开始迁移数据库...")
inboundService.MigrateDB() inboundService.MigrateDB()
fmt.Println("Migration done!") fmt.Println("")
fmt.Println("Migration done!------------>>迁移完成!")
}
func removeSecret() {
userService := service.UserService{}
secretExists, err := userService.CheckSecretExistence()
if err != nil {
fmt.Println("Error checking secret existence:", err)
return
}
if !secretExists {
fmt.Println("No secret exists to remove.")
return
}
err = userService.RemoveUserSecret()
if err != nil {
fmt.Println("Error removing secret:", err)
return
}
settingService := service.SettingService{}
err = settingService.SetSecretStatus(false)
if err != nil {
fmt.Println("Error updating secret status:", err)
return
}
fmt.Println("Secret removed successfully.")
} }
func main() { func main() {
@ -376,8 +410,6 @@ func main() {
var username string var username string
var password string var password string
var webBasePath string var webBasePath string
var listenIP string
var getListen bool
var webCertFile string var webCertFile string
var webKeyFile string var webKeyFile string
var tgbottoken string var tgbottoken string
@ -386,18 +418,14 @@ func main() {
var tgbotRuntime string var tgbotRuntime string
var reset bool var reset bool
var show bool var show bool
var getCert bool var remove_secret bool
var resetTwoFactor bool
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings") settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Display current settings") settingCmd.BoolVar(&show, "show", false, "Display current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
settingCmd.IntVar(&port, "port", 0, "Set panel port number") settingCmd.IntVar(&port, "port", 0, "Set panel port number")
settingCmd.StringVar(&username, "username", "", "Set login username") settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password") settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel") settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
settingCmd.BoolVar(&resetTwoFactor, "resetTwoFactor", false, "Reset two-factor authentication settings")
settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel") settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel") settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot") settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
@ -440,20 +468,17 @@ func main() {
if reset { if reset {
resetSetting() resetSetting()
} else { } else {
updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor) updateSetting(port, username, password, webBasePath)
} }
if show { if show {
showSetting(show) showSetting(show)
} }
if getListen {
GetListenIP(getListen)
}
if getCert {
GetCertificate(getCert)
}
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") { if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime) updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
} }
if remove_secret {
removeSecret()
}
if enabletgbot { if enabletgbot {
updateTgbotEnableSts(enabletgbot) updateTgbotEnableSts(enabletgbot)
} }
@ -468,8 +493,9 @@ func main() {
} else { } else {
updateCert(webCertFile, webKeyFile) updateCert(webCertFile, webKeyFile)
} }
default: default:
fmt.Println("Invalid subcommands") fmt.Println("Invalid subcommands----->>无效命令")
fmt.Println() fmt.Println()
runCmd.Usage() runCmd.Usage()
fmt.Println() fmt.Println()

1466
x-ui.sh

File diff suppressed because it is too large Load Diff