Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f7e819a9b | ||
|
|
834d003ab6 | ||
|
|
c627227893 | ||
|
|
2a8725a7a5 | ||
|
|
ca2d1bb901 | ||
|
|
fa19649286 | ||
|
|
56d75f5293 | ||
|
|
e1132a3f41 | ||
|
|
4d479102ad | ||
|
|
e90c575bfd | ||
|
|
9d02f455cc | ||
|
|
13f67f595c | ||
|
|
25741dcb08 | ||
|
|
a7af62162c | ||
|
|
3e0faecaae | ||
|
|
dc7dbae14a | ||
|
|
dd8c763b21 | ||
|
|
e0e7c102b8 | ||
|
|
f26a7df11b | ||
|
|
72a1b1e3f3 | ||
|
|
dfdb77c491 | ||
|
|
846efe8eb4 | ||
|
|
8b79b5a315 | ||
|
|
c71041a60d | ||
|
|
6865cb108c | ||
|
|
ee2089257a | ||
|
|
9f85ec72a8 | ||
|
|
c4c266205b | ||
|
|
e4afbcea3b | ||
|
|
44cede41fd | ||
|
|
672fd1da19 | ||
|
|
f3fe866af2 | ||
|
|
25e50aa6f1 | ||
|
|
ff3657e15a | ||
|
|
4af626bb4b | ||
|
|
936f2e6ec2 | ||
|
|
3e5984930e | ||
|
|
40a0297499 | ||
|
|
e13015a920 | ||
|
|
ec4755952a | ||
|
|
d8fb83b1f2 | ||
|
|
4fb060d25e | ||
|
|
3eff8022f2 | ||
|
|
19bb28cb26 | ||
|
|
b70ecc12b3 | ||
|
|
1e72a22c96 | ||
|
|
d695ea8192 | ||
|
|
b639a1bbd5 | ||
|
|
4f952963ae | ||
|
|
7f2ef94c7f | ||
|
|
81372de369 | ||
|
|
a3b170d6c4 | ||
|
|
4548755375 | ||
|
|
0c047cf124 | ||
|
|
c6b59f1ee3 | ||
|
|
b40759fe18 | ||
|
|
e6f08517a3 | ||
|
|
293787f867 | ||
|
|
54bf24f662 | ||
|
|
b4727cc5f8 | ||
|
|
b4fc5a7ab8 | ||
|
|
d0e53231fe | ||
|
|
cce4b0a449 | ||
|
|
ff90e2a02e | ||
|
|
c575425292 | ||
|
|
82b2809fcc | ||
|
|
7ceb6a3651 | ||
|
|
7ee9133e9b | ||
|
|
c4162e3eb4 | ||
|
|
591fb0abe3 | ||
|
|
ed424e09df | ||
|
|
2c7b8d1d36 | ||
|
|
de8ba29253 | ||
|
|
c38d75f3d9 | ||
|
|
4e7ad9e6de | ||
|
|
8c40e7281f | ||
|
|
54946e725e | ||
|
|
ab4b10f619 | ||
|
|
674109106d | ||
|
|
6a4302e45d | ||
|
|
7412bf17a9 | ||
|
|
519f2b462e | ||
|
|
11c781cce7 | ||
|
|
b7e10fc7c6 | ||
|
|
bacd0a6b72 | ||
|
|
87f9c7b9f8 | ||
|
|
d74b39b9cf | ||
|
|
5db35c32de | ||
|
|
abb79bd978 | ||
|
|
38c318737b | ||
|
|
c1ed6d8454 | ||
|
|
26a0481d82 | ||
|
|
63f71e527c | ||
|
|
5be9131078 |
56
.github/workflows/release.yml
vendored
@@ -1,16 +1,20 @@
|
||||
name: Release 3X-ui
|
||||
name: Release X-ui
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linuxamd64build:
|
||||
linuxamd64build:
|
||||
name: build x-ui amd64 version
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.0
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4.0.0
|
||||
with:
|
||||
go-version: 'stable'
|
||||
go-version: "stable"
|
||||
- name: build linux amd64 version
|
||||
run: |
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
||||
@@ -24,7 +28,7 @@ jobs:
|
||||
cd bin
|
||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/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/geosite.dat
|
||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
@@ -42,3 +46,45 @@ jobs:
|
||||
asset_name: x-ui-linux-amd64.tar.gz
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
linuxarm64build:
|
||||
name: build x-ui arm64 version
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4.0.0
|
||||
with:
|
||||
go-version: "stable"
|
||||
- name: build linux arm64 version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt install gcc-aarch64-linux-gnu
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o xui-release -v main.go
|
||||
mkdir x-ui
|
||||
cp xui-release x-ui/xui-release
|
||||
cp x-ui.service x-ui/x-ui.service
|
||||
cp x-ui.sh x-ui/x-ui.sh
|
||||
cd x-ui
|
||||
mv xui-release x-ui
|
||||
mkdir bin
|
||||
cd bin
|
||||
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
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/geosite.dat
|
||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
mv xray xray-linux-arm64
|
||||
cd ..
|
||||
cd ..
|
||||
- name: package
|
||||
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
|
||||
- name: upload
|
||||
uses: svenstaro/upload-release-action@2.5.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
file: x-ui-linux-arm64.tar.gz
|
||||
asset_name: x-ui-linux-arm64.tar.gz
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
|
||||
2
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.idea
|
||||
tmp
|
||||
backup/
|
||||
bin/
|
||||
dist/
|
||||
x-ui-*.tar.gz
|
||||
@@ -9,4 +10,5 @@ x-ui-*.tar.gz
|
||||
main
|
||||
release/
|
||||
access.log
|
||||
error.log
|
||||
.cache
|
||||
|
||||
113
README.md
@@ -1,13 +1,15 @@
|
||||
# 3x-ui
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](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**
|
||||
|
||||
**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)**
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
apt-get install certbot -y
|
||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||
|
||||
# Default settings
|
||||
|
||||
- Port: 2053
|
||||
@@ -36,18 +41,63 @@ certbot renew --dry-run
|
||||
- database path: /etc/x-ui/x-ui.db
|
||||
- xray config path: /usr/local/x-ui/bin/config.json
|
||||
|
||||
before you set ssl on settings
|
||||
- http:// ip or domain:2053/xui
|
||||
Before you set ssl on settings
|
||||
|
||||
- http://ip:2053/xui
|
||||
- http://domain:2053/xui
|
||||
|
||||
After you set ssl on settings
|
||||
|
||||
After you set ssl on settings
|
||||
- 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)
|
||||
- [enable traffic](./media/enable-traffic.txt)
|
||||
- [enable traffic+block all IR IP address](./media/enable-traffic+block-IR-IP.txt)
|
||||
- [enable traffic+block all IR domain](./media/enable-traffic+block-IR-domain.txt)
|
||||
|
||||
- [traffic](./media/configs/traffic.json)
|
||||
- [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
|
||||
|
||||
@@ -62,7 +112,8 @@ After you set ssl on settings
|
||||
- Support https access panel (self-provided domain name + ssl certificate)
|
||||
- Support one-click SSL certificate application and automatic renewal
|
||||
- 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
|
||||
|
||||
@@ -79,8 +130,8 @@ Set the robot-related parameters in the panel background, including:
|
||||
|
||||
Reference syntax:
|
||||
|
||||
- 30 * * * * * //Notify at the 30s of each point
|
||||
- 0 */10 * * * * //Notify at the first second of each 10 minutes
|
||||
- 30 \* \* \* \* \* //Notify at the 30s of each point
|
||||
- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
|
||||
- @hourly // hourly notification
|
||||
- @daily // Daily notification (00:00 in the morning)
|
||||
- @every 8h // notify every 8 hours
|
||||
@@ -100,16 +151,46 @@ Reference syntax:
|
||||
- Check depleted users
|
||||
- Receive backup by request and in periodic reports
|
||||
|
||||
## API routes
|
||||
|
||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||
- `/xui/API/inbounds` base for following actions:
|
||||
|
||||
| Method | Path | Action |
|
||||
| :----: | ---------------------------------- | ------------------------------------------- |
|
||||
| `GET` | `"/list"` | Get all inbounds |
|
||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||
| `POST` | `"/add"` | Add inbound |
|
||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||
| `POST` | `"/update/:id"` | Update Inbound |
|
||||
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||
| `POST` | `"/addClient/"` | Add Client to inbound |
|
||||
| `POST` | `"/delClient/:email"` | Delete Client |
|
||||
| `POST` | `"/updateClient/:index"` | Update Client |
|
||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||
|
||||
# A Special Thanks To
|
||||
|
||||
- [alireza0](https://github.com/alireza0/)
|
||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||
|
||||
# Suggestion System
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Debian 10+
|
||||
- CentOS 8+
|
||||
- Fedora 36+
|
||||
|
||||
# Buy Me a Coffee
|
||||
|
||||
[](#)
|
||||
|
||||
```
|
||||
TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC
|
||||
```
|
||||
|
||||
# Pictures
|
||||
|
||||

|
||||
|
||||
@@ -45,6 +45,22 @@ func IsDebug() bool {
|
||||
return os.Getenv("XUI_DEBUG") == "true"
|
||||
}
|
||||
|
||||
func GetDBPath() string {
|
||||
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName())
|
||||
func GetBinFolderPath() string {
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.1.5
|
||||
1.2.6
|
||||
|
||||
@@ -92,7 +92,7 @@ func InitDB(dbPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ type Client struct {
|
||||
LimitIP int `json:"limitIp"`
|
||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
Enable bool `json:"enable" from:"enable"`
|
||||
TgID string `json:"tgId" from:"tgId"`
|
||||
SubID string `json:"subId" from:"subId"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
TgID string `json:"tgId" form:"tgId"`
|
||||
SubID string `json:"subId" form:"subId"`
|
||||
}
|
||||
|
||||
36
go.mod
@@ -8,29 +8,29 @@ require (
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-cmd/cmd v1.4.1
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.0.7
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
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
|
||||
golang.org/x/text v0.9.0
|
||||
google.golang.org/grpc v1.54.0
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
gorm.io/gorm v1.24.6
|
||||
gorm.io/driver/sqlite v1.5.0
|
||||
gorm.io/gorm v1.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.2 // indirect
|
||||
github.com/bytedance/sonic v1.8.7 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.12.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
@@ -39,25 +39,25 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/leodido/go-urn v1.2.3 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.4 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.10 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/arch v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||
google.golang.org/protobuf v1.29.1 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
109
go.sum
@@ -9,8 +9,8 @@ github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY=
|
||||
github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
|
||||
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
@@ -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/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
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/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||
@@ -41,14 +42,14 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
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-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/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@@ -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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
@@ -73,34 +74,31 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
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/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.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
|
||||
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE=
|
||||
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -109,15 +107,15 @@ 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/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/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/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
||||
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -125,22 +123,22 @@ 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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
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-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
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/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/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/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
||||
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
||||
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
||||
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/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/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||
github.com/shoenig/go-m1cpu v0.1.4 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
|
||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -148,7 +146,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@@ -165,12 +162,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
|
||||
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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/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/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k=
|
||||
github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI=
|
||||
github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
||||
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
||||
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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
@@ -179,25 +176,25 @@ go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfG
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY=
|
||||
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
|
||||
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/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.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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -213,10 +210,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -231,21 +228,21 @@ 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-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.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-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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM=
|
||||
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -253,11 +250,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
||||
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
32
install.sh
@@ -66,33 +66,17 @@ else
|
||||
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||
fi
|
||||
|
||||
# This function installs the base packages required for most scripts
|
||||
install_base() {
|
||||
# Store the package names in a variable for easy modification
|
||||
local packages="wget curl tar"
|
||||
|
||||
# Check for the package managers and install the packages if they are not already installed
|
||||
if ! command -v wget >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1; then
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
apt-get update && apt-get install -y $packages
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y $packages
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
yum install -y $packages
|
||||
else
|
||||
echo "ERROR: No package managers found. Please install wget, curl, and tar manually."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Print a confirmation message after the installation is complete
|
||||
echo "The following packages have been successfully installed: $packages"
|
||||
else
|
||||
# Print a message confirming that the packages are already installed
|
||||
echo "The following packages are already installed: $packages"
|
||||
fi
|
||||
case "${release}" in
|
||||
centos|fedora)
|
||||
yum install -y -q wget curl tar
|
||||
;;
|
||||
*)
|
||||
apt install -y -q wget curl tar
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
#This function will be called when user installed x-ui out of sercurity
|
||||
config_after_install() {
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
|
||||
12
main.go
@@ -97,7 +97,7 @@ func showSetting(show bool) {
|
||||
settingService := service.SettingService{}
|
||||
port, err := settingService.GetPort()
|
||||
if err != nil {
|
||||
fmt.Println("get current port fialed,error info:", err)
|
||||
fmt.Println("get current port failed,error info:", err)
|
||||
}
|
||||
userService := service.UserService{}
|
||||
userModel, err := userService.GetFirstUser()
|
||||
@@ -109,7 +109,7 @@ func showSetting(show bool) {
|
||||
if (username == "") || (userpasswd == "") {
|
||||
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("userpasswd:", userpasswd)
|
||||
fmt.Println("port:", port)
|
||||
@@ -217,7 +217,7 @@ func main() {
|
||||
|
||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||
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)
|
||||
var port int
|
||||
@@ -234,9 +234,9 @@ func main() {
|
||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token")
|
||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time")
|
||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegrame bot chat id")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
|
||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
|
||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
|
||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
||||
|
||||
oldUsage := flag.Usage
|
||||
|
||||
BIN
media/1.png
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 63 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 59 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 157 KiB |
88
media/configs/traffic+block-ads+ipv4-google.json
Normal 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": {}
|
||||
}
|
||||
98
media/configs/traffic+block-ads+warp.json
Normal 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": {}
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "warning",
|
||||
"access": "./access.log"
|
||||
"access": "./access.log",
|
||||
"error": "./error.log"
|
||||
},
|
||||
"api": {
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
],
|
||||
"tag": "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"
|
||||
},
|
||||
"tag": "api"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@@ -28,16 +25,16 @@
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {},
|
||||
"tag": "blocked"
|
||||
"settings": {}
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
"statsUserDownlink": true,
|
||||
"statsUserUplink": true
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
@@ -49,36 +46,31 @@
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"inboundTag": [
|
||||
"api"
|
||||
],
|
||||
"outboundTag": "api",
|
||||
"type": "field"
|
||||
"type": "field",
|
||||
"inboundTag": ["api"],
|
||||
"outboundTag": "api"
|
||||
},
|
||||
{
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
],
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"type": "field"
|
||||
"ip": ["geoip:private"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
],
|
||||
"type": "field"
|
||||
"protocol": ["bittorrent"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"domain": [
|
||||
"regexp:.+.ir$",
|
||||
"regexp:.*\\.ir$",
|
||||
"ext:iran.dat:ir",
|
||||
"ext:iran.dat:other"
|
||||
],
|
||||
"type": "field"
|
||||
"ext:iran.dat:other",
|
||||
"geosite:category-ir"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "warning",
|
||||
"access": "./access.log"
|
||||
"access": "./access.log",
|
||||
"error": "./error.log"
|
||||
},
|
||||
"api": {
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
],
|
||||
"tag": "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"
|
||||
},
|
||||
"tag": "api"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@@ -28,16 +25,16 @@
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {},
|
||||
"tag": "blocked"
|
||||
"settings": {}
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
"statsUserDownlink": true,
|
||||
"statsUserUplink": true
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
@@ -49,34 +46,31 @@
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"inboundTag": [
|
||||
"api"
|
||||
],
|
||||
"outboundTag": "api",
|
||||
"type": "field"
|
||||
"type": "field",
|
||||
"inboundTag": ["api"],
|
||||
"outboundTag": "api"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
],
|
||||
"type": "field"
|
||||
"ip": ["geoip:private"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
],
|
||||
"type": "field"
|
||||
"protocol": ["bittorrent"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"ip": [
|
||||
"geoip:ir"
|
||||
],
|
||||
"type": "field"
|
||||
"ip": ["geoip:private"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"ip": ["geoip:ir"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "warning",
|
||||
"access": "./access.log"
|
||||
"access": "./access.log",
|
||||
"error": "./error.log"
|
||||
},
|
||||
"api": {
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
],
|
||||
"tag": "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"
|
||||
},
|
||||
"tag": "api"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@@ -28,16 +25,16 @@
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {},
|
||||
"tag": "blocked"
|
||||
"settings": {}
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
"statsUserDownlink": true,
|
||||
"statsUserUplink": true
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
@@ -49,27 +46,21 @@
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"inboundTag": [
|
||||
"api"
|
||||
],
|
||||
"outboundTag": "api",
|
||||
"type": "field"
|
||||
"type": "field",
|
||||
"inboundTag": ["api"],
|
||||
"outboundTag": "api"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
],
|
||||
"type": "field"
|
||||
"ip": ["geoip:private"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
],
|
||||
"type": "field"
|
||||
"protocol": ["bittorrent"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +246,11 @@
|
||||
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 {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #161b22;
|
||||
|
||||
@@ -49,6 +49,7 @@ const XTLS_FLOW_CONTROL = {
|
||||
|
||||
const TLS_FLOW_CONTROL = {
|
||||
VISION: "xtls-rprx-vision",
|
||||
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||
};
|
||||
|
||||
const TLS_VERSION_OPTION = {
|
||||
@@ -106,7 +107,6 @@ Object.freeze(XTLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_VERSION_OPTION);
|
||||
Object.freeze(TLS_CIPHER_OPTION);
|
||||
Object.freeze(UTLS_FINGERPRINT);
|
||||
Object.freeze(ALPN_OPTION);
|
||||
|
||||
class XrayCommonClass {
|
||||
@@ -177,7 +177,6 @@ class TcpStreamSettings extends XrayCommonClass {
|
||||
this.type = type;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
@@ -185,9 +184,8 @@ class TcpStreamSettings extends XrayCommonClass {
|
||||
if (!header) {
|
||||
header = {};
|
||||
}
|
||||
return new TcpStreamSettings(
|
||||
return new TcpStreamSettings(json.acceptProxyProtocol,
|
||||
header.type,
|
||||
json.acceptProxyProtocol,
|
||||
TcpStreamSettings.TcpRequest.fromJson(header.request),
|
||||
TcpStreamSettings.TcpResponse.fromJson(header.response),
|
||||
);
|
||||
@@ -201,7 +199,6 @@ class TcpStreamSettings extends XrayCommonClass {
|
||||
request: this.type === 'http' ? this.request.toJson() : undefined,
|
||||
response: this.type === 'http' ? this.response.toJson() : undefined,
|
||||
},
|
||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -481,8 +478,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||
cipherSuites = '',
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
alpn=[],
|
||||
settings=[new TlsStreamSettings.Settings()]) {
|
||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
settings=new TlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.server = serverName;
|
||||
this.minVersion = minVersion;
|
||||
@@ -509,8 +506,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
let values = json.settings[0];
|
||||
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
||||
}
|
||||
return new TlsStreamSettings(
|
||||
json.serverName,
|
||||
@@ -531,7 +527,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
cipherSuites: this.cipherSuites,
|
||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||
alpn: this.alpn,
|
||||
settings: TlsStreamSettings.toJsonArray(this.settings),
|
||||
settings: this.settings,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -599,22 +595,218 @@ 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 {
|
||||
|
||||
constructor(
|
||||
show = false,xver = 0,
|
||||
dest = 'yahoo.com:443',
|
||||
serverNames = 'yahoo.com,www.yahoo.com',
|
||||
privateKey = '',
|
||||
minClient = '',
|
||||
maxClient = '',
|
||||
maxTimediff = 0,
|
||||
shortIds = RandomUtil.randowShortId(),
|
||||
settings= new RealityStreamSettings.Settings()
|
||||
){
|
||||
super();
|
||||
this.show = show;
|
||||
this.xver = xver;
|
||||
this.dest = dest;
|
||||
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
||||
this.privateKey = privateKey;
|
||||
this.minClient = minClient;
|
||||
this.maxClient = maxClient;
|
||||
this.maxTimediff = maxTimediff;
|
||||
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);
|
||||
}
|
||||
return new RealityStreamSettings(
|
||||
json.show,
|
||||
json.xver,
|
||||
json.dest,
|
||||
json.serverNames,
|
||||
json.privateKey,
|
||||
json.minClient,
|
||||
json.maxClient,
|
||||
json.maxTimediff,
|
||||
json.shortIds,
|
||||
json.settings,
|
||||
);
|
||||
|
||||
}
|
||||
toJson() {
|
||||
return {
|
||||
show: this.show,
|
||||
xver: this.xver,
|
||||
dest: this.dest,
|
||||
serverNames: this.serverNames.split(","),
|
||||
privateKey: this.privateKey,
|
||||
minClient: this.minClient,
|
||||
maxClient: this.maxClient,
|
||||
maxTimediff: this.maxTimediff,
|
||||
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 {
|
||||
constructor(network='tcp',
|
||||
security='none',
|
||||
tlsSettings=new TlsStreamSettings(),
|
||||
tcpSettings=new TcpStreamSettings(),
|
||||
kcpSettings=new KcpStreamSettings(),
|
||||
wsSettings=new WsStreamSettings(),
|
||||
httpSettings=new HttpStreamSettings(),
|
||||
quicSettings=new QuicStreamSettings(),
|
||||
grpcSettings=new GrpcStreamSettings(),
|
||||
) {
|
||||
security='none',
|
||||
tlsSettings=new TlsStreamSettings(),
|
||||
xtlsSettings=new XtlsStreamSettings(),
|
||||
realitySettings = new RealityStreamSettings(),
|
||||
tcpSettings=new TcpStreamSettings(),
|
||||
kcpSettings=new KcpStreamSettings(),
|
||||
wsSettings=new WsStreamSettings(),
|
||||
httpSettings=new HttpStreamSettings(),
|
||||
quicSettings=new QuicStreamSettings(),
|
||||
grpcSettings=new GrpcStreamSettings(),
|
||||
) {
|
||||
super();
|
||||
this.network = network;
|
||||
this.security = security;
|
||||
this.tls = tlsSettings;
|
||||
this.xtls = xtlsSettings;
|
||||
this.reality = realitySettings;
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
this.ws = wsSettings;
|
||||
@@ -624,7 +816,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
get isTls() {
|
||||
return this.security === 'tls';
|
||||
return this.security === "tls";
|
||||
}
|
||||
|
||||
set isTls(isTls) {
|
||||
@@ -635,29 +827,39 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get isXTLS() {
|
||||
get isXtls() {
|
||||
return this.security === "xtls";
|
||||
}
|
||||
|
||||
set isXTLS(isXTLS) {
|
||||
if (isXTLS) {
|
||||
set isXtls(isXtls) {
|
||||
if (isXtls) {
|
||||
this.security = 'xtls';
|
||||
} else {
|
||||
this.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
let tls;
|
||||
if (json.security === "xtls") {
|
||||
tls = TlsStreamSettings.fromJson(json.XTLSSettings);
|
||||
//for Reality
|
||||
get isReality() {
|
||||
return this.security === "reality";
|
||||
}
|
||||
|
||||
set isReality(isReality) {
|
||||
if (isReality) {
|
||||
this.security = 'reality';
|
||||
} else {
|
||||
tls = TlsStreamSettings.fromJson(json.tlsSettings);
|
||||
this.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
|
||||
return new StreamSettings(
|
||||
json.network,
|
||||
json.security,
|
||||
tls,
|
||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
||||
RealityStreamSettings.fromJson(json.realitySettings),
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
WsStreamSettings.fromJson(json.wsSettings),
|
||||
@@ -673,7 +875,8 @@ class StreamSettings extends XrayCommonClass {
|
||||
network: network,
|
||||
security: this.security,
|
||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||
XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined,
|
||||
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||
@@ -753,18 +956,31 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get XTLS() {
|
||||
get xtls() {
|
||||
return this.stream.security === 'xtls';
|
||||
}
|
||||
|
||||
set XTLS(isXTLS) {
|
||||
if (isXTLS) {
|
||||
set xtls(isXtls) {
|
||||
if (isXtls) {
|
||||
this.stream.security = 'xtls';
|
||||
} else {
|
||||
this.stream.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
//for Reality
|
||||
get reality() {
|
||||
return this.stream.security === 'reality';
|
||||
}
|
||||
|
||||
set reality(isReality) {
|
||||
if (isReality) {
|
||||
this.stream.security = 'reality';
|
||||
} else {
|
||||
this.stream.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
get network() {
|
||||
return this.stream.network;
|
||||
}
|
||||
@@ -868,7 +1084,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
|
||||
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 "";
|
||||
@@ -961,9 +1177,27 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
canEnableReality() {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VLESS:
|
||||
case Protocols.TROJAN:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
switch (this.network) {
|
||||
case "tcp":
|
||||
case "http":
|
||||
case "grpc":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
canEnableTlsFlow() {
|
||||
if ((this.stream.security === 'tls') && (this.network === "tcp")) {
|
||||
if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VLESS:
|
||||
return true;
|
||||
@@ -978,7 +1212,7 @@ class Inbound extends XrayCommonClass {
|
||||
return this.canEnableTls();
|
||||
}
|
||||
|
||||
canEnableXTLS() {
|
||||
canEnableXtls() {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VLESS:
|
||||
case Protocols.TROJAN:
|
||||
@@ -994,6 +1228,7 @@ class Inbound extends XrayCommonClass {
|
||||
case Protocols.VMESS:
|
||||
case Protocols.VLESS:
|
||||
case Protocols.TROJAN:
|
||||
case Protocols.SHADOWSOCKS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -1082,10 +1317,10 @@ class Inbound extends XrayCommonClass {
|
||||
host: host,
|
||||
path: path,
|
||||
tls: this.stream.security,
|
||||
sni: this.stream.tls.settings[0]['serverName'],
|
||||
fp: this.stream.tls.settings[0]['fingerprint'],
|
||||
sni: this.stream.tls.settings.serverName,
|
||||
fp: this.stream.tls.settings.fingerprint,
|
||||
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));
|
||||
}
|
||||
@@ -1144,34 +1379,54 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
if (this.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);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
if(this.stream.tls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||
if (this.stream.tls.settings.serverName !== ''){
|
||||
params.set("sni", this.stream.tls.settings.serverName);
|
||||
}
|
||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.XTLS) {
|
||||
if (this.xtls) {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.tls.alpn);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
params.set("alpn", this.stream.xtls.alpn);
|
||||
if(this.stream.xtls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||
address = this.stream.xtls.server;
|
||||
}
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
|
||||
if (this.reality) {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||
}
|
||||
if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
if (this.stream.reality.shortIds.length > 0) {
|
||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.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 url = new URL(link);
|
||||
for (const [key, value] of params) {
|
||||
@@ -1243,27 +1498,47 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
if (this.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);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
if(this.stream.tls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||
if (this.stream.tls.settings.serverName !== ''){
|
||||
params.set("sni", this.stream.tls.settings.serverName);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.XTLS) {
|
||||
if (this.reality) {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||
address = this.stream.reality.settings.serverName;
|
||||
}
|
||||
if (this.stream.reality.shortIds.length > 0) {
|
||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.fingerprint)) {
|
||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||
address = this.stream.reality.settings.serverName;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.xtls) {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.tls.alpn);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
params.set("alpn", this.stream.xtls.alpn);
|
||||
if(this.stream.xtls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -1513,7 +1788,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
toJson() {
|
||||
return {
|
||||
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
||||
decryption: this.decryption,
|
||||
decryption: 'none',
|
||||
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
|
||||
};
|
||||
}
|
||||
@@ -1573,7 +1848,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
}
|
||||
};
|
||||
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
||||
constructor(name="", alpn=[], path='', dest='', xver=0) {
|
||||
constructor(name="", alpn='', path='', dest='', xver=0) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.alpn = alpn;
|
||||
@@ -1712,7 +1987,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
};
|
||||
|
||||
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
||||
constructor(name="", alpn=[], path='', dest='', xver=0) {
|
||||
constructor(name="", alpn='', path='', dest='', xver=0) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.alpn = alpn;
|
||||
|
||||
@@ -89,6 +89,11 @@ const seq = [
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z'
|
||||
];
|
||||
|
||||
const shortIdSeq = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
];
|
||||
|
||||
class RandomUtil {
|
||||
|
||||
static randomIntRange(min, max) {
|
||||
@@ -107,6 +112,14 @@ class RandomUtil {
|
||||
return str;
|
||||
}
|
||||
|
||||
static randomShortIdSeq(count) {
|
||||
let str = '';
|
||||
for (let i = 0; i < count; ++i) {
|
||||
str += shortIdSeq[this.randomInt(16)];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static randomLowerAndNum(count) {
|
||||
let str = '';
|
||||
for (let i = 0; i < count; ++i) {
|
||||
@@ -146,6 +159,12 @@ class RandomUtil {
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
static randowShortId() {
|
||||
let str = '';
|
||||
str += this.randomShortIdSeq(8)
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectUtil {
|
||||
|
||||
@@ -17,7 +17,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/xui/API/inbounds")
|
||||
g.Use(a.checkLogin)
|
||||
|
||||
g.POST("/list", a.getAllInbounds)
|
||||
g.GET("/list", a.getAllInbounds)
|
||||
g.GET("/get/:id", a.getSingleInbound)
|
||||
g.POST("/add", a.addInbound)
|
||||
g.POST("/del/:id", a.delInbound)
|
||||
|
||||
@@ -33,7 +33,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/update/:id", a.updateInbound)
|
||||
g.POST("/clientIps/:email", a.getClientIps)
|
||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||
g.POST("/addClient/", a.addInboundClient)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/delClient/:email", a.delInboundClient)
|
||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
@@ -151,19 +151,19 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
||||
jsonMsg(c, "Log Cleared", nil)
|
||||
}
|
||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
inbound := &model.Inbound{}
|
||||
err := c.ShouldBind(inbound)
|
||||
data := &model.Inbound{}
|
||||
err := c.ShouldBind(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.inboundService.AddInboundClient(inbound)
|
||||
err = a.inboundService.AddInboundClient(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client added", nil)
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/restartXrayService", a.restartXrayService)
|
||||
g.POST("/installXray/:version", a.installXray)
|
||||
g.POST("/logs/:count", a.getLogs)
|
||||
g.POST("/getConfigJson", a.getConfigJson)
|
||||
g.GET("/getDb", a.getDb)
|
||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||
}
|
||||
|
||||
func (a *ServerController) refreshStatus() {
|
||||
@@ -112,8 +115,40 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||
count := c.Param("count")
|
||||
logs, err := a.serverService.GetLogs(count)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
||||
jsonMsg(c, "getLogs", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, logs, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||
configJson, err := a.serverService.GetConfigJson()
|
||||
if err != nil {
|
||||
jsonMsg(c, "get config.json", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, configJson, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getDb(c *gin.Context) {
|
||||
db, err := a.serverService.GetDb()
|
||||
if err != nil {
|
||||
jsonMsg(c, "get Database", err)
|
||||
return
|
||||
}
|
||||
// Set the headers for the response
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename=x-ui.db")
|
||||
|
||||
// Write the file contents to the response
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/update", a.updateSetting)
|
||||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
|
||||
}
|
||||
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
@@ -48,6 +49,15 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
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) {
|
||||
expireDiff, err := a.settingService.GetExpireDiff()
|
||||
if err != nil {
|
||||
|
||||
@@ -29,14 +29,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||
func (a *SUBController) subs(c *gin.Context) {
|
||||
subId := c.Param("subid")
|
||||
host := strings.Split(c.Request.Host, ":")[0]
|
||||
subs, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil {
|
||||
subs, header, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
result := ""
|
||||
for _, sub := range subs {
|
||||
result += sub + "\n"
|
||||
}
|
||||
|
||||
// Add subscription-userinfo
|
||||
c.Writer.Header().Set("subscription-userinfo", header)
|
||||
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,30 @@
|
||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</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-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||
</a-form-item>
|
||||
@@ -51,10 +75,10 @@
|
||||
</span>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
</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-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-form-item>
|
||||
<a-form-item v-else>
|
||||
@@ -83,9 +107,9 @@
|
||||
confirm: null,
|
||||
dbInbound: new DBInbound(),
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
quantity: 1,
|
||||
totalGB: 0,
|
||||
limitIp: 0,
|
||||
expiryTime: '',
|
||||
emailMethod: 0,
|
||||
firstNum: 1,
|
||||
@@ -94,8 +118,10 @@
|
||||
emailPostfix: "",
|
||||
subId: "",
|
||||
tgId: "",
|
||||
flow: "",
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
clients = [];
|
||||
method=clientsBulkModal.emailMethod;
|
||||
if(method>1){
|
||||
start=clientsBulkModal.firstNum;
|
||||
@@ -113,11 +139,18 @@
|
||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||
newClient.subId = clientsBulkModal.subId;
|
||||
newClient.tgId = clientsBulkModal.tgId;
|
||||
newClient.limitIp = clientsBulkModal.limitIp;
|
||||
newClient._totalGB = clientsBulkModal.totalGB;
|
||||
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)=>{} }) {
|
||||
this.visible = true;
|
||||
@@ -128,15 +161,16 @@
|
||||
this.totalGB = 0;
|
||||
this.expiryTime = 0;
|
||||
this.emailMethod= 0;
|
||||
this.limitIp= 0;
|
||||
this.firstNum= 1;
|
||||
this.lastNum= 1;
|
||||
this.emailPrefix= "";
|
||||
this.emailPostfix= "";
|
||||
this.subId= "";
|
||||
this.tgId= "";
|
||||
this.flow= "";
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||
this.delayedStart = false;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
confirmLoading: false,
|
||||
title: '',
|
||||
okText: '',
|
||||
isEdit: false,
|
||||
dbInbound: new DBInbound(),
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
@@ -21,9 +22,13 @@
|
||||
isExpired: false,
|
||||
delayedStart: false,
|
||||
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.title = title;
|
||||
this.okText = okText;
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</a-textarea>
|
||||
</a-form>
|
||||
</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-option value="">{{ i18n "none" }}</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>
|
||||
</template>
|
||||
</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-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-form-item>
|
||||
<a-form-item v-else>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/trojan"}}
|
||||
<a-form layout="inline">
|
||||
<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-item>
|
||||
<span slot="label">
|
||||
@@ -31,8 +31,8 @@
|
||||
</span>
|
||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.XTLS" label="Flow">
|
||||
<a-select v-model="client.flow" style="width: 150px">
|
||||
<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-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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/vless"}}
|
||||
<a-form layout="inline">
|
||||
<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-item>
|
||||
<span slot="label">
|
||||
@@ -20,25 +20,25 @@
|
||||
<a-input v-model.trim="client.id" style="width: 300px;" ></a-input>
|
||||
</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="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||
<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="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.XTLS" label="Flow">
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px">
|
||||
<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-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>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px">
|
||||
<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 v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
@@ -66,8 +66,8 @@
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/vmess"}}
|
||||
<a-form layout="inline">
|
||||
<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-item>
|
||||
<span slot="label">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<a-select-option value="tcp">TCP</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="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="grpc">gRPC</a-select-option>
|
||||
</a-select>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a-form-item label="AcceptProxyProtocol">
|
||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="HTTP Camouflage">
|
||||
<a-form-item label="HTTP {{ i18n "camouflage" }}">
|
||||
<a-switch
|
||||
:checked="inbound.stream.tcp.type === 'http'"
|
||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
{{define "form/tlsSettings"}}
|
||||
<!-- tls enable -->
|
||||
<a-form layout="inline" v-if="inbound.canSetTls()">
|
||||
<a-form-item label="TLS">
|
||||
<a-form-item v-if="inbound.canEnableTls()" label="TLS">
|
||||
<a-switch v-model="inbound.tls">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.canEnableXTLS()" label="XTLS">
|
||||
<a-switch v-model="inbound.XTLS"></a-switch>
|
||||
<a-form-item v-if="inbound.canEnableReality()">
|
||||
<span slot="label">
|
||||
Reality
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.Realitydec" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inbound.reality"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.canEnableXtls()">
|
||||
<span slot="label">
|
||||
XTLS
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.XTLSdec" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inbound.xtls"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tls settings -->
|
||||
<a-form v-if="inbound.tls || inbound.XTLS" layout="inline">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
||||
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
||||
<a-form v-if="inbound.tls" layout="inline">
|
||||
<a-form-item label='{{ i18n "domainName" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="CipherSuites">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
||||
@@ -31,22 +52,22 @@
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS" v-if="inbound.tls" >
|
||||
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<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 v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</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-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-group>
|
||||
</a-form-item>
|
||||
<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 label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
||||
@@ -72,4 +93,79 @@
|
||||
</a-form-item>
|
||||
</template>
|
||||
</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-item label="Show">
|
||||
<a-switch v-model="inbound.stream.reality.show">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="xVer">
|
||||
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS" >
|
||||
<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>
|
||||
</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 label="dest">
|
||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Server Names">
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="ShortIds">
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
||||
</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>
|
||||
{{end}}
|
||||
@@ -53,6 +53,10 @@
|
||||
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>
|
||||
</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>
|
||||
</tr>
|
||||
@@ -240,7 +244,7 @@
|
||||
return infoModal.dbInbound.isEnable;
|
||||
},
|
||||
get subBase() {
|
||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + "/sub/";
|
||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
|
||||
},
|
||||
get tgBase() {
|
||||
return "https://t.me/"
|
||||
|
||||
@@ -43,6 +43,14 @@
|
||||
loading(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 = {
|
||||
@@ -62,6 +70,7 @@
|
||||
inModal: inModal,
|
||||
Protocols: protocols,
|
||||
SSMethods: SSMethods,
|
||||
delayedStart: false,
|
||||
get inbound() {
|
||||
return inModal.inbound;
|
||||
},
|
||||
@@ -70,36 +79,40 @@
|
||||
},
|
||||
get 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: {
|
||||
streamNetworkChange(oldValue) {
|
||||
if (oldValue === 'kcp') {
|
||||
this.inModal.inbound.tls = false;
|
||||
streamNetworkChange() {
|
||||
if (!inModal.inbound.canSetTls()) {
|
||||
this.inModal.inbound.stream.security = 'none';
|
||||
}
|
||||
},
|
||||
addClient(protocol, clients) {
|
||||
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;
|
||||
if (!inModal.inbound.canEnableReality()) {
|
||||
this.inModal.inbound.reality = false;
|
||||
}
|
||||
},
|
||||
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(){
|
||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||
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) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
|
||||
@@ -133,25 +133,26 @@
|
||||
<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;" 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>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="clients" slot-scope="text, dbInbound">
|
||||
<template v-if="clientCount[dbInbound.id]">
|
||||
<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">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<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 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">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||
</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-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">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
@@ -239,7 +240,7 @@
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.protocol" }}',
|
||||
align: 'left',
|
||||
width: 70,
|
||||
width: 80,
|
||||
scopedSlots: { customRender: 'protocol' },
|
||||
}, {
|
||||
title: '{{ i18n "clients" }}',
|
||||
@@ -262,7 +263,7 @@
|
||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 70, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'UID', width: 120, dataIndex: "id" },
|
||||
];
|
||||
@@ -271,9 +272,9 @@
|
||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 70, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'Password', width: 100, dataIndex: "password" },
|
||||
{ title: 'Password', width: 120, dataIndex: "password" },
|
||||
];
|
||||
|
||||
const app = new Vue({
|
||||
@@ -530,9 +531,9 @@
|
||||
title: '{{ i18n "pages.client.add"}}',
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound, index) => {
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientModal.loading();
|
||||
await this.addClient(inbound, dbInbound);
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
@@ -544,9 +545,9 @@
|
||||
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
||||
okText: '{{ i18n "pages.client.bulk"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound) => {
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientsBulkModal.loading();
|
||||
await this.addClient(inbound, dbInbound);
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientsBulkModal.close();
|
||||
},
|
||||
});
|
||||
@@ -560,9 +561,9 @@
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
dbInbound: dbInbound,
|
||||
index: index,
|
||||
confirm: async (inbound, dbInbound, index) => {
|
||||
confirm: async (client, dbInboundId, index) => {
|
||||
clientModal.loading();
|
||||
await this.updateClient(inbound, dbInbound, index);
|
||||
await this.updateClient(client, dbInboundId, index);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
@@ -572,17 +573,17 @@
|
||||
firstKey = Object.keys(client)[0];
|
||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||
},
|
||||
async addClient(inbound, dbInbound) {
|
||||
async addClient(clients, dbInboundId) {
|
||||
const data = {
|
||||
id: dbInbound.id,
|
||||
settings: inbound.settings.toString(),
|
||||
id: dbInboundId,
|
||||
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 = {
|
||||
id: dbInbound.id,
|
||||
settings: inbound.settings.toString(),
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() +']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
||||
},
|
||||
@@ -657,8 +658,8 @@
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||
index = this.findIndexOfClient(clients, client);
|
||||
clients[index].enable = ! clients[index].enable
|
||||
await this.updateClient(inbound, dbInbound, index);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
await this.updateClient(clients[index],dbInboundId, index);
|
||||
this.loading(false);
|
||||
},
|
||||
async submit(url, data) {
|
||||
|
||||
@@ -76,18 +76,9 @@
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "pages.index.xrayStatus" }}:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.state === State.Error">
|
||||
<template slot="title">
|
||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||
Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
@@ -102,6 +93,29 @@
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "pages.index.xrayStatus" }}:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.state === State.Error">
|
||||
<template slot="title">
|
||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "menu.link" }}:
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
@@ -169,13 +183,6 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">Telegram</a-tag></a>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</transition>
|
||||
@@ -226,6 +233,7 @@
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
|
||||
const State = {
|
||||
@@ -406,6 +414,18 @@
|
||||
return;
|
||||
}
|
||||
logModal.show(msg.obj,rows);
|
||||
},
|
||||
async openConfig(){
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/getConfigJson');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
|
||||
},
|
||||
getBackup(){
|
||||
window.location = basePath + 'server/getDb';
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"regexp"
|
||||
ss "strings"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"os"
|
||||
ss "strings"
|
||||
"regexp"
|
||||
"encoding/json"
|
||||
// "strconv"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
// "strconv"
|
||||
"github.com/go-cmd/cmd"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"net"
|
||||
"github.com/go-cmd/cmd"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type CheckClientIpJob struct {
|
||||
xrayService service.XrayService
|
||||
inboundService service.InboundService
|
||||
}
|
||||
|
||||
var job *CheckClientIpJob
|
||||
var disAllowedIps []string
|
||||
var disAllowedIps []string
|
||||
|
||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||
job = new(CheckClientIpJob)
|
||||
@@ -34,94 +36,91 @@ func (j *CheckClientIpJob) Run() {
|
||||
processLogFile()
|
||||
|
||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
||||
blockedIps := []byte(ss.Join(disAllowedIps,","))
|
||||
err := os.WriteFile("./bin/blockedIPs", blockedIps, 0755)
|
||||
blockedIps := []byte(ss.Join(disAllowedIps, ","))
|
||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
||||
checkError(err)
|
||||
|
||||
}
|
||||
|
||||
func processLogFile() {
|
||||
accessLogPath := GetAccessLogPath()
|
||||
if(accessLogPath == "") {
|
||||
if accessLogPath == "" {
|
||||
logger.Warning("xray log not init in config.json")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(accessLogPath)
|
||||
data, err := os.ReadFile(accessLogPath)
|
||||
InboundClientIps := make(map[string][]string)
|
||||
checkError(err)
|
||||
checkError(err)
|
||||
|
||||
// clean log
|
||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
||||
checkError(err)
|
||||
}
|
||||
|
||||
|
||||
lines := ss.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||
|
||||
matchesIp := ipRegx.FindString(line)
|
||||
if(len(matchesIp) > 0) {
|
||||
if len(matchesIp) > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
matchesEmail := emailRegx.FindString(line)
|
||||
if(matchesEmail == "") {
|
||||
if matchesEmail == "" {
|
||||
continue
|
||||
}
|
||||
matchesEmail = ss.Split(matchesEmail, "email: ")[1]
|
||||
|
||||
if(InboundClientIps[matchesEmail] != nil) {
|
||||
if(contains(InboundClientIps[matchesEmail],ip)){
|
||||
|
||||
if InboundClientIps[matchesEmail] != nil {
|
||||
if contains(InboundClientIps[matchesEmail], ip) {
|
||||
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{}
|
||||
|
||||
for clientEmail, ips := range InboundClientIps {
|
||||
inboundClientIps,err := GetInboundClientIps(clientEmail)
|
||||
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
||||
sort.Sort(sort.StringSlice(ips))
|
||||
if(err != nil){
|
||||
addInboundClientIps(clientEmail,ips)
|
||||
if err != nil {
|
||||
addInboundClientIps(clientEmail, ips)
|
||||
|
||||
}else{
|
||||
updateInboundClientIps(inboundClientIps,clientEmail,ips)
|
||||
} else {
|
||||
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if inbound connection is more than limited ip and drop connection
|
||||
LimitDevice := func() { LimitDevice() }
|
||||
|
||||
stop := schedule(LimitDevice, 1000 *time.Millisecond)
|
||||
stop := schedule(LimitDevice, 1000*time.Millisecond)
|
||||
time.Sleep(10 * time.Second)
|
||||
stop <- true
|
||||
|
||||
|
||||
}
|
||||
func GetAccessLogPath() string {
|
||||
|
||||
config, err := os.ReadFile("bin/config.json")
|
||||
checkError(err)
|
||||
|
||||
config, err := os.ReadFile(xray.GetConfigPath())
|
||||
checkError(err)
|
||||
|
||||
jsonConfig := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||
checkError(err)
|
||||
if(jsonConfig["log"] != nil) {
|
||||
if jsonConfig["log"] != nil {
|
||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
||||
if(jsonLog["access"] != nil) {
|
||||
if jsonLog["access"] != nil {
|
||||
|
||||
accessLogPath := jsonLog["access"].(string)
|
||||
|
||||
@@ -132,7 +131,7 @@ func GetAccessLogPath() string {
|
||||
|
||||
}
|
||||
func checkError(e error) {
|
||||
if e != nil {
|
||||
if e != nil {
|
||||
logger.Warning("client ip job err:", e)
|
||||
}
|
||||
}
|
||||
@@ -182,20 +181,20 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
||||
}
|
||||
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)
|
||||
|
||||
inboundClientIps.ClientEmail = clientEmail
|
||||
inboundClientIps.Ips = string(jsonIps)
|
||||
|
||||
|
||||
// check inbound limitation
|
||||
inbound, err := GetInboundByEmail(clientEmail)
|
||||
checkError(err)
|
||||
|
||||
if inbound.Settings == "" {
|
||||
logger.Debug("wrong data ",inbound)
|
||||
logger.Debug("wrong data ", inbound)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -205,17 +204,17 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
|
||||
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
|
||||
|
||||
limitIp := client.LimitIP
|
||||
|
||||
if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) {
|
||||
|
||||
disAllowedIps = append(disAllowedIps,ips[limitIp:]...)
|
||||
|
||||
if limitIp < len(ips) && limitIp != 0 && inbound.Enable {
|
||||
|
||||
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Debug("disAllowedIps ",disAllowedIps)
|
||||
sort.Sort(sort.StringSlice(disAllowedIps))
|
||||
logger.Debug("disAllowedIps ", disAllowedIps)
|
||||
sort.Sort(sort.StringSlice(disAllowedIps))
|
||||
|
||||
db := database.GetDB()
|
||||
err = db.Save(inboundClientIps).Error
|
||||
@@ -224,13 +223,13 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func DisableInbound(id int) error{
|
||||
func DisableInbound(id int) error {
|
||||
db := database.GetDB()
|
||||
result := db.Model(model.Inbound{}).
|
||||
Where("id = ? and enable = ?", id, true).
|
||||
Update("enable", false)
|
||||
err := result.Error
|
||||
logger.Warning("disable inbound with id:",id)
|
||||
logger.Warning("disable inbound with id:", id)
|
||||
|
||||
if err == nil {
|
||||
job.xrayService.SetToNeedRestart()
|
||||
@@ -242,7 +241,7 @@ func DisableInbound(id int) error{
|
||||
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -250,45 +249,44 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||
}
|
||||
|
||||
func LimitDevice() {
|
||||
var destIp, destPort, srcIp, srcPort string
|
||||
|
||||
localIp,err := LocalIP()
|
||||
checkError(err)
|
||||
var destIp, destPort, srcIp, srcPort string
|
||||
|
||||
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()
|
||||
if len(c.Status().Stdout) > 0 {
|
||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
||||
c := cmd.NewCmd("bash", "-c", "ss --tcp | grep -E '"+IPsToRegex(localIp)+"'| awk '{if($1==\"ESTAB\") print $4,$5;}'", "| sort | uniq -c | sort -nr | head")
|
||||
|
||||
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 {
|
||||
continue // Skip this row if it doesn't have at least two elements
|
||||
}
|
||||
data := strings.Split(row, " ")
|
||||
|
||||
destIp = string(ipRegx.FindString(data[0]))
|
||||
destPort = portRegx.FindString(data[0])
|
||||
destPort = strings.Replace(destPort,":","",-1)
|
||||
if len(data) < 2 {
|
||||
continue // Skip this row if it doesn't have at least two elements
|
||||
}
|
||||
|
||||
srcIp = string(ipRegx.FindString(data[1]))
|
||||
srcPort = portRegx.FindString(data[1])
|
||||
srcPort = strings.Replace(srcPort,":","",-1)
|
||||
destIp = string(ipRegx.FindString(data[0]))
|
||||
destPort = portRegx.FindString(data[0])
|
||||
destPort = strings.Replace(destPort, ":", "", -1)
|
||||
|
||||
if contains(disAllowedIps,srcIp){
|
||||
dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort)
|
||||
dropCmd.Start()
|
||||
srcIp = string(ipRegx.FindString(data[1]))
|
||||
srcPort = portRegx.FindString(data[1])
|
||||
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) {
|
||||
// get machine ips
|
||||
|
||||
@@ -312,24 +310,23 @@ func LocalIP() ([]string, error) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
func IPsToRegex(ips []string) (string){
|
||||
func IPsToRegex(ips []string) string {
|
||||
|
||||
regx := ""
|
||||
for _, ip := range ips {
|
||||
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
||||
|
||||
}
|
||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
||||
|
||||
return regx
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "warning",
|
||||
"access": "./access.log"
|
||||
"access": "./access.log",
|
||||
"error": "./error.log"
|
||||
},
|
||||
"api": {
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
],
|
||||
"tag": "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"
|
||||
},
|
||||
"tag": "api"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@@ -28,16 +25,16 @@
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {},
|
||||
"tag": "blocked"
|
||||
"settings": {}
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
"statsUserDownlink": true,
|
||||
"statsUserUplink": true
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
@@ -49,27 +46,21 @@
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"inboundTag": [
|
||||
"api"
|
||||
],
|
||||
"outboundTag": "api",
|
||||
"type": "field"
|
||||
"type": "field",
|
||||
"inboundTag": ["api"],
|
||||
"outboundTag": "api"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
],
|
||||
"type": "field"
|
||||
"ip": ["geoip:private"]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
],
|
||||
"type": "field"
|
||||
"protocol": ["bittorrent"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
||||
func (s *InboundService) getAllEmails() ([]string, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
||||
if ignoreId > 0 {
|
||||
db = db.Where("id != ?", ignoreId)
|
||||
}
|
||||
db = db.Find(&inbounds)
|
||||
if db.Error != nil {
|
||||
return "", db.Error
|
||||
}
|
||||
var emails []string
|
||||
err := db.Raw(`
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
`).Scan(&emails).Error
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.getClients(inbound)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return emails, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) contains(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
if emails[client.Email] {
|
||||
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
|
||||
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
|
||||
}
|
||||
if s.contains(allEmails, client.Email) {
|
||||
return client.Email, nil
|
||||
}
|
||||
emails = append(emails, client.Email)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
@@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
emails := make(map[string]bool)
|
||||
allEmails, err := s.getAllEmails()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var emails []string
|
||||
for _, client := range clients {
|
||||
if client.Email != "" {
|
||||
if emails[client.Email] {
|
||||
if s.contains(emails, client.Email) {
|
||||
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) {
|
||||
@@ -215,14 +239,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
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)
|
||||
if err != nil {
|
||||
return inbound, err
|
||||
@@ -245,39 +261,53 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
return inbound, db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
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 != "" {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
|
||||
clients, err := s.getClients(inbound)
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
var oldSettings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound.Settings = inbound.Settings
|
||||
oldInbound.Settings = string(newSettings)
|
||||
|
||||
if len(clients[len(clients)-1].Email) > 0 {
|
||||
s.AddClientStat(inbound.Id, &clients[len(clients)-1])
|
||||
}
|
||||
for i := len(oldClients); i < len(clients); i++ {
|
||||
if len(clients[i].Email) > 0 {
|
||||
s.AddClientStat(inbound.Id, &clients[i])
|
||||
for _, client := range clients {
|
||||
if len(client.Email) > 0 {
|
||||
s.AddClientStat(data.Id, &client)
|
||||
}
|
||||
}
|
||||
db := database.GetDB()
|
||||
@@ -309,21 +339,21 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
|
||||
return db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error {
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existEmail != "" {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
|
||||
clients, err := s.getClients(inbound)
|
||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -333,22 +363,47 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
||||
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()
|
||||
|
||||
if len(clients[index].Email) > 0 {
|
||||
if len(clients[0].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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
s.AddClientStat(inbound.Id, &clients[index])
|
||||
s.AddClientStat(data.Id, &clients[0])
|
||||
}
|
||||
} else {
|
||||
err = s.DelClientStat(db, oldClients[index].Email)
|
||||
@@ -433,7 +488,7 @@ func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_tr
|
||||
}
|
||||
}()
|
||||
|
||||
for traffic_index, traffic := range traffics {
|
||||
for _, traffic := range traffics {
|
||||
inbound := &model.Inbound{}
|
||||
client_traffic := &xray.ClientTraffic{}
|
||||
err := db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(client_traffic).Error
|
||||
@@ -453,25 +508,34 @@ func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_tr
|
||||
}
|
||||
continue
|
||||
}
|
||||
// get settings clients
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
// get clients
|
||||
clients, err := s.getClients(inbound)
|
||||
needUpdate := false
|
||||
for client_index, client := range clients {
|
||||
if traffic.Email == client.Email {
|
||||
if client.ExpiryTime < 0 {
|
||||
clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime
|
||||
needUpdate = true
|
||||
if err == nil {
|
||||
for client_index, client := range clients {
|
||||
if traffic.Email == client.Email {
|
||||
if client.ExpiryTime < 0 {
|
||||
clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime
|
||||
needUpdate = true
|
||||
}
|
||||
client_traffic.ExpiryTime = client.ExpiryTime
|
||||
client_traffic.Total = client.TotalGB
|
||||
break
|
||||
}
|
||||
client_traffic.ExpiryTime = client.ExpiryTime
|
||||
client_traffic.Total = client.TotalGB
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if needUpdate {
|
||||
settings["clients"] = clients
|
||||
settings := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
|
||||
// Convert clients to []interface to update clients in settings
|
||||
var clientsInterface []interface{}
|
||||
for _, c := range clients {
|
||||
clientsInterface = append(clientsInterface, interface{}(c))
|
||||
}
|
||||
|
||||
settings["clients"] = clientsInterface
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -483,9 +547,9 @@ func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_tr
|
||||
}
|
||||
}
|
||||
|
||||
traffics[traffic_index] = client_traffic
|
||||
full_traffics = append(full_traffics, client_traffic)
|
||||
}
|
||||
return traffics, nil
|
||||
return full_traffics, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||
@@ -508,6 +572,17 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||
count := result.RowsAffected
|
||||
return count, err
|
||||
}
|
||||
func (s *InboundService) RemoveOrphanedTraffics() {
|
||||
db := database.GetDB()
|
||||
db.Exec(`
|
||||
DELETE FROM client_traffics
|
||||
WHERE email NOT IN (
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
)
|
||||
`)
|
||||
}
|
||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
||||
db := database.GetDB()
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/sys"
|
||||
"x-ui/xray"
|
||||
@@ -349,3 +350,69 @@ func (s *ServerService) GetLogs(count string) ([]string, error) {
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
// Open the file for reading
|
||||
file, err := os.Open(xray.GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file contents
|
||||
fileContents, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var jsonData interface{}
|
||||
err = json.Unmarshal(fileContents, &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetDb() ([]byte, error) {
|
||||
// Open the file for reading
|
||||
file, err := os.Open(config.GetDBPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read the file contents
|
||||
fileContents, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -42,6 +43,15 @@ var defaultValueMap = map[string]string{
|
||||
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) {
|
||||
db := database.GetDB()
|
||||
settings := make([]*model.Setting, 0)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"gorm.io/gorm"
|
||||
@@ -18,12 +19,15 @@ type SubService struct {
|
||||
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
|
||||
var result []string
|
||||
var header string
|
||||
var traffic xray.ClientTraffic
|
||||
var clientTraffics []xray.ClientTraffic
|
||||
inbounds, err := s.getInboundsBySubId(subId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.getClients(inbound)
|
||||
@@ -37,22 +41,60 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
||||
if client.SubID == subId {
|
||||
link := s.getLink(inbound, client.Email)
|
||||
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) {
|
||||
db := database.GetDB()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
switch inbound.Protocol {
|
||||
case "vmess":
|
||||
@@ -269,6 +311,39 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
}
|
||||
}
|
||||
|
||||
if security == "reality" {
|
||||
params["security"] = "reality"
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]interface{})
|
||||
params["sni"], _ = sNames[0].(string)
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]interface{})
|
||||
params["sid"], _ = shortIds[0].(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
}
|
||||
}
|
||||
|
||||
if security == "xtls" {
|
||||
params["security"] = "xtls"
|
||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||
@@ -413,6 +488,39 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
}
|
||||
}
|
||||
|
||||
if security == "reality" {
|
||||
params["security"] = "reality"
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]interface{})
|
||||
params["sni"], _ = sNames[0].(string)
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]interface{})
|
||||
params["sid"], _ = shortIds[0].(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
}
|
||||
}
|
||||
|
||||
if security == "xtls" {
|
||||
params["security"] = "xtls"
|
||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||
|
||||
@@ -167,7 +167,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
||||
case "client_traffic":
|
||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||
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":
|
||||
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>")
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
|
||||
s.inboundService.DisableInvalidClients()
|
||||
s.inboundService.RemoveOrphanedTraffics()
|
||||
|
||||
inbounds, err := s.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"domainName" = "Domain name"
|
||||
"additional" = "Alter"
|
||||
"monitor" = "Listen IP"
|
||||
"certificate" = "Certificat"
|
||||
"certificate" = "Certificate"
|
||||
"fail" = "Fail"
|
||||
"success" = " Success"
|
||||
"getVersion" = "Get version"
|
||||
@@ -106,8 +106,8 @@
|
||||
"expireDate" = "Expire date"
|
||||
"resetTraffic" = "Reset traffic"
|
||||
"addInbound" = "Add Inbound"
|
||||
"addTo" = "Add To"
|
||||
"revise" = "Revise"
|
||||
"addTo" = "Create"
|
||||
"revise" = "Update"
|
||||
"modifyInbound" = "Modify InBound"
|
||||
"deleteInbound" = "Delete Inbound"
|
||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
||||
@@ -152,6 +152,8 @@
|
||||
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
||||
"IPLimitlogclear" = "Clear The Log"
|
||||
"setDefaultCert" = "Set cert from panel"
|
||||
"XTLSdec" = "Xray core needs to be 1.7.5 and below"
|
||||
"Realitydec" = "Xray core needs to be 1.8.0 and above"
|
||||
|
||||
[pages.client]
|
||||
"add" = "Add client"
|
||||
@@ -194,6 +196,8 @@
|
||||
"save" = "Save"
|
||||
"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"
|
||||
"actions" = "Actions"
|
||||
"resetDefaultConfig" = "Reset to default config"
|
||||
"panelConfig" = "Panel Configuration"
|
||||
"userSetting" = "User Setting"
|
||||
"xrayConfiguration" = "Xray Configuration"
|
||||
@@ -213,24 +217,59 @@
|
||||
"currentPassword" = "Current Password"
|
||||
"newUsername" = "New Username"
|
||||
"newPassword" = "New Password"
|
||||
"advancedTemplate" = "Advanced template parts"
|
||||
"completeTemplate" = "Complete template of Xray configuration"
|
||||
"basicTemplate" = "Basic Template"
|
||||
"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"
|
||||
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
||||
"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"
|
||||
"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"
|
||||
"xrayConfigIRIpDesc" = "Change the configuration temlate to avoid connecting with Iran IP ranges, restart the panel to take effect"
|
||||
"xrayConfigIRdomain" = "Ban IR domains to connect"
|
||||
"xrayConfigIRdomainDesc" = "Change the configuration temlate to avoid connecting with IR domains, 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 Iran Domains to connect"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
||||
"telegramToken" = "Telegram Token"
|
||||
@@ -255,4 +294,4 @@
|
||||
"getSetting" = "Get setting"
|
||||
"modifyUser" = "Modify user"
|
||||
"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"
|
||||
|
||||
@@ -150,6 +150,8 @@
|
||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||
"XTLSdec" = "هسته Xray باید 1.7.5 و کمتر باشد"
|
||||
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||
|
||||
[pages.client]
|
||||
"add" = "کاربر جدید"
|
||||
@@ -192,6 +194,8 @@
|
||||
"save" = "ذخیره"
|
||||
"restartPanel" = "ریستارت پنل"
|
||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||
"actions" = "عملیات ها"
|
||||
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||
"panelConfig" = "تنظیمات پنل"
|
||||
"userSetting" = "تنظیمات مدیر"
|
||||
"xrayConfiguration" = "تنظیمات Xray"
|
||||
@@ -211,18 +215,53 @@
|
||||
"currentPassword" = "رمز عبور فعلی"
|
||||
"newUsername" = "نام کاربری جدید"
|
||||
"newPassword" = "رمز عبور جدید"
|
||||
"basicTemplate" = "بخش پایه"
|
||||
"advancedTemplate" = "بخش های پیشرفته الگو"
|
||||
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
|
||||
"generalConfigs" = "تنظیمات عمومی"
|
||||
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
|
||||
"countryConfigs" = "تنظیمات برای کشورها"
|
||||
"countryConfigsDesc" = "این گزینه از اتصال کاربران به دامنه های کشوری خاص جلوگیری می کند."
|
||||
"ipv4Configs" = "تنظیمات برای IPv4"
|
||||
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن 4 به دامنه های هدف هدایت می شود."
|
||||
"warpConfigs" = "تنظیمات برای WARP"
|
||||
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند."
|
||||
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
||||
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
||||
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر"
|
||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigIRIp" = "جلوگیری از اتصال آی پی های ایران"
|
||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigIRdomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||
"xrayConfigIRdomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
||||
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
||||
"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" = "تنظیمات ورودی"
|
||||
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
||||
@@ -253,4 +292,4 @@
|
||||
"getSetting" = "دریافت تنظیمات"
|
||||
"modifyUser" = "ویرایش کاربر"
|
||||
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
|
||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
||||
|
||||
@@ -150,6 +150,8 @@
|
||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||
"IPLimitlogclear" = "清除日志"
|
||||
"setDefaultCert" = "从面板设置证书"
|
||||
"XTLSdec" = "Xray核心需要1.7.5及以下版本"
|
||||
"Realitydec" = "Xray核心需要1.8.0及以上版本"
|
||||
|
||||
[pages.client]
|
||||
"add" = "添加客户端"
|
||||
@@ -192,6 +194,8 @@
|
||||
"save" = "保存配置"
|
||||
"restartPanel" = "重启面板"
|
||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||
"actions" = "动作"
|
||||
"resetDefaultConfig" = "重置为默认配置"
|
||||
"panelConfig" = "面板配置"
|
||||
"userSetting" = "用户设置"
|
||||
"xrayConfiguration" = "xray 相关设置"
|
||||
@@ -211,18 +215,53 @@
|
||||
"currentPassword" = "原密码"
|
||||
"newUsername" = "新用户名"
|
||||
"newPassword" = "新密码"
|
||||
"basicTemplate" = "基本模板"
|
||||
"advancedTemplate" = "高级模板部件"
|
||||
"completeTemplate" = "Xray 配置的完整模板"
|
||||
"generalConfigs" = "一般配置"
|
||||
"generalConfigsDesc" = "此选项将阻止用户连接到特定协议和网站。"
|
||||
"countryConfigs" = "国家配置"
|
||||
"countryConfigsDesc" = "此选项将阻止用户连接到特定国家/地区的域。"
|
||||
"ipv4Configs" = "IPv4 配置"
|
||||
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域。"
|
||||
"warpConfigs" = "WARP 配置"
|
||||
"warpConfigsDesc" = "警告:在使用此选项之前,请按照面板 GitHub 上的步骤在您的服务器上以 socks5 代理模式安装 WARP。 WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
||||
"xrayConfigTemplate" = "xray 配置模板"
|
||||
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件,重新启动面板生成效率"
|
||||
"xrayConfigTorrent" = "禁止使用 bittorrent"
|
||||
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
|
||||
"xrayConfigPrivateIp" = "禁止私人 ip 范围连接"
|
||||
"xrayConfigPrivateIp" = "禁止私人 IP 范围连接"
|
||||
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
|
||||
"xrayConfigAds" = "屏蔽广告"
|
||||
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
|
||||
"xrayConfigPorn" = "禁止色情网站连接"
|
||||
"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
|
||||
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
||||
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP范围,重启面板生效"
|
||||
"xrayConfigIRdomain" = "禁止伊朗域连接"
|
||||
"xrayConfigIRdomainDesc" = "修改配置模板避免连接伊朗域名,重启面板生效"
|
||||
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
|
||||
"xrayConfigIRDomain" = "禁止伊朗域连接"
|
||||
"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" = "将OpenAI(ChatGPT)路由添加到WARP,重启面板生效"
|
||||
"xrayConfigNetflixWARP" = "将 Netflix 路由到 WARP"
|
||||
"xrayConfigNetflixWARPDesc" = "为Netflix添加路由到WARP,重启面板生效"
|
||||
"xrayConfigSpotifyWARP" = "将 Spotify 路由到 WARP"
|
||||
"xrayConfigSpotifyWARPDesc" = "为Spotify添加路由到WARP,重启面板生效"
|
||||
"xrayConfigIRWARP" = "将伊朗域名路由到 WARP"
|
||||
"xrayConfigIRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
|
||||
"xrayConfigInbounds" = "入站配置"
|
||||
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
|
||||
"xrayConfigOutbounds" = "出站配置"
|
||||
@@ -253,4 +292,4 @@
|
||||
"getSetting" = "获取设置"
|
||||
"modifyUser" = "修改用户"
|
||||
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||
|
||||
213
x-ui.sh
@@ -17,6 +17,7 @@ function LOGE() {
|
||||
function LOGI() {
|
||||
echo -e "${green}[INF] $* ${plain}"
|
||||
}
|
||||
|
||||
# check root
|
||||
[[ $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"
|
||||
|
||||
|
||||
os_version=""
|
||||
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||
|
||||
@@ -44,21 +44,18 @@ if [[ "${release}" == "centos" ]]; then
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; 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
|
||||
|
||||
elif [[ "${release}" == "fedora" ]]; 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
|
||||
|
||||
elif [[ "${release}" == "debian" ]]; then
|
||||
if [[ ${os_version} -lt 10 ]]; then
|
||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
confirm() {
|
||||
if [[ $# > 1 ]]; then
|
||||
echo && read -p "$1 [Default$2]: " temp
|
||||
@@ -133,7 +130,7 @@ uninstall() {
|
||||
rm /usr/local/x-ui/ -rf
|
||||
|
||||
echo ""
|
||||
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 -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 ""
|
||||
|
||||
if [[ $# == 0 ]]; then
|
||||
@@ -150,12 +147,12 @@ reset_user() {
|
||||
return 0
|
||||
fi
|
||||
/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
|
||||
}
|
||||
|
||||
reset_config() {
|
||||
confirm "Are you sure you want to reset all panel settings,Account data will not be lost,Username 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
|
||||
show_menu
|
||||
@@ -163,14 +160,14 @@ reset_config() {
|
||||
return 0
|
||||
fi
|
||||
/usr/local/x-ui/x-ui setting -reset
|
||||
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"
|
||||
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
|
||||
}
|
||||
|
||||
check_config() {
|
||||
info=$(/usr/local/x-ui/x-ui setting -show true)
|
||||
if [[ $? != 0 ]]; then
|
||||
LOGE "get current settings error,please check logs"
|
||||
LOGE "get current settings error, please check logs"
|
||||
show_menu
|
||||
fi
|
||||
LOGI "${info}"
|
||||
@@ -183,7 +180,7 @@ set_port() {
|
||||
before_show_menu
|
||||
else
|
||||
/usr/local/x-ui/x-ui setting -port ${port}
|
||||
echo -e "The port is set,Please restart the panel now,and 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
|
||||
fi
|
||||
}
|
||||
@@ -192,7 +189,7 @@ start() {
|
||||
check_status
|
||||
if [[ $? == 0 ]]; then
|
||||
echo ""
|
||||
LOGI "Panel is running,No need to start again,If 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
|
||||
systemctl start x-ui
|
||||
sleep 2
|
||||
@@ -200,7 +197,7 @@ start() {
|
||||
if [[ $? == 0 ]]; then
|
||||
LOGI "x-ui Started Successfully"
|
||||
else
|
||||
LOGE "panel Failed to start,Probably because it takes longer than two seconds to start,Please 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
|
||||
|
||||
@@ -213,7 +210,7 @@ stop() {
|
||||
check_status
|
||||
if [[ $? == 1 ]]; then
|
||||
echo ""
|
||||
LOGI "Panel stopped,No need to stop again!"
|
||||
LOGI "Panel stopped, No need to stop again!"
|
||||
else
|
||||
systemctl stop x-ui
|
||||
sleep 2
|
||||
@@ -221,7 +218,7 @@ stop() {
|
||||
if [[ $? == 1 ]]; then
|
||||
LOGI "x-ui and xray stopped successfully"
|
||||
else
|
||||
LOGE "Panel stop failed,Probably because the stop time exceeds two seconds,Please 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
|
||||
|
||||
@@ -237,7 +234,7 @@ restart() {
|
||||
if [[ $? == 0 ]]; then
|
||||
LOGI "x-ui and xray Restarted successfully"
|
||||
else
|
||||
LOGE "Panel restart failed,Probably because it takes longer than two seconds to start,Please 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
|
||||
if [[ $# == 0 ]]; then
|
||||
before_show_menu
|
||||
@@ -285,51 +282,49 @@ show_log() {
|
||||
}
|
||||
|
||||
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
|
||||
echo -e "${green}BBR is already enabled!${plain}"
|
||||
exit 0
|
||||
fi
|
||||
# Check the OS and install necessary packages
|
||||
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
|
||||
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
|
||||
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
|
||||
if [[ "$(cat /etc/os-release | grep -E '^ID=' | awk -F '=' '{print $2}')" == "ubuntu" ]]; then
|
||||
sudo apt-get update && sudo apt-get install -yqq --no-install-recommends ca-certificates
|
||||
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
|
||||
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
|
||||
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
|
||||
|
||||
# Enable BBR
|
||||
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
|
||||
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
|
||||
|
||||
# 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
|
||||
# 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
|
||||
}
|
||||
|
||||
update_shell() {
|
||||
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
|
||||
if [[ $? != 0 ]]; then
|
||||
echo ""
|
||||
LOGE "Failed to download script,Please check whether the machine can connect Github"
|
||||
LOGE "Failed to download script, Please check whether the machine can connect Github"
|
||||
before_show_menu
|
||||
else
|
||||
chmod +x /usr/bin/x-ui
|
||||
LOGI "Upgrade script succeeded,Please rerun the script" && exit 0
|
||||
LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -359,7 +354,7 @@ check_uninstall() {
|
||||
check_status
|
||||
if [[ $? != 2 ]]; then
|
||||
echo ""
|
||||
LOGE "Panel installed,Please do not reinstall"
|
||||
LOGE "Panel installed, Please do not reinstall"
|
||||
if [[ $# == 0 ]]; then
|
||||
before_show_menu
|
||||
fi
|
||||
@@ -455,61 +450,76 @@ ssl_cert_issue() {
|
||||
}
|
||||
|
||||
open_ports() {
|
||||
|
||||
# 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
|
||||
if ! command -v ufw &> /dev/null
|
||||
then
|
||||
echo "ufw firewall is not installed. Installing now..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ufw
|
||||
else
|
||||
sudo ufw allow "$port"
|
||||
echo "ufw firewall is already installed"
|
||||
fi
|
||||
done
|
||||
|
||||
# Confirm that the ports are open
|
||||
sudo ufw status | grep $ports
|
||||
# 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
|
||||
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
|
||||
cd /usr/local/x-ui/bin
|
||||
cd ${binFolder}
|
||||
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/geosite.dat
|
||||
wget -N https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
||||
systemctl start x-ui
|
||||
echo -e "${green}Geosite and Geoip have been updated successfully!${plain}"
|
||||
before_show_menu
|
||||
echo -e "${green}Geosite.dat + Geoip.dat + Iran.dat have been updated successfully in bin folder '${binfolder}'!${plain}"
|
||||
before_show_menu
|
||||
}
|
||||
|
||||
install_acme() {
|
||||
@@ -706,10 +716,11 @@ ssl_cert_issue_by_cloudflare() {
|
||||
show_menu
|
||||
fi
|
||||
}
|
||||
|
||||
google_recaptcha() {
|
||||
curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh
|
||||
echo ""
|
||||
before_show_menu
|
||||
curl -O https://raw.githubusercontent.com/jinwyp/one_click_script/master/install_kernel.sh && chmod +x ./install_kernel.sh && ./install_kernel.sh
|
||||
echo ""
|
||||
before_show_menu
|
||||
}
|
||||
|
||||
run_speedtest() {
|
||||
@@ -718,19 +729,19 @@ run_speedtest() {
|
||||
# If not installed, install it
|
||||
if command -v dnf &> /dev/null; then
|
||||
sudo dnf install -y curl
|
||||
curl -s https://install.speedtest.net/app/cli/install.rpm.sh | sudo bash
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh | sudo bash
|
||||
sudo dnf install -y speedtest
|
||||
elif command -v yum &> /dev/null; then
|
||||
sudo yum install -y curl
|
||||
curl -s https://install.speedtest.net/app/cli/install.rpm.sh | sudo bash
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh | sudo bash
|
||||
sudo yum install -y speedtest
|
||||
elif command -v apt-get &> /dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y curl
|
||||
curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash
|
||||
sudo apt-get install -y speedtest
|
||||
elif command -v apt &> /dev/null; then
|
||||
sudo apt update && sudo apt install -y curl
|
||||
curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash
|
||||
sudo apt install -y speedtest
|
||||
else
|
||||
echo "Error: Package manager not found. You may need to install Speedtest manually."
|
||||
@@ -782,7 +793,7 @@ show_menu() {
|
||||
${green}12.${plain} Check x-ui Logs
|
||||
————————————————
|
||||
${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}16.${plain} Apply for an SSL Certificate
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/config"
|
||||
"x-ui/util/common"
|
||||
|
||||
"github.com/Workiva/go-datastructures/queue"
|
||||
@@ -29,19 +30,23 @@ func GetBinaryName() string {
|
||||
}
|
||||
|
||||
func GetBinaryPath() string {
|
||||
return "bin/" + GetBinaryName()
|
||||
return config.GetBinFolderPath() + "/" + GetBinaryName()
|
||||
}
|
||||
|
||||
func GetConfigPath() string {
|
||||
return "bin/config.json"
|
||||
return config.GetBinFolderPath() + "/config.json"
|
||||
}
|
||||
|
||||
func GetGeositePath() string {
|
||||
return "bin/geosite.dat"
|
||||
return config.GetBinFolderPath() + "/geosite.dat"
|
||||
}
|
||||
|
||||
func GetGeoipPath() string {
|
||||
return "bin/geoip.dat"
|
||||
return config.GetBinFolderPath() + "/geoip.dat"
|
||||
}
|
||||
|
||||
func GetBlockedIPsPath() string {
|
||||
return config.GetBinFolderPath() + "/blockedIPs"
|
||||
}
|
||||
|
||||
func stopProcess(p *Process) {
|
||||
@@ -162,7 +167,7 @@ func (p *process) Start() (err error) {
|
||||
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
|
||||
|
||||
stdReader, err := cmd.StdoutPipe()
|
||||
|
||||