Merge branch 'MHSanaei:main' into main

This commit is contained in:
uzziell 2023-04-22 16:08:56 +03:30 committed by GitHub
commit 386eed2f01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 2342 additions and 1031 deletions

View file

@ -10,11 +10,11 @@ jobs:
name: build x-ui amd64 version name: build x-ui amd64 version
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3.5.1 - uses: actions/checkout@v3.5.2
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v4.0.0 uses: actions/setup-go@v4.0.0
with: with:
go-version: 'stable' go-version: "stable"
- name: build linux amd64 version - name: build linux amd64 version
run: | run: |
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
@ -28,7 +28,7 @@ jobs:
cd bin cd bin
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip geoip.dat geosite.dat rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
@ -50,11 +50,11 @@ jobs:
name: build x-ui arm64 version name: build x-ui arm64 version
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3.5.1 - uses: actions/checkout@v3.5.2
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v4.0.0 uses: actions/setup-go@v4.0.0
with: with:
go-version: 'stable' go-version: "stable"
- name: build linux arm64 version - name: build linux arm64 version
run: | run: |
sudo apt-get update sudo apt-get update
@ -70,7 +70,7 @@ jobs:
cd bin cd bin
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat

2
.gitignore vendored
View file

@ -1,5 +1,6 @@
.idea .idea
tmp tmp
backup/
bin/ bin/
dist/ dist/
x-ui-*.tar.gz x-ui-*.tar.gz
@ -9,4 +10,5 @@ x-ui-*.tar.gz
main main
release/ release/
access.log access.log
error.log
.cache .cache

119
README.md
View file

@ -1,13 +1,15 @@
# 3x-ui # 3x-ui
[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) [![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#) [![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) [![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#) [![Downloads](https://img.shields.io/github/downloads/mhsanaei/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) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment** > **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
**If you think this project is helpful to you, you may wish to give a** :star2:
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)** xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
# Install & Upgrade # Install & Upgrade
@ -15,20 +17,23 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
``` ```
## Install custom version ## Install custom version
To install your desired version you can add the version to the end of install command. Example for ver `v1.0.9`:
To install your desired version you can add the version to the end of install command. Example for ver `v1.2.6`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.0.9 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.2.6
``` ```
# SSL # SSL
``` ```
apt-get install certbot -y apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run certbot renew --dry-run
``` ```
**If you think this project is helpful to you, you may wish to give a** :star2:
# Default settings # Default settings
- Port: 2053 - Port: 2053
@ -36,18 +41,63 @@ certbot renew --dry-run
- database path: /etc/x-ui/x-ui.db - database path: /etc/x-ui/x-ui.db
- xray config path: /usr/local/x-ui/bin/config.json - xray config path: /usr/local/x-ui/bin/config.json
before you set ssl on settings Before you set ssl on settings
- http:// ip or domain:2053/xui
- http://ip:2053/xui
- http://domain:2053/xui
After you set ssl on settings After you set ssl on settings
- https://yourdomain:2053/xui - https://yourdomain:2053/xui
# Enable Traffic For Users: # Environment Variables
| Variable | Type | Default |
| -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
Example:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
# Xray Configurations:
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install) **copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
- [enable traffic](./media/enable-traffic.txt)
- [enable traffic+block all IR IP address](./media/enable-traffic+block-IR-IP.txt) - [traffic](./media/configs/traffic.json)
- [enable traffic+block all IR domain](./media/enable-traffic+block-IR-domain.txt) - [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
If you want to use routing to WARP follow steps as below:
1. If you already installed warp, you can uninstall using below command:
```sh
warp u
```
2. Install WARP on **socks proxy mode**:
```sh
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
```
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
Config Features:
- Block Ads
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
- Fix Google 403 error
# Features # Features
@ -62,7 +112,8 @@ After you set ssl on settings
- Support https access panel (self-provided domain name + ssl certificate) - Support https access panel (self-provided domain name + ssl certificate)
- Support one-click SSL certificate application and automatic renewal - Support one-click SSL certificate application and automatic renewal
- For more advanced configuration items, please refer to the panel - For more advanced configuration items, please refer to the panel
- fix api routes (user setting will create with api) - Fix api routes (user setting will create with api)
- Support to change configs by different items provided in panel
# Tg robot use # Tg robot use
@ -79,8 +130,8 @@ Set the robot-related parameters in the panel background, including:
Reference syntax: Reference syntax:
- 30 * * * * * //Notify at the 30s of each point - 30 \* \* \* \* \* //Notify at the 30s of each point
- 0 */10 * * * * //Notify at the first second of each 10 minutes - 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
- @hourly // hourly notification - @hourly // hourly notification
- @daily // Daily notification (00:00 in the morning) - @daily // Daily notification (00:00 in the morning)
- @every 8h // notify every 8 hours - @every 8h // notify every 8 hours
@ -100,38 +151,46 @@ Reference syntax:
- Check depleted users - Check depleted users
- Receive backup by request and in periodic reports - Receive backup by request and in periodic reports
## API routes ## API routes
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login - `/login` with `PUSH` user data: `{username: '', password: ''}` for login
- `/xui/API/inbounds` base for following actions: - `/xui/API/inbounds` base for following actions:
| Method | Path | Action | | Method | Path | Action |
| ------------- | ------------- | ------------- | | :----: | ---------------------------------- | ------------------------------------------- |
| GET | "/list" | Get all inbounds | | `GET` | `"/list"` | Get all inbounds |
| GET | "/get/:id" | Get inbound with inbound.id | | `GET` | `"/get/:id"` | Get inbound with inbound.id |
| POST | "/add" | Add inbound | | `POST` | `"/add"` | Add inbound |
| POST | "/del/:id" | Delete Inbound | | `POST` | `"/del/:id"` | Delete Inbound |
| POST | "/update/:id" | Update Inbound | | `POST` | `"/update/:id"` | Update Inbound |
| POST | "/clientIps/:email" | Client Ip address | | `POST` | `"/clientIps/:email"` | Client Ip address |
| POST | "/clearClientIps/:email" | Clear Client Ip address | | `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
| POST | "/addClient/" | Add Client to inbound | | `POST` | `"/addClient/"` | Add Client to inbound |
| POST | "/delClient/:email" | Delete Client | | `POST` | `"/delClient/:email"` | Delete Client |
| POST | "/updateClient/:index" | Update Client | | `POST` | `"/updateClient/:index"` | Update Client |
| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic | | `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
| POST | "/resetAllTraffics" | Reset traffics of all inbounds | | `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound | | `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
# A Special Thanks To # A Special Thanks To
- [alireza0](https://github.com/alireza0/) - [alireza0](https://github.com/alireza0/)
- [FranzKafkaYu](https://github.com/FranzKafkaYu)
# Suggestion System # Suggestion System
- Ubuntu 20.04+ - Ubuntu 20.04+
- Debian 10+ - Debian 10+
- CentOS 8+ - CentOS 8+
- Fedora 36+ - Fedora 36+
# Buy Me a Coffee
[![](https://img.shields.io/badge/Wallet-USDT__TRC20-green.svg)](#)
```
TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC
```
# Pictures # Pictures
![1](./media/1.png) ![1](./media/1.png)

View file

@ -1 +1 @@
1.2.3 1.2.7

View file

@ -29,6 +29,7 @@ func initUser() error {
user := &model.User{ user := &model.User{
Username: "admin", Username: "admin",
Password: "admin", Password: "admin",
LoginSecret: "",
} }
return db.Create(user).Error return db.Create(user).Error
} }

View file

@ -21,6 +21,7 @@ type User struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"` Id int `json:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
LoginSecret string `json:"loginSecret"`
} }
type Inbound struct { type Inbound struct {

2
go.mod
View file

@ -14,7 +14,7 @@ require (
github.com/pelletier/go-toml/v2 v2.0.7 github.com/pelletier/go-toml/v2 v2.0.7
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.23.3 github.com/shirou/gopsutil/v3 v3.23.3
github.com/xtls/xray-core v1.8.0 github.com/xtls/xray-core v1.8.1
go.uber.org/atomic v1.10.0 go.uber.org/atomic v1.10.0
golang.org/x/text v0.9.0 golang.org/x/text v0.9.0
google.golang.org/grpc v1.54.0 google.golang.org/grpc v1.54.0

33
go.sum
View file

@ -19,6 +19,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo= github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo= github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
@ -43,7 +44,7 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
@ -61,7 +62,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
@ -79,7 +80,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@ -97,7 +98,7 @@ github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -106,7 +107,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA= github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0= github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@ -122,15 +123,15 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:ULRv/GPW5KYDafE0FACN2no+HTCyQLUtfyOIeyp3GNc= github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08= github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q= github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE= github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
@ -164,9 +165,9 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50= github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k= github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI= github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
@ -183,10 +184,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -227,7 +228,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -23,23 +23,14 @@ else
fi fi
echo "The OS release is: $release" echo "The OS release is: $release"
arch=$(arch) arch3xui() {
case "$(uname -m)" in
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then x86_64 | x64 | amd64 ) echo 'amd64' ;;
arch="amd64" armv8 | arm64 | aarch64 ) echo 'arm64' ;;
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then * ) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
arch="arm64" esac
else }
arch="amd64" echo "arch: $(arch3xui)"
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
fi
echo "arch: ${arch}"
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
exit -1
fi
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@ -122,18 +113,18 @@ install_x-ui() {
exit 1 exit 1
fi fi
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..." echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}" echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
exit 1 exit 1
fi fi
else else
last_version=$1 last_version=$1
url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz" url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz"
echo -e "Begining to install x-ui $1" echo -e "Begining to install x-ui $1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url} wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz ${url}
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed,please check the version exists${plain}" echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}"
exit 1 exit 1
fi fi
fi fi
@ -142,10 +133,10 @@ install_x-ui() {
rm /usr/local/x-ui/ -rf rm /usr/local/x-ui/ -rf
fi fi
tar zxvf x-ui-linux-${arch}.tar.gz tar zxvf x-ui-linux-$(arch3xui).tar.gz
rm x-ui-linux-${arch}.tar.gz -f rm x-ui-linux-$(arch3xui).tar.gz -f
cd x-ui cd x-ui
chmod +x x-ui bin/xray-linux-${arch} chmod +x x-ui bin/xray-linux-$(arch3xui)
cp -f x-ui.service /etc/systemd/system/ cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh

29
main.go
View file

@ -51,8 +51,8 @@ func runWebServer() {
} }
sigCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)
//信号量捕获处理 // Trap shutdown signals
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL) signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
for { for {
sig := <-sigCh sig := <-sigCh
@ -204,6 +204,24 @@ func updateSetting(port int, username string, password string) {
} }
} }
func removeSecret() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
userService := service.UserService{}
err = userService.RemoveUserSecret()
if err != nil {
fmt.Println(err)
}
settingService := service.SettingService{}
err = settingService.SetSecretStatus(false)
if err != nil {
fmt.Println(err)
}
}
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {
runWebServer() runWebServer()
@ -229,6 +247,7 @@ func main() {
var tgbotRuntime string var tgbotRuntime string
var reset bool var reset bool
var show bool var show bool
var remove_secret bool
settingCmd.BoolVar(&reset, "reset", false, "reset all settings") settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
settingCmd.BoolVar(&show, "show", false, "show current settings") settingCmd.BoolVar(&show, "show", false, "show current settings")
settingCmd.IntVar(&port, "port", 0, "set panel port") settingCmd.IntVar(&port, "port", 0, "set panel port")
@ -290,6 +309,12 @@ func main() {
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") { if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime) updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
} }
if remove_secret {
removeSecret()
}
if enabletgbot {
updateTgbotEnableSts(enabletgbot)
}
default: default:
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands") fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
fmt.Println() fmt.Println()

View file

@ -0,0 +1,88 @@
{
"log": {
"loglevel": "warning",
"access": "./access.log",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": ["HandlerService", "LoggerService", "StatsService"]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
},
{
"tag": "IPv4",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv4"
}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": ["geoip:private"]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": ["bittorrent"]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"geosite:category-ads-all",
"geosite:category-ads",
"geosite:google-ads",
"geosite:spotify-ads"
]
},
{
"type": "field",
"outboundTag": "IPv4",
"domain": ["geosite:google"]
}
]
},
"stats": {}
}

View file

@ -0,0 +1,98 @@
{
"log": {
"loglevel": "warning",
"access": "./access.log",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": ["HandlerService", "LoggerService", "StatsService"]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
},
{
"tag": "WARP",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 40000
}
]
}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": ["geoip:private"]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": ["bittorrent"]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"geosite:category-ads-all",
"geosite:category-ads",
"geosite:google-ads",
"geosite:spotify-ads"
]
},
{
"type": "field",
"outboundTag": "WARP",
"domain": [
"geosite:google",
"geosite:netflix",
"geosite:spotify",
"geosite:openai"
]
}
]
},
"stats": {}
}

View file

@ -1,25 +1,22 @@
{ {
"log": { "log": {
"loglevel": "warning", "loglevel": "warning",
"access": "./access.log" "access": "./access.log",
"error": "./error.log"
}, },
"api": { "api": {
"services": [ "tag": "api",
"HandlerService", "services": ["HandlerService", "LoggerService", "StatsService"]
"LoggerService",
"StatsService"
],
"tag": "api"
}, },
"inbounds": [ "inbounds": [
{ {
"tag": "api",
"listen": "127.0.0.1", "listen": "127.0.0.1",
"port": 62789, "port": 62789,
"protocol": "dokodemo-door", "protocol": "dokodemo-door",
"settings": { "settings": {
"address": "127.0.0.1" "address": "127.0.0.1"
}, }
"tag": "api"
} }
], ],
"outbounds": [ "outbounds": [
@ -28,16 +25,16 @@
"settings": {} "settings": {}
}, },
{ {
"tag": "blocked",
"protocol": "blackhole", "protocol": "blackhole",
"settings": {}, "settings": {}
"tag": "blocked"
} }
], ],
"policy": { "policy": {
"levels": { "levels": {
"0": { "0": {
"statsUserUplink": true, "statsUserDownlink": true,
"statsUserDownlink": true "statsUserUplink": true
} }
}, },
"system": { "system": {
@ -49,34 +46,29 @@
"domainStrategy": "IPIfNonMatch", "domainStrategy": "IPIfNonMatch",
"rules": [ "rules": [
{ {
"inboundTag": [ "type": "field",
"api" "inboundTag": ["api"],
], "outboundTag": "api"
"outboundTag": "api",
"type": "field"
}, },
{ {
"ip": [ "type": "field",
"geoip:private"
],
"outboundTag": "blocked", "outboundTag": "blocked",
"type": "field" "ip": ["geoip:private"]
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"protocol": [ "protocol": ["bittorrent"]
"bittorrent"
],
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"domain": [ "domain": [
"regexp:.+.ir$", "regexp:.*\\.ir$",
"ext:iran.dat:ir", "ext:iran.dat:ir",
"ext:iran.dat:other" "ext:iran.dat:other",
], "geosite:category-ir"
"type": "field" ]
} }
] ]
}, },

View file

@ -1,25 +1,22 @@
{ {
"log": { "log": {
"loglevel": "warning", "loglevel": "warning",
"access": "./access.log" "access": "./access.log",
"error": "./error.log"
}, },
"api": { "api": {
"services": [ "tag": "api",
"HandlerService", "services": ["HandlerService", "LoggerService", "StatsService"]
"LoggerService",
"StatsService"
],
"tag": "api"
}, },
"inbounds": [ "inbounds": [
{ {
"tag": "api",
"listen": "127.0.0.1", "listen": "127.0.0.1",
"port": 62789, "port": 62789,
"protocol": "dokodemo-door", "protocol": "dokodemo-door",
"settings": { "settings": {
"address": "127.0.0.1" "address": "127.0.0.1"
}, }
"tag": "api"
} }
], ],
"outbounds": [ "outbounds": [
@ -28,16 +25,16 @@
"settings": {} "settings": {}
}, },
{ {
"tag": "blocked",
"protocol": "blackhole", "protocol": "blackhole",
"settings": {}, "settings": {}
"tag": "blocked"
} }
], ],
"policy": { "policy": {
"levels": { "levels": {
"0": { "0": {
"statsUserUplink": true, "statsUserDownlink": true,
"statsUserDownlink": true "statsUserUplink": true
} }
}, },
"system": { "system": {
@ -49,32 +46,29 @@
"domainStrategy": "IPIfNonMatch", "domainStrategy": "IPIfNonMatch",
"rules": [ "rules": [
{ {
"inboundTag": [ "type": "field",
"api" "inboundTag": ["api"],
], "outboundTag": "api"
"outboundTag": "api",
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"protocol": [ "ip": ["geoip:private"]
"bittorrent"
],
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"ip": [ "protocol": ["bittorrent"]
"geoip:private"
],
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"ip": [ "ip": ["geoip:private"]
"geoip:ir" },
], {
"type": "field" "type": "field",
"outboundTag": "blocked",
"ip": ["geoip:ir"]
} }
] ]
}, },

View file

@ -1,25 +1,22 @@
{ {
"log": { "log": {
"loglevel": "warning", "loglevel": "warning",
"access": "./access.log" "access": "./access.log",
"error": "./error.log"
}, },
"api": { "api": {
"services": [ "tag": "api",
"HandlerService", "services": ["HandlerService", "LoggerService", "StatsService"]
"LoggerService",
"StatsService"
],
"tag": "api"
}, },
"inbounds": [ "inbounds": [
{ {
"tag": "api",
"listen": "127.0.0.1", "listen": "127.0.0.1",
"port": 62789, "port": 62789,
"protocol": "dokodemo-door", "protocol": "dokodemo-door",
"settings": { "settings": {
"address": "127.0.0.1" "address": "127.0.0.1"
}, }
"tag": "api"
} }
], ],
"outbounds": [ "outbounds": [
@ -28,16 +25,16 @@
"settings": {} "settings": {}
}, },
{ {
"tag": "blocked",
"protocol": "blackhole", "protocol": "blackhole",
"settings": {}, "settings": {}
"tag": "blocked"
} }
], ],
"policy": { "policy": {
"levels": { "levels": {
"0": { "0": {
"statsUserUplink": true, "statsUserDownlink": true,
"statsUserDownlink": true "statsUserUplink": true
} }
}, },
"system": { "system": {
@ -49,25 +46,19 @@
"domainStrategy": "IPIfNonMatch", "domainStrategy": "IPIfNonMatch",
"rules": [ "rules": [
{ {
"inboundTag": [ "type": "field",
"api" "inboundTag": ["api"],
], "outboundTag": "api"
"outboundTag": "api",
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"ip": [ "ip": ["geoip:private"]
"geoip:private"
],
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"protocol": [ "protocol": ["bittorrent"]
"bittorrent"
],
"type": "field"
} }
] ]
}, },

View file

@ -246,6 +246,11 @@
background-color: #2e3b52; background-color: #2e3b52;
} }
.ant-card-dark .ant-select-disabled .ant-select-selection {
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: #242c3a;
}
.ant-card-dark .ant-collapse-item { .ant-card-dark .ant-collapse-item {
color: hsla(0,0%,100%,.65); color: hsla(0,0%,100%,.65);
background-color: #161b22; background-color: #161b22;

View file

@ -3,6 +3,7 @@ class User {
constructor() { constructor() {
this.username = ""; this.username = "";
this.password = ""; this.password = "";
this.LoginSecret = "";
} }
} }
@ -180,6 +181,7 @@ class AllSetting {
this.tgBotBackup = false; this.tgBotBackup = false;
this.tgCpu = ""; this.tgCpu = "";
this.xrayTemplateConfig = ""; this.xrayTemplateConfig = "";
this.secretEnable = false;
this.timeLocation = "Asia/Tehran"; this.timeLocation = "Asia/Tehran";

View file

@ -49,6 +49,7 @@ const XTLS_FLOW_CONTROL = {
const TLS_FLOW_CONTROL = { const TLS_FLOW_CONTROL = {
VISION: "xtls-rprx-vision", VISION: "xtls-rprx-vision",
VISION_UDP443: "xtls-rprx-vision-udp443",
}; };
const TLS_VERSION_OPTION = { const TLS_VERSION_OPTION = {
@ -91,9 +92,6 @@ const UTLS_FINGERPRINT = {
UTLS_RANDOMIZED: "randomized", UTLS_RANDOMIZED: "randomized",
}; };
const bytesToHex = e => Array.from(e).map(e => e.toString(16).padStart(2, 0)).join('');
const hexToBytes = e => new Uint8Array(e.match(/[0-9a-f]{2}/gi).map(e => parseInt(e, 16)));
const ALPN_OPTION = { const ALPN_OPTION = {
H3: "h3", H3: "h3",
H2: "h2", H2: "h2",
@ -481,7 +479,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '', cipherSuites = '',
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1], alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
settings=[new TlsStreamSettings.Settings()]) { settings=new TlsStreamSettings.Settings()) {
super(); super();
this.server = serverName; this.server = serverName;
this.minVersion = minVersion; this.minVersion = minVersion;
@ -508,8 +506,7 @@ class TlsStreamSettings extends XrayCommonClass {
} }
if (!ObjectUtil.isEmpty(json.settings)) { if (!ObjectUtil.isEmpty(json.settings)) {
let values = json.settings[0]; settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
} }
return new TlsStreamSettings( return new TlsStreamSettings(
json.serverName, json.serverName,
@ -530,7 +527,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites: this.cipherSuites, cipherSuites: this.cipherSuites,
certificates: TlsStreamSettings.toJsonArray(this.certs), certificates: TlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn, alpn: this.alpn,
settings: TlsStreamSettings.toJsonArray(this.settings), settings: this.settings,
}; };
} }
} }
@ -598,71 +595,204 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
}; };
} }
}; };
class XtlsStreamSettings extends XrayCommonClass {
constructor(serverName='',
certificates=[new XtlsStreamSettings.Cert()],
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
settings=new XtlsStreamSettings.Settings()) {
super();
this.server = serverName;
this.certs = certificates;
this.alpn = alpn;
this.settings = settings;
}
addCert(cert) {
this.certs.push(cert);
}
removeCert(index) {
this.certs.splice(index, 1);
}
static fromJson(json={}) {
let certs;
let settings;
if (!ObjectUtil.isEmpty(json.certificates)) {
certs = json.certificates.map(cert => XtlsStreamSettings.Cert.fromJson(cert));
}
if (!ObjectUtil.isEmpty(json.settings)) {
settings = new XtlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.serverName);
}
return new XtlsStreamSettings(
json.serverName,
certs,
json.alpn,
settings,
);
}
toJson() {
return {
serverName: this.server,
certificates: XtlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn,
settings: this.settings,
};
}
}
XtlsStreamSettings.Cert = class extends XrayCommonClass {
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
super();
this.useFile = useFile;
this.certFile = certificateFile;
this.keyFile = keyFile;
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
this.key = key instanceof Array ? key.join('\n') : key;
}
static fromJson(json={}) {
if ('certificateFile' in json && 'keyFile' in json) {
return new XtlsStreamSettings.Cert(
true,
json.certificateFile,
json.keyFile,
);
} else {
return new XtlsStreamSettings.Cert(
false, '', '',
json.certificate.join('\n'),
json.key.join('\n'),
);
}
}
toJson() {
if (this.useFile) {
return {
certificateFile: this.certFile,
keyFile: this.keyFile,
};
} else {
return {
certificate: this.cert.split('\n'),
key: this.key.split('\n'),
};
}
}
};
XtlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, serverName = '') {
super();
this.allowInsecure = allowInsecure;
this.serverName = serverName;
}
static fromJson(json = {}) {
return new XtlsStreamSettings.Settings(
json.allowInsecure,
json.servername,
);
}
toJson() {
return {
allowInsecure: this.allowInsecure,
serverName: this.serverName,
};
}
};
class RealityStreamSettings extends XrayCommonClass { class RealityStreamSettings extends XrayCommonClass {
constructor( constructor(
show = false,xver = 0, show = false,xver = 0,
fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX,
dest = 'yahoo.com:443', dest = 'yahoo.com:443',
serverNames = 'yahoo.com,www.yahoo.com', serverNames = 'yahoo.com,www.yahoo.com',
privateKey = RandomUtil.randomX25519PrivateKey(), privateKey = '',
publicKey = '',
minClient = '', minClient = '',
maxClient = '', maxClient = '',
maxTimediff = 0, maxTimediff = 0,
shortIds = RandomUtil.randowShortId() shortIds = RandomUtil.randowShortId(),
) settings= new RealityStreamSettings.Settings()
{ ){
super(); super();
this.show = show; this.show = show;
this.xver = xver; this.xver = xver;
this.fingerprint = fingerprint;
this.dest = dest; this.dest = dest;
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames; this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
this.privateKey = privateKey; this.privateKey = privateKey;
this.publicKey = RandomUtil.randomX25519PublicKey(this.privateKey);
this.minClient = minClient; this.minClient = minClient;
this.maxClient = maxClient; this.maxClient = maxClient;
this.maxTimediff = maxTimediff; this.maxTimediff = maxTimediff;
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds; this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
this.settings = settings;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
let settings;
if (!ObjectUtil.isEmpty(json.settings)) {
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
}
return new RealityStreamSettings( return new RealityStreamSettings(
json.show, json.show,
json.xver, json.xver,
json.fingerprint,
json.dest, json.dest,
json.serverNames, json.serverNames,
json.privateKey, json.privateKey,
json.publicKey,
json.minClient, json.minClient,
json.maxClient, json.maxClient,
json.maxTimediff, json.maxTimediff,
json.shortIds json.shortIds,
json.settings,
); );
} }
toJson() { toJson() {
return { return {
show: this.show, show: this.show,
xver: this.xver, xver: this.xver,
fingerprint: this.fingerprint,
dest: this.dest, dest: this.dest,
serverNames: this.serverNames.split(/,||\s+/), serverNames: this.serverNames.split(","),
privateKey: this.privateKey, privateKey: this.privateKey,
publicKey: this.publicKey,
minClient: this.minClient, minClient: this.minClient,
maxClient: this.maxClient, maxClient: this.maxClient,
maxTimediff: this.maxTimediff, maxTimediff: this.maxTimediff,
shortIds: this.shortIds.split(/,||\s+/) shortIds: this.shortIds.split(","),
settings: this.settings,
}; };
} }
}
RealityStreamSettings.Settings = class extends XrayCommonClass {
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '') {
super();
this.publicKey = publicKey;
this.fingerprint = fingerprint;
this.serverName = serverName;
} }
static fromJson(json = {}) {
return new RealityStreamSettings.Settings(
json.publicKey,
json.fingerprint,
json.serverName,
);
}
toJson() {
return {
publicKey: this.publicKey,
fingerprint: this.fingerprint,
serverName: this.serverName,
};
}
};
class StreamSettings extends XrayCommonClass { class StreamSettings extends XrayCommonClass {
constructor(network='tcp', constructor(network='tcp',
security='none', security='none',
tlsSettings=new TlsStreamSettings(), tlsSettings=new TlsStreamSettings(),
xtlsSettings=new XtlsStreamSettings(),
realitySettings = new RealityStreamSettings(), realitySettings = new RealityStreamSettings(),
tcpSettings=new TcpStreamSettings(), tcpSettings=new TcpStreamSettings(),
kcpSettings=new KcpStreamSettings(), kcpSettings=new KcpStreamSettings(),
@ -675,6 +805,7 @@ class StreamSettings extends XrayCommonClass {
this.network = network; this.network = network;
this.security = security; this.security = security;
this.tls = tlsSettings; this.tls = tlsSettings;
this.xtls = xtlsSettings;
this.reality = realitySettings; this.reality = realitySettings;
this.tcp = tcpSettings; this.tcp = tcpSettings;
this.kcp = kcpSettings; this.kcp = kcpSettings;
@ -685,7 +816,7 @@ class StreamSettings extends XrayCommonClass {
} }
get isTls() { get isTls() {
return this.security === 'tls'; return this.security === "tls";
} }
set isTls(isTls) { set isTls(isTls) {
@ -696,12 +827,12 @@ class StreamSettings extends XrayCommonClass {
} }
} }
get isXTLS() { get isXtls() {
return this.security === "xtls"; return this.security === "xtls";
} }
set isXTLS(isXTLS) { set isXtls(isXtls) {
if (isXTLS) { if (isXtls) {
this.security = 'xtls'; this.security = 'xtls';
} else { } else {
this.security = 'none'; this.security = 'none';
@ -715,27 +846,20 @@ class StreamSettings extends XrayCommonClass {
set isReality(isReality) { set isReality(isReality) {
if (isReality) { if (isReality) {
this.security = "reality"; this.security = 'reality';
} else { } else {
this.security = "none"; this.security = 'none';
} }
} }
static fromJson(json = {}) { static fromJson(json={}) {
let tls, reality;
if (json.security === "xtls") {
tls = TlsStreamSettings.fromJson(json.XTLSSettings);
} else if (json.security === "tls") {
tls = TlsStreamSettings.fromJson(json.tlsSettings);
}
if (json.security === "reality") {
reality = RealityStreamSettings.fromJson(json.realitySettings)
}
return new StreamSettings( return new StreamSettings(
json.network, json.network,
json.security, json.security,
tls, TlsStreamSettings.fromJson(json.tlsSettings),
reality, XtlsStreamSettings.fromJson(json.xtlsSettings),
RealityStreamSettings.fromJson(json.realitySettings),
TcpStreamSettings.fromJson(json.tcpSettings), TcpStreamSettings.fromJson(json.tcpSettings),
KcpStreamSettings.fromJson(json.kcpSettings), KcpStreamSettings.fromJson(json.kcpSettings),
WsStreamSettings.fromJson(json.wsSettings), WsStreamSettings.fromJson(json.wsSettings),
@ -751,9 +875,9 @@ class StreamSettings extends XrayCommonClass {
network: network, network: network,
security: this.security, security: this.security,
tlsSettings: this.isTls ? this.tls.toJson() : undefined, tlsSettings: this.isTls ? this.tls.toJson() : undefined,
XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined, xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
realitySettings: this.isReality ? this.reality.toJson() : undefined, realitySettings: this.isReality ? this.reality.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined, kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
wsSettings: network === 'ws' ? this.ws.toJson() : undefined, wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
httpSettings: network === 'http' ? this.http.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined,
@ -826,22 +950,18 @@ class Inbound extends XrayCommonClass {
set tls(isTls) { set tls(isTls) {
if (isTls) { if (isTls) {
this.xtls = false;
this.reality = false;
this.stream.security = 'tls'; this.stream.security = 'tls';
} else { } else {
this.stream.security = 'none'; this.stream.security = 'none';
} }
} }
get XTLS() { get xtls() {
return this.stream.security === 'xtls'; return this.stream.security === 'xtls';
} }
set XTLS(isXTLS) { set xtls(isXtls) {
if (isXTLS) { if (isXtls) {
this.xtls = false;
this.reality = false;
this.stream.security = 'xtls'; this.stream.security = 'xtls';
} else { } else {
this.stream.security = 'none'; this.stream.security = 'none';
@ -850,19 +970,14 @@ class Inbound extends XrayCommonClass {
//for Reality //for Reality
get reality() { get reality() {
if (this.stream.security === "reality") { return this.stream.security === 'reality';
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
}
return false;
} }
set reality(isReality) { set reality(isReality) {
if (isReality) { if (isReality) {
this.tls = false; this.stream.security = 'reality';
this.xtls = false;
this.stream.security = "reality";
} else { } else {
this.stream.security = "none"; this.stream.security = 'none';
} }
} }
@ -969,7 +1084,7 @@ class Inbound extends XrayCommonClass {
} }
get serverName() { get serverName() {
if (this.stream.isTls || this.stream.isXTLS) { if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
return this.stream.tls.server; return this.stream.tls.server;
} }
return ""; return "";
@ -1070,7 +1185,14 @@ class Inbound extends XrayCommonClass {
default: default:
return false; return false;
} }
return this.network === "tcp" || this.network === "grpc" || this.network === "http"; switch (this.network) {
case "tcp":
case "http":
case "grpc":
return true;
default:
return false;
}
} }
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
@ -1090,7 +1212,7 @@ class Inbound extends XrayCommonClass {
return this.canEnableTls(); return this.canEnableTls();
} }
canEnableXTLS() { canEnableXtls() {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
case Protocols.TROJAN: case Protocols.TROJAN:
@ -1195,10 +1317,10 @@ class Inbound extends XrayCommonClass {
host: host, host: host,
path: path, path: path,
tls: this.stream.security, tls: this.stream.security,
sni: this.stream.tls.settings[0]['serverName'], sni: this.stream.tls.settings.serverName,
fp: this.stream.tls.settings[0]['fingerprint'], fp: this.stream.tls.settings.fingerprint,
alpn: this.stream.tls.alpn.join(','), alpn: this.stream.tls.alpn.join(','),
allowInsecure: this.stream.tls.settings[0].allowInsecure, allowInsecure: this.stream.tls.settings.allowInsecure,
}; };
return 'vmess://' + base64(JSON.stringify(obj, null, 2)); return 'vmess://' + base64(JSON.stringify(obj, null, 2));
} }
@ -1257,51 +1379,51 @@ class Inbound extends XrayCommonClass {
if (this.tls) { if (this.tls) {
params.set("security", "tls"); params.set("security", "tls");
params.set("fp" , this.stream.tls.settings[0]['fingerprint']); params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
} }
if (this.stream.tls.settings[0]['serverName'] !== ''){ if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings[0]['serverName']); params.set("sni", this.stream.tls.settings.serverName);
} }
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) { if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
params.set("flow", this.settings.vlesses[clientIndex].flow); params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
} }
if (this.XTLS) { if (this.xtls) {
params.set("security", "xtls"); params.set("security", "xtls");
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.xtls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.xtls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
address = this.stream.tls.server; address = this.stream.xtls.server;
} }
params.set("flow", this.settings.vlesses[clientIndex].flow); params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
if (this.reality) { if (this.reality) {
params.set("security", "reality"); params.set("security", "reality");
params.set("pbk", this.stream.reality.settings.publicKey);
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("sni", this.stream.reality.serverNames.split(/,||\s+/)[0]); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
} }
if (this.stream.reality.publicKey != "") { if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
params.set("pbk", this.stream.reality.publicKey);
}
if (this.stream.network === 'tcp') {
params.set("flow", this.settings.vlesses[clientIndex].flow); params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
if (this.stream.reality.shortIds != "") { if (this.stream.reality.shortIds.length > 0) {
params.set("sid", this.stream.reality.shortIds); params.set("sid", this.stream.reality.shortIds.split(",")[0]);
} }
if (this.stream.reality.fingerprint != "") { if (!ObjectUtil.isEmpty(this.stream.reality.settings.fingerprint)) {
params.set("fp", this.stream.reality.fingerprint); params.set("fp", this.stream.reality.settings.fingerprint);
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
address = this.stream.reality.settings.serverName;
} }
} }
@ -1376,47 +1498,47 @@ class Inbound extends XrayCommonClass {
if (this.tls) { if (this.tls) {
params.set("security", "tls"); params.set("security", "tls");
params.set("fp" , this.stream.tls.settings[0]['fingerprint']); params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.tls.server;
} }
if (this.stream.tls.settings[0]['serverName'] !== ''){ if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings[0]['serverName']); params.set("sni", this.stream.tls.settings.serverName);
} }
} }
if (this.reality) { if (this.reality) {
params.set("security", "reality"); params.set("security", "reality");
params.set("pbk", this.stream.reality.settings.publicKey);
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("sni", this.stream.reality.serverNames.split(/,||\s+/)[0]); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
} }
if (this.stream.reality.publicKey != "") { if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey)); address = this.stream.reality.settings.serverName;
params.set("pbk", this.stream.reality.publicKey);
} }
if (this.stream.network === 'tcp') { if (this.stream.reality.shortIds.length > 0) {
params.set("flow", this.settings.trojans[clientIndex].flow); params.set("sid", this.stream.reality.shortIds.split(",")[0]);
} }
if (this.stream.reality.shortIds != "") { if (!ObjectUtil.isEmpty(this.stream.reality.settings.fingerprint)) {
params.set("sid", this.stream.reality.shortIds); params.set("fp", this.stream.reality.settings.fingerprint);
} }
if (this.stream.reality.fingerprint != "") { if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
params.set("fp", this.stream.reality.fingerprint); address = this.stream.reality.settings.serverName;
} }
} }
if (this.XTLS) { if (this.xtls) {
params.set("security", "xtls"); params.set("security", "xtls");
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.xtls.alpn);
if(this.stream.tls.settings[0].allowInsecure){ if(this.stream.xtls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server; address = this.stream.xtls.server;
} }
params.set("flow", this.settings.trojans[clientIndex].flow); params.set("flow", this.settings.trojans[clientIndex].flow);
} }

View file

@ -94,26 +94,6 @@ const shortIdSeq = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
]; ];
const x25519Map = new Map(
[
['EH2FWe-Ij_FFAa2u9__-aiErLvVIneP601GOCdlyPWw', "goY3OtfaA4UYbiz7Hn0NysI5QJrK0VT_Chg6RLgUPQU"],
['cKI_6DoMSP1IepeWWXrG3G9nkehl94KYBhagU50g2U0', "VigpKFbSLnHLzBWobZaS1IBmw--giJ51w92y723ajnU"],
['qM2SNyK3NyHB6deWpEP3ITyCGKQFRTna_mlKP0w1QH0', "HYyIGuyNFslmcnNT7mrDdmuXwn4cm7smE_FZbYguKHQ"],
['qCWg5GMEDFd3n1nxDswlIpOHoPUXMLuMOIiLUVzubkI', "rJFC3dUjJxMnVZiUGzmf_LFsJUwFWY-CU5RQgFOHCWM"],
['4NOBxDrEsOhNI3Y3EnVIy_TN-uyBoAjQw6QM0YsOi0s', "CbcY9qc4YuMDJDyyL0OITlU824TBg1O84ClPy27e2RM"],
['eBvFb0M4HpSOwWjtXV8zliiEs_hg56zX4a2LpuuqpEI', "CjulQ2qVIky7ImIfysgQhNX7s_drGLheCGSkVHcLZhc"],
['yEpOzQV04NNcycWVeWtRNTzv5TS-ynTuKRacZCH-6U8', "O9RSr5gSdok2K_tobQnf_scyKVqnCx6C4Jrl7_rCZEQ"],
['CNt6TAUVCwqM6xIBHyni0K3Zqbn2htKQLvLb6XDgh0s', "d9cGLVBrDFS02L2OvkqyqwFZ1Ux3AHs28ehl4Rwiyl0"],
['EInKw-6Wr0rAHXlxxDuZU5mByIzcD3Z-_iWPzXlUL1k', "LlYD2nNVAvyjNvjZGZh4R8PkMIwkc6EycPTvR2LE0nQ"],
['GKIKo7rcXVyle-EUHtGIDtYnDsI6osQmOUl3DTJRAGc', "VcqHivYGGoBkcxOI6cSSjQmneltstkb2OhvO53dyhEM"],
['-FVDzv68IC17fJVlNDlhrrgX44WeBfbhwjWpCQVXGHE', "PGG2EYOvsFt2lAQTD7lqHeRxz2KxvllEDKcUrtizPBU"],
['0H3OJEYEu6XW7woqy7cKh2vzg6YHkbF_xSDTHKyrsn4', "mzevpYbS8kXengBY5p7tt56QE4tS3lwlwRemmkcQeyc"],
['8F8XywN6ci44ES6em2Z0fYYxyptB9uaXY9Hc1WSSPE4', "qCZUdWQZ2H33vWXnOkG8NpxBeq3qn5QWXlfCOWBNkkc"],
['IN0dqfkC10dj-ifRHrg2PmmOrzYs697ajGMwcLbu-1g', "2UW_EO3r7uczPGUUlpJBnMDpDmWUHE2yDzCmXS4sckE"],
['uIcmks5rAhvBe4dRaJOdeSqgxLGGMZhsGk4J4PEKL2s', "F9WJV_74IZp0Ide4hWjiJXk9FRtBUBkUr3mzU-q1lzk"],
]
);
class RandomUtil { class RandomUtil {
static randomIntRange(min, max) { static randomIntRange(min, max) {
@ -170,26 +150,6 @@ class RandomUtil {
}); });
} }
static randowShortId() {
let str = '';
str += this.randomShortIdSeq(8)
return str;
}
static randomX25519PrivateKey() {
let num = x25519Map.size;
let index = this.randomInt(num);
let cntr = 0;
for (let key of x25519Map.keys()) {
if (cntr++ === index) {
return key;
}
}
}
static randomX25519PublicKey(key) {
return x25519Map.get(key)
}
static randomText() { static randomText() {
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
var string = ''; var string = '';
@ -199,6 +159,12 @@ class RandomUtil {
} }
return string; return string;
} }
static randowShortId() {
let str = '';
str += this.randomShortIdSeq(8)
return str;
}
} }
class ObjectUtil { class ObjectUtil {

View file

@ -19,6 +19,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
g.GET("/list", a.getAllInbounds) g.GET("/list", a.getAllInbounds)
g.GET("/get/:id", a.getSingleInbound) g.GET("/get/:id", a.getSingleInbound)
g.GET("/getClientTraffics/:email", a.getClientTraffics)
g.POST("/add", a.addInbound) g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound) g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound) g.POST("/update/:id", a.updateInbound)
@ -39,6 +40,9 @@ func (a *APIController) getAllInbounds(c *gin.Context) {
func (a *APIController) getSingleInbound(c *gin.Context) { func (a *APIController) getSingleInbound(c *gin.Context) {
a.inboundController.getInbound(c) a.inboundController.getInbound(c)
} }
func (a *APIController) getClientTraffics(c *gin.Context) {
a.inboundController.getClientTraffics(c)
}
func (a *APIController) addInbound(c *gin.Context) { func (a *APIController) addInbound(c *gin.Context) {
a.inboundController.addInbound(c) a.inboundController.addInbound(c)
} }

View file

@ -33,7 +33,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/update/:id", a.updateInbound) g.POST("/update/:id", a.updateInbound)
g.POST("/clientIps/:email", a.getClientIps) g.POST("/clientIps/:email", a.getClientIps)
g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/addClient/", a.addInboundClient) g.POST("/addClient", a.addInboundClient)
g.POST("/delClient/:email", a.delInboundClient) g.POST("/delClient/:email", a.delInboundClient)
g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/updateClient/:index", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
@ -78,6 +78,16 @@ func (a *InboundController) getInbound(c *gin.Context) {
jsonObj(c, inbound, nil) jsonObj(c, inbound, nil)
} }
func (a *InboundController) getClientTraffics(c *gin.Context) {
email := c.Param("email")
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
if err != nil {
jsonMsg(c, "Error getting traffics", err)
return
}
jsonObj(c, clientTraffics, nil)
}
func (a *InboundController) addInbound(c *gin.Context) { func (a *InboundController) addInbound(c *gin.Context) {
inbound := &model.Inbound{} inbound := &model.Inbound{}
err := c.ShouldBind(inbound) err := c.ShouldBind(inbound)
@ -151,19 +161,19 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
jsonMsg(c, "Log Cleared", nil) jsonMsg(c, "Log Cleared", nil)
} }
func (a *InboundController) addInboundClient(c *gin.Context) { func (a *InboundController) addInboundClient(c *gin.Context) {
inbound := &model.Inbound{} data := &model.Inbound{}
err := c.ShouldBind(inbound) err := c.ShouldBind(data)
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
return return
} }
err = a.inboundService.AddInboundClient(inbound) err = a.inboundService.AddInboundClient(data)
if err != nil { if err != nil {
jsonMsg(c, "something worng!", err) jsonMsg(c, "something worng!", err)
return return
} }
jsonMsg(c, "Client added", nil) jsonMsg(c, "Client(s) added", nil)
if err == nil { if err == nil {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }

View file

@ -13,11 +13,13 @@ import (
type LoginForm struct { type LoginForm struct {
Username string `json:"username" form:"username"` Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"` Password string `json:"password" form:"password"`
LoginSecret string `json:"loginSecret" form:"loginSecret"`
} }
type IndexController struct { type IndexController struct {
BaseController BaseController
settingService service.SettingService
userService service.UserService userService service.UserService
tgbot service.Tgbot tgbot service.Tgbot
} }
@ -32,6 +34,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
g.GET("/", a.index) g.GET("/", a.index)
g.POST("/login", a.login) g.POST("/login", a.login)
g.GET("/logout", a.logout) g.GET("/logout", a.logout)
g.POST("/getSecretStatus", a.getSecretStatus)
} }
func (a *IndexController) index(c *gin.Context) { func (a *IndexController) index(c *gin.Context) {
@ -57,7 +60,7 @@ func (a *IndexController) login(c *gin.Context) {
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword")) pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
return return
} }
user := a.userService.CheckUser(form.Username, form.Password) user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
timeStr := time.Now().Format("2006-01-02 15:04:05") timeStr := time.Now().Format("2006-01-02 15:04:05")
if user == nil { if user == nil {
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
@ -82,3 +85,11 @@ func (a *IndexController) logout(c *gin.Context) {
session.ClearSession(c) session.ClearSession(c)
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
} }
func (a *IndexController) getSecretStatus(c *gin.Context) {
status, err := a.settingService.GetSecretStatus()
if err == nil {
jsonObj(c, status, nil)
}
}

View file

@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/logs/:count", a.getLogs) g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson) g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb) g.GET("/getDb", a.getDb)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
} }
func (a *ServerController) refreshStatus() { func (a *ServerController) refreshStatus() {
@ -114,7 +115,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
count := c.Param("count") count := c.Param("count")
logs, err := a.serverService.GetLogs(count) logs, err := a.serverService.GetLogs(count)
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, "getLogs", err)
return return
} }
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
@ -123,7 +124,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
func (a *ServerController) getConfigJson(c *gin.Context) { func (a *ServerController) getConfigJson(c *gin.Context) {
configJson, err := a.serverService.GetConfigJson() configJson, err := a.serverService.GetConfigJson()
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, "get config.json", err)
return return
} }
jsonObj(c, configJson, nil) jsonObj(c, configJson, nil)
@ -132,7 +133,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) {
func (a *ServerController) getDb(c *gin.Context) { func (a *ServerController) getDb(c *gin.Context) {
db, err := a.serverService.GetDb() db, err := a.serverService.GetDb()
if err != nil { if err != nil {
jsonMsg(c, I18n(c, "getLogs"), err) jsonMsg(c, "get Database", err)
return return
} }
// Set the headers for the response // Set the headers for the response
@ -142,3 +143,12 @@ func (a *ServerController) getDb(c *gin.Context) {
// Write the file contents to the response // Write the file contents to the response
c.Writer.Write(db) c.Writer.Write(db)
} }
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
cert, err := a.serverService.GetNewX25519Cert()
if err != nil {
jsonMsg(c, "get x25519 certificate", err)
return
}
jsonObj(c, cert, nil)
}

View file

@ -17,6 +17,10 @@ type updateUserForm struct {
NewPassword string `json:"newPassword" form:"newPassword"` NewPassword string `json:"newPassword" form:"newPassword"`
} }
type updateSecretForm struct {
LoginSecret string `json:"loginSecret" form:"loginSecret"`
}
type SettingController struct { type SettingController struct {
settingService service.SettingService settingService service.SettingService
userService service.UserService userService service.UserService
@ -37,6 +41,9 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
g.POST("/update", a.updateSetting) g.POST("/update", a.updateSetting)
g.POST("/updateUser", a.updateUser) g.POST("/updateUser", a.updateUser)
g.POST("/restartPanel", a.restartPanel) g.POST("/restartPanel", a.restartPanel)
g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
g.POST("/updateUserSecret", a.updateSecret)
g.POST("/getUserSecret", a.getUserSecret)
} }
func (a *SettingController) getAllSetting(c *gin.Context) { func (a *SettingController) getAllSetting(c *gin.Context) {
@ -48,6 +55,15 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
jsonObj(c, allSetting, nil) jsonObj(c, allSetting, nil)
} }
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
if err != nil {
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
return
}
jsonObj(c, defaultJsonConfig, nil)
}
func (a *SettingController) getDefaultSettings(c *gin.Context) { func (a *SettingController) getDefaultSettings(c *gin.Context) {
expireDiff, err := a.settingService.GetExpireDiff() expireDiff, err := a.settingService.GetExpireDiff()
if err != nil { if err != nil {
@ -118,3 +134,25 @@ func (a *SettingController) restartPanel(c *gin.Context) {
err := a.panelService.RestartPanel(time.Second * 3) err := a.panelService.RestartPanel(time.Second * 3)
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err) jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
} }
func (a *SettingController) updateSecret(c *gin.Context) {
form := &updateSecretForm{}
err := c.ShouldBind(form)
if err != nil {
jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
}
user := session.GetLoginUser(c)
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
if err == nil {
user.LoginSecret = form.LoginSecret
session.SetLoginUser(c, user)
}
jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), err)
}
func (a *SettingController) getUserSecret(c *gin.Context) {
loginUser := session.GetLoginUser(c)
user := a.userService.GetUserSecret(loginUser.Id)
if user != nil {
jsonObj(c, user, nil)
}
}

View file

@ -29,14 +29,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
func (a *SUBController) subs(c *gin.Context) { func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := strings.Split(c.Request.Host, ":")[0]
subs, err := a.subService.GetSubs(subId, host) subs, header, err := a.subService.GetSubs(subId, host)
if err != nil { if err != nil || len(subs) == 0 {
c.String(400, "Error!") c.String(400, "Error!")
} else { } else {
result := "" result := ""
for _, sub := range subs { for _, sub := range subs {
result += sub + "\n" result += sub + "\n"
} }
// Add subscription-userinfo
c.Writer.Header().Set("Subscription-Userinfo", header)
c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} }
} }

View file

@ -42,6 +42,7 @@ type AllSetting struct {
TgCpu int `json:"tgCpu" form:"tgCpu"` TgCpu int `json:"tgCpu" form:"tgCpu"`
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"` XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
TimeLocation string `json:"timeLocation" form:"timeLocation"` TimeLocation string `json:"timeLocation" form:"timeLocation"`
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
} }
func (s *AllSetting) CheckValid() error { func (s *AllSetting) CheckValid() error {

View file

@ -57,6 +57,11 @@
<a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/> <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="secretEnable">
<a-input type="text" placeholder='{{ i18n "secretToken" }}' v-model.trim="user.loginSecret" @keydown.enter.native="login">
<a-icon slot="prefix" type="key" style="color: rgba(0,0,0,.25)"/>
</a-input>
</a-form-item>
<a-form-item> <a-form-item>
<a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button> <a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
</a-form-item> </a-form-item>
@ -98,10 +103,12 @@
data: { data: {
loading: false, loading: false,
user: new User(), user: new User(),
secretEnable: false,
lang : "" lang : ""
}, },
created(){ created(){
this.lang = getLang(); this.lang = getLang();
this.secretEnable = this.getSecretStatus();
}, },
methods: { methods: {
async login() { async login() {
@ -111,6 +118,15 @@
if (msg.success) { if (msg.success) {
location.href = basePath + 'xui/'; location.href = basePath + 'xui/';
} }
},
async getSecretStatus() {
this.loading= true;
const msg = await HttpUtil.post('/getSecretStatus');
this.loading = false;
if (msg.success){
this.secretEnable = msg.obj;
return msg.obj;
}
} }
} }
}); });

View file

@ -33,6 +33,30 @@
<span slot="label">{{ i18n "pages.client.clientCount" }}</span> <span slot="label">{{ i18n "pages.client.clientCount" }}</span>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number> <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item>
<span slot="label">
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</span>
<a-input type="number" v-model.number="clientsBulkModal.limitIp" min="0" style="width: 70px;" ></a-input>
</a-form-item>
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
<a-select v-model="clientsBulkModal.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
<a-select v-model="clientsBulkModal.flow" style="width: 150px">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Subscription"> <a-form-item label="Subscription">
<a-input v-model.trim="clientsBulkModal.subId"></a-input> <a-input v-model.trim="clientsBulkModal.subId"></a-input>
</a-form-item> </a-form-item>
@ -51,10 +75,10 @@
</span> </span>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> <a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="{{ i18n "pages.client.delayedStart" }}"> <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch> <a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart"> <a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input> <a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-else> <a-form-item v-else>
@ -83,9 +107,9 @@
confirm: null, confirm: null,
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
inbound: new Inbound(), inbound: new Inbound(),
clients: [],
quantity: 1, quantity: 1,
totalGB: 0, totalGB: 0,
limitIp: 0,
expiryTime: '', expiryTime: '',
emailMethod: 0, emailMethod: 0,
firstNum: 1, firstNum: 1,
@ -94,8 +118,10 @@
emailPostfix: "", emailPostfix: "",
subId: "", subId: "",
tgId: "", tgId: "",
flow: "",
delayedStart: false, delayedStart: false,
ok() { ok() {
clients = [];
method=clientsBulkModal.emailMethod; method=clientsBulkModal.emailMethod;
if(method>1){ if(method>1){
start=clientsBulkModal.firstNum; start=clientsBulkModal.firstNum;
@ -113,11 +139,18 @@
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
newClient.subId = clientsBulkModal.subId; newClient.subId = clientsBulkModal.subId;
newClient.tgId = clientsBulkModal.tgId; newClient.tgId = clientsBulkModal.tgId;
newClient.limitIp = clientsBulkModal.limitIp;
newClient._totalGB = clientsBulkModal.totalGB; newClient._totalGB = clientsBulkModal.totalGB;
newClient._expiryTime = clientsBulkModal.expiryTime; newClient._expiryTime = clientsBulkModal.expiryTime;
clientsBulkModal.clients.push(newClient); if(clientsBulkModal.inbound.canEnableTlsFlow()){
newClient.flow = clientsBulkModal.flow;
} }
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound); if(clientsBulkModal.inbound.xtls){
newClient.flow = clientsBulkModal.flow;
}
clients.push(newClient);
}
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
}, },
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) { show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
this.visible = true; this.visible = true;
@ -128,15 +161,16 @@
this.totalGB = 0; this.totalGB = 0;
this.expiryTime = 0; this.expiryTime = 0;
this.emailMethod= 0; this.emailMethod= 0;
this.limitIp= 0;
this.firstNum= 1; this.firstNum= 1;
this.lastNum= 1; this.lastNum= 1;
this.emailPrefix= ""; this.emailPrefix= "";
this.emailPostfix= ""; this.emailPostfix= "";
this.subId= ""; this.subId= "";
this.tgId= ""; this.tgId= "";
this.flow= "";
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
this.delayedStart = false; this.delayedStart = false;
}, },
getClients(protocol, clientSettings) { getClients(protocol, clientSettings) {

View file

@ -12,6 +12,7 @@
confirmLoading: false, confirmLoading: false,
title: '', title: '',
okText: '', okText: '',
isEdit: false,
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
inbound: new Inbound(), inbound: new Inbound(),
clients: [], clients: [],
@ -21,9 +22,13 @@
isExpired: false, isExpired: false,
delayedStart: false, delayedStart: false,
ok() { ok() {
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index); if(clientModal.isEdit){
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
} else {
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
}
}, },
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) { show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
this.visible = true; this.visible = true;
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
@ -139,6 +144,24 @@
} }
document.getElementById("clientIPs").value = "" document.getElementById("clientIPs").value = ""
}, },
resetClientTraffic(email,dbInboundId,iconElement) {
this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
class: siderDrawer.isDarkTheme ? darkClass : '',
okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {
iconElement.disabled = true;
const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email);
if (msg.success) {
this.clientModal.clientStats.up = 0;
this.clientModal.clientStats.down = 0;
}
iconElement.disabled = false;
},
})
},
}, },
}); });
</script> </script>

View file

@ -68,7 +68,7 @@
</a-textarea> </a-textarea>
</a-form> </a-form>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.XTLS" label="Flow"> <a-form-item v-if="inbound.xtls" label="Flow">
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="">{{ i18n "none" }}</a-select-option> <a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
@ -98,12 +98,16 @@
[[ sizeFormat(clientStats.down) ]] [[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]]) ([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag> </a-tag>
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
</a-tooltip>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label="{{ i18n "pages.client.delayedStart" }}"> <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch> <a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart"> <a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientModal.delayedStart">
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input> <a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-else> <a-form-item v-else>

View file

@ -1,7 +1,7 @@
{{define "form/trojan"}} {{define "form/trojan"}}
<a-form layout="inline"> <a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}"> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
@ -31,7 +31,7 @@
</span> </span>
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input> <a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.XTLS" label="Flow"> <a-form-item v-if="inbound.xtls" label="Flow">
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="">{{ i18n "none" }}</a-select-option> <a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>

View file

@ -1,7 +1,7 @@
{{define "form/vless"}} {{define "form/vless"}}
<a-form layout="inline"> <a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}"> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
@ -31,7 +31,7 @@
</span> </span>
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input> <a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.XTLS" label="Flow"> <a-form-item v-if="inbound.xtls" label="Flow">
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>

View file

@ -1,7 +1,7 @@
{{define "form/vmess"}} {{define "form/vmess"}}
<a-form layout="inline"> <a-form layout="inline">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit"> <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}"> <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">

View file

@ -17,7 +17,7 @@
</span> </span>
<a-switch v-model="inbound.reality"></a-switch> <a-switch v-model="inbound.reality"></a-switch>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.canEnableXTLS()"> <a-form-item v-if="inbound.canEnableXtls()">
<span slot="label"> <span slot="label">
XTLS XTLS
<a-tooltip> <a-tooltip>
@ -27,14 +27,14 @@
<a-icon type="question-circle" theme="filled"></a-icon> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</span> </span>
<a-switch v-model="inbound.XTLS"></a-switch> <a-switch v-model="inbound.xtls"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- tls settings --> <!-- tls settings -->
<a-form v-if="inbound.tls || inbound.XTLS" layout="inline"> <a-form v-if="inbound.tls" layout="inline">
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls"> <a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input> <a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="CipherSuites"> <a-form-item label="CipherSuites">
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px"> <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
@ -52,22 +52,22 @@
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="uTLS" v-if="inbound.tls" > <a-form-item label="SNI" placeholder="Server Name Indication">
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px"> <a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 170px">
<a-select-option value=''>None</a-select-option> <a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
</a-form-item>
<a-form-item label="Alpn"> <a-form-item label="Alpn">
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px"> <a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox> <a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
</a-checkbox-group> </a-checkbox-group>
</a-form-item> </a-form-item>
<a-form-item label="Allow insecure"> <a-form-item label="Allow insecure">
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch> <a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "certificate" }}'> <a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid"> <a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
@ -93,33 +93,79 @@
</a-form-item> </a-form-item>
</template> </template>
</a-form> </a-form>
<!-- xtls settings -->
<a-form v-if="inbound.xtls" layout="inline">
<a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
</a-form-item>
<a-form-item label="Alpn">
<a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px">
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="Allow insecure">
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="inbound.stream.xtls.certs[0].useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
</a-form-item>
<template v-if="inbound.stream.xtls.certs[0].useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
<a-input v-model.trim="inbound.stream.xtls.certs[0].certFile" style="width:300px;"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="inbound.stream.xtls.certs[0].keyFile" style="width:300px;"></a-input>
</a-form-item>
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].key"></a-input>
</a-form-item>
</template>
</a-form>
<!-- reality settings -->
<a-form v-else-if="inbound.reality" layout="inline"> <a-form v-else-if="inbound.reality" layout="inline">
<a-form-item label="show"> <a-form-item label="Show">
<a-switch v-model="inbound.stream.reality.show"> <a-switch v-model="inbound.stream.reality.show">
</a-switch> </a-switch>
</a-form-item> </a-form-item>
<a-form-item label="xver"> <a-form-item label="xVer">
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input> <a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS" > <a-form-item label="uTLS" >
<a-select v-model="inbound.stream.reality.fingerprint" style="width: 135px"> <a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 135px">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item>
<a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="dest"> <a-form-item label="dest">
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 360px"></a-input> <a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="serverNames"> <a-form-item label="Server Names">
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 360px"></a-input> <a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="privateKey"> <a-form-item label="ShortIds">
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 360px"></a-input>
</a-form-item>
<a-form-item label="publicKey">
<a-input v-model.trim="inbound.stream.reality.publicKey" style="width: 360px"></a-input>
</a-form-item>
<a-form-item label="shortIds">
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input> <a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Private Key">
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
</a-form-item>
<a-form-item label="Public Key">
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
</a-form-item>
<a-form-item >
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -49,10 +49,14 @@
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
<td v-else-if="inbound.XTLS"> <td v-else-if="inbound.xtls">
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
<td v-else-if="inbound.reality">
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td>
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag> <td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
</td> </td>
</tr> </tr>

View file

@ -43,6 +43,14 @@
loading(loading) { loading(loading) {
inModal.confirmLoading = loading; inModal.confirmLoading = loading;
}, },
getClients(protocol, clientSettings) {
switch(protocol){
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
default: return null;
}
},
}; };
const protocols = { const protocols = {
@ -62,6 +70,7 @@
inModal: inModal, inModal: inModal,
Protocols: protocols, Protocols: protocols,
SSMethods: SSMethods, SSMethods: SSMethods,
delayedStart: false,
get inbound() { get inbound() {
return inModal.inbound; return inModal.inbound;
}, },
@ -70,36 +79,40 @@
}, },
get isEdit() { get isEdit() {
return inModal.isEdit; return inModal.isEdit;
} },
get client() {
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
},
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
},
set delayedExpireDays(days){
this.client.expiryTime = -86400000 * days;
},
}, },
methods: { methods: {
streamNetworkChange(oldValue) { streamNetworkChange() {
if (oldValue === 'kcp') { if (!inModal.inbound.canSetTls()) {
this.inModal.inbound.tls = false; this.inModal.inbound.stream.security = 'none';
} }
}, if (!inModal.inbound.canEnableReality()) {
addClient(protocol, clients) { this.inModal.inbound.reality = false;
switch (protocol) {
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
default: return null;
} }
}, },
removeClient(index, clients) {
clients.splice(index, 1);
},
isExpiry(index) {
return this.inbound.isExpiry(index)
},
isClientEnable(email) {
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
return clientStats ? clientStats['enable'] : true
},
setDefaultCertData(){ setDefaultCertData(){
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert; inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey; inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
}, },
async getNewX25519Cert(){
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewX25519Cert');
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
},
getNewEmail(client) { getNewEmail(client) {
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
var string = ''; var string = '';

View file

@ -133,26 +133,26 @@
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag> <a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXTLS" color="cyan">XTLS</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag>
</template> </template>
</template> </template>
<template slot="clients" slot-scope="text, dbInbound"> <template slot="clients" slot-scope="text, dbInbound">
<template v-if="clientCount[dbInbound.id]"> <template v-if="clientCount[dbInbound.id]">
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag> <a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
<a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content"> <template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p> <p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
</template> </template>
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag> <a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
</a-popover> </a-popover>
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content"> <template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p> <p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
</template> </template>
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag> <a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
</a-popover> </a-popover>
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''"> <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
<template slot="content"> <template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p> <p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
</template> </template>
@ -531,9 +531,9 @@
title: '{{ i18n "pages.client.add"}}', title: '{{ i18n "pages.client.add"}}',
okText: '{{ i18n "pages.client.submitAdd"}}', okText: '{{ i18n "pages.client.submitAdd"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (inbound, dbInbound, index) => { confirm: async (clients, dbInboundId) => {
clientModal.loading(); clientModal.loading();
await this.addClient(inbound, dbInbound); await this.addClient(clients, dbInboundId);
clientModal.close(); clientModal.close();
}, },
isEdit: false isEdit: false
@ -545,9 +545,9 @@
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark, title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
okText: '{{ i18n "pages.client.bulk"}}', okText: '{{ i18n "pages.client.bulk"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (inbound, dbInbound) => { confirm: async (clients, dbInboundId) => {
clientsBulkModal.loading(); clientsBulkModal.loading();
await this.addClient(inbound, dbInbound); await this.addClient(clients, dbInboundId);
clientsBulkModal.close(); clientsBulkModal.close();
}, },
}); });
@ -561,9 +561,9 @@
okText: '{{ i18n "pages.client.submitEdit"}}', okText: '{{ i18n "pages.client.submitEdit"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
index: index, index: index,
confirm: async (inbound, dbInbound, index) => { confirm: async (client, dbInboundId, index) => {
clientModal.loading(); clientModal.loading();
await this.updateClient(inbound, dbInbound, index); await this.updateClient(client, dbInboundId, index);
clientModal.close(); clientModal.close();
}, },
isEdit: true isEdit: true
@ -573,17 +573,17 @@
firstKey = Object.keys(client)[0]; firstKey = Object.keys(client)[0];
return clients.findIndex(c => c[firstKey] === client[firstKey]); return clients.findIndex(c => c[firstKey] === client[firstKey]);
}, },
async addClient(inbound, dbInbound) { async addClient(clients, dbInboundId) {
const data = { const data = {
id: dbInbound.id, id: dbInboundId,
settings: inbound.settings.toString(), settings: '{"clients": [' + clients.toString() +']}',
}; };
await this.submit('/xui/inbound/addClient/', data); await this.submit(`/xui/inbound/addClient`, data);
}, },
async updateClient(inbound, dbInbound, index) { async updateClient(client, dbInboundId, index) {
const data = { const data = {
id: dbInbound.id, id: dbInboundId,
settings: inbound.settings.toString(), settings: '{"clients": [' + client.toString() +']}',
}; };
await this.submit(`/xui/inbound/updateClient/${index}`, data); await this.submit(`/xui/inbound/updateClient/${index}`, data);
}, },
@ -658,8 +658,8 @@
inbound = dbInbound.toInbound(); inbound = dbInbound.toInbound();
clients = this.getClients(dbInbound.protocol, inbound.settings); clients = this.getClients(dbInbound.protocol, inbound.settings);
index = this.findIndexOfClient(clients, client); index = this.findIndexOfClient(clients, client);
clients[index].enable = ! clients[index].enable clients[index].enable = !clients[index].enable;
await this.updateClient(inbound, dbInbound, index); await this.updateClient(clients[index],dbInboundId, index);
this.loading(false); this.loading(false);
}, },
async submit(url, data) { async submit(url, data) {

View file

@ -24,8 +24,9 @@
background: white; background: white;
} }
</style> </style>
<body> <body>
<a-layout id="app" v-cloak> <a-layout id="app" v-cloak>
{{ template "commonSider" . }} {{ template "commonSider" . }}
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''"> <a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
<a-layout-content> <a-layout-content>
@ -35,9 +36,9 @@
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button> <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button> <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
</a-space> </a-space>
<a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''"> <a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
<a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'> <a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'"> <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
<setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
@ -49,8 +50,9 @@
<a-list-item> <a-list-item>
<a-row style="padding: 20px"> <a-row style="padding: 20px">
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<a-list-item-meta title="Language"/> <a-list-item-meta title="Language" />
</a-col> </a-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select <a-select
@ -61,8 +63,7 @@
style="width: 100%" style="width: 100%"
> >
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
<span role="img" aria-label="l.name" v-text="l.icon"></span> <span role="img" aria-label="l.name" v-text="l.icon"></span>&nbsp;&nbsp;<span v-text="l.name"></span>
&nbsp;&nbsp;<span v-text="l.name"></span>
</a-select-option> </a-select-option>
</a-select> </a-select>
</template> </template>
@ -71,50 +72,135 @@
</a-list-item> </a-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'> <a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'"> <a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
<a-form-item label='{{ i18n "pages.setting.oldUsername"}}'> <a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input> <a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.setting.currentPassword"}}'> <a-form-item label='{{ i18n "pages.setting.currentPassword"}}'>
<a-input type="password" v-model="user.oldPassword" <a-input type="password" v-model="user.oldPassword" style="max-width: 300px"></a-input>
style="max-width: 300px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.setting.newUsername"}}'> <a-form-item label='{{ i18n "pages.setting.newUsername"}}'>
<a-input v-model="user.newUsername" style="max-width: 300px"></a-input> <a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.setting.newPassword"}}'> <a-form-item label='{{ i18n "pages.setting.newPassword"}}'>
<a-input type="password" v-model="user.newPassword" <a-input type="password" v-model="user.newPassword" style="max-width: 300px"></a-input>
style="max-width: 300px"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<!-- <a-button type="primary" @click="updateUser">Revise</a-button>-->
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button> <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
<a-list-item style="padding: 20px">
<a-row>
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.setting.loginSecurity" }}' description='{{ i18n "pages.setting.loginSecurityDesc" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
</template>
</a-col>
</a-row>
</a-list-item>
<a-list-item style="padding: 20px">
<a-row>
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.setting.secretToken" }}' description='{{ i18n "pages.setting.secretTokenDesc" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<svg
@click="getNewSecret"
xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/>
</svg>
<template>
<a-textarea type="text" id='token' :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
</template>
</a-col>
</a-row>
</a-list-item>
<a-button type="primary" @click="updateSecret">{{ i18n "confirm" }}</a-button>
</a-form>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'> <a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'"> <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
<a-divider>{{ i18n "pages.setting.actions"}}</a-divider>
<a-space direction="horizontal" style="padding: 0 20px">
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
</a-space>
<a-divider>{{ i18n "pages.setting.basicTemplate"}}</a-divider>
<a-collapse>
<a-collapse-panel header='{{ i18n "pages.setting.generalConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
{{ i18n "pages.setting.generalConfigsDesc" }}
</h2>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigAds"}}' desc='{{ i18n "pages.setting.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPorn"}}' desc='{{ i18n "pages.setting.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.setting.countryConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
{{ i18n "pages.setting.countryConfigsDesc" }}
</h2>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRIp"}}' desc='{{ i18n "pages.setting.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRIp"}}' desc='{{ i18n "pages.setting.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRdomain"}}' desc='{{ i18n "pages.setting.xrayConfigIRdomainDesc"}}' v-model="IRdomainSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRDomain"}}' desc='{{ i18n "pages.setting.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigChinaIp"}}' desc='{{ i18n "pages.setting.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
<a-collapse> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigInbounds"}}"> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model ="inboundSettings"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
</a-collapse-panel> </a-collapse-panel>
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigOutbounds"}}"> <a-collapse-panel header='{{ i18n "pages.setting.ipv4Configs"}}'>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model ="outboundSettings"></setting-list-item> <a-row :xs="24" :sm="24" :lg="12">
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
{{ i18n "pages.setting.ipv4ConfigsDesc" }}
</h2>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
</a-collapse-panel> </a-collapse-panel>
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigRoutings"}}"> <a-collapse-panel header='{{ i18n "pages.setting.warpConfigs"}}'>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model ="routingRuleSettings"></setting-list-item> <a-row :xs="24" :sm="24" :lg="12">
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
{{ i18n "pages.setting.warpConfigsDesc" }}
</h2>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.setting.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.setting.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
<a-collapse>
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigInbounds"}}'>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigOutbounds"}}'>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigRoutings"}}'>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
</a-collapse-panel>
</a-collapse>
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider> <a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item> <setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'> <a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'"> <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
<setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
@ -125,6 +211,7 @@
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'> <a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'"> <a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
<setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
@ -135,10 +222,10 @@
</a-spin> </a-spin>
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
{{template "component/setting"}} {{template "component/setting"}}
<script> <script>
const app = new Vue({ const app = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
@ -149,35 +236,90 @@
oldAllSetting: new AllSetting(), oldAllSetting: new AllSetting(),
allSetting: new AllSetting(), allSetting: new AllSetting(),
saveBtnDisable: true, saveBtnDisable: true,
user: {}, user: new User(),
lang : getLang() lang: getLang(),
ipv4Settings: {
tag: "IPv4",
protocol: "freedom",
settings: {
domainStrategy: "UseIPv4"
}
},
warpSettings: {
tag: "WARP",
protocol: "socks",
settings: {
servers: [
{
address: "127.0.0.1",
port: 40000
}
]
}
},
settingsData: {
protocols: {
bittorrent: ["bittorrent"],
},
ips: {
local: ["geoip:private"],
google: ["geoip:google"],
cn: ["geoip:cn"],
ir: ["geoip:ir"],
ru: ["geoip:ru"],
},
domains: {
ads: [
"geosite:category-ads-all",
"geosite:category-ads",
"geosite:google-ads",
"geosite:spotify-ads"
],
porn: ["geosite:category-porn"],
openai: ["geosite:openai"],
google: ["geosite:google"],
spotify: ["geosite:spotify"],
netflix: ["geosite:netflix"],
cn: ["geosite:cn"],
ru: ["geosite:category-ru-gov"],
ir: [
"regexp:.*\\.ir$",
"ext:iran.dat:ir",
"ext:iran.dat:other",
"ext:iran.dat:ads",
"geosite:category-ir"
]
},
}
}, },
methods: { methods: {
loading(spinning = true) { loading(spinning = true , obj) {
if(obj == null)
this.spinning = spinning; this.spinning = spinning;
}, },
async getAllSetting() { async getAllSetting() {
this.loading(true); this.loading(true,{});
const msg = await HttpUtil.post("/xui/setting/all"); const msg = await HttpUtil.post("/xui/setting/all");
this.loading(false); this.loading(false,null);
if (msg.success) { if (msg.success) {
this.oldAllSetting = new AllSetting(msg.obj); this.oldAllSetting = new AllSetting(msg.obj);
this.allSetting = new AllSetting(msg.obj); this.allSetting = new AllSetting(msg.obj);
this.saveBtnDisable = true; this.saveBtnDisable = true;
} }
await this.getUserSecret();
}, },
async updateAllSetting() { async updateAllSetting() {
this.loading(true); this.loading(true,{});
const msg = await HttpUtil.post("/xui/setting/update", this.allSetting); const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
this.loading(false); this.loading(false,null);
if (msg.success) { if (msg.success) {
await this.getAllSetting(); await this.getAllSetting();
} }
}, },
async updateUser() { async updateUser() {
this.loading(true); this.loading(true,{});
const msg = await HttpUtil.post("/xui/setting/updateUser", this.user); const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
this.loading(false); this.loading(false,null);
if (msg.success) { if (msg.success) {
this.user = {}; this.user = {};
} }
@ -192,14 +334,125 @@
onOk: () => resolve(), onOk: () => resolve(),
}); });
}); });
this.loading(true); this.loading(true,{});
const msg = await HttpUtil.post("/xui/setting/restartPanel"); const msg = await HttpUtil.post("/xui/setting/restartPanel");
this.loading(false); this.loading(false,null);
if (msg.success) { if (msg.success) {
this.loading(true); this.loading(true,{});
await PromiseUtil.sleep(5000); await PromiseUtil.sleep(5000);
location.reload(); location.reload();
} }
},
async getUserSecret(){
const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
if (user_msg.success){
this.user = user_msg.obj;
}
this.loading(false);
},
async updateSecret(){
this.loading(true,{});
const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
if (msg.success){
this.user = msg.obj;
}
this.loading(false,null);
await this.updateAllSetting();
},
async getNewSecret(){
this.loading(true,{});
await PromiseUtil.sleep(1000);
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
var string = '';
var len = 64;
for(var ii=0; ii<len; ii++){
string += chars[Math.floor(Math.random() * chars.length)];
}
this.user.loginSecret = string;
document.getElementById('token').value =this.user.loginSecret;
this.loading(false,null);
},
async toggleToken(value){
if(value)
this.getNewSecret();
else
this.user.loginSecret = "";
},
async resetXrayConfigToDefault() {
this.loading(true,{});
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
this.loading(false,null);
if (msg.success) {
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
this.saveBtnDisable = true;
}
},
checkRequiredOutbounds() {
const newTemplateSettings = this.templateSettings;
const haveIPv4Outbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "IPv4");
const haveIPv4Rules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === "IPv4");
const haveWARPOutbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "WARP");
const haveWARPRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === "WARP");
if (haveWARPRules && !haveWARPOutbounds) {
newTemplateSettings.outbounds.push(this.warpSettings);
}
if (haveIPv4Rules && !haveIPv4Outbounds) {
newTemplateSettings.outbounds.push(this.ipv4Settings);
}
this.templateSettings = newTemplateSettings;
},
templateRuleGetter(routeSettings) {
const { data, property, outboundTag } = routeSettings;
let result = false;
if (this.templateSettings != null) {
this.templateSettings.routing.rules.forEach(
(routingRule) => {
if (
routingRule.hasOwnProperty(property) &&
routingRule.hasOwnProperty("outboundTag") &&
routingRule.outboundTag === outboundTag
) {
if (data.includes(routingRule[property][0])) {
result = true;
}
}
}
);
}
return result;
},
templateRuleSetter(routeSettings) {
const { newValue, data, property, outboundTag } = routeSettings;
const oldTemplateSettings = this.templateSettings;
const newTemplateSettings = oldTemplateSettings;
if (newValue) {
const propertyRule = {
type: "field",
outboundTag,
[property]: data
};
newTemplateSettings.routing.rules.push(propertyRule);
}
else {
const newRules = [];
newTemplateSettings.routing.rules.forEach(
(routingRule) => {
if (
routingRule.hasOwnProperty(property) &&
routingRule.hasOwnProperty("outboundTag") &&
routingRule.outboundTag === outboundTag
) {
if (data.includes(routingRule[property][0])) {
return;
}
}
newRules.push(routingRule);
}
);
newTemplateSettings.routing.rules = newRules;
}
this.templateSettings = newTemplateSettings;
this.checkRequiredOutbounds();
} }
}, },
async mounted() { async mounted() {
@ -211,7 +464,7 @@
}, },
computed: { computed: {
templateSettings: { templateSettings: {
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null ; }, get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) }, set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) },
}, },
inboundSettings: { inboundSettings: {
@ -240,140 +493,280 @@
}, },
torrentSettings: { torrentSettings: {
get: function () { get: function () {
torrentFilter = false return this.templateRuleGetter({
if(this.templateSettings != null){ outboundTag: "blocked",
this.templateSettings.routing.rules.forEach(routingRule => { property: "protocol",
if(routingRule.hasOwnProperty("protocol")){ data: this.settingsData.protocols.bittorrent
if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){
torrentFilter = true
}
}
}); });
}
return torrentFilter
}, },
set: function (newValue) { set: function (newValue) {
newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig); this.templateRuleSetter({
if (newValue){ newValue,
newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"protocol\": [\"bittorrent\"],\"type\": \"field\"}")) outboundTag: "blocked",
} property: "protocol",
else { data: this.settingsData.protocols.bittorrent
newTemplateSettings.routing.rules = [];
this.templateSettings.routing.rules.forEach(routingRule => {
if (routingRule.hasOwnProperty('protocol')){
if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){
return;
}
}
newTemplateSettings.routing.rules.push(routingRule);
}); });
}
this.templateSettings = newTemplateSettings
}, },
}, },
privateIpSettings: { privateIpSettings: {
get: function () { get: function () {
localIpFilter = false return this.templateRuleGetter({
if(this.templateSettings != null){ outboundTag: "blocked",
this.templateSettings.routing.rules.forEach(routingRule => { property: "ip",
if(routingRule.hasOwnProperty("ip")){ data: this.settingsData.ips.local
if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){
localIpFilter = true
}
}
}); });
}
return localIpFilter
}, },
set: function (newValue) { set: function (newValue) {
newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig); this.templateRuleSetter({
if (newValue){ newValue,
newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"ip\": [\"geoip:private\"],\"type\": \"field\"}")) outboundTag: "blocked",
} property: "ip",
else { data: this.settingsData.ips.local
newTemplateSettings.routing.rules = []; });
this.templateSettings.routing.rules.forEach(routingRule => { },
if (routingRule.hasOwnProperty('ip')){ },
if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){ AdsSettings: {
return; get: function () {
} return this.templateRuleGetter({
} outboundTag: "blocked",
newTemplateSettings.routing.rules.push(routingRule); property: "domain",
data: this.settingsData.domains.ads
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.ads
});
},
},
PornSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.porn
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.porn
});
},
},
GoogleIPv4Settings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "IPv4",
property: "domain",
data: this.settingsData.domains.google
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "IPv4",
property: "domain",
data: this.settingsData.domains.google
});
},
},
NetflixIPv4Settings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "IPv4",
property: "domain",
data: this.settingsData.domains.netflix
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "IPv4",
property: "domain",
data: this.settingsData.domains.netflix
}); });
}
this.templateSettings = newTemplateSettings
}, },
}, },
IRIpSettings: { IRIpSettings: {
get: function () { get: function () {
localIpFilter = false return this.templateRuleGetter({
if(this.templateSettings != null){ outboundTag: "blocked",
this.templateSettings.routing.rules.forEach(routingRule => { property: "ip",
if(routingRule.hasOwnProperty("ip")){ data: this.settingsData.ips.ir
if (routingRule.ip[0] === "geoip:ir" && routingRule.outboundTag == "blocked"){
localIpFilter = true
}
}
}); });
}
return localIpFilter
}, },
set: function (newValue) { set: function (newValue) {
newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig); this.templateRuleSetter({
if (newValue){ newValue,
newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"ip\": [\"geoip:ir\"],\"type\": \"field\"}")) outboundTag: "blocked",
} property: "ip",
else { data: this.settingsData.ips.ir
newTemplateSettings.routing.rules = [];
this.templateSettings.routing.rules.forEach(routingRule => {
if (routingRule.hasOwnProperty('ip')){
if (routingRule.ip[0] === "geoip:ir" && routingRule.outboundTag == "blocked"){
return;
}
}
newTemplateSettings.routing.rules.push(routingRule);
}); });
}
this.templateSettings = newTemplateSettings
}, },
}, },
IRdomainSettings: { IRDomainSettings: {
get: function () { get: function () {
localdomainFilter = false return this.templateRuleGetter({
if(this.templateSettings != null){ outboundTag: "blocked",
this.templateSettings.routing.rules.forEach(routingRule => { property: "domain",
if(routingRule.hasOwnProperty("domain")){ data: this.settingsData.domains.ir
if ((routingRule.domain[0] === "regexp:.+.ir$" || routingRule.domain[0] === "ext:iran.dat:ir" || routingRule.domain[0] === "ext:iran.dat:other") && routingRule.outboundTag == "blocked") {
localdomainFilter = true
}
}
}); });
}
return localdomainFilter
}, },
set: function (newValue) { set: function (newValue) {
newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig); this.templateRuleSetter({
if (newValue){ newValue,
newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"domain\": [\"regexp:.+.ir$\", \"ext:iran.dat:ir\", \"ext:iran.dat:other\"],\"type\": \"field\"}")) outboundTag: "blocked",
} property: "domain",
else { data: this.settingsData.domains.ir
newTemplateSettings.routing.rules = []; });
this.templateSettings.routing.rules.forEach(routingRule => { },
if (routingRule.hasOwnProperty('domain')){ },
if ((routingRule.domain[0] === "regexp:.+.ir$" || routingRule.domain[0] === "ext:iran.dat:ir" || routingRule.domain[0] === "ext:iran.dat:other") && routingRule.outboundTag == "blocked"){ ChinaIpSettings: {
return; get: function () {
} return this.templateRuleGetter({
} outboundTag: "blocked",
newTemplateSettings.routing.rules.push(routingRule); property: "ip",
data: this.settingsData.ips.cn
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "blocked",
property: "ip",
data: this.settingsData.ips.cn
});
},
},
ChinaDomainSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.cn
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.cn
});
},
},
RussiaIpSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "blocked",
property: "ip",
data: this.settingsData.ips.ru
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "blocked",
property: "ip",
data: this.settingsData.ips.ru
});
},
},
RussiaDomainSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.ru
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "blocked",
property: "domain",
data: this.settingsData.domains.ru
});
},
},
GoogleWARPSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.google
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.google
});
},
},
OpenAIWARPSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.openai
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.openai
});
},
},
NetflixWARPSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.netflix
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.netflix
});
},
},
SpotifyWARPSettings: {
get: function () {
return this.templateRuleGetter({
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.spotify
});
},
set: function (newValue) {
this.templateRuleSetter({
newValue,
outboundTag: "WARP",
property: "domain",
data: this.settingsData.domains.spotify
}); });
}
this.templateSettings = newTemplateSettings
}, },
}, },
} }
}); });
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,25 +1,22 @@
{ {
"log": { "log": {
"loglevel": "warning", "loglevel": "warning",
"access": "./access.log" "access": "./access.log",
"error": "./error.log"
}, },
"api": { "api": {
"services": [ "tag": "api",
"HandlerService", "services": ["HandlerService", "LoggerService", "StatsService"]
"LoggerService",
"StatsService"
],
"tag": "api"
}, },
"inbounds": [ "inbounds": [
{ {
"tag": "api",
"listen": "127.0.0.1", "listen": "127.0.0.1",
"port": 62789, "port": 62789,
"protocol": "dokodemo-door", "protocol": "dokodemo-door",
"settings": { "settings": {
"address": "127.0.0.1" "address": "127.0.0.1"
}, }
"tag": "api"
} }
], ],
"outbounds": [ "outbounds": [
@ -28,16 +25,16 @@
"settings": {} "settings": {}
}, },
{ {
"tag": "blocked",
"protocol": "blackhole", "protocol": "blackhole",
"settings": {}, "settings": {}
"tag": "blocked"
} }
], ],
"policy": { "policy": {
"levels": { "levels": {
"0": { "0": {
"statsUserUplink": true, "statsUserDownlink": true,
"statsUserDownlink": true "statsUserUplink": true
} }
}, },
"system": { "system": {
@ -49,25 +46,19 @@
"domainStrategy": "IPIfNonMatch", "domainStrategy": "IPIfNonMatch",
"rules": [ "rules": [
{ {
"inboundTag": [ "type": "field",
"api" "inboundTag": ["api"],
], "outboundTag": "api"
"outboundTag": "api",
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"ip": [ "ip": ["geoip:private"]
"geoip:private"
],
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"protocol": [ "protocol": ["bittorrent"]
"bittorrent"
],
"type": "field"
} }
] ]
}, },

View file

@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
return clients, nil return clients, nil
} }
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) { func (s *InboundService) getAllEmails() ([]string, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var emails []string
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan}) err := db.Raw(`
if ignoreId > 0 { SELECT JSON_EXTRACT(client.value, '$.email')
db = db.Where("id != ?", ignoreId) FROM inbounds,
} JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
db = db.Find(&inbounds) `).Scan(&emails).Error
if db.Error != nil {
return "", db.Error
}
for _, inbound := range inbounds { if err != nil {
clients, err := s.getClients(inbound) return nil, err
}
return emails, nil
}
func (s *InboundService) contains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
allEmails, err := s.getAllEmails()
if err != nil { if err != nil {
return "", err return "", err
} }
var emails []string
for _, client := range clients { for _, client := range clients {
if emails[client.Email] { if client.Email != "" {
if s.contains(emails, client.Email) {
return client.Email, nil return client.Email, nil
} }
if s.contains(allEmails, client.Email) {
return client.Email, nil
}
emails = append(emails, client.Email)
} }
} }
return "", nil return "", nil
@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
if err != nil { if err != nil {
return "", err return "", err
} }
emails := make(map[string]bool) allEmails, err := s.getAllEmails()
if err != nil {
return "", err
}
var emails []string
for _, client := range clients { for _, client := range clients {
if client.Email != "" { if client.Email != "" {
if emails[client.Email] { if s.contains(emails, client.Email) {
return client.Email, nil return client.Email, nil
} }
emails[client.Email] = true if s.contains(allEmails, client.Email) {
return client.Email, nil
}
emails = append(emails, client.Email)
} }
} }
return s.checkEmailsExist(emails, inbound.Id) return "", nil
} }
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) { func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
@ -215,14 +239,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, common.NewError("Port already exists:", inbound.Port) return inbound, common.NewError("Port already exists:", inbound.Port)
} }
existEmail, err := s.checkEmailExistForInbound(inbound)
if err != nil {
return inbound, err
}
if existEmail != "" {
return inbound, common.NewError("Duplicate email:", existEmail)
}
oldInbound, err := s.GetInbound(inbound.Id) oldInbound, err := s.GetInbound(inbound.Id)
if err != nil { if err != nil {
return inbound, err return inbound, err
@ -245,39 +261,53 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, db.Save(oldInbound).Error return inbound, db.Save(oldInbound).Error
} }
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error { func (s *InboundService) AddInboundClient(data *model.Inbound) error {
existEmail, err := s.checkEmailExistForInbound(inbound) clients, err := s.getClients(data)
if err != nil { if err != nil {
return err return err
} }
var settings map[string]interface{}
err = json.Unmarshal([]byte(data.Settings), &settings)
if err != nil {
return err
}
interfaceClients := settings["clients"].([]interface{})
existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil {
return err
}
if existEmail != "" { if existEmail != "" {
return common.NewError("Duplicate email:", existEmail) return common.NewError("Duplicate email:", existEmail)
} }
clients, err := s.getClients(inbound) oldInbound, err := s.GetInbound(data.Id)
if err != nil { if err != nil {
return err return err
} }
oldInbound, err := s.GetInbound(inbound.Id) var oldSettings map[string]interface{}
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
if err != nil { if err != nil {
return err return err
} }
oldClients, err := s.getClients(oldInbound) oldClients := oldSettings["clients"].([]interface{})
oldClients = append(oldClients, interfaceClients...)
oldSettings["clients"] = oldClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
if err != nil { if err != nil {
return err return err
} }
oldInbound.Settings = inbound.Settings oldInbound.Settings = string(newSettings)
if len(clients[len(clients)-1].Email) > 0 { for _, client := range clients {
s.AddClientStat(inbound.Id, &clients[len(clients)-1]) if len(client.Email) > 0 {
} s.AddClientStat(data.Id, &client)
for i := len(oldClients); i < len(clients); i++ {
if len(clients[i].Email) > 0 {
s.AddClientStat(inbound.Id, &clients[i])
} }
} }
db := database.GetDB() db := database.GetDB()
@ -309,21 +339,21 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
return db.Save(oldInbound).Error return db.Save(oldInbound).Error
} }
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error { func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
existEmail, err := s.checkEmailExistForInbound(inbound) clients, err := s.getClients(data)
if err != nil {
return err
}
if existEmail != "" {
return common.NewError("Duplicate email:", existEmail)
}
clients, err := s.getClients(inbound)
if err != nil { if err != nil {
return err return err
} }
oldInbound, err := s.GetInbound(inbound.Id) var settings map[string]interface{}
err = json.Unmarshal([]byte(data.Settings), &settings)
if err != nil {
return err
}
inerfaceClients := settings["clients"].([]interface{})
oldInbound, err := s.GetInbound(data.Id)
if err != nil { if err != nil {
return err return err
} }
@ -333,22 +363,47 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
return err return err
} }
oldInbound.Settings = inbound.Settings if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
existEmail, err := s.checkEmailsExistForClients(clients)
db := database.GetDB()
if len(clients[index].Email) > 0 {
if len(oldClients[index].Email) > 0 {
err = s.UpdateClientStat(oldClients[index].Email, &clients[index])
if err != nil { if err != nil {
return err return err
} }
err = s.UpdateClientIPs(db, oldClients[index].Email, clients[index].Email) if existEmail != "" {
return common.NewError("Duplicate email:", existEmail)
}
}
var oldSettings map[string]interface{}
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
if err != nil {
return err
}
settingsClients := oldSettings["clients"].([]interface{})
settingsClients[index] = inerfaceClients[0]
oldSettings["clients"] = settingsClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
if err != nil {
return err
}
oldInbound.Settings = string(newSettings)
db := database.GetDB()
if len(clients[0].Email) > 0 {
if len(oldClients[index].Email) > 0 {
err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
if err != nil {
return err
}
err = s.UpdateClientIPs(db, oldClients[index].Email, clients[0].Email)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
s.AddClientStat(inbound.Id, &clients[index]) s.AddClientStat(data.Id, &clients[0])
} }
} else { } else {
err = s.DelClientStat(db, oldClients[index].Email) err = s.DelClientStat(db, oldClients[index].Email)
@ -507,6 +562,16 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
count := result.RowsAffected count := result.RowsAffected
return count, err return count, err
} }
func (s *InboundService) DisableInvalidClients() (int64, error) {
db := database.GetDB()
now := time.Now().Unix() * 1000
result := db.Model(xray.ClientTraffic{}).
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
Update("enable", false)
err := result.Error
count := result.RowsAffected
return count, err
}
func (s *InboundService) RemoveOrphanedTraffics() { func (s *InboundService) RemoveOrphanedTraffics() {
db := database.GetDB() db := database.GetDB()
db.Exec(` db.Exec(`
@ -518,16 +583,6 @@ func (s *InboundService) RemoveOrphanedTraffics() {
) )
`) `)
} }
func (s *InboundService) DisableInvalidClients() (int64, error) {
db := database.GetDB()
now := time.Now().Unix() * 1000
result := db.Model(xray.ClientTraffic{}).
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
Update("enable", false)
err := result.Error
count := result.RowsAffected
return count, err
}
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error { func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
db := database.GetDB() db := database.GetDB()
@ -655,7 +710,7 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic []*xray.
db := database.GetDB() db := database.GetDB()
var traffics []*xray.ClientTraffic var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email like ?", "%"+email+"%").Find(&traffics).Error err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
logger.Warning(err) logger.Warning(err)

View file

@ -323,6 +323,10 @@ func (s *ServerService) UpdateXray(version string) error {
if err != nil { if err != nil {
return err return err
} }
err = copyZipFile("iran.dat", xray.GetIranPath())
if err != nil {
return err
}
return nil return nil
@ -390,3 +394,29 @@ func (s *ServerService) GetDb() ([]byte, error) {
return fileContents, nil return fileContents, nil
} }
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
privateKeyLine := strings.Split(lines[0], ":")
publicKeyLine := strings.Split(lines[1], ":")
privateKey := strings.TrimSpace(privateKeyLine[1])
publicKey := strings.TrimSpace(publicKeyLine[1])
keyPair := map[string]interface{}{
"privateKey": privateKey,
"publicKey": publicKey,
}
return keyPair, nil
}

View file

@ -2,6 +2,7 @@ package service
import ( import (
_ "embed" _ "embed"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
@ -37,11 +38,21 @@ var defaultValueMap = map[string]string{
"tgRunTime": "@daily", "tgRunTime": "@daily",
"tgBotBackup": "false", "tgBotBackup": "false",
"tgCpu": "0", "tgCpu": "0",
"secretEnable": "false",
} }
type SettingService struct { type SettingService struct {
} }
func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
var jsonData interface{}
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
if err != nil {
return nil, err
}
return jsonData, nil
}
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) { func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
db := database.GetDB() db := database.GetDB()
settings := make([]*model.Setting, 0) settings := make([]*model.Setting, 0)
@ -119,7 +130,13 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
func (s *SettingService) ResetSettings() error { func (s *SettingService) ResetSettings() error {
db := database.GetDB() db := database.GetDB()
return db.Where("1 = 1").Delete(model.Setting{}).Error err := db.Where("1 = 1").Delete(model.Setting{}).Error
if err != nil {
return err
}
return db.Model(model.User{}).
Where("1 = 1").
Update("login_secret", "").Error
} }
func (s *SettingService) getSetting(key string) (*model.Setting, error) { func (s *SettingService) getSetting(key string) (*model.Setting, error) {
@ -278,6 +295,14 @@ func (s *SettingService) SetgetTrafficDiff(value int) error {
return s.setInt("trafficDiff", value) return s.setInt("trafficDiff", value)
} }
func (s *SettingService) GetSecretStatus() (bool, error) {
return s.getBool("secretEnable")
}
func (s *SettingService) SetSecretStatus(value bool) error {
return s.setBool("secretEnable", value)
}
func (s *SettingService) GetSecret() ([]byte, error) { func (s *SettingService) GetSecret() ([]byte, error) {
secret, err := s.getString("secret") secret, err := s.getString("secret")
if secret == defaultValueMap["secret"] { if secret == defaultValueMap["secret"] {

View file

@ -8,6 +8,7 @@ import (
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
"x-ui/xray"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"gorm.io/gorm" "gorm.io/gorm"
@ -18,12 +19,15 @@ type SubService struct {
inboundService InboundService inboundService InboundService
} }
func (s *SubService) GetSubs(subId string, host string) ([]string, error) { func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
s.address = host s.address = host
var result []string var result []string
var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId) inbounds, err := s.getInboundsBySubId(subId)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.getClients(inbound) clients, err := s.inboundService.getClients(inbound)
@ -37,22 +41,54 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
if client.SubID == subId { if client.SubID == subId {
link := s.getLink(inbound, client.Email) link := s.getLink(inbound, client.Email)
result = append(result, link) result = append(result, link)
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
} }
} }
} }
return result, nil for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
traffic.Down = clientTraffic.Down
traffic.Total = clientTraffic.Total
if clientTraffic.ExpiryTime > 0 {
traffic.ExpiryTime = clientTraffic.ExpiryTime
}
} else {
traffic.Up += clientTraffic.Up
traffic.Down += clientTraffic.Down
if traffic.Total == 0 || clientTraffic.Total == 0 {
traffic.Total = 0
} else {
traffic.Total += clientTraffic.Total
}
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
traffic.ExpiryTime = 0
}
}
}
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return result, header, nil
} }
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound { if err != nil && err != gorm.ErrRecordNotFound {
return nil, err return nil, err
} }
return inbounds, nil return inbounds, nil
} }
func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic {
for _, traffic := range traffics {
if traffic.Email == email {
return traffic
}
}
return xray.ClientTraffic{}
}
func (s *SubService) getLink(inbound *model.Inbound, email string) string { func (s *SubService) getLink(inbound *model.Inbound, email string) string {
switch inbound.Protocol { switch inbound.Protocol {
case "vmess": case "vmess":
@ -271,21 +307,29 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if security == "reality" { if security == "reality" {
params["security"] = "reality" params["security"] = "reality"
realitySettings, _ := stream["realitySettings"].(map[string]interface{}) realitySetting, _ := stream["realitySettings"].(map[string]interface{})
if realitySettings != nil { realitySettings, _ := searchKey(realitySetting, "settings")
if sniValue, ok := searchKey(realitySettings, "serverNames"); ok { if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{}) sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string) params["sni"], _ = sNames[0].(string)
} }
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok { if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string) params["pbk"], _ = pbkValue.(string)
} }
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok { if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{}) shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string) params["sid"], _ = shortIds[0].(string)
} }
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string) if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp
}
}
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
}
} }
} }
@ -296,7 +340,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if security == "xtls" { if security == "xtls" {
params["security"] = "xtls" params["security"] = "xtls"
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{}) xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{}) alpns, _ := xtlsSetting["alpn"].([]interface{})
var alpn []string var alpn []string
for _, a := range alpns { for _, a := range alpns {
@ -306,15 +350,15 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["alpn"] = strings.Join(alpn, ",") params["alpn"] = strings.Join(alpn, ",")
} }
XTLSSettings, _ := searchKey(xtlsSetting, "settings") xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil { if xtlsSetting != nil {
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok { if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string) params["sni"], _ = sniValue.(string)
} }
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok { if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string) params["fp"], _ = fpValue.(string)
} }
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok { if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
if insecure.(bool) { if insecure.(bool) {
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
@ -440,9 +484,10 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if security == "reality" { if security == "reality" {
params["security"] = "reality" params["security"] = "reality"
realitySettings, _ := stream["realitySettings"].(map[string]interface{}) realitySetting, _ := stream["realitySettings"].(map[string]interface{})
if realitySettings != nil { realitySettings, _ := searchKey(realitySetting, "settings")
if sniValue, ok := searchKey(realitySettings, "serverNames"); ok { if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{}) sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string) params["sni"], _ = sNames[0].(string)
} }
@ -454,7 +499,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["sid"], _ = shortIds[0].(string) params["sid"], _ = shortIds[0].(string)
} }
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string) if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp
}
}
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
}
} }
} }
@ -465,7 +517,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if security == "xtls" { if security == "xtls" {
params["security"] = "xtls" params["security"] = "xtls"
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{}) xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{}) alpns, _ := xtlsSetting["alpn"].([]interface{})
var alpn []string var alpn []string
for _, a := range alpns { for _, a := range alpns {
@ -475,15 +527,15 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["alpn"] = strings.Join(alpn, ",") params["alpn"] = strings.Join(alpn, ",")
} }
XTLSSettings, _ := searchKey(xtlsSetting, "settings") xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil { if xtlsSetting != nil {
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok { if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string) params["sni"], _ = sniValue.(string)
} }
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok { if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string) params["fp"], _ = fpValue.(string)
} }
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok { if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
if insecure.(bool) { if insecure.(bool) {
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }

View file

@ -25,12 +25,12 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
return user, nil return user, nil
} }
func (s *UserService) CheckUser(username string, password string) *model.User { func (s *UserService) CheckUser(username string, password string, secret string) *model.User {
db := database.GetDB() db := database.GetDB()
user := &model.User{} user := &model.User{}
err := db.Model(model.User{}). err := db.Model(model.User{}).
Where("username = ? and password = ?", username, password). Where("username = ? and password = ? and login_secret = ?", username, password, secret).
First(user). First(user).
Error Error
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
@ -50,6 +50,35 @@ func (s *UserService) UpdateUser(id int, username string, password string) error
Error Error
} }
func (s *UserService) UpdateUserSecret(id int, secret string) error {
db := database.GetDB()
return db.Model(model.User{}).
Where("id = ?", id).
Update("login_secret", secret).
Error
}
func (s *UserService) RemoveUserSecret() error {
db := database.GetDB()
return db.Model(model.User{}).
Where("1 = 1").
Update("login_secret", "").
Error
}
func (s *UserService) GetUserSecret(id int) *model.User {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
Where("id = ?", id).
First(user).
Error
if err == gorm.ErrRecordNotFound {
return nil
}
return user
}
func (s *UserService) UpdateFirstUser(username string, password string) error { func (s *UserService) UpdateFirstUser(username string, password string) error {
if username == "" { if username == "" {
return errors.New("username can not be empty") return errors.New("username can not be empty")

View file

@ -26,7 +26,7 @@
"edit" = "Edit" "edit" = "Edit"
"delete" = "Delete" "delete" = "Delete"
"reset" = "Reset" "reset" = "Reset"
"copySuccess" = "Copy successfully" "copySuccess" = "Copied successfully"
"sure" = "Sure" "sure" = "Sure"
"encryption" = "Encryption" "encryption" = "Encryption"
"transmission" = "Transmission" "transmission" = "Transmission"
@ -40,7 +40,7 @@
"depletingSoon" = "Depleting soon" "depletingSoon" = "Depleting soon"
"domainName" = "Domain name" "domainName" = "Domain name"
"additional" = "Alter" "additional" = "Alter"
"monitor" = "Listen IP" "monitor" = "Listening IP"
"certificate" = "Certificate" "certificate" = "Certificate"
"fail" = "Fail" "fail" = "Fail"
"success" = " Success" "success" = " Success"
@ -48,12 +48,13 @@
"install" = "Install" "install" = "Install"
"clients" = "Clients" "clients" = "Clients"
"usage" = "Usage" "usage" = "Usage"
"secretToken" = "Secret token"
[menu] [menu]
"dashboard" = "System Status" "dashboard" = "System Status"
"inbounds" = "Inbounds" "inbounds" = "Inbounds"
"setting" = "Panel Setting" "setting" = "Panel Setting"
"logout" = "LogOut" "logout" = "Logout"
"link" = "Other" "link" = "Other"
[pages.login] [pages.login]
@ -61,7 +62,7 @@
"loginAgain" = "The login time limit has expired, please log in again" "loginAgain" = "The login time limit has expired, please log in again"
[pages.login.toasts] [pages.login.toasts]
"invalidFormData" = "Input Data Format Is Invalid" "invalidFormData" = "Input Data Format is Invalid"
"emptyUsername" = "Please Enter Username" "emptyUsername" = "Please Enter Username"
"emptyPassword" = "Please Enter Password" "emptyPassword" = "Please Enter Password"
"wrongUsernameOrPassword" = "Invalid username or password" "wrongUsernameOrPassword" = "Invalid username or password"
@ -75,17 +76,17 @@
"stopXray" = "Stop" "stopXray" = "Stop"
"restartXray" = "Restart" "restartXray" = "Restart"
"xraySwitch" = "Switch Version" "xraySwitch" = "Switch Version"
"xraySwitchClick" = "Click on the version you want to switch" "xraySwitchClick" = "Choose the version you want to switch to."
"xraySwitchClickDesk" = "Please choose carefully, older versions may have incompatible configurations" "xraySwitchClickDesk" = "Choose wisely, as older versions may not be compatible with current configurations."
"operationHours" = "Operation Hours" "operationHours" = "Operation Hours"
"operationHoursDesc" = "The running time of the system since it was started" "operationHoursDesc" = "System uptime: time since startup."
"systemLoad" = "System Load" "systemLoad" = "System Load"
"connectionCount" = "Connection Count" "connectionCount" = "Number of connections"
"connectionCountDesc" = "The total number of connections for all network cards" "connectionCountDesc" = "Total connections across all network cards"
"upSpeed" = "Total upload speed for all network cards" "upSpeed" = "Total upload speed for all network cards"
"downSpeed" = "Total download speed for all network cards" "downSpeed" = "Total download speed for all network cards"
"totalSent" = "Total upload traffic of all network cards since system startup" "totalSent" = "Total upload traffic of all network cards since system startup"
"totalReceive" = "Total download traffic of all network cards since system startup" "totalReceive" = "Total download data across all network cards since system startup"
"xraySwitchVersionDialog" = "Switch xray version" "xraySwitchVersionDialog" = "Switch xray version"
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to" "xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
"dontRefreshh" = "Installation is in progress, please do not refresh this page" "dontRefreshh" = "Installation is in progress, please do not refresh this page"
@ -110,8 +111,8 @@
"revise" = "Update" "revise" = "Update"
"modifyInbound" = "Modify InBound" "modifyInbound" = "Modify InBound"
"deleteInbound" = "Delete Inbound" "deleteInbound" = "Delete Inbound"
"deleteInboundContent" = "Are you sure you want to delete inbound?" "deleteInboundContent" = "Confirm deletion of inbound?"
"resetTrafficContent" = "Are you sure you want to reset traffic?" "resetTrafficContent" = "Confirm traffic reset?"
"copyLink" = "Copy Link" "copyLink" = "Copy Link"
"address" = "Address" "address" = "Address"
"network" = "Network" "network" = "Network"
@ -121,8 +122,8 @@
"monitorDesc" = "Leave blank by default" "monitorDesc" = "Leave blank by default"
"meansNoLimit" = "Means no limit" "meansNoLimit" = "Means no limit"
"totalFlow" = "Total flow" "totalFlow" = "Total flow"
"leaveBlankToNeverExpire" = "Leave blank to never expire" "leaveBlankToNeverExpire" = "Leave blank to set no expiration"
"noRecommendKeepDefault" = "There are no special requirements to keep the default" "noRecommendKeepDefault" = "No special requirements to maintain default settings"
"certificatePath" = "Certificate file path" "certificatePath" = "Certificate file path"
"certificateContent" = "Certificate file content" "certificateContent" = "Certificate file content"
"publicKeyPath" = "Public key path" "publicKeyPath" = "Public key path"
@ -134,7 +135,7 @@
"export" = "Export links" "export" = "Export links"
"Clone" = "Clone" "Clone" = "Clone"
"cloneInbound" = "Create" "cloneInbound" = "Create"
"cloneInboundContent" = "All items of this inbound except Port, Listening IP, Clients will be applied to the clone" "cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone"
"cloneInboundOk" = "Creating a clone from" "cloneInboundOk" = "Creating a clone from"
"resetAllTraffic" = "Reset All Inbounds Traffic" "resetAllTraffic" = "Reset All Inbounds Traffic"
"resetAllTrafficTitle" = "Reset all inbounds traffic" "resetAllTrafficTitle" = "Reset all inbounds traffic"
@ -142,12 +143,12 @@
"resetAllTrafficOkText" = "Confirm" "resetAllTrafficOkText" = "Confirm"
"resetAllTrafficCancelText" = "Cancel" "resetAllTrafficCancelText" = "Cancel"
"IPLimit" = "IP Limit" "IPLimit" = "IP Limit"
"IPLimitDesc" = "disable inbound if more than entered count (0 for disable limit ip)" "IPLimitDesc" = "Disable inbound if the count exceeds the entered value (Enter 0 to disable IP limit)"
"resetAllClientTraffics" = "Reset Clients Traffic" "resetAllClientTraffics" = "Reset Clients Traffic"
"resetAllClientTrafficTitle" = "Reset all clients traffic" "resetAllClientTrafficTitle" = "Reset all clients traffic"
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?" "resetAllClientTrafficContent" = "Confirm reset of all traffic for clients of this inbound?"
"Email" = "Email" "Email" = "Email"
"EmailDesc" = "The Email Must Be Completely Unique" "EmailDesc" = "Please provide a unique email address"
"IPLimitlog" = "IP Log" "IPLimitlog" = "IP Log"
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)" "IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
"IPLimitlogclear" = "Clear The Log" "IPLimitlogclear" = "Clear The Log"
@ -196,6 +197,8 @@
"save" = "Save" "save" = "Save"
"restartPanel" = "Restart Panel" "restartPanel" = "Restart Panel"
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information" "restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
"actions" = "Actions"
"resetDefaultConfig" = "Reset to default config"
"panelConfig" = "Panel Configuration" "panelConfig" = "Panel Configuration"
"userSetting" = "User Setting" "userSetting" = "User Setting"
"xrayConfiguration" = "Xray Configuration" "xrayConfiguration" = "Xray Configuration"
@ -215,18 +218,53 @@
"currentPassword" = "Current Password" "currentPassword" = "Current Password"
"newUsername" = "New Username" "newUsername" = "New Username"
"newPassword" = "New Password" "newPassword" = "New Password"
"advancedTemplate" = "Advanced template parts" "basicTemplate" = "Basic Template"
"completeTemplate" = "Complete template of Xray configuration" "advancedTemplate" = "Advanced Template parts"
"completeTemplate" = "Complete Template of Xray configuration"
"generalConfigs" = "General Configs"
"generalConfigsDesc" = "This options will prevent users from connecting to specific protocols and websites."
"countryConfigs" = "Country Configs"
"countryConfigsDesc" = "This options will prevent users from connecting to specific country domains."
"ipv4Configs" = "IPv4 Configs"
"ipv4ConfigsDesc" = "This options will be route to target domains only via IPv4."
"warpConfigs" = "WARP Configs"
"warpConfigsDesc" = "Caution: Before using this options, Install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
"xrayConfigTemplate" = "Xray Configuration Template" "xrayConfigTemplate" = "Xray Configuration Template"
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect." "xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
"xrayConfigTorrent" = "Ban bittorrent usage" "xrayConfigTorrent" = "Ban bittorrent usage"
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect" "xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect"
"xrayConfigPrivateIp" = "Ban private IP ranges to connect" "xrayConfigPrivateIp" = "Ban private IP ranges to connect"
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect" "xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect"
"xrayConfigAds" = "Block Ads"
"xrayConfigAdsDesc" = "Change the configuration template to block Ads, restart the panel to take effect"
"xrayConfigPorn" = "Block Porn Websites"
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to Porn websites, restart the panel to take effect"
"xrayConfigIRIp" = "Ban Iran IP ranges to connect" "xrayConfigIRIp" = "Ban Iran IP ranges to connect"
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges, restart the panel to take effect" "xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges, restart the panel to take effect"
"xrayConfigIRdomain" = "Ban IR domains to connect" "xrayConfigIRDomain" = "Ban Iran Domains to connect"
"xrayConfigIRdomainDesc" = "Change the configuration template to avoid connecting with IR domains, restart the panel to take effect" "xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains, restart the panel to take effect"
"xrayConfigChinaIp" = "Ban China IP ranges to connect"
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges, restart the panel to take effect"
"xrayConfigChinaDomain" = "Ban China Domains to connect"
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains, restart the panel to take effect"
"xrayConfigRussiaIp" = "Ban Russia IP ranges to connect"
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges, restart the panel to take effect"
"xrayConfigRussiaDomain" = "Ban Russia Domains to connect"
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains, restart the panel to take effect"
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
"xrayConfigGoogleIPv4Desc" = "Add routing for google to connect with IPv4, restart the panel to take effect"
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4, restart the panel to take effect"
"xrayConfigGoogleWARP" = "Route Google to WARP"
"xrayConfigGoogleWARPDesc" = "Add routing for Google to WARP, restart the panel to take effect"
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) to WARP"
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) to WARP, restart the panel to take effect"
"xrayConfigNetflixWARP" = "Route Netflix to WARP"
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix to WARP, restart the panel to take effect"
"xrayConfigSpotifyWARP" = "Route Spotify to WARP"
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify to WARP, restart the panel to take effect"
"xrayConfigIRWARP" = "Route Iran Domains to WARP"
"xrayConfigIRWARPDesc" = "Add routing for Iran Domains to WARP. restart the panel to take effect"
"xrayConfigInbounds" = "Configuration of Inbounds" "xrayConfigInbounds" = "Configuration of Inbounds"
"xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect" "xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect"
"xrayConfigOutbounds" = "Configuration of Outbounds" "xrayConfigOutbounds" = "Configuration of Outbounds"
@ -251,6 +289,10 @@
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)" "tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
"timeZonee" = "Time Zone" "timeZonee" = "Time Zone"
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect" "timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
"loginSecurity" = "Login security"
"loginSecurityDesc" = "Toggle additional step in user login page"
"secretToken" = "Secret Token"
"secretTokenDesc" = "Copy this secret token and keep it in a safe place; without this you won't be able to login. This can not be recovered from x-ui command tool neither"
[pages.setting.toasts] [pages.setting.toasts]
"modifySetting" = "Modify setting" "modifySetting" = "Modify setting"

View file

@ -48,6 +48,7 @@
"install" = "نصب" "install" = "نصب"
"clients" = "کاربران" "clients" = "کاربران"
"usage" = "استفاده" "usage" = "استفاده"
"secretToken" = "توکن امنیتی"
[menu] [menu]
"dashboard" = "وضعیت سیستم" "dashboard" = "وضعیت سیستم"
@ -194,6 +195,8 @@
"save" = "ذخیره" "save" = "ذخیره"
"restartPanel" = "ریستارت پنل" "restartPanel" = "ریستارت پنل"
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید" "restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
"actions" = "عملیات ها"
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
"panelConfig" = "تنظیمات پنل" "panelConfig" = "تنظیمات پنل"
"userSetting" = "تنظیمات مدیر" "userSetting" = "تنظیمات مدیر"
"xrayConfiguration" = "تنظیمات Xray" "xrayConfiguration" = "تنظیمات Xray"
@ -213,18 +216,53 @@
"currentPassword" = "رمز عبور فعلی" "currentPassword" = "رمز عبور فعلی"
"newUsername" = "نام کاربری جدید" "newUsername" = "نام کاربری جدید"
"newPassword" = "رمز عبور جدید" "newPassword" = "رمز عبور جدید"
"basicTemplate" = "بخش پایه"
"advancedTemplate" = "بخش های پیشرفته الگو" "advancedTemplate" = "بخش های پیشرفته الگو"
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری" "completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
"generalConfigs" = "تنظیمات عمومی"
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
"countryConfigs" = "تنظیمات برای کشورها"
"countryConfigsDesc" = "این گزینه از اتصال کاربران به دامنه های کشوری خاص جلوگیری می کند."
"ipv4Configs" = "تنظیمات برای IPv4"
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن 4 به دامنه های هدف هدایت می شود."
"warpConfigs" = "تنظیمات برای WARP"
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند."
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری" "xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود" "xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت" "xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود" "xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر" "xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود" "xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigIRIp" = "جلوگیری از اتصال آی پی های ایران" "xrayConfigAds" = "مسدود کردن تبلیغات"
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود" "xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigIRdomain" = "جلوگیری از اتصال دامنه های ایران" "xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
"xrayConfigIRdomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود" "xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigInbounds" = "تنظیمات ورودی" "xrayConfigInbounds" = "تنظیمات ورودی"
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود" "xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
"xrayConfigOutbounds" = "تنظیمات خروجی" "xrayConfigOutbounds" = "تنظیمات خروجی"
@ -249,6 +287,10 @@
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)" "tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
"timeZonee" = "منظقه زمانی" "timeZonee" = "منظقه زمانی"
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود" "timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
"loginSecurity" = "لاگین ایمن"
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
"secretToken" = "توکن امنیتی"
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!"
[pages.setting.toasts] [pages.setting.toasts]
"modifySetting" = "ویرایش تنظیمات" "modifySetting" = "ویرایش تنظیمات"

View file

@ -48,6 +48,7 @@
"install" = "安装" "install" = "安装"
"clients" = "客户端" "clients" = "客户端"
"usage" = "用法" "usage" = "用法"
"secretToken" = "秘密令牌"
[menu] [menu]
"dashboard" = "系统状态" "dashboard" = "系统状态"
@ -194,6 +195,8 @@
"save" = "保存配置" "save" = "保存配置"
"restartPanel" = "重启面板" "restartPanel" = "重启面板"
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息" "restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
"actions" = "动作"
"resetDefaultConfig" = "重置为默认配置"
"panelConfig" = "面板配置" "panelConfig" = "面板配置"
"userSetting" = "用户设置" "userSetting" = "用户设置"
"xrayConfiguration" = "xray 相关设置" "xrayConfiguration" = "xray 相关设置"
@ -213,18 +216,53 @@
"currentPassword" = "原密码" "currentPassword" = "原密码"
"newUsername" = "新用户名" "newUsername" = "新用户名"
"newPassword" = "新密码" "newPassword" = "新密码"
"basicTemplate" = "基本模板"
"advancedTemplate" = "高级模板部件" "advancedTemplate" = "高级模板部件"
"completeTemplate" = "Xray 配置的完整模板" "completeTemplate" = "Xray 配置的完整模板"
"generalConfigs" = "一般配置"
"generalConfigsDesc" = "此选项将阻止用户连接到特定协议和网站。"
"countryConfigs" = "国家配置"
"countryConfigsDesc" = "此选项将阻止用户连接到特定国家/地区的域。"
"ipv4Configs" = "IPv4 配置"
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域。"
"warpConfigs" = "WARP 配置"
"warpConfigsDesc" = "警告:在使用此选项之前,请按照面板 GitHub 上的步骤在您的服务器上以 socks5 代理模式安装 WARP。 WARP 将通过 Cloudflare 服务器将流量路由到网站。"
"xrayConfigTemplate" = "xray 配置模板" "xrayConfigTemplate" = "xray 配置模板"
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件重新启动面板生成效率" "xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件重新启动面板生成效率"
"xrayConfigTorrent" = "禁止使用 bittorrent" "xrayConfigTorrent" = "禁止使用 bittorrent"
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent重启面板生效" "xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent重启面板生效"
"xrayConfigPrivateIp" = "禁止私人 ip 范围连接" "xrayConfigPrivateIp" = "禁止私人 IP 范围连接"
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效" "xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
"xrayConfigAds" = "屏蔽广告"
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
"xrayConfigPorn" = "禁止色情网站连接"
"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接" "xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP范围重启面板生效" "xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段重启面板生效"
"xrayConfigIRdomain" = "禁止伊朗域连接" "xrayConfigIRDomain" = "禁止伊朗域连接"
"xrayConfigIRdomainDesc" = "修改配置模板避免连接伊朗域名,重启面板生效" "xrayConfigIRDomainDesc" = "更改配置模板避免连接伊朗域名,重启面板生效"
"xrayConfigChinaIp" = "禁止中国 IP 范围连接"
"xrayConfigChinaIpDesc" = "修改配置模板避免连接中国IP段重启面板生效"
"xrayConfigChinaDomain" = "禁止中国域名连接"
"xrayConfigChinaDomainDesc" = "更改配置模板避免连接中国域,重启面板生效"
"xrayConfigRussiaIp" = "禁止俄罗斯 IP 范围连接"
"xrayConfigRussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围重启面板生效"
"xrayConfigRussiaDomain" = "禁止俄罗斯域连接"
"xrayConfigRussiaDomainDesc" = "更改配置模板避免连接俄罗斯域,重启面板生效"
"xrayConfigGoogleIPv4" = "为谷歌使用 IPv4"
"xrayConfigGoogleIPv4Desc" = "添加谷歌连接IPv4的路由重启面板生效"
"xrayConfigNetflixIPv4" = "为 Netflix 使用 IPv4"
"xrayConfigNetflixIPv4Desc" = "添加Netflix连接IPv4的路由重启面板生效"
"xrayConfigGoogleWARP" = "将谷歌路由到 WARP"
"xrayConfigGoogleWARPDesc" = "为谷歌添加路由到WARP重启面板生效"
"xrayConfigOpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
"xrayConfigOpenAIWARPDesc" = "将OpenAIChatGPT路由添加到WARP重启面板生效"
"xrayConfigNetflixWARP" = "将 Netflix 路由到 WARP"
"xrayConfigNetflixWARPDesc" = "为Netflix添加路由到WARP重启面板生效"
"xrayConfigSpotifyWARP" = "将 Spotify 路由到 WARP"
"xrayConfigSpotifyWARPDesc" = "为Spotify添加路由到WARP重启面板生效"
"xrayConfigIRWARP" = "将伊朗域名路由到 WARP"
"xrayConfigIRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
"xrayConfigInbounds" = "入站配置" "xrayConfigInbounds" = "入站配置"
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效" "xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
"xrayConfigOutbounds" = "出站配置" "xrayConfigOutbounds" = "出站配置"
@ -249,6 +287,10 @@
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知" "tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
"timeZonee" = "时区" "timeZonee" = "时区"
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效" "timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
"loginSecurity" = "登录安全"
"loginSecurityDesc" = "在用户登录页面中切换附加步骤"
"secretToken" = "秘密令牌"
"secretTokenDesc" = "复制此秘密令牌并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复"
[pages.setting.toasts] [pages.setting.toasts]
"modifySetting" = "修改设置" "modifySetting" = "修改设置"

182
x-ui.sh
View file

@ -17,6 +17,7 @@ function LOGE() {
function LOGI() { function LOGI() {
echo -e "${green}[INF] $* ${plain}" echo -e "${green}[INF] $* ${plain}"
} }
# check root # check root
[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1 [[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
@ -34,7 +35,6 @@ fi
echo "The OS release is: $release" echo "The OS release is: $release"
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@ -44,24 +44,29 @@ if [[ "${release}" == "centos" ]]; then
fi fi
elif [[ "${release}" == "ubuntu" ]]; then elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version${plain}\n" && exit 1 echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "fedora" ]]; then elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then if [[ ${os_version} -lt 36 ]]; then
echo -e "${red}please use Fedora 36 or higher version${plain}\n" && exit 1 echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "debian" ]]; then elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
fi fi
fi fi
arch3xui() {
case "$(uname -m)" in
x86_64 | x64 | amd64 ) echo 'amd64' ;;
armv8 | arm64 | aarch64 ) echo 'arm64' ;;
* ) echo -e "${red} Unsupported CPU architecture!${plain}" && exit 1 ;;
esac
}
confirm() { confirm() {
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
echo && read -p "$1 [Default$2]: " temp echo && read -p "$1 [Default $2]: " temp
if [[ x"${temp}" == x"" ]]; then if [[ x"${temp}" == x"" ]]; then
temp=$2 temp=$2
fi fi
@ -101,18 +106,49 @@ install() {
} }
update() { update() {
confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "n" read -rp "This function will update the X-UI panel to the latest version. Data will not be lost. Whether to continues? [Y/N]: " yn
if [[ $? != 0 ]]; then if [[ $yn =~ "Y"|"y" ]]; then
LOGE "Cancelled" systemctl stop x-ui
if [[ $# == 0 ]]; then if [[ -e /usr/local/x-ui/ ]]; then
before_show_menu cd
rm -rf /usr/local/x-ui/
fi fi
return 0
last_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') || last_version=$(curl -sm8 https://raw.githubusercontent.com/MHSanaei/3x-ui/main/config/version)
if [[ -z "$last_version" ]]; then
echo -e "${red}Detecting the X-UI version failed, please make sure your server can connect to the GitHub API ${plain}"
exit 1
fi fi
bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh)
if [[ $? == 0 ]]; then echo -e "${yellow}The latest version of X-UI is: ${last_version}, starting update...${plain}"
LOGI "Update is complete, Panel has automatically restarted " wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz
exit 0 if [[ $? -ne 0 ]]; then
echo -e "${red}Download the X-UI failure, please make sure your server can connect and download the files from github ${plain}"
exit 1
fi
cd /usr/local/
tar zxvf x-ui-linux-$(arch3xui).tar.gz
rm -f x-ui-linux-$(arch3xui).tar.gz
cd x-ui
chmod +x x-ui bin/xray-linux-$(arch3xui)
cp -f x-ui.service /etc/systemd/system/
wget -N --no-check-certificate https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh -O /usr/bin/x-ui
chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui
systemctl daemon-reload
systemctl enable x-ui >/dev/null 2>&1
systemctl start x-ui
systemctl restart x-ui
echo -e "${green}The update is completed, and the X-UI panel has been automatically restarted ${plain}"
exit 1
else
echo -e "${red}The upgrade X-UI panel has been canceled! ${plain}"
exit 1
fi fi
} }
@ -133,7 +169,7 @@ uninstall() {
rm /usr/local/x-ui/ -rf rm /usr/local/x-ui/ -rf
echo "" echo ""
echo -e "Uninstalled SuccessfullyIf you want to remove this scriptthen after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it." echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
echo "" echo ""
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
@ -142,20 +178,28 @@ uninstall() {
} }
reset_user() { reset_user() {
confirm "Reset your username and password to admin?" "n" confirm "Are you sure to reset the username and password of the panel?" "n"
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
show_menu show_menu
fi fi
return 0 return 0
fi fi
/usr/local/x-ui/x-ui setting -username admin -password admin read -rp "Please set the login username [default is a random username]: " config_account
echo -e "Username and password have been reset to ${green}admin${plain}Please restart the panel now." [[ -z $config_account ]] && config_account=$(date +%s%N | md5sum | cut -c 1-8)
read -rp "Please set the login password [default is a random password]: " config_password
[[ -z $config_password ]] && config_password=$(date +%s%N | md5sum | cut -c 1-8)
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} >/dev/null 2>&1
/usr/local/x-ui/x-ui setting -remove_secret >/dev/null 2>&1
echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
echo -e "${yellow} Panel login secret token disabled ${plain}"
echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
confirm_restart confirm_restart
} }
reset_config() { reset_config() {
confirm "Are you sure you want to reset all panel settingsAccount data will not be lostUsername and password will not change" "n" confirm "Are you sure you want to reset all panel settings, Account data will not be lost, Username and password will not change" "n"
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
show_menu show_menu
@ -163,14 +207,14 @@ reset_config() {
return 0 return 0
fi fi
/usr/local/x-ui/x-ui setting -reset /usr/local/x-ui/x-ui setting -reset
echo -e "All panel settings have been reset to defaultPlease restart the panel nowand use the default ${green}2053${plain} Port to Access the web Panel" echo -e "All panel settings have been reset to default, Please restart the panel now, and use the default ${green}2053${plain} Port to Access the web Panel"
confirm_restart confirm_restart
} }
check_config() { check_config() {
info=$(/usr/local/x-ui/x-ui setting -show true) info=$(/usr/local/x-ui/x-ui setting -show true)
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
LOGE "get current settings error,please check logs" LOGE "get current settings error, please check logs"
show_menu show_menu
fi fi
LOGI "${info}" LOGI "${info}"
@ -183,7 +227,7 @@ set_port() {
before_show_menu before_show_menu
else else
/usr/local/x-ui/x-ui setting -port ${port} /usr/local/x-ui/x-ui setting -port ${port}
echo -e "The port is setPlease restart the panel nowand use the new port ${green}${port}${plain} to access web panel" echo -e "The port is set, Please restart the panel now, and use the new port ${green}${port}${plain} to access web panel"
confirm_restart confirm_restart
fi fi
} }
@ -192,7 +236,7 @@ start() {
check_status check_status
if [[ $? == 0 ]]; then if [[ $? == 0 ]]; then
echo "" echo ""
LOGI "Panel is runningNo need to start againIf you need to restart, please select restart" LOGI "Panel is running, No need to start again, If you need to restart, please select restart"
else else
systemctl start x-ui systemctl start x-ui
sleep 2 sleep 2
@ -200,7 +244,7 @@ start() {
if [[ $? == 0 ]]; then if [[ $? == 0 ]]; then
LOGI "x-ui Started Successfully" LOGI "x-ui Started Successfully"
else else
LOGE "panel Failed to startProbably because it takes longer than two seconds to startPlease check the log information later" LOGE "panel Failed to start, Probably because it takes longer than two seconds to start, Please check the log information later"
fi fi
fi fi
@ -213,7 +257,7 @@ stop() {
check_status check_status
if [[ $? == 1 ]]; then if [[ $? == 1 ]]; then
echo "" echo ""
LOGI "Panel stoppedNo need to stop again!" LOGI "Panel stopped, No need to stop again!"
else else
systemctl stop x-ui systemctl stop x-ui
sleep 2 sleep 2
@ -221,7 +265,7 @@ stop() {
if [[ $? == 1 ]]; then if [[ $? == 1 ]]; then
LOGI "x-ui and xray stopped successfully" LOGI "x-ui and xray stopped successfully"
else else
LOGE "Panel stop failedProbably because the stop time exceeds two secondsPlease check the log information later" LOGE "Panel stop failed, Probably because the stop time exceeds two seconds, Please check the log information later"
fi fi
fi fi
@ -237,7 +281,7 @@ restart() {
if [[ $? == 0 ]]; then if [[ $? == 0 ]]; then
LOGI "x-ui and xray Restarted successfully" LOGI "x-ui and xray Restarted successfully"
else else
LOGE "Panel restart failedProbably because it takes longer than two seconds to startPlease check the log information later" LOGE "Panel restart failed, Probably because it takes longer than two seconds to start, Please check the log information later"
fi fi
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
before_show_menu before_show_menu
@ -285,51 +329,49 @@ show_log() {
} }
enable_bbr() { enable_bbr() {
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo -e "${green}BBR is already enabled!${plain}" echo -e "${green}BBR is already enabled!${plain}"
exit 0 exit 0
fi fi
# Check the OS and install necessary packages # Check the OS and install necessary packages
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "debian" ]]; then
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
sudo dnf -y update && sudo dnf -y install ca-certificates sudo dnf -y update && sudo dnf -y install ca-certificates
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
sudo yum -y update && sudo yum -y install ca-certificates sudo yum -y update && sudo yum -y install ca-certificates
else else
echo "Unsupported operating system. Please check the script and install the necessary packages manually." echo "Unsupported operating system. Please check the script and install the necessary packages manually."
exit 1 exit 1
fi fi
# Enable BBR # Enable BBR
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
# Apply changes # Apply changes
sudo sysctl -p sudo sysctl -p
# Verify that BBR is enabled # Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
echo -e "${green}BBR has been enabled successfully.${plain}" echo -e "${green}BBR has been enabled successfully.${plain}"
else else
echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}" echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
fi fi
} }
update_shell() { update_shell() {
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo "" echo ""
LOGE "Failed to download scriptPlease check whether the machine can connect Github" LOGE "Failed to download script, Please check whether the machine can connect Github"
before_show_menu before_show_menu
else else
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
LOGI "Upgrade script succeededPlease rerun the script" && exit 0 LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
fi fi
} }
@ -359,7 +401,7 @@ check_uninstall() {
check_status check_status
if [[ $? != 2 ]]; then if [[ $? != 2 ]]; then
echo "" echo ""
LOGE "Panel installedPlease do not reinstall" LOGE "Panel installed, Please do not reinstall"
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
before_show_menu before_show_menu
fi fi
@ -455,14 +497,14 @@ ssl_cert_issue() {
} }
open_ports() { open_ports() {
if ! command -v ufw &> /dev/null if ! command -v ufw &> /dev/null
then then
echo "ufw firewall is not installed. Installing now..." echo "ufw firewall is not installed. Installing now..."
sudo apt-get update sudo apt-get update
sudo apt-get install -y ufw sudo apt-get install -y ufw
else else
echo "ufw firewall is already installed" echo "ufw firewall is already installed"
fi fi
# Check if the firewall is inactive # Check if the firewall is inactive
if sudo ufw status | grep -q "Status: active"; then if sudo ufw status | grep -q "Status: active"; then
@ -506,18 +548,25 @@ fi
sudo ufw status | grep $ports sudo ufw status | grep $ports
} }
update_geo() {
local defaultBinFolder="/usr/local/x-ui/bin"
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
binFolder=${binFolder:-${defaultBinFolder}}
if [[ ! -d ${binFolder} ]]; then
LOGE "Folder ${binFolder} not exists!"
LOGI "making bin folder: ${binFolder}..."
mkdir -p ${binFolder}
fi
update_geo(){
systemctl stop x-ui systemctl stop x-ui
cd /usr/local/x-ui/bin cd ${binFolder}
rm -f geoip.dat geosite.dat iran.dat rm -f geoip.dat geosite.dat iran.dat
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
systemctl start x-ui systemctl start x-ui
echo -e "${green}Geosite and Geoip have been updated successfully!${plain}" echo -e "${green}Geosite.dat + Geoip.dat + Iran.dat have been updated successfully in bin folder '${binfolder}'!${plain}"
before_show_menu before_show_menu
} }
install_acme() { install_acme() {
@ -714,8 +763,9 @@ ssl_cert_issue_by_cloudflare() {
show_menu show_menu
fi fi
} }
google_recaptcha() {
curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh warp_fixchatgpt() {
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
echo "" echo ""
before_show_menu before_show_menu
} }
@ -778,7 +828,7 @@ show_menu() {
${green}2.${plain} Update x-ui ${green}2.${plain} Update x-ui
${green}3.${plain} Uninstall x-ui ${green}3.${plain} Uninstall x-ui
———————————————— ————————————————
${green}4.${plain} Reset Username And Password ${green}4.${plain} Reset Username & Password & Secret Token
${green}5.${plain} Reset Panel Settings ${green}5.${plain} Reset Panel Settings
${green}6.${plain} Change Panel Port ${green}6.${plain} Change Panel Port
${green}7.${plain} View Current Panel Settings ${green}7.${plain} View Current Panel Settings
@ -796,7 +846,7 @@ show_menu() {
${green}16.${plain} Apply for an SSL Certificate ${green}16.${plain} Apply for an SSL Certificate
${green}17.${plain} Update Geo Files ${green}17.${plain} Update Geo Files
${green}18.${plain} Active Firewall and open ports ${green}18.${plain} Active Firewall and open ports
${green}19.${plain} Fixing Google reCAPTCHA ${green}19.${plain} Install WARP
${green}20.${plain} Speedtest by Ookla ${green}20.${plain} Speedtest by Ookla
" "
show_status show_status
@ -861,7 +911,7 @@ show_menu() {
open_ports open_ports
;; ;;
19) 19)
google_recaptcha warp_fixchatgpt
;; ;;
20) 20)
run_speedtest run_speedtest

View file

@ -45,6 +45,10 @@ func GetGeoipPath() string {
return config.GetBinFolderPath() + "/geoip.dat" return config.GetBinFolderPath() + "/geoip.dat"
} }
func GetIranPath() string {
return config.GetBinFolderPath() + "/iran.dat"
}
func GetBlockedIPsPath() string { func GetBlockedIPsPath() string {
return config.GetBinFolderPath() + "/blockedIPs" return config.GetBinFolderPath() + "/blockedIPs"
} }