Compare commits

...

53 Commits

Author SHA1 Message Date
Ho3ein
3f7e819a9b Update README.md 2023-04-19 15:41:17 +03:30
Ho3ein
834d003ab6 v1.2.6 2023-04-19 15:40:47 +03:30
MHSanaei
c627227893 bug fixed 2023-04-19 15:37:24 +03:30
MHSanaei
2a8725a7a5 mistake 2023-04-19 13:26:35 +03:30
Ho3ein
ca2d1bb901 Update README.md 2023-04-19 11:59:07 +03:30
Ho3ein
fa19649286 Merge pull request #269 from hamid-gh98/main
Add description for config groups
2023-04-19 11:56:46 +03:30
Ho3ein
56d75f5293 v1.2.5 2023-04-19 11:56:16 +03:30
MHSanaei
e1132a3f41 bug fix
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-04-19 11:55:38 +03:30
MHSanaei
4d479102ad reality link bug fix
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-04-19 11:55:31 +03:30
Hamidreza Ghavami
e90c575bfd add description for config groups 2023-04-19 04:02:53 +04:30
Hamidreza
9d02f455cc Merge pull request #268 from hamid-gh98/main
update README.md
2023-04-18 22:41:07 +03:30
Hamidreza Ghavami
13f67f595c update README.md 2023-04-18 23:40:08 +04:30
MHSanaei
25741dcb08 Update README.md 2023-04-18 22:34:43 +03:30
Ho3ein
a7af62162c v1.2.4 2023-04-18 21:36:01 +03:30
MHSanaei
3e0faecaae improve reality setting
split xtls from tls - remove iran warp - remove old setting reality from franzkafka (it was a messy code) -and other improvement

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-04-18 21:34:06 +03:30
Ho3ein
dc7dbae14a Merge pull request #266 from hamid-gh98/main
[Support] change settings by different items in panel
2023-04-18 21:10:33 +03:30
Hamidreza Ghavami
dd8c763b21 update translation 2023-04-18 21:25:16 +04:30
Hamidreza Ghavami
e0e7c102b8 update ui of setting.html + add russia setting 2023-04-18 21:24:32 +04:30
Hamidreza Ghavami
f26a7df11b add reset default config function 2023-04-18 21:23:51 +04:30
Hamidreza Ghavami
72a1b1e3f3 update domain list of setting.html 2023-04-18 21:23:12 +04:30
Hamidreza Ghavami
dfdb77c491 add func to setting service 2023-04-18 21:21:37 +04:30
Hamidreza Ghavami
846efe8eb4 add api route setting/getDefaultJsonConfig 2023-04-18 21:21:09 +04:30
Hamidreza Ghavami
8b79b5a315 update README.md 2023-04-18 21:20:28 +04:30
Hamidreza Ghavami
c71041a60d update media/configs 2023-04-18 21:19:25 +04:30
Ho3ein
6865cb108c Merge branch 'main' into main 2023-04-18 15:22:17 +03:30
Ho3ein
ee2089257a Merge pull request #267 from MHSanaei/dependabot/go_modules/github.com/xtls/xray-core-1.8.1
Bump github.com/xtls/xray-core from 1.8.0 to 1.8.1
2023-04-18 14:07:08 +03:30
dependabot[bot]
9f85ec72a8 Bump github.com/xtls/xray-core from 1.8.0 to 1.8.1
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-18 10:04:51 +00:00
Ho3ein
c4c266205b buy me a coffee 2023-04-18 13:34:09 +03:30
Hamidreza Ghavami
e4afbcea3b update README.md 2023-04-18 11:33:55 +04:30
Hamidreza Ghavami
44cede41fd update README.md 2023-04-18 11:31:52 +04:30
Hamidreza Ghavami
672fd1da19 update translation 2023-04-18 11:26:51 +04:30
Hamidreza Ghavami
f3fe866af2 change settings to use refactored function in setting.html 2023-04-18 11:23:43 +04:30
Hamidreza Ghavami
25e50aa6f1 Refactor a function for switching settings in setting.html 2023-04-18 11:23:15 +04:30
Hamidreza Ghavami
ff3657e15a add default configs in setting.html 2023-04-18 11:22:44 +04:30
Hamidreza Ghavami
4af626bb4b update html of setting.html 2023-04-18 11:21:12 +04:30
Hamidreza Ghavami
936f2e6ec2 update config.json 2023-04-18 10:22:04 +04:30
Hamidreza Ghavami
3e5984930e update x-ui.sh 2023-04-18 10:21:21 +04:30
Hamidreza Ghavami
40a0297499 update .gitignore 2023-04-18 10:16:43 +04:30
Hamidreza Ghavami
e13015a920 update release.yml 2023-04-18 10:16:10 +04:30
Ho3ein
ec4755952a Merge pull request #265 from MHSanaei/dependabot/github_actions/actions/checkout-3.5.2
Bump actions/checkout from 3.5.1 to 3.5.2
2023-04-17 16:46:36 +03:30
dependabot[bot]
d8fb83b1f2 Bump actions/checkout from 3.5.1 to 3.5.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.1...v3.5.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 10:08:20 +00:00
MHSanaei
4fb060d25e fix typo 2 2023-04-14 17:22:49 +03:30
MHSanaei
3eff8022f2 fix typo 2023-04-14 16:14:57 +03:30
Ho3ein
19bb28cb26 Merge pull request #262 from MHSanaei/dev
alpn set by default + syntax fix
2023-04-14 15:22:33 +03:30
Ho3ein
b70ecc12b3 Merge pull request #261 from hamid-gh98/main
Support set db and bin folder path from env
2023-04-14 05:28:03 +03:30
MHSanaei
1e72a22c96 syntax fix thanks to @lk29
Co-Authored-By: lk29 <12291632+lk29@users.noreply.github.com>
2023-04-14 01:28:08 +03:30
MHSanaei
d695ea8192 edit name http to h2 2023-04-14 01:26:32 +03:30
MHSanaei
b639a1bbd5 alpn set by default 2023-04-14 01:26:08 +03:30
Hamidreza Ghavami
4f952963ae update get paths functions 2023-04-14 00:10:01 +04:30
Hamidreza Ghavami
7f2ef94c7f update v2-ui.db path 2023-04-14 00:09:14 +04:30
Hamidreza Ghavami
81372de369 update en lang 2023-04-14 00:08:37 +04:30
Hamidreza Ghavami
a3b170d6c4 update and rename client ip job file 2023-04-14 00:07:13 +04:30
Hamidreza Ghavami
4548755375 update db config path 2023-04-14 00:03:46 +04:30
44 changed files with 2116 additions and 1077 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

123
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

@@ -45,6 +45,22 @@ func IsDebug() bool {
return os.Getenv("XUI_DEBUG") == "true" return os.Getenv("XUI_DEBUG") == "true"
} }
func GetDBPath() string { func GetBinFolderPath() string {
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName()) binFolderPath := os.Getenv("XUI_BIN_FOLDER")
if binFolderPath == "" {
binFolderPath = "bin"
}
return binFolderPath
}
func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" {
dbFolderPath = "/etc/x-ui"
}
return dbFolderPath
}
func GetDBPath() string {
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
} }

View File

@@ -1 +1 @@
1.2.3 1.2.6

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=

12
main.go
View File

@@ -97,7 +97,7 @@ func showSetting(show bool) {
settingService := service.SettingService{} settingService := service.SettingService{}
port, err := settingService.GetPort() port, err := settingService.GetPort()
if err != nil { if err != nil {
fmt.Println("get current port fialed,error info:", err) fmt.Println("get current port failed,error info:", err)
} }
userService := service.UserService{} userService := service.UserService{}
userModel, err := userService.GetFirstUser() userModel, err := userService.GetFirstUser()
@@ -109,7 +109,7 @@ func showSetting(show bool) {
if (username == "") || (userpasswd == "") { if (username == "") || (userpasswd == "") {
fmt.Println("current username or password is empty") fmt.Println("current username or password is empty")
} }
fmt.Println("current pannel settings as follows:") fmt.Println("current panel settings as follows:")
fmt.Println("username:", username) fmt.Println("username:", username)
fmt.Println("userpasswd:", userpasswd) fmt.Println("userpasswd:", userpasswd)
fmt.Println("port:", port) fmt.Println("port:", port)
@@ -217,7 +217,7 @@ func main() {
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError) v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
var dbPath string var dbPath string
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path") v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError) settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
var port int var port int
@@ -234,9 +234,9 @@ func main() {
settingCmd.IntVar(&port, "port", 0, "set panel port") settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username") settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password") settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token") settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time") settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegrame bot chat id") settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify") settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
oldUsage := flag.Usage oldUsage := flag.Usage

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,36 +46,31 @@
"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" ]
} }
] ]
}, },
"stats": {} "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,31 @@
"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"]
} }
] ]
}, },
"stats": {} "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,27 +46,21 @@
"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"
} }
] ]
}, },
"stats": {} "stats": {}
} }

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

@@ -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",
@@ -480,8 +478,8 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13, maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '', cipherSuites = '',
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[], 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 = {}) {
let settings;
if (!ObjectUtil.isEmpty(json.settings)) {
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
} }
static fromJson(json = {}) {
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,54 +1379,54 @@ 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;
} }
} }
const link = `vless://${uuid}@${address}:${port}`; const link = `vless://${uuid}@${address}:${port}`;
const url = new URL(link); const url = new URL(link);
for (const [key, value] of params) { for (const [key, value] of params) {
@@ -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

@@ -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)
@@ -151,19 +151,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

@@ -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

@@ -37,6 +37,7 @@ 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)
} }
func (a *SettingController) getAllSetting(c *gin.Context) { func (a *SettingController) getAllSetting(c *gin.Context) {
@@ -48,6 +49,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 {

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

@@ -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;
}
if(clientsBulkModal.inbound.xtls){
newClient.flow = clientsBulkModal.flow;
}
clients.push(newClient);
} }
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound); 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;

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>
@@ -100,10 +100,10 @@
</a-tag> </a-tag>
</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

@@ -6,7 +6,7 @@
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">KCP</a-select-option> <a-select-option value="kcp">KCP</a-select-option>
<a-select-option value="ws">WS</a-select-option> <a-select-option value="ws">WS</a-select-option>
<a-select-option value="http">HTTP</a-select-option> <a-select-option value="http">H2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
</a-select> </a-select>

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) {

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,30 @@
package job package job
import ( import (
"x-ui/logger" "encoding/json"
"x-ui/web/service" "os"
"regexp"
ss "strings"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"os" "x-ui/logger"
ss "strings" "x-ui/web/service"
"regexp" "x-ui/xray"
"encoding/json" // "strconv"
// "strconv" "github.com/go-cmd/cmd"
"net"
"sort"
"strings" "strings"
"time" "time"
"net"
"github.com/go-cmd/cmd"
"sort"
) )
type CheckClientIpJob struct { type CheckClientIpJob struct {
xrayService service.XrayService xrayService service.XrayService
inboundService service.InboundService inboundService service.InboundService
} }
var job *CheckClientIpJob var job *CheckClientIpJob
var disAllowedIps []string var disAllowedIps []string
func NewCheckClientIpJob() *CheckClientIpJob { func NewCheckClientIpJob() *CheckClientIpJob {
job = new(CheckClientIpJob) job = new(CheckClientIpJob)
@@ -34,94 +36,91 @@ func (j *CheckClientIpJob) Run() {
processLogFile() processLogFile()
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"} // disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
blockedIps := []byte(ss.Join(disAllowedIps,",")) blockedIps := []byte(ss.Join(disAllowedIps, ","))
err := os.WriteFile("./bin/blockedIPs", blockedIps, 0755) err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
checkError(err) checkError(err)
} }
func processLogFile() { func processLogFile() {
accessLogPath := GetAccessLogPath() accessLogPath := GetAccessLogPath()
if(accessLogPath == "") { if accessLogPath == "" {
logger.Warning("xray log not init in config.json") logger.Warning("xray log not init in config.json")
return return
} }
data, err := os.ReadFile(accessLogPath) data, err := os.ReadFile(accessLogPath)
InboundClientIps := make(map[string][]string) InboundClientIps := make(map[string][]string)
checkError(err) checkError(err)
// clean log // clean log
if err := os.Truncate(GetAccessLogPath(), 0); err != nil { if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
checkError(err) checkError(err)
} }
lines := ss.Split(string(data), "\n") lines := ss.Split(string(data), "\n")
for _, line := range lines { for _, line := range lines {
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`) ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
emailRegx, _ := regexp.Compile(`email:.+`) emailRegx, _ := regexp.Compile(`email:.+`)
matchesIp := ipRegx.FindString(line) matchesIp := ipRegx.FindString(line)
if(len(matchesIp) > 0) { if len(matchesIp) > 0 {
ip := string(matchesIp) ip := string(matchesIp)
if( ip == "127.0.0.1" || ip == "1.1.1.1") { if ip == "127.0.0.1" || ip == "1.1.1.1" {
continue continue
} }
matchesEmail := emailRegx.FindString(line) matchesEmail := emailRegx.FindString(line)
if(matchesEmail == "") { if matchesEmail == "" {
continue continue
} }
matchesEmail = ss.Split(matchesEmail, "email: ")[1] matchesEmail = ss.Split(matchesEmail, "email: ")[1]
if(InboundClientIps[matchesEmail] != nil) { if InboundClientIps[matchesEmail] != nil {
if(contains(InboundClientIps[matchesEmail],ip)){ if contains(InboundClientIps[matchesEmail], ip) {
continue continue
} }
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip) InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
} else {
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
}else{ }
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
}
} }
} }
disAllowedIps = []string{} disAllowedIps = []string{}
for clientEmail, ips := range InboundClientIps { for clientEmail, ips := range InboundClientIps {
inboundClientIps,err := GetInboundClientIps(clientEmail) inboundClientIps, err := GetInboundClientIps(clientEmail)
sort.Sort(sort.StringSlice(ips)) sort.Sort(sort.StringSlice(ips))
if(err != nil){ if err != nil {
addInboundClientIps(clientEmail,ips) addInboundClientIps(clientEmail, ips)
}else{ } else {
updateInboundClientIps(inboundClientIps,clientEmail,ips) updateInboundClientIps(inboundClientIps, clientEmail, ips)
} }
}
}
// check if inbound connection is more than limited ip and drop connection // check if inbound connection is more than limited ip and drop connection
LimitDevice := func() { LimitDevice() } LimitDevice := func() { LimitDevice() }
stop := schedule(LimitDevice, 1000 *time.Millisecond) stop := schedule(LimitDevice, 1000*time.Millisecond)
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
stop <- true stop <- true
} }
func GetAccessLogPath() string { func GetAccessLogPath() string {
config, err := os.ReadFile("bin/config.json") config, err := os.ReadFile(xray.GetConfigPath())
checkError(err) checkError(err)
jsonConfig := map[string]interface{}{} jsonConfig := map[string]interface{}{}
err = json.Unmarshal([]byte(config), &jsonConfig) err = json.Unmarshal([]byte(config), &jsonConfig)
checkError(err) checkError(err)
if(jsonConfig["log"] != nil) { if jsonConfig["log"] != nil {
jsonLog := jsonConfig["log"].(map[string]interface{}) jsonLog := jsonConfig["log"].(map[string]interface{})
if(jsonLog["access"] != nil) { if jsonLog["access"] != nil {
accessLogPath := jsonLog["access"].(string) accessLogPath := jsonLog["access"].(string)
@@ -132,7 +131,7 @@ func GetAccessLogPath() string {
} }
func checkError(e error) { func checkError(e error) {
if e != nil { if e != nil {
logger.Warning("client ip job err:", e) logger.Warning("client ip job err:", e)
} }
} }
@@ -182,20 +181,20 @@ func addInboundClientIps(clientEmail string, ips []string) error {
} }
return nil return nil
} }
func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail string,ips []string) error { func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) error {
jsonIps, err := json.Marshal(ips) jsonIps, err := json.Marshal(ips)
checkError(err) checkError(err)
inboundClientIps.ClientEmail = clientEmail inboundClientIps.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps) inboundClientIps.Ips = string(jsonIps)
// check inbound limitation // check inbound limitation
inbound, err := GetInboundByEmail(clientEmail) inbound, err := GetInboundByEmail(clientEmail)
checkError(err) checkError(err)
if inbound.Settings == "" { if inbound.Settings == "" {
logger.Debug("wrong data ",inbound) logger.Debug("wrong data ", inbound)
return nil return nil
} }
@@ -205,17 +204,17 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
for _, client := range clients { for _, client := range clients {
if client.Email == clientEmail { if client.Email == clientEmail {
limitIp := client.LimitIP limitIp := client.LimitIP
if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) { if limitIp < len(ips) && limitIp != 0 && inbound.Enable {
disAllowedIps = append(disAllowedIps,ips[limitIp:]...) disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
} }
} }
} }
logger.Debug("disAllowedIps ",disAllowedIps) logger.Debug("disAllowedIps ", disAllowedIps)
sort.Sort(sort.StringSlice(disAllowedIps)) sort.Sort(sort.StringSlice(disAllowedIps))
db := database.GetDB() db := database.GetDB()
err = db.Save(inboundClientIps).Error err = db.Save(inboundClientIps).Error
@@ -224,13 +223,13 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
} }
return nil return nil
} }
func DisableInbound(id int) error{ func DisableInbound(id int) error {
db := database.GetDB() db := database.GetDB()
result := db.Model(model.Inbound{}). result := db.Model(model.Inbound{}).
Where("id = ? and enable = ?", id, true). Where("id = ? and enable = ?", id, true).
Update("enable", false) Update("enable", false)
err := result.Error err := result.Error
logger.Warning("disable inbound with id:",id) logger.Warning("disable inbound with id:", id)
if err == nil { if err == nil {
job.xrayService.SetToNeedRestart() job.xrayService.SetToNeedRestart()
@@ -242,7 +241,7 @@ func DisableInbound(id int) error{
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) { func GetInboundByEmail(clientEmail 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 ?", "%" + clientEmail + "%").Find(&inbounds).Error err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -250,45 +249,44 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
} }
func LimitDevice() { func LimitDevice() {
var destIp, destPort, srcIp, srcPort string var destIp, destPort, srcIp, srcPort string
localIp,err := LocalIP()
checkError(err)
c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head") localIp, err := LocalIP()
checkError(err)
<-c.Start() c := cmd.NewCmd("bash", "-c", "ss --tcp | grep -E '"+IPsToRegex(localIp)+"'| awk '{if($1==\"ESTAB\") print $4,$5;}'", "| sort | uniq -c | sort -nr | head")
if len(c.Status().Stdout) > 0 {
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
for _, row := range c.Status().Stdout { <-c.Start()
if len(c.Status().Stdout) > 0 {
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
data := strings.Split(row," ") for _, row := range c.Status().Stdout {
if len(data) < 2 { data := strings.Split(row, " ")
continue // Skip this row if it doesn't have at least two elements
}
destIp = string(ipRegx.FindString(data[0])) if len(data) < 2 {
destPort = portRegx.FindString(data[0]) continue // Skip this row if it doesn't have at least two elements
destPort = strings.Replace(destPort,":","",-1) }
srcIp = string(ipRegx.FindString(data[1])) destIp = string(ipRegx.FindString(data[0]))
srcPort = portRegx.FindString(data[1]) destPort = portRegx.FindString(data[0])
srcPort = strings.Replace(srcPort,":","",-1) destPort = strings.Replace(destPort, ":", "", -1)
if contains(disAllowedIps,srcIp){ srcIp = string(ipRegx.FindString(data[1]))
dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort) srcPort = portRegx.FindString(data[1])
dropCmd.Start() srcPort = strings.Replace(srcPort, ":", "", -1)
logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort) if contains(disAllowedIps, srcIp) {
} dropCmd := cmd.NewCmd("bash", "-c", "ss -K dport = "+srcPort)
} dropCmd.Start()
}
logger.Debug("request droped : ", srcIp, srcPort, "to", destIp, destPort)
}
}
}
} }
func LocalIP() ([]string, error) { func LocalIP() ([]string, error) {
// get machine ips // get machine ips
@@ -312,24 +310,23 @@ func LocalIP() ([]string, error) {
ip = v.IP ip = v.IP
} }
ips = append(ips,ip.String()) ips = append(ips, ip.String())
} }
} }
logger.Debug("System IPs : ",ips) logger.Debug("System IPs : ", ips)
return ips, nil return ips, nil
} }
func IPsToRegex(ips []string) string {
func IPsToRegex(ips []string) (string){
regx := "" regx := ""
for _, ip := range ips { for _, ip := range ips {
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")" regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
} }
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")" regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
return regx return regx
} }

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,27 +46,21 @@
"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"
} }
] ]
}, },
"stats": {} "stats": {}
} }

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
if err != nil { }
return "", err return emails, nil
}
func (s *InboundService) contains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
} }
}
return false
}
for _, client := range clients { func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
if emails[client.Email] { allEmails, err := s.getAllEmails()
if err != nil {
return "", err
}
var emails []string
for _, client := range clients {
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)
if err != nil {
return err
}
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() db := database.GetDB()
if len(clients[index].Email) > 0 { if len(clients[0].Email) > 0 {
if len(oldClients[index].Email) > 0 { if len(oldClients[index].Email) > 0 {
err = s.UpdateClientStat(oldClients[index].Email, &clients[index]) err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
if err != nil { if err != nil {
return err return err
} }
err = s.UpdateClientIPs(db, oldClients[index].Email, clients[index].Email) 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()

View File

@@ -390,3 +390,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"
@@ -42,6 +43,15 @@ var defaultValueMap = map[string]string{
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)

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,60 @@ 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", traffic.Up, traffic.Down)
if traffic.Total > 0 {
header = header + fmt.Sprintf(";total=%d", traffic.Total)
}
if traffic.ExpiryTime > 0 {
header = header + fmt.Sprintf(";expire=%d", traffic.ExpiryTime)
}
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 +313,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 +346,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 +356,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 +490,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 +505,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 +523,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 +533,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

@@ -167,7 +167,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
case "client_traffic": case "client_traffic":
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName) t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
case "client_commands": case "client_commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Passowrd]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.") t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
case "commands": case "commands":
t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>") t.SendMsgToTgbot(callbackQuery.From.ID, "Search for a client email:\r\n<code>/usage email</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [remark]</code>")
} }

View File

@@ -41,7 +41,7 @@
"domainName" = "Domain name" "domainName" = "Domain name"
"additional" = "Alter" "additional" = "Alter"
"monitor" = "Listen IP" "monitor" = "Listen IP"
"certificate" = "Certificat" "certificate" = "Certificate"
"fail" = "Fail" "fail" = "Fail"
"success" = " Success" "success" = " Success"
"getVersion" = "Get version" "getVersion" = "Get version"
@@ -106,8 +106,8 @@
"expireDate" = "Expire date" "expireDate" = "Expire date"
"resetTraffic" = "Reset traffic" "resetTraffic" = "Reset traffic"
"addInbound" = "Add Inbound" "addInbound" = "Add Inbound"
"addTo" = "Add To" "addTo" = "Create"
"revise" = "Revise" "revise" = "Update"
"modifyInbound" = "Modify InBound" "modifyInbound" = "Modify InBound"
"deleteInbound" = "Delete Inbound" "deleteInbound" = "Delete Inbound"
"deleteInboundContent" = "Are you sure you want to delete inbound?" "deleteInboundContent" = "Are you sure you want to delete inbound?"
@@ -196,6 +196,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,24 +217,59 @@
"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 temlate 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 temlate 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 temlate 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 temlate 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 temlate 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"
"xrayConfigOutboundsDesc" = "Change the configuration temlate to define outgoing ways for this server, restart the panel to take effect" "xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server, restart the panel to take effect"
"xrayConfigRoutings" = "Configuration of Routing rules" "xrayConfigRoutings" = "Configuration of Routing rules"
"xrayConfigRoutingsDesc" = "Change the configuration temlate to define Routing rules for this server, restart the panel to take effect" "xrayConfigRoutingsDesc" = "Change the configuration template to define Routing rules for this server, restart the panel to take effect"
"telegramBotEnable" = "Enable telegram bot" "telegramBotEnable" = "Enable telegram bot"
"telegramBotEnableDesc" = "Restart the panel to take effect" "telegramBotEnableDesc" = "Restart the panel to take effect"
"telegramToken" = "Telegram Token" "telegramToken" = "Telegram Token"
@@ -257,4 +294,4 @@
"getSetting" = "Get setting" "getSetting" = "Get setting"
"modifyUser" = "Modify user" "modifyUser" = "Modify user"
"originalUserPassIncorrect" = "The original user name or original password is incorrect" "originalUserPassIncorrect" = "The original user name or original password is incorrect"
"userPassMustBeNotEmpty" = "New username and new password cannot be empty" "userPassMustBeNotEmpty" = "New username and new password cannot be empty"

View File

@@ -194,6 +194,8 @@
"save" = "ذخیره" "save" = "ذخیره"
"restartPanel" = "ریستارت پنل" "restartPanel" = "ریستارت پنل"
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید" "restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
"actions" = "عملیات ها"
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
"panelConfig" = "تنظیمات پنل" "panelConfig" = "تنظیمات پنل"
"userSetting" = "تنظیمات مدیر" "userSetting" = "تنظیمات مدیر"
"xrayConfiguration" = "تنظیمات Xray" "xrayConfiguration" = "تنظیمات Xray"
@@ -213,18 +215,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" = "تنظیمات خروجی"
@@ -255,4 +292,4 @@
"getSetting" = "دریافت تنظیمات" "getSetting" = "دریافت تنظیمات"
"modifyUser" = "ویرایش کاربر" "modifyUser" = "ویرایش کاربر"
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ." "originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ." "userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."

View File

@@ -194,6 +194,8 @@
"save" = "保存配置" "save" = "保存配置"
"restartPanel" = "重启面板" "restartPanel" = "重启面板"
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息" "restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
"actions" = "动作"
"resetDefaultConfig" = "重置为默认配置"
"panelConfig" = "面板配置" "panelConfig" = "面板配置"
"userSetting" = "用户设置" "userSetting" = "用户设置"
"xrayConfiguration" = "xray 相关设置" "xrayConfiguration" = "xray 相关设置"
@@ -213,18 +215,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" = "出站配置"
@@ -255,4 +292,4 @@
"getSetting" = "获取设置" "getSetting" = "获取设置"
"modifyUser" = "修改用户" "modifyUser" = "修改用户"
"originalUserPassIncorrect" = "原用户名或原密码错误" "originalUserPassIncorrect" = "原用户名或原密码错误"
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空" "userPassMustBeNotEmpty" = "新用户名和新密码不能为空"

213
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,21 +44,18 @@ 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
confirm() { confirm() {
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
echo && read -p "$1 [Default$2]: " temp echo && read -p "$1 [Default$2]: " temp
@@ -133,7 +130,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
@@ -150,12 +147,12 @@ reset_user() {
return 0 return 0
fi fi
/usr/local/x-ui/x-ui setting -username admin -password admin /usr/local/x-ui/x-ui setting -username admin -password admin
echo -e "Username and password have been reset to ${green}admin${plain}Please restart the panel now." echo -e "Username and password have been reset to ${green}admin${plain}, Please restart the panel now."
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 +160,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 +180,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 +189,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 +197,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 +210,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 +218,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 +234,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 +282,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
echo -e "${green}BBR is already enabled!${plain}"
exit 0
fi
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then # Check the OS and install necessary packages
echo -e "${green}BBR is already enabled!${plain}" if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
exit 0 sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
fi 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
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
sudo dnf -y update && sudo dnf -y install ca-certificates
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
sudo yum -y update && sudo yum -y install ca-certificates
else
echo "Unsupported operating system. Please check the script and install the necessary packages manually."
exit 1
fi
# Check the OS and install necessary packages # Enable BBR
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
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
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "fedora" ]]; then
sudo dnf -y update && sudo dnf -y install ca-certificates
elif [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "centos" ]]; then
sudo yum -y update && sudo yum -y install ca-certificates
else
echo "Unsupported operating system. Please check the script and install the necessary packages manually."
exit 1
fi
# Enable BBR # Apply changes
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf sudo sysctl -p
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
# Apply changes
sudo sysctl -p
# Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
echo -e "${green}BBR has been enabled successfully.${plain}"
else
echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
fi
# Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
echo -e "${green}BBR has been enabled successfully.${plain}"
else
echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
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 +354,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,69 +450,76 @@ 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
echo "ufw firewall is already installed"
fi
# Check if the firewall is inactive
if sudo ufw status | grep -q "Status: active"; then
echo "firewall is already active"
else
# Open the necessary ports
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw allow 2053/tcp
# Enable the firewall
sudo ufw --force enable
fi
# Prompt the user to enter a list of ports
read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
# Check if the input is valid
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
fi
# Open the specified ports using ufw
IFS=',' read -ra PORT_LIST <<< "$ports"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
# Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and open each port
for ((i=start_port; i<=end_port; i++)); do
sudo ufw allow $i
done
else else
sudo ufw allow "$port" echo "ufw firewall is already installed"
fi fi
done
# Confirm that the ports are open # Check if the firewall is inactive
sudo ufw status | grep $ports if sudo ufw status | grep -q "Status: active"; then
echo "firewall is already active"
else
# Open the necessary ports
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw allow 2053/tcp
# Enable the firewall
sudo ufw --force enable
fi
# Prompt the user to enter a list of ports
read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
# Check if the input is valid
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
fi
# Open the specified ports using ufw
IFS=',' read -ra PORT_LIST <<< "$ports"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
# Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and open each port
for ((i=start_port; i<=end_port; i++)); do
sudo ufw allow $i
done
else
sudo ufw allow "$port"
fi
done
# Confirm that the ports are open
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,10 +716,11 @@ ssl_cert_issue_by_cloudflare() {
show_menu show_menu
fi fi
} }
google_recaptcha() { google_recaptcha() {
curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh
echo "" echo ""
before_show_menu before_show_menu
} }
run_speedtest() { run_speedtest() {
@@ -790,7 +793,7 @@ show_menu() {
${green}12.${plain} Check x-ui Logs ${green}12.${plain} Check x-ui Logs
———————————————— ————————————————
${green}13.${plain} Enable x-ui On System Startup ${green}13.${plain} Enable x-ui On System Startup
${green}14.${plain} Disabel x-ui On System Startup ${green}14.${plain} Disable x-ui On System Startup
———————————————— ————————————————
${green}15.${plain} Enable BBR ${green}15.${plain} Enable BBR
${green}16.${plain} Apply for an SSL Certificate ${green}16.${plain} Apply for an SSL Certificate

View File

@@ -14,6 +14,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"x-ui/config"
"x-ui/util/common" "x-ui/util/common"
"github.com/Workiva/go-datastructures/queue" "github.com/Workiva/go-datastructures/queue"
@@ -29,19 +30,23 @@ func GetBinaryName() string {
} }
func GetBinaryPath() string { func GetBinaryPath() string {
return "bin/" + GetBinaryName() return config.GetBinFolderPath() + "/" + GetBinaryName()
} }
func GetConfigPath() string { func GetConfigPath() string {
return "bin/config.json" return config.GetBinFolderPath() + "/config.json"
} }
func GetGeositePath() string { func GetGeositePath() string {
return "bin/geosite.dat" return config.GetBinFolderPath() + "/geosite.dat"
} }
func GetGeoipPath() string { func GetGeoipPath() string {
return "bin/geoip.dat" return config.GetBinFolderPath() + "/geoip.dat"
}
func GetBlockedIPsPath() string {
return config.GetBinFolderPath() + "/blockedIPs"
} }
func stopProcess(p *Process) { func stopProcess(p *Process) {
@@ -162,7 +167,7 @@ func (p *process) Start() (err error) {
return common.NewErrorf("Failed to write configuration file: %v", err) return common.NewErrorf("Failed to write configuration file: %v", err)
} }
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", "./bin/blockedIPs") cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", GetBlockedIPsPath())
p.cmd = cmd p.cmd = cmd
stdReader, err := cmd.StdoutPipe() stdReader, err := cmd.StdoutPipe()